├── .editorconfig ├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ ├── check-code.yml │ ├── check-pull-request.yml │ ├── develop.deploy.yml │ └── publish.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc ├── .versionrc ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── README.md ├── apps ├── .gitkeep └── demo │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── abstract-class │ │ │ ├── abstract-class.controller.e2e-spec.ts │ │ │ ├── abstract-class.controller.spec.ts │ │ │ ├── abstract-class.controller.ts │ │ │ ├── abstract-class.module.ts │ │ │ ├── abstract-class.service.ts │ │ │ ├── animal-cats │ │ │ │ ├── animal-cats.module.ts │ │ │ │ └── animal-cats.service.ts │ │ │ ├── animal-cows │ │ │ │ ├── animal-cows.module.ts │ │ │ │ └── animal-cows.service.ts │ │ │ ├── animal-dogs │ │ │ │ ├── animal-dogs.module.ts │ │ │ │ └── animal-dogs.service.ts │ │ │ ├── animal-ducks-and-geeses │ │ │ │ ├── animal-ducks-and-geeses.module.ts │ │ │ │ ├── animal-ducks.service.ts │ │ │ │ └── animal-geeses.service.ts │ │ │ ├── animal-horses │ │ │ │ ├── animal-horses.module.ts │ │ │ │ └── animal-horses.service.ts │ │ │ ├── animal-sheeps │ │ │ │ ├── animal-goats.service.ts │ │ │ │ ├── animal-sheeps.module.ts │ │ │ │ └── animal-sheeps.service.ts │ │ │ └── animal │ │ │ │ ├── animal.module.ts │ │ │ │ ├── animal.provider.ts │ │ │ │ ├── animal.service.ts │ │ │ │ └── check-animal-voice.pipe.ts │ │ ├── app.module.ts │ │ ├── string-token │ │ │ ├── animal-cats │ │ │ │ ├── animal-cats.module.ts │ │ │ │ └── animal-cats.service.ts │ │ │ ├── animal-cows │ │ │ │ ├── animal-cows.module.ts │ │ │ │ └── animal-cows.service.ts │ │ │ ├── animal-dogs │ │ │ │ ├── animal-dogs.module.ts │ │ │ │ └── animal-dogs.service.ts │ │ │ ├── animal-ducks-and-geeses │ │ │ │ ├── animal-ducks-and-geeses.module.ts │ │ │ │ ├── animal-ducks.service.ts │ │ │ │ └── animal-geeses.service.ts │ │ │ ├── animal-horses │ │ │ │ ├── animal-horses.module.ts │ │ │ │ └── animal-horses.service.ts │ │ │ ├── animal-sheeps │ │ │ │ ├── animal-goats.service.ts │ │ │ │ ├── animal-sheeps.module.ts │ │ │ │ └── animal-sheeps.service.ts │ │ │ ├── animal │ │ │ │ ├── animal-provider.interface.ts │ │ │ │ ├── animal.module.ts │ │ │ │ ├── animal.service.ts │ │ │ │ └── check-animal-voice.pipe.ts │ │ │ ├── string-token.controller.e2e-spec.ts │ │ │ ├── string-token.controller.spec.ts │ │ │ ├── string-token.controller.ts │ │ │ ├── string-token.module.ts │ │ │ └── string-token.service.ts │ │ └── symbol-token │ │ │ ├── animal-cats │ │ │ ├── animal-cats.module.ts │ │ │ └── animal-cats.service.ts │ │ │ ├── animal-cows │ │ │ ├── animal-cows.module.ts │ │ │ └── animal-cows.service.ts │ │ │ ├── animal-dogs │ │ │ ├── animal-dogs.module.ts │ │ │ └── animal-dogs.service.ts │ │ │ ├── animal-ducks-and-geeses │ │ │ ├── animal-ducks-and-geeses.module.ts │ │ │ ├── animal-ducks.service.ts │ │ │ └── animal-geeses.service.ts │ │ │ ├── animal-horses │ │ │ ├── animal-horses.module.ts │ │ │ └── animal-horses.service.ts │ │ │ ├── animal-sheeps │ │ │ ├── animal-goats.service.ts │ │ │ ├── animal-sheeps.module.ts │ │ │ └── animal-sheeps.service.ts │ │ │ ├── animal │ │ │ ├── animal-provider.interface.ts │ │ │ ├── animal.module.ts │ │ │ ├── animal.service.ts │ │ │ └── check-animal-voice.pipe.ts │ │ │ ├── symbol-token.controller.e2e-spec.ts │ │ │ ├── symbol-token.controller.spec.ts │ │ │ ├── symbol-token.controller.ts │ │ │ ├── symbol-token.module.ts │ │ │ └── symbol-token.service.ts │ ├── assets │ │ ├── .gitkeep │ │ └── i18n │ │ │ └── en.vendor.json │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── webpack.config.js ├── jest.config.ts ├── jest.preset.js ├── libs ├── .gitkeep └── nestjs-custom-injector │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ ├── custom-injector-bootstrap.service.ts │ │ ├── custom-injector.decorators.ts │ │ ├── custom-injector.module.ts │ │ ├── custom-injector.service.ts │ │ ├── custom-injector.types.ts │ │ └── custom-injector.utils.ts │ ├── tests │ ├── multi-providers-by-property.spec.ts │ ├── multi-providers.spec.ts │ ├── one-provider-by-property.spec.ts │ └── one-provider.spec.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── migrations.json ├── nx.json ├── package.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── transloco.config.js ├── transloco.config.json ├── tsconfig.base.json └── workspace.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [EndyKaufman] 2 | patreon: EndyKaufman 3 | # open_collective: # Replace with a single Open Collective username 4 | ko_fi: endykaufman 5 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 6 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 7 | liberapay: EndyKaufman 8 | issuehunt: endykaufman 9 | # otechie: # Replace with a single Otechie username 10 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 11 | # custom: # [https://yoomoney.ru/to/41001220209977] 12 | -------------------------------------------------------------------------------- /.github/workflows/check-code.yml: -------------------------------------------------------------------------------- 1 | name: Check code 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'develop' 8 | - 'feature/**' 9 | tags-ignore: 10 | - v* 11 | 12 | jobs: 13 | check-code: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node: [14, 16, 18] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup Node.js ${{ matrix.node }} to publish to npmjs.org 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: '${{ matrix.node }}' 27 | registry-url: 'https://registry.npmjs.org' 28 | 29 | - uses: actions/cache@v2 30 | with: 31 | path: ~/.npm 32 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 33 | restore-keys: | 34 | ${{ runner.os }}-node- 35 | 36 | - name: Install Packages 37 | run: npm i --force 38 | 39 | - name: Lint 40 | run: npm run lint 41 | env: 42 | CI: true 43 | -------------------------------------------------------------------------------- /.github/workflows/check-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Check pull request 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | check-code: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node: [14, 16, 18] 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup Node.js ${{ matrix.node }} to publish to npmjs.org 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: '${{ matrix.node }}' 21 | registry-url: 'https://registry.npmjs.org' 22 | 23 | - uses: actions/cache@v2 24 | with: 25 | path: ~/.npm 26 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 27 | restore-keys: | 28 | ${{ runner.os }}-node- 29 | 30 | - name: Install Packages 31 | run: npm i --force 32 | 33 | - name: Lint 34 | run: npm run lint 35 | env: 36 | CI: true 37 | 38 | - name: Test 39 | run: npm run test 40 | env: 41 | CI: true 42 | 43 | - uses: tanmen/jest-reporter@v1 44 | if: always() 45 | with: 46 | github-token: ${{ secrets.GITHUB_TOKEN }} 47 | result-file: jest.result.json 48 | -------------------------------------------------------------------------------- /.github/workflows/develop.deploy.yml: -------------------------------------------------------------------------------- 1 | name: 'deploy' 2 | 3 | # yamllint disable-line rule:truthy 4 | on: 5 | push: 6 | branches: 7 | - develop 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Cloning repo 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Push to dokku 19 | uses: dokku/github-action@master 20 | with: 21 | branch: 'develop' 22 | git_remote_url: 'ssh://dokku@${{secrets.HOST}}:22/nestjs-custom-injector' 23 | ssh_private_key: ${{secrets.SSH_PRIVATE_KEY}} 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup Node.js 16.x to publish to npmjs.org 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: '16.x' 20 | registry-url: 'https://registry.npmjs.org' 21 | 22 | - name: Install Packages 23 | run: npm i --force 24 | 25 | - name: Test 26 | run: npm run test 27 | env: 28 | CI: true 29 | 30 | - name: Build 31 | run: npm run lib:build 32 | 33 | - name: Generate Release Body 34 | run: npx extract-changelog-release > RELEASE_BODY.md 35 | 36 | - name: Publish to NPM 37 | run: npm run lib:publish 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | 41 | - name: Create GitHub Release 42 | uses: ncipollo/release-action@v1 43 | with: 44 | bodyFile: 'RELEASE_BODY.md' 45 | token: ${{ secrets.GITHUB_TOKEN }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | jest.result.json 42 | package-lock.json -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "bumpFiles": [ 3 | { 4 | "filename": "./libs/nestjs-custom-injector/package.json", 5 | "type": "json" 6 | }, 7 | { 8 | "filename": "./package.json", 9 | "type": "json" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#ca0a0a", 4 | "titleBar.activeBackground": "#000000", 5 | "titleBar.activeForeground": "#FCFCFF" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [2.2.3](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.2.2...v2.2.3) (2022-09-14) 6 | 7 | ### Bug Fixes 8 | 9 | - add support work in node 14 ([#8](https://github.com/EndyKaufman/nestjs-custom-injector/issues/8)) ([2e56308](https://github.com/EndyKaufman/nestjs-custom-injector/commit/2e56308ca1f8ca794d45f40c4ed49b7b27c544f8)) 10 | 11 | ### [2.2.2](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.2.1...v2.2.2) (2022-09-09) 12 | 13 | ### Bug Fixes 14 | 15 | - ignore errors if bad components in DI ([49e9496](https://github.com/EndyKaufman/nestjs-custom-injector/commit/49e9496bf03df9d9be78843968fad4740b27d328)) 16 | 17 | ### [2.2.1](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.2.0...v2.2.1) (2022-09-09) 18 | 19 | ### Bug Fixes 20 | 21 | - add updateBuildableProjectDepsInPackageJson=false for disable append deps version to lib package json file ([77fc566](https://github.com/EndyKaufman/nestjs-custom-injector/commit/77fc566b57658a14450dd0419286b1f521674c0f)) 22 | 23 | ## [2.2.0](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.1.2...v2.2.0) (2022-09-09) 24 | 25 | ### Features 26 | 27 | - update deps, nest version 14.6.5 ([5f4382a](https://github.com/EndyKaufman/nestjs-custom-injector/commit/5f4382a56be86971589274276bb509952fbf45db)) 28 | 29 | ### [2.1.2](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.1.1...v2.1.2) (2022-09-09) 30 | 31 | ### Bug Fixes 32 | 33 | - remove peerDependencies ([730f6cd](https://github.com/EndyKaufman/nestjs-custom-injector/commit/730f6cd70ac0aa6d5b9f6dcd8d3cc6f07b4bea36)) 34 | 35 | ### [2.1.1](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.1.0...v2.1.1) (2022-07-26) 36 | 37 | ### Bug Fixes 38 | 39 | - add check metadata key name of providers in initAllNeedProviders before run asyncInit ([efc67d9](https://github.com/EndyKaufman/nestjs-custom-injector/commit/efc67d971edf55eea7074558cbb4703a74008480)) 40 | 41 | ## [2.1.0](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.0.2...v2.1.0) (2022-07-17) 42 | 43 | ### Features 44 | 45 | - removed global storage for custom injector metadata, now use reflect metadata storage over classes for it, now ignore custom injected providers when its not included in DI tree NestJS ([5c9b1b7](https://github.com/EndyKaufman/nestjs-custom-injector/commit/5c9b1b7919c25bcf4b0aa22035f3106693cb9106)) 46 | 47 | ### [2.0.2](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.0.1...v2.0.2) (2022-07-15) 48 | 49 | ### Bug Fixes 50 | 51 | - append DiscoveryModule to imports of CustomInjectorCoreModule ([55a4cab](https://github.com/EndyKaufman/nestjs-custom-injector/commit/55a4cab519d21b21148a4783b07f749214c900a4)) 52 | - restore peerDependencies of lib ([1913477](https://github.com/EndyKaufman/nestjs-custom-injector/commit/1913477d8f32ba00c2af84f273e04fc0c49f6f23)) 53 | 54 | ### [2.0.1](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v2.0.0...v2.0.1) (2022-07-14) 55 | 56 | ## [2.0.0](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v1.0.1...v2.0.0) (2022-07-14) 57 | 58 | ### ⚠ BREAKING CHANGES 59 | 60 | - more types, remove getLastComponentByName, getLastComponentByClass, getComponentsByName, getComponentsByClass, change generic for property of object 61 | 62 | ### Features 63 | 64 | - add types for more methods and classes, add tests for all logics and options ([#6](https://github.com/EndyKaufman/nestjs-custom-injector/issues/6)) ([07dfa0e](https://github.com/EndyKaufman/nestjs-custom-injector/commit/07dfa0e1d76fe07c90b44ffd6ca0f9bb27988e53)) 65 | - append throw errors if provider not set, append support Promise default value, append support set factory for create instance of provider, append support Promise for factory, append init all need providers after load applications, append support typing for set propertyName on use service or decorators ([#6](https://github.com/EndyKaufman/nestjs-custom-injector/issues/6)) ([1a28dd2](https://github.com/EndyKaufman/nestjs-custom-injector/commit/1a28dd26b2fe6ada49d69e9d73a4cc3d487f19e9)) 66 | 67 | ### [1.0.1](https://github.com/EndyKaufman/nestjs-custom-injector/compare/v1.0.0...v1.0.1) (2022-02-23) 68 | 69 | ## 1.0.0 (2022-02-23) 70 | 71 | ### Features 72 | 73 | - create library with all needed basic logic ([11f5a93](https://github.com/EndyKaufman/nestjs-custom-injector/commit/11f5a93bde47f479456df5258118c8291267bc4c)) 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nestjs-custom-injector 2 | 3 | [![npm version](https://badge.fury.io/js/nestjs-custom-injector.svg)](https://badge.fury.io/js/nestjs-custom-injector) 4 | [![monthly downloads](https://badgen.net/npm/dm/nestjs-custom-injector)](https://www.npmjs.com/package/nestjs-custom-injector) 5 | 6 | ## Installation 7 | 8 | ```bash 9 | npm i --save nestjs-custom-injector 10 | ``` 11 | 12 | ## Links 13 | 14 | https://nestjs-custom-injector.site15.ru/api - Demo application with nestjs-custom-injector. 15 | https://github.com/EndyKaufman/nestjs-custom-injector-example - Example generated with nest cli for "Usage" sections in readme. 16 | 17 | ## Usage 18 | 19 | Create common interface with token in **animal-provider.interface.ts** 20 | 21 | ```typescript 22 | export const ANIMAL_PROVIDER = 'ANIMAL_PROVIDER'; 23 | 24 | export interface AnimalProviderInteface { 25 | type: string; 26 | say(): string; 27 | } 28 | ``` 29 | 30 | Create first type of logic for cats in **animal-cats.service.ts** 31 | 32 | ```typescript 33 | import { Injectable } from '@nestjs/common'; 34 | import { AnimalProviderInteface } from './animal-provider.interface'; 35 | 36 | @Injectable() 37 | export class AnimalCatsService implements AnimalProviderInteface { 38 | type = 'cat'; 39 | say(): string { 40 | return 'meow'; 41 | } 42 | } 43 | ``` 44 | 45 | Create second type of logic for dogs in **animal-dogs.service.ts** 46 | 47 | ```typescript 48 | import { Injectable } from '@nestjs/common'; 49 | import { AnimalProviderInteface } from './animal-provider.interface'; 50 | 51 | @Injectable() 52 | export class AnimalDogsService implements AnimalProviderInteface { 53 | type = 'dog'; 54 | say(): string { 55 | return 'woof'; 56 | } 57 | } 58 | ``` 59 | 60 | Create controller **animals.controller.ts** 61 | 62 | ```typescript 63 | import { Controller, Get, Query } from '@nestjs/common'; 64 | import { CustomInject } from 'nestjs-custom-injector'; 65 | import { 66 | AnimalProviderInteface, 67 | ANIMAL_PROVIDER, 68 | } from './animal-provider.interface'; 69 | 70 | @Controller('animals') 71 | export class AnimalsController { 72 | @CustomInject(ANIMAL_PROVIDER, { multi: true }) 73 | private animalProviders!: AnimalProviderInteface[]; 74 | 75 | @Get('animal-types') 76 | animalTypes() { 77 | return this.animalProviders.map((animalProvider) => animalProvider.type); 78 | } 79 | 80 | @Get('what-says-animals') 81 | whatSaysAnimals() { 82 | return this.animalProviders.map( 83 | (animal) => `${animal.type} say ${animal.say()}` 84 | ); 85 | } 86 | 87 | @Get('who-say') 88 | whoSay(@Query('voice') voice: string) { 89 | const animal = this.animalProviders.find( 90 | (animal) => animal.say() === voice 91 | ); 92 | if (!animal) { 93 | return { error: `I don't know who say ${voice}` }; 94 | } 95 | return `${animal.type} say ${animal.say()}`; 96 | } 97 | } 98 | ``` 99 | 100 | Append all logic to main app module **app.module.ts** 101 | 102 | ```typescript 103 | import { Module } from '@nestjs/common'; 104 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 105 | import { AnimalCatsService } from './animal-cats.service'; 106 | import { AnimalDogsService } from './animal-dogs.service'; 107 | import { AnimalsController } from './animals.controller'; 108 | 109 | @Module({ 110 | ... 111 | imports: [ 112 | ... 113 | CustomInjectorModule.forRoot(), 114 | CustomInjectorModule.forFeature({ 115 | providers: [{ provide: ANIMAL_PROVIDER, useClass: AnimalCatsService }], 116 | }), 117 | CustomInjectorModule.forFeature({ 118 | providers: [ 119 | { provide: ANIMAL_PROVIDER, useValue: new AnimalDogsService() }, 120 | ], 121 | }), 122 | ... 123 | ], 124 | controllers: [ 125 | ... 126 | AnimalsController 127 | ... 128 | ] 129 | ... 130 | }) 131 | export class AppModule {} 132 | ``` 133 | 134 | ## License 135 | 136 | MIT 137 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndyKaufman/nestjs-custom-injector/345ae8277f18f7c83fa8f9cb0bbd9828b771a207/apps/.gitkeep -------------------------------------------------------------------------------- /apps/demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/demo/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'demo', 4 | preset: '../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | testEnvironment: 'node', 11 | transform: { 12 | '^.+\\.[tj]s$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'js', 'html'], 15 | coverageDirectory: '../../coverage/apps/demo', 16 | }; 17 | -------------------------------------------------------------------------------- /apps/demo/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoot": "apps/demo/src", 3 | "projectType": "application", 4 | "targets": { 5 | "build": { 6 | "executor": "@nrwl/node:webpack", 7 | "outputs": ["{options.outputPath}"], 8 | "options": { 9 | "outputPath": "dist/apps/demo", 10 | "main": "apps/demo/src/main.ts", 11 | "tsConfig": "apps/demo/tsconfig.app.json", 12 | "assets": ["apps/demo/src/assets"], 13 | "webpackConfig": "apps/demo/webpack.config.js" 14 | }, 15 | "configurations": { 16 | "production": { 17 | "optimization": true, 18 | "extractLicenses": true, 19 | "inspect": false, 20 | "fileReplacements": [ 21 | { 22 | "replace": "apps/demo/src/environments/environment.ts", 23 | "with": "apps/demo/src/environments/environment.prod.ts" 24 | } 25 | ] 26 | } 27 | } 28 | }, 29 | "serve": { 30 | "executor": "@nrwl/node:node", 31 | "options": { 32 | "buildTarget": "demo:build" 33 | } 34 | }, 35 | "lint": { 36 | "executor": "@nrwl/linter:eslint", 37 | "outputs": ["{options.outputFile}"], 38 | "options": { 39 | "lintFilePatterns": ["apps/demo/**/*.ts"] 40 | } 41 | }, 42 | "test": { 43 | "executor": "@nrwl/jest:jest", 44 | "outputs": ["coverage/apps/demo"], 45 | "options": { 46 | "jestConfig": "apps/demo/jest.config.ts", 47 | "passWithNoTests": true 48 | } 49 | } 50 | }, 51 | "tags": [] 52 | } 53 | -------------------------------------------------------------------------------- /apps/demo/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndyKaufman/nestjs-custom-injector/345ae8277f18f7c83fa8f9cb0bbd9828b771a207/apps/demo/src/app/.gitkeep -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/abstract-class.controller.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 4 | import request from 'supertest'; 5 | import { AbstractClassModule } from './abstract-class.module'; 6 | 7 | describe('AbstractClassController (e2e)', () => { 8 | jest.setTimeout(3 * 60 * 1000); 9 | 10 | let app: INestApplication; 11 | 12 | beforeAll(async () => { 13 | const moduleRef = await Test.createTestingModule({ 14 | imports: [CustomInjectorModule.forRoot(), AbstractClassModule], 15 | }).compile(); 16 | app = moduleRef.createNestApplication(); 17 | await app.init(); 18 | }); 19 | 20 | describe('Demo - collect providers with string token type', () => { 21 | it("Get all types of animals (@CustomInject(AbstractAnimalProvider['type'], { multi: true, propertyName: 'type' }))", () => { 22 | return request(app.getHttpServer()) 23 | .get('/abstract-class/animal-types-with-inject') 24 | .expect(200) 25 | .expect(['dog', 'cat', 'horse', 'sheep', 'cow', 'duck', 'geese']); 26 | }); 27 | 28 | it('What says all animals (customInjectorService: CustomInjectorService)', () => { 29 | return request(app.getHttpServer()) 30 | .get('/abstract-class/what-says-animals') 31 | .expect(200) 32 | .expect([ 33 | 'dog say woof', 34 | 'cat say meow', 35 | 'horse say neigh', 36 | 'sheep say baa', 37 | 'cow say moo', 38 | 'duck say quack', 39 | 'geese say honk', 40 | ]); 41 | }); 42 | 43 | it('What says all animals (@Inject(AbstractAnimalProvider))', () => { 44 | return request(app.getHttpServer()) 45 | .get('/abstract-class/what-says-animals-original') 46 | .expect(200) 47 | .expect('cat say meow'); 48 | }); 49 | 50 | it('What says all animals (@CustomInject(AbstractAnimalProvider, { multi: true }))', () => { 51 | return request(app.getHttpServer()) 52 | .get('/abstract-class/what-says-animals-with-inject') 53 | .expect(200) 54 | .expect([ 55 | 'dog say woof', 56 | 'cat say meow', 57 | 'horse say neigh', 58 | 'sheep say baa', 59 | 'cow say moo', 60 | 'duck say quack', 61 | 'geese say honk', 62 | ]); 63 | }); 64 | 65 | it('What says all animals (@CustomInjector())', () => { 66 | return request(app.getHttpServer()) 67 | .get('/abstract-class/what-says-animals-with-injector') 68 | .expect(200) 69 | .expect([ 70 | 'dog say woof', 71 | 'cat say meow', 72 | 'horse say neigh', 73 | 'sheep say baa', 74 | 'cow say moo', 75 | 'duck say quack', 76 | 'geese say honk', 77 | ]); 78 | }); 79 | 80 | it('Who say (customInjectorService: CustomInjectorService)', () => { 81 | return request(app.getHttpServer()) 82 | .get('/abstract-class/who-say?voice=baa') 83 | .expect(200) 84 | .expect('sheep say baa'); 85 | }); 86 | 87 | it('Error in request who say (customInjectorService: CustomInjectorService)', () => { 88 | return request(app.getHttpServer()) 89 | .get('/abstract-class/who-say?voice=gav') 90 | .expect(200) 91 | .expect({ error: `I don't know who say gav` }); 92 | }); 93 | 94 | it("Who say (@Query('voice', CheckAnimalTypePipe)", () => { 95 | try { 96 | return request(app.getHttpServer()) 97 | .get('/abstract-class/who-say-with-validate-type?voice=baa') 98 | .expect(200) 99 | .expect('sheep say baa'); 100 | } catch (err) { 101 | console.log(err); 102 | } 103 | }); 104 | 105 | it("Error in request who say (@Query('voice', CheckAnimalTypePipe)", () => { 106 | try { 107 | return request(app.getHttpServer()) 108 | .get('/abstract-class/who-say-with-validate-type?voice=gav') 109 | .expect(400) 110 | .expect({ 111 | statusCode: 400, 112 | message: 'Validation failed (incorrect voice of animals)', 113 | error: 'Bad Request', 114 | }); 115 | } catch (err) { 116 | console.log(err); 117 | } 118 | }); 119 | 120 | it('Who say (@Inject(AbstractAnimalProvider))', () => { 121 | return request(app.getHttpServer()) 122 | .get('/abstract-class/who-say-original?voice=baa') 123 | .expect(200) 124 | .expect({ error: `I don't know who say baa` }); 125 | }); 126 | 127 | it('Who say (@CustomInject(AbstractAnimalProvider, { multi: true }))', () => { 128 | return request(app.getHttpServer()) 129 | .get('/abstract-class/who-say-with-inject?voice=baa') 130 | .expect(200) 131 | .expect('sheep say baa'); 132 | }); 133 | 134 | it('Error in request who say (@CustomInject(AbstractAnimalProvider, { multi: true }))', () => { 135 | return request(app.getHttpServer()) 136 | .get('/abstract-class/who-say-with-inject?voice=gav') 137 | .expect(200) 138 | .expect({ error: `I don't know who say gav` }); 139 | }); 140 | 141 | it('Who say (@CustomInjector())', () => { 142 | return request(app.getHttpServer()) 143 | .get('/abstract-class/who-say-with-injector?voice=baa') 144 | .expect(200) 145 | .expect('sheep say baa'); 146 | }); 147 | 148 | it('Error in request who say (@CustomInjector())', () => { 149 | return request(app.getHttpServer()) 150 | .get('/abstract-class/who-say-with-injector?voice=gav') 151 | .expect(200) 152 | .expect({ error: `I don't know who say gav` }); 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/abstract-class.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { 3 | CustomInjectorBootstrapService, 4 | CustomInjectorModule, 5 | } from 'nestjs-custom-injector'; 6 | import { AbstractClassController } from './abstract-class.controller'; 7 | import { AbstractClassModule } from './abstract-class.module'; 8 | 9 | describe('AbstractClassController (unit)', () => { 10 | jest.setTimeout(3 * 60 * 1000); 11 | 12 | let app: TestingModule; 13 | let controller: AbstractClassController; 14 | 15 | beforeAll(async () => { 16 | app = await Test.createTestingModule({ 17 | imports: [CustomInjectorModule.forRoot(), AbstractClassModule], 18 | }).compile(); 19 | await app 20 | .get(CustomInjectorBootstrapService) 21 | .onApplicationBootstrap(); 22 | controller = app.get(AbstractClassController); 23 | }); 24 | 25 | describe('Demo - collect providers with string token type', () => { 26 | it("Get all types of animals (@CustomInject(AbstractAnimalProvider['type'], { multi: true, propertyName: 'type' }))", () => { 27 | const result = controller.animalTypesWithInject(); 28 | expect([ 29 | 'dog', 30 | 'cat', 31 | 'horse', 32 | 'sheep', 33 | 'cow', 34 | 'duck', 35 | 'geese', 36 | ]).toMatchObject(result); 37 | }); 38 | 39 | it('What says all animals (customInjectorService: CustomInjectorService)', () => { 40 | const result = controller.whatSaysAnimals(); 41 | expect([ 42 | 'dog say woof', 43 | 'cat say meow', 44 | 'horse say neigh', 45 | 'sheep say baa', 46 | 'cow say moo', 47 | 'duck say quack', 48 | 'geese say honk', 49 | ]).toMatchObject(result); 50 | }); 51 | 52 | it('What says all animals (@Inject(AbstractAnimalProvider))', () => { 53 | const result = controller.whatSaysAnimalsOriginal(); 54 | expect('cat say meow').toEqual(result); 55 | }); 56 | 57 | it('What says all animals (@CustomInject(AbstractAnimalProvider, { multi: true }))', () => { 58 | const result = controller.whatSaysAnimalsWithInject(); 59 | expect([ 60 | 'dog say woof', 61 | 'cat say meow', 62 | 'horse say neigh', 63 | 'sheep say baa', 64 | 'cow say moo', 65 | 'duck say quack', 66 | 'geese say honk', 67 | ]).toMatchObject(result); 68 | }); 69 | 70 | it('What says all animals (@CustomInjector())', () => { 71 | const result = controller.whatSaysAnimalsWithInjector(); 72 | expect([ 73 | 'dog say woof', 74 | 'cat say meow', 75 | 'horse say neigh', 76 | 'sheep say baa', 77 | 'cow say moo', 78 | 'duck say quack', 79 | 'geese say honk', 80 | ]).toMatchObject(result); 81 | }); 82 | 83 | it('Who say (customInjectorService: CustomInjectorService)', () => { 84 | const result = controller.whoSay('baa'); 85 | expect('sheep say baa').toEqual(result); 86 | }); 87 | 88 | it('Error in request who say (customInjectorService: CustomInjectorService)', () => { 89 | const result = controller.whoSay('gav'); 90 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 91 | }); 92 | 93 | it('Who say (@Inject(AbstractAnimalProvider))', () => { 94 | const result = controller.whoSayOriginal('baa'); 95 | expect({ error: `I don't know who say baa` }).toMatchObject(result); 96 | }); 97 | 98 | it('Who say (@CustomInject(AbstractAnimalProvider, { multi: true }))', () => { 99 | const result = controller.whoSayWithInject('baa'); 100 | expect('sheep say baa').toEqual(result); 101 | }); 102 | 103 | it('Error in request who say (@CustomInject(AbstractAnimalProvider, { multi: true }))', () => { 104 | const result = controller.whoSayWithInject('gav'); 105 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 106 | }); 107 | 108 | it('Who say (@CustomInjector())', () => { 109 | const result = controller.whoSayWithInjector('baa'); 110 | expect('sheep say baa').toEqual(result); 111 | }); 112 | 113 | it('Error in request who say (@CustomInjector())', () => { 114 | const result = controller.whoSayWithInjector('gav'); 115 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/abstract-class.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Inject, Query } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { CustomInject } from 'nestjs-custom-injector'; 4 | import { AbstractClassService } from './abstract-class.service'; 5 | import { AbstractAnimalProvider } from './animal/animal.provider'; 6 | import { CheckAnimalVoicePipe } from './animal/check-animal-voice.pipe'; 7 | 8 | @ApiTags('abstract-class') 9 | @Controller('abstract-class') 10 | export class AbstractClassController { 11 | @CustomInject(AbstractAnimalProvider, { 12 | multi: true, 13 | propertyName: 'type', 14 | }) 15 | private animalProviderTypes!: AbstractAnimalProvider['type'][]; 16 | 17 | constructor( 18 | @Inject(AbstractAnimalProvider) 19 | private readonly animalProvider: AbstractAnimalProvider, 20 | private readonly abstractClassService: AbstractClassService 21 | ) {} 22 | 23 | @Get('animal-types-with-inject') 24 | animalTypesWithInject() { 25 | return this.animalProviderTypes; 26 | } 27 | 28 | @Get('what-says-animals') 29 | whatSaysAnimals() { 30 | return this.abstractClassService.whatSaysAnimals(); 31 | } 32 | 33 | @Get('what-says-animals-original') 34 | whatSaysAnimalsOriginal() { 35 | return `${this.animalProvider.type} say ${this.animalProvider.say()}`; 36 | } 37 | 38 | @Get('what-says-animals-with-inject') 39 | whatSaysAnimalsWithInject() { 40 | return this.abstractClassService.whatSaysAnimalsWithInject(); 41 | } 42 | 43 | @Get('what-says-animals-with-injector') 44 | whatSaysAnimalsWithInjector() { 45 | return this.abstractClassService.whatSaysAnimalsWithInjector(); 46 | } 47 | 48 | @Get('who-say') 49 | whoSay(@Query('voice') voice: string) { 50 | return this.abstractClassService.whoSay(voice); 51 | } 52 | 53 | @Get('who-say-with-validate-type') 54 | whoSayWithValidateType(@Query('voice', CheckAnimalVoicePipe) voice: string) { 55 | return this.abstractClassService.whoSay(voice); 56 | } 57 | 58 | @Get('who-say-original') 59 | whoSayOriginal(@Query('voice') voice: string) { 60 | return this.animalProvider.say() === voice 61 | ? `${this.animalProvider.type} say ${this.animalProvider.say()}` 62 | : { error: `I don't know who say ${voice}` }; 63 | } 64 | 65 | @Get('who-say-with-inject') 66 | whoSayWithInject(@Query('voice') voice: string) { 67 | return this.abstractClassService.whoSayWithInject(voice); 68 | } 69 | 70 | @Get('who-say-with-injector') 71 | whoSayWithInjector(@Query('voice') voice: string) { 72 | return this.abstractClassService.whoSayWithInjector(voice); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/abstract-class.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AnimalCatsModule } from './animal-cats/animal-cats.module'; 3 | import { AnimalCowsModule } from './animal-cows/animal-cows.module'; 4 | import { AnimalDogsModule } from './animal-dogs/animal-dogs.module'; 5 | import { AnimalDucksAndGeesesModule } from './animal-ducks-and-geeses/animal-ducks-and-geeses.module'; 6 | import { AnimalHorsesModule } from './animal-horses/animal-horses.module'; 7 | import { AnimalSheepsModule } from './animal-sheeps/animal-sheeps.module'; 8 | import { AnimalModule } from './animal/animal.module'; 9 | import { AbstractClassController } from './abstract-class.controller'; 10 | import { AbstractClassService } from './abstract-class.service'; 11 | 12 | @Module({ 13 | imports: [ 14 | AnimalModule, 15 | AnimalDogsModule, 16 | AnimalCatsModule, 17 | AnimalHorsesModule, 18 | AnimalSheepsModule, 19 | AnimalCowsModule, 20 | AnimalDucksAndGeesesModule, 21 | ], 22 | controllers: [AbstractClassController], 23 | providers: [AbstractClassService], 24 | }) 25 | export class AbstractClassModule {} 26 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/abstract-class.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | CustomInject, 4 | CustomInjector, 5 | CustomInjectorService, 6 | } from 'nestjs-custom-injector'; 7 | import { AbstractAnimalProvider } from './animal/animal.provider'; 8 | import { AbstractAnimalService } from './animal/animal.service'; 9 | 10 | @Injectable() 11 | export class AbstractClassService { 12 | @CustomInjector() 13 | private customInjector!: CustomInjectorService; 14 | 15 | @CustomInject(AbstractAnimalProvider, { multi: true }) 16 | private animalProviders!: AbstractAnimalProvider[]; 17 | 18 | constructor(private readonly animalService: AbstractAnimalService) {} 19 | 20 | whatSaysAnimalsWithInjector() { 21 | return this.customInjector 22 | .getProviders(AbstractAnimalProvider) 23 | .map((animal) => `${animal.type} say ${animal.say()}`); 24 | } 25 | 26 | whatSaysAnimalsWithInject() { 27 | return this.animalProviders.map( 28 | (animal) => `${animal.type} say ${animal.say()}` 29 | ); 30 | } 31 | 32 | whatSaysAnimals() { 33 | return this.animalService 34 | .getAnimals() 35 | .map((animal) => `${animal.type} say ${animal.say()}`); 36 | } 37 | 38 | whoSayWithInjector(voice: string) { 39 | const animal = this.customInjector 40 | .getProviders(AbstractAnimalProvider) 41 | .find((animal) => animal.say() === voice); 42 | if (!animal) { 43 | return { error: `I don't know who say ${voice}` }; 44 | } 45 | return `${animal.type} say ${animal.say()}`; 46 | } 47 | 48 | whoSayWithInject(voice: string) { 49 | const animal = this.animalProviders.find( 50 | (animal) => animal.say() === voice 51 | ); 52 | if (!animal) { 53 | return { error: `I don't know who say ${voice}` }; 54 | } 55 | return `${animal.type} say ${animal.say()}`; 56 | } 57 | 58 | whoSay(voice: string) { 59 | const animal = this.animalService 60 | .getAnimals() 61 | .find((animal) => animal.say() === voice); 62 | if (!animal) { 63 | return { error: `I don't know who say ${voice}` }; 64 | } 65 | return `${animal.type} say ${animal.say()}`; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-cats/animal-cats.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | import { AnimalCatsService } from './animal-cats.service'; 4 | 5 | @Module({ 6 | providers: [ 7 | { provide: AbstractAnimalProvider, useValue: new AnimalCatsService() }, 8 | ], 9 | exports: [AbstractAnimalProvider], 10 | }) 11 | export class AnimalCatsModule {} 12 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-cats/animal-cats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalCatsService extends AbstractAnimalProvider { 6 | type = 'cat'; 7 | say(): string { 8 | return 'meow'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-cows/animal-cows.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { lastValueFrom, Observable } from 'rxjs'; 3 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 4 | import { AnimalCowsService } from './animal-cows.service'; 5 | 6 | @Module({ 7 | providers: [ 8 | { 9 | provide: AbstractAnimalProvider, 10 | useFactory: () => 11 | lastValueFrom( 12 | new Observable((observer) => { 13 | setTimeout(() => { 14 | observer.next(new AnimalCowsService()); 15 | observer.complete(); 16 | }, 5000); 17 | }) 18 | ), 19 | }, 20 | ], 21 | }) 22 | export class AnimalCowsModule {} 23 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-cows/animal-cows.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalCowsService extends AbstractAnimalProvider { 6 | type = 'cow'; 7 | say(): string { 8 | return 'moo'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-dogs/animal-dogs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | import { AnimalDogsService } from './animal-dogs.service'; 4 | 5 | @Module({ 6 | providers: [{ provide: AbstractAnimalProvider, useClass: AnimalDogsService }], 7 | }) 8 | export class AnimalDogsModule {} 9 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-dogs/animal-dogs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalDogsService extends AbstractAnimalProvider { 6 | type = 'dog'; 7 | say(): string { 8 | return 'woof'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-ducks-and-geeses/animal-ducks-and-geeses.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 4 | import { AnimalDucksService } from './animal-ducks.service'; 5 | import { AnimalGeesesService } from './animal-geeses.service'; 6 | 7 | @Module({ 8 | imports: [ 9 | CustomInjectorModule.forFeature({ 10 | providers: [ 11 | { provide: AbstractAnimalProvider, useClass: AnimalDucksService }, 12 | ], 13 | }), 14 | CustomInjectorModule.forFeature({ 15 | providers: [ 16 | { 17 | provide: AbstractAnimalProvider, 18 | useValue: new AnimalGeesesService(), 19 | }, 20 | ], 21 | }), 22 | ], 23 | }) 24 | export class AnimalDucksAndGeesesModule {} 25 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-ducks-and-geeses/animal-ducks.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalDucksService extends AbstractAnimalProvider { 6 | type = 'duck'; 7 | say(): string { 8 | return 'quack'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-ducks-and-geeses/animal-geeses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalGeesesService extends AbstractAnimalProvider { 6 | type = 'geese'; 7 | say(): string { 8 | return 'honk'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-horses/animal-horses.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 4 | import { AnimalHorsesService } from './animal-horses.service'; 5 | 6 | @Module({ 7 | imports: [CustomInjectorModule], 8 | providers: [ 9 | { 10 | provide: AbstractAnimalProvider, 11 | useFactory: () => new AnimalHorsesService(), 12 | }, 13 | ], 14 | exports: [CustomInjectorModule], 15 | }) 16 | export class AnimalHorsesModule {} 17 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-horses/animal-horses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalHorsesService extends AbstractAnimalProvider { 6 | type = 'horse'; 7 | say(): string { 8 | return 'neigh'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-sheeps/animal-goats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalGoatsService extends AbstractAnimalProvider { 6 | type = 'goat'; 7 | say(): string { 8 | return 'baa'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-sheeps/animal-sheeps.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | import { AnimalGoatsService } from './animal-goats.service'; 4 | import { AnimalSheepsService } from './animal-sheeps.service'; 5 | 6 | @Module({ 7 | providers: [ 8 | { 9 | provide: AbstractAnimalProvider, 10 | useValue: new AnimalGoatsService(), 11 | }, 12 | { 13 | provide: AbstractAnimalProvider, 14 | useFactory: () => 15 | new Promise((resolve) => 16 | setTimeout(() => resolve(new AnimalSheepsService()), 5000) 17 | ), 18 | }, 19 | ], 20 | }) 21 | export class AnimalSheepsModule {} 22 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal-sheeps/animal-sheeps.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AbstractAnimalProvider } from '../animal/animal.provider'; 3 | 4 | @Injectable() 5 | export class AnimalSheepsService extends AbstractAnimalProvider { 6 | type = 'sheep'; 7 | say(): string { 8 | return 'baa'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal/animal.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { AbstractAnimalService } from './animal.service'; 4 | 5 | @Module({ 6 | imports: [CustomInjectorModule], 7 | providers: [AbstractAnimalService], 8 | exports: [AbstractAnimalService], 9 | }) 10 | export class AnimalModule {} 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal/animal.provider.ts: -------------------------------------------------------------------------------- 1 | export abstract class AbstractAnimalProvider { 2 | abstract type: string; 3 | abstract say(): string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal/animal.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CustomInjectorService } from 'nestjs-custom-injector'; 3 | import { AbstractAnimalProvider } from './animal.provider'; 4 | 5 | @Injectable() 6 | export class AbstractAnimalService { 7 | constructor(private readonly customInjectorService: CustomInjectorService) {} 8 | 9 | getAnimals() { 10 | return this.customInjectorService.getProviders( 11 | AbstractAnimalProvider 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/demo/src/app/abstract-class/animal/check-animal-voice.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpStatus, 3 | Injectable, 4 | Optional, 5 | ParseIntPipeOptions, 6 | PipeTransform, 7 | } from '@nestjs/common'; 8 | import { 9 | ErrorHttpStatusCode, 10 | HttpErrorByCode, 11 | } from '@nestjs/common/utils/http-error-by-code.util'; 12 | import { CustomInject } from 'nestjs-custom-injector'; 13 | import { AbstractAnimalProvider } from './animal.provider'; 14 | 15 | export interface CheckAnimalTypePipeOptions { 16 | errorHttpStatusCode?: ErrorHttpStatusCode; 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | exceptionFactory?: (error: string) => any; 19 | } 20 | 21 | @Injectable() 22 | export class CheckAnimalVoicePipe implements PipeTransform { 23 | @CustomInject(AbstractAnimalProvider, { multi: true }) 24 | protected animalProviders!: AbstractAnimalProvider[]; 25 | 26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 27 | protected exceptionFactory: (error: string) => any; 28 | 29 | constructor(@Optional() options?: ParseIntPipeOptions) { 30 | options = options || {}; 31 | const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } = 32 | options; 33 | 34 | this.exceptionFactory = 35 | exceptionFactory || 36 | ((error) => new HttpErrorByCode[errorHttpStatusCode](error)); 37 | } 38 | 39 | async transform(voice: string): Promise { 40 | if ( 41 | !this.animalProviders 42 | .map((animalProvider) => animalProvider.say()) 43 | .includes(voice) 44 | ) { 45 | throw this.exceptionFactory( 46 | 'Validation failed (incorrect voice of animals)' 47 | ); 48 | } 49 | return voice; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /apps/demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { AbstractClassModule } from './abstract-class/abstract-class.module'; 4 | import { StringTokenModule } from './string-token/string-token.module'; 5 | import { SymbolTokenModule } from './symbol-token/symbol-token.module'; 6 | 7 | @Module({ 8 | imports: [ 9 | CustomInjectorModule.forRoot(), 10 | StringTokenModule, 11 | AbstractClassModule, 12 | SymbolTokenModule, 13 | ], 14 | }) 15 | export class AppModule {} 16 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-cats/animal-cats.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 3 | import { AnimalCatsService } from './animal-cats.service'; 4 | 5 | @Module({ 6 | providers: [{ provide: ANIMAL_PROVIDER, useValue: new AnimalCatsService() }], 7 | exports: [ANIMAL_PROVIDER], 8 | }) 9 | export class AnimalCatsModule {} 10 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-cats/animal-cats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalCatsService implements AnimalProviderInteface { 6 | type = 'cat'; 7 | say(): string { 8 | return 'meow'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-cows/animal-cows.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { lastValueFrom, Observable } from 'rxjs'; 3 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 4 | import { AnimalCowsService } from './animal-cows.service'; 5 | 6 | @Module({ 7 | providers: [ 8 | { 9 | provide: ANIMAL_PROVIDER, 10 | useFactory: () => 11 | lastValueFrom( 12 | new Observable((observer) => { 13 | setTimeout(() => { 14 | observer.next(new AnimalCowsService()); 15 | observer.complete(); 16 | }, 5000); 17 | }) 18 | ), 19 | }, 20 | ], 21 | }) 22 | export class AnimalCowsModule {} 23 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-cows/animal-cows.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalCowsService implements AnimalProviderInteface { 6 | type = 'cow'; 7 | say(): string { 8 | return 'moo'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-dogs/animal-dogs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 3 | import { AnimalDogsService } from './animal-dogs.service'; 4 | 5 | @Module({ 6 | providers: [{ provide: ANIMAL_PROVIDER, useClass: AnimalDogsService }], 7 | }) 8 | export class AnimalDogsModule {} 9 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-dogs/animal-dogs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalDogsService implements AnimalProviderInteface { 6 | type = 'dog'; 7 | say(): string { 8 | return 'woof'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-ducks-and-geeses/animal-ducks-and-geeses.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 4 | import { AnimalDucksService } from './animal-ducks.service'; 5 | import { AnimalGeesesService } from './animal-geeses.service'; 6 | 7 | @Module({ 8 | imports: [ 9 | CustomInjectorModule.forFeature({ 10 | providers: [{ provide: ANIMAL_PROVIDER, useClass: AnimalDucksService }], 11 | }), 12 | CustomInjectorModule.forFeature({ 13 | providers: [ 14 | { provide: ANIMAL_PROVIDER, useValue: new AnimalGeesesService() }, 15 | ], 16 | }), 17 | ], 18 | }) 19 | export class AnimalDucksAndGeesesModule {} 20 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-ducks-and-geeses/animal-ducks.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalDucksService implements AnimalProviderInteface { 6 | type = 'duck'; 7 | say(): string { 8 | return 'quack'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-ducks-and-geeses/animal-geeses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalGeesesService implements AnimalProviderInteface { 6 | type = 'geese'; 7 | say(): string { 8 | return 'honk'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-horses/animal-horses.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 4 | import { AnimalHorsesService } from './animal-horses.service'; 5 | 6 | @Module({ 7 | imports: [CustomInjectorModule], 8 | providers: [ 9 | { provide: ANIMAL_PROVIDER, useFactory: () => new AnimalHorsesService() }, 10 | ], 11 | exports: [CustomInjectorModule], 12 | }) 13 | export class AnimalHorsesModule {} 14 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-horses/animal-horses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalHorsesService implements AnimalProviderInteface { 6 | type = 'horse'; 7 | say(): string { 8 | return 'neigh'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-sheeps/animal-goats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalGoatsService implements AnimalProviderInteface { 6 | type = 'goat'; 7 | say(): string { 8 | return 'baa'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-sheeps/animal-sheeps.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 3 | import { AnimalGoatsService } from './animal-goats.service'; 4 | import { AnimalSheepsService } from './animal-sheeps.service'; 5 | 6 | @Module({ 7 | providers: [ 8 | { 9 | provide: ANIMAL_PROVIDER, 10 | useValue: new AnimalGoatsService(), 11 | }, 12 | { 13 | provide: ANIMAL_PROVIDER, 14 | useFactory: () => 15 | new Promise((resolve) => 16 | setTimeout(() => resolve(new AnimalSheepsService()), 5000) 17 | ), 18 | }, 19 | ], 20 | }) 21 | export class AnimalSheepsModule {} 22 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal-sheeps/animal-sheeps.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalSheepsService implements AnimalProviderInteface { 6 | type = 'sheep'; 7 | say(): string { 8 | return 'baa'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal/animal-provider.interface.ts: -------------------------------------------------------------------------------- 1 | export const ANIMAL_PROVIDER = 'ANIMAL_PROVIDER'; 2 | 3 | export interface AnimalProviderInteface { 4 | type: string; 5 | say(): string; 6 | } 7 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal/animal.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { StringAnimalService } from './animal.service'; 4 | 5 | @Module({ 6 | imports: [CustomInjectorModule], 7 | providers: [StringAnimalService], 8 | exports: [StringAnimalService], 9 | }) 10 | export class AnimalModule {} 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal/animal.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CustomInjectorService } from 'nestjs-custom-injector'; 3 | import { 4 | AnimalProviderInteface, 5 | ANIMAL_PROVIDER, 6 | } from '../animal/animal-provider.interface'; 7 | 8 | @Injectable() 9 | export class StringAnimalService { 10 | constructor(private readonly customInjectorService: CustomInjectorService) {} 11 | 12 | getAnimals() { 13 | return this.customInjectorService.getProviders( 14 | ANIMAL_PROVIDER 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/animal/check-animal-voice.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpStatus, 3 | Injectable, 4 | Optional, 5 | ParseIntPipeOptions, 6 | PipeTransform, 7 | } from '@nestjs/common'; 8 | import { 9 | ErrorHttpStatusCode, 10 | HttpErrorByCode, 11 | } from '@nestjs/common/utils/http-error-by-code.util'; 12 | import { CustomInject } from 'nestjs-custom-injector'; 13 | import { 14 | AnimalProviderInteface, 15 | ANIMAL_PROVIDER, 16 | } from './animal-provider.interface'; 17 | 18 | export interface CheckAnimalTypePipeOptions { 19 | errorHttpStatusCode?: ErrorHttpStatusCode; 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | exceptionFactory?: (error: string) => any; 22 | } 23 | 24 | @Injectable() 25 | export class CheckAnimalVoicePipe implements PipeTransform { 26 | @CustomInject(ANIMAL_PROVIDER, { multi: true }) 27 | protected animalProviders!: AnimalProviderInteface[]; 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | protected exceptionFactory: (error: string) => any; 31 | 32 | constructor(@Optional() options?: ParseIntPipeOptions) { 33 | options = options || {}; 34 | const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } = 35 | options; 36 | 37 | this.exceptionFactory = 38 | exceptionFactory || 39 | ((error) => new HttpErrorByCode[errorHttpStatusCode](error)); 40 | } 41 | 42 | async transform(voice: string): Promise { 43 | if ( 44 | !this.animalProviders 45 | .map((animalProvider) => animalProvider.say()) 46 | .includes(voice) 47 | ) { 48 | throw this.exceptionFactory( 49 | 'Validation failed (incorrect voice of animals)' 50 | ); 51 | } 52 | return voice; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/string-token.controller.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 4 | import request from 'supertest'; 5 | import { StringTokenModule } from './string-token.module'; 6 | 7 | describe('StringTokenController (e2e)', () => { 8 | jest.setTimeout(3 * 60 * 1000); 9 | 10 | let app: INestApplication; 11 | 12 | beforeAll(async () => { 13 | const moduleRef = await Test.createTestingModule({ 14 | imports: [CustomInjectorModule.forRoot(), StringTokenModule], 15 | }).compile(); 16 | app = moduleRef.createNestApplication(); 17 | await app.init(); 18 | }); 19 | 20 | describe('Demo - collect providers with string token type', () => { 21 | it("Get all types of animals (@CustomInject(ANIMAL_PROVIDER, { multi: true, propertyName: 'type' }))", () => { 22 | return request(app.getHttpServer()) 23 | .get('/string-token/animal-types-with-inject') 24 | .expect(200) 25 | .expect(['dog', 'cat', 'horse', 'sheep', 'cow', 'duck', 'geese']); 26 | }); 27 | 28 | it('What says all animals (customInjectorService: CustomInjectorService)', () => { 29 | return request(app.getHttpServer()) 30 | .get('/string-token/what-says-animals') 31 | .expect(200) 32 | .expect([ 33 | 'dog say woof', 34 | 'cat say meow', 35 | 'horse say neigh', 36 | 'sheep say baa', 37 | 'cow say moo', 38 | 'duck say quack', 39 | 'geese say honk', 40 | ]); 41 | }); 42 | 43 | it('What says all animals (@Inject(ANIMAL_PROVIDER))', () => { 44 | return request(app.getHttpServer()) 45 | .get('/string-token/what-says-animals-original') 46 | .expect(200) 47 | .expect('cat say meow'); 48 | }); 49 | 50 | it('What says all animals (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 51 | return request(app.getHttpServer()) 52 | .get('/string-token/what-says-animals-with-inject') 53 | .expect(200) 54 | .expect([ 55 | 'dog say woof', 56 | 'cat say meow', 57 | 'horse say neigh', 58 | 'sheep say baa', 59 | 'cow say moo', 60 | 'duck say quack', 61 | 'geese say honk', 62 | ]); 63 | }); 64 | 65 | it('What says all animals (@CustomInjector())', () => { 66 | return request(app.getHttpServer()) 67 | .get('/string-token/what-says-animals-with-injector') 68 | .expect(200) 69 | .expect([ 70 | 'dog say woof', 71 | 'cat say meow', 72 | 'horse say neigh', 73 | 'sheep say baa', 74 | 'cow say moo', 75 | 'duck say quack', 76 | 'geese say honk', 77 | ]); 78 | }); 79 | 80 | it('Who say (customInjectorService: CustomInjectorService)', () => { 81 | return request(app.getHttpServer()) 82 | .get('/string-token/who-say?voice=baa') 83 | .expect(200) 84 | .expect('sheep say baa'); 85 | }); 86 | 87 | it('Error in request who say (customInjectorService: CustomInjectorService)', () => { 88 | return request(app.getHttpServer()) 89 | .get('/string-token/who-say?voice=gav') 90 | .expect(200) 91 | .expect({ error: `I don't know who say gav` }); 92 | }); 93 | 94 | it("Who say (@Query('voice', CheckAnimalTypePipe)", () => { 95 | try { 96 | return request(app.getHttpServer()) 97 | .get('/string-token/who-say-with-validate-type?voice=baa') 98 | .expect(200) 99 | .expect('sheep say baa'); 100 | } catch (err) { 101 | console.log(err); 102 | } 103 | }); 104 | 105 | it("Error in request who say (@Query('voice', CheckAnimalTypePipe)", () => { 106 | try { 107 | return request(app.getHttpServer()) 108 | .get('/string-token/who-say-with-validate-type?voice=gav') 109 | .expect(400) 110 | .expect({ 111 | statusCode: 400, 112 | message: 'Validation failed (incorrect voice of animals)', 113 | error: 'Bad Request', 114 | }); 115 | } catch (err) { 116 | console.log(err); 117 | } 118 | }); 119 | 120 | it('Who say (@Inject(ANIMAL_PROVIDER))', () => { 121 | return request(app.getHttpServer()) 122 | .get('/string-token/who-say-original?voice=baa') 123 | .expect(200) 124 | .expect({ error: `I don't know who say baa` }); 125 | }); 126 | 127 | it('Who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 128 | return request(app.getHttpServer()) 129 | .get('/string-token/who-say-with-inject?voice=baa') 130 | .expect(200) 131 | .expect('sheep say baa'); 132 | }); 133 | 134 | it('Error in request who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 135 | return request(app.getHttpServer()) 136 | .get('/string-token/who-say-with-inject?voice=gav') 137 | .expect(200) 138 | .expect({ error: `I don't know who say gav` }); 139 | }); 140 | 141 | it('Who say (@CustomInjector())', () => { 142 | return request(app.getHttpServer()) 143 | .get('/string-token/who-say-with-injector?voice=baa') 144 | .expect(200) 145 | .expect('sheep say baa'); 146 | }); 147 | 148 | it('Error in request who say (@CustomInjector())', () => { 149 | return request(app.getHttpServer()) 150 | .get('/string-token/who-say-with-injector?voice=gav') 151 | .expect(200) 152 | .expect({ error: `I don't know who say gav` }); 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/string-token.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { StringTokenController } from './string-token.controller'; 4 | import { StringTokenModule } from './string-token.module'; 5 | 6 | describe('StringTokenController (unit)', () => { 7 | jest.setTimeout(3 * 60 * 1000); 8 | 9 | let app: TestingModule; 10 | let controller: StringTokenController; 11 | 12 | beforeAll(async () => { 13 | app = await Test.createTestingModule({ 14 | imports: [CustomInjectorModule.forRoot(), StringTokenModule], 15 | }).compile(); 16 | await app.init(); 17 | controller = app.get(StringTokenController); 18 | }); 19 | 20 | describe('Demo - collect providers with string token type', () => { 21 | it("Get all types of animals (@CustomInject(ANIMAL_PROVIDER, { multi: true, propertyName: 'type' }))", () => { 22 | const result = controller.animalTypesWithInject(); 23 | expect([ 24 | 'dog', 25 | 'cat', 26 | 'horse', 27 | 'sheep', 28 | 'cow', 29 | 'duck', 30 | 'geese', 31 | ]).toMatchObject(result); 32 | }); 33 | 34 | it('What says all animals (customInjectorService: CustomInjectorService)', () => { 35 | const result = controller.whatSaysAnimals(); 36 | expect([ 37 | 'dog say woof', 38 | 'cat say meow', 39 | 'horse say neigh', 40 | 'sheep say baa', 41 | 'cow say moo', 42 | 'duck say quack', 43 | 'geese say honk', 44 | ]).toMatchObject(result); 45 | }); 46 | 47 | it('What says all animals (@Inject(ANIMAL_PROVIDER))', () => { 48 | const result = controller.whatSaysAnimalsOriginal(); 49 | expect('cat say meow').toEqual(result); 50 | }); 51 | 52 | it('What says all animals (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 53 | const result = controller.whatSaysAnimalsWithInject(); 54 | expect([ 55 | 'dog say woof', 56 | 'cat say meow', 57 | 'horse say neigh', 58 | 'sheep say baa', 59 | 'cow say moo', 60 | 'duck say quack', 61 | 'geese say honk', 62 | ]).toMatchObject(result); 63 | }); 64 | 65 | it('What says all animals (@CustomInjector())', () => { 66 | const result = controller.whatSaysAnimalsWithInjector(); 67 | expect([ 68 | 'dog say woof', 69 | 'cat say meow', 70 | 'horse say neigh', 71 | 'sheep say baa', 72 | 'cow say moo', 73 | 'duck say quack', 74 | 'geese say honk', 75 | ]).toMatchObject(result); 76 | }); 77 | 78 | it('Who say (customInjectorService: CustomInjectorService)', () => { 79 | const result = controller.whoSay('baa'); 80 | expect('sheep say baa').toEqual(result); 81 | }); 82 | 83 | it('Error in request who say (customInjectorService: CustomInjectorService)', () => { 84 | const result = controller.whoSay('gav'); 85 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 86 | }); 87 | 88 | it('Who say (@Inject(ANIMAL_PROVIDER))', () => { 89 | const result = controller.whoSayOriginal('baa'); 90 | expect({ error: `I don't know who say baa` }).toMatchObject(result); 91 | }); 92 | 93 | it('Who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 94 | const result = controller.whoSayWithInject('baa'); 95 | expect('sheep say baa').toEqual(result); 96 | }); 97 | 98 | it('Error in request who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 99 | const result = controller.whoSayWithInject('gav'); 100 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 101 | }); 102 | 103 | it('Who say (@CustomInjector())', () => { 104 | const result = controller.whoSayWithInjector('baa'); 105 | expect('sheep say baa').toEqual(result); 106 | }); 107 | 108 | it('Error in request who say (@CustomInjector())', () => { 109 | const result = controller.whoSayWithInjector('gav'); 110 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/string-token.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Inject, Query } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { CustomInject } from 'nestjs-custom-injector'; 4 | import { 5 | AnimalProviderInteface, 6 | ANIMAL_PROVIDER, 7 | } from './animal/animal-provider.interface'; 8 | import { CheckAnimalVoicePipe } from './animal/check-animal-voice.pipe'; 9 | import { StringTokenService } from './string-token.service'; 10 | 11 | @ApiTags('string-token') 12 | @Controller('string-token') 13 | export class StringTokenController { 14 | @CustomInject(ANIMAL_PROVIDER, { 15 | multi: true, 16 | propertyName: 'type', 17 | }) 18 | private animalProviderTypes!: AnimalProviderInteface['type'][]; 19 | 20 | constructor( 21 | @Inject(ANIMAL_PROVIDER) 22 | private readonly animalProvider: AnimalProviderInteface, 23 | private readonly stringTokenService: StringTokenService 24 | ) {} 25 | 26 | @Get('animal-types-with-inject') 27 | animalTypesWithInject() { 28 | return this.animalProviderTypes; 29 | } 30 | 31 | @Get('what-says-animals') 32 | whatSaysAnimals() { 33 | return this.stringTokenService.whatSaysAnimals(); 34 | } 35 | 36 | @Get('what-says-animals-original') 37 | whatSaysAnimalsOriginal() { 38 | return `${this.animalProvider.type} say ${this.animalProvider.say()}`; 39 | } 40 | 41 | @Get('what-says-animals-with-inject') 42 | whatSaysAnimalsWithInject() { 43 | return this.stringTokenService.whatSaysAnimalsWithInject(); 44 | } 45 | 46 | @Get('what-says-animals-with-injector') 47 | whatSaysAnimalsWithInjector() { 48 | return this.stringTokenService.whatSaysAnimalsWithInjector(); 49 | } 50 | 51 | @Get('who-say') 52 | whoSay(@Query('voice') voice: string) { 53 | return this.stringTokenService.whoSay(voice); 54 | } 55 | 56 | @Get('who-say-with-validate-type') 57 | whoSayWithValidateType(@Query('voice', CheckAnimalVoicePipe) voice: string) { 58 | return this.stringTokenService.whoSay(voice); 59 | } 60 | 61 | @Get('who-say-original') 62 | whoSayOriginal(@Query('voice') voice: string) { 63 | return this.animalProvider.say() === voice 64 | ? `${this.animalProvider.type} say ${this.animalProvider.say()}` 65 | : { error: `I don't know who say ${voice}` }; 66 | } 67 | 68 | @Get('who-say-with-inject') 69 | whoSayWithInject(@Query('voice') voice: string) { 70 | return this.stringTokenService.whoSayWithInject(voice); 71 | } 72 | 73 | @Get('who-say-with-injector') 74 | whoSayWithInjector(@Query('voice') voice: string) { 75 | return this.stringTokenService.whoSayWithInjector(voice); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/string-token.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AnimalCatsModule } from './animal-cats/animal-cats.module'; 3 | import { AnimalCowsModule } from './animal-cows/animal-cows.module'; 4 | import { AnimalDogsModule } from './animal-dogs/animal-dogs.module'; 5 | import { AnimalDucksAndGeesesModule } from './animal-ducks-and-geeses/animal-ducks-and-geeses.module'; 6 | import { AnimalHorsesModule } from './animal-horses/animal-horses.module'; 7 | import { AnimalSheepsModule } from './animal-sheeps/animal-sheeps.module'; 8 | import { AnimalModule } from './animal/animal.module'; 9 | import { StringTokenController } from './string-token.controller'; 10 | import { StringTokenService } from './string-token.service'; 11 | 12 | @Module({ 13 | imports: [ 14 | AnimalModule, 15 | AnimalDogsModule, 16 | AnimalCatsModule, 17 | AnimalHorsesModule, 18 | AnimalSheepsModule, 19 | AnimalCowsModule, 20 | AnimalDucksAndGeesesModule, 21 | ], 22 | controllers: [StringTokenController], 23 | providers: [StringTokenService], 24 | }) 25 | export class StringTokenModule {} 26 | -------------------------------------------------------------------------------- /apps/demo/src/app/string-token/string-token.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | CustomInject, 4 | CustomInjector, 5 | CustomInjectorService, 6 | } from 'nestjs-custom-injector'; 7 | import { 8 | AnimalProviderInteface, 9 | ANIMAL_PROVIDER, 10 | } from './animal/animal-provider.interface'; 11 | import { StringAnimalService } from './animal/animal.service'; 12 | 13 | @Injectable() 14 | export class StringTokenService { 15 | @CustomInjector() 16 | private customInjector!: CustomInjectorService; 17 | 18 | @CustomInject(ANIMAL_PROVIDER, { multi: true }) 19 | private animalProviders!: AnimalProviderInteface[]; 20 | 21 | constructor(private readonly animalService: StringAnimalService) {} 22 | 23 | whatSaysAnimalsWithInjector() { 24 | return this.customInjector 25 | .getProviders(ANIMAL_PROVIDER) 26 | .map((animal) => `${animal.type} say ${animal.say()}`); 27 | } 28 | 29 | whatSaysAnimalsWithInject() { 30 | return this.animalProviders.map( 31 | (animal) => `${animal.type} say ${animal.say()}` 32 | ); 33 | } 34 | 35 | async whatSaysAnimals() { 36 | return this.animalService 37 | .getAnimals() 38 | .map((animal) => `${animal.type} say ${animal.say()}`); 39 | } 40 | 41 | whoSayWithInjector(voice: string) { 42 | const animal = this.customInjector 43 | .getProviders(ANIMAL_PROVIDER) 44 | .find((animal) => animal.say() === voice); 45 | if (!animal) { 46 | return { error: `I don't know who say ${voice}` }; 47 | } 48 | return `${animal.type} say ${animal.say()}`; 49 | } 50 | 51 | whoSayWithInject(voice: string) { 52 | const animal = this.animalProviders.find( 53 | (animal) => animal.say() === voice 54 | ); 55 | if (!animal) { 56 | return { error: `I don't know who say ${voice}` }; 57 | } 58 | return `${animal.type} say ${animal.say()}`; 59 | } 60 | 61 | whoSay(voice: string) { 62 | const animal = this.animalService 63 | .getAnimals() 64 | .find((animal) => animal.say() === voice); 65 | if (!animal) { 66 | return { error: `I don't know who say ${voice}` }; 67 | } 68 | return `${animal.type} say ${animal.say()}`; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-cats/animal-cats.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 3 | import { AnimalCatsService } from './animal-cats.service'; 4 | 5 | @Module({ 6 | providers: [{ provide: ANIMAL_PROVIDER, useValue: new AnimalCatsService() }], 7 | exports: [ANIMAL_PROVIDER], 8 | }) 9 | export class AnimalCatsModule {} 10 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-cats/animal-cats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalCatsService implements AnimalProviderInteface { 6 | type = 'cat'; 7 | say(): string { 8 | return 'meow'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-cows/animal-cows.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { lastValueFrom, Observable } from 'rxjs'; 3 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 4 | import { AnimalCowsService } from './animal-cows.service'; 5 | 6 | @Module({ 7 | providers: [ 8 | { 9 | provide: ANIMAL_PROVIDER, 10 | useFactory: () => 11 | lastValueFrom( 12 | new Observable((observer) => { 13 | setTimeout(() => { 14 | observer.next(new AnimalCowsService()); 15 | observer.complete(); 16 | }, 5000); 17 | }) 18 | ), 19 | }, 20 | ], 21 | }) 22 | export class AnimalCowsModule {} 23 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-cows/animal-cows.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalCowsService implements AnimalProviderInteface { 6 | type = 'cow'; 7 | say(): string { 8 | return 'moo'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-dogs/animal-dogs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 3 | import { AnimalDogsService } from './animal-dogs.service'; 4 | 5 | @Module({ 6 | providers: [{ provide: ANIMAL_PROVIDER, useClass: AnimalDogsService }], 7 | }) 8 | export class AnimalDogsModule {} 9 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-dogs/animal-dogs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalDogsService implements AnimalProviderInteface { 6 | type = 'dog'; 7 | say(): string { 8 | return 'woof'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-ducks-and-geeses/animal-ducks-and-geeses.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 4 | import { AnimalDucksService } from './animal-ducks.service'; 5 | import { AnimalGeesesService } from './animal-geeses.service'; 6 | 7 | @Module({ 8 | imports: [ 9 | CustomInjectorModule.forFeature({ 10 | providers: [{ provide: ANIMAL_PROVIDER, useClass: AnimalDucksService }], 11 | }), 12 | CustomInjectorModule.forFeature({ 13 | providers: [ 14 | { provide: ANIMAL_PROVIDER, useValue: new AnimalGeesesService() }, 15 | ], 16 | }), 17 | ], 18 | }) 19 | export class AnimalDucksAndGeesesModule {} 20 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-ducks-and-geeses/animal-ducks.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalDucksService implements AnimalProviderInteface { 6 | type = 'duck'; 7 | say(): string { 8 | return 'quack'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-ducks-and-geeses/animal-geeses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalGeesesService implements AnimalProviderInteface { 6 | type = 'geese'; 7 | say(): string { 8 | return 'honk'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-horses/animal-horses.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 4 | import { AnimalHorsesService } from './animal-horses.service'; 5 | 6 | @Module({ 7 | imports: [CustomInjectorModule], 8 | providers: [ 9 | { provide: ANIMAL_PROVIDER, useFactory: () => new AnimalHorsesService() }, 10 | ], 11 | exports: [CustomInjectorModule], 12 | }) 13 | export class AnimalHorsesModule {} 14 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-horses/animal-horses.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalHorsesService implements AnimalProviderInteface { 6 | type = 'horse'; 7 | say(): string { 8 | return 'neigh'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-sheeps/animal-goats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalGoatsService implements AnimalProviderInteface { 6 | type = 'goat'; 7 | say(): string { 8 | return 'baa'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-sheeps/animal-sheeps.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ANIMAL_PROVIDER } from '../animal/animal-provider.interface'; 3 | import { AnimalGoatsService } from './animal-goats.service'; 4 | import { AnimalSheepsService } from './animal-sheeps.service'; 5 | 6 | @Module({ 7 | providers: [ 8 | { 9 | provide: ANIMAL_PROVIDER, 10 | useValue: new AnimalGoatsService(), 11 | }, 12 | { 13 | provide: ANIMAL_PROVIDER, 14 | useFactory: () => 15 | new Promise((resolve) => 16 | setTimeout(() => resolve(new AnimalSheepsService()), 5000) 17 | ), 18 | }, 19 | ], 20 | }) 21 | export class AnimalSheepsModule {} 22 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal-sheeps/animal-sheeps.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AnimalProviderInteface } from '../animal/animal-provider.interface'; 3 | 4 | @Injectable() 5 | export class AnimalSheepsService implements AnimalProviderInteface { 6 | type = 'sheep'; 7 | say(): string { 8 | return 'baa'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal/animal-provider.interface.ts: -------------------------------------------------------------------------------- 1 | export const ANIMAL_PROVIDER = Symbol('ANIMAL_PROVIDER'); 2 | 3 | export interface AnimalProviderInteface { 4 | type: string; 5 | say(): string; 6 | } 7 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal/animal.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 3 | import { SymbolAnimalService } from './animal.service'; 4 | 5 | @Module({ 6 | imports: [CustomInjectorModule], 7 | providers: [SymbolAnimalService], 8 | exports: [SymbolAnimalService], 9 | }) 10 | export class AnimalModule {} 11 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal/animal.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CustomInjectorService } from 'nestjs-custom-injector'; 3 | import { 4 | AnimalProviderInteface, 5 | ANIMAL_PROVIDER, 6 | } from '../animal/animal-provider.interface'; 7 | 8 | @Injectable() 9 | export class SymbolAnimalService { 10 | constructor(private readonly customInjectorService: CustomInjectorService) {} 11 | 12 | getAnimals() { 13 | return this.customInjectorService.getProviders( 14 | ANIMAL_PROVIDER 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/animal/check-animal-voice.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpStatus, 3 | Injectable, 4 | Optional, 5 | ParseIntPipeOptions, 6 | PipeTransform, 7 | } from '@nestjs/common'; 8 | import { 9 | ErrorHttpStatusCode, 10 | HttpErrorByCode, 11 | } from '@nestjs/common/utils/http-error-by-code.util'; 12 | import { CustomInject } from 'nestjs-custom-injector'; 13 | import { 14 | AnimalProviderInteface, 15 | ANIMAL_PROVIDER, 16 | } from './animal-provider.interface'; 17 | 18 | export interface CheckAnimalTypePipeOptions { 19 | errorHttpStatusCode?: ErrorHttpStatusCode; 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | exceptionFactory?: (error: string) => any; 22 | } 23 | 24 | @Injectable() 25 | export class CheckAnimalVoicePipe implements PipeTransform { 26 | @CustomInject(ANIMAL_PROVIDER, { multi: true }) 27 | protected animalProviders!: AnimalProviderInteface[]; 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | protected exceptionFactory: (error: string) => any; 31 | 32 | constructor(@Optional() options?: ParseIntPipeOptions) { 33 | options = options || {}; 34 | const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } = 35 | options; 36 | 37 | this.exceptionFactory = 38 | exceptionFactory || 39 | ((error) => new HttpErrorByCode[errorHttpStatusCode](error)); 40 | } 41 | 42 | async transform(voice: string): Promise { 43 | if ( 44 | !this.animalProviders 45 | .map((animalProvider) => animalProvider.say()) 46 | .includes(voice) 47 | ) { 48 | throw this.exceptionFactory( 49 | 'Validation failed (incorrect voice of animals)' 50 | ); 51 | } 52 | return voice; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/symbol-token.controller.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 4 | import request from 'supertest'; 5 | import { SymbolTokenModule } from './symbol-token.module'; 6 | 7 | describe('SymbolTokenController (e2e)', () => { 8 | jest.setTimeout(3 * 60 * 1000); 9 | 10 | let app: INestApplication; 11 | 12 | beforeAll(async () => { 13 | const moduleRef = await Test.createTestingModule({ 14 | imports: [CustomInjectorModule.forRoot(), SymbolTokenModule], 15 | }).compile(); 16 | app = moduleRef.createNestApplication(); 17 | await app.init(); 18 | }); 19 | 20 | describe('Demo - collect providers with string token type', () => { 21 | it("Get all types of animals (@CustomInject(ANIMAL_PROVIDER, { multi: true, propertyName: 'type' }))", () => { 22 | return request(app.getHttpServer()) 23 | .get('/symbol-token/animal-types-with-inject') 24 | .expect(200) 25 | .expect(['dog', 'cat', 'horse', 'sheep', 'cow', 'duck', 'geese']); 26 | }); 27 | 28 | it('What says all animals (customInjectorService: CustomInjectorService)', () => { 29 | return request(app.getHttpServer()) 30 | .get('/symbol-token/what-says-animals') 31 | .expect(200) 32 | .expect([ 33 | 'dog say woof', 34 | 'cat say meow', 35 | 'horse say neigh', 36 | 'sheep say baa', 37 | 'cow say moo', 38 | 'duck say quack', 39 | 'geese say honk', 40 | ]); 41 | }); 42 | 43 | it('What says all animals (@Inject(ANIMAL_PROVIDER))', () => { 44 | return request(app.getHttpServer()) 45 | .get('/symbol-token/what-says-animals-original') 46 | .expect(200) 47 | .expect('cat say meow'); 48 | }); 49 | 50 | it('What says all animals (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 51 | return request(app.getHttpServer()) 52 | .get('/symbol-token/what-says-animals-with-inject') 53 | .expect(200) 54 | .expect([ 55 | 'dog say woof', 56 | 'cat say meow', 57 | 'horse say neigh', 58 | 'sheep say baa', 59 | 'cow say moo', 60 | 'duck say quack', 61 | 'geese say honk', 62 | ]); 63 | }); 64 | 65 | it('What says all animals (@CustomInjector())', () => { 66 | return request(app.getHttpServer()) 67 | .get('/symbol-token/what-says-animals-with-injector') 68 | .expect(200) 69 | .expect([ 70 | 'dog say woof', 71 | 'cat say meow', 72 | 'horse say neigh', 73 | 'sheep say baa', 74 | 'cow say moo', 75 | 'duck say quack', 76 | 'geese say honk', 77 | ]); 78 | }); 79 | 80 | it('Who say (customInjectorService: CustomInjectorService)', () => { 81 | return request(app.getHttpServer()) 82 | .get('/symbol-token/who-say?voice=baa') 83 | .expect(200) 84 | .expect('sheep say baa'); 85 | }); 86 | 87 | it('Error in request who say (customInjectorService: CustomInjectorService)', () => { 88 | return request(app.getHttpServer()) 89 | .get('/symbol-token/who-say?voice=gav') 90 | .expect(200) 91 | .expect({ error: `I don't know who say gav` }); 92 | }); 93 | 94 | it("Who say (@Query('voice', CheckAnimalTypePipe)", () => { 95 | try { 96 | return request(app.getHttpServer()) 97 | .get('/symbol-token/who-say-with-validate-type?voice=baa') 98 | .expect(200) 99 | .expect('sheep say baa'); 100 | } catch (err) { 101 | console.log(err); 102 | } 103 | }); 104 | 105 | it("Error in request who say (@Query('voice', CheckAnimalTypePipe)", () => { 106 | try { 107 | return request(app.getHttpServer()) 108 | .get('/symbol-token/who-say-with-validate-type?voice=gav') 109 | .expect(400) 110 | .expect({ 111 | statusCode: 400, 112 | message: 'Validation failed (incorrect voice of animals)', 113 | error: 'Bad Request', 114 | }); 115 | } catch (err) { 116 | console.log(err); 117 | } 118 | }); 119 | 120 | it('Who say (@Inject(ANIMAL_PROVIDER))', () => { 121 | return request(app.getHttpServer()) 122 | .get('/symbol-token/who-say-original?voice=baa') 123 | .expect(200) 124 | .expect({ error: `I don't know who say baa` }); 125 | }); 126 | 127 | it('Who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 128 | return request(app.getHttpServer()) 129 | .get('/symbol-token/who-say-with-inject?voice=baa') 130 | .expect(200) 131 | .expect('sheep say baa'); 132 | }); 133 | 134 | it('Error in request who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 135 | return request(app.getHttpServer()) 136 | .get('/symbol-token/who-say-with-inject?voice=gav') 137 | .expect(200) 138 | .expect({ error: `I don't know who say gav` }); 139 | }); 140 | 141 | it('Who say (@CustomInjector())', () => { 142 | return request(app.getHttpServer()) 143 | .get('/symbol-token/who-say-with-injector?voice=baa') 144 | .expect(200) 145 | .expect('sheep say baa'); 146 | }); 147 | 148 | it('Error in request who say (@CustomInjector())', () => { 149 | return request(app.getHttpServer()) 150 | .get('/symbol-token/who-say-with-injector?voice=gav') 151 | .expect(200) 152 | .expect({ error: `I don't know who say gav` }); 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/symbol-token.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { 3 | CustomInjectorBootstrapService, 4 | CustomInjectorModule, 5 | } from 'nestjs-custom-injector'; 6 | import { SymbolTokenController } from './symbol-token.controller'; 7 | import { SymbolTokenModule } from './symbol-token.module'; 8 | 9 | describe('SymbolTokenController (unit)', () => { 10 | jest.setTimeout(3 * 60 * 1000); 11 | 12 | let app: TestingModule; 13 | let controller: SymbolTokenController; 14 | 15 | beforeAll(async () => { 16 | app = await Test.createTestingModule({ 17 | imports: [CustomInjectorModule.forRoot(), SymbolTokenModule], 18 | }).compile(); 19 | await app 20 | .get(CustomInjectorBootstrapService) 21 | .onApplicationBootstrap(); 22 | controller = app.get(SymbolTokenController); 23 | }); 24 | 25 | describe('Demo - collect providers with string token type', () => { 26 | it("Get all types of animals (@CustomInject(ANIMAL_PROVIDER, { multi: true, propertyName: 'type' }))", () => { 27 | const result = controller.animalTypesWithInject(); 28 | expect([ 29 | 'dog', 30 | 'cat', 31 | 'horse', 32 | 'sheep', 33 | 'cow', 34 | 'duck', 35 | 'geese', 36 | ]).toMatchObject(result); 37 | }); 38 | 39 | it('What says all animals (customInjectorService: CustomInjectorService)', () => { 40 | const result = controller.whatSaysAnimals(); 41 | expect([ 42 | 'dog say woof', 43 | 'cat say meow', 44 | 'horse say neigh', 45 | 'sheep say baa', 46 | 'cow say moo', 47 | 'duck say quack', 48 | 'geese say honk', 49 | ]).toMatchObject(result); 50 | }); 51 | 52 | it('What says all animals (@Inject(ANIMAL_PROVIDER))', () => { 53 | const result = controller.whatSaysAnimalsOriginal(); 54 | expect('cat say meow').toEqual(result); 55 | }); 56 | 57 | it('What says all animals (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 58 | const result = controller.whatSaysAnimalsWithInject(); 59 | expect([ 60 | 'dog say woof', 61 | 'cat say meow', 62 | 'horse say neigh', 63 | 'sheep say baa', 64 | 'cow say moo', 65 | 'duck say quack', 66 | 'geese say honk', 67 | ]).toMatchObject(result); 68 | }); 69 | 70 | it('What says all animals (@CustomInjector())', () => { 71 | const result = controller.whatSaysAnimalsWithInjector(); 72 | expect([ 73 | 'dog say woof', 74 | 'cat say meow', 75 | 'horse say neigh', 76 | 'sheep say baa', 77 | 'cow say moo', 78 | 'duck say quack', 79 | 'geese say honk', 80 | ]).toMatchObject(result); 81 | }); 82 | 83 | it('Who say (customInjectorService: CustomInjectorService)', () => { 84 | const result = controller.whoSay('baa'); 85 | expect('sheep say baa').toEqual(result); 86 | }); 87 | 88 | it('Error in request who say (customInjectorService: CustomInjectorService)', () => { 89 | const result = controller.whoSay('gav'); 90 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 91 | }); 92 | 93 | it('Who say (@Inject(ANIMAL_PROVIDER))', () => { 94 | const result = controller.whoSayOriginal('baa'); 95 | expect({ error: `I don't know who say baa` }).toMatchObject(result); 96 | }); 97 | 98 | it('Who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 99 | const result = controller.whoSayWithInject('baa'); 100 | expect('sheep say baa').toEqual(result); 101 | }); 102 | 103 | it('Error in request who say (@CustomInject(ANIMAL_PROVIDER, { multi: true }))', () => { 104 | const result = controller.whoSayWithInject('gav'); 105 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 106 | }); 107 | 108 | it('Who say (@CustomInjector())', () => { 109 | const result = controller.whoSayWithInjector('baa'); 110 | expect('sheep say baa').toEqual(result); 111 | }); 112 | 113 | it('Error in request who say (@CustomInjector())', () => { 114 | const result = controller.whoSayWithInjector('gav'); 115 | expect({ error: `I don't know who say gav` }).toMatchObject(result); 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/symbol-token.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Inject, Query } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { CustomInject } from 'nestjs-custom-injector'; 4 | import { 5 | AnimalProviderInteface, 6 | ANIMAL_PROVIDER, 7 | } from './animal/animal-provider.interface'; 8 | import { CheckAnimalVoicePipe } from './animal/check-animal-voice.pipe'; 9 | import { SymbolTokenService } from './symbol-token.service'; 10 | 11 | @ApiTags('symbol-token') 12 | @Controller('symbol-token') 13 | export class SymbolTokenController { 14 | @CustomInject(ANIMAL_PROVIDER, { 15 | multi: true, 16 | propertyName: 'type', 17 | }) 18 | private animalProviderTypes!: AnimalProviderInteface['type'][]; 19 | 20 | constructor( 21 | @Inject(ANIMAL_PROVIDER) 22 | private readonly animalProvider: AnimalProviderInteface, 23 | private readonly symbolTokenService: SymbolTokenService 24 | ) {} 25 | 26 | @Get('animal-types-with-inject') 27 | animalTypesWithInject() { 28 | return this.animalProviderTypes; 29 | } 30 | 31 | @Get('what-says-animals') 32 | whatSaysAnimals() { 33 | return this.symbolTokenService.whatSaysAnimals(); 34 | } 35 | 36 | @Get('what-says-animals-original') 37 | whatSaysAnimalsOriginal() { 38 | return `${this.animalProvider.type} say ${this.animalProvider.say()}`; 39 | } 40 | 41 | @Get('what-says-animals-with-inject') 42 | whatSaysAnimalsWithInject() { 43 | return this.symbolTokenService.whatSaysAnimalsWithInject(); 44 | } 45 | 46 | @Get('what-says-animals-with-injector') 47 | whatSaysAnimalsWithInjector() { 48 | return this.symbolTokenService.whatSaysAnimalsWithInjector(); 49 | } 50 | 51 | @Get('who-say') 52 | whoSay(@Query('voice') voice: string) { 53 | return this.symbolTokenService.whoSay(voice); 54 | } 55 | 56 | @Get('who-say-with-validate-type') 57 | whoSayWithValidateType(@Query('voice', CheckAnimalVoicePipe) voice: string) { 58 | return this.symbolTokenService.whoSay(voice); 59 | } 60 | 61 | @Get('who-say-original') 62 | whoSayOriginal(@Query('voice') voice: string) { 63 | return this.animalProvider.say() === voice 64 | ? `${this.animalProvider.type} say ${this.animalProvider.say()}` 65 | : { error: `I don't know who say ${voice}` }; 66 | } 67 | 68 | @Get('who-say-with-inject') 69 | whoSayWithInject(@Query('voice') voice: string) { 70 | return this.symbolTokenService.whoSayWithInject(voice); 71 | } 72 | 73 | @Get('who-say-with-injector') 74 | whoSayWithInjector(@Query('voice') voice: string) { 75 | return this.symbolTokenService.whoSayWithInjector(voice); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/symbol-token.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AnimalCatsModule } from './animal-cats/animal-cats.module'; 3 | import { AnimalCowsModule } from './animal-cows/animal-cows.module'; 4 | import { AnimalDogsModule } from './animal-dogs/animal-dogs.module'; 5 | import { AnimalDucksAndGeesesModule } from './animal-ducks-and-geeses/animal-ducks-and-geeses.module'; 6 | import { AnimalHorsesModule } from './animal-horses/animal-horses.module'; 7 | import { AnimalSheepsModule } from './animal-sheeps/animal-sheeps.module'; 8 | import { AnimalModule } from './animal/animal.module'; 9 | import { SymbolTokenController } from './symbol-token.controller'; 10 | import { SymbolTokenService } from './symbol-token.service'; 11 | 12 | @Module({ 13 | imports: [ 14 | AnimalModule, 15 | AnimalDogsModule, 16 | AnimalCatsModule, 17 | AnimalHorsesModule, 18 | AnimalSheepsModule, 19 | AnimalCowsModule, 20 | AnimalDucksAndGeesesModule, 21 | ], 22 | controllers: [SymbolTokenController], 23 | providers: [SymbolTokenService], 24 | }) 25 | export class SymbolTokenModule {} 26 | -------------------------------------------------------------------------------- /apps/demo/src/app/symbol-token/symbol-token.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | CustomInject, 4 | CustomInjector, 5 | CustomInjectorService, 6 | } from 'nestjs-custom-injector'; 7 | import { 8 | AnimalProviderInteface, 9 | ANIMAL_PROVIDER, 10 | } from './animal/animal-provider.interface'; 11 | import { SymbolAnimalService } from './animal/animal.service'; 12 | 13 | @Injectable() 14 | export class SymbolTokenService { 15 | @CustomInjector() 16 | private customInjector!: CustomInjectorService; 17 | 18 | @CustomInject(ANIMAL_PROVIDER, { multi: true }) 19 | private animalProviders!: AnimalProviderInteface[]; 20 | 21 | constructor(private readonly animalService: SymbolAnimalService) {} 22 | 23 | whatSaysAnimalsWithInjector() { 24 | return this.customInjector 25 | .getProviders(ANIMAL_PROVIDER) 26 | .map((animal) => `${animal.type} say ${animal.say()}`); 27 | } 28 | 29 | whatSaysAnimalsWithInject() { 30 | return this.animalProviders.map( 31 | (animal) => `${animal.type} say ${animal.say()}` 32 | ); 33 | } 34 | 35 | whatSaysAnimals() { 36 | return this.animalService 37 | .getAnimals() 38 | .map((animal) => `${animal.type} say ${animal.say()}`); 39 | } 40 | 41 | whoSayWithInjector(voice: string) { 42 | const animal = this.customInjector 43 | .getProviders(ANIMAL_PROVIDER) 44 | .find((animal) => animal.say() === voice); 45 | if (!animal) { 46 | return { error: `I don't know who say ${voice}` }; 47 | } 48 | return `${animal.type} say ${animal.say()}`; 49 | } 50 | 51 | whoSayWithInject(voice: string) { 52 | const animal = this.animalProviders.find( 53 | (animal) => animal.say() === voice 54 | ); 55 | if (!animal) { 56 | return { error: `I don't know who say ${voice}` }; 57 | } 58 | return `${animal.type} say ${animal.say()}`; 59 | } 60 | 61 | whoSay(voice: string) { 62 | const animal = this.animalService 63 | .getAnimals() 64 | .find((animal) => animal.say() === voice); 65 | if (!animal) { 66 | return { error: `I don't know who say ${voice}` }; 67 | } 68 | return `${animal.type} say ${animal.say()}`; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /apps/demo/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndyKaufman/nestjs-custom-injector/345ae8277f18f7c83fa8f9cb0bbd9828b771a207/apps/demo/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/demo/src/assets/i18n/en.vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "nestjs-custom-injector": {} 3 | } 4 | -------------------------------------------------------------------------------- /apps/demo/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/demo/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | */ 5 | 6 | import { Logger } from '@nestjs/common'; 7 | import { NestFactory } from '@nestjs/core'; 8 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 9 | import { readFileSync } from 'fs'; 10 | import { AppModule } from './app/app.module'; 11 | 12 | async function bootstrap() { 13 | const app = await NestFactory.create(AppModule); 14 | const globalPrefix = 'api'; 15 | app.setGlobalPrefix(globalPrefix); 16 | const port = process.env.PORT || 3333; 17 | 18 | const packageJson: { version: string; name: string; description: string } = 19 | JSON.parse(readFileSync('./package.json').toString()); 20 | const config = new DocumentBuilder() 21 | .setTitle(packageJson.name) 22 | .setDescription(packageJson.description) 23 | .setVersion(packageJson.version) 24 | .build(); 25 | const document = SwaggerModule.createDocument(app, config); 26 | SwaggerModule.setup(globalPrefix, app, document); 27 | 28 | await app.listen(port); 29 | Logger.log( 30 | `🚀 Application is running on: http://localhost:${port}/${globalPrefix}` 31 | ); 32 | } 33 | 34 | bootstrap(); 35 | -------------------------------------------------------------------------------- /apps/demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"], 7 | "emitDecoratorMetadata": true, 8 | "target": "es2015" 9 | }, 10 | "composite": true, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.e2e-spec.ts", 14 | "**/*.test.ts", 15 | "jest.config.ts" 16 | ], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /apps/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/demo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "composite": true, 9 | "include": [ 10 | "**/*.test.ts", 11 | "**/*.spec.ts", 12 | "**/*.e2e-spec.ts", 13 | "**/*.d.ts", 14 | "jest.config.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /apps/demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ts = require('typescript'); 4 | 5 | /** 6 | * Extend the default Webpack configuration from nx / ng. 7 | * this webpack.config is used w/ node:build builder 8 | * see angular.json greenroom-rest-api 9 | */ 10 | module.exports = (config, context) => { 11 | // Install additional plugins 12 | console.log('loading plugins'); 13 | 14 | addSwagger(config); 15 | 16 | config.plugins = [ 17 | ...(config.plugins || []), 18 | new webpack.ProvidePlugin({ 19 | openapi: '@nestjs/swagger', 20 | }), 21 | ]; 22 | 23 | return config; 24 | }; 25 | 26 | /** 27 | * Adds nestjs swagger plugin 28 | * 29 | * nestjs swagger: https://docs.nestjs.com/recipes/swagger#plugin 30 | * ts-loader: https://github.com/Igorbek/typescript-plugin-styled-components#ts-loader 31 | * getCustomTransformers: https://github.com/TypeStrong/ts-loader#getcustomtransformers 32 | * 33 | * Someone else has done this, see: 34 | * https://github.com/nrwl/nx/issues/2147 35 | */ 36 | const addSwagger = (config) => { 37 | const rule = config.module.rules.find((rule) => 38 | rule.loader.includes('ts-loader') 39 | ); 40 | if (!rule) throw new Error('no ts-loader rule found'); 41 | rule.options = { 42 | ...rule.options, 43 | getCustomTransformers: () => { 44 | const program = ts.createProgram( 45 | [path.join(__dirname, 'src', 'main.ts')], 46 | {} 47 | ); 48 | return { 49 | before: [ 50 | require('@nestjs/swagger/plugin').before( 51 | { 52 | dtoFileNameSuffix: ['.dto.ts', '.model.ts'], 53 | classValidatorShim: true, 54 | introspectComments: true, 55 | }, 56 | program 57 | ), 58 | ], 59 | }; 60 | }, 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nrwl/jest'); 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset').default; 2 | 3 | module.exports = { 4 | ...nxPreset, 5 | testMatch: ['**/?(*.)+(spec|e2e-spec|smoke-spec).[jt]s?(x)'], 6 | }; 7 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndyKaufman/nestjs-custom-injector/345ae8277f18f7c83fa8f9cb0bbd9828b771a207/libs/.gitkeep -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/nestjs-custom-injector.svg)](https://badge.fury.io/js/nestjs-custom-injector) 2 | [![monthly downloads](https://badgen.net/npm/dm/nestjs-custom-injector)](https://www.npmjs.com/package/nestjs-custom-injector) 3 | 4 | ## Installation 5 | 6 | ```bash 7 | npm i --save nestjs-custom-injector 8 | ``` 9 | 10 | ## Links 11 | 12 | https://nestjs-custom-injector.site15.ru/api - Demo application with nestjs-custom-injector. 13 | https://github.com/EndyKaufman/nestjs-custom-injector-example - Example generated with nest cli for "Usage" sections in readme. 14 | 15 | ## Usage 16 | 17 | Create common interface with token in **animal-provider.interface.ts** 18 | 19 | ```typescript 20 | export const ANIMAL_PROVIDER = 'ANIMAL_PROVIDER'; 21 | 22 | export interface AnimalProviderInteface { 23 | type: string; 24 | say(): string; 25 | } 26 | ``` 27 | 28 | Create first type of logic for cats in **animal-cats.service.ts** 29 | 30 | ```typescript 31 | import { Injectable } from '@nestjs/common'; 32 | import { AnimalProviderInteface } from './animal-provider.interface'; 33 | 34 | @Injectable() 35 | export class AnimalCatsService implements AnimalProviderInteface { 36 | type = 'cat'; 37 | say(): string { 38 | return 'meow'; 39 | } 40 | } 41 | ``` 42 | 43 | Create second type of logic for dogs in **animal-dogs.service.ts** 44 | 45 | ```typescript 46 | import { Injectable } from '@nestjs/common'; 47 | import { AnimalProviderInteface } from './animal-provider.interface'; 48 | 49 | @Injectable() 50 | export class AnimalDogsService implements AnimalProviderInteface { 51 | type = 'dog'; 52 | say(): string { 53 | return 'woof'; 54 | } 55 | } 56 | ``` 57 | 58 | Create controller **animals.controller.ts** 59 | 60 | ```typescript 61 | import { Controller, Get, Query } from '@nestjs/common'; 62 | import { CustomInject } from 'nestjs-custom-injector'; 63 | import { 64 | AnimalProviderInteface, 65 | ANIMAL_PROVIDER, 66 | } from './animal-provider.interface'; 67 | 68 | @Controller('animals') 69 | export class AnimalsController { 70 | @CustomInject(ANIMAL_PROVIDER, { multi: true }) 71 | private animalProviders!: AnimalProviderInteface[]; 72 | 73 | @Get('animal-types') 74 | animalTypes() { 75 | return this.animalProviders.map((animalProvider) => animalProvider.type); 76 | } 77 | 78 | @Get('what-says-animals') 79 | whatSaysAnimals() { 80 | return this.animalProviders.map( 81 | (animal) => `${animal.type} say ${animal.say()}` 82 | ); 83 | } 84 | 85 | @Get('who-say') 86 | whoSay(@Query('voice') voice: string) { 87 | const animal = this.animalProviders.find( 88 | (animal) => animal.say() === voice 89 | ); 90 | if (!animal) { 91 | return { error: `I don't know who say ${voice}` }; 92 | } 93 | return `${animal.type} say ${animal.say()}`; 94 | } 95 | } 96 | ``` 97 | 98 | Append all logic to main app module **app.module.ts** 99 | 100 | ```typescript 101 | import { Module } from '@nestjs/common'; 102 | import { CustomInjectorModule } from 'nestjs-custom-injector'; 103 | import { AnimalCatsService } from './animal-cats.service'; 104 | import { AnimalDogsService } from './animal-dogs.service'; 105 | import { AnimalsController } from './animals.controller'; 106 | 107 | @Module({ 108 | ... 109 | imports: [ 110 | ... 111 | CustomInjectorModule.forRoot(), 112 | CustomInjectorModule.forFeature({ 113 | providers: [{ provide: ANIMAL_PROVIDER, useClass: AnimalCatsService }], 114 | }), 115 | CustomInjectorModule.forFeature({ 116 | providers: [ 117 | { provide: ANIMAL_PROVIDER, useValue: new AnimalDogsService() }, 118 | ], 119 | }), 120 | ... 121 | ], 122 | controllers: [ 123 | ... 124 | AnimalsController 125 | ... 126 | ] 127 | ... 128 | }) 129 | export class AppModule {} 130 | ``` 131 | 132 | ## License 133 | 134 | MIT 135 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'nestjs-custom-injector', 4 | preset: '../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | testEnvironment: 'node', 11 | transform: { 12 | '^.+\\.[tj]sx?$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: '../../coverage/libs/nestjs-custom-injector', 16 | }; 17 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-custom-injector", 3 | "version": "2.2.3", 4 | "description": "Custom injecting logic for NestJS with support multi providing", 5 | "keywords": [ 6 | "nestjs", 7 | "custom", 8 | "multi", 9 | "providers", 10 | "decorators", 11 | "global", 12 | "injector", 13 | "inject" 14 | ], 15 | "license": "MIT", 16 | "author": "EndyKaufman ", 17 | "engines": { 18 | "node": ">=14", 19 | "npm": ">=6" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/EndyKaufman/nestjs-custom-injector/issues" 23 | }, 24 | "homepage": "https://github.com/EndyKaufman/nestjs-custom-injector", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/EndyKaufman/nestjs-custom-injector.git" 28 | }, 29 | "maintainers": [ 30 | { 31 | "name": "EndyKaufman", 32 | "email": "admin@site15.ru" 33 | } 34 | ], 35 | "dependencies": { 36 | "tslib": "2.0.0" 37 | }, 38 | "i18n": [ 39 | { 40 | "scope": "nestjs-custom-injector", 41 | "path": "src/i18n", 42 | "strategy": "join" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoot": "libs/nestjs-custom-injector/src", 3 | "projectType": "library", 4 | "targets": { 5 | "build": { 6 | "executor": "@nrwl/js:tsc", 7 | "outputs": ["{options.outputPath}"], 8 | "options": { 9 | "outputPath": "dist/libs/nestjs-custom-injector", 10 | "tsConfig": "libs/nestjs-custom-injector/tsconfig.lib.json", 11 | "packageJson": "libs/nestjs-custom-injector/package.json", 12 | "main": "libs/nestjs-custom-injector/src/index.ts", 13 | "assets": ["libs/nestjs-custom-injector/*.md"], 14 | "updateBuildableProjectDepsInPackageJson": false 15 | } 16 | }, 17 | "lint": { 18 | "executor": "@nrwl/linter:eslint", 19 | "outputs": ["{options.outputFile}"], 20 | "options": { 21 | "lintFilePatterns": ["libs/nestjs-custom-injector/**/*.ts"] 22 | } 23 | }, 24 | "test": { 25 | "executor": "@nrwl/jest:jest", 26 | "outputs": ["coverage/libs/nestjs-custom-injector"], 27 | "options": { 28 | "jestConfig": "libs/nestjs-custom-injector/jest.config.ts", 29 | "passWithNoTests": true 30 | } 31 | } 32 | }, 33 | "tags": [] 34 | } 35 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/custom-injector-bootstrap.service'; 2 | export * from './lib/custom-injector.decorators'; 3 | export * from './lib/custom-injector.module'; 4 | export * from './lib/custom-injector.service'; 5 | export * from './lib/custom-injector.types'; 6 | export * from './lib/custom-injector.utils'; 7 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/lib/custom-injector-bootstrap.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; 2 | import { CustomInjectorService } from './custom-injector.service'; 3 | 4 | @Injectable() 5 | export class CustomInjectorBootstrapService implements OnApplicationBootstrap { 6 | constructor(private readonly customInjectorService: CustomInjectorService) {} 7 | 8 | async onApplicationBootstrap() { 9 | await this.customInjectorService.initAllNeedProviders(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/lib/custom-injector.decorators.ts: -------------------------------------------------------------------------------- 1 | import { InstanceToken } from '@nestjs/core/injector/module'; 2 | import { CustomInjectorService } from './custom-injector.service'; 3 | import { 4 | CustomInjectorError, 5 | CUSTOM_INJECTOR_METADATA, 6 | InjectedProvidersStorageItemOptions, 7 | } from './custom-injector.types'; 8 | 9 | export function CustomInjector() { 10 | return function (target: unknown, propertyKey: string) { 11 | Object.defineProperty(target, propertyKey, { 12 | get: function () { 13 | return CustomInjectorService.getInstance(); 14 | }, 15 | }); 16 | }; 17 | } 18 | 19 | export function CustomInject< 20 | T, 21 | E extends CustomInjectorError = CustomInjectorError 22 | >(token: InstanceToken, options?: InjectedProvidersStorageItemOptions) { 23 | return function (target: object, propertyKey: string) { 24 | let injectedProvidersStorageItem = Reflect.getMetadata( 25 | `${CUSTOM_INJECTOR_METADATA}_${propertyKey}`, 26 | target 27 | ); 28 | if (injectedProvidersStorageItem === undefined) { 29 | injectedProvidersStorageItem = 30 | CustomInjectorService.getInstance().createInjectedProvidersStorageItem< 31 | T, 32 | E 33 | >(token, target, options || {}); 34 | } 35 | Reflect.defineMetadata( 36 | `${CUSTOM_INJECTOR_METADATA}_${propertyKey}`, 37 | injectedProvidersStorageItem, 38 | target 39 | ); 40 | Object.defineProperty(target, propertyKey, { 41 | get: function () { 42 | if (!injectedProvidersStorageItem) { 43 | throw new CustomInjectorError('injectedProvidersStorageItem not set'); 44 | } 45 | if (options?.lazy || !injectedProvidersStorageItem.appiled) { 46 | injectedProvidersStorageItem.init(); 47 | } 48 | return injectedProvidersStorageItem.instance; 49 | }, 50 | }); 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/lib/custom-injector.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common'; 2 | import { DiscoveryModule } from '@nestjs/core'; 3 | import { CustomInjectorBootstrapService } from './custom-injector-bootstrap.service'; 4 | import { CustomInjectorService } from './custom-injector.service'; 5 | 6 | @Module({ 7 | imports: [DiscoveryModule], 8 | providers: [CustomInjectorService], 9 | exports: [DiscoveryModule, CustomInjectorService], 10 | }) 11 | export class CustomInjectorCoreModule {} 12 | 13 | @Module({ 14 | imports: [CustomInjectorCoreModule], 15 | exports: [CustomInjectorCoreModule], 16 | }) 17 | export class CustomInjectorModule { 18 | static forRoot(): DynamicModule { 19 | return { 20 | module: CustomInjectorModule, 21 | providers: [CustomInjectorBootstrapService], 22 | }; 23 | } 24 | 25 | static forFeature(options: ModuleMetadata): DynamicModule { 26 | return { 27 | module: CustomInjectorModule, 28 | ...options, 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/lib/custom-injector.service.ts: -------------------------------------------------------------------------------- 1 | import { Abstract, Injectable, Scope, Type } from '@nestjs/common'; 2 | import { DiscoveryService, ModulesContainer } from '@nestjs/core'; 3 | import { STATIC_CONTEXT } from '@nestjs/core/injector/constants'; 4 | import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; 5 | import { InstanceToken } from '@nestjs/core/injector/module'; 6 | import { 7 | CustomInjectorError, 8 | CUSTOM_INJECTOR_METADATA, 9 | GetComponentsOptions, 10 | GetLastComponentOptions, 11 | GetProviderOptions, 12 | GetProvidersOptions, 13 | InjectedProvidersStorageItem, 14 | InjectedProvidersStorageItemOptions, 15 | } from './custom-injector.types'; 16 | import { isPromise } from './custom-injector.utils'; 17 | 18 | @Injectable() 19 | export class CustomInjectorService { 20 | public static _instance = new CustomInjectorService( 21 | new DiscoveryService(new ModulesContainer()) 22 | ); 23 | public static getInstance() { 24 | return CustomInjectorService._instance; 25 | } 26 | 27 | constructor(private readonly discoveryService: DiscoveryService) { 28 | CustomInjectorService._instance = this; 29 | } 30 | 31 | public getProvider< 32 | T, 33 | E extends CustomInjectorError = CustomInjectorError 34 | >(token: InstanceToken, options?: GetProviderOptions): T { 35 | return this._getProvider(token, options || {}, undefined); 36 | } 37 | 38 | public async getAsyncProvider< 39 | T, 40 | E extends CustomInjectorError = CustomInjectorError 41 | >(token: InstanceToken, options?: GetProviderOptions): Promise { 42 | return this._getAsyncProvider(token, options || {}, undefined); 43 | } 44 | 45 | public getProviders< 46 | T, 47 | E extends CustomInjectorError = CustomInjectorError 48 | >(token: InstanceToken, options?: GetProvidersOptions): T[] { 49 | return this._getProviders(token, options || {}, undefined); 50 | } 51 | 52 | public async getAsyncProviders< 53 | T, 54 | E extends CustomInjectorError = CustomInjectorError 55 | >(token: InstanceToken, options?: GetProvidersOptions): Promise { 56 | return this._getAsyncProviders(token, options || {}, undefined); 57 | } 58 | 59 | public createInjectedProvidersStorageItem< 60 | T, 61 | E extends CustomInjectorError = CustomInjectorError 62 | >( 63 | token: InstanceToken, 64 | target: unknown, 65 | options: InjectedProvidersStorageItemOptions 66 | ) { 67 | return { 68 | appiled: false, 69 | instance: null, 70 | init: function () { 71 | this.options = { 72 | ...(this.options || {}), 73 | multi: Boolean(this.options.multi), 74 | }; 75 | if (!this.options?.lazy && this.appiled) { 76 | return; 77 | } 78 | if (this.options?.multi) { 79 | this.instance = CustomInjectorService.getInstance()._getProviders( 80 | this.token, 81 | this.options, 82 | this 83 | ); 84 | } else { 85 | this.instance = CustomInjectorService.getInstance()._getProvider( 86 | this.token, 87 | this.options, 88 | this 89 | ); 90 | } 91 | this.appiled = true; 92 | }, 93 | asyncInit: async function () { 94 | this.options = { 95 | ...(this.options || {}), 96 | multi: Boolean(this.options.multi), 97 | }; 98 | if (!this.options?.lazy && this.appiled) { 99 | return; 100 | } 101 | if (this.options?.multi) { 102 | this.instance = 103 | await CustomInjectorService.getInstance()._getAsyncProviders( 104 | this.token, 105 | this.options, 106 | this 107 | ); 108 | } else { 109 | this.instance = 110 | await CustomInjectorService.getInstance()._getAsyncProvider( 111 | this.token, 112 | this.options, 113 | this 114 | ); 115 | } 116 | this.appiled = true; 117 | }, 118 | target, 119 | token, 120 | options, 121 | } as InjectedProvidersStorageItem; 122 | } 123 | 124 | async initAllNeedProviders() { 125 | const items = this.discoveryService 126 | .getProviders() 127 | .map((component) => this.toDiscoveredClass(component)) 128 | .filter(Boolean) 129 | .map((c) => { 130 | try { 131 | return Reflect.getMetadataKeys(c) 132 | .filter((k) => k.indexOf(CUSTOM_INJECTOR_METADATA) === 0) 133 | .map((k) => Reflect.getMetadata(k, c)); 134 | } catch (err) { 135 | // todo: add support debug for library and catch all errors 136 | if (process.env.DEBUG) { 137 | console.debug(c); 138 | console.error(err); 139 | } 140 | return []; 141 | } 142 | }) 143 | .reduce((all, cur) => [...all, ...cur], []); 144 | for (let index = 0; index < items.length; index++) { 145 | if (!items[index]?.options?.lazy && items[index].asyncInit) { 146 | await items[index].asyncInit(); 147 | } 148 | } 149 | } 150 | 151 | private _getProvider< 152 | T, 153 | E extends CustomInjectorError = CustomInjectorError 154 | >( 155 | token: InstanceToken, 156 | options: GetProviderOptions, 157 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 158 | ): T { 159 | const object = this.getOneProvider( 160 | token, 161 | options, 162 | injectedProvidersStorageItem 163 | ); 164 | if (Object.getOwnPropertyDescriptor(options || {}, 'propertyName')) { 165 | const propertyName = Object.getOwnPropertyDescriptor( 166 | options || {}, 167 | 'propertyName' 168 | )?.value; 169 | if (Object.getOwnPropertyDescriptor(object || {}, propertyName)) { 170 | const propertyObject = Object.getOwnPropertyDescriptor( 171 | object || {}, 172 | propertyName 173 | )?.value; 174 | 175 | return propertyObject as T; 176 | } 177 | throw this.errorFactory( 178 | token, 179 | injectedProvidersStorageItem, 180 | options?.errorFactory 181 | ); 182 | } 183 | 184 | return object as T; 185 | } 186 | 187 | private async _getAsyncProvider< 188 | T, 189 | E extends CustomInjectorError = CustomInjectorError 190 | >( 191 | token: InstanceToken, 192 | options: GetProviderOptions, 193 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 194 | ): Promise { 195 | let object = this.getOneProvider( 196 | token, 197 | options, 198 | injectedProvidersStorageItem 199 | ); 200 | if (Object.getOwnPropertyDescriptor(options || {}, 'propertyName')) { 201 | const propertyName = Object.getOwnPropertyDescriptor( 202 | options || {}, 203 | 'propertyName' 204 | )?.value; 205 | 206 | if (isPromise(object)) { 207 | object = await object; 208 | } 209 | 210 | if (Object.getOwnPropertyDescriptor(object || {}, propertyName)) { 211 | const propertyObject = Object.getOwnPropertyDescriptor( 212 | object || {}, 213 | propertyName 214 | )?.value; 215 | 216 | if (isPromise(propertyObject)) { 217 | return await propertyObject; 218 | } else { 219 | return propertyObject as T; 220 | } 221 | } 222 | throw this.errorFactory( 223 | token, 224 | injectedProvidersStorageItem, 225 | options?.errorFactory 226 | ); 227 | } 228 | 229 | if (isPromise(object)) { 230 | return await object; 231 | } else { 232 | return object as T; 233 | } 234 | } 235 | 236 | private getOneProvider< 237 | T, 238 | E extends CustomInjectorError = CustomInjectorError 239 | >( 240 | token: InstanceToken, 241 | options: GetProviderOptions, 242 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 243 | ) { 244 | return typeof token === 'string' || 245 | typeof token === 'symbol' || 246 | String(token).includes('Abstract') || 247 | String(token).includes('abstract') 248 | ? this._getLastComponentByName>( 249 | token, 250 | options, 251 | injectedProvidersStorageItem 252 | ) 253 | : this._getLastComponentsByClass>( 254 | token, 255 | options, 256 | injectedProvidersStorageItem 257 | ); 258 | } 259 | 260 | private _getProviders< 261 | T, 262 | E extends CustomInjectorError = CustomInjectorError 263 | >( 264 | token: InstanceToken, 265 | options: GetProvidersOptions, 266 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 267 | ): T[] { 268 | const objects = this.getManyProviders( 269 | token, 270 | options, 271 | injectedProvidersStorageItem 272 | ); 273 | 274 | if (Object.getOwnPropertyDescriptor(options || {}, 'propertyName')) { 275 | const propertyName = Object.getOwnPropertyDescriptor( 276 | options || {}, 277 | 'propertyName' 278 | )?.value; 279 | const resolvedObjects: T[] = []; 280 | for (let index = 0; index < objects.length; index++) { 281 | resolvedObjects.push(objects[index]); 282 | } 283 | return resolvedObjects 284 | .filter((o) => Object.getOwnPropertyDescriptor(o || {}, propertyName)) 285 | .map( 286 | (o) => Object.getOwnPropertyDescriptor(o || {}, propertyName)?.value 287 | ) as T[]; 288 | } 289 | 290 | const resolvedObjects: T[] = []; 291 | for (let index = 0; index < objects.length; index++) { 292 | resolvedObjects.push(objects[index]); 293 | } 294 | return resolvedObjects; 295 | } 296 | 297 | private getManyProviders< 298 | T, 299 | E extends CustomInjectorError = CustomInjectorError 300 | >( 301 | token: InstanceToken, 302 | options: GetProvidersOptions, 303 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 304 | ) { 305 | return typeof token === 'string' || 306 | typeof token === 'symbol' || 307 | String(token).includes('Abstract') || 308 | String(token).includes('abstract') 309 | ? this._getComponentsByName>( 310 | token, 311 | options, 312 | injectedProvidersStorageItem 313 | ) 314 | : this._getComponentsByClass>( 315 | token, 316 | options, 317 | injectedProvidersStorageItem 318 | ); 319 | } 320 | 321 | private async _getAsyncProviders< 322 | T, 323 | E extends CustomInjectorError = CustomInjectorError 324 | >( 325 | token: InstanceToken, 326 | options: GetProvidersOptions, 327 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 328 | ): Promise { 329 | const objects = this.getManyProviders( 330 | token, 331 | options, 332 | injectedProvidersStorageItem 333 | ); 334 | 335 | if (Object.getOwnPropertyDescriptor(options || {}, 'propertyName')) { 336 | const propertyName = Object.getOwnPropertyDescriptor( 337 | options || {}, 338 | 'propertyName' 339 | )?.value; 340 | const resolvedObjects: T[] = []; 341 | for (let index = 0; index < objects.length; index++) { 342 | let object: T; 343 | if (isPromise(objects[index])) { 344 | object = await objects[index]; 345 | } else { 346 | object = objects[index] as T; 347 | } 348 | resolvedObjects.push(object); 349 | } 350 | return resolvedObjects 351 | .filter((o) => Object.getOwnPropertyDescriptor(o || {}, propertyName)) 352 | .map( 353 | (o) => Object.getOwnPropertyDescriptor(o || {}, propertyName)?.value 354 | ) as T[]; 355 | } 356 | 357 | const resolvedObjects: T[] = []; 358 | for (let index = 0; index < objects.length; index++) { 359 | let object: T; 360 | if (isPromise(objects[index])) { 361 | object = await objects[index]; 362 | } else { 363 | object = objects[index] as T; 364 | } 365 | resolvedObjects.push(object); 366 | } 367 | return resolvedObjects; 368 | } 369 | 370 | private _getLastComponentByName< 371 | T, 372 | E extends CustomInjectorError = CustomInjectorError, 373 | O extends GetLastComponentOptions = GetLastComponentOptions 374 | >( 375 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 376 | token: string | symbol | Abstract, 377 | options: O, 378 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 379 | ): T | Promise { 380 | let components: T[]; 381 | try { 382 | components = this._getComponentsByName( 383 | token, 384 | options, 385 | injectedProvidersStorageItem 386 | ); 387 | } catch (err) { 388 | components = []; 389 | } 390 | const defaultProviderValue = options?.defaultProviderValue; 391 | const component = components.length === 0 ? undefined : components[0]; 392 | if (component === undefined) { 393 | if (defaultProviderValue === undefined) { 394 | throw this.errorFactory( 395 | token, 396 | injectedProvidersStorageItem, 397 | options?.errorFactory 398 | ); 399 | } 400 | return defaultProviderValue; 401 | } 402 | 403 | return component; 404 | } 405 | 406 | private _getLastComponentsByClass< 407 | T, 408 | E extends CustomInjectorError = CustomInjectorError, 409 | O extends GetLastComponentOptions = GetLastComponentOptions 410 | >( 411 | // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any 412 | token: Type | Function, 413 | options: O, 414 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 415 | ): T | Promise { 416 | let components: T[]; 417 | try { 418 | components = this._getComponentsByClass( 419 | token, 420 | options, 421 | injectedProvidersStorageItem 422 | ); 423 | } catch (err) { 424 | components = []; 425 | } 426 | const defaultProviderValue = options?.defaultProviderValue; 427 | const component = components.length === 0 ? undefined : components[0]; 428 | 429 | if (component === undefined) { 430 | if (defaultProviderValue === undefined) { 431 | throw this.errorFactory( 432 | token, 433 | injectedProvidersStorageItem, 434 | options?.errorFactory 435 | ); 436 | } 437 | return defaultProviderValue; 438 | } 439 | 440 | return component; 441 | } 442 | 443 | private _getComponentsByName< 444 | T, 445 | E extends CustomInjectorError = CustomInjectorError, 446 | O extends GetComponentsOptions = GetComponentsOptions 447 | >( 448 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 449 | token: string | symbol | Abstract, 450 | options: O, 451 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 452 | ): T[] { 453 | if (!this.discoveryService) { 454 | throw new CustomInjectorError(`this.discoveryService not set`); 455 | } 456 | 457 | let values = this.discoveryService 458 | .getProviders() 459 | .filter( 460 | (component) => 461 | component.scope !== Scope.REQUEST && 462 | (component.name === token || component.token === token) 463 | ) 464 | .map((component) => this.toDiscoveredClass(component)) 465 | .map((component) => 466 | options?.providerFactory 467 | ? options?.providerFactory(component) 468 | : component 469 | ); 470 | if ( 471 | values.length === 0 && 472 | !Object.getOwnPropertyDescriptor(options, 'providersFactory') && 473 | !Object.getOwnPropertyDescriptor(options, 'defaultProvidersValue') 474 | ) { 475 | throw this.errorFactory( 476 | token, 477 | injectedProvidersStorageItem, 478 | options?.errorFactory 479 | ); 480 | } 481 | 482 | if (options?.providersFactory) { 483 | values = options.providersFactory(values); 484 | } 485 | if (options?.defaultProvidersValue) { 486 | values = options.defaultProvidersValue; 487 | } 488 | return values as T[]; 489 | } 490 | 491 | private _getComponentsByClass< 492 | T, 493 | E extends CustomInjectorError = CustomInjectorError, 494 | O extends GetComponentsOptions = GetComponentsOptions 495 | >( 496 | // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any 497 | cls: Type | Abstract | Function, 498 | options: O, 499 | injectedProvidersStorageItem: InjectedProvidersStorageItem | undefined 500 | ): T[] { 501 | if (!this.discoveryService) { 502 | throw new CustomInjectorError(`this.discoveryService not set`); 503 | } 504 | 505 | let values = this.discoveryService 506 | .getProviders() 507 | .filter((component) => component.scope !== Scope.REQUEST) 508 | .map((component) => this.toDiscoveredClass(component)) 509 | .filter((obj) => { 510 | try { 511 | return obj && obj instanceof cls; 512 | } catch (err) { 513 | return false; 514 | } 515 | }) 516 | .map((component) => 517 | options?.providerFactory 518 | ? options?.providerFactory(component) 519 | : component 520 | ); 521 | 522 | if ( 523 | values.length === 0 && 524 | !Object.getOwnPropertyDescriptor(options, 'providersFactory') && 525 | !Object.getOwnPropertyDescriptor(options, 'defaultProvidersValue') 526 | ) { 527 | throw this.errorFactory( 528 | cls, 529 | injectedProvidersStorageItem, 530 | options?.errorFactory 531 | ); 532 | } 533 | 534 | if (options?.providersFactory) { 535 | values = options.providersFactory(values); 536 | } 537 | if (options?.defaultProvidersValue) { 538 | values = options.defaultProvidersValue; 539 | } 540 | return values as T[]; 541 | } 542 | 543 | private errorFactory< 544 | T, 545 | E extends CustomInjectorError = CustomInjectorError 546 | >( 547 | token: InstanceToken, 548 | injectedProvidersStorageItem?: InjectedProvidersStorageItem, 549 | errorFactory?: ( 550 | message: string, 551 | injectedProvidersStorageItem?: InjectedProvidersStorageItem 552 | ) => E 553 | ): E { 554 | return errorFactory 555 | ? errorFactory( 556 | `${ 557 | injectedProvidersStorageItem?.options?.multi 558 | ? 'Providers' 559 | : 'Provider' 560 | } "${token.toString()}" not found!`, 561 | injectedProvidersStorageItem 562 | ) 563 | : (new CustomInjectorError( 564 | `${ 565 | injectedProvidersStorageItem?.options?.multi 566 | ? 'Providers' 567 | : 'Provider' 568 | } "${token.toString()}" not found!`, 569 | injectedProvidersStorageItem 570 | ) as E); 571 | } 572 | 573 | private toDiscoveredClass(wrapper: InstanceWrapper): T { 574 | const instanceHost: { 575 | instance: T; 576 | } = wrapper.getInstanceByContextId( 577 | STATIC_CONTEXT, 578 | wrapper && wrapper.id ? wrapper.id : undefined 579 | ); 580 | return instanceHost.instance; 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/lib/custom-injector.types.ts: -------------------------------------------------------------------------------- 1 | import { InstanceToken } from '@nestjs/core/injector/module'; 2 | 3 | export const CUSTOM_INJECTOR_METADATA = 'custom-injector:metadata'; 4 | 5 | export type InjectedProvidersStorageItemOptions< 6 | T, 7 | E extends CustomInjectorError = CustomInjectorError 8 | > = 9 | | (GetProvidersOptions & { 10 | lazy?: boolean; 11 | multi: true; 12 | }) 13 | | (GetProviderOptions & { 14 | lazy?: boolean; 15 | multi?: false; 16 | }); 17 | 18 | export type InjectedProvidersStorageItem< 19 | T = unknown, 20 | E extends CustomInjectorError = CustomInjectorError 21 | > = { 22 | target: object; 23 | token: InstanceToken; 24 | options?: InjectedProvidersStorageItemOptions; 25 | instance: null | T | T[]; 26 | appiled: boolean; 27 | init: () => void; 28 | asyncInit: () => Promise; 29 | }; 30 | 31 | export class CustomInjectorError extends Error { 32 | constructor( 33 | public override message: string, 34 | public injectedProvidersStorageItem?: InjectedProvidersStorageItem 35 | ) { 36 | super(message); 37 | } 38 | } 39 | 40 | export interface GetComponentsOptions> { 41 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 42 | providerFactory?: (value: any) => T | Promise; 43 | defaultProvidersValue?: T[]; 44 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 45 | providersFactory?: (value: any) => T[]; 46 | errorFactory?: ( 47 | message: string, 48 | injectedProvidersStorageItem?: InjectedProvidersStorageItem 49 | ) => E; 50 | } 51 | 52 | export interface GetLastComponentOptions> { 53 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | providerFactory?: (value: any) => T | Promise; 55 | defaultProviderValue?: T | Promise; 56 | errorFactory?: ( 57 | message: string, 58 | injectedProvidersStorageItem?: InjectedProvidersStorageItem 59 | ) => E; 60 | } 61 | 62 | export type GetProvidersOptions< 63 | T, 64 | E extends CustomInjectorError 65 | > = GetComponentsOptions & { 66 | propertyName?: keyof T; 67 | }; 68 | 69 | export type GetProviderOptions< 70 | T, 71 | E extends CustomInjectorError 72 | > = GetLastComponentOptions & { 73 | propertyName?: keyof T; 74 | }; 75 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/src/lib/custom-injector.utils.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | export function isPromise(object: any) { 3 | return Object.prototype.toString.call(object) === '[object Promise]'; 4 | } 5 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tests/multi-providers-by-property.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { 4 | CustomInject, 5 | CustomInjectorError, 6 | CustomInjectorModule, 7 | } from '../src'; 8 | 9 | describe('Multi providers by property (unit)', () => { 10 | jest.setTimeout(60 * 1000); 11 | 12 | it('Work with multi providers', async () => { 13 | interface CProvider { 14 | type: string; 15 | } 16 | const C_PROVIDER = Symbol('C_PROVIDER'); 17 | @Injectable() 18 | class C1 implements CProvider { 19 | type = 'c1'; 20 | } 21 | @Injectable() 22 | class C2 implements CProvider { 23 | type = 'c2'; 24 | } 25 | @Injectable() 26 | class P { 27 | @CustomInject(C_PROVIDER, { 28 | multi: true, 29 | propertyName: 'type', 30 | }) 31 | providers!: CProvider['type'][]; 32 | } 33 | const module = await Test.createTestingModule({ 34 | imports: [ 35 | CustomInjectorModule.forRoot(), 36 | CustomInjectorModule.forFeature({ 37 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 38 | }), 39 | CustomInjectorModule.forFeature({ 40 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 41 | }), 42 | ], 43 | providers: [P], 44 | exports: [P], 45 | }).compile(); 46 | const app = module.createNestApplication(); 47 | await app.init(); 48 | const p = app.get

