├── .prettierignore ├── .github ├── CODEOWNERS └── workflows │ ├── publish.yaml │ └── tests.yaml ├── index.d.ts ├── index.ts ├── .eslintignore ├── lib ├── addons │ ├── index.ts │ └── rxjs │ │ ├── index.ts │ │ └── scheduled │ │ ├── index.ts │ │ ├── schedulePromise.spec.ts │ │ ├── scheduled.ts │ │ ├── scheduleAsyncIterable.spec.ts │ │ ├── schedulePromise.ts │ │ └── scheduleAsyncIterable.ts ├── utils │ ├── functionLike.ts │ ├── index.ts │ └── isPromise.ts ├── index.ts ├── got.constant.ts ├── got.interface.ts ├── stream.request.ts ├── paginate.service.ts ├── stream.service.spec.ts ├── stream.service.ts ├── got.module.ts ├── paginate.service.spec.ts ├── got.service.ts └── got.service.spec.ts ├── .vscode ├── settings.json └── launch.json ├── .commitlintrc ├── .husky ├── pre-commit └── commit-msg ├── tests ├── shared │ └── gotConfig.ts ├── src │ ├── utils │ │ └── test.txt │ ├── existing.module.ts │ ├── got-config.service.ts │ ├── paginate.service.ts │ ├── app.service.ts │ ├── stream.service.ts │ └── app.module.ts ├── jest-e2e.json └── e2e │ └── module.e2e-spec.ts ├── .lintstagedrc ├── tsconfig.build.json ├── .prettierrc ├── .prepare ├── .prepare.bat ├── .editorconfig ├── .gitignore ├── .release-it.json ├── tsconfig.json ├── .npmignore ├── .dev-dependencies ├── renovate.json ├── jest.config.json ├── index.js ├── .eslintrc ├── LICENCE ├── package.json ├── CONTRIBUTING.md ├── .circleci └── config.yml └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @toondaey 2 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | tests/** 2 | node_modules 3 | -------------------------------------------------------------------------------- /lib/addons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rxjs'; 2 | -------------------------------------------------------------------------------- /lib/addons/rxjs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './scheduled'; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /lib/addons/rxjs/scheduled/index.ts: -------------------------------------------------------------------------------- 1 | export * from './scheduled'; 2 | -------------------------------------------------------------------------------- /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /lib/utils/functionLike.ts: -------------------------------------------------------------------------------- 1 | export type FunctionLike = (...args: any[]) => T; 2 | -------------------------------------------------------------------------------- /lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isPromise'; 2 | export * from './functionLike'; 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit 5 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './got.module'; 2 | export * from './got.service'; 3 | export * from './got.interface'; 4 | -------------------------------------------------------------------------------- /tests/shared/gotConfig.ts: -------------------------------------------------------------------------------- 1 | import { ExtendOptions } from 'got'; 2 | 3 | export const GOT_CONFIG = {} as ExtendOptions; 4 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.ts": [ 3 | "prettier --write", 4 | "eslint --fix . --ext .js,.jsx,.ts,.tsx" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/got.constant.ts: -------------------------------------------------------------------------------- 1 | export const GOT_OPTIONS_TOKEN = Symbol('got options'); 2 | 3 | export const GOT_INSTANCE_TOKEN = Symbol('got instance'); 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "semi": true, 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/isPromise.ts: -------------------------------------------------------------------------------- 1 | export function isPromise = any>( 2 | input: T, 3 | ): boolean { 4 | return input instanceof Promise || 'then' in input; 5 | } 6 | -------------------------------------------------------------------------------- /tests/src/utils/test.txt: -------------------------------------------------------------------------------- 1 | { "id": 1 } 2 | { "id": 2 } 3 | { "id": 3 } 4 | { "id": 4 } 5 | { "id": 5 } 6 | { "id": 6 } 7 | { "id": 7 } 8 | { "id": 8 } 9 | { "id": 9 } 10 | { "id": 10 } -------------------------------------------------------------------------------- /tests/src/existing.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { GotConfigService } from './got-config.service'; 4 | 5 | @Module({ 6 | providers: [GotConfigService], 7 | exports: [GotConfigService], 8 | }) 9 | export class ExistingModule {} 10 | -------------------------------------------------------------------------------- /.prepare: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ## This checks if husky is installed in the npm modules 4 | ## before setting it up. This is so no installation 5 | ## fails in production. 6 | npm ls husky --depth 0 >> /dev/null 7 | 8 | if [ $? = 0 ]; then 9 | husky install; 10 | fi; 11 | -------------------------------------------------------------------------------- /.prepare.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | @REM This checks if husky is installed in the npm modules 4 | @REM before setting it up. This is so no installation 5 | @REM fails in production. 6 | 7 | CALL npm ls husky --depth 0 > NUL 8 | IF %ERRORLEVEL% EQU 0 ( 9 | husky install 10 | ) 11 | @ECHO ON 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 4 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.{yml,json,yaml}] 10 | indent_size = 11 | 12 | [*.{json,txt}] 13 | insert_final_newline = false 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # IDE 5 | /.idea 6 | /.awcache 7 | /.vscode/* 8 | !.vscode/settings.json 9 | !.vscode/launch.json 10 | 11 | # misc 12 | npm-debug.log 13 | /**/*.DS_Store 14 | .dccache 15 | 16 | # tests 17 | /test 18 | /coverage 19 | /.nyc_output 20 | **/*.log 21 | 22 | # dist 23 | dist 24 | .env 25 | .npmrc 26 | -------------------------------------------------------------------------------- /tests/src/got-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { GOT_CONFIG } from '../shared/gotConfig'; 3 | import { GotModuleOptionsFactory, GotModuleOptions } from '../../lib'; 4 | 5 | @Injectable() 6 | export class GotConfigService implements GotModuleOptionsFactory { 7 | createGotOptions(): GotModuleOptions { 8 | return GOT_CONFIG; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore(release): ${version}", 4 | "tag": false, 5 | "push": true, 6 | "requireCleanWorkingDir": false 7 | }, 8 | "npm": { 9 | "publish": true, 10 | "skipChecks": false, 11 | "ignoreVersion": false 12 | }, 13 | "github": { 14 | "release": true, 15 | "tokenRef": "GITHUB_TOKE", 16 | "releaseName": "Release v${version}" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/addons/rxjs/scheduled/schedulePromise.spec.ts: -------------------------------------------------------------------------------- 1 | import { asapScheduler } from 'rxjs'; 2 | 3 | import { schedulePromise } from './schedulePromise'; 4 | 5 | describe('schedulePromise()', () => { 6 | it('', complete => { 7 | const promised = Promise.resolve(1); 8 | 9 | schedulePromise(promised, asapScheduler).subscribe({ 10 | next(v) { 11 | expect(v).toEqual(1); 12 | }, 13 | complete, 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/src/paginate.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { OptionsWithPagination } from 'got'; 3 | 4 | import { GotService } from '../../lib'; 5 | 6 | @Injectable() 7 | export class PaginateService { 8 | constructor(private readonly gotService: GotService) {} 9 | 10 | each(url: string, options?: OptionsWithPagination) { 11 | return this.gotService.pagination.each(url, options); 12 | } 13 | 14 | all(url: string, options?: OptionsWithPagination) { 15 | return this.gotService.pagination.all(url, options); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "module": "commonjs", 5 | "declaration": true, 6 | "strict": true, 7 | "removeComments": true, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "sourceMap": true, 13 | "outDir": "./dist", 14 | "rootDir": "./lib", 15 | "skipLibCheck": true, 16 | "moduleResolution": "node" 17 | }, 18 | "include": ["lib/**/*.ts"], 19 | "exclude": ["node_modules", "tests", "**/*.js", "lib/**/*.spec.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | lib 3 | tests 4 | index.ts 5 | package-lock.json 6 | tslint.json 7 | tsconfig.json 8 | tsconfig.build.json 9 | .prettierrc 10 | .prettierignore 11 | .dev-dependencies 12 | .dependencies 13 | .eslintignore 14 | .dccache 15 | .env 16 | .vscode 17 | .editorconfig 18 | .release-it.json 19 | .eslintrc 20 | .commitlintrc 21 | pdf-icon.svg 22 | jest.config.json 23 | .prepare* 24 | yarn-error.log 25 | 26 | # github 27 | .github 28 | CONTRIBUTING.md 29 | README.md 30 | renovate.json 31 | .husky 32 | .lintstagedrc 33 | LICENCE 34 | 35 | # ci 36 | .circleci 37 | coverage 38 | .npmrc 39 | -------------------------------------------------------------------------------- /.dev-dependencies: -------------------------------------------------------------------------------- 1 | @nestjs/common 2 | @nestjs/core 3 | @nestjs/platform-express 4 | @nestjs/testing 5 | @commitlint/cli 6 | @commitlint/config-conventional 7 | @commitlint/prompt-cli 8 | @compodoc/compodoc 9 | @faker-js/faker 10 | @release-it/conventional-changelog 11 | @types/jest 12 | @types/nock 13 | @types/node 14 | @types/rimraf 15 | @typescript-eslint/eslint-plugin 16 | @typescript-eslint/parser 17 | dotenv-cli 18 | eslint 19 | eslint-config-prettier 20 | eslint-plugin-import 21 | got 22 | husky 23 | jest 24 | lint-staged 25 | nock 26 | prettier 27 | reflect-metadata 28 | release-it 29 | renovate 30 | rimraf 31 | rxjs 32 | ts-jest 33 | typescript 34 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "semanticCommits": "enabled", 4 | "labels": ["dependencies"], 5 | "assigneesFromCodeOwners": true, 6 | "packageRules": [ 7 | { 8 | "depTypeList": ["devDependencies"], 9 | "automerge": true 10 | }, 11 | { 12 | "updateTypes": ["major"], 13 | "labels": ["major-update", "dependencies"] 14 | }, 15 | { 16 | "updateTypes": ["minor"], 17 | "labels": ["minor-update", "dependencies"] 18 | }, 19 | { 20 | "updateTypes": ["patch"], 21 | "labels": ["patch-update", "dependencies"] 22 | } 23 | ], 24 | "schedule": ["every weekday"] 25 | } 26 | -------------------------------------------------------------------------------- /lib/got.interface.ts: -------------------------------------------------------------------------------- 1 | import { ExtendOptions } from 'got'; 2 | import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; 3 | 4 | import { FunctionLike } from './utils'; 5 | 6 | export type GotModuleOptions = ExtendOptions; 7 | 8 | export interface GotModuleOptionsFactory { 9 | createGotOptions(): GotModuleOptions | Promise; 10 | } 11 | 12 | // prettier-ignore 13 | export interface GotModuleAsyncOptions 14 | extends Pick { 15 | useFactory?: FunctionLike>; 16 | useClass?: Type; 17 | useExisting?: Type; 18 | inject?: any[]; 19 | } 20 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest", 3 | "testEnvironment": "node", 4 | "moduleFileExtensions": ["ts", "js"], 5 | "transform": { 6 | "^.+\\.ts$": "ts-jest" 7 | }, 8 | "testRegex": "lib/.*spec.ts$", 9 | "collectCoverageFrom": [ 10 | "lib/**/*.{ts}", 11 | "!**/node_modules/**", 12 | "!**/vendor/**", 13 | "!**/*.spec.ts" 14 | ], 15 | "transformIgnorePatterns": ["/node_modules/(?!(got))/"], 16 | "coverageReporters": ["json", "lcov"], 17 | "coverageThreshold": { 18 | "global": { 19 | "branches": 80, 20 | "functions": 80, 21 | "lines": 80, 22 | "statements": -10 23 | } 24 | }, 25 | "coverageDirectory": "coverage/unit" 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | exports.__esModule = true; 17 | __exportStar(require("./dist"), exports); 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "tsconfig.json", 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["@typescript-eslint/eslint-plugin"], 8 | "extends": [ 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "prettier" 12 | ], 13 | "root": true, 14 | "env": { 15 | "node": true, 16 | "jest": true 17 | }, 18 | "rules": { 19 | "@typescript-eslint/interface-name-prefix": "off", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-use-before-define": "off", 23 | "@typescript-eslint/no-non-null-assertion": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/addons/rxjs/scheduled/scheduled.ts: -------------------------------------------------------------------------------- 1 | import { Observable, SchedulerLike } from 'rxjs'; 2 | 3 | import { isPromise } from '../../../utils'; 4 | import { schedulePromise } from './schedulePromise'; 5 | import { scheduledAsyncIterable } from './scheduleAsyncIterable'; 6 | 7 | export const scheduled = ( 8 | input: Promise | AsyncIterator, 9 | scheduler: SchedulerLike, 10 | unsubscriber?: ((...args: any[]) => any) | void, 11 | ): Observable => { 12 | if (isPromise(input)) { 13 | // prettier-ignore 14 | return schedulePromise( 15 | input as Promise, 16 | scheduler, 17 | unsubscriber 18 | ); 19 | } 20 | 21 | return scheduledAsyncIterable( 22 | input as AsyncIterator, 23 | scheduler, 24 | unsubscriber, 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /lib/addons/rxjs/scheduled/scheduleAsyncIterable.spec.ts: -------------------------------------------------------------------------------- 1 | import { asapScheduler } from 'rxjs'; 2 | import { take } from 'rxjs/operators'; 3 | 4 | import { scheduledAsyncIterable } from './scheduleAsyncIterable'; 5 | 6 | describe('scheduleAsyncIterable()', () => { 7 | it('', complete => { 8 | const iterator = async function* () { 9 | let i = 1; 10 | while (true) { 11 | yield i; 12 | i += 1; 13 | } 14 | }; 15 | 16 | const iterable = iterator(); 17 | let count = 1; 18 | 19 | scheduledAsyncIterable(iterable, asapScheduler) 20 | .pipe(take(5)) 21 | .subscribe({ 22 | next(v) { 23 | expect(v).toEqual(count++); 24 | }, 25 | complete, 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "ts", "json"], 3 | "rootDir": "../", 4 | "testEnvironment": "node", 5 | "transform": { 6 | "^.+\\.ts$": "ts-jest" 7 | }, 8 | "testRegex": "/e2e/.*\\.(e2e-test|e2e-spec).ts$", 9 | "collectCoverageFrom": [ 10 | "lib/**/*.{js,jsx,tsx,ts}", 11 | "!lib/**/*.spec.{js,jsx,tsx,ts}", 12 | "!**/node_modules/**", 13 | "!**/vendor/**" 14 | ], 15 | "coveragePathIgnorePatterns": ["./tests", "/node_modules/"], 16 | "transformIgnorePatterns": [ 17 | "/node_modules/(?!(got|p-cancelable|@szmarczak|lowercase-keys)/)" 18 | ], 19 | "coverageReporters": ["json", "lcov"], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 80, 23 | "functions": 80, 24 | "lines": 80, 25 | "statements": -10 26 | } 27 | }, 28 | "coverageDirectory": "coverage/e2e" 29 | } 30 | -------------------------------------------------------------------------------- /lib/addons/rxjs/scheduled/schedulePromise.ts: -------------------------------------------------------------------------------- 1 | import { Observable, SchedulerLike } from 'rxjs'; 2 | 3 | export const schedulePromise = ( 4 | input: Promise, 5 | scheduler: SchedulerLike, 6 | unsubscriber?: ((...args: any[]) => any) | void, 7 | ): Observable => 8 | new Observable(subscription => { 9 | input 10 | .then((response: T) => 11 | subscription.add( 12 | scheduler.schedule(() => subscription.next(response)), 13 | ), 14 | ) 15 | .catch(error => 16 | subscription.add( 17 | scheduler.schedule(() => subscription.error(error)), 18 | ), 19 | ) 20 | .finally(() => 21 | subscription.add( 22 | scheduler.schedule(() => subscription.complete()), 23 | ), 24 | ); 25 | 26 | return unsubscriber; 27 | }); 28 | -------------------------------------------------------------------------------- /tests/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { OptionsOfJSONResponseBody } from 'got'; 3 | 4 | import { GotService } from '../../lib'; 5 | 6 | @Injectable() 7 | export class AppService { 8 | constructor(readonly gotService: GotService) {} 9 | 10 | head(url: string, options?: OptionsOfJSONResponseBody) { 11 | return this.gotService.head(url, options); 12 | } 13 | 14 | get(url: string, options?: OptionsOfJSONResponseBody) { 15 | return this.gotService.get(url, options); 16 | } 17 | 18 | post(url: string, options?: OptionsOfJSONResponseBody) { 19 | return this.gotService.post(url, options); 20 | } 21 | 22 | put(url: string, options?: OptionsOfJSONResponseBody) { 23 | return this.gotService.put(url, options); 24 | } 25 | 26 | patch(url: string, options?: OptionsOfJSONResponseBody) { 27 | return this.gotService.patch(url, options); 28 | } 29 | 30 | delete(url: string, options?: OptionsOfJSONResponseBody) { 31 | return this.gotService.delete(url, options); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 B'Tunde Aromire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/addons/rxjs/scheduled/scheduleAsyncIterable.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subscriber, SchedulerLike } from 'rxjs'; 2 | 3 | export const scheduledAsyncIterable = ( 4 | input: AsyncIterator, 5 | scheduler: SchedulerLike, 6 | unsubscriber?: ((...args: any[]) => any) | void, 7 | ): Observable => 8 | new Observable((subscriber: Subscriber) => { 9 | scheduler.schedule(() => { 10 | const iterator = input[Symbol.asyncIterator](); 11 | subscriber.add( 12 | scheduler.schedule(function () { 13 | iterator 14 | .next() 15 | .then((result: IteratorResult) => { 16 | if (result.done) { 17 | subscriber.complete(); 18 | } else { 19 | subscriber.next(result.value); 20 | this.schedule(); 21 | } 22 | }); 23 | }), 24 | ); 25 | }); 26 | return unsubscriber; 27 | }); 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Jest unit test", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}\\node_modules\\jest\\bin\\jest.js", 13 | "args": ["-i"], 14 | "internalConsoleOptions": "openOnFirstSessionStart", 15 | "preLaunchTask": "npm: build", 16 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Jest integration test", 22 | "skipFiles": ["/**"], 23 | "program": "${workspaceFolder}\\node_modules\\jest\\bin\\jest.js", 24 | "args": ["-i", "--config", "./tests/jest-e2e.json", "--runInBand"], 25 | "internalConsoleOptions": "openOnFirstSessionStart", 26 | "preLaunchTask": "npm: build", 27 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/stream.service.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import { StreamOptions } from 'got'; 4 | import { Injectable } from '@nestjs/common'; 5 | 6 | import { GotService } from '../../lib'; 7 | 8 | @Injectable() 9 | export class StreamTestService { 10 | constructor(readonly gotService: GotService) {} 11 | 12 | head(url: string, options?: StreamOptions) { 13 | return this.gotService.stream.head(url, options); 14 | } 15 | 16 | get(url: string, options?: StreamOptions) { 17 | return this.gotService.stream.get(url, options); 18 | } 19 | 20 | delete(url: string, options?: StreamOptions) { 21 | return this.gotService.stream.delete(url, undefined, options); 22 | } 23 | 24 | post(url: string, options?: StreamOptions) { 25 | return this.gotService.stream.post( 26 | url, 27 | join('tests', 'src', 'utils', 'test.txt'), 28 | options, 29 | ); 30 | } 31 | 32 | put(url: string, options?: StreamOptions) { 33 | return this.gotService.stream.put( 34 | url, 35 | join('tests', 'src', 'utils', 'test.txt'), 36 | options, 37 | ); 38 | } 39 | 40 | patch(url: string, options?: StreamOptions) { 41 | return this.gotService.stream.patch( 42 | url, 43 | join('tests', 'src', 'utils', 'test.txt'), 44 | options, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: deploy to npm 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: checkout current 14 | uses: actions/checkout@v3 15 | with: 16 | ref: master 17 | 18 | - name: setup node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: "18" 22 | 23 | # - name: publish version 24 | # id: publish_version 25 | # run: echo "RELEASE_VERSION=${{ github.ref_name }}" >> $GITHUB_OUTPUT 26 | 27 | - name: git config 28 | run: | 29 | git config user.name "${GITHUB_ACTOR}" 30 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 31 | 32 | # - name: npm version bump 33 | # run: npm version --no-git-tag-version ${{ steps.publish_version.outputs.RELEASE_VERSION }} 34 | 35 | # - name: push to github 36 | # run: | 37 | # git add . 38 | # git commit -m "chore: publish version ${{ steps.publish_version.outputs.RELEASE_VERSION }}" 39 | # git push origin HEAD:${{ github.event.repository.default_branch }} 40 | 41 | - name: npm publish 42 | run: | 43 | npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN 44 | yarn 45 | yarn release 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 49 | -------------------------------------------------------------------------------- /tests/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, DynamicModule } from '@nestjs/common'; 2 | 3 | import { GotModule } from '../../lib'; 4 | import { GOT_CONFIG } from '../shared/gotConfig'; 5 | import { ExistingModule } from './existing.module'; 6 | import { GotConfigService } from './got-config.service'; 7 | 8 | @Module({ 9 | exports: [GotModule], 10 | }) 11 | export class AppModule { 12 | static withRegister(): DynamicModule { 13 | return { 14 | module: AppModule, 15 | imports: [GotModule.register()], 16 | }; 17 | } 18 | 19 | static withUseFactoryRegisterAsync(): DynamicModule { 20 | return { 21 | module: AppModule, 22 | imports: [ 23 | GotModule.registerAsync({ 24 | useFactory: () => GOT_CONFIG, 25 | }), 26 | ], 27 | }; 28 | } 29 | 30 | static withUseClassRegisterAsync(): DynamicModule { 31 | return { 32 | module: AppModule, 33 | imports: [ 34 | GotModule.registerAsync({ 35 | useClass: GotConfigService, 36 | }), 37 | ], 38 | }; 39 | } 40 | 41 | static withUseExistingRegisterAsync(): DynamicModule { 42 | return { 43 | module: AppModule, 44 | imports: [ 45 | GotModule.registerAsync({ 46 | imports: [ExistingModule], 47 | useExisting: GotConfigService, 48 | }), 49 | ], 50 | }; 51 | } 52 | 53 | static isGotInstance(value: Record): boolean { 54 | return 'defaults' in value && 'options' in value.defaults; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/stream.request.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { Readable } from 'stream'; 3 | import { createReadStream } from 'fs'; 4 | 5 | import Request from 'got/dist/source/core'; 6 | import { fromEvent, Observable } from 'rxjs'; 7 | import { Injectable, Scope } from '@nestjs/common'; 8 | import { Got, HTTPAlias, StreamOptions, GotStream } from 'got'; 9 | 10 | @Injectable({ scope: Scope.TRANSIENT }) 11 | export class StreamRequest { 12 | private stream!: Request; 13 | 14 | process( 15 | got: Got, 16 | method: HTTPAlias, 17 | url: string | URL, 18 | file?: string | Readable, 19 | streamOptions: StreamOptions = {}, 20 | ): this { 21 | this.createRequest(got, method, url, { 22 | ...streamOptions, 23 | isStream: true, 24 | }).writeToRequest(method, file); 25 | 26 | return this; 27 | } 28 | 29 | on( 30 | eventName: 31 | | 'end' 32 | | 'data' 33 | | 'error' 34 | | 'request' 35 | | 'readable' 36 | | 'response' 37 | | 'redirect' 38 | | 'uploadProgress' 39 | | 'downloadProgress', 40 | ): Observable { 41 | return fromEvent(this.stream, eventName); 42 | } 43 | 44 | private createRequest( 45 | got: Got, 46 | method: string, 47 | url: string | URL, 48 | streamOptions?: StreamOptions, 49 | ): this { 50 | this.stream = (got.stream[method] as GotStream)(url, streamOptions); 51 | 52 | return this; 53 | } 54 | 55 | private writeToRequest(method: HTTPAlias, file?: string | Readable): this { 56 | if (typeof file === 'string') { 57 | file = createReadStream(resolve(process.cwd(), file)); 58 | file.on('end', file.destroy.bind(file)); 59 | } 60 | 61 | if (file instanceof Readable) { 62 | file.on('data', this.stream.write.bind(this.stream)); 63 | file.on('end', this.stream.end.bind(this.stream)); 64 | } else if (['post', 'put', 'patch', 'delete'].includes(method)) { 65 | this.stream.end(); 66 | } 67 | 68 | return this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/paginate.service.ts: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | import { 3 | Got, 4 | GotPaginate, 5 | OptionsWithPagination 6 | } from 'got'; 7 | import { Inject, Injectable } from '@nestjs/common'; 8 | import { Observable, asapScheduler, SchedulerLike } from 'rxjs'; 9 | 10 | import { scheduled } from './addons'; 11 | import { GOT_INSTANCE_TOKEN } from './got.constant'; 12 | 13 | @Injectable() 14 | export class PaginationService { 15 | constructor(@Inject(GOT_INSTANCE_TOKEN) private readonly got: Got) {} 16 | 17 | each( 18 | url: string | URL, 19 | options?: OptionsWithPagination, 20 | scheduler?: SchedulerLike, 21 | ): Observable { 22 | return this.makeObservable( 23 | this.got.paginate.each, 24 | url, 25 | options, 26 | scheduler, 27 | ); 28 | } 29 | 30 | all( 31 | url: string | URL, 32 | options?: OptionsWithPagination, 33 | scheduler?: SchedulerLike, 34 | ): Observable { 35 | return this.makeObservable( 36 | this.got.paginate.all, 37 | url, 38 | options, 39 | scheduler, 40 | ); 41 | } 42 | 43 | private makeObservable( 44 | paginate: GotPaginate['all'], 45 | url: string | URL, 46 | options?: OptionsWithPagination, 47 | scheduler?: SchedulerLike, 48 | ): Observable; 49 | private makeObservable( 50 | paginate: GotPaginate['each'], 51 | url: string | URL, 52 | options?: OptionsWithPagination, 53 | scheduler?: SchedulerLike, 54 | ): Observable; 55 | private makeObservable( 56 | paginate: ( 57 | url: string | URL, 58 | options: OptionsWithPagination, 59 | ) => Promise | AsyncIterableIterator, 60 | url: string | URL, 61 | options?: OptionsWithPagination, 62 | scheduler: SchedulerLike = asapScheduler, 63 | ): Observable { 64 | options = { ...options, isStream: false }; 65 | 66 | return scheduled(paginate(url, options), scheduler); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/stream.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Duplex } from 'stream'; 2 | 3 | import { Got, GotStream } from 'got'; 4 | import { faker } from '@faker-js/faker'; 5 | import { Test, TestingModule } from '@nestjs/testing'; 6 | 7 | import { StreamRequest } from './stream.request'; 8 | import { StreamService } from './stream.service'; 9 | import { GOT_INSTANCE_TOKEN } from './got.constant'; 10 | 11 | describe('StreamService', () => { 12 | let service: StreamService; 13 | const gotInstance: Partial = { 14 | defaults: { 15 | options: jest.fn(), 16 | } as any, 17 | }; 18 | 19 | beforeEach(async () => { 20 | const module: TestingModule = await Test.createTestingModule({ 21 | providers: [ 22 | StreamService, 23 | StreamRequest, 24 | { 25 | provide: GOT_INSTANCE_TOKEN, 26 | useValue: gotInstance, 27 | }, 28 | ], 29 | }).compile(); 30 | 31 | service = module.get(StreamService); 32 | }); 33 | 34 | it('should be defined', () => { 35 | expect(service).toBeDefined(); 36 | }); 37 | 38 | ['get', 'head', 'post', 'put', 'patch', 'delete'].forEach(verb => { 39 | it(`${verb}()`, complete => { 40 | class CustomReadable extends Duplex { 41 | _read() { 42 | let length = 0; 43 | const str = `a 44 | b 45 | c 46 | d 47 | e 48 | f 49 | g 50 | h 51 | i 52 | k 53 | l 54 | m 55 | n 56 | o`.split(/\n/g); 57 | 58 | while (length < str.length) { 59 | this.push(str[length]); 60 | length++; 61 | } 62 | this.push(null); 63 | } 64 | } 65 | 66 | const readable = new CustomReadable(); 67 | 68 | (gotInstance.stream as Partial) = { 69 | [verb]: jest.fn().mockReturnValue(readable), 70 | }; 71 | 72 | const request = service[verb]( 73 | faker.internet.url(), 74 | ) as StreamRequest; 75 | 76 | request.on('data').subscribe({ 77 | next(response) { 78 | expect(response.toString()).toEqual(expect.any(String)); 79 | }, 80 | }); 81 | request.on('end').subscribe(complete); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /lib/stream.service.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | 3 | import { Inject, Injectable } from '@nestjs/common'; 4 | import { Got, HTTPAlias, StreamOptions } from 'got'; 5 | 6 | import { StreamRequest } from './stream.request'; 7 | import { GOT_INSTANCE_TOKEN } from './got.constant'; 8 | 9 | @Injectable() 10 | export class StreamService { 11 | constructor( 12 | @Inject(GOT_INSTANCE_TOKEN) private readonly got: Got, 13 | private readonly request: StreamRequest, 14 | ) {} 15 | 16 | get(url: string | URL, options?: StreamOptions): StreamRequest { 17 | return this.makeRequest('get', url, undefined, options); 18 | } 19 | 20 | head(url: string | URL, options?: StreamOptions): StreamRequest { 21 | return this.makeRequest('head', url, undefined, options); 22 | } 23 | 24 | delete( 25 | url: string | URL, 26 | filePathOrStream?: string | Readable, 27 | options?: StreamOptions, 28 | ): StreamRequest { 29 | return this.makeRequest('delete', url, filePathOrStream, options); 30 | } 31 | 32 | post( 33 | url: string | URL, 34 | filePathOrStream?: string | Readable, 35 | options?: StreamOptions, 36 | ): StreamRequest { 37 | return this.makeRequest('post', url, filePathOrStream, options); 38 | } 39 | 40 | patch( 41 | url: string | URL, 42 | filePathOrStream?: string | Readable, 43 | options?: StreamOptions, 44 | ): StreamRequest { 45 | return this.makeRequest('patch', url, filePathOrStream, options); 46 | } 47 | 48 | put( 49 | url: string | URL, 50 | filePathOrStream?: string | Readable, 51 | options?: StreamOptions, 52 | ): StreamRequest { 53 | return this.makeRequest('put', url, filePathOrStream, options); 54 | } 55 | 56 | private makeRequest( 57 | verb: Extract, 58 | url: string | URL, 59 | filePathOrStream?: string | Readable, 60 | options?: StreamOptions, 61 | ): StreamRequest; 62 | private makeRequest( 63 | verb: Extract, 64 | url: string | URL, 65 | filePathOrStream?: string | Readable, 66 | options?: StreamOptions, 67 | ): StreamRequest; 68 | private makeRequest( 69 | verb: HTTPAlias, 70 | url: string | URL, 71 | filePathOrStream?: string | Readable, 72 | options?: StreamOptions, 73 | ): StreamRequest { 74 | return this.request.process( 75 | this.got, 76 | verb, 77 | url, 78 | filePathOrStream, 79 | options, 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/got.module.ts: -------------------------------------------------------------------------------- 1 | import got from 'got'; 2 | import { DynamicModule, Module, Provider, Type } from '@nestjs/common'; 3 | 4 | import { 5 | GotModuleOptions, 6 | GotModuleAsyncOptions, 7 | GotModuleOptionsFactory, 8 | } from './got.interface'; 9 | import { GotService } from './got.service'; 10 | import { StreamService } from './stream.service'; 11 | import { StreamRequest } from './stream.request'; 12 | import { PaginationService } from './paginate.service'; 13 | import { GOT_INSTANCE_TOKEN, GOT_OPTIONS_TOKEN } from './got.constant'; 14 | 15 | @Module({ 16 | exports: [GotService], 17 | providers: [ 18 | { 19 | provide: GOT_INSTANCE_TOKEN, 20 | useValue: got, 21 | }, 22 | GotService, 23 | StreamRequest, 24 | StreamService, 25 | PaginationService, 26 | ], 27 | }) 28 | export class GotModule { 29 | static register(options: GotModuleOptions = {}): DynamicModule { 30 | return { 31 | module: GotModule, 32 | providers: [ 33 | { 34 | provide: GOT_INSTANCE_TOKEN, 35 | useValue: got.extend(options), 36 | }, 37 | ], 38 | }; 39 | } 40 | 41 | static registerAsync(options: GotModuleAsyncOptions): DynamicModule { 42 | return { 43 | module: GotModule, 44 | providers: [ 45 | ...GotModule.createAsyncProviders(options), 46 | { 47 | provide: GOT_INSTANCE_TOKEN, 48 | useFactory: (config: GotModuleOptions) => 49 | got.extend(config), 50 | inject: [GOT_OPTIONS_TOKEN], 51 | }, 52 | ], 53 | imports: options.imports || [], 54 | }; 55 | } 56 | 57 | static createAsyncProviders(options: GotModuleAsyncOptions): Provider[] { 58 | if (options.useFactory || options.useExisting) { 59 | return [GotModule.createAsyncOptionsProvider(options)]; 60 | } 61 | 62 | const useClass = options.useClass as Type; 63 | 64 | return [ 65 | GotModule.createAsyncOptionsProvider(options), 66 | { provide: useClass, useClass }, 67 | ]; 68 | } 69 | 70 | static createAsyncOptionsProvider( 71 | options: GotModuleAsyncOptions, 72 | ): Provider { 73 | if (options.useFactory) { 74 | return { 75 | provide: GOT_OPTIONS_TOKEN, 76 | useFactory: options.useFactory, 77 | inject: options.inject || [], 78 | }; 79 | } 80 | 81 | const inject = [ 82 | (options.useClass || 83 | options.useExisting) as Type, 84 | ]; 85 | 86 | return { 87 | provide: GOT_OPTIONS_TOKEN, 88 | useFactory: (factory: GotModuleOptionsFactory) => 89 | factory.createGotOptions(), 90 | inject, 91 | }; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | branches: 10 | - master 11 | 12 | jobs: 13 | generic-tests: 14 | strategy: 15 | fail-fast: true 16 | matrix: 17 | node: 18 | - "14" 19 | - "16" 20 | - "18" 21 | 22 | runs-on: ubuntu-latest 23 | name: Test on node ${{ matrix.node }} 24 | 25 | steps: 26 | - name: Checkout current 27 | uses: actions/checkout@v3 28 | 29 | - name: Setup node 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: ${{ matrix.node }} 33 | 34 | - name: Update npm 35 | run: sudo npm install -g npm@latest 36 | 37 | - name: install yarn 38 | run: sudo npm install -g --force yarn 39 | 40 | - name: install dependencies 41 | run: yarn --unsafe-perm 42 | 43 | - name: run linting 44 | run: yarn lint 45 | 46 | - name: run test 47 | run: yarn test 48 | 49 | - name: run integration test 50 | run: yarn test:integration 51 | 52 | - name: run test coverage 53 | run: yarn test:cov 54 | 55 | - name: Coveralls GitHub Action 56 | uses: coverallsapp/github-action@1.1.3 57 | with: 58 | github-token: ${{ github.token }} 59 | path-to-lcov: ./coverage/e2e/lcov.info 60 | flag-name: node-${{ matrix.node }} 61 | parallel: true 62 | 63 | support-tests: 64 | strategy: 65 | fail-fast: true 66 | matrix: 67 | supported: 68 | - "@nestjs/{common,core,platform-express,testing}@^7.0.0" 69 | - "@nestjs/{common,core,platform-express,testing}@^8.0.0" 70 | - rxjs@^6.0.0 71 | 72 | name: Support for ${{ matrix.supported }} 73 | runs-on: ubuntu-latest 74 | 75 | steps: 76 | - name: Checkout current 77 | uses: actions/checkout@v3 78 | 79 | - name: Setup node 80 | uses: actions/setup-node@v3 81 | with: 82 | node-version: ${{ matrix.node }} 83 | 84 | - name: Update npm 85 | run: sudo npm install -g npm@latest 86 | 87 | - name: install yarn 88 | run: sudo npm install -g --force yarn 89 | 90 | - name: install dependencies 91 | run: yarn --unsafe-perm 92 | 93 | - name: update dependencies to support ${{ matrix.supported }} 94 | run: yarn add -D ${{ matrix.supported }} 95 | 96 | - name: run test 97 | run: yarn test 98 | 99 | - name: run integration test 100 | run: yarn test:integration 101 | 102 | finish: 103 | needs: 104 | - generic-tests 105 | - support-tests 106 | runs-on: ubuntu-latest 107 | 108 | steps: 109 | - name: Coveralls finished 110 | uses: coverallsapp/github-action@1.1.3 111 | with: 112 | github-token: ${{ github.token }} 113 | path-to-lcov: ./coverage/e2e/lcov.info 114 | parallel-finished: true 115 | -------------------------------------------------------------------------------- /lib/paginate.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { Got, GotPaginate, HTTPError } from 'got'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | 5 | import { GotService } from './got.service'; 6 | import { GOT_INSTANCE_TOKEN } from './got.constant'; 7 | import { PaginationService } from './paginate.service'; 8 | 9 | describe('GotService', () => { 10 | let service: PaginationService; 11 | const gotInstance: Partial = { 12 | defaults: { 13 | options: jest.fn(), 14 | } as any, 15 | }; 16 | 17 | beforeEach(async () => { 18 | const module: TestingModule = await Test.createTestingModule({ 19 | providers: [ 20 | PaginationService, 21 | { 22 | provide: GOT_INSTANCE_TOKEN, 23 | useValue: gotInstance, 24 | }, 25 | { 26 | provide: GotService, 27 | useValue: {}, 28 | }, 29 | ], 30 | }).compile(); 31 | 32 | service = module.get(PaginationService); 33 | }); 34 | 35 | it('should be defined', () => { 36 | expect(service).toBeDefined(); 37 | }); 38 | 39 | it(`each()`, complete => { 40 | async function* asyncIterator() { 41 | const itemsCount = faker.datatype.number(20); 42 | 43 | for (let _ = 0; _ < itemsCount; _++) { 44 | yield { a: faker.random.alphaNumeric() }; 45 | } 46 | } 47 | 48 | (gotInstance.paginate as Partial) = { 49 | each: jest.fn().mockImplementation(() => asyncIterator()), 50 | all: jest.fn().mockResolvedValue([1, 2, 3, 4]), 51 | }; 52 | 53 | service.each(faker.internet.url()).subscribe({ 54 | next(response) { 55 | expect(response).toEqual( 56 | expect.objectContaining({ a: expect.any(String) }), 57 | ); 58 | }, 59 | complete, 60 | }); 61 | }); 62 | 63 | it('all()', complete => { 64 | (gotInstance.paginate as Partial) = { 65 | each: jest.fn(), 66 | all: jest.fn().mockResolvedValue([1, 2, 3, 4, 5]), 67 | }; 68 | 69 | service.all(faker.internet.url()).subscribe({ 70 | next(response) { 71 | expect(response).toEqual( 72 | expect.arrayContaining([expect.any(Number)]), 73 | ); 74 | }, 75 | complete, 76 | }); 77 | }); 78 | 79 | it('should check error reporting', () => { 80 | const result: any = { body: {}, statusCode: 400 }; 81 | 82 | (gotInstance.paginate as Partial) = { 83 | each: jest.fn(), 84 | all: jest.fn().mockRejectedValueOnce(new HTTPError(result)), 85 | }; 86 | 87 | service.all(faker.internet.url()).subscribe({ 88 | error(error) { 89 | expect(error).toBeInstanceOf(HTTPError); 90 | }, 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /lib/got.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Got, 3 | Response, 4 | GotRequestFunction, 5 | OptionsOfJSONResponseBody, 6 | } from 'got'; 7 | import { Inject, Injectable } from '@nestjs/common'; 8 | import { asapScheduler, Observable, SchedulerLike } from 'rxjs'; 9 | 10 | import { scheduled } from './addons'; 11 | import { StreamService } from './stream.service'; 12 | import { GOT_INSTANCE_TOKEN } from './got.constant'; 13 | import { PaginationService } from './paginate.service'; 14 | 15 | @Injectable() 16 | export class GotService { 17 | constructor( 18 | readonly stream: StreamService, 19 | readonly pagination: PaginationService, 20 | @Inject(GOT_INSTANCE_TOKEN) private readonly got: Got, 21 | ) {} 22 | 23 | head | []>( 24 | url: string | URL, 25 | options?: OptionsOfJSONResponseBody, 26 | scheduler?: SchedulerLike, 27 | ): Observable> { 28 | return this.makeObservable(this.got.head, url, options, scheduler); 29 | } 30 | 31 | get | []>( 32 | url: string | URL, 33 | options?: OptionsOfJSONResponseBody, 34 | scheduler?: SchedulerLike, 35 | ): Observable> { 36 | return this.makeObservable(this.got.get, url, options, scheduler); 37 | } 38 | 39 | post | []>( 40 | url: string | URL, 41 | options?: OptionsOfJSONResponseBody, 42 | scheduler?: SchedulerLike, 43 | ): Observable> { 44 | return this.makeObservable(this.got.post, url, options, scheduler); 45 | } 46 | 47 | put | []>( 48 | url: string | URL, 49 | options?: OptionsOfJSONResponseBody, 50 | scheduler?: SchedulerLike, 51 | ): Observable> { 52 | return this.makeObservable(this.got.put, url, options, scheduler); 53 | } 54 | 55 | patch | []>( 56 | url: string | URL, 57 | options?: OptionsOfJSONResponseBody, 58 | scheduler?: SchedulerLike, 59 | ): Observable> { 60 | return this.makeObservable(this.got.patch, url, options, scheduler); 61 | } 62 | 63 | delete | []>( 64 | url: string | URL, 65 | options?: OptionsOfJSONResponseBody, 66 | scheduler?: SchedulerLike, 67 | ): Observable> { 68 | return this.makeObservable(this.got.delete, url, options, scheduler); 69 | } 70 | 71 | get gotRef(): Got { 72 | return this.got; 73 | } 74 | 75 | private makeObservable( 76 | got: GotRequestFunction, 77 | url: string | URL, 78 | options?: OptionsOfJSONResponseBody, 79 | scheduler: SchedulerLike = asapScheduler, 80 | ): Observable> { 81 | const request = got(url, { 82 | ...options, 83 | responseType: 'json', 84 | isStream: false, 85 | }); 86 | 87 | return scheduled>( 88 | request, 89 | scheduler, 90 | request.cancel.bind(request), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/got.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { Got, HTTPError } from 'got'; 3 | import { faker } from '@faker-js/faker'; 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | 6 | import { GotService } from './got.service'; 7 | import { StreamService } from './stream.service'; 8 | import { GOT_INSTANCE_TOKEN } from './got.constant'; 9 | import { PaginationService } from './paginate.service'; 10 | 11 | describe('GotService', () => { 12 | let service: GotService; 13 | const gotInstance: Partial = { 14 | defaults: { 15 | options: jest.fn(), 16 | } as any, 17 | }; 18 | 19 | beforeEach(async () => { 20 | const module: TestingModule = await Test.createTestingModule({ 21 | providers: [ 22 | GotService, 23 | { 24 | provide: GOT_INSTANCE_TOKEN, 25 | useValue: gotInstance, 26 | }, 27 | { 28 | provide: PaginationService, 29 | useValue: {}, 30 | }, 31 | { 32 | provide: StreamService, 33 | useValue: {}, 34 | }, 35 | ], 36 | }).compile(); 37 | 38 | service = module.get(GotService); 39 | }); 40 | 41 | it('should be defined', () => { 42 | expect(service).toBeDefined(); 43 | }); 44 | 45 | ['get', 'head', 'post', 'put', 'patch', 'delete'].forEach( 46 | (key, index, methods) => { 47 | it(`${key}()`, complete => { 48 | const result = { body: {} }; 49 | const mock = Promise.resolve(result); 50 | (mock as any).cancel = jest.fn(); 51 | 52 | gotInstance[key] = jest.fn().mockReturnValueOnce(mock); 53 | 54 | const request = service[key]>( 55 | faker.internet.url(), 56 | ) as Observable>; 57 | 58 | request.subscribe({ 59 | next(response) { 60 | expect(response).toBe(result); 61 | }, 62 | complete, 63 | }); 64 | }); 65 | 66 | if (methods.length - 1 === index) { 67 | it('gotRef', () => 68 | expect('defaults' in service.gotRef).toBeTruthy()); 69 | 70 | it('should check error reporting', () => { 71 | const result: any = { body: {}, statusCode: 400 }; 72 | const mock = Promise.reject(new HTTPError(result)); 73 | (mock as any).cancel = jest.fn(); 74 | 75 | gotInstance[key] = jest.fn().mockReturnValueOnce(mock); 76 | 77 | const request = service[key](faker.internet.url()); 78 | 79 | request.subscribe({ 80 | error(error) { 81 | expect(error).toBeInstanceOf(HTTPError); 82 | }, 83 | }); 84 | }); 85 | } 86 | }, 87 | ); 88 | }); 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@t00nday/nestjs-got", 3 | "version": "2.2.19", 4 | "description": "A simple nestjs http module built on got", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib", 8 | "tests": "tests" 9 | }, 10 | "scripts": { 11 | "build": "rimraf -rf dist && tsc -p tsconfig.build.json", 12 | "lint": "eslint lib/**/*.ts --ignore-pattern lib/**/*.spec.ts --fix", 13 | "format": "prettier --write **/*.ts", 14 | "prepublish:npm": "npm run build", 15 | "publish:npm": "npm publish", 16 | "test:unit": "jest --runInBand", 17 | "test:unit:watch": "jest --watch --runInBand", 18 | "test": "jest --config ./tests/jest-e2e.json --runInBand", 19 | "test:integration": "jest --config ./tests/jest-e2e.json --runInBand", 20 | "test:cov": "jest --config ./tests/jest-e2e.json --runInBand --coverage", 21 | "prerelease": "npm run build", 22 | "release": "dotenv release-it -- --ci", 23 | "prepare": ".\\.prepare.bat || ./.prepare" 24 | }, 25 | "keywords": [ 26 | "nest got", 27 | "nest-got", 28 | "@nestjs/common", 29 | "nestjs-got", 30 | "nestjs got", 31 | "nestjs-http", 32 | "nestjs http", 33 | "rxjs", 34 | "Observer pattern", 35 | "Observables" 36 | ], 37 | "author": "B'Tunde Aromire", 38 | "license": "MIT", 39 | "husky": { 40 | "hooks": { 41 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 42 | "pre-commit": "lint-staged" 43 | } 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/toondaey/nestjs-got.git" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/toondaey/nestjs-got/issues" 51 | }, 52 | "homepage": "https://github.com/toondaey/nestjs-got#readme", 53 | "publishConfig": { 54 | "access": "public", 55 | "registry": "https://registry.npmjs.org/" 56 | }, 57 | "devDependencies": { 58 | "@commitlint/cli": "17.3.0", 59 | "@commitlint/config-conventional": "17.3.0", 60 | "@commitlint/prompt-cli": "17.3.0", 61 | "@compodoc/compodoc": "1.1.19", 62 | "@faker-js/faker": "7.6.0", 63 | "@nestjs/common": "9.2.1", 64 | "@nestjs/core": "9.2.1", 65 | "@nestjs/platform-express": "9.2.1", 66 | "@nestjs/testing": "9.2.1", 67 | "@release-it/conventional-changelog": "5.1.1", 68 | "@types/jest": "29.2.4", 69 | "@types/nock": "11.1.0", 70 | "@types/node": "18.11.16", 71 | "@types/rimraf": "3.0.2", 72 | "@typescript-eslint/eslint-plugin": "5.46.1", 73 | "@typescript-eslint/parser": "5.46.1", 74 | "dotenv-cli": "6.0.0", 75 | "eslint": "8.30.0", 76 | "eslint-config-prettier": "8.5.0", 77 | "eslint-plugin-import": "2.26.0", 78 | "got": "11.8.6", 79 | "husky": "8.0.2", 80 | "jest": "29.3.1", 81 | "lint-staged": "13.1.0", 82 | "nock": "13.2.9", 83 | "prettier": "2.8.1", 84 | "reflect-metadata": "0.1.13", 85 | "release-it": "15.5.1", 86 | "renovate": "34.60.1", 87 | "rimraf": "3.0.2", 88 | "rxjs": "7.8.0", 89 | "ts-jest": "29.0.3", 90 | "typescript": "4.9.4" 91 | }, 92 | "engines": { 93 | "node": "^14.0.0 || >=16.0.0 || ^18.0.0" 94 | }, 95 | "peerDependencies": { 96 | "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", 97 | "got": "^11.8.6", 98 | "reflect-metadata": "^0.1.13", 99 | "rxjs": "^6.0.0 || ^7.0.0" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Before you submit your Pull Request (PR) consider the following guidelines: 2 | 3 | 1. Search [GitHub](https://github.com/toondaey/nestjs-got/pulls) for an open or closed PR 4 | that relates to your submission. You don't want to duplicate effort. 5 | 1. Fork the toondaey/nestjs-got. 6 | 1. Make your changes in a new git branch: 7 | 8 | ```shell 9 | git checkout -b my-fix-branch master 10 | ``` 11 | 12 | 1. Create your patch, **including appropriate test cases**. 13 | 1. Follow our [Coding Rules](#rules). 14 | 1. Run the tests scripts specified in `package.json` and ensure that all tests pass. 15 | 1. Commit your changes using a descriptive commit message that follows our 16 | [commit message conventions](#commit). Adherence to these conventions 17 | is necessary because release notes are automatically generated from these messages. 18 | 19 | ```shell 20 | git commit -a 21 | ``` 22 | 23 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 24 | 25 | 1. Push your branch to GitHub: 26 | 27 | ```shell 28 | git push origin my-fix-branch 29 | ``` 30 | 31 | 1. In GitHub, send a pull request to `toondaey/nestjs-got:master`. 32 | 33 | - If we suggest changes then: 34 | 35 | - Make the required updates. 36 | - Re-run the Nest test suites to ensure tests are still passing. 37 | - Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 38 | 39 | ```shell 40 | git rebase master -i 41 | git push -f 42 | ``` 43 | 44 | That's it! Thank you for your contribution! 45 | 46 | ## Coding Rules 47 | 48 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 49 | 50 | 53 | 54 | - All features or bug fixes **must be tested** by one or more specs (unit-tests). 55 | - We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at 56 | **100 characters**. An automated formatter is available (`npm run format`). 57 | 58 | ## Commit Message Guidelines 59 | 60 | We have very precise rules over how our git commit messages can be formatted. This leads to **more 61 | readable messages** that are easy to follow when looking through the **project history**. But also, 62 | we use the git commit messages to **generate the Nest change log**. 63 | 64 | ### Commit Message Format 65 | 66 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 67 | format that includes a **type**, a **scope** and a **subject**: 68 | 69 | ``` 70 | (): 71 | 72 | 73 | 74 |