Compare commits

..

2 Commits

Author SHA1 Message Date
Kamil Myśliwiec
e5612e0197 chore: merge 10.0.0 2023-06-12 10:58:30 +02:00
Kamil Myśliwiec
6c54448bf7 fix(fastify): validate middleware paths 2023-06-12 10:51:02 +02:00
313 changed files with 165368 additions and 152487 deletions

View File

@@ -1,31 +1,9 @@
version: 2.1
parameters:
check-legacy-node-version:
type: boolean
default: false
legacy-node-version:
type: string
default: '14.21.3'
maintenance-node-version:
type: string
default: '16.20'
active-node-version:
type: string
default: '18.17'
current-node-version:
type: string
default: '20.5'
version: 2
aliases:
- &restore-cache
restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- &save-cache
save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- &install-deps
run:
name: Install dependencies
@@ -39,89 +17,70 @@ aliases:
name: Test
command: npm run test
unit-tests-template: &unit-tests-template
working_directory: ~/nest
steps:
- checkout
- *restore-cache
- *install-deps
- *build-packages
- *run-unit-tests
jobs:
build:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
- image: cimg/node:16.19
steps:
- checkout
- run:
name: Update NPM version
command: 'sudo npm install -g npm@^9'
- *restore-cache
- *install-deps
- *save-cache
- *build-packages
command: 'sudo npm install -g npm@^8'
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: Install dependencies
command: npm ci --legacy-peer-deps
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- run:
name: Build
command: npm run build
test:
parameters:
node-version:
type: string
test_node_16:
working_directory: ~/nest
docker:
- image: cimg/node:<< parameters.node-version >>
- image: cimg/node:16.19
steps:
- when:
condition:
and:
- equal:
[
'<< parameters.node-version >>',
'<< pipeline.parameters.legacy-node-version >>',
]
- not: << pipeline.parameters.check-legacy-node-version >>
steps:
- run:
name: Skip
command: |
echo Skipping
- when:
condition:
or:
- not:
equal:
[
'<< parameters.node-version >>',
'<< pipeline.parameters.legacy-node-version >>',
]
- << pipeline.parameters.check-legacy-node-version >>
steps:
- checkout
- *restore-cache
- *install-deps
- *build-packages
- when:
condition:
equal:
[
'<< parameters.node-version >>',
'<< pipeline.parameters.maintenance-node-version >>',
]
steps:
- run:
name: Test (coverage)
command: npm run test:cov
- run:
name: Collect coverage
command: npm run coverage
- store_artifacts:
path: coverage
- when:
condition:
not:
equal:
[
'<< parameters.node-version >>',
'<< pipeline.parameters.maintenance-node-version >>',
]
steps:
- *run-unit-tests
- checkout
- *restore-cache
- *install-deps
- *build-packages
- run:
name: Test (coverage)
command: npm run test:cov
- run:
name: Collect coverage
command: npm run coverage
- store_artifacts:
path: coverage
test_node_18:
<<: *unit-tests-template
docker:
- image: cimg/node:18.14
test_node_19:
<<: *unit-tests-template
docker:
- image: cimg/node:19.8
lint:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
- image: circleci/node:16
steps:
- checkout
- *restore-cache
@@ -146,9 +105,9 @@ jobs:
- run:
name: Upgrade Node.js
command: |
nvm install << pipeline.parameters.maintenance-node-version >>
nvm install v16
node -v
nvm alias default << pipeline.parameters.maintenance-node-version >>
nvm alias default v16
- run:
name: Install Docker Compose
command: |
@@ -171,7 +130,7 @@ jobs:
codechecks_benchmarks:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
- image: cimg/node:16.19
steps:
- checkout
- *restore-cache
@@ -187,9 +146,9 @@ jobs:
samples:
working_directory: ~/nest
docker:
- image: cimg/node:<< pipeline.parameters.maintenance-node-version >>
- image: cimg/node:16.19
environment:
DISABLE_OPENCOLLECTIVE: 'true'
- DISABLE_OPENCOLLECTIVE: true
steps:
- checkout
- *restore-cache
@@ -199,21 +158,16 @@ jobs:
command: npm run build:samples
workflows:
version: 2
build-and-test:
jobs:
- build
- test:
- test_node_16:
requires:
- build
- test_node_18:
requires:
- build
matrix:
parameters:
node-version:
[
'<< pipeline.parameters.legacy-node-version >>',
'<< pipeline.parameters.maintenance-node-version >>',
'<< pipeline.parameters.active-node-version >>',
'<< pipeline.parameters.current-node-version >>',
]
- lint:
requires:
- build

View File

