diff --git a/README.md b/README.md
index 6497cf96e..934b5c812 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,10 @@ This is a simple nestjs module that exposes the [got](https://www.npmjs.com/pack
-
+
+
@@ -142,14 +143,14 @@ The `GotModuleOptions` is an alias for the `got` package's `ExtendOptions` hence
## API Methods
-The module currently only exposes the basic JSON HTTP verbs through the GotService i.e. `get`, `head`, `post`, `put`, `patch` and `delete`.
+The module currently only exposes the basic JSON HTTP verbs, as well as the pagination methods through the `GotService`.
-All these methods support the same argument inputs i.e.:
+For all JSON HTTP verbs - `get`, `head`, `post`, `put`, `patch` and `delete` - which are also the exposed methods, below is the the method signature where `method: string` **MUST** be any of their corresponding verbs.
```ts
// This is just used to explain the methods as this code doesn't exist in the package
import { Observable } from 'rxjs';
-immport { Response, OptionsOfJSONResponseBody } from 'got';
+import { Response, OptionsOfJSONResponseBody } from 'got';
interface GotServiceInterface {
[method: string]: (
@@ -159,13 +160,45 @@ interface GotServiceInterface {
}
```
+For all pagination methods - `each` and `all`, below is the method signature each of them.
+
+```ts
+// This is just used to explain the methods as this code doesn't exist in the package
+import { Observable } from 'rxjs';
+import { Response, OptionsOfJSONResponseBody } from 'got';
+
+interface GotServiceInterface {
+ [method: string]: (
+ url: string | URL,
+ options?: OptionsWithPagination,
+ ) => Observable;
+}
+```
+
+A usage example of would be:
+
+```ts
+@Controller()
+export class ExampleController {
+ constructor(private readonly gotService: GotService) {}
+
+ controllerMethod() {
+ // ...
+ this.gotService.pagination.all(someUrl, withOptions); // Returns Observable
+ // or
+ this.gotService.pagination.each(someUrl, withOptions); // Returns Observable
+ // ...
+ }
+}
+```
+
+For more information of the usage pattern, please check [here](https://www.npmjs.com/package/got#pagination-1)
+
## ToDos
As stated above, this module only support some http verbs, however, the following are still in progress:
-1. Support for pagination
-
-2. Support for a `StreamService` which is supported by the **got** package itself.
+1. Support for a `StreamService` which is supported by the **got** package itself.
## Contributing
diff --git a/lib/abstrace.service.ts b/lib/abstrace.service.ts
new file mode 100644
index 000000000..1e45d1f6f
--- /dev/null
+++ b/lib/abstrace.service.ts
@@ -0,0 +1,9 @@
+import { Got, InstanceDefaults } from 'got';
+
+export abstract class AbstractService {
+ readonly defaults: InstanceDefaults;
+
+ constructor(protected readonly got: Got) {
+ this.defaults = this.got.defaults;
+ }
+}
diff --git a/lib/addons/index.ts b/lib/addons/index.ts
new file mode 100644
index 000000000..928c8e546
--- /dev/null
+++ b/lib/addons/index.ts
@@ -0,0 +1 @@
+export * from './rxjs';
diff --git a/lib/addons/rxjs/index.ts b/lib/addons/rxjs/index.ts
new file mode 100644
index 000000000..eab154b40
--- /dev/null
+++ b/lib/addons/rxjs/index.ts
@@ -0,0 +1 @@
+export * from './scheduleAsyncIterable';
diff --git a/lib/addons/rxjs/scheduleAsyncIterable.spec.ts b/lib/addons/rxjs/scheduleAsyncIterable.spec.ts
new file mode 100644
index 000000000..ea830f6e5
--- /dev/null
+++ b/lib/addons/rxjs/scheduleAsyncIterable.spec.ts
@@ -0,0 +1,27 @@
+import { asapScheduler } from 'rxjs';
+import { take } from 'rxjs/operators';
+
+import { scheduledAsyncIterable } from './scheduleAsyncIterable';
+
+describe('scheduleAsyncIterable()', () => {
+ it('', () => {
+ const iterator = async function* () {
+ let i = 1;
+ while (true) {
+ yield i;
+ i += 1;
+ }
+ };
+
+ const iterable = iterator();
+ let count = 1;
+
+ scheduledAsyncIterable(iterable, asapScheduler)
+ .pipe(take(5))
+ .subscribe({
+ next(v) {
+ expect(v).toEqual(count++);
+ },
+ });
+ });
+});
diff --git a/lib/addons/rxjs/scheduleAsyncIterable.ts b/lib/addons/rxjs/scheduleAsyncIterable.ts
new file mode 100644
index 000000000..4c5a981c8
--- /dev/null
+++ b/lib/addons/rxjs/scheduleAsyncIterable.ts
@@ -0,0 +1,28 @@
+import { Observable, Subscriber, Subscription, SchedulerLike } from 'rxjs';
+
+export const scheduledAsyncIterable = (
+ input: AsyncIterable | AsyncGenerator,
+ scheduler: SchedulerLike,
+): Observable => {
+ return new Observable((subscriber: Subscriber) => {
+ const subscription = new Subscription();
+ subscription.add(
+ scheduler.schedule(() => {
+ const iterator = input[Symbol.asyncIterator]();
+ subscription.add(
+ scheduler.schedule(function () {
+ iterator.next().then((result: IteratorResult) => {
+ if (result.done) {
+ subscriber.complete();
+ } else {
+ subscriber.next(result.value);
+ this.schedule();
+ }
+ });
+ }),
+ );
+ }),
+ );
+ return subscription;
+ });
+};
diff --git a/lib/got.module.ts b/lib/got.module.ts
index 54cf17c7b..283ab1df1 100644
--- a/lib/got.module.ts
+++ b/lib/got.module.ts
@@ -7,10 +7,11 @@ import {
GotModuleAsyncOptions,
GotModuleOptionsFactory,
} from './got.interface';
+import { PaginationService } from './paginate.service';
import { GOT_INSTANCE, GOT_OPTIONS } from './got.constant';
@Module({
- providers: [GotService],
+ providers: [GotService, PaginationService],
exports: [GotService],
})
export class GotModule {
diff --git a/lib/got.service.spec.ts b/lib/got.service.spec.ts
index c0b8b4c45..8525dd363 100644
--- a/lib/got.service.spec.ts
+++ b/lib/got.service.spec.ts
@@ -1,9 +1,10 @@
import * as faker from 'faker';
-import { Got, HTTPError, Response } from 'got/dist/source';
+import { Got, HTTPError, Response } from 'got';
import { Test, TestingModule } from '@nestjs/testing';
import { GotService } from './got.service';
import { GOT_INSTANCE } from './got.constant';
+import { PaginationService } from './paginate.service';
describe('GotService', () => {
let service: GotService;
@@ -22,6 +23,10 @@ describe('GotService', () => {
provide: GOT_INSTANCE,
useValue: gotInstance,
},
+ {
+ provide: PaginationService,
+ useValue: {},
+ },
],
}).compile();
diff --git a/lib/got.service.ts b/lib/got.service.ts
index 729f979b1..c66d20016 100644
--- a/lib/got.service.ts
+++ b/lib/got.service.ts
@@ -1,7 +1,6 @@
import {
Got,
Response,
- InstanceDefaults,
CancelableRequest,
OptionsOfJSONResponseBody,
} from 'got';
@@ -9,14 +8,18 @@ import { Observable, Subscriber } from 'rxjs';
import { Inject, Injectable } from '@nestjs/common';
import { GOT_INSTANCE } from './got.constant';
+import { AbstractService } from './abstrace.service';
+import { PaginationService } from './paginate.service';
@Injectable()
-export class GotService {
- readonly defaults: InstanceDefaults;
- private _request!: CancelableRequest;
+export class GotService extends AbstractService {
+ protected _request!: CancelableRequest>;
- constructor(@Inject(GOT_INSTANCE) private readonly got: Got) {
- this.defaults = this.got.defaults;
+ constructor(
+ @Inject(GOT_INSTANCE) got: Got,
+ readonly pagination: PaginationService,
+ ) {
+ super(got);
}
head | []>(
@@ -70,33 +73,35 @@ export class GotService {
url: string | URL,
options?: OptionsOfJSONResponseBody,
): Observable> {
- this._request = this.got[method](url, {
+ this._request = this.got[method](url, {
...options,
responseType: 'json',
...this.defaults,
});
- return new Observable((subscriber: Subscriber) => {
- this._request
- .then(response => subscriber.next(response))
- .catch(
- (
- error: Pick<
- Got,
- | 'ReadError'
- | 'HTTPError'
- | 'ParseError'
- | 'CacheError'
- | 'UploadError'
- | 'CancelError'
- | 'RequestError'
- | 'TimeoutError'
- | 'MaxRedirectsError'
- | 'UnsupportedProtocolError'
- >,
- ) => subscriber.error(error),
- )
- .finally(() => subscriber.complete());
- });
+ return new Observable>(
+ (subscriber: Subscriber>) => {
+ this._request
+ .then((response: Response) => subscriber.next(response))
+ .catch(
+ (
+ error: Pick<
+ Got,
+ | 'ReadError'
+ | 'HTTPError'
+ | 'ParseError'
+ | 'CacheError'
+ | 'UploadError'
+ | 'CancelError'
+ | 'RequestError'
+ | 'TimeoutError'
+ | 'MaxRedirectsError'
+ | 'UnsupportedProtocolError'
+ >,
+ ) => subscriber.error(error),
+ )
+ .finally(() => subscriber.complete());
+ },
+ );
}
}
diff --git a/lib/paginate.service.spec.ts b/lib/paginate.service.spec.ts
new file mode 100644
index 000000000..6c55f02cb
--- /dev/null
+++ b/lib/paginate.service.spec.ts
@@ -0,0 +1,81 @@
+import * as faker from 'faker';
+import { Got, HTTPError, Response } from 'got';
+import { Test, TestingModule } from '@nestjs/testing';
+
+import { GotService } from './got.service';
+import { GOT_INSTANCE } from './got.constant';
+import { PaginationService } from './paginate.service';
+
+describe('GotService', () => {
+ let service: PaginationService;
+ const gotInstance: Partial = {
+ defaults: {
+ options: jest.fn(),
+ } as any,
+ },
+ exemptedKeys = ['makeObservable', 'defaults', 'constructor'];
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ PaginationService,
+ {
+ provide: GOT_INSTANCE,
+ useValue: gotInstance,
+ },
+ {
+ provide: GotService,
+ useValue: {},
+ },
+ ],
+ }).compile();
+
+ service = module.get(PaginationService);
+ });
+
+ it('should be defined', () => {
+ expect(service).toBeDefined();
+ });
+
+ const methods = Object.getOwnPropertyNames(
+ PaginationService.prototype,
+ ).filter(key => !~exemptedKeys.indexOf(key));
+
+ methods.forEach((key, index) => {
+ it(`${key}()`, complete => {
+ const result: Partial = { body: {} };
+
+ gotInstance[key] = jest.fn().mockResolvedValueOnce(result);
+
+ service[key](faker.internet.url()).subscribe({
+ next(response) {
+ expect(response).toBe(result);
+ },
+ complete,
+ });
+ });
+
+ if (methods.length - 2 === index) {
+ it('check that defaults is set', () =>
+ expect('options' in service.defaults).toBe(true));
+
+ it('should get request', () => {
+ service[key](faker.internet.url());
+ });
+
+ it('should check error reporting', () => {
+ const result: any = { body: {}, statusCode: 400 };
+
+ gotInstance[key] = jest
+ .fn()
+ .mockRejectedValueOnce(new HTTPError(result));
+
+ service[key](faker.internet.url()).subscribe({
+ error(error) {
+ expect(error).toBeInstanceOf(HTTPError);
+ },
+ });
+ });
+ }
+ });
+});
diff --git a/lib/paginate.service.ts b/lib/paginate.service.ts
new file mode 100644
index 000000000..49d157f2e
--- /dev/null
+++ b/lib/paginate.service.ts
@@ -0,0 +1,62 @@
+// prettier-ignore
+import {
+ Got,
+ OptionsWithPagination
+} from 'got';
+import { Inject, Injectable } from '@nestjs/common';
+import { Observable, asapScheduler, scheduled } from 'rxjs';
+
+import { GOT_INSTANCE } from './got.constant';
+import { scheduledAsyncIterable } from './addons';
+import { AbstractService } from './abstrace.service';
+
+@Injectable()
+export class PaginationService extends AbstractService {
+ constructor(@Inject(GOT_INSTANCE) got: Got) {
+ super(got);
+ }
+
+ each(
+ url: string | URL,
+ options?: OptionsWithPagination,
+ ): Observable {
+ return this.makeObservable('each', url, options);
+ }
+
+ all(
+ url: string | URL,
+ options?: OptionsWithPagination,
+ ): Observable {
+ return this.makeObservable('all', url, options);
+ }
+
+ private makeObservable(
+ method: 'all',
+ url: string | URL,
+ options?: OptionsWithPagination,
+ ): Observable;
+ private makeObservable(
+ method: 'each',
+ url: string | URL,
+ options?: OptionsWithPagination,
+ ): Observable;
+ private makeObservable(
+ method: 'each' | 'all',
+ url: string | URL,
+ options?: OptionsWithPagination,
+ ): Observable {
+ options = { ...options, ...this.defaults };
+
+ if (method === 'all') {
+ return scheduled(
+ this.got.paginate.all(url, options),
+ asapScheduler,
+ );
+ }
+
+ return scheduledAsyncIterable(
+ this.got.paginate.each(url, options),
+ asapScheduler,
+ );
+ }
+}
diff --git a/tests/e2e/module.e2e-spec.ts b/tests/e2e/module.e2e-spec.ts
index f3fad2477..b80e557d1 100644
--- a/tests/e2e/module.e2e-spec.ts
+++ b/tests/e2e/module.e2e-spec.ts
@@ -3,9 +3,12 @@ import * as faker from 'faker';
import { Got, RequestError } from 'got';
import { Test, TestingModule } from '@nestjs/testing';
+import { getMethods } from '../src/utils';
import { AppModule } from '../src/app.module';
import { AppService } from '../src/app.service';
import { GOT_INSTANCE } from '../../lib/got.constant';
+import { PaginateService } from '../src/paginate.service';
+import { HttpStatus } from '@nestjs/common';
describe('GotModule', () => {
let module: TestingModule, gotInstance: Got;
@@ -54,13 +57,8 @@ describe('GotModule', () => {
describe('test features', () => {
describe('GotService', () => {
- let appService: AppService,
- exemptedKeys = [
- 'makeObservable',
- 'request',
- 'defaults',
- 'constructor',
- ];
+ let appService: AppService;
+
beforeEach(async () => {
module = await Test.createTestingModule({
imports: [AppModule.withRegister()],
@@ -71,33 +69,29 @@ describe('GotModule', () => {
appService = module.get(AppService);
});
- const methods = Object.getOwnPropertyNames(
- AppService.prototype,
- ).filter(key => !~exemptedKeys.indexOf(key));
+ const appMethods = getMethods(AppService);
- methods.forEach((key, index) => {
+ appMethods.forEach((key, index) => {
it(`${key}()`, complete => {
const url = faker.internet.url();
const route = faker.internet.domainWord();
const res = { random: 'random' };
- nock(url)[key](`/${route}`).reply(200, res, {
+ nock(url)[key](`/${route}`).reply(HttpStatus.OK, res, {
'Content-Type': 'application/json',
});
appService[key](`${url}/${route}`).subscribe({
next(response) {
expect(response).toEqual(
- expect.objectContaining({
- body: res,
- }),
+ expect.objectContaining({ body: res }),
);
},
complete,
});
});
- if (methods.length - 2 === index) {
+ if (appMethods.length - 2 === index) {
it('should check error reporting', complete => {
const url = faker.internet.url();
const route = faker.internet.domainWord();
@@ -106,7 +100,7 @@ describe('GotModule', () => {
get response() {
return this.message;
},
- code: 400,
+ code: HttpStatus.BAD_REQUEST,
};
nock(url)[key](`/${route}`).replyWithError(res);
@@ -122,6 +116,59 @@ describe('GotModule', () => {
}
});
});
+
+ describe('PaginationService', () => {
+ let paginateService: PaginateService;
+
+ beforeEach(async () => {
+ module = await Test.createTestingModule({
+ imports: [AppModule.withRegister()],
+ providers: [PaginateService],
+ exports: [PaginateService],
+ }).compile();
+
+ paginateService = module.get(
+ PaginateService,
+ );
+ });
+
+ const paginateMethod = getMethods(
+ PaginateService,
+ );
+
+ paginateMethod.forEach(key => {
+ it(`${key}()`, async () => {
+ const url = faker.internet.url();
+ const route = faker.internet.domainWord();
+ const object = {
+ name: `${faker.name.firstName()} ${faker.name.lastName()}`,
+ };
+ const response = [object];
+
+ nock(url)
+ .get(`/${route}`)
+ .reply(HttpStatus.OK, response, {
+ 'Content-Type': 'application/json',
+ });
+
+ if (key === 'all') {
+ paginateService.all(`${url}/${route}`).subscribe({
+ next(res) {
+ expect(res).toEqual(
+ expect.arrayContaining(response),
+ );
+ },
+ });
+ } else {
+ expect(
+ await paginateService
+ .each(`${url}/${route}`)
+ .toPromise(),
+ ).toEqual(expect.objectContaining(object));
+ }
+ });
+ });
+ });
});
});
});
diff --git a/tests/src/app.service.ts b/tests/src/app.service.ts
index 31cf44a91..c95989c9a 100644
--- a/tests/src/app.service.ts
+++ b/tests/src/app.service.ts
@@ -1,8 +1,11 @@
-import * as faker from 'faker';
+// prettier-ignore
+import {
+ OptionsWithPagination,
+ OptionsOfJSONResponseBody,
+} from 'got';
import { Injectable } from '@nestjs/common';
import { GotService } from '../../lib';
-import { OptionsOfJSONResponseBody } from 'got/dist/source';
@Injectable()
export class AppService {
diff --git a/tests/src/paginate.service.ts b/tests/src/paginate.service.ts
new file mode 100644
index 000000000..7f46ff7c6
--- /dev/null
+++ b/tests/src/paginate.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@nestjs/common';
+import { OptionsWithPagination } from 'got';
+
+import { GotService } from '../../lib';
+
+@Injectable()
+export class PaginateService {
+ constructor(private readonly gotService: GotService) {}
+
+ each(url: string, options?: OptionsWithPagination) {
+ return this.gotService.pagination.each(url, options);
+ }
+
+ all(url: string, options?: OptionsWithPagination) {
+ return this.gotService.pagination.all(url, options);
+ }
+}
diff --git a/tests/src/utils/index.ts b/tests/src/utils/index.ts
new file mode 100644
index 000000000..753392d75
--- /dev/null
+++ b/tests/src/utils/index.ts
@@ -0,0 +1,8 @@
+import { Type } from '@nestjs/common';
+
+const exemptedKeys = ['pagination', 'constructor'];
+
+export const getMethods = (cl: Type) =>
+ Object.getOwnPropertyNames(cl.prototype).filter(
+ key => !~exemptedKeys.indexOf(key),
+ );