(P); 49 | expect(p.providers).toEqual(['c1', 'c2']); 50 | await app.close(); 51 | }); 52 | 53 | it('Work with multi regular and async providers', async () => { 54 | interface CProvider { 55 | type: string; 56 | } 57 | const C_PROVIDER = Symbol('C_PROVIDER'); 58 | @Injectable() 59 | class C1 implements CProvider { 60 | type = 'c1'; 61 | } 62 | @Injectable() 63 | class C2 implements CProvider { 64 | type = 'c2'; 65 | } 66 | @Injectable() 67 | class P { 68 | @CustomInject(C_PROVIDER, { 69 | multi: true, 70 | propertyName: 'type', 71 | }) 72 | providers!: CProvider['type'][]; 73 | } 74 | const module = await Test.createTestingModule({ 75 | imports: [ 76 | CustomInjectorModule.forRoot(), 77 | CustomInjectorModule.forFeature({ 78 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 79 | }), 80 | CustomInjectorModule.forFeature({ 81 | providers: [ 82 | { 83 | provide: C_PROVIDER, 84 | useFactory: () => 85 | new Promise((resolve) => 86 | setTimeout(() => resolve(new C2()), 1000) 87 | ), 88 | }, 89 | ], 90 | }), 91 | ], 92 | providers: [P], 93 | exports: [P], 94 | }).compile(); 95 | const app = module.createNestApplication(); 96 | await app.init(); 97 | const p = app.get

(P); 98 | expect(p.providers).toEqual(['c1', 'c2']); 99 | await app.close(); 100 | }); 101 | 102 | it('Error if array of providers not set', async () => { 103 | interface CProvider { 104 | type: string; 105 | } 106 | const C_PROVIDER1 = 'C_PROVIDER1'; 107 | @Injectable() 108 | class P { 109 | @CustomInject(C_PROVIDER1, { 110 | multi: true, 111 | propertyName: 'type', 112 | }) 113 | providers!: CProvider['type']; 114 | } 115 | const module = await Test.createTestingModule({ 116 | imports: [CustomInjectorModule.forRoot()], 117 | providers: [P], 118 | exports: [P], 119 | }).compile(); 120 | const app = module.createNestApplication(); 121 | try { 122 | await app.init(); 123 | expect(true).toEqual(false); 124 | } catch (err) { 125 | expect(err instanceof CustomInjectorError && err.message).toEqual( 126 | `Providers "C_PROVIDER1" not found!` 127 | ); 128 | expect( 129 | err instanceof CustomInjectorError && 130 | err.injectedProvidersStorageItem?.token 131 | ).toEqual(C_PROVIDER1); 132 | } 133 | await app.close(); 134 | }); 135 | 136 | it('Custom error if array of providers not set', async () => { 137 | interface CProvider { 138 | type: string; 139 | } 140 | class CustomError extends CustomInjectorError { 141 | constructor(public override message: string) { 142 | super(message); 143 | } 144 | } 145 | const C_PROVIDER1 = 'C_PROVIDER1'; 146 | @Injectable() 147 | class P { 148 | @CustomInject(C_PROVIDER1, { 149 | multi: true, 150 | propertyName: 'type', 151 | errorFactory: (message: string) => new CustomError(message), 152 | }) 153 | providers!: CProvider['type']; 154 | } 155 | const module = await Test.createTestingModule({ 156 | imports: [CustomInjectorModule.forRoot()], 157 | providers: [P], 158 | exports: [P], 159 | }).compile(); 160 | const app = module.createNestApplication(); 161 | try { 162 | await app.init(); 163 | expect(true).toEqual(false); 164 | } catch (err) { 165 | expect(err instanceof CustomError && err.message).toEqual( 166 | `Providers "C_PROVIDER1" not found!` 167 | ); 168 | expect( 169 | err instanceof CustomError && err.injectedProvidersStorageItem?.token 170 | ).toEqual(undefined); 171 | } 172 | await app.close(); 173 | }); 174 | 175 | it('Create multi providers with factory', async () => { 176 | interface CProvider { 177 | type: string; 178 | } 179 | const C_PROVIDER = Symbol('C_PROVIDER'); 180 | @Injectable() 181 | class C1 { 182 | name = 'c1'; 183 | } 184 | @Injectable() 185 | class C2 { 186 | name = 'c2'; 187 | } 188 | @Injectable() 189 | class P { 190 | @CustomInject(C_PROVIDER, { 191 | multi: true, 192 | propertyName: 'type', 193 | providerFactory: (data) => ({ type: data.name }), 194 | }) 195 | providers!: CProvider['type'][]; 196 | } 197 | const module = await Test.createTestingModule({ 198 | imports: [ 199 | CustomInjectorModule.forRoot(), 200 | CustomInjectorModule.forFeature({ 201 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 202 | }), 203 | CustomInjectorModule.forFeature({ 204 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 205 | }), 206 | ], 207 | providers: [P], 208 | exports: [P], 209 | }).compile(); 210 | const app = module.createNestApplication(); 211 | await app.init(); 212 | const p = app.get

(P); 213 | expect(p.providers).toEqual(['c1', 'c2']); 214 | await app.close(); 215 | }); 216 | 217 | it('Create multi providers with async factory', async () => { 218 | interface CProvider { 219 | type: string; 220 | } 221 | const C_PROVIDER = Symbol('C_PROVIDER'); 222 | @Injectable() 223 | class C1 { 224 | name = 'c1'; 225 | } 226 | @Injectable() 227 | class C2 { 228 | name = 'c2'; 229 | } 230 | @Injectable() 231 | class P { 232 | @CustomInject(C_PROVIDER, { 233 | multi: true, 234 | propertyName: 'type', 235 | providerFactory: (data) => 236 | new Promise((resolve) => 237 | setTimeout(() => resolve({ type: data.name }), 1000) 238 | ), 239 | }) 240 | providers!: CProvider['type'][]; 241 | } 242 | const module = await Test.createTestingModule({ 243 | imports: [ 244 | CustomInjectorModule.forRoot(), 245 | CustomInjectorModule.forFeature({ 246 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 247 | }), 248 | CustomInjectorModule.forFeature({ 249 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 250 | }), 251 | ], 252 | providers: [P], 253 | exports: [P], 254 | }).compile(); 255 | const app = module.createNestApplication(); 256 | await app.init(); 257 | const p = app.get

(P); 258 | expect(p.providers).toEqual(['c1', 'c2']); 259 | await app.close(); 260 | }); 261 | 262 | it('Load multi providers with lazy option on getting data, without set them from application bootstrap', async () => { 263 | interface CProvider { 264 | type: string; 265 | } 266 | const C_PROVIDER = Symbol('C_PROVIDER'); 267 | @Injectable() 268 | class C1 implements CProvider { 269 | type = 'c1'; 270 | } 271 | @Injectable() 272 | class C2 implements CProvider { 273 | type = 'c2'; 274 | } 275 | @Injectable() 276 | class P { 277 | @CustomInject(C_PROVIDER, { 278 | multi: true, 279 | propertyName: 'type', 280 | lazy: true, 281 | }) 282 | providers!: CProvider['type'][]; 283 | } 284 | const module = await Test.createTestingModule({ 285 | imports: [ 286 | CustomInjectorModule.forRoot(), 287 | CustomInjectorModule.forFeature({ 288 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 289 | }), 290 | CustomInjectorModule.forFeature({ 291 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 292 | }), 293 | ], 294 | providers: [P], 295 | exports: [P], 296 | }).compile(); 297 | const app = module.createNestApplication(); 298 | // we don't need start application, because providers marked with lazy options will be ignored when the application bootstrap 299 | // await app.init(); 300 | const p = app.get

(P); 301 | expect(p.providers).toEqual(['c1', 'c2']); 302 | await app.close(); 303 | }); 304 | 305 | it('Use default providers if providers for token not found', async () => { 306 | interface CProvider { 307 | type: string; 308 | } 309 | const C_PROVIDER = Symbol('C_PROVIDER'); 310 | @Injectable() 311 | class P { 312 | @CustomInject(C_PROVIDER, { 313 | multi: true, 314 | propertyName: 'type', 315 | defaultProvidersValue: [], 316 | }) 317 | providers!: CProvider['type'][]; 318 | } 319 | const module = await Test.createTestingModule({ 320 | imports: [CustomInjectorModule.forRoot()], 321 | providers: [P], 322 | exports: [P], 323 | }).compile(); 324 | const app = module.createNestApplication(); 325 | await app.init(); 326 | const providers = app.get

(P).providers; 327 | expect(providers).toEqual([]); 328 | await app.close(); 329 | }); 330 | }); 331 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tests/multi-providers.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { 4 | CustomInject, 5 | CustomInjectorError, 6 | CustomInjectorModule, 7 | } from '../src'; 8 | 9 | describe('Multi providers (unit)', () => { 10 | jest.setTimeout(60 * 1000); 11 | 12 | it('Work with multi providers', async () => { 13 | interface CProvider { 14 | type: string; 15 | } 16 | const C_PROVIDER = Symbol('C_PROVIDER'); 17 | @Injectable() 18 | class C1 implements CProvider { 19 | type = 'c1'; 20 | } 21 | @Injectable() 22 | class C2 implements CProvider { 23 | type = 'c2'; 24 | } 25 | @Injectable() 26 | class P { 27 | @CustomInject(C_PROVIDER, { multi: true }) 28 | providers!: CProvider[]; 29 | } 30 | const module = await Test.createTestingModule({ 31 | imports: [ 32 | CustomInjectorModule.forRoot(), 33 | CustomInjectorModule.forFeature({ 34 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 35 | }), 36 | CustomInjectorModule.forFeature({ 37 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 38 | }), 39 | ], 40 | providers: [P], 41 | exports: [P], 42 | }).compile(); 43 | const app = module.createNestApplication(); 44 | await app.init(); 45 | const p = app.get

(P); 46 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 47 | await app.close(); 48 | }); 49 | 50 | it('Work with multi regular and async providers', async () => { 51 | interface CProvider { 52 | type: string; 53 | } 54 | const C_PROVIDER = Symbol('C_PROVIDER'); 55 | @Injectable() 56 | class C1 implements CProvider { 57 | type = 'c1'; 58 | } 59 | @Injectable() 60 | class C2 implements CProvider { 61 | type = 'c2'; 62 | } 63 | @Injectable() 64 | class P { 65 | @CustomInject(C_PROVIDER, { multi: true }) 66 | providers!: CProvider[]; 67 | } 68 | const module = await Test.createTestingModule({ 69 | imports: [ 70 | CustomInjectorModule.forRoot(), 71 | CustomInjectorModule.forFeature({ 72 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 73 | }), 74 | CustomInjectorModule.forFeature({ 75 | providers: [ 76 | { 77 | provide: C_PROVIDER, 78 | useFactory: () => 79 | new Promise((resolve) => 80 | setTimeout(() => resolve(new C2()), 1000) 81 | ), 82 | }, 83 | ], 84 | }), 85 | ], 86 | providers: [P], 87 | exports: [P], 88 | }).compile(); 89 | const app = module.createNestApplication(); 90 | await app.init(); 91 | const p = app.get

(P); 92 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 93 | await app.close(); 94 | }); 95 | 96 | it('Error if array of providers not set', async () => { 97 | interface CProvider { 98 | type: string; 99 | } 100 | const C_PROVIDER1 = 'C_PROVIDER1'; 101 | @Injectable() 102 | class P { 103 | @CustomInject(C_PROVIDER1, { multi: true }) 104 | providers!: CProvider; 105 | } 106 | const module = await Test.createTestingModule({ 107 | imports: [CustomInjectorModule.forRoot()], 108 | providers: [P], 109 | exports: [P], 110 | }).compile(); 111 | const app = module.createNestApplication(); 112 | try { 113 | await app.init(); 114 | expect(true).toEqual(false); 115 | } catch (err) { 116 | expect(err instanceof CustomInjectorError && err.message).toEqual( 117 | `Providers "C_PROVIDER1" not found!` 118 | ); 119 | expect( 120 | err instanceof CustomInjectorError && 121 | err.injectedProvidersStorageItem?.token 122 | ).toEqual(C_PROVIDER1); 123 | } 124 | await app.close(); 125 | }); 126 | 127 | it('Custom error if array of providers not set', async () => { 128 | interface CProvider { 129 | type: string; 130 | } 131 | class CustomError extends CustomInjectorError { 132 | constructor(public override message: string) { 133 | super(message); 134 | } 135 | } 136 | const C_PROVIDER1 = 'C_PROVIDER1'; 137 | @Injectable() 138 | class P { 139 | @CustomInject(C_PROVIDER1, { 140 | multi: true, 141 | errorFactory: (message: string) => new CustomError(message), 142 | }) 143 | providers!: CProvider; 144 | } 145 | const module = await Test.createTestingModule({ 146 | imports: [CustomInjectorModule.forRoot()], 147 | providers: [P], 148 | exports: [P], 149 | }).compile(); 150 | const app = module.createNestApplication(); 151 | try { 152 | await app.init(); 153 | expect(true).toEqual(false); 154 | } catch (err) { 155 | expect(err instanceof CustomError && err.message).toEqual( 156 | `Providers "C_PROVIDER1" not found!` 157 | ); 158 | expect( 159 | err instanceof CustomError && err.injectedProvidersStorageItem?.token 160 | ).toEqual(undefined); 161 | } 162 | await app.close(); 163 | }); 164 | 165 | it('Create multi providers with factory', async () => { 166 | interface CProvider { 167 | type: string; 168 | } 169 | const C_PROVIDER = Symbol('C_PROVIDER'); 170 | @Injectable() 171 | class C1 { 172 | name = 'c1'; 173 | } 174 | @Injectable() 175 | class C2 { 176 | name = 'c2'; 177 | } 178 | @Injectable() 179 | class P { 180 | @CustomInject(C_PROVIDER, { 181 | multi: true, 182 | providerFactory: (data) => ({ type: data.name }), 183 | }) 184 | providers!: CProvider[]; 185 | } 186 | const module = await Test.createTestingModule({ 187 | imports: [ 188 | CustomInjectorModule.forRoot(), 189 | CustomInjectorModule.forFeature({ 190 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 191 | }), 192 | CustomInjectorModule.forFeature({ 193 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 194 | }), 195 | ], 196 | providers: [P], 197 | exports: [P], 198 | }).compile(); 199 | const app = module.createNestApplication(); 200 | await app.init(); 201 | const p = app.get

(P); 202 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 203 | await app.close(); 204 | }); 205 | 206 | it('Create multi providers with async factory', async () => { 207 | interface CProvider { 208 | type: string; 209 | } 210 | const C_PROVIDER = Symbol('C_PROVIDER'); 211 | @Injectable() 212 | class C1 { 213 | name = 'c1'; 214 | } 215 | @Injectable() 216 | class C2 { 217 | name = 'c2'; 218 | } 219 | @Injectable() 220 | class P { 221 | @CustomInject(C_PROVIDER, { 222 | multi: true, 223 | providerFactory: (data) => 224 | new Promise((resolve) => 225 | setTimeout(() => resolve({ type: data.name }), 1000) 226 | ), 227 | }) 228 | providers!: CProvider[]; 229 | } 230 | const module = await Test.createTestingModule({ 231 | imports: [ 232 | CustomInjectorModule.forRoot(), 233 | CustomInjectorModule.forFeature({ 234 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 235 | }), 236 | CustomInjectorModule.forFeature({ 237 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 238 | }), 239 | ], 240 | providers: [P], 241 | exports: [P], 242 | }).compile(); 243 | const app = module.createNestApplication(); 244 | await app.init(); 245 | const p = app.get

(P); 246 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 247 | await app.close(); 248 | }); 249 | 250 | it('Load multi providers with lazy option on getting data, without set them from application bootstrap', async () => { 251 | interface CProvider { 252 | type: string; 253 | } 254 | const C_PROVIDER = Symbol('C_PROVIDER'); 255 | @Injectable() 256 | class C1 implements CProvider { 257 | type = 'c1'; 258 | } 259 | @Injectable() 260 | class C2 implements CProvider { 261 | type = 'c2'; 262 | } 263 | @Injectable() 264 | class P { 265 | @CustomInject(C_PROVIDER, { multi: true, lazy: true }) 266 | providers!: CProvider[]; 267 | } 268 | const module = await Test.createTestingModule({ 269 | imports: [ 270 | CustomInjectorModule.forRoot(), 271 | CustomInjectorModule.forFeature({ 272 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 273 | }), 274 | CustomInjectorModule.forFeature({ 275 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 276 | }), 277 | ], 278 | providers: [P], 279 | exports: [P], 280 | }).compile(); 281 | const app = module.createNestApplication(); 282 | // we don't need start application, because providers marked with lazy options will be ignored when the application bootstrap 283 | // await app.init(); 284 | const p = app.get

(P); 285 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 286 | await app.close(); 287 | }); 288 | 289 | it('Update multi providers on runtime', async () => { 290 | interface CProvider { 291 | type: string; 292 | } 293 | const C_PROVIDER_NEW = Symbol('C_PROVIDER_NEW'); 294 | @Injectable() 295 | class C1 implements CProvider { 296 | type = 'c1'; 297 | } 298 | @Injectable() 299 | class C2 implements CProvider { 300 | type = 'c2'; 301 | } 302 | @Injectable() 303 | class P { 304 | @CustomInject(C_PROVIDER_NEW, { multi: true }) 305 | providers!: CProvider[]; 306 | } 307 | const module = await Test.createTestingModule({ 308 | imports: [ 309 | CustomInjectorModule.forRoot(), 310 | CustomInjectorModule.forFeature({ 311 | providers: [{ provide: C_PROVIDER_NEW, useValue: new C1() }], 312 | }), 313 | CustomInjectorModule.forFeature({ 314 | providers: [{ provide: C_PROVIDER_NEW, useClass: C2 }], 315 | }), 316 | ], 317 | providers: [P], 318 | exports: [P], 319 | }).compile(); 320 | const app = module.createNestApplication(); 321 | await app.init(); 322 | const p = app.get

(P); 323 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 324 | p.providers.forEach((o) => { 325 | o.type = `updated ${o.type}`; 326 | }); 327 | const p2 = app.get

(P); 328 | expect(p2.providers.map((o) => o.type)).toEqual([ 329 | 'updated c1', 330 | 'updated c2', 331 | ]); 332 | await app.close(); 333 | }); 334 | 335 | it('Update multi providers created with factory on runtime', async () => { 336 | interface CProvider { 337 | type: string; 338 | } 339 | const C_PROVIDER_NEW = Symbol('C_PROVIDER_NEW'); 340 | @Injectable() 341 | class C1 implements CProvider { 342 | type = 'c1'; 343 | } 344 | @Injectable() 345 | class C2 implements CProvider { 346 | type = 'c2'; 347 | } 348 | @Injectable() 349 | class P { 350 | @CustomInject(C_PROVIDER_NEW, { 351 | multi: true, 352 | providerFactory: (data) => ({ ...data, type: `factory ${data.type}` }), 353 | }) 354 | providers!: CProvider[]; 355 | } 356 | const module = await Test.createTestingModule({ 357 | imports: [ 358 | CustomInjectorModule.forRoot(), 359 | CustomInjectorModule.forFeature({ 360 | providers: [{ provide: C_PROVIDER_NEW, useValue: new C1() }], 361 | }), 362 | CustomInjectorModule.forFeature({ 363 | providers: [{ provide: C_PROVIDER_NEW, useClass: C2 }], 364 | }), 365 | ], 366 | providers: [P], 367 | exports: [P], 368 | }).compile(); 369 | const app = module.createNestApplication(); 370 | await app.init(); 371 | const providers = app.get

(P).providers; 372 | expect(providers.map((o) => o.type)).toEqual(['factory c1', 'factory c2']); 373 | providers.forEach((o) => { 374 | o.type = `updated ${o.type}`; 375 | }); 376 | const currentProviders = [...app.get

(P).providers]; 377 | expect(currentProviders.map((o) => o.type)).toEqual([ 378 | 'updated factory c1', 379 | 'updated factory c2', 380 | ]); 381 | await app.close(); 382 | }); 383 | 384 | it('Use default providers if providers for token not found', async () => { 385 | interface CProvider { 386 | type: string; 387 | } 388 | const C_PROVIDER = Symbol('C_PROVIDER'); 389 | @Injectable() 390 | class P { 391 | @CustomInject(C_PROVIDER, { 392 | multi: true, 393 | defaultProvidersValue: [], 394 | }) 395 | providers!: CProvider[]; 396 | } 397 | const module = await Test.createTestingModule({ 398 | imports: [CustomInjectorModule.forRoot()], 399 | providers: [P], 400 | exports: [P], 401 | }).compile(); 402 | const app = module.createNestApplication(); 403 | await app.init(); 404 | const providers = app.get

(P).providers; 405 | expect(providers).toEqual([]); 406 | await app.close(); 407 | }); 408 | 409 | it('Ignore custom injected providers when its not included in DI tree NestJS', async () => { 410 | interface CProvider { 411 | type: string; 412 | } 413 | const C_PROVIDER = Symbol('C_PROVIDER'); 414 | const C_PROVIDER_IGNORED = Symbol('C_PROVIDER_IGNORED'); 415 | @Injectable() 416 | class C1 implements CProvider { 417 | type = 'c1'; 418 | } 419 | @Injectable() 420 | class C2 implements CProvider { 421 | type = 'c2'; 422 | } 423 | @Injectable() 424 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 425 | class P_IGNORED { 426 | @CustomInject(C_PROVIDER_IGNORED, { multi: true }) 427 | providers!: CProvider[]; 428 | } 429 | @Injectable() 430 | class P { 431 | @CustomInject(C_PROVIDER, { multi: true }) 432 | providers!: CProvider[]; 433 | } 434 | const module = await Test.createTestingModule({ 435 | imports: [ 436 | CustomInjectorModule.forRoot(), 437 | CustomInjectorModule.forFeature({ 438 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 439 | }), 440 | CustomInjectorModule.forFeature({ 441 | providers: [{ provide: C_PROVIDER, useClass: C2 }], 442 | }), 443 | ], 444 | providers: [P], 445 | exports: [P], 446 | }).compile(); 447 | const app = module.createNestApplication(); 448 | await app.init(); 449 | const p = app.get

(P); 450 | expect(p.providers.map((o) => o.type)).toEqual(['c1', 'c2']); 451 | await app.close(); 452 | }); 453 | }); 454 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tests/one-provider-by-property.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { 4 | CustomInject, 5 | CustomInjectorError, 6 | CustomInjectorModule, 7 | } from '../src'; 8 | 9 | describe('One provider by property (unit)', () => { 10 | jest.setTimeout(60 * 1000); 11 | 12 | it('Work with one provider', async () => { 13 | interface CProvider { 14 | type: string; 15 | } 16 | const C_PROVIDER = Symbol('C_PROVIDER'); 17 | @Injectable() 18 | class C1 implements CProvider { 19 | type = 'c1'; 20 | } 21 | @Injectable() 22 | class P { 23 | @CustomInject(C_PROVIDER, { propertyName: 'type' }) 24 | provider!: CProvider['type']; 25 | } 26 | const module = await Test.createTestingModule({ 27 | imports: [ 28 | CustomInjectorModule.forRoot(), 29 | CustomInjectorModule.forFeature({ 30 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 31 | }), 32 | ], 33 | providers: [P], 34 | exports: [P], 35 | }).compile(); 36 | const app = module.createNestApplication(); 37 | await app.init(); 38 | const p = app.get

(P); 39 | expect(p.provider).toEqual('c1'); 40 | await app.close(); 41 | }); 42 | 43 | it('Error if provider not set', async () => { 44 | interface CProvider { 45 | type: string; 46 | } 47 | const C_PROVIDER1 = 'C_PROVIDER1'; 48 | @Injectable() 49 | class P { 50 | @CustomInject(C_PROVIDER1, { propertyName: 'type' }) 51 | provider!: CProvider['type']; 52 | } 53 | const module = await Test.createTestingModule({ 54 | imports: [CustomInjectorModule.forRoot()], 55 | providers: [P], 56 | exports: [P], 57 | }).compile(); 58 | const app = module.createNestApplication(); 59 | try { 60 | await app.init(); 61 | expect(true).toEqual(false); 62 | } catch (err) { 63 | expect(err instanceof CustomInjectorError && err.message).toEqual( 64 | `Provider "C_PROVIDER1" not found!` 65 | ); 66 | expect( 67 | err instanceof CustomInjectorError && 68 | err.injectedProvidersStorageItem?.token 69 | ).toEqual(C_PROVIDER1); 70 | } 71 | await app.close(); 72 | }); 73 | 74 | it('Custom error if provider not set', async () => { 75 | interface CProvider { 76 | type: string; 77 | } 78 | class CustomError extends CustomInjectorError { 79 | constructor(public override message: string) { 80 | super(message); 81 | } 82 | } 83 | const C_PROVIDER2 = 'C_PROVIDER2'; 84 | @Injectable() 85 | class P { 86 | @CustomInject(C_PROVIDER2, { 87 | propertyName: 'type', 88 | errorFactory: (message: string) => new CustomError(message), 89 | }) 90 | provider!: CProvider['type']; 91 | } 92 | const module = await Test.createTestingModule({ 93 | imports: [CustomInjectorModule.forRoot()], 94 | providers: [P], 95 | exports: [P], 96 | }).compile(); 97 | const app = module.createNestApplication(); 98 | try { 99 | await app.init(); 100 | expect(true).toEqual(false); 101 | } catch (err) { 102 | expect(err instanceof CustomError && err.message).toEqual( 103 | `Provider "C_PROVIDER2" not found!` 104 | ); 105 | } 106 | await app.close(); 107 | }); 108 | 109 | it('Default value if provider not set', async () => { 110 | interface CProvider { 111 | type: string; 112 | } 113 | const C_PROVIDER1 = 'C_PROVIDER1'; 114 | @Injectable() 115 | class P { 116 | @CustomInject(C_PROVIDER1, { 117 | propertyName: 'type', 118 | defaultProviderValue: { type: 'def' }, 119 | }) 120 | provider!: CProvider['type']; 121 | } 122 | const module = await Test.createTestingModule({ 123 | imports: [CustomInjectorModule.forRoot()], 124 | providers: [P], 125 | exports: [P], 126 | }).compile(); 127 | const app = module.createNestApplication(); 128 | await app.init(); 129 | const p = app.get

(P); 130 | expect(p.provider).toEqual('def'); 131 | await app.close(); 132 | }); 133 | 134 | it('Async default value if provider not set', async () => { 135 | interface CProvider { 136 | type: string; 137 | } 138 | const C_PROVIDER1 = 'C_PROVIDER1'; 139 | @Injectable() 140 | class P { 141 | @CustomInject(C_PROVIDER1, { 142 | propertyName: 'type', 143 | defaultProviderValue: new Promise((resolve) => 144 | setTimeout(() => resolve({ type: 'async' }), 1000) 145 | ), 146 | }) 147 | provider!: CProvider['type']; 148 | } 149 | const module = await Test.createTestingModule({ 150 | imports: [CustomInjectorModule.forRoot()], 151 | providers: [P], 152 | exports: [P], 153 | }).compile(); 154 | const app = module.createNestApplication(); 155 | await app.init(); 156 | const p = app.get

(P); 157 | expect(p.provider).toEqual('async'); 158 | await app.close(); 159 | }); 160 | 161 | it('Create provider with factory', async () => { 162 | interface CProvider { 163 | type: string; 164 | } 165 | const C_PROVIDER = Symbol('C_PROVIDER'); 166 | @Injectable() 167 | class C1 { 168 | name = 'c1'; 169 | } 170 | @Injectable() 171 | class P { 172 | @CustomInject(C_PROVIDER, { 173 | propertyName: 'type', 174 | providerFactory: (data) => ({ type: data.name }), 175 | }) 176 | provider!: CProvider['type']; 177 | } 178 | const module = await Test.createTestingModule({ 179 | imports: [ 180 | CustomInjectorModule.forRoot(), 181 | CustomInjectorModule.forFeature({ 182 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 183 | }), 184 | ], 185 | providers: [P], 186 | exports: [P], 187 | }).compile(); 188 | const app = module.createNestApplication(); 189 | await app.init(); 190 | const p = app.get

(P); 191 | expect(p.provider).toEqual('c1'); 192 | await app.close(); 193 | }); 194 | 195 | it('Create provider with async factory', async () => { 196 | interface CProvider { 197 | type: string; 198 | } 199 | const C_PROVIDER = Symbol('C_PROVIDER'); 200 | @Injectable() 201 | class C1 { 202 | name = 'c1'; 203 | } 204 | @Injectable() 205 | class P { 206 | @CustomInject(C_PROVIDER, { 207 | propertyName: 'type', 208 | providerFactory: (data) => 209 | new Promise((resolve) => 210 | setTimeout(() => resolve({ type: data.name }), 1000) 211 | ), 212 | }) 213 | provider!: CProvider['type']; 214 | } 215 | const module = await Test.createTestingModule({ 216 | imports: [ 217 | CustomInjectorModule.forRoot(), 218 | CustomInjectorModule.forFeature({ 219 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 220 | }), 221 | ], 222 | providers: [P], 223 | exports: [P], 224 | }).compile(); 225 | const app = module.createNestApplication(); 226 | await app.init(); 227 | const p = app.get

(P); 228 | expect(p.provider).toEqual('c1'); 229 | await app.close(); 230 | }); 231 | }); 232 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tests/one-provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { 4 | CustomInject, 5 | CustomInjectorError, 6 | CustomInjectorModule, 7 | } from '../src'; 8 | 9 | describe('One provider (unit)', () => { 10 | jest.setTimeout(60 * 1000); 11 | 12 | it('Work with one provider', async () => { 13 | interface CProvider { 14 | type: string; 15 | } 16 | const C_PROVIDER = Symbol('C_PROVIDER'); 17 | @Injectable() 18 | class C1 implements CProvider { 19 | type = 'c1'; 20 | } 21 | @Injectable() 22 | class P { 23 | @CustomInject(C_PROVIDER) 24 | provider!: CProvider; 25 | } 26 | const module = await Test.createTestingModule({ 27 | imports: [ 28 | CustomInjectorModule.forRoot(), 29 | CustomInjectorModule.forFeature({ 30 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 31 | }), 32 | ], 33 | providers: [P], 34 | exports: [P], 35 | }).compile(); 36 | const app = module.createNestApplication(); 37 | await app.init(); 38 | const p = app.get

(P); 39 | expect(p.provider.type).toEqual('c1'); 40 | await app.close(); 41 | }); 42 | 43 | it('Error if provider not set', async () => { 44 | interface CProvider { 45 | type: string; 46 | } 47 | const C_PROVIDER1 = 'C_PROVIDER1'; 48 | @Injectable() 49 | class P { 50 | @CustomInject(C_PROVIDER1) 51 | provider!: CProvider; 52 | } 53 | const module = await Test.createTestingModule({ 54 | imports: [CustomInjectorModule.forRoot()], 55 | providers: [P], 56 | exports: [P], 57 | }).compile(); 58 | const app = module.createNestApplication(); 59 | try { 60 | await app.init(); 61 | expect(true).toEqual(false); 62 | } catch (err) { 63 | expect(err instanceof CustomInjectorError && err.message).toEqual( 64 | `Provider "C_PROVIDER1" not found!` 65 | ); 66 | expect( 67 | err instanceof CustomInjectorError && 68 | err.injectedProvidersStorageItem?.token 69 | ).toEqual(C_PROVIDER1); 70 | } 71 | await app.close(); 72 | }); 73 | 74 | it('Custom error if provider not set', async () => { 75 | interface CProvider { 76 | type: string; 77 | } 78 | class CustomError extends CustomInjectorError { 79 | constructor(public override message: string) { 80 | super(message); 81 | } 82 | } 83 | const C_PROVIDER2 = 'C_PROVIDER2'; 84 | @Injectable() 85 | class P { 86 | @CustomInject(C_PROVIDER2, { 87 | errorFactory: (message: string) => new CustomError(message), 88 | }) 89 | provider!: CProvider; 90 | } 91 | const module = await Test.createTestingModule({ 92 | imports: [CustomInjectorModule.forRoot()], 93 | providers: [P], 94 | exports: [P], 95 | }).compile(); 96 | const app = module.createNestApplication(); 97 | try { 98 | await app.init(); 99 | expect(true).toEqual(false); 100 | } catch (err) { 101 | expect(err instanceof CustomError && err.message).toEqual( 102 | `Provider "C_PROVIDER2" not found!` 103 | ); 104 | } 105 | await app.close(); 106 | }); 107 | 108 | it('Default value if provider not set', async () => { 109 | interface CProvider { 110 | type: string; 111 | } 112 | const C_PROVIDER1 = 'C_PROVIDER1'; 113 | @Injectable() 114 | class P { 115 | @CustomInject(C_PROVIDER1, { 116 | defaultProviderValue: { type: 'def' }, 117 | }) 118 | provider!: CProvider; 119 | } 120 | const module = await Test.createTestingModule({ 121 | imports: [CustomInjectorModule.forRoot()], 122 | providers: [P], 123 | exports: [P], 124 | }).compile(); 125 | const app = module.createNestApplication(); 126 | await app.init(); 127 | const p = app.get

(P); 128 | expect(p.provider.type).toEqual('def'); 129 | await app.close(); 130 | }); 131 | 132 | it('Async default value if provider not set', async () => { 133 | interface CProvider { 134 | type: string; 135 | } 136 | const C_PROVIDER1 = 'C_PROVIDER1'; 137 | @Injectable() 138 | class P { 139 | @CustomInject(C_PROVIDER1, { 140 | defaultProviderValue: new Promise((resolve) => 141 | setTimeout(() => resolve({ type: 'async' }), 1000) 142 | ), 143 | }) 144 | provider!: CProvider; 145 | } 146 | const module = await Test.createTestingModule({ 147 | imports: [CustomInjectorModule.forRoot()], 148 | providers: [P], 149 | exports: [P], 150 | }).compile(); 151 | const app = module.createNestApplication(); 152 | await app.init(); 153 | const p = app.get

(P); 154 | expect(p.provider.type).toEqual('async'); 155 | await app.close(); 156 | }); 157 | 158 | it('Create provider with factory', async () => { 159 | interface CProvider { 160 | type: string; 161 | } 162 | const C_PROVIDER = Symbol('C_PROVIDER'); 163 | @Injectable() 164 | class C1 { 165 | name = 'c1'; 166 | } 167 | @Injectable() 168 | class P { 169 | @CustomInject(C_PROVIDER, { 170 | providerFactory: (data) => ({ type: data.name }), 171 | }) 172 | provider!: CProvider; 173 | } 174 | const module = await Test.createTestingModule({ 175 | imports: [ 176 | CustomInjectorModule.forRoot(), 177 | CustomInjectorModule.forFeature({ 178 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 179 | }), 180 | ], 181 | providers: [P], 182 | exports: [P], 183 | }).compile(); 184 | const app = module.createNestApplication(); 185 | await app.init(); 186 | const p = app.get

(P); 187 | expect(p.provider.type).toEqual('c1'); 188 | await app.close(); 189 | }); 190 | 191 | it('Create provider with async factory', async () => { 192 | interface CProvider { 193 | type: string; 194 | } 195 | const C_PROVIDER = Symbol('C_PROVIDER'); 196 | @Injectable() 197 | class C1 { 198 | name = 'c1'; 199 | } 200 | @Injectable() 201 | class P { 202 | @CustomInject(C_PROVIDER, { 203 | providerFactory: (data) => 204 | new Promise((resolve) => 205 | setTimeout(() => resolve({ type: data.name }), 1000) 206 | ), 207 | }) 208 | provider!: CProvider; 209 | } 210 | const module = await Test.createTestingModule({ 211 | imports: [ 212 | CustomInjectorModule.forRoot(), 213 | CustomInjectorModule.forFeature({ 214 | providers: [{ provide: C_PROVIDER, useClass: C1 }], 215 | }), 216 | ], 217 | providers: [P], 218 | exports: [P], 219 | }).compile(); 220 | const app = module.createNestApplication(); 221 | await app.init(); 222 | const p = app.get

(P); 223 | expect(p.provider.type).toEqual('c1'); 224 | await app.close(); 225 | }); 226 | }); 227 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": [ 11 | "**/*.spec.ts", 12 | "**/*.e2e-spec.ts", 13 | "**/*.test.ts", 14 | "jest.config.ts" 15 | ], 16 | "include": ["**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /libs/nestjs-custom-injector/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.e2e-spec.ts", 12 | "**/*.test.tsx", 13 | "**/*.spec.tsx", 14 | "**/*.test.js", 15 | "**/*.spec.js", 16 | "**/*.test.jsx", 17 | "**/*.spec.jsx", 18 | "**/*.d.ts", 19 | "jest.config.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "migrations": [ 3 | { 4 | "version": "13.9.0-beta.0", 5 | "description": "Replace @nrwl/tao with nx", 6 | "cli": "nx", 7 | "implementation": "./src/migrations/update-13-9-0/replace-tao-with-nx", 8 | "package": "@nrwl/workspace", 9 | "name": "13-9-0-replace-tao-with-nx" 10 | }, 11 | { 12 | "version": "13.10.0-beta.0", 13 | "description": "Update the decorate-angular-cli script to require nx instead of @nrwl/cli", 14 | "cli": "nx", 15 | "implementation": "./src/migrations/update-13-10-0/update-decorate-cli", 16 | "package": "@nrwl/workspace", 17 | "name": "13-10-0-update-decorate-cli" 18 | }, 19 | { 20 | "version": "13.10.0-beta.0", 21 | "description": "Update the tasks runner property to import it from the nx package instead of @nrwl/worksapce", 22 | "cli": "nx", 23 | "implementation": "./src/migrations/update-13-10-0/update-tasks-runner", 24 | "package": "@nrwl/workspace", 25 | "name": "13-10-0-update-tasks-runner" 26 | }, 27 | { 28 | "version": "14.0.0-beta.0", 29 | "description": "Changes the presets in nx.json to come from the nx package", 30 | "cli": "nx", 31 | "implementation": "./src/migrations/update-14-0-0/change-nx-json-presets", 32 | "package": "@nrwl/workspace", 33 | "name": "14-0-0-change-nx-json-presets" 34 | }, 35 | { 36 | "version": "14.0.0-beta.0", 37 | "description": "Migrates from @nrwl/workspace:run-script to nx:run-script", 38 | "cli": "nx", 39 | "implementation": "./src/migrations/update-14-0-0/change-npm-script-executor", 40 | "package": "@nrwl/workspace", 41 | "name": "14-0-0-change-npm-script-executor" 42 | }, 43 | { 44 | "version": "14.2.0", 45 | "description": "Explicitly enable sourceAnalysis for all workspaces extending from npm.json or core.json (this was default behavior prior to 14.2)", 46 | "cli": "nx", 47 | "implementation": "./src/migrations/update-14-2-0/enable-source-analysis", 48 | "package": "@nrwl/workspace", 49 | "name": "14-2-0-enable-source-analysis" 50 | }, 51 | { 52 | "version": "14.0.0-beta.2", 53 | "cli": "nx", 54 | "description": "Update move jest config files to .ts files.", 55 | "factory": "./src/migrations/update-14-0-0/update-jest-config-ext", 56 | "package": "@nrwl/jest", 57 | "name": "update-jest-config-extensions" 58 | }, 59 | { 60 | "version": "14.1.5-beta.0", 61 | "cli": "nx", 62 | "description": "Update to export default in jest config and revert jest.preset.ts to jest.preset.js", 63 | "factory": "./src/migrations/update-14-1-5/update-exports-jest-config", 64 | "package": "@nrwl/jest", 65 | "name": "update-to-export-default" 66 | }, 67 | { 68 | "version": "14.5.5-beta.0", 69 | "cli": "nx", 70 | "description": "Exclude jest.config.ts from tsconfig where missing.", 71 | "factory": "./src/migrations/update-14-0-0/update-jest-config-ext", 72 | "package": "@nrwl/jest", 73 | "name": "exclude-jest-config-from-ts-config" 74 | }, 75 | { 76 | "version": "14.6.0-beta.0", 77 | "cli": "nx", 78 | "description": "Update jest configs to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28#configuration-options)", 79 | "factory": "./src/migrations/update-14-6-0/update-configs-jest-28", 80 | "package": "@nrwl/jest", 81 | "name": "update-configs-jest-28" 82 | }, 83 | { 84 | "version": "14.6.0-beta.0", 85 | "cli": "nx", 86 | "description": "Update jest test files to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28)", 87 | "factory": "./src/migrations/update-14-6-0/update-tests-jest-28", 88 | "package": "@nrwl/jest", 89 | "name": "update-tests-jest-28" 90 | }, 91 | { 92 | "cli": "nx", 93 | "version": "14.1.9-beta.0", 94 | "description": "Adds @swc/core and @swc-node as a dev dep if you are using them", 95 | "factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed", 96 | "package": "@nrwl/linter", 97 | "name": "add-swc-deps" 98 | }, 99 | { 100 | "cli": "nx", 101 | "version": "14.2.3-beta.0", 102 | "description": "Adds @swc/core and @swc-node as a dev dep if you are using them (repeated due to prior mistake)", 103 | "factory": "./src/migrations/update-14-1-9/add-swc-deps-if-needed", 104 | "package": "@nrwl/linter", 105 | "name": "add-swc-deps-again" 106 | }, 107 | { 108 | "cli": "nx", 109 | "version": "14.4.4", 110 | "description": "Adds @typescript-eslint/utils as a dev dep", 111 | "factory": "./src/migrations/update-14-4-4/experimental-to-utils-deps", 112 | "package": "@nrwl/linter", 113 | "name": "experimental-to-utils-deps" 114 | }, 115 | { 116 | "cli": "nx", 117 | "version": "14.4.4", 118 | "description": "Switch from @typescript-eslint/experimental-utils to @typescript-eslint/utils in all rules and rules.spec files", 119 | "factory": "./src/migrations/update-14-4-4/experimental-to-utils-rules", 120 | "package": "@nrwl/linter", 121 | "name": "experimental-to-utils-rules" 122 | }, 123 | { 124 | "cli": "nx", 125 | "version": "13.8.5-beta.1", 126 | "description": "Renames @nrwl/node:build to @nrwl/node:webpack", 127 | "factory": "./src/migrations/update-13-8-5/rename-build-to-webpack", 128 | "package": "@nrwl/node", 129 | "name": "rename-build-to-webpack" 130 | }, 131 | { 132 | "cli": "nx", 133 | "version": "13.8.5-beta.1", 134 | "description": "Renames @nrwl/node:execute to @nrwl/node:node", 135 | "factory": "./src/migrations/update-13-8-5/rename-execute-to-node", 136 | "package": "@nrwl/node", 137 | "name": "rename-execute-to-node" 138 | }, 139 | { 140 | "cli": "nx", 141 | "version": "13.8.5-beta.1", 142 | "description": "Renames @nrwl/node:package to @nrwl/js:tsc", 143 | "factory": "./src/migrations/update-13-8-5/update-package-to-tsc", 144 | "package": "@nrwl/node", 145 | "name": "update-package-to-tsc" 146 | }, 147 | { 148 | "cli": "nx", 149 | "version": "13.8.5-beta.1", 150 | "description": "Renames @nrwl/js:node to @nrwl/node:node", 151 | "factory": "./src/migrations/update-13-8-5/update-node-executor", 152 | "package": "@nrwl/js", 153 | "name": "update-node-executor" 154 | }, 155 | { 156 | "cli": "nx", 157 | "version": "13.8.5-beta.1", 158 | "description": "Adjust .swcrc to .lib.swcrc", 159 | "factory": "./src/migrations/update-13-8-5/update-swcrc", 160 | "package": "@nrwl/js", 161 | "name": "update-swcrc" 162 | }, 163 | { 164 | "cli": "nx", 165 | "version": "13.10.1-beta.1", 166 | "description": "Update .lib.swcrc to exclude missing test files", 167 | "factory": "./src/migrations/update-13-10-1/update-lib-swcrc-exclude", 168 | "package": "@nrwl/js", 169 | "name": "update-swcrc-exclude" 170 | }, 171 | { 172 | "cli": "nx", 173 | "version": "14.0.0-beta.2", 174 | "description": "Exclude jest config from .lib.swcrc", 175 | "factory": "./src/migrations/update-14-0-0/exclude-jest-config-swcrc", 176 | "package": "@nrwl/js", 177 | "name": "exclude-jest-config-swcrc" 178 | }, 179 | { 180 | "cli": "nx", 181 | "version": "14.1.5-beta.0", 182 | "description": "Rename option swcrcPath to swcrc, and resolve relative to workspace root", 183 | "factory": "./src/migrations/update-14.1.5-beta.0/update-swcrc-path", 184 | "package": "@nrwl/js", 185 | "name": "update-swcrc-path" 186 | } 187 | ] 188 | } 189 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "nestjs-custom-injector-workspace", 3 | "affected": { 4 | "defaultBase": "master" 5 | }, 6 | "implicitDependencies": { 7 | "package.json": { 8 | "dependencies": "*", 9 | "devDependencies": "*" 10 | }, 11 | ".eslintrc.json": "*" 12 | }, 13 | "tasksRunnerOptions": { 14 | "default": { 15 | "runner": "nx/tasks-runners/default", 16 | "options": { 17 | "cacheableOperations": ["build", "lint", "test", "e2e"], 18 | "parallel": 1 19 | } 20 | } 21 | }, 22 | "targetDependencies": { 23 | "build": [ 24 | { 25 | "target": "build", 26 | "projects": "dependencies" 27 | } 28 | ] 29 | }, 30 | "cli": { 31 | "defaultCollection": "@nrwl/node" 32 | }, 33 | "defaultProject": "demo" 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-custom-injector-workspace", 3 | "version": "2.2.3", 4 | "description": "Custom injecting logic for NestJS with support multi providing", 5 | "keywords": [ 6 | "nestjs", 7 | "custom", 8 | "multi", 9 | "providers", 10 | "decorators", 11 | "global", 12 | "injector", 13 | "inject" 14 | ], 15 | "license": "MIT", 16 | "author": "EndyKaufman ", 17 | "engines": { 18 | "node": ">=14", 19 | "npm": ">=6" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/EndyKaufman/nestjs-custom-injector/issues" 23 | }, 24 | "homepage": "https://github.com/EndyKaufman/nestjs-custom-injector", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/EndyKaufman/nestjs-custom-injector.git" 28 | }, 29 | "maintainers": [ 30 | { 31 | "name": "EndyKaufman", 32 | "email": "admin@site15.ru" 33 | } 34 | ], 35 | "scripts": { 36 | "rucken": "rucken", 37 | "nx": "nx", 38 | "start": "node dist/apps/demo/main.js", 39 | "build": "npm run nx -- build demo", 40 | "lib:build": "npm run generate && npm run nx -- build nestjs-custom-injector && npm run lib:build-changelog", 41 | "lib:release": "standard-version", 42 | "lib:publish": "npm publish ./dist/libs/nestjs-custom-injector", 43 | "lib:create-release": "npm run test && npm run lib:build && npm run lib:release && git push --follow-tags origin develop", 44 | "lib:build-changelog": "./node_modules/.bin/cp-cli ./CHANGELOG.md ./dist/libs/nestjs-custom-injector/CHANGELOG.md", 45 | "test": "jest --all --runInBand --detectOpenHandles --forceExit --passWithNoTests --reporters=default --includeConsoleOutput=true --json --outputFile=jest.result.json --testLocationInResults", 46 | "serve": "npm run nx -- serve demo", 47 | "serve:local": "export $(xargs < ./.env.local) && npm run serve", 48 | "prepare": "husky install", 49 | "lint": "npm run tsc:lint && nx workspace-lint && npm run nx -- run-many --target=lint --all", 50 | "lint:fix": "npm run tsc:lint && nx workspace-lint --fix && nx run-many --target=lint --all --fix && nx format:write --all", 51 | "tsc:lint": "tsc --noEmit -p tsconfig.base.json", 52 | "generate": "npm run rucken -- prepare && npm run lint:fix" 53 | }, 54 | "private": true, 55 | "devDependencies": { 56 | "@nestjs/schematics": "9.0.3", 57 | "@nestjs/testing": "9.0.11", 58 | "@nrwl/cli": "14.6.5", 59 | "@nrwl/eslint-plugin-nx": "14.6.5", 60 | "@nrwl/jest": "14.6.5", 61 | "@nrwl/js": "14.6.5", 62 | "@nrwl/linter": "14.6.5", 63 | "@nrwl/nest": "14.6.5", 64 | "@nrwl/node": "14.6.5", 65 | "@nrwl/workspace": "14.6.5", 66 | "@types/jest": "28.1.8", 67 | "@types/node": "18.7.1", 68 | "@typescript-eslint/eslint-plugin": "5.33.1", 69 | "@typescript-eslint/parser": "5.33.1", 70 | "cp-cli": "^1.0.2", 71 | "eslint": "8.15.0", 72 | "eslint-config-prettier": "8.1.0", 73 | "husky": "8.0.1", 74 | "jest": "28.1.3", 75 | "lint-staged": "13.0.3", 76 | "nx": "14.6.5", 77 | "prettier": "2.7.1", 78 | "rucken": "4.2.1", 79 | "standard-version": "9.5.0", 80 | "supertest": "6.2.2", 81 | "ts-jest": "28.0.8", 82 | "ts-node": "10.9.1", 83 | "tsconfig-paths-jest": "0.0.1", 84 | "typescript": "4.7.4" 85 | }, 86 | "dependencies": { 87 | "@nestjs/common": "9.0.11", 88 | "@nestjs/core": "9.0.11", 89 | "@nestjs/platform-express": "9.0.11", 90 | "@nestjs/swagger": "6.1.2", 91 | "env-var": "7.3.0", 92 | "reflect-metadata": "0.1.13", 93 | "rxjs": "7.1.0", 94 | "swagger-ui-express": "4.5.0", 95 | "tslib": "2.0.0" 96 | }, 97 | "lint-staged": { 98 | "*.{js,ts}": "eslint --fix", 99 | "*.{js,ts,css,scss,md}": "prettier --ignore-unknown --write" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndyKaufman/nestjs-custom-injector/345ae8277f18f7c83fa8f9cb0bbd9828b771a207/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /transloco.config.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, existsSync } = require('fs'); 2 | module.exports = existsSync('transloco.config.json') 3 | ? JSON.parse(readFileSync('transloco.config.json').toString()) 4 | : {}; 5 | -------------------------------------------------------------------------------- /transloco.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "serverRootTranslationsPath": "./apps/demo/src/assets/i18n/", 3 | "langs": ["en"], 4 | "serverScopedLibs": ["libs/nestjs-custom-injector"], 5 | "rootTranslationsPath": "./apps/demo/src/assets/i18n/", 6 | "scopedLibs": ["libs/nestjs-custom-injector"], 7 | "clientRootTranslationsPath": "./apps/demo/src/assets/i18n/", 8 | "clientScopedLibs": ["libs/nestjs-custom-injector"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "allowSyntheticDefaultImports": true, 18 | "strictNullChecks": true, 19 | "noImplicitOverride": true, 20 | "strictPropertyInitialization": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "esModuleInterop": true, 24 | "noImplicitAny": false, 25 | "paths": { 26 | "nestjs-custom-injector": ["libs/nestjs-custom-injector/src/index.ts"] 27 | } 28 | }, 29 | "exclude": ["node_modules", "tmp"] 30 | } 31 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "demo": "apps/demo", 5 | "nestjs-custom-injector": "libs/nestjs-custom-injector" 6 | } 7 | } 8 | --------------------------------------------------------------------------------