@@ -23,7 +23,6 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-array-constructor': 'off',
},
},
{

View File

@@ -6,7 +6,7 @@ body:
attributes:
value: |
## :warning: We use GitHub Issues to track bug reports, feature requests and regressions
If you are not sure that your issue is a bug, you could:
- read the [FAQ's common errors](https://docs.nestjs.com/faq/common-errors) page
@@ -46,7 +46,7 @@ body:
label: "Steps to reproduce"
description: |
How the issue manifests?
You could leave this blank if you already write this in your reproduction code
You could leave this blank if you alread write this in your reproduction code
placeholder: |
1. `npm ci`
2. `npm start:dev`

View File

@@ -6,7 +6,7 @@ body:
attributes:
value: |
## :warning: We use GitHub Issues to track bug reports, feature requests and regressions
If you are not sure that your issue is a bug, you could:
- read the [FAQ's common errors](https://docs.nestjs.com/faq/common-errors) page

View File

@@ -6,7 +6,7 @@ body:
attributes:
value: |
## :warning: We use GitHub Issues to track bug reports, feature requests and regressions
If you are not sure that your issue is a bug, you could:
- read the [FAQ's common errors](https://docs.nestjs.com/faq/common-errors) page

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

View File

@@ -164,7 +164,7 @@ You will need [Node.js](https://nodejs.org) version >= 10.13.0 (except for v13).
1. After cloning the repo, run:
```bash
$ npm ci --legacy-peer-deps # (or yarn install)
$ npm ci # (or yarn install)
```
2. In order to prepare your environment run `prepare.sh` shell script:

View File

@@ -23,7 +23,7 @@
## Description
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
@@ -34,10 +34,10 @@ Nest is a framework for building efficient, scalable <a href="https://nodejs.org
## Getting started
- To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
- 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
- [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
- [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
* [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
* [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
## Questions
@@ -56,29 +56,26 @@ With official support, you can get expert help straight from Nest core team. We
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support from the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
#### Principal Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td><td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="110" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
@@ -87,11 +84,17 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
#### Silver Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
<td><a href="https://immediateedgeapp.org/" target="_blank"><img src="https://nestjs.com/img/immediate-edge-logo.png" width="120" valign="middle" /></td>
</tr>
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://www.myleodsc.com/" target="_blank"><img src="https://nestjs.com/img/myleo-logo.png" width="180" valign="middle" /></td><td>
<a href="https://careers.meetdandy.com/?gh_src=063ba61e3us" target="_blank"><img src="https://nestjs.com/img/dandy-roles-logo.svg" width="150" valign="middle" /></td><td>
<a href="https://www.castlecraft.in" target="_blank"><img src="https://nestjs.com/img/castlecraft-logo.png" width="150" valign="middle" /></td>
<td><a href="https://www.tinystacks.com" target="_blank"><img src="https://nestjs.com/img/tinystacks-logo.png#1" width="140" valign="middle" /></td>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td></tr><tr>
<td><a href="https://bilberrry.com/" target="_blank"><img src="https://nestjs.com/img/bilberrry-logo.svg" width="180" valign="middle" /></td>
<td><a href="https://ipinfo.ai/" target="_blank"><img src="https://nestjs.com/img/ipinfo-logo.png" width="130" valign="middle" /></td>
<td><a href="https://chax.at" target="_blank"><img src="https://nestjs.com/img/chaxat-logo.png" width="100" valign="middle" /></td></tr>
</table>
#### Sponsors
@@ -99,26 +102,42 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" width="72" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" width="125" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" width="100" valign="middle" /></a> </td></tr><tr><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" width="71" valign="middle" /></a></td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="75" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://firesticktricks.com" target="_blank"><img src="https://nestjs.com/img/firesticktricks-logo.png" width="120" valign="middle" /></a></td></tr><tr><td align="center" valign="middle">
<a href="https://www.codeguesser.co.uk" target="_blank"><img src="https://nestjs.com/img/codeguesser-logo.svg" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://f-a.nz/" target="_blank"><img src="https://nestjs.com/img/franz.svg" width="80" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://sparkfabrik.com/" target="_blank"><img src="https://nestjs.com/img/sparkfabrik-logo.png" width="120" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://www.thebigphonestore.co.uk/" target="_blank"><img src="https://nestjs.com/img/the-big-phone-store-logo.png" width="65" valign="middle" /></a></td>
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td></tr><tr>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td>
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://houseofangular.io/" target="_blank"><img src="https://nestjs.com/img/house-of-angular.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.bystored.com/" target="_blank"><img src="https://nestjs.com/img/stored-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://studyclerk.com/pay-for-research-paper" target="_blank"><img src="https://nestjs.com/img/studyclerk-logo.png" width="125" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://xyndata.com" target="_blank"><img src="https://nestjs.com/img/xyndata-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://messaged.com/" target="_blank"><img src="https://nestjs.com/img/messaged-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://klqc.de" target="_blank"><img src="https://nestjs.com/img/klcqcl-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://themobilereality.com/" target="_blank"><img src="https://nestjs.com/img/mobile-reality-logo.png" width="45" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.mobilefactory.jp/" target="_blank"><img src="https://nestjs.com/img/mobilefactory-logo.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers
@@ -127,9 +146,9 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
## Stay in touch
- Author - [Kamil Myśliwiec](https://x.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- X - [@nestframework](https://x.com/nestframework)
* Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
* Website - [https://nestjs.com](https://nestjs.com/)
* Twitter - [@nestframework](https://twitter.com/nestframework)
## License

View File

@@ -1,59 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DiscoveryService } from '@nestjs/core';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
import { WebhooksExplorer } from '../src/webhooks.explorer';
import { NonAppliedDecorator } from '../src/decorators/non-applied.decorator';
describe('DiscoveryModule', () => {
let moduleRef: TestingModule;
beforeEach(async () => {
moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
});
it('should discover all providers & handlers with corresponding annotations', async () => {
const webhooksExplorer = moduleRef.get(WebhooksExplorer);
expect(webhooksExplorer.getWebhooks()).to.be.eql([
{
handlers: [
{
event: 'start',
methodName: 'onStart',
},
],
name: 'cleanup',
},
{
handlers: [
{
event: 'start',
methodName: 'onStart',
},
],
name: 'flush',
},
]);
});
it('should return an empty array if no providers were found for a given discoverable decorator', () => {
const discoveryService = moduleRef.get(DiscoveryService);
const providers = discoveryService.getProviders({
metadataKey: NonAppliedDecorator.KEY,
});
expect(providers).to.be.eql([]);
});
it('should return an empty array if no controllers were found for a given discoverable decorator', () => {
const discoveryService = moduleRef.get(DiscoveryService);
const controllers = discoveryService.getControllers({
metadataKey: NonAppliedDecorator.KEY,
});
expect(controllers).to.be.eql([]);
});
});

View File

@@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { DiscoveryModule } from '@nestjs/core';
import { MyWebhookModule } from './my-webhook/my-webhook.module';
import { WebhooksExplorer } from './webhooks.explorer';
@Module({
imports: [MyWebhookModule, DiscoveryModule],
providers: [WebhooksExplorer],
})
export class AppModule {}

View File

@@ -1,9 +0,0 @@
import { DiscoveryService } from '@nestjs/core';
/**
* This decorator must not be used anywhere!
*
* This will be used to test the scenario where we are trying to retrieving
* metadata for a discoverable decorator that was not applied to any class.
*/
export const NonAppliedDecorator = DiscoveryService.createDecorator();

View File

@@ -1,6 +0,0 @@
import { DiscoveryService } from '@nestjs/core';
export const Webhook = DiscoveryService.createDecorator<{ name: string }>();
export const WebhookHandler = DiscoveryService.createDecorator<{
event: string;
}>();

View File

@@ -1,9 +0,0 @@
import { Webhook, WebhookHandler } from '../decorators/webhook.decorators';
@Webhook({ name: 'cleanup' })
export class CleanupWebhook {
@WebhookHandler({ event: 'start' })
onStart() {
console.log('cleanup started');
}
}

View File

@@ -1,9 +0,0 @@
import { Webhook, WebhookHandler } from '../decorators/webhook.decorators';
@Webhook({ name: 'flush' })
export class FlushWebhook {
@WebhookHandler({ event: 'start' })
onStart() {
console.log('flush started');
}
}

View File

@@ -1,6 +0,0 @@
import { Module } from '@nestjs/common';
import { CleanupWebhook } from './cleanup.webhook';
import { FlushWebhook } from './flush.webhook';
@Module({ providers: [CleanupWebhook, FlushWebhook] })
export class MyWebhookModule {}

View File

@@ -1,39 +0,0 @@
import { Injectable } from '@nestjs/common';
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
import { Webhook, WebhookHandler } from './decorators/webhook.decorators';
@Injectable()
export class WebhooksExplorer {
constructor(
private readonly discoveryService: DiscoveryService,
private readonly metadataScanner: MetadataScanner,
) {}
getWebhooks() {
const webhooks = this.discoveryService.getProviders({
metadataKey: Webhook.KEY,
});
return webhooks.map(wrapper => {
const { name } = this.discoveryService.getMetadataByDecorator(
Webhook,
wrapper,
);
return {
name,
handlers: this.metadataScanner
.getAllMethodNames(wrapper.metatype.prototype)
.map(methodName => {
const { event } = this.discoveryService.getMetadataByDecorator(
WebhookHandler,
wrapper,
methodName,
);
return {
methodName,
event,
};
}),
};
});
}
}

View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES2021",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist",
"paths": {
"@nestjs/common": ["../../packages/common"],
"@nestjs/common/*": ["../../packages/common/*"],
"@nestjs/core": ["../../packages/core"],
"@nestjs/core/*": ["../../packages/core/*"],
"@nestjs/microservices": ["../../packages/microservices"],
"@nestjs/microservices/*": ["../../packages/microservices/*"],
"@nestjs/websockets": ["../../packages/websockets"],
"@nestjs/websockets/*": ["../../packages/websockets/*"],
"@nestjs/testing": ["../../packages/testing"],
"@nestjs/testing/*": ["../../packages/testing/*"],
"@nestjs/platform-express": ["../../packages/platform-express"],
"@nestjs/platform-express/*": ["../../packages/platform-express/*"],
"@nestjs/platform-socket.io": ["../../packages/platform-socket.io"],
"@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"],
"@nestjs/platform-ws": ["../../packages/platform-ws"],
"@nestjs/platform-ws/*": ["../../packages/platform-ws/*"]
}
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}

View File

@@ -25,7 +25,7 @@ services:
- "9001:9001"
restart: always
mysql:
image: mysql:8.1.0
image: mysql:8.0.32
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
@@ -50,7 +50,7 @@ services:
zookeeper:
container_name: test-zookeeper
hostname: zookeeper
image: confluentinc/cp-zookeeper:7.4.1
image: confluentinc/cp-zookeeper:7.3.2
ports:
- "2181:2181"
environment:
@@ -59,7 +59,7 @@ services:
kafka:
container_name: test-kafka
hostname: kafka
image: confluentinc/cp-kafka:7.4.1
image: confluentinc/cp-kafka:7.3.2
depends_on:
- zookeeper
ports:

View File

@@ -192,7 +192,7 @@ describe('Middleware (FastifyAdapter)', () => {
});
});
describe('should execute middleware only once for given routes', () => {
describe.only('should execute middleware only once for given routes', () => {
class Middleware implements NestMiddleware {
use(request: any, reply: any, next: () => void) {
if (request.middlewareExecutionCount === undefined) {

View File

@@ -1,63 +0,0 @@
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { Controller, Injectable, Module } from '@nestjs/common';
class B {}
@Injectable()
class A {
constructor(b: B) {}
}
@Injectable()
class BImpl {
constructor(a: A) {}
}
@Controller()
class AppController {
constructor(a: A) {}
}
@Module({
imports: [],
controllers: [AppController],
providers: [A, { provide: B, useClass: BImpl }],
})
export class AppModule {}
describe('Circular custom providers', () => {
it('should throw an exception (useClass + regular provider)', async () => {
try {
const builder = Test.createTestingModule({
imports: [AppModule],
});
await builder.compile();
expect(true).to.be.eql(false);
} catch (err) {
expect(err.message).to.be.eql(
'A circular dependency has been detected inside "A". Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()". Note that circular relationships between custom providers (e.g., factories) are not supported since functions cannot be called more than once.',
);
}
});
it('should throw an exception (2 factories)', async () => {
try {
const builder = Test.createTestingModule({
providers: [
{ provide: 'ABC', useFactory: () => ({}), inject: ['DEF'] },
{ provide: 'DEF', useFactory: () => ({}), inject: ['ABC'] },
],
});
await builder.compile();
expect(true).to.be.eql(false);
} catch (err) {
expect(err.message).to.be.eql(
'A circular dependency has been detected inside "ABC". Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()". Note that circular relationships between custom providers (e.g., factories) are not supported since functions cannot be called more than once.',
);
}
});
});

View File

@@ -118,14 +118,14 @@ describe('Optional factory provider deps', () => {
} catch (err) {
expect(err).to.be.instanceOf(UnknownDependenciesException);
expect(err.message).to
.equal(`Nest can't resolve dependencies of the POSSIBLY_MISSING_DEP (?). Please make sure that the argument "MISSING_DEP" at index [0] is available in the RootTestModule context.
.equal(`Nest can't resolve dependencies of the POSSIBLY_MISSING_DEP (?). Please make sure that the argument MISSING_DEP at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If "MISSING_DEP" is a provider, is it part of the current RootTestModule?
- If "MISSING_DEP" is exported from a separate @Module, is that module imported within RootTestModule?
- If MISSING_DEP is a provider, is it part of the current RootTestModule?
- If MISSING_DEP is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing "MISSING_DEP" */ ]
imports: [ /* the Module containing MISSING_DEP */ ]
})
`);
}

View File

@@ -1,29 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as chai from 'chai';
import { expect } from 'chai';
import chaiAsPromised = require('chai-as-promised');
import { AppModule } from '../src/app.module';
chai.use(chaiAsPromised);
describe('Lazy imports', () => {
let server;
let app: INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
server = app.getHttpAdapter().getInstance();
});
it(`should allow imports of global modules`, async () => {
await expect(app.init()).to.eventually.be.fulfilled;
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -1,16 +0,0 @@
import { Module } from '@nestjs/common';
import { LazyModuleLoader } from '@nestjs/core';
import { EagerModule } from './eager.module';
import { GlobalModule } from './global.module';
import { LazyModule } from './lazy.module';
@Module({
imports: [GlobalModule, EagerModule],
})
export class AppModule {
constructor(public loader: LazyModuleLoader) {}
async onApplicationBootstrap() {
await this.loader.load(() => LazyModule);
}
}

View File

@@ -1,12 +0,0 @@
import { Module, Injectable } from '@nestjs/common';
import { GlobalService } from './global.module';
@Injectable()
export class EagerService {
constructor(public globalService: GlobalService) {}
}
@Module({
providers: [EagerService],
})
export class EagerModule {}

View File

@@ -1,13 +0,0 @@
import { Module, Injectable, Global } from '@nestjs/common';
@Injectable()
export class GlobalService {
constructor() {}
}
@Global()
@Module({
providers: [GlobalService],
exports: [GlobalService],
})
export class GlobalModule {}

View File

@@ -1,12 +0,0 @@
import { Module, Injectable } from '@nestjs/common';
import { GlobalService } from './global.module';
@Injectable()
export class LazyService {
constructor(public globalService: GlobalService) {}
}
@Module({
providers: [LazyService],
})
export class LazyModule {}

View File

@@ -1,8 +0,0 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();

View File

@@ -4,7 +4,9 @@ import { CatsModule } from './cats/cats.module';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/test'),
MongooseModule.forRoot('mongodb://localhost:27017/test', {
useNewUrlParser: true,
}),
CatsModule,
],
})

View File

@@ -10,6 +10,7 @@ class ConfigService implements MongooseOptionsFactory {
createMongooseOptions(): MongooseModuleOptions {
return {
uri: 'mongodb://localhost:27017/test',
useNewUrlParser: true,
};
}
}

View File

@@ -10,6 +10,7 @@ class ConfigService implements MongooseOptionsFactory {
createMongooseOptions(): MongooseModuleOptions {
return {
uri: 'mongodb://localhost:27017/test',
useNewUrlParser: true,
};
}
}

View File

@@ -6,6 +6,7 @@ import { CatsModule } from './cats/cats.module';
imports: [
MongooseModule.forRootAsync({
useFactory: () => ({
useNewUrlParser: true,
uri: 'mongodb://localhost:27017/test',
}),
}),

View File

@@ -6,7 +6,7 @@ export class AppController {
@Sse('sse')
sse(): Observable<MessageEvent> {
return interval(1000).pipe(
map(_ => ({ data: { hello: 'world' } }) as MessageEvent),
map(_ => ({ data: { hello: 'world' } } as MessageEvent)),
);
}
}

View File

@@ -77,7 +77,7 @@ describe('Fastify FileSend', () => {
expect(headers['content-disposition']).to.equal(
'attachment; filename="Readme.md"',
);
expect(headers['content-length']).to.equal(`${readme.byteLength}`);
expect(headers['content-length']).to.equal(readme.byteLength);
expect(payload).to.equal(readmeString);
});
});

View File

@@ -1,6 +1,6 @@
{
"type": "mysql",
"host": "127.0.0.1",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",

View File

@@ -7,7 +7,7 @@ import { PhotoModule } from './photo/photo.module';
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: '127.0.0.1',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',

View File

@@ -11,7 +11,7 @@ class ConfigService implements TypeOrmOptionsFactory {
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: '127.0.0.1',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',

View File

@@ -11,7 +11,7 @@ class ConfigService implements TypeOrmOptionsFactory {
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: '127.0.0.1',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',

View File

@@ -8,7 +8,7 @@ import { PhotoModule } from './photo/photo.module';
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: '127.0.0.1',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',

View File

@@ -11,7 +11,7 @@ export class DatabaseModule {
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: '127.0.0.1',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',

View File

@@ -418,67 +418,4 @@ describe('URI Versioning', () => {
await app.close();
});
});
// ======================================================================== //
describe('with middleware applied', () => {
before(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
await app.init();
});
describe('GET /middleware', () => {
it('should return "Hello from middleware function!"', () => {
return request(app.getHttpServer())
.get('/v1/middleware')
.expect(200)
.expect('Hello from middleware function!');
});
});
describe('GET /middleware/override', () => {
it('should return "Hello from middleware function!"', () => {
return request(app.getHttpServer())
.get('/v2/middleware/override')
.expect(200)
.expect('Hello from middleware function!');
});
});
describe('GET /middleware/multiple', () => {
it('should return "Hello from middleware function!" (v1)', () => {
return request(app.getHttpServer())
.get('/v1/middleware/multiple')
.expect(200)
.expect('Hello from middleware function!');
});
it('should return "Hello from middleware function!" (v2)', () => {
return request(app.getHttpServer())
.get('/v2/middleware/multiple')
.expect(200)
.expect('Hello from middleware function!');
});
});
describe('GET /middleware/neutral', () => {
it('should return "Hello from middleware function!"', () => {
return request(app.getHttpServer())
.get('/middleware/neutral')
.expect(200)
.expect('Hello from middleware function!');
});
});
after(async () => {
await app.close();
});
});
});

View File

@@ -1,14 +1,11 @@
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { AppV1Controller } from './app-v1.controller';
import { AppV2Controller } from './app-v2.controller';
import { MiddlewareController } from './middleware.controller';
import { MultipleMiddlewareVersionController } from './multiple-middleware.controller';
import { MultipleVersionController } from './multiple.controller';
import { VersionNeutralMiddlewareController } from './neutral-middleware.controller';
import { VersionNeutralController } from './neutral.controller';
import { NoVersioningController } from './no-versioning.controller';
import { OverridePartialController } from './override-partial.controller';
import { VersionNeutralController } from './neutral.controller';
import { OverrideController } from './override.controller';
import { OverridePartialController } from './override-partial.controller';
@Module({
imports: [],
@@ -20,19 +17,6 @@ import { OverrideController } from './override.controller';
VersionNeutralController,
OverrideController,
OverridePartialController,
MiddlewareController,
MultipleMiddlewareVersionController,
VersionNeutralMiddlewareController,
],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res) => res.end('Hello from middleware function!'))
.forRoutes(
MiddlewareController,
MultipleMiddlewareVersionController,
VersionNeutralMiddlewareController,
);
}
}
export class AppModule {}

View File

@@ -1,18 +0,0 @@
import { Controller, Get, Version } from '@nestjs/common';
@Controller({
path: 'middleware',
version: '1',
})
export class MiddlewareController {
@Get('/')
hello() {
return 'Hello from "MiddlewareController"!';
}
@Version('2')
@Get('/override')
hellov2() {
return 'Hello from "MiddlewareController"!';
}
}

View File

@@ -1,12 +0,0 @@
import { Controller, Get } from '@nestjs/common';
@Controller({
version: ['1', '2'],
path: 'middleware',
})
export class MultipleMiddlewareVersionController {
@Get('/multiple')
multiple() {
return 'Multiple Versions 1 or 2';
}
}

View File

@@ -1,12 +0,0 @@
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';
@Controller({
path: 'middleware',
version: VERSION_NEUTRAL,
})
export class VersionNeutralMiddlewareController {
@Get('/neutral')
neutral() {
return 'Neutral';
}
}

View File

@@ -66,37 +66,5 @@ describe('WebSocketGateway', () => {
);
});
it(`should be able to get the pattern in an interceptor`, async () => {
app = await createNestApp(ApplicationGateway);
await app.listen(3000);
ws = io('http://localhost:8080');
ws.emit('getClient', {
test: 'test',
});
await new Promise<void>(resolve =>
ws.on('popClient', data => {
expect(data.path).to.be.eql('getClient');
resolve();
}),
);
});
it(`should be able to get the pattern in a filter (when the error comes from a known handler)`, async () => {
app = await createNestApp(ApplicationGateway);
await app.listen(3000);
ws = io('http://localhost:8080');
ws.emit('getClientWithError', {
test: 'test',
});
await new Promise<void>(resolve =>
ws.on('exception', data => {
expect(data.pattern).to.be.eql('getClientWithError');
resolve();
}),
);
});
afterEach(() => app.close());
});

View File

@@ -194,30 +194,6 @@ describe('WebSocketGateway (WsAdapter)', () => {
});
});
it('should let the execution context have a getPattern() method on getClient()', async () => {
app = await createNestApp(ApplicationGateway);
await app.listen(3000);
ws = new WebSocket('ws://localhost:8080');
await new Promise(resolve => ws.on('open', resolve));
ws.send(
JSON.stringify({
event: 'getClient',
data: {
test: 'test',
},
}),
);
await new Promise<void>(resolve =>
ws.on('message', data => {
expect(JSON.parse(data).data.path).to.be.eql('getClient');
ws.close();
resolve();
}),
);
});
afterEach(async function () {
await app.close();
});

View File

@@ -1,13 +1,8 @@
import { UseFilters, UseInterceptors } from '@nestjs/common';
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WsException,
} from '@nestjs/websockets';
import { RequestInterceptor } from './request.interceptor';
import { throwError } from 'rxjs';
import { RequestFilter } from './request.filter';
@WebSocketGateway(8080)
export class ApplicationGateway {
@@ -18,19 +13,4 @@ export class ApplicationGateway {
data,
};
}
@UseInterceptors(RequestInterceptor)
@SubscribeMessage('getClient')
getPathCalled(client, data) {
return {
event: 'popClient',
data: { ...data, path: client.pattern },
};
}
@UseFilters(RequestFilter)
@SubscribeMessage('getClientWithError')
getPathCalledWithError() {
return throwError(() => new WsException('This is an error'));
}
}

View File

@@ -1,12 +0,0 @@
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
@Catch(WsException)
export class RequestFilter implements ExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
const wsCtx = host.switchToWs();
const pattern = wsCtx.getPattern();
const client = wsCtx.getClient();
client.emit('exception', { pattern, message: exception.message });
}
}

View File

@@ -1,11 +0,0 @@
import { CallHandler, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class RequestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const client = context.switchToWs().getClient();
const pattern = context.switchToWs().getPattern();
client.pattern = pattern;
return next.handle();
}
}

View File

@@ -1,6 +1,4 @@
import { UseInterceptors } from '@nestjs/common';
import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets';
import { RequestInterceptor } from './request.interceptor';
@WebSocketGateway()
export class ServerGateway {
@@ -11,13 +9,4 @@ export class ServerGateway {
data,
};
}
@UseInterceptors(RequestInterceptor)
@SubscribeMessage('getClient')
getPathCalled(client, data) {
return {
event: 'popClient',
data: { ...data, path: client.pattern },
};
}
}

View File

@@ -3,5 +3,5 @@
"packages": [
"packages/*"
],
"version": "10.2.8"
"version": "9.4.2"
}

44221
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "10.2.7",
"version": "9.4.0",
"description": "Modern, fast, powerful node.js web framework",
"homepage": "https://nestjs.com",
"repository": {
@@ -25,7 +25,7 @@
"move:node_modules": "gulp move:node_modules",
"build:samples": "gulp install:samples && npm run build && npm run move:samples && gulp build:samples && gulp test:samples && gulp test:e2e:samples",
"codechecks:benchmarks": "codechecks ./tools/benchmarks/check-benchmarks.ts",
"coverage": "nyc report --reporter=text-lcov | coveralls -v",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"format": "prettier \"**/*.ts\" \"packages/**/*.json\" --ignore-path ./.prettierignore --write && git status",
"postinstall": "opencollective",
"test": "mocha packages/**/*.spec.ts",
@@ -64,7 +64,7 @@
"cli-color": "2.0.3",
"cors": "2.8.5",
"express": "4.18.2",
"fast-json-stringify": "5.8.0",
"fast-json-stringify": "5.7.0",
"fast-safe-stringify": "2.1.1",
"graphql-subscriptions": "2.0.0",
"iterare": "1.2.1",
@@ -72,67 +72,67 @@
"path-to-regexp": "3.2.0",
"reflect-metadata": "0.1.13",
"rxjs": "7.8.1",
"socket.io": "4.7.2",
"tslib": "2.6.2",
"socket.io": "4.6.1",
"tslib": "2.5.2",
"uid": "2.0.2",
"uuid": "9.0.1"
"uuid": "9.0.0"
},
"devDependencies": {
"@apollo/server": "4.9.5",
"@apollo/server": "4.7.1",
"@codechecks/client": "0.1.12",
"@commitlint/cli": "18.2.0",
"@commitlint/config-angular": "18.1.0",
"@fastify/cors": "8.4.0",
"@commitlint/cli": "17.6.3",
"@commitlint/config-angular": "17.6.3",
"@fastify/cors": "8.2.1",
"@fastify/formbody": "7.4.0",
"@fastify/middie": "8.3.0",
"@fastify/multipart": "8.0.0",
"@fastify/static": "6.12.0",
"@fastify/view": "8.2.0",
"@grpc/grpc-js": "1.9.7",
"@grpc/proto-loader": "0.7.10",
"@nestjs/apollo": "12.0.9",
"@nestjs/graphql": "12.0.9",
"@nestjs/mongoose": "10.0.1",
"@nestjs/typeorm": "10.0.0",
"@types/amqplib": "0.10.3",
"@types/bytes": "3.1.3",
"@types/chai": "4.3.9",
"@types/chai-as-promised": "7.1.7",
"@types/cors": "2.8.15",
"@types/express": "4.17.20",
"@types/gulp": "4.0.16",
"@types/http-errors": "2.0.3",
"@types/mocha": "10.0.3",
"@types/node": "20.8.10",
"@types/sinon": "10.0.20",
"@types/supertest": "2.0.15",
"@types/ws": "8.5.8",
"@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.9.0",
"amqp-connection-manager": "4.1.14",
"@fastify/multipart": "7.6.0",
"@fastify/static": "6.10.1",
"@fastify/view": "7.4.1",
"@grpc/grpc-js": "1.8.14",
"@grpc/proto-loader": "0.7.7",
"@nestjs/apollo": "11.0.5",
"@nestjs/graphql": "11.0.5",
"@nestjs/mongoose": "9.2.2",
"@nestjs/typeorm": "9.0.1",
"@types/amqplib": "0.10.1",
"@types/bytes": "3.1.1",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/cors": "2.8.13",
"@types/express": "4.17.17",
"@types/gulp": "4.0.10",
"@types/http-errors": "2.0.1",
"@types/mocha": "10.0.1",
"@types/node": "20.2.3",
"@types/sinon": "10.0.15",
"@types/supertest": "2.0.12",
"@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "4.33.0",
"@typescript-eslint/parser": "4.33.0",
"amqp-connection-manager": "4.1.13",
"amqplib": "0.10.3",
"artillery": "1.7.9",
"body-parser": "1.20.2",
"bytes": "3.1.2",
"cache-manager": "5.2.4",
"cache-manager": "5.2.1",
"cache-manager-redis-store": "3.0.1",
"chai": "4.3.10",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"clang-format": "1.8.0",
"commitlint-circle": "1.0.0",
"concurrently": "8.2.2",
"conventional-changelog": "5.1.0",
"core-js": "3.33.1",
"concurrently": "8.0.1",
"conventional-changelog": "3.1.25",
"core-js": "3.30.2",
"coveralls": "3.1.1",
"delete-empty": "3.0.0",
"engine.io-client": "6.5.2",
"eslint": "8.52.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.29.0",
"engine.io-client": "6.4.0",
"eslint": "7.32.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-import": "2.27.5",
"eventsource": "2.0.2",
"fancy-log": "2.0.0",
"fastify": "4.24.3",
"graphql": "16.8.1",
"fastify": "4.17.0",
"graphql": "16.6.0",
"graphql-tools": "9.0.0",
"gulp": "4.0.2",
"gulp-clang-format": "1.0.27",
@@ -148,35 +148,35 @@
"kafkajs": "2.2.4",
"lerna": "2.11.0",
"lerna-changelog": "2.2.0",
"light-my-request": "5.11.0",
"lint-staged": "15.0.2",
"light-my-request": "5.9.1",
"lint-staged": "13.2.2",
"markdown-table": "2.0.0",
"merge-graphql-schemas": "1.7.8",
"mocha": "10.2.0",
"mongoose": "7.6.3",
"mqtt": "5.1.3",
"mongoose": "7.2.0",
"mqtt": "4.3.7",
"multer": "1.4.4",
"mysql2": "3.6.2",
"nats": "2.17.0",
"nodemon": "3.0.1",
"nyc": "14.1.1",
"prettier": "3.0.3",
"redis": "4.6.10",
"mysql2": "3.3.1",
"nats": "2.13.1",
"nodemon": "2.0.22",
"nyc": "15.1.0",
"prettier": "2.8.8",
"redis": "4.6.6",
"rxjs-compat": "6.6.7",
"sinon": "17.0.0",
"sinon": "15.1.0",
"sinon-chai": "3.7.0",
"socket.io-client": "4.7.2",
"socket.io-client": "4.6.1",
"subscriptions-transport-ws": "0.11.0",
"supertest": "6.3.3",
"ts-morph": "20.0.0",
"ts-morph": "18.0.0",
"ts-node": "10.9.1",
"typeorm": "0.3.17",
"typescript": "5.2.2",
"typeorm": "0.3.16",
"typescript": "5.0.4",
"wrk": "1.2.1",
"ws": "8.14.2"
"ws": "8.13.0"
},
"engines": {
"node": ">= 16"
"node": ">= 12.9.0"
},
"collective": {
"type": "opencollective",

View File

@@ -23,7 +23,7 @@
## Description
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
@@ -34,10 +34,10 @@ Nest is a framework for building efficient, scalable <a href="https://nodejs.org
## Getting started
- To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
- 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
- [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
- [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
* [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
* [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
## Questions
@@ -56,29 +56,26 @@ With official support, you can get expert help straight from Nest core team. We
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support from the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
#### Principal Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td><td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="110" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
@@ -87,11 +84,17 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
#### Silver Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
<td><a href="https://immediateedgeapp.org/" target="_blank"><img src="https://nestjs.com/img/immediate-edge-logo.png" width="120" valign="middle" /></td>
</tr>
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://www.myleodsc.com/" target="_blank"><img src="https://nestjs.com/img/myleo-logo.png" width="180" valign="middle" /></td><td>
<a href="https://careers.meetdandy.com/?gh_src=063ba61e3us" target="_blank"><img src="https://nestjs.com/img/dandy-roles-logo.svg" width="150" valign="middle" /></td><td>
<a href="https://www.castlecraft.in" target="_blank"><img src="https://nestjs.com/img/castlecraft-logo.png" width="150" valign="middle" /></td>
<td><a href="https://www.tinystacks.com" target="_blank"><img src="https://nestjs.com/img/tinystacks-logo.png#1" width="140" valign="middle" /></td>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td></tr><tr>
<td><a href="https://bilberrry.com/" target="_blank"><img src="https://nestjs.com/img/bilberrry-logo.svg" width="180" valign="middle" /></td>
<td><a href="https://ipinfo.ai/" target="_blank"><img src="https://nestjs.com/img/ipinfo-logo.png" width="130" valign="middle" /></td>
<td><a href="https://chax.at" target="_blank"><img src="https://nestjs.com/img/chaxat-logo.png" width="100" valign="middle" /></td></tr>
</table>
#### Sponsors
@@ -99,26 +102,41 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" width="72" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" width="125" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" width="100" valign="middle" /></a> </td></tr><tr><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" width="71" valign="middle" /></a></td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="75" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://firesticktricks.com" target="_blank"><img src="https://nestjs.com/img/firesticktricks-logo.png" width="120" valign="middle" /></a></td></tr><tr><td align="center" valign="middle">
<a href="https://www.codeguesser.co.uk" target="_blank"><img src="https://nestjs.com/img/codeguesser-logo.svg" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://f-a.nz/" target="_blank"><img src="https://nestjs.com/img/franz.svg" width="80" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://sparkfabrik.com/" target="_blank"><img src="https://nestjs.com/img/sparkfabrik-logo.png" width="120" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://www.thebigphonestore.co.uk/" target="_blank"><img src="https://nestjs.com/img/the-big-phone-store-logo.png" width="65" valign="middle" /></a></td>
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td></tr><tr>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td>
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://houseofangular.io/" target="_blank"><img src="https://nestjs.com/img/house-of-angular.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.bystored.com/" target="_blank"><img src="https://nestjs.com/img/stored-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://studyclerk.com/pay-for-research-paper" target="_blank"><img src="https://nestjs.com/img/studyclerk-logo.png" width="125" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://xyndata.com" target="_blank"><img src="https://nestjs.com/img/xyndata-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://messaged.com/" target="_blank"><img src="https://nestjs.com/img/messaged-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://klqc.de" target="_blank"><img src="https://nestjs.com/img/klcqcl-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://themobilereality.com/" target="_blank"><img src="https://nestjs.com/img/mobile-reality-logo.png" width="45" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.mobilefactory.jp/" target="_blank"><img src="https://nestjs.com/img/mobilefactory-logo.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers
@@ -127,9 +145,9 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
## Stay in touch
- Author - [Kamil Myśliwiec](https://x.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- X - [@nestframework](https://x.com/nestframework)
* Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
* Website - [https://nestjs.com](https://nestjs.com/)
* Twitter - [@nestframework](https://twitter.com/nestframework)
## License

View File

@@ -111,7 +111,7 @@ export const Head = createMappingDecorator(RequestMethod.HEAD);
export const All = createMappingDecorator(RequestMethod.ALL);
/**
* Route handler (method) Decorator. Routes HTTP SEARCH requests to the specified path.
* Route handler (method) Decorator. Routes all HTTP requests to the specified path.
*
* @see [Routing](https://docs.nestjs.com/controllers#routing)
*

View File

@@ -5,8 +5,7 @@ import {
import { isObject, isString } from '../utils/shared.utils';
export interface HttpExceptionOptions {
/** original cause of the error */
cause?: unknown;
cause?: Error;
description?: string;
}
@@ -69,13 +68,14 @@ export class HttpException extends Error {
this.initCause();
}
public cause: unknown;
public cause: Error | undefined;
/**
* Configures error chaining support
*
* @see https://nodejs.org/en/blog/release/v16.9.0/#error-cause
* @see https://github.com/microsoft/TypeScript/issues/45167
* See:
* - https://nodejs.org/en/blog/release/v16.9.0/#error-cause
* - https://github.com/microsoft/TypeScript/issues/45167
*/
public initCause(): void {
if (this.options?.cause) {

View File

@@ -3,7 +3,6 @@ import { types } from 'util';
import { HttpStatus } from '../enums';
import { isFunction } from '../utils/shared.utils';
import { StreamableFileOptions, StreamableHandlerResponse } from './interfaces';
import { Logger } from '../services';
/**
* @see [Streaming files](https://docs.nestjs.com/techniques/streaming-files)
@@ -12,7 +11,6 @@ import { Logger } from '../services';
*/
export class StreamableFile {
private readonly stream: Readable;
protected logger = new Logger('StreamableFile');
protected handleError: (
err: Error,
@@ -30,10 +28,6 @@ export class StreamableFile {
res.send(err.message);
};
protected logError: (err: Error) => void = (err: Error) => {
this.logger.error(err.message, err.stack);
};
constructor(buffer: Uint8Array, options?: StreamableFileOptions);
constructor(readable: Readable, options?: StreamableFileOptions);
constructor(
@@ -80,13 +74,4 @@ export class StreamableFile {
this.handleError = handler;
return this;
}
get errorLogger() {
return this.logError;
}
setErrorLogger(handler: (err: Error) => void) {
this.logError = handler;
return this;
}
}

View File

@@ -28,7 +28,6 @@ export {
HttpServer,
HttpExceptionBody,
HttpExceptionBodyMessage,
HttpRedirectResponse,
INestApplication,
INestApplicationContext,
INestMicroservice,

View File

@@ -31,10 +31,6 @@ export interface WsArgumentsHost {
* Returns the client object.
*/
getClient<T = any>(): T;
/**
* Returns the pattern for the event
*/
getPattern(): string;
}
/**

View File

@@ -1,6 +0,0 @@
import { HttpStatus } from '../../enums';
export interface HttpRedirectResponse {
url: string;
statusCode: HttpStatus;
}

View File

@@ -1,5 +1,4 @@
export * from './http-exception-body.interface';
export * from './http-redirect-response.interface';
export * from './http-server.interface';
export * from './message-event.interface';
export * from './raw-body-request.interface';

View File

@@ -51,8 +51,7 @@ export interface ConfigurableModuleBuilderOptions {
export class ConfigurableModuleBuilder<
ModuleOptions,
StaticMethodKey extends string = typeof DEFAULT_METHOD_KEY,
FactoryClassMethodKey extends
string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
ExtraModuleDefinitionOptions = {},
> {
protected staticMethodKey: StaticMethodKey;

View File

@@ -28,8 +28,7 @@ export type ConfigurableModuleOptionsFactory<
*/
export interface ConfigurableModuleAsyncOptions<
ModuleOptions,
FactoryClassMethodKey extends
string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
> extends Pick<ModuleMetadata, 'imports'> {
/**
* Injection token resolving to an existing provider. The provider must implement

View File

@@ -15,8 +15,7 @@ import { ConfigurableModuleAsyncOptions } from './configurable-module-async-opti
export type ConfigurableModuleCls<
ModuleOptions,
MethodKey extends string = typeof DEFAULT_METHOD_KEY,
FactoryClassMethodKey extends
string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
FactoryClassMethodKey extends string = typeof DEFAULT_FACTORY_CLASS_METHOD_KEY,
ExtraModuleDefinitionOptions = {},
> = {
new (): any;

View File

@@ -42,7 +42,8 @@ export function getInjectionProviders(
// get injection tokens of the matched providers, if any
search = match
.filter(p => (p as any)?.inject)
.flatMap(p => (p as FactoryProvider).inject)
.map(p => (p as FactoryProvider).inject)
.flat()
.map(mapInjectToTokens);
}
return result;

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "10.2.8",
"version": "9.4.2",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"homepage": "https://nestjs.com",
@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"iterare": "1.2.1",
"tslib": "2.6.2",
"tslib": "2.5.2",
"uid": "2.0.2"
},
"peerDependencies": {

View File

@@ -16,21 +16,17 @@ export type FileTypeValidatorOptions = {
*
* @publicApi
*/
export class FileTypeValidator extends FileValidator<
FileTypeValidatorOptions,
IFile
> {
export class FileTypeValidator extends FileValidator<FileTypeValidatorOptions> {
buildErrorMessage(): string {
return `Validation failed (expected type is ${this.validationOptions.fileType})`;
}
isValid(file?: IFile): boolean {
isValid<TFile extends IFile = any>(file: TFile): boolean {
if (!this.validationOptions) {
return true;
}
return (
!!file &&
'mimetype' in file &&
!!file.mimetype.match(this.validationOptions.fileType)
);

View File

@@ -6,18 +6,15 @@ import { IFile } from './interfaces';
* @see {ParseFilePipe}
* @publicApi
*/
export abstract class FileValidator<
TValidationOptions = Record<string, any>,
TFile extends IFile = IFile,
> {
export abstract class FileValidator<TValidationOptions = Record<string, any>> {
constructor(protected readonly validationOptions: TValidationOptions) {}
/**
* Indicates if this file should be considered valid, according to the options passed in the constructor.
* @param file the file from the request object
*/
abstract isValid(
file?: TFile | TFile[] | Record<string, TFile[]>,
abstract isValid<TFile extends IFile = any>(
file?: TFile,
): boolean | Promise<boolean>;
/**

View File

@@ -13,10 +13,7 @@ export type MaxFileSizeValidatorOptions = {
*
* @publicApi
*/
export class MaxFileSizeValidator extends FileValidator<
MaxFileSizeValidatorOptions,
IFile
> {
export class MaxFileSizeValidator extends FileValidator<MaxFileSizeValidatorOptions> {
buildErrorMessage(): string {
if ('message' in this.validationOptions) {
if (typeof this.validationOptions.message === 'function') {
@@ -29,8 +26,8 @@ export class MaxFileSizeValidator extends FileValidator<
return `Validation failed (expected size is less than ${this.validationOptions.maxSize})`;
}
public isValid(file?: IFile): boolean {
if (!this.validationOptions || !file) {
public isValid<TFile extends IFile = any>(file: TFile): boolean {
if (!this.validationOptions) {
return true;
}

View File

@@ -9,7 +9,6 @@ import {
ErrorHttpStatusCode,
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { isNil } from '../utils/shared.utils';
/**
* @publicApi
@@ -17,7 +16,6 @@ import { isNil } from '../utils/shared.utils';
export interface ParseBoolPipeOptions {
errorHttpStatusCode?: ErrorHttpStatusCode;
exceptionFactory?: (error: string) => any;
optional?: boolean;
}
/**
@@ -33,7 +31,7 @@ export class ParseBoolPipe
{
protected exceptionFactory: (error: string) => any;
constructor(@Optional() protected readonly options?: ParseBoolPipeOptions) {
constructor(@Optional() options?: ParseBoolPipeOptions) {
options = options || {};
const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } =
options;
@@ -53,9 +51,6 @@ export class ParseBoolPipe
value: string | boolean,
metadata: ArgumentMetadata,
): Promise<boolean> {
if (isNil(value) && this.options?.optional) {
return value;
}
if (this.isTrue(value)) {
return true;
}

View File

@@ -1,17 +1,14 @@
import { Injectable, Optional } from '../decorators/core';
import { ArgumentMetadata, HttpStatus } from '../index';
import { ArgumentMetadata, HttpStatus, Injectable, Optional } from '../index';
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
import {
ErrorHttpStatusCode,
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { isNil } from '../utils/shared.utils';
/**
* @publicApi
*/
export interface ParseEnumPipeOptions {
optional?: boolean;
errorHttpStatusCode?: ErrorHttpStatusCode;
exceptionFactory?: (error: string) => any;
}
@@ -26,9 +23,10 @@ export interface ParseEnumPipeOptions {
@Injectable()
export class ParseEnumPipe<T = any> implements PipeTransform<T> {
protected exceptionFactory: (error: string) => any;
constructor(
protected readonly enumType: T,
@Optional() protected readonly options?: ParseEnumPipeOptions,
@Optional() options?: ParseEnumPipeOptions,
) {
if (!enumType) {
throw new Error(
@@ -52,9 +50,6 @@ export class ParseEnumPipe<T = any> implements PipeTransform<T> {
* @param metadata contains metadata about the currently processed route argument
*/
async transform(value: T, metadata: ArgumentMetadata): Promise<T> {
if (isNil(value) && this.options?.optional) {
return value;
}
if (!this.isEnum(value)) {
throw this.exceptionFactory(
'Validation failed (enum string is expected)',

View File

@@ -1,11 +1,10 @@
import { Injectable, Optional } from '../decorators/core';
import { ArgumentMetadata, HttpStatus } from '../index';
import { Injectable, Optional } from '../decorators/core';
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
import {
ErrorHttpStatusCode,
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { isNil } from '../utils/shared.utils';
/**
* @publicApi
@@ -13,7 +12,6 @@ import { isNil } from '../utils/shared.utils';
export interface ParseFloatPipeOptions {
errorHttpStatusCode?: ErrorHttpStatusCode;
exceptionFactory?: (error: string) => any;
optional?: boolean;
}
/**
@@ -27,7 +25,7 @@ export interface ParseFloatPipeOptions {
export class ParseFloatPipe implements PipeTransform<string> {
protected exceptionFactory: (error: string) => any;
constructor(@Optional() protected readonly options?: ParseFloatPipeOptions) {
constructor(@Optional() options?: ParseFloatPipeOptions) {
options = options || {};
const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } =
options;
@@ -45,9 +43,6 @@ export class ParseFloatPipe implements PipeTransform<string> {
* @param metadata contains metadata about the currently processed route argument
*/
async transform(value: string, metadata: ArgumentMetadata): Promise<number> {
if (isNil(value) && this.options?.optional) {
return value;
}
if (!this.isNumeric(value)) {
throw this.exceptionFactory(
'Validation failed (numeric string is expected)',

View File

@@ -9,7 +9,6 @@ import {
ErrorHttpStatusCode,
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { isNil } from '../utils/shared.utils';
/**
* @publicApi
@@ -17,7 +16,6 @@ import { isNil } from '../utils/shared.utils';
export interface ParseIntPipeOptions {
errorHttpStatusCode?: ErrorHttpStatusCode;
exceptionFactory?: (error: string) => any;
optional?: boolean;
}
/**
@@ -31,7 +29,7 @@ export interface ParseIntPipeOptions {
export class ParseIntPipe implements PipeTransform<string> {
protected exceptionFactory: (error: string) => any;
constructor(@Optional() protected readonly options?: ParseIntPipeOptions) {
constructor(@Optional() options?: ParseIntPipeOptions) {
options = options || {};
const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } =
options;
@@ -49,9 +47,6 @@ export class ParseIntPipe implements PipeTransform<string> {
* @param metadata contains metadata about the currently processed route argument
*/
async transform(value: string, metadata: ArgumentMetadata): Promise<number> {
if (isNil(value) && this.options?.optional) {
return value;
}
if (!this.isNumeric(value)) {
throw this.exceptionFactory(
'Validation failed (numeric string is expected)',

View File

@@ -9,7 +9,7 @@ import {
ErrorHttpStatusCode,
HttpErrorByCode,
} from '../utils/http-error-by-code.util';
import { isNil, isString } from '../utils/shared.utils';
import { isString } from '../utils/shared.utils';
/**
* @publicApi
@@ -18,7 +18,6 @@ export interface ParseUUIDPipeOptions {
version?: '3' | '4' | '5';
errorHttpStatusCode?: ErrorHttpStatusCode;
exceptionFactory?: (errors: string) => any;
optional?: boolean;
}
/**
@@ -39,7 +38,7 @@ export class ParseUUIDPipe implements PipeTransform<string> {
private readonly version: '3' | '4' | '5';
protected exceptionFactory: (errors: string) => any;
constructor(@Optional() protected readonly options?: ParseUUIDPipeOptions) {
constructor(@Optional() options?: ParseUUIDPipeOptions) {
options = options || {};
const {
exceptionFactory,
@@ -54,9 +53,6 @@ export class ParseUUIDPipe implements PipeTransform<string> {
}
async transform(value: string, metadata: ArgumentMetadata): Promise<string> {
if (isNil(value) && this.options?.optional) {
return value;
}
if (!this.isUUID(value, this.version)) {
throw this.exceptionFactory(
`Validation failed (uuid${

View File

@@ -179,7 +179,7 @@ export class ValidationPipe implements PipeTransform<any> {
if (type === 'custom' && !this.validateCustomDecorators) {
return false;
}
const types = [String, Boolean, Number, Array, Object, Buffer, Date];
const types = [String, Boolean, Number, Array, Object, Buffer];
return !types.some(t => metatype === t) && !isNil(metatype);
}

View File

@@ -26,7 +26,6 @@ const DEFAULT_LOG_LEVELS: LogLevel[] = [
'warn',
'debug',
'verbose',
'fatal',
];
const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
@@ -146,23 +145,6 @@ export class ConsoleLogger implements LoggerService {
this.printMessages(messages, context, 'verbose');
}
/**
* Write a 'fatal' level log, if the configured level allows for it.
* Prints to `stdout` with newline.
*/
fatal(message: any, context?: string): void;
fatal(message: any, ...optionalParams: [...any, string?]): void;
fatal(message: any, ...optionalParams: any[]) {
if (!this.isLevelEnabled('fatal')) {
return;
}
const { messages, context } = this.getContextAndMessagesToPrint([
message,
...optionalParams,
]);
this.printMessages(messages, context, 'fatal');
}
/**
* Set log levels
* @param levels log levels
@@ -245,18 +227,10 @@ export class ConsoleLogger implements LoggerService {
}
protected stringifyMessage(message: unknown, logLevel: LogLevel) {
if (isFunction(message)) {
const messageAsStr = Function.prototype.toString.call(message);
const isClass = messageAsStr.startsWith('class ');
if (isClass) {
// If the message is a class, we will display the class name.
return this.stringifyMessage(message.name, logLevel);
}
// If the message is a non-class function, call it and re-resolve its value.
return this.stringifyMessage(message(), logLevel);
}
return isPlainObject(message) || Array.isArray(message)
// If the message is a function, call it and re-resolve its value.
return isFunction(message)
? this.stringifyMessage(message(), logLevel)
: isPlainObject(message) || Array.isArray(message)
? `${this.colorize('Object:', logLevel)}\n${JSON.stringify(
message,
(key, value) =>
@@ -356,8 +330,6 @@ export class ConsoleLogger implements LoggerService {
return clc.red;
case 'verbose':
return clc.cyanBright;
case 'fatal':
return clc.bold;
default:
return clc.green;
}

View File

@@ -6,7 +6,7 @@ import { isLogLevelEnabled } from './utils';
/**
* @publicApi
*/
export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose' | 'fatal';
export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose';
/**
* @publicApi
@@ -37,11 +37,6 @@ export interface LoggerService {
*/
verbose?(message: any, ...optionalParams: any[]): any;
/**
* Write a 'fatal' level log.
*/
fatal?(message: any, ...optionalParams: any[]): any;
/**
* Set log levels.
* @param levels log levels
@@ -190,19 +185,6 @@ export class Logger implements LoggerService {
this.localInstance?.verbose?.(message, ...optionalParams);
}
/**
* Write a 'fatal' level log.
*/
fatal(message: any, context?: string): void;
fatal(message: any, ...optionalParams: [...any, string?]): void;
@Logger.WrapBuffer
fatal(message: any, ...optionalParams: any[]) {
optionalParams = this.context
? optionalParams.concat(this.context)
: optionalParams;
this.localInstance?.fatal?.(message, ...optionalParams);
}
/**
* Write an 'error' level log.
*/
@@ -259,16 +241,6 @@ export class Logger implements LoggerService {
this.staticInstanceRef?.verbose?.(message, ...optionalParams);
}
/**
* Write a 'fatal' level log.
*/
static fatal(message: any, context?: string): void;
static fatal(message: any, ...optionalParams: [...any, string?]): void;
@Logger.WrapBuffer
static fatal(message: any, ...optionalParams: any[]) {
this.staticInstanceRef?.fatal?.(message, ...optionalParams);
}
/**
* Print buffered logs and detach buffer.
*/

View File

@@ -1,12 +1,11 @@
import { LogLevel } from '../logger.service';
const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
verbose: 0,
debug: 1,
debug: 0,
verbose: 1,
log: 2,
warn: 3,
error: 4,
fatal: 5,
};
/**

View File

@@ -1,9 +1,7 @@
import { expect } from 'chai';
import { Body, HostParam, Param, Query, Search } from '../../decorators';
import { RequestMethod } from '../../enums/request-method.enum';
import { All, Delete, Get, ParseIntPipe, Patch, Post, Put } from '../../index';
import { ROUTE_ARGS_METADATA } from '../../constants';
import { RouteParamtypes } from '../../enums/route-paramtypes.enum';
import { All, Delete, Get, Patch, Post, Put } from '../../index';
describe('@Get', () => {
const requestPath = 'test';
@@ -21,28 +19,18 @@ describe('@Get', () => {
it('should enhance class with expected request metadata', () => {
class Test {
@Get(requestPath)
public static test(@Param('id', ParseIntPipe) params) {}
public static test(@Param('id') params) {}
@Get(requestPathUsingArray)
public static testUsingArray(@Param('id') params) {}
}
const path = Reflect.getMetadata('path', Test.test);
const args = Reflect.getMetadata(
ROUTE_ARGS_METADATA,
Test.constructor,
'test',
);
const method = Reflect.getMetadata('method', Test.test);
const pathUsingArray = Reflect.getMetadata('path', Test.testUsingArray);
const methodUsingArray = Reflect.getMetadata('method', Test.testUsingArray);
expect(path).to.be.eql(requestPath);
expect(args[`${RouteParamtypes.PARAM}:0`]).to.be.eql({
index: 0,
data: 'id',
pipes: [ParseIntPipe],
});
expect(method).to.be.eql(requestProps.method);
expect(pathUsingArray).to.be.eql(requestPathUsingArray);
expect(methodUsingArray).to.be.eql(requestPropsUsingArray.method);

View File

@@ -104,6 +104,7 @@ describe('ConfigurableModuleBuilder', () => {
expect(definition.global).to.equal(true);
expect(definition.providers).to.have.length(5);
console.log(definition.providers);
expect(definition.providers).to.deep.contain('test');
expect(definition.providers).to.include.members(
provideInjectionTokensFrom.slice(0, 2),

View File

@@ -72,14 +72,6 @@ describe('FileTypeValidator', () => {
expect(fileTypeValidator.isValid(requestFile)).to.equal(false);
});
it('should return false when no file provided', () => {
const fileTypeValidator = new FileTypeValidator({
fileType: 'image/jpeg',
});
expect(fileTypeValidator.isValid()).to.equal(false);
});
});
describe('buildErrorMessage', () => {

View File

@@ -40,14 +40,6 @@ describe('MaxFileSizeValidator', () => {
expect(maxFileSizeValidator.isValid(requestFile)).to.equal(false);
});
it('should return true when no file provided', () => {
const maxFileSizeValidator = new MaxFileSizeValidator({
maxSize: oneKb,
});
expect(maxFileSizeValidator.isValid()).to.equal(true);
});
});
describe('buildErrorMessage', () => {

View File

@@ -18,12 +18,6 @@ describe('ParseBoolPipe', () => {
expect(await target.transform(false, {} as ArgumentMetadata)).to.be
.false;
});
it('should not throw an error if the value is undefined/null and optional is true', async () => {
const target = new ParseBoolPipe({ optional: true });
const value = await target.transform(undefined, {} as ArgumentMetadata);
expect(value).to.equal(undefined);
});
});
describe('when validation fails', () => {
it('should throw an error', async () => {

View File

@@ -14,7 +14,6 @@ describe('ParseEnumPipe', () => {
Up = 'UP',
}
let target: ParseEnumPipe;
beforeEach(() => {
target = new ParseEnumPipe(Direction, {
exceptionFactory: (error: any) => new CustomTestError(),
@@ -27,12 +26,6 @@ describe('ParseEnumPipe', () => {
Direction.Up,
);
});
it('should not throw an error if enumType is undefined/null and optional is true', async () => {
const target = new ParseEnumPipe('DOWN', { optional: true });
const value = await target.transform(undefined, {} as ArgumentMetadata);
expect(value).to.equal(undefined);
});
});
describe('when validation fails', () => {
it('should throw an error', async () => {
@@ -40,16 +33,6 @@ describe('ParseEnumPipe', () => {
target.transform('DOWN', {} as ArgumentMetadata),
).to.be.rejectedWith(CustomTestError);
});
it('should throw an error if enumType is wrong and optional is true', async () => {
target = new ParseEnumPipe(Direction, {
exceptionFactory: (error: any) => new CustomTestError(),
optional: true,
});
return expect(
target.transform('DOWN', {} as ArgumentMetadata),
).to.be.rejectedWith(CustomTestError);
});
});
});
describe('constructor', () => {

View File

@@ -25,11 +25,6 @@ describe('ParseFloatPipe', () => {
parseFloat(num),
);
});
it('should not throw an error if the value is undefined/null and optional is true', async () => {
const target = new ParseFloatPipe({ optional: true });
const value = await target.transform(undefined, {} as ArgumentMetadata);
expect(value).to.equal(undefined);
});
});
describe('when validation fails', () => {
it('should throw an error', async () => {

View File

@@ -30,11 +30,6 @@ describe('ParseIntPipe', () => {
-3,
);
});
it('should not throw an error if the value is undefined/null and optional is true', async () => {
const target = new ParseIntPipe({ optional: true });
const value = await target.transform(undefined, {} as ArgumentMetadata);
expect(value).to.equal(undefined);
});
});
describe('when validation fails', () => {
it('should throw an error', async () => {

View File

@@ -41,11 +41,6 @@ describe('ParseUUIDPipe', () => {
target = new ParseUUIDPipe({ version: '5', exceptionFactory });
expect(await target.transform(v5, {} as ArgumentMetadata)).to.equal(v5);
});
it('should not throw an error if the value is undefined/null and optional is true', async () => {
const target = new ParseUUIDPipe({ optional: true });
const value = await target.transform(undefined, {} as ArgumentMetadata);
expect(value).to.equal(undefined);
});
});
describe('when validation fails', () => {

View File

@@ -298,34 +298,6 @@ describe('Logger', () => {
);
});
});
describe('classes for message', () => {
let processStdoutWriteSpy: sinon.SinonSpy;
beforeEach(() => {
processStdoutWriteSpy = sinon.spy(process.stdout, 'write');
});
afterEach(() => {
processStdoutWriteSpy.restore();
});
it("should display class's name or empty for anonymous classes", () => {
const logger = new ConsoleLogger();
// in-line anonymous class
logger.log(class {});
// named class
class Test {
#privateField = 'private field';
publicField = 'public field';
}
logger.log(Test);
expect(processStdoutWriteSpy.firstCall.firstArg).to.include('');
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(Test.name);
});
});
});
describe('[instance methods]', () => {

View File

@@ -23,7 +23,7 @@
## Description
Nest is a framework for building efficient, scalable <a href="https://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Nest is a framework for building efficient, scalable <a href="http://nodejs.org" target="_blank">Node.js</a> server-side applications. It uses modern JavaScript, is built with <a href="http://www.typescriptlang.org" target="_blank">TypeScript</a> (preserves compatibility with pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
<p>Under the hood, Nest makes use of <a href="https://expressjs.com/" target="_blank">Express</a>, but also, provides compatibility with a wide range of other libraries, like e.g. <a href="https://github.com/fastify/fastify" target="_blank">Fastify</a>, allowing for easy use of the myriad third-party plugins which are available.</p>
@@ -34,10 +34,10 @@ Nest is a framework for building efficient, scalable <a href="https://nodejs.org
## Getting started
- To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
- 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
- [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
- [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
* To check out the [guide](https://docs.nestjs.com), visit [docs.nestjs.com](https://docs.nestjs.com). :books:
* 要查看中文 [指南](readme_zh.md), 请访问 [docs.nestjs.cn](https://docs.nestjs.cn). :books:
* [가이드](readme_kr.md) 문서는 [docs.nestjs.com](https://docs.nestjs.com)에서 확인하실 수 있습니다. :books:
* [ガイド](readme_jp.md)は [docs.nestjs.com](https://docs.nestjs.com)でご確認ください。 :books:
## Questions
@@ -56,29 +56,26 @@ With official support, you can get expert help straight from Nest core team. We
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support from the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
#### Principal Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td>
<td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/red-hat-logo.svg" width="200" valign="middle" /></a></td>
<td>
<a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
<td>
<a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/nx-logo.png" height="45" valign="middle" /></a></td>
<td>
<td>
<a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td><td>
<a href="https://amplication.com/" target="_blank"><img src="https://nestjs.com/img/amplication-logo.svg" width="190" valign="middle" /></a></td>
</tr></table>
#### Gold Sponsors
<table style="text-align:center;"><tr><td>
<a href="https://weld.app/" target="_blank"><img src="https://nestjs.com/img/weld-logo.svg" width="140" valign="middle" /></a></td>
<td>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td></tr><tr>
<a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/intrinisic-logo.png" width="210" valign="middle" /></a></td>
<td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="90" valign="middle" /></a></td><td>
<a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/jetbrains-logo.svg" width="110" valign="middle" /></a></td><td>
<a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/snyk-logo-black.png" width="185" valign="middle" /></a></td><td>
<a href="https://fuseautotech.com/" target="_blank"><img src="https://nestjs.com/img/fuse-logo.svg" width="105" valign="middle" /></a></td>
<td>
@@ -87,11 +84,17 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
#### Silver Sponsors
<table style="text-align:center;"><tr>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td>
<td><a href="https://twistag.com/" target="_blank"><img src="https://nestjs.com/img/twistag-logo.png" width="120" valign="middle" /></td>
<td><a href="https://immediateedgeapp.org/" target="_blank"><img src="https://nestjs.com/img/immediate-edge-logo.png" width="120" valign="middle" /></td>
</tr>
<table style="text-align:center;"><tr><td>
<a href="https://neoteric.eu/" target="_blank"><img src="https://nestjs.com/img/neoteric-cut.png" width="120" valign="middle" /></a> </td><td>
<a href="http://gojob.com" target="_blank"><img src="http://nestjs.com/img/gojob-logo.png" valign="middle" width="100" /></a> </td><td>
<a href="https://www.myleodsc.com/" target="_blank"><img src="https://nestjs.com/img/myleo-logo.png" width="180" valign="middle" /></td><td>
<a href="https://careers.meetdandy.com/?gh_src=063ba61e3us" target="_blank"><img src="https://nestjs.com/img/dandy-roles-logo.svg" width="150" valign="middle" /></td><td>
<a href="https://www.castlecraft.in" target="_blank"><img src="https://nestjs.com/img/castlecraft-logo.png" width="150" valign="middle" /></td>
<td><a href="https://www.tinystacks.com" target="_blank"><img src="https://nestjs.com/img/tinystacks-logo.png#1" width="140" valign="middle" /></td>
<td><a href="https://n.inc" target="_blank"><img src="https://nestjs.com/img/n-inc-logo.svg" width="120" valign="middle" /></td></tr><tr>
<td><a href="https://bilberrry.com/" target="_blank"><img src="https://nestjs.com/img/bilberrry-logo.svg" width="180" valign="middle" /></td>
<td><a href="https://ipinfo.ai/" target="_blank"><img src="https://nestjs.com/img/ipinfo-logo.png" width="130" valign="middle" /></td>
<td><a href="https://chax.at" target="_blank"><img src="https://nestjs.com/img/chaxat-logo.png" width="100" valign="middle" /></td></tr>
</table>
#### Sponsors
@@ -99,26 +102,41 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
<table><tr><td align="center" valign="middle">
<a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/swingdev-logo.svg#1" width="110" valign="middle" /> </a></td><td align="center" valign="middle">
<a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/novologic.png" width="110" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://ever.co/" target="_blank"><img src="https://nestjs.com/img/ever-logo.png" width="72" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://blokt.com" target="_blank"><img src="https://nestjs.com/img/blokt-logo.png" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="http://architectnow.net/" target="_blank"><img src="https://nestjs.com/img/architectnow.png" width="125" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://quander.io/" target="_blank"><img src="https://nestjs.com/img/quander.png" width="100" valign="middle" /></a> </td></tr><tr><td align="center" valign="middle">
<a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/mantro-logo.svg" width="95" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/triplebyte.png" width="107" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td>
<a href="https://reposit.co.uk/" target="_blank"><img src="https://nestjs.com/img/reposit-logo.png" width="71" valign="middle" /></a></td><td align="center" valign="middle">
<a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/nearpod-logo.svg" width="100" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://clay.global/" target="_blank"><img src="https://nestjs.com/img/clay-logo.svg" width="75" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://firesticktricks.com" target="_blank"><img src="https://nestjs.com/img/firesticktricks-logo.png" width="120" valign="middle" /></a></td></tr><tr><td align="center" valign="middle">
<a href="https://www.codeguesser.co.uk" target="_blank"><img src="https://nestjs.com/img/codeguesser-logo.svg" width="120" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://f-a.nz/" target="_blank"><img src="https://nestjs.com/img/franz.svg" width="80" valign="middle" /></a> </td><td align="center" valign="middle">
<a href="https://sparkfabrik.com/" target="_blank"><img src="https://nestjs.com/img/sparkfabrik-logo.png" width="120" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://www.thebigphonestore.co.uk/" target="_blank"><img src="https://nestjs.com/img/the-big-phone-store-logo.png" width="65" valign="middle" /></a></td>
<td align="center" valign="middle">
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td></tr><tr>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td>
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td>
<td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td></tr><tr><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/lambda-it-logo.svg" width="115" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://pickwriters.com/top-10-translation-services" target="_blank"><img src="https://nestjs.com/img/pickwriters-logo.png" width="40" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://thewordpoint.com/services/localization" target="_blank"><img src="https://nestjs.com/img/thewordpoint-logo.png" width="40" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://meercode.io/" target="_blank"><img src="https://nestjs.com/img/meercode-logo.png" width="60" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.najlepszeplatformyforex.pl/blog/broker-xtb/" target="_blank"><img src="https://nestjs.com/img/npf-logo.jpg" width="200" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://thestandarddaily.com/" target="_blank"><img src="https://nestjs.com/img/the-standard-daily-logo.png" width="180" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://houseofangular.io/" target="_blank"><img src="https://nestjs.com/img/house-of-angular.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/rocketech-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.bystored.com/" target="_blank"><img src="https://nestjs.com/img/stored-logo.svg" width="110" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://studyclerk.com/pay-for-research-paper" target="_blank"><img src="https://nestjs.com/img/studyclerk-logo.png" width="125" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://xyndata.com" target="_blank"><img src="https://nestjs.com/img/xyndata-logo.png" width="125" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/anonymistic-logo.png" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/naologic-logo.svg" width="125" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://messaged.com/" target="_blank"><img src="https://nestjs.com/img/messaged-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/triplecore-logo.svg" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://klqc.de" target="_blank"><img src="https://nestjs.com/img/klcqcl-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://thecasinowizard.com/bonuses/no-deposit-bonuses/" target="_blank"><img src="https://nestjs.com/img/casinowizard-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/polygon-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://themobilereality.com/" target="_blank"><img src="https://nestjs.com/img/mobile-reality-logo.png" width="45" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/boringowl-logo.svg" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td></tr><tr>
<td align="center" valign="middle"><a href="https://www.mobilefactory.jp/" target="_blank"><img src="https://nestjs.com/img/mobilefactory-logo.png" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/nordbot-logo.png" width="120" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/dopiosh-logo.png" width="50" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://julienferand.dev/" target="_blank"><img src="https://nestjs.com/img/julienferand-logo.jpeg" width="65" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/tripoffice-logo.png" width="140" valign="middle" /></a></td>
<td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
</tr></table>
## Backers
@@ -127,9 +145,9 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
## Stay in touch
- Author - [Kamil Myśliwiec](https://x.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- X - [@nestframework](https://x.com/nestframework)
* Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
* Website - [https://nestjs.com](https://nestjs.com/)
* Twitter - [@nestframework](https://twitter.com/nestframework)
## License

View File

@@ -1,153 +0,0 @@
import { Type } from '@nestjs/common';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { ModulesContainer } from '../injector/modules-container';
export class DiscoverableMetaHostCollection {
/**
* A map of class references to metadata keys.
*/
public static readonly metaHostLinks = new Map<Type | Function, string>();
/**
* A map of metadata keys to instance wrappers (providers) with the corresponding metadata key.
* The map is weakly referenced by the modules container (unique per application).
*/
private static readonly providersByMetaKey = new WeakMap<
ModulesContainer,
Map<string, Set<InstanceWrapper>>
>();
/**
* A map of metadata keys to instance wrappers (controllers) with the corresponding metadata key.
* The map is weakly referenced by the modules container (unique per application).
*/
private static readonly controllersByMetaKey = new WeakMap<
ModulesContainer,
Map<string, Set<InstanceWrapper>>
>();
/**
* Adds a link between a class reference and a metadata key.
* @param target The class reference.
* @param metadataKey The metadata key.
*/
public static addClassMetaHostLink(
target: Type | Function,
metadataKey: string,
) {
this.metaHostLinks.set(target, metadataKey);
}
/**
* Inspects a provider instance wrapper and adds it to the collection of providers
* if it has a metadata key.
* @param hostContainerRef A reference to the modules container.
* @param instanceWrapper A provider instance wrapper.
* @returns void
*/
public static inspectProvider(
hostContainerRef: ModulesContainer,
instanceWrapper: InstanceWrapper,
) {
return this.inspectInstanceWrapper(
hostContainerRef,
instanceWrapper,
this.providersByMetaKey,
);
}
/**
* Inspects a controller instance wrapper and adds it to the collection of controllers
* if it has a metadata key.
* @param hostContainerRef A reference to the modules container.
* @param instanceWrapper A controller's instance wrapper.
* @returns void
*/
public static inspectController(
hostContainerRef: ModulesContainer,
instanceWrapper: InstanceWrapper,
) {
return this.inspectInstanceWrapper(
hostContainerRef,
instanceWrapper,
this.controllersByMetaKey,
);
}
public static insertByMetaKey(
metaKey: string,
instanceWrapper: InstanceWrapper,
collection: Map<string, Set<InstanceWrapper>>,
) {
if (collection.has(metaKey)) {
const wrappers = collection.get(metaKey);
wrappers.add(instanceWrapper);
} else {
const wrappers = new Set<InstanceWrapper>();
wrappers.add(instanceWrapper);
collection.set(metaKey, wrappers);
}
}
public static getProvidersByMetaKey(
hostContainerRef: ModulesContainer,
metaKey: string,
): Set<InstanceWrapper> {
const wrappersByMetaKey = this.providersByMetaKey.get(hostContainerRef);
return wrappersByMetaKey?.get(metaKey) ?? new Set<InstanceWrapper>();
}
public static getControllersByMetaKey(
hostContainerRef: ModulesContainer,
metaKey: string,
): Set<InstanceWrapper> {
const wrappersByMetaKey = this.controllersByMetaKey.get(hostContainerRef);
return wrappersByMetaKey?.get(metaKey) ?? new Set<InstanceWrapper>();
}
private static inspectInstanceWrapper(
hostContainerRef: ModulesContainer,
instanceWrapper: InstanceWrapper,
wrapperByMetaKeyMap: WeakMap<
ModulesContainer,
Map<string, Set<InstanceWrapper>>
>,
) {
const metaKey =
DiscoverableMetaHostCollection.getMetaKeyByInstanceWrapper(
instanceWrapper,
);
if (!metaKey) {
return;
}
let collection: Map<string, Set<InstanceWrapper>>;
if (wrapperByMetaKeyMap.has(hostContainerRef)) {
collection = wrapperByMetaKeyMap.get(hostContainerRef);
} else {
collection = new Map<string, Set<InstanceWrapper>>();
wrapperByMetaKeyMap.set(hostContainerRef, collection);
}
this.insertByMetaKey(metaKey, instanceWrapper, collection);
}
private static getMetaKeyByInstanceWrapper(
instanceWrapper: InstanceWrapper<any>,
) {
return this.metaHostLinks.get(
// NOTE: Regarding the ternary statement below,
// - The condition `!wrapper.metatype` is needed because when we use `useValue`
// the value of `wrapper.metatype` will be `null`.
// - The condition `wrapper.inject` is needed here because when we use
// `useFactory`, the value of `wrapper.metatype` will be the supplied
// factory function.
// For both cases, we should use `wrapper.instance.constructor` instead
// of `wrapper.metatype` to resolve processor's class properly.
// But since calling `wrapper.instance` could degrade overall performance
// we must defer it as much we can.
instanceWrapper.metatype || instanceWrapper.inject
? instanceWrapper.instance?.constructor ?? instanceWrapper.metatype
: instanceWrapper.metatype,
);
}
}

View File

@@ -1,48 +1,15 @@
import {
CustomDecorator,
flatten,
Injectable,
SetMetadata,
} from '@nestjs/common';
import { uid } from 'uid';
import { flatten, Injectable } from '@nestjs/common';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { Module } from '../injector/module';
import { ModulesContainer } from '../injector/modules-container';
import { DiscoverableMetaHostCollection } from './discoverable-meta-host-collection';
/**
* @publicApi
*/
export interface FilterByInclude {
/**
* List of modules to include (whitelist) into the discovery process.
*/
export interface DiscoveryOptions {
include?: Function[];
}
/**
* @publicApi
*/
export interface FilterByMetadataKey {
/**
* A key to filter controllers and providers by.
* Only instance wrappers with the specified metadata key will be returned.
*/
metadataKey?: string;
}
/**
* @publicApi
*/
export type DiscoveryOptions = FilterByInclude | FilterByMetadataKey;
/**
* @publicApi
*/
export type DiscoverableDecorator<T> = ((opts?: T) => CustomDecorator) & {
KEY: string;
};
/**
* @publicApi
*/
@@ -50,107 +17,24 @@ export type DiscoverableDecorator<T> = ((opts?: T) => CustomDecorator) & {
export class DiscoveryService {
constructor(private readonly modulesContainer: ModulesContainer) {}
/**
* Creates a decorator that can be used to decorate classes and methods with metadata.
* The decorator will also add the class to the collection of discoverable classes (by metadata key).
* Decorated classes can be discovered using the `getProviders` and `getControllers` methods.
* @returns A decorator function.
*/
static createDecorator<T>(): DiscoverableDecorator<T> {
const metadataKey = uid(21);
const decoratorFn =
(opts: T) =>
(target: object | Function, key?: string | symbol, descriptor?: any) => {
if (!descriptor) {
DiscoverableMetaHostCollection.addClassMetaHostLink(
target as Function,
metadataKey,
);
}
SetMetadata(metadataKey, opts ?? {})(target, key, descriptor);
};
decoratorFn.KEY = metadataKey;
return decoratorFn as DiscoverableDecorator<T>;
}
/**
* Returns an array of instance wrappers (providers).
* Depending on the options, the array will contain either all providers or only providers with the specified metadata key.
* @param options Discovery options.
* @param modules A list of modules to filter by.
* @returns An array of instance wrappers (providers).
*/
public getProviders(
getProviders(
options: DiscoveryOptions = {},
modules: Module[] = this.getModules(options),
): InstanceWrapper[] {
if ('metadataKey' in options) {
const providers = DiscoverableMetaHostCollection.getProvidersByMetaKey(
this.modulesContainer,
options.metadataKey,
);
return Array.from(providers);
}
const providers = modules.map(item => [...item.providers.values()]);
return flatten(providers);
}
/**
* Returns an array of instance wrappers (controllers).
* Depending on the options, the array will contain either all controllers or only controllers with the specified metadata key.
* @param options Discovery options.
* @param modules A list of modules to filter by.
* @returns An array of instance wrappers (controllers).
*/
public getControllers(
getControllers(
options: DiscoveryOptions = {},
modules: Module[] = this.getModules(options),
): InstanceWrapper[] {
if ('metadataKey' in options) {
const controllers =
DiscoverableMetaHostCollection.getControllersByMetaKey(
this.modulesContainer,
options.metadataKey,
);
return Array.from(controllers);
}
const controllers = modules.map(item => [...item.controllers.values()]);
return flatten(controllers);
}
/**
* Retrieves metadata from the specified instance wrapper.
* @param decorator The decorator to retrieve metadata of.
* @param instanceWrapper Reference to the instance wrapper.
* @param methodKey An optional method key to retrieve metadata from.
* @returns Discovered metadata.
*/
public getMetadataByDecorator<T extends DiscoverableDecorator<any>>(
decorator: T,
instanceWrapper: InstanceWrapper,
methodKey?: string,
): T extends DiscoverableDecorator<infer R> ? R | undefined : T | undefined {
if (methodKey) {
return Reflect.getMetadata(
decorator.KEY,
instanceWrapper.instance[methodKey],
);
}
const clsRef =
instanceWrapper.instance?.constructor ?? instanceWrapper.metatype;
return Reflect.getMetadata(decorator.KEY, clsRef);
}
/**
* Returns a list of modules to be used for discovery.
*/
protected getModules(options: DiscoveryOptions = {}): Module[] {
const includeInOpts = 'include' in options;
if (!includeInOpts) {
if (!options.include) {
const moduleRefs = [...this.modulesContainer.values()];
return moduleRefs;
}

View File

@@ -4,7 +4,7 @@ export class CircularDependencyException extends RuntimeException {
constructor(context?: string) {
const ctx = context ? ` inside ${context}` : ``;
super(
`A circular dependency has been detected${ctx}. Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()". Note that circular relationships between custom providers (e.g., factories) are not supported since functions cannot be called more than once.`,
`A circular dependency has been detected${ctx}. Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()".`,
);
}
}

View File

@@ -23,31 +23,20 @@ const getInstanceName = (instance: unknown): string => {
};
/**
* Returns the name of the dependency.
* Returns the name of the dependency
* Tries to get the class name, otherwise the string value
* (= injection token). As fallback to any falsy value for `dependency`, it
* returns `fallbackValue`
* (= injection token). As fallback it returns '+'
* @param dependency The name of the dependency to be displayed
* @param fallbackValue The fallback value if the dependency is falsy
* @param disambiguated Whether dependency's name is disambiguated with double quotes
*/
const getDependencyName = (
dependency: InjectorDependency | undefined,
fallbackValue: string,
disambiguated = true,
): string =>
const getDependencyName = (dependency: InjectorDependency): string =>
// use class name
getInstanceName(dependency) ||
// use injection token (symbol)
(isSymbol(dependency) && dependency.toString()) ||
// use string directly
(dependency
? disambiguated
? `"${dependency as string}"`
: (dependency as string)
: undefined) ||
(dependency as string) ||
// otherwise
fallbackValue;
'+';
/**
* Returns the name of the module
@@ -65,9 +54,14 @@ export const UNKNOWN_DEPENDENCIES_MESSAGE = (
unknownDependencyContext: InjectorDependencyContext,
module: Module,
) => {
const { index, name, dependencies, key } = unknownDependencyContext;
const {
index,
name = 'dependency',
dependencies,
key,
} = unknownDependencyContext;
const moduleName = getModuleName(module);
const dependencyName = getDependencyName(name, 'dependency');
const dependencyName = getDependencyName(name);
const potentialSolutions =
// If module's name is well defined
@@ -96,9 +90,7 @@ Potential solutions:
message += `. Please make sure that the "${key.toString()}" property is available in the current context.${potentialSolutions}`;
return message;
}
const dependenciesName = (dependencies || []).map(dependencyName =>
getDependencyName(dependencyName, '+', false),
);
const dependenciesName = (dependencies || []).map(getDependencyName);
dependenciesName[index] = '?';
message += ` (`;

View File

@@ -59,7 +59,6 @@ export class ExecutionContextHost implements ExecutionContext {
return Object.assign(this, {
getClient: () => this.getArgByIndex(0),
getData: () => this.getArgByIndex(1),
getPattern: () => this.getArgByIndex(this.getArgs().length - 1),
});
}
}

View File

@@ -5,7 +5,6 @@ import {
} from '@nestjs/common/constants';
import { Injectable, Type } from '@nestjs/common/interfaces';
import { ApplicationConfig } from '../application-config';
import { DiscoverableMetaHostCollection } from '../discovery/discoverable-meta-host-collection';
import {
CircularDependencyException,
UndefinedForwardRefException,
@@ -71,51 +70,34 @@ export class NestContainer {
public async addModule(
metatype: ModuleMetatype,
scope: ModuleScope,
): Promise<
| {
moduleRef: Module;
inserted: boolean;
}
| undefined
> {
): Promise<Module | undefined> {
// In DependenciesScanner#scanForModules we already check for undefined or invalid modules
// We still need to catch the edge-case of `forwardRef(() => undefined)`
if (!metatype) {
throw new UndefinedForwardRefException(scope);
}
const { type, dynamicMetadata, token } =
await this.moduleCompiler.compile(metatype);
const { type, dynamicMetadata, token } = await this.moduleCompiler.compile(
metatype,
);
if (this.modules.has(token)) {
return {
moduleRef: this.modules.get(token),
inserted: true,
};
return this.modules.get(token);
}
return {
moduleRef: await this.setModule(
{
token,
type,
dynamicMetadata,
},
scope,
),
inserted: true,
};
return this.setModule(
{
token,
type,
dynamicMetadata,
},
scope,
);
}
public async replaceModule(
metatypeToReplace: ModuleMetatype,
newMetatype: ModuleMetatype,
scope: ModuleScope,
): Promise<
| {
moduleRef: Module;
inserted: boolean;
}
| undefined
> {
): Promise<Module | undefined> {
// In DependenciesScanner#scanForModules we already check for undefined or invalid modules
// We still need to catch the edge-case of `forwardRef(() => undefined)`
if (!metatypeToReplace || !newMetatype) {
@@ -123,20 +105,18 @@ export class NestContainer {
}
const { token } = await this.moduleCompiler.compile(metatypeToReplace);
const { type, dynamicMetadata } =
await this.moduleCompiler.compile(newMetatype);
const { type, dynamicMetadata } = await this.moduleCompiler.compile(
newMetatype,
);
return {
moduleRef: await this.setModule(
{
token,
type,
dynamicMetadata,
},
scope,
),
inserted: false,
};
return this.setModule(
{
token,
type,
dynamicMetadata,
},
scope,
);
}
private async setModule(
@@ -218,10 +198,11 @@ export class NestContainer {
return;
}
const moduleRef = this.modules.get(token);
const { token: relatedModuleToken } =
await this.moduleCompiler.compile(relatedModule);
const { token: relatedModuleToken } = await this.moduleCompiler.compile(
relatedModule,
);
const related = this.modules.get(relatedModuleToken);
moduleRef.addImport(related);
moduleRef.addRelatedModule(related);
}
public addProvider(
@@ -236,12 +217,7 @@ export class NestContainer {
if (!moduleRef) {
throw new UnknownModuleException();
}
const providerKey = moduleRef.addProvider(provider, enhancerSubtype);
const providerRef = moduleRef.getProviderByKey(providerKey);
DiscoverableMetaHostCollection.inspectProvider(this.modules, providerRef);
return providerKey as Function;
return moduleRef.addProvider(provider, enhancerSubtype) as Function;
}
public addInjectable(
@@ -271,12 +247,6 @@ export class NestContainer {
}
const moduleRef = this.modules.get(token);
moduleRef.addController(controller);
const controllerRef = moduleRef.controllers.get(controller);
DiscoverableMetaHostCollection.inspectController(
this.modules,
controllerRef,
);
}
public clear() {
@@ -301,7 +271,7 @@ export class NestContainer {
if (target === globalModule || target === this.internalCoreModule) {
return;
}
target.addImport(globalModule);
target.addRelatedModule(globalModule);
}
public getDynamicMetadataByToken(token: string): Partial<DynamicModule>;

View File

@@ -8,6 +8,5 @@ export class SilentLogger extends Logger {
warn = noop;
debug = noop;
verbose = noop;
fatal = noop;
setLogLevels = noop;
}

View File

@@ -23,7 +23,6 @@ import {
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { performance } from 'perf_hooks';
import { CircularDependencyException } from '../errors/exceptions';
import { RuntimeException } from '../errors/exceptions/runtime.exception';
import { UndefinedDependencyException } from '../errors/exceptions/undefined-dependency.exception';
import { UnknownDependenciesException } from '../errors/exceptions/unknown-dependencies.exception';
@@ -36,7 +35,6 @@ import {
PropertyMetadata,
} from './instance-wrapper';
import { Module } from './module';
import { SettlementSignal } from './settlement-signal';
/**
* The type of an injectable dependency
@@ -113,21 +111,14 @@ export class Injector {
this.getContextId(contextId, wrapper),
inquirerId,
);
if (instanceHost.isPending) {
const settlementSignal = wrapper.settlementSignal;
if (inquirer && settlementSignal?.isCycle(inquirer.id)) {
throw new CircularDependencyException(`"${wrapper.name}"`);
}
return instanceHost.donePromise.then((err?: unknown) => {
if (err) {
throw err;
}
});
}
const settlementSignal = this.applySettlementSignal(instanceHost, wrapper);
const done = this.applyDoneHook(instanceHost);
const token = wrapper.token || wrapper.name;
const { inject } = wrapper;
@@ -136,7 +127,7 @@ export class Injector {
throw new RuntimeException();
}
if (instanceHost.isResolved) {
return settlementSignal.complete();
return done();
}
try {
const t0 = this.getNowTimestamp();
@@ -158,7 +149,7 @@ export class Injector {
);
this.applyProperties(instance, properties);
wrapper.initTime = this.getNowTimestamp() - t0;
settlementSignal.complete();
done();
};
await this.resolveConstructorParams<T>(
wrapper,
@@ -170,7 +161,7 @@ export class Injector {
inquirer,
);
} catch (err) {
settlementSignal.error(err);
done(err);
throw err;
}
}
@@ -246,16 +237,15 @@ export class Injector {
await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
}
public applySettlementSignal<T>(
instancePerContext: InstancePerContext<T>,
host: InstanceWrapper<T>,
) {
const settlementSignal = new SettlementSignal();
instancePerContext.donePromise = settlementSignal.asPromise();
instancePerContext.isPending = true;
host.settlementSignal = settlementSignal;
return settlementSignal;
public applyDoneHook<T>(
wrapper: InstancePerContext<T>,
): (err?: unknown) => void {
let done: (err?: unknown) => void;
wrapper.donePromise = new Promise<unknown>((resolve, reject) => {
done = resolve;
});
wrapper.isPending = true;
return done;
}
public async resolveConstructorParams<T>(
@@ -467,8 +457,6 @@ export class Injector {
inquirerId,
);
if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
await this.loadProvider(
instanceWrapper,
instanceWrapper.host ?? moduleRef,
@@ -593,7 +581,6 @@ export class Injector {
}
this.printLookingForProviderLog(name, relatedModule);
moduleRegistry.push(relatedModule.id);
const { providers, exports } = relatedModule;
if (!exports.has(name) || !providers.has(name)) {
const instanceRef = await this.lookupComponentInImports(
@@ -622,8 +609,6 @@ export class Injector {
inquirerId,
);
if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
wrapper.settlementSignal?.insertRef(instanceWrapperRef.id);
await this.loadProvider(
instanceWrapperRef,
relatedModule,

View File

@@ -17,7 +17,6 @@ import {
isValueProvider,
} from './helpers/provider-classifier';
import { Module } from './module';
import { SettlementSignal } from './settlement-signal';
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
@@ -64,13 +63,13 @@ export class InstanceWrapper<T = any> {
public readonly host?: Module;
public readonly isAlias: boolean = false;
public readonly subtype?: EnhancerSubtype;
public scope?: Scope = Scope.DEFAULT;
public metatype: Type<T> | Function;
public inject?: FactoryProvider['inject'];
public forwardRef?: boolean;
public durable?: boolean;
public initTime?: number;
public settlementSignal?: SettlementSignal;
private static logger: LoggerService = new Logger(InstanceWrapper.name);
@@ -105,11 +104,8 @@ export class InstanceWrapper<T = any> {
}
get isNotMetatype(): boolean {
return !this.metatype || this.isFactory;
}
get isFactory(): boolean {
return this.metatype && !isNil(this.inject);
const isFactory = this.metatype && !isNil(this.inject);
return !this.metatype || isFactory;
}
get isTransient(): boolean {

View File

@@ -31,7 +31,6 @@ export class LazyModuleLoader {
const moduleInstances = await this.dependenciesScanner.scanForModules({
moduleDefinition: moduleClassOrDynamicDefinition,
overrides: this.moduleOverrides,
lazy: true,
});
if (moduleInstances.length === 0) {
// The module has been loaded already. In this case, we must

View File

@@ -128,10 +128,7 @@ export abstract class ModuleRef extends AbstractInstanceResolver {
options?: ModuleRefGetOrResolveOpts,
): Promise<TResult | Array<TResult>>;
public abstract create<T = any>(
type: Type<T>,
contextId?: ContextId,
): Promise<T>;
public abstract create<T = any>(type: Type<T>): Promise<T>;
public introspect<T = any>(
token: Type<T> | string | symbol,
@@ -154,7 +151,6 @@ export abstract class ModuleRef extends AbstractInstanceResolver {
protected async instantiateClass<T = any>(
type: Type<T>,
moduleRef: Module,
contextId?: ContextId,
): Promise<T> {
const wrapper = new InstanceWrapper({
name: type && type.name,
@@ -170,8 +166,6 @@ export abstract class ModuleRef extends AbstractInstanceResolver {
const properties = await this.injector.resolveProperties(
wrapper,
moduleRef,
undefined,
contextId,
);
const instance = new type(...instances);
this.injector.applyProperties(instance, properties);
@@ -182,7 +176,6 @@ export abstract class ModuleRef extends AbstractInstanceResolver {
moduleRef,
undefined,
callback,
contextId,
);
} catch (err) {
reject(err);

Some files were not shown because too many files have changed in this diff Show More