Compare commits

...

197 Commits

Author SHA1 Message Date
Kamil Myśliwiec
f57eacc952 chore(@nestjs) publish v9.3.3 release 2023-02-06 14:39:23 +01:00
Kamil Myśliwiec
6c99b704f6 fix(core): default deps introspection return val should be false 2023-02-06 14:28:09 +01:00
Kamil Mysliwiec
0a3a87673a Merge pull request #11053 from nestjs/chore/remove-uuid
chore(core): remove uuid dependency
2023-02-06 11:08:48 +01:00
Kamil Myśliwiec
36065d30f2 chore: update tests, id as string 2023-02-06 10:52:15 +01:00
Kamil Myśliwiec
d323edbab9 chore(core): remove uuid dependency 2023-02-06 10:33:39 +01:00
Kamil Myśliwiec
71c987e490 Merge branch 'perf/metadata-scanner' of https://github.com/H4ad/nest 2023-02-06 10:20:40 +01:00
Kamil Myśliwiec
7b69554776 Merge branch 'CodyTseng-fix-middleware-exclude' 2023-02-06 10:19:03 +01:00
Kamil Myśliwiec
1069796df7 test: update broken unit tests 2023-02-06 10:18:38 +01:00
Kamil Myśliwiec
cf88a7814e Merge branch 'fix-middleware-exclude' of https://github.com/CodyTseng/nest into CodyTseng-fix-middleware-exclude 2023-02-06 10:13:53 +01:00
Kamil Myśliwiec
bf1071f142 Merge branch 'H4ad-perf/module-token-factory-fast-path' 2023-02-06 10:12:36 +01:00
Kamil Myśliwiec
e1c2d27966 style(core): minor formatting, naming changes 2023-02-06 10:12:20 +01:00
Kamil Myśliwiec
76b7185623 chore: resolve conflicts 2023-02-06 10:08:01 +01:00
Kamil Mysliwiec
dac1219879 Merge pull request #11041 from vizio360/fixFor11002
fix(core): 🚑 Fix for calculating durability of a provider
2023-02-06 09:59:53 +01:00
Kamil Mysliwiec
97e9c184a2 Merge pull request #11034 from Tony133/chore/mark-facing-apis-public-testing
chore(testing, websockets): mark facing apis public
2023-02-06 09:54:37 +01:00
Kamil Mysliwiec
1d5856c731 Update packages/core/middleware/builder.ts 2023-02-06 09:54:00 +01:00
Kamil Mysliwiec
c9c6468861 Update packages/core/middleware/route-info-path-extractor.ts 2023-02-06 09:53:51 +01:00
Tony133
216ce4c60f chore(websockets): mark facing apis public in websockets 2023-02-06 09:39:18 +01:00
Kamil Mysliwiec
6b39aaa00e Merge pull request #11049 from nestjs/dependabot/npm_and_yarn/types/node-18.11.19
chore(deps-dev): bump @types/node from 18.11.18 to 18.11.19
2023-02-06 09:24:04 +01:00
Kamil Mysliwiec
f211dee41a Merge pull request #11050 from nestjs/dependabot/npm_and_yarn/types/express-4.17.17
chore(deps-dev): bump @types/express from 4.17.16 to 4.17.17
2023-02-06 09:23:53 +01:00
Kamil Mysliwiec
593e6ffb1a Merge pull request #11051 from nestjs/dependabot/npm_and_yarn/cache-manager-5.1.5
chore(deps-dev): bump cache-manager from 5.1.4 to 5.1.5
2023-02-06 09:21:16 +01:00
Kamil Mysliwiec
ab25307fc2 Merge pull request #11052 from nestjs/dependabot/npm_and_yarn/engine.io-client-6.3.1
chore(deps-dev): bump engine.io-client from 6.3.0 to 6.3.1
2023-02-06 09:08:38 +01:00
dependabot[bot]
5a6bc1899b chore(deps-dev): bump engine.io-client from 6.3.0 to 6.3.1
Bumps [engine.io-client](https://github.com/socketio/engine.io-client) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/socketio/engine.io-client/releases)
- [Changelog](https://github.com/socketio/engine.io-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io-client/compare/6.3.0...6.3.1)

---
updated-dependencies:
- dependency-name: engine.io-client
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 00:31:19 +00:00
dependabot[bot]
51dab24dad chore(deps-dev): bump cache-manager from 5.1.4 to 5.1.5
Bumps [cache-manager](https://github.com/node-cache-manager/node-cache-manager) from 5.1.4 to 5.1.5.
- [Release notes](https://github.com/node-cache-manager/node-cache-manager/releases)
- [Changelog](https://github.com/node-cache-manager/node-cache-manager/blob/master/CHANGELOG.md)
- [Commits](https://github.com/node-cache-manager/node-cache-manager/compare/5.1.4...5.1.5)

---
updated-dependencies:
- dependency-name: cache-manager
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 00:24:59 +00:00
dependabot[bot]
19492a9974 chore(deps-dev): bump @types/express from 4.17.16 to 4.17.17
Bumps [@types/express](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express) from 4.17.16 to 4.17.17.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express)

---
updated-dependencies:
- dependency-name: "@types/express"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 00:18:27 +00:00
dependabot[bot]
3806a7631c chore(deps-dev): bump @types/node from 18.11.18 to 18.11.19
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.11.18 to 18.11.19.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 00:14:19 +00:00
Vinícius Lourenço
7f43821d48 perf(core): change sha1 to xxhash 2023-02-04 12:39:36 -03:00
Vinícius Lourenço
713ece9e88 perf(core): added map to cache scan from prototype 2023-02-04 10:04:45 -03:00
Simone Vicentini
422eeb9ffa test(core): removed describe.only 2023-02-04 09:15:05 +00:00
Simone Vicentini
ab7cbf35f5 fix(core): 🚑 fix for 11002
Updated logic to determine dep tree durability and staticity and added a ton of tests
2023-02-04 09:12:09 +00:00
codytseng
b8702d5dbd fix(core): add global prefix to exclude paths for middleware 2023-02-04 12:17:00 +08:00
Simone Vicentini
5a333d4fa7 fix(core): 🚑 remove extra durable check 2023-02-03 22:38:56 +00:00
Simone Vicentini
c3ad373f91 fix(core): 🚑 Fix for calculating durability of a provider 2023-02-03 22:22:18 +00:00
Tony133
e92ae413f4 chore(websockets): mark facing apis public in websockets 2023-02-03 16:56:49 +01:00
Kamil Mysliwiec
ac6fc6ba9f Merge pull request #11033 from Tony133/chore/mark-facing-apis-public-platform
chore(platform): mark facing apis public adapter express, socket.io, ws
2023-02-03 13:19:17 +01:00
Kamil Mysliwiec
2124bc874c Update renovate.json 2023-02-03 13:02:45 +01:00
Tony133
c79f35c37c chore(testing): remove mark facing apis public 2023-02-03 12:37:40 +01:00
Tony133
b1d53e227b chore(testing): mark facing apis public 2023-02-03 12:27:12 +01:00
Tony133
15e4a5f9f1 chore(platform): mark facing apis public adapter express, socket.io, ws 2023-02-03 12:18:36 +01:00
Kamil Mysliwiec
6ab4b4891c Update Readme.md 2023-02-03 12:05:40 +01:00
Vinícius Lourenço
2d7703af06 chore(core): removed unused object-hash dependency 2023-02-03 08:04:06 -03:00
Vinícius Lourenço
caf056fba5 perf(core): faster module token factory 2023-02-03 08:04:00 -03:00
Kamil Myśliwiec
423b28a8bf chore(@nestjs) publish v9.3.2 release 2023-02-03 12:03:17 +01:00
Kamil Mysliwiec
fe745831d5 Merge pull request #11028 from nestjs/chore/forbid-unknown-values-validation-pipe
chore(common): set forbid unknown values to false by default
2023-02-03 12:01:45 +01:00
Kamil Mysliwiec
20b545bf48 Merge pull request #11031 from nestjs/fix/unwrap-forward-ref
fix(core): unwrap forward ref in module compiler
2023-02-03 11:59:49 +01:00
Kamil Mysliwiec
b92e0e99c7 Merge pull request #11032 from Tony133/chore/mark-facing-apis-public-fastify-adapter
chore(fastify): mark facing apis public fastify
2023-02-03 11:59:40 +01:00
Tony133
d93b77a6ab chore(fastify): mark facing apis public fastify 2023-02-03 11:54:28 +01:00
Kamil Myśliwiec
bb283861fd test: update validation pipe tests 2023-02-03 11:44:34 +01:00
Kamil Myśliwiec
25d0c3f30f Merge branch 'master' into chore/forbid-unknown-values-validation-pipe 2023-02-03 11:35:28 +01:00
Kamil Myśliwiec
c6ebbf7cb8 Merge branch 'master' of https://github.com/nestjs/nest 2023-02-03 11:32:58 +01:00
Kamil Myśliwiec
b73501c1e3 fix(core): unwrap forward ref in module compiler 2023-02-03 11:32:41 +01:00
Kamil Myśliwiec
0e3f9bd527 fix(microservices): add optional chaining to app options - backward compat 2023-02-03 11:32:17 +01:00
Kamil Mysliwiec
452a71bfcf Merge pull request #11026 from nestjs/fix/kafka-client-parallel-messages
fix(microservices): messages emitted before kafka client is ready fail
2023-02-03 10:40:47 +01:00
Kamil Mysliwiec
dac07fafb6 Merge pull request #11027 from meteorlxy/fix/import-common
fix(core): import common package correctly
2023-02-03 10:27:58 +01:00
Kamil Mysliwiec
c7ac493b53 Merge pull request #10390 from CodyTseng/fix-middleware-global-prefix
fix(core): let the middleware can  get the params in the global prefix
2023-02-03 10:24:47 +01:00
Kamil Myśliwiec
7b7b8b9260 chore: 2022 to 2023 2023-02-03 10:22:13 +01:00
Kamil Myśliwiec
f285d7c954 chore: rename to initialied, update unit tests 2023-02-03 10:19:43 +01:00
meteorlxy
c8e129b95c fix(core): import common package correctly 2023-02-03 17:16:16 +08:00
Cody Tseng
6d64d91122 Update packages/core/middleware/route-info-path-extractor.ts
Co-authored-by: Kamil Mysliwiec <mail@kamilmysliwiec.com>
2023-02-03 17:03:01 +08:00
Kamil Myśliwiec
491f132b8e chore(common): set forbid unknown values to false by default 2023-02-03 09:55:12 +01:00
Kamil Myśliwiec
255b9cb8a0 fix(microservices): messages emitted before kafka client is ready fail 2023-02-03 09:43:08 +01:00
Kamil Mysliwiec
03efdce8d6 Merge pull request #11020 from nestjs/fix/kafka-retriable-error-bugs
fix(microservices): pull kafka retriable error from all handlers, laz…
2023-02-03 09:11:20 +01:00
Kamil Myśliwiec
0e84b80138 fix(microservices): fork join event handlers 2023-02-03 08:48:50 +01:00
Kamil Mysliwiec
2aa94cd2c1 Merge pull request #11025 from nestjs/revert-11018-revert-10809-fix/durable-payload-regression
Revert "Revert "fix(core,microservices): inject the context when the tree is not durable""
2023-02-03 08:35:27 +01:00
Kamil Mysliwiec
1cc23f7d9d Revert "Revert "fix(core,microservices): inject the context when the tree is not durable"" 2023-02-03 08:35:13 +01:00
Kamil Myśliwiec
43d0c166d4 fix(microservices): pull kafka retriable error from all handlers, lazy throw 2023-02-02 15:39:45 +01:00
Kamil Mysliwiec
2ed509a0f6 Merge pull request #11019 from nestjs/fix/gracefully-reconnect-rmq
fix(microservices): rmq should gracefully reconnect upon error
2023-02-02 14:46:22 +01:00
Kamil Myśliwiec
9eb3b8903a test: change send to queue spy to a stub 2023-02-02 14:25:38 +01:00
Kamil Mysliwiec
4ad3cbc6a6 Merge pull request #11018 from nestjs/revert-10809-fix/durable-payload-regression
Revert "fix(core,microservices): inject the context when the tree is not durable"
2023-02-02 13:40:17 +01:00
Kamil Myśliwiec
754d1daebc fix(microservices): rmq should gracefully reconnect upon error 2023-02-02 13:35:40 +01:00
Kamil Mysliwiec
da708c7216 Merge pull request #10474 from tuxmachine/fix/8469-fastify-errors-content-type
Fix Content-Type for unexpected errors in Fastify
2023-02-02 13:34:49 +01:00
Kamil Mysliwiec
9141d245a8 Revert "fix(core,microservices): inject the context when the tree is not durable" 2023-02-02 13:06:19 +01:00
Kamil Mysliwiec
77ec351c1f Merge pull request #11012 from nestjs/dependabot/npm_and_yarn/sample/31-graphql-federation-code-first/gateway/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /sample/31-graphql-federation-code-first/gateway
2023-02-02 11:58:57 +01:00
dependabot[bot]
416ec6938a chore(deps): bump http-cache-semantics
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:57:34 +00:00
Kamil Mysliwiec
ae1fdfb52a Merge pull request #11015 from nestjs/dependabot/npm_and_yarn/sample/32-graphql-federation-schema-first/gateway/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /sample/32-graphql-federation-schema-first/gateway
2023-02-02 11:56:47 +01:00
dependabot[bot]
29a18ef5d7 chore(deps): bump http-cache-semantics
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:56:36 +00:00
Kamil Mysliwiec
c46cb29638 Merge pull request #11005 from nestjs/dependabot/npm_and_yarn/graphql-tools-8.3.17
chore(deps-dev): bump graphql-tools from 8.3.16 to 8.3.17
2023-02-02 11:55:59 +01:00
Kamil Mysliwiec
8ce73f3544 Merge pull request #11013 from nestjs/dependabot/npm_and_yarn/sample/31-graphql-federation-code-first/posts-application/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /sample/31-graphql-federation-code-first/posts-application
2023-02-02 11:55:52 +01:00
Kamil Mysliwiec
ff2684c217 Merge pull request #11014 from nestjs/dependabot/npm_and_yarn/sample/32-graphql-federation-schema-first/posts-application/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /sample/32-graphql-federation-schema-first/posts-application
2023-02-02 11:55:43 +01:00
dependabot[bot]
61fce51212 chore(deps): bump http-cache-semantics
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:54:24 +00:00
dependabot[bot]
0b6e59ba46 chore(deps): bump http-cache-semantics
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:54:19 +00:00
Kamil Mysliwiec
cba648b166 Merge pull request #11016 from nestjs/dependabot/npm_and_yarn/sample/32-graphql-federation-schema-first/users-application/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /sample/32-graphql-federation-schema-first/users-application
2023-02-02 11:53:45 +01:00
Kamil Mysliwiec
dad667988c Merge pull request #11017 from nestjs/dependabot/npm_and_yarn/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1
2023-02-02 11:53:38 +01:00
Kamil Mysliwiec
db7501f7b4 Merge pull request #11011 from nestjs/dependabot/npm_and_yarn/sample/31-graphql-federation-code-first/users-application/http-cache-semantics-4.1.1
chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 in /sample/31-graphql-federation-code-first/users-application
2023-02-02 11:53:22 +01:00
dependabot[bot]
82930ee86d chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:42:14 +00:00
Kamil Mysliwiec
79773fe0ae Merge pull request #11004 from nestjs/dependabot/npm_and_yarn/tslib-2.5.0
chore(deps): bump tslib from 2.4.1 to 2.5.0
2023-02-02 11:41:19 +01:00
dependabot[bot]
2c1c2a0f61 chore(deps): bump http-cache-semantics
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:41:15 +00:00
dependabot[bot]
412b60bf59 chore(deps): bump http-cache-semantics
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 10:41:08 +00:00
Kamil Mysliwiec
d860e44b8a Merge pull request #11006 from nestjs/dependabot/npm_and_yarn/redis-4.6.4
chore(deps-dev): bump redis from 4.5.1 to 4.6.4
2023-02-02 11:41:06 +01:00
Kamil Mysliwiec
e316f6cd0e Merge pull request #11007 from nestjs/dependabot/npm_and_yarn/core-js-3.27.2
chore(deps-dev): bump core-js from 3.27.1 to 3.27.2
2023-02-02 11:40:59 +01:00
Kamil Mysliwiec
e9b02dd833 Merge pull request #11008 from nestjs/dependabot/npm_and_yarn/mongoose-6.9.0
chore(deps-dev): bump mongoose from 6.8.4 to 6.9.0
2023-02-02 11:40:52 +01:00
Kamil Mysliwiec
32af34f226 Merge pull request #11003 from nestjs/dependabot/npm_and_yarn/typescript-4.9.5
chore(deps-dev): bump typescript from 4.9.4 to 4.9.5
2023-02-02 11:40:29 +01:00
Kamil Mysliwiec
664510a1b4 Merge pull request #11001 from bittlerr/nats-server-debug-messages-filter
refactor(microservices): use switch case for types of statuses in server
2023-02-02 08:45:32 +01:00
codytseng
5e5828ba12 refactor: delete unused import 2023-02-02 15:15:54 +08:00
dependabot[bot]
dd72da7294 chore(deps-dev): bump mongoose from 6.8.4 to 6.9.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 6.8.4 to 6.9.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/6.8.4...6.9.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 00:28:39 +00:00
dependabot[bot]
3c932e33bc chore(deps-dev): bump core-js from 3.27.1 to 3.27.2
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.27.1 to 3.27.2.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.27.2/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 00:25:00 +00:00
dependabot[bot]
a86fafed14 chore(deps-dev): bump redis from 4.5.1 to 4.6.4
Bumps [redis](https://github.com/redis/node-redis) from 4.5.1 to 4.6.4.
- [Release notes](https://github.com/redis/node-redis/releases)
- [Changelog](https://github.com/redis/node-redis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/redis/node-redis/compare/redis@4.5.1...redis@4.6.4)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 00:21:41 +00:00
dependabot[bot]
a99846b817 chore(deps-dev): bump graphql-tools from 8.3.16 to 8.3.17
Bumps [graphql-tools](https://github.com/ardatan/graphql-tools/tree/HEAD/packages/graphql-tools) from 8.3.16 to 8.3.17.
- [Release notes](https://github.com/ardatan/graphql-tools/releases)
- [Changelog](https://github.com/ardatan/graphql-tools/blob/master/packages/graphql-tools/CHANGELOG.md)
- [Commits](https://github.com/ardatan/graphql-tools/commits/graphql-tools@8.3.17/packages/graphql-tools)

---
updated-dependencies:
- dependency-name: graphql-tools
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 00:13:36 +00:00
dependabot[bot]
d45b16e791 chore(deps): bump tslib from 2.4.1 to 2.5.0
Bumps [tslib](https://github.com/Microsoft/tslib) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/Microsoft/tslib/releases)
- [Commits](https://github.com/Microsoft/tslib/compare/2.4.1...2.5.0)

---
updated-dependencies:
- dependency-name: tslib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 00:10:48 +00:00
dependabot[bot]
15ac489573 chore(deps-dev): bump typescript from 4.9.4 to 4.9.5
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.4 to 4.9.5.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.4...v4.9.5)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 00:04:49 +00:00
Vinícius Lourenço
ccbb604fc2 perf(core): better performance to get method names 2023-02-01 18:56:42 -03:00
Constantin Cuciurcă
1f327efd03 refactor(microservices): use switch case for types of statuses in server 2023-02-01 20:10:30 +02:00
codytseng
c6dbc05bb6 refactor: MiddlewareModule constructor parameters 2023-02-01 23:22:02 +08:00
codytseng
33fe1d0337 fix: duplicate identifier 2023-02-01 22:41:24 +08:00
codytseng
25dd621132 Merge branch 'master' into fix-middleware-global-prefix 2023-02-01 22:36:47 +08:00
Kamil Myśliwiec
d9c394bb44 chore(@nestjs) publish v9.3.1 release 2023-02-01 15:33:21 +01:00
Kamil Myśliwiec
91f7190b07 fix(common): drop class-validator imports 2023-02-01 15:32:46 +01:00
Kamil Myśliwiec
0a4f9419d8 chore: update package.json version 2023-02-01 15:04:22 +01:00
Kamil Myśliwiec
eba8f15683 chore(@nestjs) publish v9.3.0 release 2023-02-01 15:00:02 +01:00
Kamil Mysliwiec
2310c56f4c Merge pull request #10997 from nestjs/dependabot/npm_and_yarn/sample/04-grpc/json5-1.0.2
chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/04-grpc
2023-02-01 14:58:03 +01:00
Kamil Mysliwiec
06ea451f1f Merge pull request #11000 from nestjs/dependabot/npm_and_yarn/sample/01-cats-app/json5-1.0.2
chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/01-cats-app
2023-02-01 14:57:57 +01:00
dependabot[bot]
b2c55a6f79 chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/01-cats-app
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 13:35:50 +00:00
dependabot[bot]
3d366ce5c5 chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/04-grpc
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 13:35:48 +00:00
Kamil Mysliwiec
f7e366600f Merge pull request #10999 from nestjs/dependabot/npm_and_yarn/sample/02-gateways/json5-1.0.2
chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/02-gateways
2023-02-01 14:35:10 +01:00
Kamil Mysliwiec
55bc840edb Merge pull request #10998 from nestjs/dependabot/npm_and_yarn/sample/03-microservices/json5-1.0.2
chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/03-microservices
2023-02-01 14:35:03 +01:00
Kamil Mysliwiec
22c3175552 Merge pull request #10982 from Inoir/fix/kafka-rpc-exception-stall
fix: kafka rpc exception not resolved
2023-02-01 14:31:43 +01:00
Kamil Myśliwiec
c1815d2f8c fix: revert #10801 2023-02-01 14:29:21 +01:00
Dominik Koller
34654244a7 put resolve in else block 2023-02-01 14:10:57 +01:00
Kamil Myśliwiec
2924c03aa3 fix: apply uuid factory strategy in testing module, update snapshots 2023-02-01 14:03:05 +01:00
Kamil Myśliwiec
d1ec9db5f2 fix(core): revert the original metadata scanner 2023-02-01 13:47:38 +01:00
Kamil Myśliwiec
cb7e10dbc3 Merge branch 'master' of https://github.com/nestjs/nest 2023-02-01 13:40:38 +01:00
Kamil Myśliwiec
511e7162a6 style: fix lint errors, ignore js and d.ts files 2023-02-01 13:40:29 +01:00
Kamil Mysliwiec
32b0d9c600 Merge pull request #10737 from mahkassem/Parse-File-Pipe-Fix
Parse file pipe fix (multiple files validation)
2023-02-01 13:20:06 +01:00
Kamil Mysliwiec
967a136834 Merge pull request #10201 from nkitku/patch-1
Fix HTTPException on minifying class names, its not match regex expre…
2023-02-01 13:17:30 +01:00
dependabot[bot]
1aadd30f46 chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/02-gateways
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 12:16:41 +00:00
dependabot[bot]
ae76b521d6 chore(deps): bump json5 from 1.0.1 to 1.0.2 in /sample/03-microservices
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-01 12:16:27 +00:00
Kamil Mysliwiec
38c365215a Merge pull request #10531 from ChrisiPK/patch-logger-error-call
fix(common): correct order for logger error parameters
2023-02-01 13:14:17 +01:00
Kamil Mysliwiec
80b8c86c68 Merge pull request #10557 from weal1312/fix/cache_manager_store
fix(common): CacheStoreFactory type
2023-02-01 13:10:07 +01:00
Kamil Myśliwiec
8756ed3d68 Merge branch 'e-dot-master' 2023-02-01 13:09:27 +01:00
Kamil Myśliwiec
d616861698 style: minor formatting tweaks 2023-02-01 13:09:18 +01:00
Kamil Myśliwiec
c64312fded Merge branch 'master' of github.com:e-dot/nest into e-dot-master 2023-02-01 13:08:15 +01:00
Kamil Myśliwiec
304e6bf5bb Merge branch 'master' of https://github.com/nestjs/nest 2023-02-01 13:03:41 +01:00
Kamil Myśliwiec
161198379a Merge branch 'tolgap-fix/10471-use-body-parser' 2023-02-01 13:03:34 +01:00
Kamil Myśliwiec
661104d981 chore: resolve merge conflicts 2023-02-01 13:03:19 +01:00
Kamil Mysliwiec
f71554ca96 Merge pull request #10696 from kos984/fix/NestApplicationContext-listenToShutdownSignals
fix(core): process exit before shutdown hook end
2023-02-01 13:01:01 +01:00
Kamil Myśliwiec
a986e7e907 Merge branch 'master' of https://github.com/nestjs/nest 2023-02-01 13:00:10 +01:00
Kamil Myśliwiec
920f430d4d Merge branch 'zanminkian-zmj-fix_gloabl_prefix' 2023-02-01 13:00:03 +01:00
Kamil Myśliwiec
cd0462fbcb style: minor formatting tweaks 2023-02-01 12:59:40 +01:00
Kamil Myśliwiec
b9910a400a Merge branch 'zmj-fix_gloabl_prefix' of github.com:zanminkian/nest into zanminkian-zmj-fix_gloabl_prefix 2023-02-01 12:58:09 +01:00
Kamil Mysliwiec
6d88e3cd3c Merge pull request #10809 from micalevisk/fix/durable-payload-regression
fix(core,microservices): inject the context when the tree is not durable
2023-02-01 12:56:47 +01:00
Kamil Myśliwiec
fbf1ab7a81 fix(core): minor fix - reset to random if snapshot is off 2023-02-01 12:55:27 +01:00
Kamil Myśliwiec
868bbcd8b9 Merge branch 'master' of https://github.com/nestjs/nest 2023-02-01 12:53:19 +01:00
Kamil Myśliwiec
4e783852b3 feat(core): use deterministic uuids if absolutely necessary 2023-02-01 12:53:08 +01:00
Kamil Mysliwiec
07a84ae224 Merge pull request #10888 from Tony133/refactor/improvements-imports
refactor(): improvements the imports
2023-02-01 12:39:36 +01:00
Kamil Myśliwiec
7bc72671b6 build: fix ts build error 2023-02-01 12:38:47 +01:00
Kamil Myśliwiec
a560c3466a Merge branch 'H4ad-perf/metadata-scanner' 2023-02-01 12:36:49 +01:00
Kamil Myśliwiec
f10c917b71 style: change inline ifs to code blocks 2023-02-01 12:36:36 +01:00
Kamil Myśliwiec
208d8ca184 Merge branch 'perf/metadata-scanner' of github.com:H4ad/nest into H4ad-perf/metadata-scanner 2023-02-01 12:35:27 +01:00
Kamil Myśliwiec
32aeb7a8a3 Merge branch 'H4ad-perf/random-string-generator' 2023-02-01 12:32:55 +01:00
Antonio Tripodi
a185e63281 Merge branch 'master' into refactor/improvements-imports 2023-02-01 11:40:47 +01:00
Dominik Koller
0b395d1f59 resolve on error 2023-02-01 03:07:35 +01:00
Micael Levi L. Cavalcante
f396dc1ddf refactor(core): extract get context id logic into its own method 2023-01-27 12:08:12 -04:00
Micael Levi L. Cavalcante
aafdac62a0 fix(core,microservices): inject the context when the tree is not durable 2023-01-27 12:08:10 -04:00
Tony133
e8944675cb refactor(): improvements the imports 2023-01-16 22:27:58 +01:00
Tony133
4d4f3f82de refactor(): improvements the imports 2023-01-16 16:08:53 +01:00
Vinícius Lourenço
994f8c4e42 perf(core): better performance to get method names 2023-01-06 22:13:23 -03:00
曾明健
58ee3bf2f3 test: fix testing problem 2023-01-04 10:12:16 +00:00
曾明健
5deabef549 fix(core): fix global prefix not working 2023-01-04 10:05:05 +00:00
e-dot
9a47a6ce70 Merge branch 'master' of https://github.com/e-dot/nest 2022-12-14 14:07:25 +01:00
e-dot
eee8968d82 Fix port = 0 issue
When a user passes port:0 to this function, it's legit : in order to detect the presence of attributes, we use another comparison with !== undefined (works with 0).
See PR review here: 35357053b1
2022-12-14 14:06:19 +01:00
e-dot
35357053b1 Update packages/platform-fastify/adapters/fastify-adapter.ts
Ok - code has been rewritten afterwards - with better type support

Co-authored-by: Micael Levi L. Cavalcante <mllc@icomp.ufam.edu.br>
2022-12-12 17:45:03 +01:00
Konstantin Upir
ceea86d6ed fix(core): process exit before shutdown hook end 2022-12-12 12:37:08 +01:00
Mahmoud Kassem
452f19461c fix(parse-file): remove object case 2022-12-08 08:23:08 +02:00
Mahmoud Kassem
7792c43b93 fix(parse-file): multi files validation 2022-12-07 23:17:27 +02:00
Tolga Paksoy
363ab79b5f chore(platform): use type imports 2022-12-07 17:32:39 +01:00
Tolga Paksoy
9efb84fc22 refactor(platform): add example and simplify types 2022-12-07 17:32:36 +01:00
Tolga Paksoy
1b90339d8b fix(core): remove empty optional adapter method 2022-12-07 17:05:00 +01:00
Tolga Paksoy
a25bfa86ed refactor(platform): extract platform implementation detail to interfaces 2022-12-05 18:03:10 +01:00
Tolga Paksoy
d5e1cd18fc refactor(fastify): re-use use body parser function 2022-12-05 18:03:08 +01:00
Tolga Paksoy
9b073748cb test(): add integration test for raw body 2022-12-05 17:52:11 +01:00
Tolga Paksoy
0444f9c89e feat(platform): enable body parser with raw body support 2022-12-05 17:52:08 +01:00
e-dot
1b9bb76d07 Merge branch 'master' of https://github.com/e-dot/nest 2022-12-02 18:50:17 +01:00
e-dot
c817014c7d Windows IIS Compatibility : listen(FastifyListenOptions)
Adapt the fastify adapter to be compatible with Microsoft IIS : requires the new listen() API with a FastifyListenOptions parameter
2022-12-02 18:48:21 +01:00
e-dot
accb1dc014 Adapt Fastify Adapter (!) to use listen options
Improve the fastify adapter to use an object (FastifyListenOptions) in order to be able to totally configure fastify. In particular, enable use of "named pipes" for port, and render nestJs compatible with IIS and IISNode on Windows.
2022-11-30 10:08:24 +01:00
e-dot
1755537a77 Improve type checking
Use class FastifyListenOptions to restrict type checking
And check that one of the attributes is really defined (path port or host) in order to use it as the listen option
2022-11-29 16:23:44 +01:00
e-dot
7b81d0571d Merge pull request #1 from e-dot/e-dot-patch-1
Compatibility with Microsoft IIS, iisnode and fastify
2022-11-25 18:01:59 +01:00
e-dot
7afed825be Compatibility with Microsoft IIS, iisnode and fastify
To enable compatibilty with iisnode and fastify, you need to enable use of an object - passed as-is to the fastify listen function (see https://www.fastify.io/docs/latest/Reference/Server/#listen)
2022-11-25 17:56:51 +01:00
codytseng
a1f17e2271 refactor: rename getPaths to extractPathsFrom 2022-11-24 21:34:45 +08:00
codytseng
ecba3bafde refactor: create a separate class to retrieve the paths 2022-11-24 14:37:59 +08:00
codytseng
daa2054000 style: keep up with current standards 2022-11-24 11:49:42 +08:00
codytseng
151a67b752 refactor: applicationConfig instead (instance as any).config 2022-11-24 09:32:09 +08:00
codytseng
60772c39e1 feat: use mapToExcludeRoute() when set global prefix 2022-11-23 22:34:25 +08:00
codytseng
7d1fa9a216 test: global prefix options 2022-11-23 18:23:00 +08:00
codytseng
758cd2d8b1 refactor: wrap get paths logic into a private function 2022-11-23 18:18:32 +08:00
codytseng
fdc5f34219 feat: move test 2022-11-23 16:07:13 +08:00
Cody Tseng
d5f434ce75 Merge branch 'nestjs:master' into fix-middleware-global-prefix 2022-11-20 01:07:44 +08:00
codytseng
4a27d263c5 test: update the middleware get the params from global prefix test 2022-11-20 00:25:59 +08:00
codytseng
cc7e829804 feat: version-aware middleware 2022-11-19 18:11:09 +08:00
codytseng
4c4c13bf84 Merge branch 'master' into fix-middleware-global-prefix 2022-11-15 22:38:42 +08:00
weal1312
d6097a10ac fix(common): CacheStoreFactory type 2022-11-12 04:04:56 +08:00
ChrisiPK
584015bc77 fix(common): correct order for logger error parameters
The error() function of a logger has two optional parameters: the trace and the context. The logger
now no longer passes the global context as the first optional parameter when there are is no value
supplied for the trace parameter. Instead, the trace parameter is passed as undefined and the
context is passed as the second optional parameter.

This fixes a bug where the trace is overwritten by the context when passed to custom loggers.
Fixes gremo/nest-winston#473
2022-11-07 21:28:41 +00:00
Rick Dutour Geerling
61d25beae0 fix(fastify): always return errors as application/json
If user overrides the Content-Type but doesn't add a custom exception
filter to handle errors, then Nest will respond with a JSON error
object. We should ensure the Content-Type header matches the body
in this case.
2022-10-31 12:09:25 +01:00
Rick Dutour Geerling
ddf3301d49 fix(express): always return errors as application/json
If user overrides the Content-Type but doesn't add a custom exception
filter to handle errors, then Nest will respond with a JSON error
object. We should ensure the Content-Type header matches the body
in this case.
2022-10-31 12:09:24 +01:00
codytseng
3d1632693c style(core): add missing period 2022-10-15 11:08:25 +08:00
codytseng
c437ac97d0 fix(core): add leading slash to excluded paths 2022-10-15 10:50:25 +08:00
codytseng
b7f458f3fe test: add the middleware get the params from global prefix test 2022-10-11 15:33:36 +08:00
codytseng
75d5d64ec7 fix(core): let the middleware can get the params in the global prefix 2022-10-10 22:56:30 +08:00
nkitku
f3e6c54b3e Fix HTTPException on minifying class names, its not match regex expression
```js
"BadRequest".match(/[A-Z][a-z]+|[0-9]+/g).join(' ') // this is ok 
// but on minify 
"t".match(/[A-Z][a-z]+|[0-9]+/g).join(' ') // throws error
// Uncaught TypeError: Cannot read properties of null (reading 'join')
```
2022-08-30 11:47:26 +05:30
140 changed files with 7831 additions and 6110 deletions

View File

@@ -1 +1,3 @@
**/node_modules/**
**/node_modules/**
*.d.ts
*.js

View File

@@ -11,7 +11,6 @@
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>

View File

@@ -15,7 +15,7 @@ scalar Date
type Query {
recipe(id: String!): Recipe!
recipes(skip: Int = 0, take: Int = 25): [Recipe!]!
recipes(skip: Int! = 0, take: Int! = 25): [Recipe!]!
}
type Mutation {

View File

@@ -1,39 +1,134 @@
import { HttpStatus, INestApplication } from '@nestjs/common';
import { HttpServer, HttpStatus, INestApplication } from '@nestjs/common';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { ErrorsController } from '../src/errors/errors.controller';
describe('Error messages', () => {
let server;
let app: INestApplication;
let server: HttpServer;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [ErrorsController],
}).compile();
describe('Express', () => {
let app: INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [ErrorsController],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
it(`/GET`, () => {
return request(server).get('/sync').expect(HttpStatus.BAD_REQUEST).expect({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
it(`/GET`, () => {
return request(server)
.get('/sync')
.expect(HttpStatus.BAD_REQUEST)
.expect({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
});
});
it(`/GET (Promise/async)`, () => {
return request(server)
.get('/async')
.expect(HttpStatus.BAD_REQUEST)
.expect({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
});
});
it(`/GET (InternalServerError despite custom content-type)`, async () => {
return request(server)
.get('/unexpected-error')
.expect(HttpStatus.INTERNAL_SERVER_ERROR)
.expect({
statusCode: 500,
message: 'Internal server error',
});
});
afterEach(async () => {
await app.close();
});
});
it(`/GET (Promise/async)`, () => {
return request(server).get('/async').expect(HttpStatus.BAD_REQUEST).expect({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
});
});
describe('Fastify', () => {
let app: NestFastifyApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [ErrorsController],
}).compile();
afterEach(async () => {
await app.close();
app = module.createNestApplication<NestFastifyApplication>(
new FastifyAdapter(),
);
server = app.getHttpServer();
await app.init();
});
it(`/GET`, async () => {
return app
.inject({
method: 'GET',
url: '/sync',
})
.then(({ payload, statusCode }) => {
expect(statusCode).to.equal(HttpStatus.BAD_REQUEST);
expect(payload).to.equal(
JSON.stringify({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
}),
);
});
});
it(`/GET (Promise/async)`, async () => {
return app
.inject({
method: 'GET',
url: '/sync',
})
.then(({ payload, statusCode }) => {
expect(statusCode).to.equal(HttpStatus.BAD_REQUEST);
expect(payload).to.equal(
JSON.stringify({
statusCode: 400,
error: 'Bad Request',
message: 'Integration test',
}),
);
});
});
it(`/GET (InternalServerError despite custom content-type)`, async () => {
return app
.inject({
method: 'GET',
url: '/unexpected-error',
})
.then(({ payload, statusCode }) => {
expect(statusCode).to.equal(HttpStatus.INTERNAL_SERVER_ERROR);
expect(payload).to.equal(
JSON.stringify({
statusCode: 500,
message: 'Internal server error',
}),
);
});
});
afterEach(async () => {
await app.close();
});
});
});

View File

@@ -77,6 +77,26 @@ describe('Middleware', () => {
});
});
describe('when using default URI versioning with the global prefix', () => {
beforeEach(async () => {
app = await createAppWithVersioning(
{
type: VersioningType.URI,
defaultVersion: VERSION_NEUTRAL,
},
async (app: INestApplication) => {
app.setGlobalPrefix('api');
},
);
});
it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => {
return request(app.getHttpServer())
.get('/api/v1/versioned')
.expect(200, VERSIONED_VALUE);
});
});
describe('when using HEADER versioning', () => {
beforeEach(async () => {
app = await createAppWithVersioning({
@@ -133,6 +153,7 @@ describe('Middleware', () => {
async function createAppWithVersioning(
versioningOptions: VersioningOptions,
beforeInit?: (app: INestApplication) => Promise<void>,
): Promise<INestApplication> {
const app = (
await Test.createTestingModule({
@@ -141,6 +162,9 @@ async function createAppWithVersioning(
).createNestApplication();
app.enableVersioning(versioningOptions);
if (beforeInit) {
await beforeInit(app);
}
await app.init();
return app;

View File

@@ -12,6 +12,7 @@ import { AppModule } from '../src/app.module';
const RETURN_VALUE = 'test';
const SCOPED_VALUE = 'test_scoped';
const WILDCARD_VALUE = 'test_wildcard';
const EXCLUDE_VALUE = 'test_exclude';
@Controller()
class TestController {
@@ -24,6 +25,11 @@ class TestController {
wildcard_nested() {
return RETURN_VALUE;
}
@Get('exclude')
exclude() {
return EXCLUDE_VALUE;
}
}
@Module({
@@ -36,8 +42,10 @@ class TestModule {
.apply((req, res, next) => res.send(WILDCARD_VALUE))
.forRoutes('tests/*')
.apply((req, res, next) => res.send(SCOPED_VALUE))
.exclude('exclude')
.forRoutes(TestController)
.apply((req, res, next) => res.send(RETURN_VALUE))
.exclude('exclude')
.forRoutes('*');
}
}
@@ -45,25 +53,34 @@ class TestModule {
describe('Middleware', () => {
let app: INestApplication;
beforeEach(async () => {
app = (
await Test.createTestingModule({
imports: [TestModule],
}).compile()
).createNestApplication();
await app.init();
it(`forRoutes(*)`, async () => {
app = await createApp();
await request(app.getHttpServer()).get('/hello').expect(200, RETURN_VALUE);
await request(app.getHttpServer())
.get('/exclude')
.expect(200, EXCLUDE_VALUE);
});
it(`forRoutes(*)`, () => {
return request(app.getHttpServer()).get('/hello').expect(200, RETURN_VALUE);
it(`forRoutes(*) with global prefix`, async () => {
app = await createApp(app => app.setGlobalPrefix('api'));
await request(app.getHttpServer())
.get('/api/hello')
.expect(200, RETURN_VALUE);
await request(app.getHttpServer())
.get('/api/exclude')
.expect(200, EXCLUDE_VALUE);
});
it(`forRoutes(TestController)`, () => {
return request(app.getHttpServer()).get('/test').expect(200, SCOPED_VALUE);
it(`forRoutes(TestController)`, async () => {
app = await createApp();
await request(app.getHttpServer()).get('/test').expect(200, SCOPED_VALUE);
await request(app.getHttpServer())
.get('/exclude')
.expect(200, EXCLUDE_VALUE);
});
it(`forRoutes(tests/*)`, () => {
it(`forRoutes(tests/*)`, async () => {
app = await createApp();
return request(app.getHttpServer())
.get('/tests/wildcard')
.expect(200, WILDCARD_VALUE);
@@ -73,3 +90,20 @@ describe('Middleware', () => {
await app.close();
});
});
async function createApp(
beforeInit?: (app: INestApplication) => void,
): Promise<INestApplication> {
const app = (
await Test.createTestingModule({
imports: [TestModule],
}).compile()
).createNestApplication();
if (beforeInit) {
beforeInit(app);
}
await app.init();
return app;
}

View File

@@ -1,4 +1,4 @@
import { BadRequestException, Controller, Get } from '@nestjs/common';
import { BadRequestException, Controller, Get, Header } from '@nestjs/common';
@Controller()
export class ErrorsController {
@@ -12,6 +12,12 @@ export class ErrorsController {
this.throwError();
}
@Get('unexpected-error')
@Header('Content-Type', 'application/pdf')
unexpectedError() {
throw new Error();
}
throwError() {
throw new BadRequestException({
statusCode: 400,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -119,6 +119,17 @@ describe('Global prefix', () => {
await request(server).get('/api/v1/middleware/foo').expect(404);
});
it(`should get the params in the global prefix`, async () => {
app.setGlobalPrefix('/api/:tenantId');
server = app.getHttpServer();
await app.init();
await request(server)
.get('/api/test/params')
.expect(200, { '0': 'params', tenantId: 'test' });
});
afterEach(async () => {
await app.close();
});

View File

@@ -7,6 +7,11 @@ export class AppController {
return 'Hello: ' + req.extras?.data;
}
@Get('params')
getParams(@Req() req): any {
return req.middlewareParams;
}
@Get('health')
getHealth(): string {
return 'up';

View File

@@ -22,6 +22,11 @@ export class AppModule {
req.extras = { data: 'Data attached in middleware' };
next();
})
.forRoutes({ path: '*', method: RequestMethod.GET })
.apply((req, res, next) => {
req.middlewareParams = req.params;
next();
})
.forRoutes({ path: '*', method: RequestMethod.GET });
}
}

View File

@@ -0,0 +1,95 @@
import { NestExpressApplication } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import { OptionsUrlencoded } from 'body-parser';
import { expect } from 'chai';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Body Parser (Express Application)', () => {
const moduleFixture = Test.createTestingModule({
imports: [AppModule],
});
let app: NestExpressApplication;
afterEach(async () => {
await app.close();
});
describe('application/json', () => {
const stringLimit = '{ "msg": "Hello, World" }';
const stringOverLimit = '{ "msg": "Hello, World!" }';
beforeEach(async () => {
const testFixture = await moduleFixture.compile();
app = testFixture
.createNestApplication<NestExpressApplication>({
rawBody: true,
logger: false,
})
.useBodyParser('json', { limit: Buffer.from(stringLimit).byteLength });
await app.init();
});
it('should allow request with matching body limit', async () => {
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.send(stringLimit)
.expect(201);
expect(response.body).to.eql({
raw: stringLimit,
});
});
it('should fail if post body is larger than limit', async () => {
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/json')
.send(stringOverLimit)
.expect(413);
});
});
describe('application/x-www-form-urlencoded', () => {
const stringLimit = 'msg=Hello, World';
const stringOverLimit = 'msg=Hello, World!';
beforeEach(async () => {
const testFixture = await moduleFixture.compile();
app = testFixture
.createNestApplication<NestExpressApplication>({
rawBody: true,
logger: false,
})
.useBodyParser<OptionsUrlencoded>('urlencoded', {
limit: Buffer.from(stringLimit).byteLength,
extended: true,
});
await app.init();
});
it('should allow request with matching body limit', async () => {
const response = await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send(stringLimit)
.expect(201);
expect(response.body).to.eql({
raw: stringLimit,
});
});
it('should fail if post body is larger than limit', async () => {
await request(app.getHttpServer())
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send(stringOverLimit)
.expect(413);
});
});
});

View File

@@ -0,0 +1,106 @@
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { AppModule } from '../src/app.module';
describe('Body Parser (Fastify Application)', () => {
const moduleFixture = Test.createTestingModule({
imports: [AppModule],
});
let app: NestFastifyApplication;
afterEach(async () => {
await app.close();
});
describe('application/json', () => {
const stringLimit = '{ "msg": "Hello, World" }';
const stringOverLimit = '{ "msg": "Hello, World!" }';
beforeEach(async () => {
const testFixture = await moduleFixture.compile();
app = testFixture
.createNestApplication<NestFastifyApplication>(new FastifyAdapter(), {
rawBody: true,
logger: false,
})
.useBodyParser('application/json', {
bodyLimit: Buffer.from(stringLimit).byteLength,
});
await app.init();
});
it('should allow request with matching body limit', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/json' },
payload: stringLimit,
});
expect(JSON.parse(response.body)).to.eql({
raw: stringLimit,
});
});
it('should fail if post body is larger than limit', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/json' },
payload: stringOverLimit,
});
expect(response.statusCode).to.equal(413);
});
});
describe('application/x-www-form-urlencoded', () => {
const stringLimit = 'msg=Hello, World';
const stringOverLimit = 'msg=Hello, World!';
beforeEach(async () => {
const testFixture = await moduleFixture.compile();
app = testFixture
.createNestApplication<NestFastifyApplication>(new FastifyAdapter(), {
rawBody: true,
logger: false,
})
.useBodyParser('application/x-www-form-urlencoded', {
bodyLimit: Buffer.from(stringLimit).byteLength,
});
await app.init();
});
it('should allow request with matching body limit', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
payload: stringLimit,
});
expect(JSON.parse(response.body)).to.eql({
raw: stringLimit,
});
});
it('should fail if post body is larger than limit', async () => {
const response = await app.inject({
method: 'POST',
url: '/',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
payload: stringOverLimit,
});
expect(response.statusCode).to.equal(413);
});
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Post, Req, RawBodyRequest } from '@nestjs/common';
import { IncomingMessage } from 'http';
@Controller()
export class AppController {
@Post()
index(@Req() req: RawBodyRequest<IncomingMessage>) {
return {
raw: req.rawBody?.toString(),
};
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [AppController],
})
export class AppModule {}

View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"lib": ["dom"],
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist"
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules"
]
}

View File

@@ -3,5 +3,5 @@
"packages": [
"packages/*"
],
"version": "9.3.0-beta.3"
"version": "9.3.3"
}

1129
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "9.1.2",
"version": "9.3.0",
"description": "Modern, fast, powerful node.js web framework",
"homepage": "https://nestjs.com",
"repository": {
@@ -58,6 +58,7 @@
]
},
"dependencies": {
"@node-rs/xxhash": "^1.3.0",
"@nuxtjs/opencollective": "0.3.2",
"class-transformer": "0.5.1",
"class-validator": "0.14.0",
@@ -73,7 +74,7 @@
"reflect-metadata": "0.1.13",
"rxjs": "7.8.0",
"socket.io": "4.5.4",
"tslib": "2.4.1",
"tslib": "2.5.0",
"uid": "2.0.1",
"uuid": "9.0.0"
},
@@ -98,11 +99,11 @@
"@types/chai": "4.3.4",
"@types/chai-as-promised": "7.1.5",
"@types/cors": "2.8.13",
"@types/express": "4.17.16",
"@types/express": "4.17.17",
"@types/gulp": "4.0.10",
"@types/http-errors": "2.0.1",
"@types/mocha": "10.0.1",
"@types/node": "18.11.18",
"@types/node": "18.11.19",
"@types/sinon": "10.0.13",
"@types/supertest": "2.0.12",
"@types/ws": "8.5.4",
@@ -115,7 +116,7 @@
"artillery": "1.7.9",
"body-parser": "1.20.1",
"bytes": "3.1.2",
"cache-manager": "5.1.4",
"cache-manager": "5.1.5",
"cache-manager-redis-store": "3.0.1",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
@@ -123,10 +124,10 @@
"commitlint-circle": "1.0.0",
"concurrently": "7.6.0",
"conventional-changelog": "3.1.25",
"core-js": "3.27.1",
"core-js": "3.27.2",
"coveralls": "3.1.1",
"delete-empty": "3.0.0",
"engine.io-client": "6.3.0",
"engine.io-client": "6.3.1",
"eslint": "7.32.0",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-import": "2.27.5",
@@ -134,7 +135,7 @@
"fancy-log": "2.0.0",
"fastify": "4.12.0",
"graphql": "15.8.0",
"graphql-tools": "8.3.16",
"graphql-tools": "8.3.17",
"gulp": "4.0.2",
"gulp-clang-format": "1.0.27",
"gulp-clean": "0.4.0",
@@ -154,7 +155,7 @@
"markdown-table": "2.0.0",
"merge-graphql-schemas": "1.7.8",
"mocha": "10.2.0",
"mongoose": "6.8.4",
"mongoose": "6.9.0",
"mqtt": "4.3.7",
"multer": "1.4.4",
"mysql2": "3.1.0",
@@ -162,7 +163,7 @@
"nodemon": "2.0.20",
"nyc": "15.1.0",
"prettier": "2.8.3",
"redis": "4.5.1",
"redis": "4.6.4",
"rxjs-compat": "6.6.7",
"sinon": "15.0.1",
"sinon-chai": "3.7.0",
@@ -172,7 +173,7 @@
"ts-morph": "17.0.1",
"ts-node": "10.9.1",
"typeorm": "0.3.11",
"typescript": "4.9.4",
"typescript": "4.9.5",
"wrk": "1.2.1",
"ws": "8.12.0"
},

View File

@@ -47,15 +47,17 @@ export interface CacheStoreSetOptions<T> {
*
* @publicApi
*/
export interface CacheStoreFactory {
/**
* Return a configured cache store.
*
* @param args Cache manager options received from `CacheModule.register()`
* or `CacheModule.registerAsync()`
*/
create(args: LiteralObject): CacheStore;
}
export type CacheStoreFactory =
| {
/**
* Return a configured cache store.
*
* @param args Cache manager options received from `CacheModule.register()`
* or `CacheModule.registerAsync()`
*/
create(args: LiteralObject): CacheStore;
}
| ((args: LiteralObject) => CacheStore | Promise<CacheStore>);
/**
* Interface defining Cache Manager configuration options.

View File

@@ -35,7 +35,7 @@ export const ENHANCER_KEY_TO_SUBTYPE_MAP = {
} as const;
export type EnhancerSubtype =
typeof ENHANCER_KEY_TO_SUBTYPE_MAP[keyof typeof ENHANCER_KEY_TO_SUBTYPE_MAP];
(typeof ENHANCER_KEY_TO_SUBTYPE_MAP)[keyof typeof ENHANCER_KEY_TO_SUBTYPE_MAP];
export const RENDER_METADATA = '__renderTemplate__';
export const HTTP_CODE_METADATA = '__httpCode__';

View File

@@ -99,9 +99,9 @@ export class HttpException extends Error {
) {
this.message = (this.response as Record<string, any>).message;
} else if (this.constructor) {
this.message = this.constructor.name
.match(/[A-Z][a-z]+|[0-9]+/g)
.join(' ');
this.message =
this.constructor.name.match(/[A-Z][a-z]+|[0-9]+/g)?.join(' ') ??
'Error';
}
}

View File

@@ -1,6 +1,6 @@
/*
* Nest @common
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

View File

@@ -30,6 +30,7 @@ export interface HttpServer<TRequest = any, TResponse = any> {
| RequestHandler<TRequest, TResponse>
| ErrorHandler<TRequest, TResponse>,
): any;
useBodyParser?(...args: any[]): any;
get(handler: RequestHandler<TRequest, TResponse>): any;
get(path: string, handler: RequestHandler<TRequest, TResponse>): any;
post(handler: RequestHandler<TRequest, TResponse>): any;

View File

@@ -205,7 +205,7 @@ export class ConfigurableModuleBuilder<
static [self.staticMethodKey](
options: ModuleOptions & ExtraModuleDefinitionOptions,
): DynamicModule {
const providers = [
const providers: Array<Provider> = [
{
provide: self.options.optionsInjectionToken,
useValue: this.omitExtras(options, self.extras),

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/common",
"version": "9.3.0-beta.3",
"version": "9.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
"author": "Kamil Mysliwiec",
"homepage": "https://nestjs.com",
@@ -18,7 +18,7 @@
"license": "MIT",
"dependencies": {
"iterare": "1.2.1",
"tslib": "2.4.1",
"tslib": "2.5.0",
"uid": "2.0.1"
},
"peerDependencies": {

View File

@@ -1,8 +1,8 @@
import { isUndefined } from '../../utils/shared.utils';
import { Injectable, Optional } from '../../decorators/core';
import { HttpStatus } from '../../enums';
import { PipeTransform } from '../../interfaces/features/pipe-transform.interface';
import { HttpErrorByCode } from '../../utils/http-error-by-code.util';
import { isEmpty, isObject, isUndefined } from '../../utils/shared.utils';
import { FileValidator } from './file-validator.interface';
import { ParseFileOptions } from './parse-file-options.interface';
@@ -39,20 +39,34 @@ export class ParseFilePipe implements PipeTransform<any> {
}
async transform(value: any): Promise<any> {
if (isUndefined(value)) {
if (this.thereAreNoFilesIn(value)) {
if (this.fileIsRequired) {
throw this.exceptionFactory('File is required');
}
return value;
}
if (this.validators.length) {
await this.validate(value);
if (Array.isArray(value)) {
await this.validateFiles(value);
} else {
await this.validate(value);
}
}
return value;
}
private validateFiles(files: any[]): Promise<any[]> {
return Promise.all(files.map(f => this.validate(f)));
}
private thereAreNoFilesIn(value: any): boolean {
const isEmptyArray = Array.isArray(value) && isEmpty(value);
const isEmptyObject = isObject(value) && isEmpty(Object.keys(value));
return isUndefined(value) || isEmptyArray || isEmptyObject;
}
protected async validate(file: any): Promise<any> {
for (const validator of this.validators) {
await this.validateOrThrow(file, validator);

View File

@@ -66,8 +66,10 @@ export class ValidationPipe implements PipeTransform<any> {
...validatorOptions
} = options;
// @see https://github.com/nestjs/nest/issues/10683#issuecomment-1413690508
this.validatorOptions = { forbidUnknownValues: false, ...validatorOptions };
this.isTransformEnabled = !!transform;
this.validatorOptions = validatorOptions;
this.transformOptions = transformOptions;
this.isDetailedOutputDisabled = disableErrorMessages;
this.validateCustomDecorators = validateCustomDecorators || false;

View File

@@ -1,5 +1,4 @@
import { Injectable } from '../decorators/core/injectable.decorator';
import { Optional } from '../decorators/core/optional.decorator';
import { Injectable, Optional } from '../decorators/core';
import { isObject } from '../utils/shared.utils';
import { ConsoleLogger } from './console-logger.service';
import { isLogLevelEnabled } from './utils';
@@ -126,7 +125,9 @@ export class Logger implements LoggerService {
@Logger.WrapBuffer
error(message: any, ...optionalParams: any[]) {
optionalParams = this.context
? optionalParams.concat(this.context)
? (optionalParams.length ? optionalParams : [undefined]).concat(
this.context,
)
: optionalParams;
this.localInstance?.error(message, ...optionalParams);

View File

@@ -384,7 +384,7 @@ describe('ValidationPipe', () => {
});
describe('otherwise', () => {
it('should not reject', async () => {
target = new ValidationPipe({ forbidUnknownValues: false });
target = new ValidationPipe();
const testObj = [
{ prop1: 'value1', prop2: 'value2', prop3: 'value3' },
];
@@ -447,7 +447,7 @@ describe('ValidationPipe', () => {
target = new ValidationPipe({ expectedType: TestModel });
const testObj = { prop1: 'value1', prop2: 'value2' };
expect(await target.transform(testObj, m)).to.equal(testObj);
expect(await target.transform(testObj, m)).to.deep.equal(testObj);
});
it('should validate against the expected type if presented and metatype is primitive type', async () => {
@@ -460,7 +460,7 @@ describe('ValidationPipe', () => {
target = new ValidationPipe({ expectedType: TestModel });
const testObj = { prop1: 'value1', prop2: 'value2' };
expect(await target.transform(testObj, m)).to.equal(testObj);
expect(await target.transform(testObj, m)).to.deep.equal(testObj);
});
});
});

View File

@@ -480,43 +480,87 @@ describe('Logger', () => {
warn(message: any, context?: string) {}
}
const customLogger = new CustomLogger();
const originalLogger = new Logger();
describe('with global context', () => {
const customLogger = new CustomLogger();
const globalContext = 'RandomContext';
const originalLogger = new Logger(globalContext);
let previousLoggerRef: LoggerService;
let previousLoggerRef: LoggerService;
beforeEach(() => {
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(customLogger);
beforeEach(() => {
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(customLogger);
});
afterEach(() => {
Logger.overrideLogger(previousLoggerRef);
});
it('should call custom logger "#log()" method with context as second argument', () => {
const message = 'random log message with global context';
const customLoggerLogSpy = sinon.spy(customLogger, 'log');
originalLogger.log(message);
expect(customLoggerLogSpy.called).to.be.true;
expect(customLoggerLogSpy.calledWith(message, globalContext)).to.be
.true;
});
it('should call custom logger "#error()" method with context as third argument', () => {
const message = 'random error message with global context';
const customLoggerErrorSpy = sinon.spy(customLogger, 'error');
originalLogger.error(message);
expect(customLoggerErrorSpy.called).to.be.true;
expect(
customLoggerErrorSpy.calledWith(message, undefined, globalContext),
).to.be.true;
});
});
describe('without global context', () => {
const customLogger = new CustomLogger();
const originalLogger = new Logger();
afterEach(() => {
Logger.overrideLogger(previousLoggerRef);
});
let previousLoggerRef: LoggerService;
it('should call custom logger "#log()" method', () => {
const message = 'random message';
const context = 'RandomContext';
beforeEach(() => {
previousLoggerRef =
Logger['localInstanceRef'] || Logger['staticInstanceRef'];
Logger.overrideLogger(customLogger);
});
const customLoggerLogSpy = sinon.spy(customLogger, 'log');
afterEach(() => {
Logger.overrideLogger(previousLoggerRef);
});
originalLogger.log(message, context);
it('should call custom logger "#log()" method', () => {
const message = 'random message';
const context = 'RandomContext';
expect(customLoggerLogSpy.called).to.be.true;
expect(customLoggerLogSpy.calledWith(message, context)).to.be.true;
});
const customLoggerLogSpy = sinon.spy(customLogger, 'log');
it('should call custom logger "#error()" method', () => {
const message = 'random message';
const context = 'RandomContext';
originalLogger.log(message, context);
const customLoggerErrorSpy = sinon.spy(customLogger, 'error');
expect(customLoggerLogSpy.called).to.be.true;
expect(customLoggerLogSpy.calledWith(message, context)).to.be.true;
});
originalLogger.error(message, context);
it('should call custom logger "#error()" method', () => {
const message = 'random message';
const context = 'RandomContext';
expect(customLoggerErrorSpy.called).to.be.true;
expect(customLoggerErrorSpy.calledWith(message, context)).to.be.true;
const customLoggerErrorSpy = sinon.spy(customLogger, 'error');
originalLogger.error(message, undefined, context);
expect(customLoggerErrorSpy.called).to.be.true;
expect(customLoggerErrorSpy.calledWith(message, undefined, context))
.to.be.true;
});
});
});
});

View File

@@ -9,16 +9,16 @@ import { isEmpty } from '@nestjs/common/utils/shared.utils';
import { lastValueFrom, isObservable } from 'rxjs';
import { ExternalExceptionFilterContext } from '../exceptions/external-exception-filter-context';
import { FORBIDDEN_MESSAGE } from '../guards/constants';
import { GuardsConsumer } from '../guards/guards-consumer';
import { GuardsContextCreator } from '../guards/guards-context-creator';
import { GuardsConsumer, GuardsContextCreator } from '../guards';
import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { ContextId } from '../injector/instance-wrapper';
import { ModulesContainer } from '../injector/modules-container';
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
import { PipesConsumer } from '../pipes/pipes-consumer';
import { PipesContextCreator } from '../pipes/pipes-context-creator';
import {
InterceptorsConsumer,
InterceptorsContextCreator,
} from '../interceptors';
import { PipesConsumer, PipesContextCreator } from '../pipes';
import { ContextUtils, ParamProperties } from './context-utils';
import { ExternalErrorProxy } from './external-proxy';
import { HandlerMetadataStorage } from './handler-metadata-storage';

View File

@@ -1,6 +1,6 @@
/*
* Nest @core
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

View File

@@ -1,4 +1,8 @@
import { DynamicModule, Type } from '@nestjs/common/interfaces';
import {
DynamicModule,
ForwardReference,
Type,
} from '@nestjs/common/interfaces';
import { ModuleTokenFactory } from './module-token-factory';
export interface ModuleFactory {
@@ -18,19 +22,25 @@ export class ModuleCompiler {
return { type, dynamicMetadata, token };
}
public extractMetadata(metatype: Type<any> | DynamicModule): {
public extractMetadata(
metatype: Type<any> | ForwardReference | DynamicModule,
): {
type: Type<any>;
dynamicMetadata?: Partial<DynamicModule> | undefined;
} {
if (!this.isDynamicModule(metatype)) {
return { type: metatype };
return {
type: (metatype as ForwardReference)?.forwardRef
? (metatype as ForwardReference).forwardRef()
: metatype,
};
}
const { module: type, ...dynamicMetadata } = metatype;
return { type, dynamicMetadata };
}
public isDynamicModule(
module: Type<any> | DynamicModule,
module: Type<any> | DynamicModule | ForwardReference,
): module is DynamicModule {
return !!(module as DynamicModule).module;
}

View File

@@ -353,7 +353,9 @@ export class Injector {
}
public reflectConstructorParams<T>(type: Type<T>): any[] {
const paramtypes = [...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || [])];
const paramtypes = [
...(Reflect.getMetadata(PARAMTYPES_METADATA, type) || []),
];
const selfParams = this.reflectSelfParams<T>(type);
selfParams.forEach(({ index, param }) => (paramtypes[index] = param));

View File

@@ -9,7 +9,7 @@ import {
isUndefined,
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { DeterministicUuidRegistry } from '../inspector/deterministic-uuid-registry';
import { UuidFactory } from '../inspector/uuid-factory';
import { STATIC_CONTEXT } from './constants';
import {
isClassProvider,
@@ -203,15 +203,18 @@ export class InstanceWrapper<T = any> {
if (!isUndefined(this.isTreeDurable)) {
return this.isTreeDurable;
}
if (this.durable === true) {
this.isTreeDurable = true;
this.printIntrospectedAsDurable();
if (this.scope === Scope.REQUEST) {
this.isTreeDurable = this.durable === undefined ? false : this.durable;
if (this.isTreeDurable) {
this.printIntrospectedAsDurable();
}
return this.isTreeDurable;
}
const isStatic = this.isDependencyTreeStatic();
if (isStatic) {
return false;
}
const isTreeNonDurable = this.introspectDepsAttribute(
(collection, registry) =>
collection.some(
@@ -221,7 +224,7 @@ export class InstanceWrapper<T = any> {
),
lookupRegistry,
);
this.isTreeDurable = !isTreeNonDurable && this.durable !== false;
this.isTreeDurable = !isTreeNonDurable;
if (this.isTreeDurable) {
this.printIntrospectedAsDurable();
}
@@ -236,32 +239,30 @@ export class InstanceWrapper<T = any> {
lookupRegistry: string[] = [],
): boolean {
if (lookupRegistry.includes(this[INSTANCE_ID_SYMBOL])) {
return true;
return false;
}
lookupRegistry = lookupRegistry.concat(this[INSTANCE_ID_SYMBOL]);
const { dependencies, properties, enhancers } =
this[INSTANCE_METADATA_SYMBOL];
let introspectionResult =
(dependencies && callback(dependencies, lookupRegistry)) || !dependencies;
let introspectionResult = dependencies
? callback(dependencies, lookupRegistry)
: false;
if (!introspectionResult || !(properties || enhancers)) {
if (introspectionResult || !(properties || enhancers)) {
return introspectionResult;
}
const propertiesHosts = (properties || []).map(item => item.wrapper);
introspectionResult =
introspectionResult &&
((properties &&
callback(
introspectionResult = properties
? callback(
properties.map(item => item.wrapper),
lookupRegistry,
)) ||
!properties);
if (!introspectionResult || !enhancers) {
)
: false;
if (introspectionResult || !enhancers) {
return introspectionResult;
}
return callback(enhancers, lookupRegistry);
return enhancers ? callback(enhancers, lookupRegistry) : false;
}
public isDependencyTreeStatic(lookupRegistry: string[] = []): boolean {
@@ -273,10 +274,10 @@ export class InstanceWrapper<T = any> {
this.printIntrospectedAsRequestScoped();
return this.isTreeStatic;
}
this.isTreeStatic = this.introspectDepsAttribute(
this.isTreeStatic = !this.introspectDepsAttribute(
(collection, registry) =>
collection.every((item: InstanceWrapper) =>
item.isDependencyTreeStatic(registry),
collection.some(
(item: InstanceWrapper) => !item.isDependencyTreeStatic(registry),
),
lookupRegistry,
);
@@ -469,6 +470,6 @@ export class InstanceWrapper<T = any> {
let key = this.name?.toString() ?? this.token?.toString();
key += this.host?.name ?? '';
return key ? DeterministicUuidRegistry.get(key) : randomStringGenerator();
return key ? UuidFactory.get(key) : randomStringGenerator();
}
}

View File

@@ -2,10 +2,14 @@ import { DynamicModule } from '@nestjs/common';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { isFunction, isSymbol } from '@nestjs/common/utils/shared.utils';
import { xxh32 } from '@node-rs/xxhash';
import stringify from 'fast-safe-stringify';
import * as hash from 'object-hash';
const CLASS_STR = 'class ';
const CLASS_STR_LEN = CLASS_STR.length;
export class ModuleTokenFactory {
private readonly moduleTokenCache = new Map<string, string>();
private readonly moduleIdsCache = new WeakMap<Type<unknown>, string>();
public create(
@@ -13,23 +17,36 @@ export class ModuleTokenFactory {
dynamicModuleMetadata?: Partial<DynamicModule> | undefined,
): string {
const moduleId = this.getModuleId(metatype);
if (!dynamicModuleMetadata) {
return this.getStaticModuleToken(moduleId, this.getModuleName(metatype));
}
const opaqueToken = {
id: moduleId,
module: this.getModuleName(metatype),
dynamic: this.getDynamicMetadataToken(dynamicModuleMetadata),
dynamic: dynamicModuleMetadata,
};
return hash(opaqueToken, { ignoreUnknown: true });
const opaqueTokenString = this.getStringifiedOpaqueToken(opaqueToken);
return this.hashString(opaqueTokenString);
}
public getDynamicMetadataToken(
dynamicModuleMetadata: Partial<DynamicModule> | undefined,
): string {
public getStaticModuleToken(moduleId: string, moduleName: string): string {
const key = `${moduleId}_${moduleName}`;
if (this.moduleTokenCache.has(key)) {
return this.moduleTokenCache.get(key);
}
const hash = this.hashString(key);
this.moduleTokenCache.set(key, hash);
return hash;
}
public getStringifiedOpaqueToken(opaqueToken: object | undefined): string {
// Uses safeStringify instead of JSON.stringify to support circular dynamic modules
// The replacer function is also required in order to obtain real class names
// instead of the unified "Function" key
return dynamicModuleMetadata
? stringify(dynamicModuleMetadata, this.replacer)
: '';
return opaqueToken ? stringify(opaqueToken, this.replacer) : '';
}
public getModuleId(metatype: Type<unknown>): string {
@@ -46,14 +63,18 @@ export class ModuleTokenFactory {
return metatype.name;
}
private hashString(value: string): string {
return xxh32(value).toString();
}
private replacer(key: string, value: any) {
if (isFunction(value)) {
const funcAsString = value.toString();
const isClass = /^class\s/.test(funcAsString);
const isClass = funcAsString.slice(0, CLASS_STR_LEN) === CLASS_STR;
if (isClass) {
return value.name;
}
return hash(funcAsString, { ignoreUnknown: true });
return funcAsString;
}
if (isSymbol(value)) {
return value.toString();

View File

@@ -30,11 +30,11 @@ import {
import { createContextId } from '../helpers/context-id-factory';
import { getClassScope } from '../helpers/get-class-scope';
import { isDurable } from '../helpers/is-durable';
import { DeterministicUuidRegistry } from '../inspector/deterministic-uuid-registry';
import { UuidFactory } from '../inspector/uuid-factory';
import { CONTROLLER_ID_KEY } from './constants';
import { NestContainer } from './container';
import { InstanceWrapper } from './instance-wrapper';
import { ModuleRefGetOrResolveOpts, ModuleRef } from './module-ref';
import { ModuleRef, ModuleRefGetOrResolveOpts } from './module-ref';
/**
* @note
@@ -626,8 +626,6 @@ export class Module {
private generateUuid(): string {
const UUID_NAMESPACE = 'fb848993-0c82-4b9e-ae95-3c3c1dbe3d6b';
const key = this.name?.toString() ?? this.token?.toString();
return key
? DeterministicUuidRegistry.get(key, UUID_NAMESPACE)
: randomStringGenerator();
return key ? UuidFactory.get(key, UUID_NAMESPACE) : randomStringGenerator();
}
}

View File

@@ -1,8 +1,8 @@
import { v4 as uuid } from 'uuid';
import { uid } from 'uid';
import { Module } from './module';
export class ModulesContainer extends Map<string, Module> {
private readonly _applicationId = uuid();
private readonly _applicationId = uid(21);
get applicationId(): string {
return this._applicationId;

View File

@@ -1,4 +1,4 @@
import { v5 as uuid } from 'uuid';
import { xxh32 } from '@node-rs/xxhash';
const DEFAULT_UUID_NAMESPACE = 'efa0df42-88af-474f-9cad-4206a2319f07';
@@ -6,12 +6,16 @@ export class DeterministicUuidRegistry {
private static readonly registry = new Set<string>();
static get(str: string, namespace: string = DEFAULT_UUID_NAMESPACE, inc = 0) {
const id = inc ? uuid(str + `${inc}`, namespace) : uuid(str, namespace);
if (this.registry.has(id)) {
const id = inc
? xxh32(`${namespace}_${str}_${inc}`)
: xxh32(`${namespace}_${str}`);
const idAsString = `${id}`;
if (this.registry.has(idAsString)) {
return this.get(str, namespace, inc + 1);
}
this.registry.add(id);
return id;
this.registry.add(idAsString);
return idAsString;
}
static clear() {

View File

@@ -0,0 +1,21 @@
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { DeterministicUuidRegistry } from './deterministic-uuid-registry';
export enum UuidFactoryMode {
Random = 'random',
Deterministic = 'deterministic',
}
export class UuidFactory {
private static _mode = UuidFactoryMode.Random;
static set mode(value: UuidFactoryMode) {
this._mode = value;
}
static get(key = '', namespace?: string) {
return this._mode === UuidFactoryMode.Deterministic
? DeterministicUuidRegistry.get(key, namespace)
: randomStringGenerator();
}
}

View File

@@ -4,36 +4,111 @@ import {
isFunction,
isNil,
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
export class MetadataScanner {
private readonly cachedScannedPrototypes: Map<object, string[]> = new Map();
/**
* @deprecated Use {@link getAllMethodNames} instead.
*/
public scanFromPrototype<T extends Injectable, R = any>(
instance: T,
prototype: object,
callback: (name: string) => R,
): R[] {
const methodNames = new Set(this.getAllFilteredMethodNames(prototype));
return iterate(methodNames)
.map(callback)
.filter(metadata => !isNil(metadata))
.toArray();
}
if (!prototype) {
return [];
}
const visitedNames = new Map<string, boolean>();
const result: R[] = [];
*getAllFilteredMethodNames(prototype: object): IterableIterator<string> {
const isMethod = (prop: string) => {
const descriptor = Object.getOwnPropertyDescriptor(prototype, prop);
if (descriptor.set || descriptor.get) {
return false;
}
return !isConstructor(prop) && isFunction(prototype[prop]);
};
do {
yield* iterate(Object.getOwnPropertyNames(prototype))
.filter(isMethod)
.toArray();
for (const property of Object.getOwnPropertyNames(prototype)) {
if (visitedNames.has(property)) {
continue;
}
visitedNames.set(property, true);
// reason: https://github.com/nestjs/nest/pull/10821#issuecomment-1411916533
const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
if (
descriptor.set ||
descriptor.get ||
isConstructor(property) ||
!isFunction(prototype[property])
) {
continue;
}
const value = callback(property);
if (isNil(value)) {
continue;
}
result.push(value);
}
} while (
(prototype = Reflect.getPrototypeOf(prototype)) &&
prototype !== Object.prototype
);
return result;
}
/**
* @deprecated Use {@link getAllMethodNames} instead.
*/
public *getAllFilteredMethodNames(
prototype: object,
): IterableIterator<string> {
yield* this.getAllMethodNames(prototype);
}
public getAllMethodNames(prototype: object | null): string[] {
if (!prototype) {
return [];
}
if (this.cachedScannedPrototypes.has(prototype)) {
return this.cachedScannedPrototypes.get(prototype);
}
const visitedNames = new Map<string, boolean>();
const result: string[] = [];
this.cachedScannedPrototypes.set(prototype, result);
do {
for (const property of Object.getOwnPropertyNames(prototype)) {
if (visitedNames.has(property)) {
continue;
}
visitedNames.set(property, true);
// reason: https://github.com/nestjs/nest/pull/10821#issuecomment-1411916533
const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
if (
descriptor.set ||
descriptor.get ||
isConstructor(property) ||
!isFunction(prototype[property])
) {
continue;
}
result.push(property);
}
} while (
(prototype = Reflect.getPrototypeOf(prototype)) &&
prototype !== Object.prototype
);
return result;
}
}

View File

@@ -6,10 +6,11 @@ import {
} from '@nestjs/common/interfaces';
import {
MiddlewareConfigProxy,
RouteInfo,
MiddlewareConfiguration,
RouteInfo,
} from '@nestjs/common/interfaces/middleware';
import { iterate } from 'iterare';
import { RouteInfoPathExtractor } from './route-info-path-extractor';
import { RoutesMapper } from './routes-mapper';
import { filterMiddleware } from './utils';
@@ -19,12 +20,17 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
constructor(
private readonly routesMapper: RoutesMapper,
private readonly httpAdapter: HttpServer,
private readonly routeInfoPathExtractor: RouteInfoPathExtractor,
) {}
public apply(
...middleware: Array<Type<any> | Function | any>
): MiddlewareConfigProxy {
return new MiddlewareBuilder.ConfigProxy(this, flatten(middleware));
return new MiddlewareBuilder.ConfigProxy(
this,
flatten(middleware),
this.routeInfoPathExtractor,
);
}
public build(): MiddlewareConfiguration[] {
@@ -41,6 +47,7 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
constructor(
private readonly builder: MiddlewareBuilder,
private readonly middleware: Array<Type<any> | Function | any>,
private routeInfoPathExtractor: RouteInfoPathExtractor,
) {}
public getExcludedRoutes(): RouteInfo[] {
@@ -50,7 +57,10 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
public exclude(
...routes: Array<string | RouteInfo>
): MiddlewareConfigProxy {
this.excludedRoutes = this.getRoutesFlatList(routes);
this.excludedRoutes = this.getRoutesFlatList(routes).map(route => ({
...route,
path: this.routeInfoPathExtractor.extractPathFrom(route),
}));
return this;
}

View File

@@ -1,4 +1,4 @@
import { HttpServer, Logger, VersioningType } from '@nestjs/common';
import { HttpServer, Logger } from '@nestjs/common';
import { RequestMethod } from '@nestjs/common/enums/request-method.enum';
import {
MiddlewareConfiguration,
@@ -6,10 +6,7 @@ import {
RouteInfo,
} from '@nestjs/common/interfaces/middleware';
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
import {
addLeadingSlash,
isUndefined,
} from '@nestjs/common/utils/shared.utils';
import { isUndefined } from '@nestjs/common/utils/shared.utils';
import { ApplicationConfig } from '../application-config';
import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middleware.exception';
import { RuntimeException } from '../errors/exceptions/runtime.exception';
@@ -18,7 +15,7 @@ import { ExecutionContextHost } from '../helpers/execution-context-host';
import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { Injector } from '../injector/injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
import { InstanceToken, Module } from '../injector/module';
import { GraphInspector } from '../inspector/graph-inspector';
import {
@@ -26,13 +23,13 @@ import {
MiddlewareEntrypointMetadata,
} from '../inspector/interfaces/entrypoint.interface';
import { REQUEST_CONTEXT_ID } from '../router/request/request-constants';
import { RoutePathFactory } from '../router/route-path-factory';
import { RouterExceptionFilters } from '../router/router-exception-filters';
import { RouterProxy } from '../router/router-proxy';
import { isRequestMethodAll, isRouteExcluded } from '../router/utils';
import { isRequestMethodAll } from '../router/utils';
import { MiddlewareBuilder } from './builder';
import { MiddlewareContainer } from './container';
import { MiddlewareResolver } from './resolver';
import { RouteInfoPathExtractor } from './route-info-path-extractor';
import { RoutesMapper } from './routes-mapper';
export class MiddlewareModule<
@@ -46,13 +43,11 @@ export class MiddlewareModule<
private routerExceptionFilter: RouterExceptionFilters;
private routesMapper: RoutesMapper;
private resolver: MiddlewareResolver;
private config: ApplicationConfig;
private container: NestContainer;
private httpAdapter: HttpServer;
private graphInspector: GraphInspector;
private appOptions: TAppOptions;
constructor(private readonly routePathFactory: RoutePathFactory) {}
private routeInfoPathExtractor: RouteInfoPathExtractor;
public async register(
middlewareContainer: MiddlewareContainer,
@@ -73,8 +68,7 @@ export class MiddlewareModule<
);
this.routesMapper = new RoutesMapper(container);
this.resolver = new MiddlewareResolver(middlewareContainer, injector);
this.config = config;
this.routeInfoPathExtractor = new RouteInfoPathExtractor(config);
this.injector = injector;
this.container = container;
this.httpAdapter = httpAdapter;
@@ -111,6 +105,7 @@ export class MiddlewareModule<
const middlewareBuilder = new MiddlewareBuilder(
this.routesMapper,
this.httpAdapter,
this.routeInfoPathExtractor,
);
try {
await instance.configure(middlewareBuilder);
@@ -250,6 +245,9 @@ export class MiddlewareModule<
const proxy = await this.createProxy(instance);
return this.registerHandler(applicationRef, routeInfo, proxy);
}
const isTreeDurable = wrapper.isDependencyTreeDurable();
await this.registerHandler(
applicationRef,
routeInfo,
@@ -259,19 +257,7 @@ export class MiddlewareModule<
next: () => void,
) => {
try {
const contextId = ContextIdFactory.getByRequest(req);
if (!req[REQUEST_CONTEXT_ID]) {
Object.defineProperty(req, REQUEST_CONTEXT_ID, {
value: contextId,
enumerable: false,
writable: false,
configurable: false,
});
this.container.registerRequestProvider(
contextId.getParent ? contextId.payload : req,
contextId,
);
}
const contextId = this.getContextId(req, isTreeDurable);
const contextInstance = await this.injector.loadPerContext(
instance,
moduleRef,
@@ -316,56 +302,46 @@ export class MiddlewareModule<
private async registerHandler(
applicationRef: HttpServer,
{ path, method, version }: RouteInfo,
routeInfo: RouteInfo,
proxy: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => void,
) {
const prefix = this.config.getGlobalPrefix();
const excludedRoutes = this.config.getGlobalPrefixOptions().exclude;
if (
(Array.isArray(excludedRoutes) &&
isRouteExcluded(excludedRoutes, path, method)) ||
['*', '/*', '(.*)', '/(.*)'].includes(path)
) {
path = addLeadingSlash(path);
} else {
const basePath = addLeadingSlash(prefix);
if (basePath?.endsWith('/') && path?.startsWith('/')) {
// strip slash when a wildcard is being used
// and global prefix has been set
path = path?.slice(1);
}
path = basePath + path;
}
const applicationVersioningConfig = this.config.getVersioning();
if (version && applicationVersioningConfig.type === VersioningType.URI) {
const versionPrefix = this.routePathFactory.getVersionPrefix(
applicationVersioningConfig,
);
path = `/${versionPrefix}${version.toString()}${path}`;
}
const { method } = routeInfo;
const paths = this.routeInfoPathExtractor.extractPathsFrom(routeInfo);
const isMethodAll = isRequestMethodAll(method);
const requestMethod = RequestMethod[method];
const router = await applicationRef.createMiddlewareFactory(method);
router(
path,
isMethodAll
? proxy
: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => {
if (applicationRef.getRequestMethod(req) === requestMethod) {
return proxy(req, res, next);
}
return next();
},
);
const middlewareFunction = isMethodAll
? proxy
: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => {
if (applicationRef.getRequestMethod(req) === requestMethod) {
return proxy(req, res, next);
}
return next();
};
paths.forEach(path => router(path, middlewareFunction));
}
private getContextId(request: unknown, isTreeDurable: boolean): ContextId {
const contextId = ContextIdFactory.getByRequest(request);
if (!request[REQUEST_CONTEXT_ID]) {
Object.defineProperty(request, REQUEST_CONTEXT_ID, {
value: contextId,
enumerable: false,
writable: false,
configurable: false,
});
const requestProviderValue = isTreeDurable ? contextId.payload : request;
this.container.registerRequestProvider(requestProviderValue, contextId);
}
return contextId;
}
}

View File

@@ -0,0 +1,87 @@
import { VersioningType } from '@nestjs/common';
import {
RouteInfo,
VersioningOptions,
VersionValue,
} from '@nestjs/common/interfaces';
import {
addLeadingSlash,
stripEndSlash,
} from '@nestjs/common/utils/shared.utils';
import { ApplicationConfig } from '../application-config';
import { ExcludeRouteMetadata } from '../router/interfaces/exclude-route-metadata.interface';
import { isRouteExcluded } from '../router/utils';
import { RoutePathFactory } from './../router/route-path-factory';
export class RouteInfoPathExtractor {
private routePathFactory: RoutePathFactory;
private readonly prefixPath: string;
private readonly excludedGlobalPrefixRoutes: ExcludeRouteMetadata[];
private readonly versioningConfig?: VersioningOptions;
constructor(private readonly applicationConfig: ApplicationConfig) {
this.routePathFactory = new RoutePathFactory(applicationConfig);
this.prefixPath = stripEndSlash(
addLeadingSlash(this.applicationConfig.getGlobalPrefix()),
);
this.excludedGlobalPrefixRoutes =
this.applicationConfig.getGlobalPrefixOptions().exclude;
this.versioningConfig = this.applicationConfig.getVersioning();
}
public extractPathsFrom({ path, method, version }: RouteInfo): string[] {
const versionPath = this.extractVersionPathFrom(version);
if (this.isAWildcard(path)) {
return Array.isArray(this.excludedGlobalPrefixRoutes)
? [
this.prefixPath + versionPath + addLeadingSlash(path),
...this.excludedGlobalPrefixRoutes.map(
route => versionPath + addLeadingSlash(route.path),
),
]
: [this.prefixPath + versionPath + addLeadingSlash(path)];
}
return [this.extractNonWildcardPathFrom({ path, method, version })];
}
public extractPathFrom(route: RouteInfo): string {
if (this.isAWildcard(route.path) && !route.version) {
return addLeadingSlash(route.path);
}
return this.extractNonWildcardPathFrom(route);
}
private isAWildcard(path: string): boolean {
return ['*', '/*', '/*/', '(.*)', '/(.*)'].includes(path);
}
private extractNonWildcardPathFrom({
path,
method,
version,
}: RouteInfo): string {
const versionPath = this.extractVersionPathFrom(version);
if (
Array.isArray(this.excludedGlobalPrefixRoutes) &&
isRouteExcluded(this.excludedGlobalPrefixRoutes, path, method)
) {
return versionPath + addLeadingSlash(path);
}
return this.prefixPath + versionPath + addLeadingSlash(path);
}
private extractVersionPathFrom(version?: VersionValue): string {
if (!version || this.versioningConfig?.type !== VersioningType.URI)
return '';
const versionPrefix = this.routePathFactory.getVersionPrefix(
this.versioningConfig,
);
return addLeadingSlash(versionPrefix + version.toString());
}
}

View File

@@ -1,19 +1,33 @@
import { RequestMethod } from '@nestjs/common';
import { HttpServer, RouteInfo, Type } from '@nestjs/common/interfaces';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import {
addLeadingSlash,
isFunction,
isString,
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import * as pathToRegexp from 'path-to-regexp';
import { v4 as uuid } from 'uuid';
import { uid } from 'uid';
import { ExcludeRouteMetadata } from '../router/interfaces/exclude-route-metadata.interface';
import { isRouteExcluded } from '../router/utils';
export const mapToExcludeRoute = (
routes: RouteInfo[],
routes: (string | RouteInfo)[],
): ExcludeRouteMetadata[] => {
return routes.map(({ path, method }) => ({
pathRegex: pathToRegexp(path),
requestMethod: method,
}));
return routes.map(route => {
if (isString(route)) {
return {
path: route,
requestMethod: RequestMethod.ALL,
pathRegex: pathToRegexp(addLeadingSlash(route)),
};
}
return {
path: route.path,
requestMethod: route.method,
pathRegex: pathToRegexp(addLeadingSlash(route.path)),
};
});
};
export const filterMiddleware = <T extends Function | Type<any> = any>(
@@ -85,7 +99,7 @@ export function isMiddlewareClass(middleware: any): middleware is Type<any> {
);
}
export function assignToken(metatype: Type<any>, token = uuid()): Type<any> {
export function assignToken(metatype: Type<any>, token = uid(21)): Type<any> {
Object.defineProperty(metatype, 'name', { value: token });
return metatype;
}

View File

@@ -323,13 +323,20 @@ export class NestApplicationContext<
* @param {string[]} signals The system signals it should listen to
*/
protected listenToShutdownSignals(signals: string[]) {
let receivedSignal = false;
const cleanup = async (signal: string) => {
try {
signals.forEach(sig => process.removeListener(sig, cleanup));
if (receivedSignal) {
// If we receive another signal while we're waiting
// for the server to stop, just ignore it.
return;
}
receivedSignal = true;
await this.callDestroyHook();
await this.callBeforeShutdownHook(signal);
await this.dispose();
await this.callShutdownHook(signal);
signals.forEach(sig => process.removeListener(sig, cleanup));
process.kill(process.pid, signal);
} catch (err) {
Logger.error(

View File

@@ -7,7 +7,6 @@ import {
NestHybridApplicationOptions,
NestInterceptor,
PipeTransform,
RequestMethod,
VersioningOptions,
VersioningType,
WebSocketAdapter,
@@ -15,7 +14,6 @@ import {
import {
GlobalPrefixOptions,
NestApplicationOptions,
RouteInfo,
} from '@nestjs/common/interfaces';
import {
CorsOptions,
@@ -31,7 +29,6 @@ import {
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { platform } from 'os';
import * as pathToRegexp from 'path-to-regexp';
import { AbstractHttpAdapter } from './adapters';
import { ApplicationConfig } from './application-config';
import { MESSAGES } from './constants';
@@ -41,10 +38,9 @@ import { Injector } from './injector/injector';
import { GraphInspector } from './inspector/graph-inspector';
import { MiddlewareContainer } from './middleware/container';
import { MiddlewareModule } from './middleware/middleware-module';
import { mapToExcludeRoute } from './middleware/utils';
import { NestApplicationContext } from './nest-application-context';
import { ExcludeRouteMetadata } from './router/interfaces/exclude-route-metadata.interface';
import { Resolver } from './router/interfaces/resolver.interface';
import { RoutePathFactory } from './router/route-path-factory';
import { RoutesResolver } from './router/routes-resolver';
const { SocketModule } = optionalRequire(
@@ -89,9 +85,8 @@ export class NestApplication
this.selectContextModule();
this.registerHttpServer();
this.injector = new Injector({ preview: this.appOptions.preview });
this.middlewareModule = new MiddlewareModule(new RoutePathFactory(config));
this.middlewareModule = new MiddlewareModule();
this.routesResolver = new RoutesResolver(
this.container,
this.config,
@@ -266,6 +261,20 @@ export class NestApplication
return this;
}
public useBodyParser(...args: [any, any?]): this {
if (!('useBodyParser' in this.httpAdapter)) {
this.logger.warn('Your HTTP Adapter does not support `.useBodyParser`.');
return this;
}
const [parserType, ...otherArgs] = args;
const rawBody = !!this.appOptions.rawBody;
this.httpAdapter.useBodyParser(...[parserType, rawBody, ...otherArgs]);
return this;
}
public enableCors(options?: CorsOptions | CorsOptionsDelegate<any>): void {
this.httpAdapter.enableCors(options);
}
@@ -358,20 +367,9 @@ export class NestApplication
public setGlobalPrefix(prefix: string, options?: GlobalPrefixOptions): this {
this.config.setGlobalPrefix(prefix);
if (options) {
const exclude = options?.exclude.map(
(route: string | RouteInfo): ExcludeRouteMetadata => {
if (isString(route)) {
return {
requestMethod: RequestMethod.ALL,
pathRegex: pathToRegexp(addLeadingSlash(route)),
};
}
return {
requestMethod: route.method,
pathRegex: pathToRegexp(addLeadingSlash(route.path)),
};
},
);
const exclude = options?.exclude
? mapToExcludeRoute(options.exclude)
: [];
this.config.setGlobalPrefixOptions({
...options,
exclude,

View File

@@ -21,6 +21,7 @@ import { Injector } from './injector/injector';
import { InstanceLoader } from './injector/instance-loader';
import { GraphInspector } from './inspector/graph-inspector';
import { NoopGraphInspector } from './inspector/noop-graph-inspector';
import { UuidFactory, UuidFactoryMode } from './inspector/uuid-factory';
import { MetadataScanner } from './metadata-scanner';
import { NestApplication } from './nest-application';
import { NestApplicationContext } from './nest-application-context';
@@ -195,6 +196,10 @@ export class NestFactoryStatic {
options: NestApplicationContextOptions = {},
httpServer: HttpServer = null,
) {
UuidFactory.mode = options.snapshot
? UuidFactoryMode.Deterministic
: UuidFactoryMode.Random;
const injector = new Injector({ preview: options.preview });
const instanceLoader = new InstanceLoader(
container,

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/core",
"version": "9.3.0-beta.3",
"version": "9.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -27,16 +27,15 @@
}
},
"dependencies": {
"@node-rs/xxhash": "^1.3.0",
"@nuxtjs/opencollective": "0.3.2",
"fast-safe-stringify": "2.1.1",
"iterare": "1.2.1",
"object-hash": "3.0.0",
"path-to-regexp": "3.2.0",
"tslib": "2.4.1",
"uuid": "9.0.0"
"tslib": "2.5.0"
},
"devDependencies": {
"@nestjs/common": "^9.3.0-beta.3"
"@nestjs/common": "9.3.3"
},
"peerDependencies": {
"@nestjs/common": "^9.0.0",

View File

@@ -20,9 +20,7 @@ export class MethodsReplFn extends ReplFunction {
? Object.getPrototypeOf(this.ctx.app.get(token))
: token?.prototype;
const methods = new Set(
this.metadataScanner.getAllFilteredMethodNames(proto),
);
const methods = this.metadataScanner.getAllMethodNames(proto);
this.ctx.writeToStdout('\n');
this.ctx.writeToStdout(`${clc.green('Methods')}:\n`);

View File

@@ -1,6 +1,11 @@
import { RequestMethod } from '@nestjs/common';
export interface ExcludeRouteMetadata {
/**
* Route path.
*/
path: string;
/**
* Regular expression representing the route path.
*/

View File

@@ -33,18 +33,28 @@ export class PathsExplorer {
? Object.getPrototypeOf(instance)
: prototype;
return this.metadataScanner.scanFromPrototype<Controller, RouteDefinition>(
instance,
instancePrototype,
method => this.exploreMethodMetadata(instance, instancePrototype, method),
);
return this.metadataScanner
.getAllMethodNames(instancePrototype)
.reduce((acc, method) => {
const route = this.exploreMethodMetadata(
instance,
instancePrototype,
method,
);
if (route) {
acc.push(route);
}
return acc;
}, []);
}
public exploreMethodMetadata(
instance: Controller,
prototype: object,
methodName: string,
): RouteDefinition {
): RouteDefinition | null {
const instanceCallback = instance[methodName];
const prototypeCallback = prototype[methodName];
const routePath = Reflect.getMetadata(PATH_METADATA, prototypeCallback);

View File

@@ -13,8 +13,7 @@ import {
import * as pathToRegexp from 'path-to-regexp';
import { ApplicationConfig } from '../application-config';
import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception';
import { GuardsConsumer } from '../guards/guards-consumer';
import { GuardsContextCreator } from '../guards/guards-context-creator';
import { GuardsConsumer, GuardsContextCreator } from '../guards';
import { ContextIdFactory } from '../helpers/context-id-factory';
import { ExecutionContextHost } from '../helpers/execution-context-host';
import {
@@ -27,16 +26,17 @@ import { NestContainer } from '../injector/container';
import { Injector } from '../injector/injector';
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
import { Module } from '../injector/module';
import {
InterceptorsConsumer,
InterceptorsContextCreator,
} from '../interceptors';
import { GraphInspector } from '../inspector/graph-inspector';
import {
Entrypoint,
HttpEntrypointMetadata,
} from '../inspector/interfaces/entrypoint.interface';
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
import { MetadataScanner } from '../metadata-scanner';
import { PipesConsumer } from '../pipes/pipes-consumer';
import { PipesContextCreator } from '../pipes/pipes-context-creator';
import { PipesConsumer, PipesContextCreator } from '../pipes';
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
import { PathsExplorer } from './paths-explorer';
@@ -355,13 +355,16 @@ export class RouterExplorer {
) {
const { instance } = instanceWrapper;
const collection = moduleRef.controllers;
const isTreeDurable = instanceWrapper.isDependencyTreeDurable();
return async <TRequest extends Record<any, any>, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => {
try {
const contextId = this.getContextId(req);
const contextId = this.getContextId(req, isTreeDurable);
const contextInstance = await this.injector.loadPerContext(
instance,
moduleRef,
@@ -397,6 +400,7 @@ export class RouterExplorer {
private getContextId<T extends Record<any, unknown> = any>(
request: T,
isTreeDurable: boolean,
): ContextId {
const contextId = ContextIdFactory.getByRequest(request);
if (!request[REQUEST_CONTEXT_ID as any]) {
@@ -406,10 +410,9 @@ export class RouterExplorer {
writable: false,
configurable: false,
});
this.container.registerRequestProvider(
contextId.getParent ? contextId.payload : request,
contextId,
);
const requestProviderValue = isTreeDurable ? contextId.payload : request;
this.container.registerRequestProvider(requestProviderValue, contextId);
}
return contextId;
}

View File

@@ -1,4 +1,5 @@
import { RequestMethod } from '@nestjs/common';
import { addLeadingSlash } from '@nestjs/common/utils/shared.utils';
import { ExcludeRouteMetadata } from '../interfaces/exclude-route-metadata.interface';
export const isRequestMethodAll = (method: RequestMethod) => {
@@ -15,7 +16,7 @@ export function isRouteExcluded(
isRequestMethodAll(route.requestMethod) ||
route.requestMethod === requestMethod
) {
return route.pathRegex.exec(path);
return route.pathRegex.exec(addLeadingSlash(path));
}
return false;
});

View File

@@ -23,10 +23,11 @@ import {
PipeTransform,
Scope,
ValueProvider,
Controller,
Injectable,
Type,
} from '@nestjs/common/interfaces';
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import {
isFunction,
isNil,
@@ -50,9 +51,10 @@ import { NestContainer } from './injector/container';
import { InstanceWrapper } from './injector/instance-wrapper';
import { InternalCoreModuleFactory } from './injector/internal-core-module/internal-core-module-factory';
import { Module } from './injector/module';
import { DeterministicUuidRegistry } from './inspector/deterministic-uuid-registry';
import { GraphInspector } from './inspector/graph-inspector';
import { UuidFactory } from './inspector/uuid-factory';
import { MetadataScanner } from './metadata-scanner';
import { ParamsMetadata } from './helpers/interfaces/params-metadata.interface';
interface ApplicationProviderWrapper {
moduleKey: string;
@@ -249,14 +251,21 @@ export class DependenciesScanner {
metadataKey,
component,
);
const methodInjectables = this.metadataScanner.scanFromPrototype<
Type,
{ methodKey: string; metadata: Type<Injectable>[] }
>(
null,
component.prototype,
this.reflectKeyMetadata.bind(this, component, metadataKey),
);
const methodInjectables = this.metadataScanner
.getAllMethodNames(component.prototype)
.reduce((acc, method) => {
const methodInjectable = this.reflectKeyMetadata(
component,
metadataKey,
method,
);
if (methodInjectable) {
acc.push(methodInjectable);
}
return acc;
}, []);
controllerInjectables.forEach(injectable =>
this.insertInjectable(
@@ -266,17 +275,17 @@ export class DependenciesScanner {
ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey],
),
);
methodInjectables.forEach(({ methodKey, metadata }) =>
metadata.forEach(injectable =>
methodInjectables.forEach(methodInjectable => {
methodInjectable.metadata.forEach(injectable =>
this.insertInjectable(
injectable,
token,
component,
ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey],
methodKey,
methodInjectable.methodKey,
),
),
);
);
});
}
public reflectParamInjectables(
@@ -284,28 +293,24 @@ export class DependenciesScanner {
token: string,
metadataKey: string,
) {
const paramsMetadata = this.metadataScanner.scanFromPrototype<
Type,
{
methodKey: string;
metadata: Record<
string,
{
index: number;
data: unknown;
pipes: Array<Type<PipeTransform> | PipeTransform>;
}
>;
}
>(null, component.prototype, methodKey => {
const metadata = Reflect.getMetadata(metadataKey, component, methodKey);
const paramsMethods = this.metadataScanner.getAllMethodNames(
component.prototype,
);
paramsMethods.forEach(methodKey => {
const metadata: Record<
string,
{
index: number;
data: unknown;
pipes: Array<Type<PipeTransform> | PipeTransform>;
}
> = Reflect.getMetadata(metadataKey, component, methodKey);
if (!metadata) {
return;
}
return { methodKey, metadata };
});
paramsMetadata.forEach(({ methodKey, metadata }) => {
const params = Object.values(metadata);
params
.map(item => item.pipes)
@@ -326,7 +331,7 @@ export class DependenciesScanner {
component: Type<Injectable>,
key: string,
methodKey: string,
) {
): { methodKey: string; metadata: any } | undefined {
let prototype = component.prototype;
do {
const descriptor = Reflect.getOwnPropertyDescriptor(prototype, methodKey);
@@ -410,7 +415,7 @@ export class DependenciesScanner {
if (!providersKeys.includes(type as string)) {
return this.container.addProvider(provider as any, token);
}
const uuid = DeterministicUuidRegistry.get(type.toString());
const uuid = UuidFactory.get(type.toString());
const providerToken = `${type as string} (UUID: ${uuid})`;
let scope = (provider as ClassProvider | FactoryProvider).scope;

View File

@@ -23,7 +23,11 @@ describe('ApplicationConfig', () => {
it('should set global path options', () => {
const options: GlobalPrefixOptions<ExcludeRouteMetadata> = {
exclude: [
{ pathRegex: new RegExp(/health/), requestMethod: RequestMethod.GET },
{
path: '/health',
pathRegex: new RegExp(/health/),
requestMethod: RequestMethod.GET,
},
],
};
appConfig.setGlobalPrefixOptions(options);

View File

@@ -39,7 +39,68 @@ describe('InstanceWrapper', () => {
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
describe('when request scoped durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
describe('when request scoped explicit non durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: false,
});
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
describe('when default', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({});
expect(wrapper.isDependencyTreeStatic()).to.be.true;
});
});
describe('when statically scoped', () => {
describe('dependencies, properties, enhancers', () => {
describe('dependecies non static, properties static, enhancers static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
describe('dependecies static, properties non static, enhancers static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(0, new InstanceWrapper());
wrapper.addPropertiesMetadata(
'key1',
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
describe('dependecies static, properties static, enhancers non static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(0, new InstanceWrapper());
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addEnhancerMetadata(
new InstanceWrapper({ scope: Scope.REQUEST }),
);
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
});
describe('dependencies', () => {
describe('when each is static', () => {
it('should return true', () => {
@@ -116,8 +177,157 @@ describe('InstanceWrapper', () => {
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when request scoped and non durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when request scoped and explicit non durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: false,
});
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when default scope', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when statically scoped', () => {
describe('dependencies, properties, enhancers', () => {
describe('dependecies non durable, properties non durable, enhancers durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addCtorMetadata(1, new InstanceWrapper());
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('dependecies non durable, properties durable, enhancers durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addCtorMetadata(1, new InstanceWrapper());
wrapper.addPropertiesMetadata(
'key1',
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('dependecies non durable, properties durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addCtorMetadata(1, new InstanceWrapper());
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addPropertiesMetadata(
'key2',
new InstanceWrapper({ scope: Scope.REQUEST, durable: true }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('properties durable, enhancers non durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addPropertiesMetadata(
'key2',
new InstanceWrapper({ scope: Scope.REQUEST, durable: true }),
);
wrapper.addEnhancerMetadata(
new InstanceWrapper({ scope: Scope.REQUEST }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('dependencies durable, enhancers non durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST, durable: true }),
);
wrapper.addEnhancerMetadata(
new InstanceWrapper({ scope: Scope.REQUEST }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
});
describe('dependencies', () => {
describe('when wrapper is non durable and dependecy is static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({ scope: Scope.REQUEST });
wrapper.addCtorMetadata(0, new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when wrapper is durable and dependecy is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addCtorMetadata(0, new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when wrapper is non durable and dependecy is durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST, durable: true }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when wrapper is durable and dependecy is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addCtorMetadata(0, new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when wrapper is durable and dependecy is non durable', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addCtorMetadata(
0,
new InstanceWrapper({ scope: Scope.REQUEST }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when each is static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
@@ -174,6 +384,58 @@ describe('InstanceWrapper', () => {
});
});
describe('properties', () => {
describe('when wrapper is non durable and dependecy is static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({ scope: Scope.REQUEST });
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when wrapper is durable and dependecy is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when wrapper is non durable and dependecy is durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
wrapper.addPropertiesMetadata(
'key1',
new InstanceWrapper({ scope: Scope.REQUEST, durable: true }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when wrapper is durable and dependecy is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when wrapper is durable and dependecy is non durable', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addPropertiesMetadata(
'key1',
new InstanceWrapper({ scope: Scope.REQUEST }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when each is static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
@@ -221,6 +483,56 @@ describe('InstanceWrapper', () => {
});
});
describe('enhancers', () => {
describe('when wrapper is non durable and dependecy is static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({ scope: Scope.REQUEST });
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when wrapper is durable and dependecy is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when wrapper is non durable and dependecy is durable', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
wrapper.addEnhancerMetadata(
new InstanceWrapper({ scope: Scope.REQUEST, durable: true }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.false;
});
});
describe('when wrapper is durable and dependecy is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when wrapper is durable and dependecy is non durable', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
durable: true,
});
wrapper.addEnhancerMetadata(
new InstanceWrapper({ scope: Scope.REQUEST }),
);
expect(wrapper.isDependencyTreeDurable()).to.be.true;
});
});
describe('when each is static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();

View File

@@ -1,6 +1,4 @@
import { expect } from 'chai';
import stringify from 'fast-safe-stringify';
import * as hash from 'object-hash';
import * as sinon from 'sinon';
import { ModuleTokenFactory } from '../../injector/module-token-factory';
@@ -16,30 +14,20 @@ describe('ModuleTokenFactory', () => {
class Module {}
it('should return expected token', () => {
const type = Module;
const token = factory.create(type, undefined);
expect(token).to.be.deep.eq(
hash({
id: moduleId,
module: Module.name,
dynamic: '',
}),
);
const token1 = factory.create(type, undefined);
const token2 = factory.create(type, undefined);
expect(token1).to.be.deep.eq(token2);
});
it('should include dynamic metadata', () => {
const type = Module;
const token = factory.create(type, {
const token1 = factory.create(type, {
providers: [{}],
} as any);
const token2 = factory.create(type, {
providers: [{}],
} as any);
expect(token).to.be.deep.eq(
hash({
id: moduleId,
module: Module.name,
dynamic: stringify({
providers: [{}],
}),
}),
);
expect(token1).to.be.deep.eq(token2);
});
});
describe('getModuleName', () => {
@@ -48,18 +36,18 @@ describe('ModuleTokenFactory', () => {
expect(factory.getModuleName(metatype as any)).to.be.eql(metatype.name);
});
});
describe('getDynamicMetadataToken', () => {
describe('getStringifiedOpaqueToken', () => {
describe('when metadata exists', () => {
it('should return hash', () => {
const metadata = { providers: ['', {}] };
expect(factory.getDynamicMetadataToken(metadata as any)).to.be.eql(
expect(factory.getStringifiedOpaqueToken(metadata as any)).to.be.eql(
JSON.stringify(metadata),
);
});
it('should return hash with class', () => {
class Provider {}
const metadata = { providers: [Provider], exports: [Provider] };
expect(factory.getDynamicMetadataToken(metadata)).to.be.eql(
expect(factory.getStringifiedOpaqueToken(metadata)).to.be.eql(
'{"providers":["Provider"],"exports":["Provider"]}',
);
});
@@ -69,10 +57,8 @@ describe('ModuleTokenFactory', () => {
useValue: function Provider() {},
};
const metadata = { providers: [provider] };
expect(factory.getDynamicMetadataToken(metadata)).to.be.eql(
`{"providers":[{"provide":"ProvideValue","useValue":"${hash(
provider.useValue.toString(),
)}"}]}`,
expect(factory.getStringifiedOpaqueToken(metadata)).to.be.eql(
`{"providers":[{"provide":"ProvideValue","useValue":"${provider.useValue.toString()}"}]}`,
);
});
it('should serialize symbols in a dynamic metadata object', () => {
@@ -89,14 +75,14 @@ describe('ModuleTokenFactory', () => {
],
};
expect(factory.getDynamicMetadataToken(metadata)).to.be.eql(
expect(factory.getStringifiedOpaqueToken(metadata)).to.be.eql(
'{"providers":[{"provide":"Symbol(a)","useValue":"a"},{"provide":"Symbol(b)","useValue":"b"}]}',
);
});
});
describe('when metadata does not exist', () => {
it('should return empty string', () => {
expect(factory.getDynamicMetadataToken(undefined)).to.be.eql('');
expect(factory.getStringifiedOpaqueToken(undefined)).to.be.eql('');
});
});
});

View File

@@ -30,12 +30,32 @@ describe('MetadataScanner', () => {
}
it('should return only methods', () => {
const methods = scanner.scanFromPrototype(
const methods = scanner.getAllMethodNames(Test.prototype);
expect(methods).to.eql(['test', 'test2', 'testParent', 'testParent2']);
});
it('should return the same instance for the same prototype', () => {
const methods1 = scanner.getAllMethodNames(Test.prototype);
const methods2 = scanner.getAllMethodNames(Test.prototype);
expect(methods1 === methods2).to.eql(true);
});
it('should keep compatibility with older methods', () => {
const methods1 = scanner.getAllMethodNames(Test.prototype).map(m => m[0]);
const methods2 = scanner.scanFromPrototype(
new Test(),
Test.prototype,
a => a,
r => r[0],
);
expect(methods).to.eql(['test', 'test2', 'testParent', 'testParent2']);
expect(methods1).to.eql(methods2);
const methods3 = scanner.getAllMethodNames(Test.prototype);
const methods4 = [
...new Set(scanner.getAllFilteredMethodNames(Test.prototype)),
];
expect(methods3).to.eql(methods4);
});
});
});

View File

@@ -1,7 +1,9 @@
import { expect } from 'chai';
import { Controller, Get, RequestMethod, Version } from '../../../common';
import { ApplicationConfig } from '../../application-config';
import { NestContainer } from '../../injector/container';
import { MiddlewareBuilder } from '../../middleware/builder';
import { RouteInfoPathExtractor } from '../../middleware/route-info-path-extractor';
import { RoutesMapper } from '../../middleware/routes-mapper';
import { NoopHttpAdapter } from './../utils/noop-adapter.spec';
@@ -10,9 +12,11 @@ describe('MiddlewareBuilder', () => {
beforeEach(() => {
const container = new NestContainer();
const appConfig = new ApplicationConfig();
builder = new MiddlewareBuilder(
new RoutesMapper(container),
new NoopHttpAdapter({}),
new RouteInfoPathExtractor(appConfig),
);
});
describe('apply', () => {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { RoutePathFactory } from '@nestjs/core/router/route-path-factory';
import { RouteInfoPathExtractor } from '@nestjs/core/middleware/route-info-path-extractor';
import * as chai from 'chai';
import { expect } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
@@ -47,15 +47,21 @@ describe('MiddlewareModule', () => {
beforeEach(() => {
const container = new NestContainer();
const appConfig = new ApplicationConfig();
graphInspector = new GraphInspector(container);
middlewareModule = new MiddlewareModule(new RoutePathFactory(appConfig));
middlewareModule = new MiddlewareModule();
middlewareModule['routerExceptionFilter'] = new RouterExceptionFilters(
new NestContainer(),
appConfig,
new NoopHttpAdapter({}),
);
middlewareModule['routeInfoPathExtractor'] = new RouteInfoPathExtractor(
appConfig,
);
middlewareModule['routerExceptionFilter'] = new RouterExceptionFilters(
container,
appConfig,
new NoopHttpAdapter({}),
);
middlewareModule['config'] = appConfig;
middlewareModule['graphInspector'] = graphInspector;
});
@@ -86,6 +92,7 @@ describe('MiddlewareModule', () => {
new MiddlewareBuilder(
(middlewareModule as any).routesMapper,
undefined,
new RouteInfoPathExtractor(new ApplicationConfig()),
),
),
).to.be.true;

View File

@@ -0,0 +1,185 @@
import { RequestMethod, VersioningType } from '@nestjs/common';
import { ApplicationConfig } from '@nestjs/core';
import { mapToExcludeRoute } from '@nestjs/core/middleware/utils';
import { expect } from 'chai';
import { RouteInfoPathExtractor } from './../../middleware/route-info-path-extractor';
describe('RouteInfoPathExtractor', () => {
describe('extractPathsFrom', () => {
let appConfig: ApplicationConfig;
let routeInfoPathExtractor: RouteInfoPathExtractor;
beforeEach(() => {
appConfig = new ApplicationConfig();
appConfig.enableVersioning({
type: VersioningType.URI,
});
routeInfoPathExtractor = new RouteInfoPathExtractor(appConfig);
});
it(`should return correct paths`, () => {
expect(
routeInfoPathExtractor.extractPathsFrom({
path: '*',
method: RequestMethod.ALL,
}),
).to.eql(['/*']);
expect(
routeInfoPathExtractor.extractPathsFrom({
path: '*',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/v1/*']);
});
it(`should return correct paths when set global prefix`, () => {
Reflect.set(routeInfoPathExtractor, 'prefixPath', '/api');
expect(
routeInfoPathExtractor.extractPathsFrom({
path: '*',
method: RequestMethod.ALL,
}),
).to.eql(['/api/*']);
expect(
routeInfoPathExtractor.extractPathsFrom({
path: '*',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/api/v1/*']);
});
it(`should return correct paths when set global prefix and global prefix options`, () => {
Reflect.set(routeInfoPathExtractor, 'prefixPath', '/api');
Reflect.set(
routeInfoPathExtractor,
'excludedGlobalPrefixRoutes',
mapToExcludeRoute(['foo']),
);
expect(
routeInfoPathExtractor.extractPathsFrom({
path: '*',
method: RequestMethod.ALL,
}),
).to.eql(['/api/*', '/foo']);
expect(
routeInfoPathExtractor.extractPathsFrom({
path: '*',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/api/v1/*', '/v1/foo']);
expect(
routeInfoPathExtractor.extractPathsFrom({
path: 'foo',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/v1/foo']);
expect(
routeInfoPathExtractor.extractPathsFrom({
path: 'bar',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql(['/api/v1/bar']);
});
});
describe('extractPathFrom', () => {
let appConfig: ApplicationConfig;
let routeInfoPathExtractor: RouteInfoPathExtractor;
beforeEach(() => {
appConfig = new ApplicationConfig();
appConfig.enableVersioning({
type: VersioningType.URI,
});
routeInfoPathExtractor = new RouteInfoPathExtractor(appConfig);
});
it(`should return correct path`, () => {
expect(
routeInfoPathExtractor.extractPathFrom({
path: '*',
method: RequestMethod.ALL,
}),
).to.eql('/*');
expect(
routeInfoPathExtractor.extractPathFrom({
path: '*',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql('/v1/*');
});
it(`should return correct path when set global prefix`, () => {
Reflect.set(routeInfoPathExtractor, 'prefixPath', '/api');
expect(
routeInfoPathExtractor.extractPathFrom({
path: '*',
method: RequestMethod.ALL,
}),
).to.eql('/*');
expect(
routeInfoPathExtractor.extractPathFrom({
path: '*',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql('/api/v1/*');
});
it(`should return correct path when set global prefix and global prefix options`, () => {
Reflect.set(routeInfoPathExtractor, 'prefixPath', '/api');
Reflect.set(
routeInfoPathExtractor,
'excludedGlobalPrefixRoutes',
mapToExcludeRoute(['foo']),
);
expect(
routeInfoPathExtractor.extractPathFrom({
path: '*',
method: RequestMethod.ALL,
}),
).to.eql('/*');
expect(
routeInfoPathExtractor.extractPathFrom({
path: '*',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql('/api/v1/*');
expect(
routeInfoPathExtractor.extractPathFrom({
path: 'foo',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql('/v1/foo');
expect(
routeInfoPathExtractor.extractPathFrom({
path: 'bar',
method: RequestMethod.ALL,
version: '1',
}),
).to.eql('/api/v1/bar');
});
});
});

View File

@@ -1,4 +1,5 @@
import { RequestMethod, Type } from '@nestjs/common';
import { addLeadingSlash } from '@nestjs/common/utils/shared.utils';
import { expect } from 'chai';
import * as sinon from 'sinon';
import {
@@ -10,6 +11,7 @@ import {
mapToExcludeRoute,
} from '../../middleware/utils';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
import * as pathToRegexp from 'path-to-regexp';
describe('middleware utils', () => {
const noopAdapter = new NoopHttpAdapter({});
@@ -17,6 +19,27 @@ describe('middleware utils', () => {
class Test {}
function fnMiddleware(req, res, next) {}
describe('mapToExcludeRoute', () => {
it('should return exclude route metadata', () => {
const stringRoute = 'foo';
const routeInfo = {
path: 'bar',
method: RequestMethod.GET,
};
expect(mapToExcludeRoute([stringRoute, routeInfo])).to.eql([
{
path: stringRoute,
requestMethod: RequestMethod.ALL,
pathRegex: pathToRegexp(addLeadingSlash(stringRoute)),
},
{
path: routeInfo.path,
requestMethod: routeInfo.method,
pathRegex: pathToRegexp(addLeadingSlash(routeInfo.path)),
},
]);
});
});
describe('filterMiddleware', () => {
let middleware: any[];
beforeEach(() => {

View File

@@ -6,6 +6,7 @@ import { Injector } from '../injector/injector';
import { InstanceLoader } from '../injector/instance-loader';
import { GraphInspector } from '../inspector/graph-inspector';
import { NestApplicationContext } from '../nest-application-context';
import * as sinon from 'sinon';
describe('NestApplicationContext', () => {
class A {}
@@ -49,6 +50,54 @@ describe('NestApplicationContext', () => {
return applicationContext;
}
describe('listenToShutdownSignals', () => {
it('shutdown process should not be interrupted by another handler', async () => {
const signal = 'SIGTERM';
let processUp = true;
let promisesResolved = false;
const applicationContext = await testHelper(A, Scope.DEFAULT);
applicationContext.enableShutdownHooks([signal]);
const waitProcessDown = new Promise(resolve => {
const shutdownCleanupRef = applicationContext['shutdownCleanupRef'];
const handler = () => {
if (
!process
.listeners(signal)
.find(handler => handler == shutdownCleanupRef)
) {
processUp = false;
process.removeListener(signal, handler);
resolve(undefined);
}
return undefined;
};
process.on(signal, handler);
});
// add some third party handler
process.on(signal, signal => {
// do some work
process.kill(process.pid, signal);
});
const hookStub = sinon
.stub(applicationContext as any, 'callShutdownHook')
.callsFake(async () => {
// run some async code
await new Promise(resolve => setImmediate(() => resolve(undefined)));
if (processUp) {
promisesResolved = true;
}
});
process.kill(process.pid, signal);
await waitProcessDown;
hookStub.restore();
expect(processUp).to.be.false;
expect(promisesResolved).to.be.true;
});
});
describe('get', () => {
describe('when scope = DEFAULT', () => {
it('should get value with function injection key', async () => {

View File

@@ -1,8 +1,10 @@
import { RequestMethod } from '@nestjs/common';
import { expect } from 'chai';
import { ApplicationConfig } from '../application-config';
import { NestContainer } from '../injector/container';
import { GraphInspector } from '../inspector/graph-inspector';
import { NestApplication } from '../nest-application';
import { mapToExcludeRoute } from './../middleware/utils';
import { NoopHttpAdapter } from './utils/noop-adapter.spec';
describe('NestApplication', () => {
@@ -54,4 +56,24 @@ describe('NestApplication', () => {
).to.equal(1);
});
});
describe('Global Prefix', () => {
it('should get correct global prefix options', () => {
const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
const instance = new NestApplication(
container,
new NoopHttpAdapter({}),
applicationConfig,
new GraphInspector(container),
{},
);
const excludeRoute = ['foo', { path: 'bar', method: RequestMethod.GET }];
instance.setGlobalPrefix('api', {
exclude: excludeRoute,
});
expect(applicationConfig.getGlobalPrefixOptions()).to.eql({
exclude: mapToExcludeRoute(excludeRoute),
});
});
});
});

View File

@@ -247,6 +247,7 @@ describe('RoutePathFactory', () => {
sinon.stub(applicationConfig, 'getGlobalPrefixOptions').returns({
exclude: [
{
path: '/random',
pathRegex: pathToRegexp('/random'),
requestMethod: RequestMethod.ALL,
},
@@ -265,6 +266,7 @@ describe('RoutePathFactory', () => {
sinon.stub(applicationConfig, 'getGlobalPrefixOptions').returns({
exclude: [
{
path: '/cats',
pathRegex: pathToRegexp('/cats'),
requestMethod: RequestMethod.ALL,
},
@@ -283,6 +285,7 @@ describe('RoutePathFactory', () => {
sinon.stub(applicationConfig, 'getGlobalPrefixOptions').returns({
exclude: [
{
path: '/cats',
pathRegex: pathToRegexp('/cats'),
requestMethod: RequestMethod.GET,
},

View File

@@ -42,13 +42,13 @@ let kafkaPackage: any = {};
export class ClientKafka extends ClientProxy {
protected logger = new Logger(ClientKafka.name);
protected client: Kafka = null;
protected consumer: Consumer = null;
protected producer: Producer = null;
protected parser: KafkaParser = null;
protected client: Kafka | null = null;
protected consumer: Consumer | null = null;
protected producer: Producer | null = null;
protected parser: KafkaParser | null = null;
protected initialized: Promise<void> | null = null;
protected responsePatterns: string[] = [];
protected consumerAssignments: { [key: string]: number } = {};
protected brokers: string[] | BrokersFunction;
protected clientId: string;
protected groupId: string;
@@ -94,46 +94,56 @@ export class ClientKafka extends ClientProxy {
this.consumer && (await this.consumer.disconnect());
this.producer = null;
this.consumer = null;
this.initialized = null;
this.client = null;
}
public async connect(): Promise<Producer> {
if (this.client) {
return this.producer;
if (this.initialized) {
return this.initialized.then(() => this.producer);
}
this.client = this.createClient();
this.initialized = new Promise(async (resolve, reject) => {
try {
this.client = this.createClient();
if (!this.producerOnlyMode) {
const partitionAssigners = [
(
config: ConstructorParameters<typeof KafkaReplyPartitionAssigner>[1],
) => new KafkaReplyPartitionAssigner(this, config),
] as any[];
if (!this.producerOnlyMode) {
const partitionAssigners = [
(
config: ConstructorParameters<
typeof KafkaReplyPartitionAssigner
>[1],
) => new KafkaReplyPartitionAssigner(this, config),
];
const consumerOptions = Object.assign(
{
partitionAssigners,
},
this.options.consumer || {},
{
groupId: this.groupId,
},
);
const consumerOptions = Object.assign(
{
partitionAssigners,
},
this.options.consumer || {},
{
groupId: this.groupId,
},
);
this.consumer = this.client.consumer(consumerOptions);
// set member assignments on join and rebalance
this.consumer.on(
this.consumer.events.GROUP_JOIN,
this.setConsumerAssignments.bind(this),
);
await this.consumer.connect();
await this.bindTopics();
}
this.consumer = this.client.consumer(consumerOptions);
// set member assignments on join and rebalance
this.consumer.on(
this.consumer.events.GROUP_JOIN,
this.setConsumerAssignments.bind(this),
);
await this.consumer.connect();
await this.bindTopics();
}
this.producer = this.client.producer(this.options.producer || {});
await this.producer.connect();
this.producer = this.client.producer(this.options.producer || {});
await this.producer.connect();
return this.producer;
resolve();
} catch (err) {
reject(err);
}
});
return this.initialized.then(() => this.producer);
}
public async bindTopics(): Promise<void> {

View File

@@ -3,21 +3,29 @@ import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import { EventEmitter } from 'events';
import { EmptyError, fromEvent, lastValueFrom, merge, Observable } from 'rxjs';
import { first, map, retryWhen, scan, share, switchMap } from 'rxjs/operators';
import {
EmptyError,
firstValueFrom,
fromEvent,
merge,
Observable,
ReplaySubject,
} from 'rxjs';
import { first, map, retryWhen, scan, skip, switchMap } from 'rxjs/operators';
import {
CONNECT_EVENT,
CONNECT_FAILED_EVENT,
DISCONNECTED_RMQ_MESSAGE,
DISCONNECT_EVENT,
ERROR_EVENT,
RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT,
RQM_DEFAULT_NOACK,
RQM_DEFAULT_NO_ASSERT,
RQM_DEFAULT_PERSISTENT,
RQM_DEFAULT_PREFETCH_COUNT,
RQM_DEFAULT_QUEUE,
RQM_DEFAULT_QUEUE_OPTIONS,
RQM_DEFAULT_URL,
RQM_DEFAULT_NO_ASSERT,
} from '../constants';
import { RmqUrl } from '../external/rmq-url.interface';
import { ReadPacket, RmqOptions, WritePacket } from '../interfaces';
@@ -25,18 +33,30 @@ import { RmqRecord } from '../record-builders';
import { RmqRecordSerializer } from '../serializers/rmq-record.serializer';
import { ClientProxy } from './client-proxy';
// import type {
// AmqpConnectionManager,
// ChannelWrapper,
// } from 'amqp-connection-manager';
// import type { Channel, ConsumeMessage } from 'amqplib';
type Channel = any;
type ChannelWrapper = any;
type ConsumeMessage = any;
type AmqpConnectionManager = any;
let rqmPackage: any = {};
const REPLY_QUEUE = 'amq.rabbitmq.reply-to';
export class ClientRMQ extends ClientProxy {
protected readonly logger = new Logger(ClientProxy.name);
protected connection$: ReplaySubject<any>;
protected connection: Promise<any>;
protected client: any = null;
protected channel: any = null;
protected client: AmqpConnectionManager = null;
protected channel: ChannelWrapper = null;
protected urls: string[] | RmqUrl[];
protected queue: string;
protected queueOptions: any;
protected queueOptions: Record<string, any>;
protected responseEmitter: EventEmitter;
protected replyQueue: string;
protected persistent: boolean;
@@ -75,42 +95,44 @@ export class ClientRMQ extends ClientProxy {
public connect(): Promise<any> {
if (this.client) {
return this.connection;
return this.convertConnectionToPromise();
}
this.client = this.createClient();
this.handleError(this.client);
this.handleDisconnectError(this.client);
const connect$ = this.connect$(this.client);
this.connection = lastValueFrom(
this.mergeDisconnectEvent(this.client, connect$).pipe(
switchMap(() => this.createChannel()),
share(),
),
).catch(err => {
if (err instanceof EmptyError) {
return;
}
throw err;
});
this.responseEmitter = new EventEmitter();
this.responseEmitter.setMaxListeners(0);
return this.connection;
const connect$ = this.connect$(this.client);
const withDisconnect$ = this.mergeDisconnectEvent(
this.client,
connect$,
).pipe(switchMap(() => this.createChannel()));
const withReconnect$ = fromEvent(this.client, CONNECT_EVENT).pipe(skip(1));
const source$ = merge(withDisconnect$, withReconnect$);
this.connection$ = new ReplaySubject(1);
source$.subscribe(this.connection$);
return this.convertConnectionToPromise();
}
public createChannel(): Promise<void> {
return new Promise(resolve => {
this.channel = this.client.createChannel({
json: false,
setup: (channel: any) => this.setupChannel(channel, resolve),
setup: (channel: Channel) => this.setupChannel(channel, resolve),
});
});
}
public createClient<T = any>(): T {
public createClient(): AmqpConnectionManager {
const socketOptions = this.getOptionsProp(this.options, 'socketOptions');
return rqmPackage.connect(this.urls, {
connectionOptions: socketOptions,
}) as T;
});
}
public mergeDisconnectEvent<T = any>(
@@ -119,7 +141,7 @@ export class ClientRMQ extends ClientProxy {
): Observable<T> {
const eventToError = (eventType: string) =>
fromEvent(instance, eventType).pipe(
map((err: any) => {
map((err: unknown) => {
throw err;
}),
);
@@ -138,10 +160,23 @@ export class ClientRMQ extends ClientProxy {
),
),
);
// If we ever decide to propagate all disconnect errors & re-emit them through
// the "connection" stream then comment out "first()" operator.
return merge(source$, disconnect$, connectFailed$).pipe(first());
}
public async setupChannel(channel: any, resolve: Function) {
public async convertConnectionToPromise() {
try {
return await firstValueFrom(this.connection$);
} catch (err) {
if (err instanceof EmptyError) {
return;
}
throw err;
}
}
public async setupChannel(channel: Channel, resolve: Function) {
const prefetchCount =
this.getOptionsProp(this.options, 'prefetchCount') ||
RQM_DEFAULT_PREFETCH_COUNT;
@@ -153,18 +188,15 @@ export class ClientRMQ extends ClientProxy {
await channel.assertQueue(this.queue, this.queueOptions);
}
await channel.prefetch(prefetchCount, isGlobalPrefetchCount);
this.responseEmitter = new EventEmitter();
this.responseEmitter.setMaxListeners(0);
await this.consumeChannel(channel);
resolve();
}
public async consumeChannel(channel: any) {
public async consumeChannel(channel: Channel) {
const noAck = this.getOptionsProp(this.options, 'noAck', RQM_DEFAULT_NOACK);
await channel.consume(
this.replyQueue,
(msg: any) =>
(msg: ConsumeMessage) =>
this.responseEmitter.emit(msg.properties.correlationId, msg),
{
noAck,
@@ -172,16 +204,14 @@ export class ClientRMQ extends ClientProxy {
);
}
public handleError(client: any): void {
public handleError(client: AmqpConnectionManager): void {
client.addListener(ERROR_EVENT, (err: any) => this.logger.error(err));
}
public handleDisconnectError(client: any): void {
public handleDisconnectError(client: AmqpConnectionManager): void {
client.addListener(DISCONNECT_EVENT, (err: any) => {
this.logger.error(DISCONNECTED_RMQ_MESSAGE);
this.logger.error(err);
this.close();
});
}
@@ -231,7 +261,7 @@ export class ClientRMQ extends ClientProxy {
content,
options,
}: {
content: any;
content: Buffer;
options: Record<string, unknown>;
}) =>
this.handleMessage(JSON.parse(content.toString()), options, callback);
@@ -244,17 +274,19 @@ export class ClientRMQ extends ClientProxy {
delete serializedPacket.options;
this.responseEmitter.on(correlationId, listener);
this.channel.sendToQueue(
this.queue,
Buffer.from(JSON.stringify(serializedPacket)),
{
replyTo: this.replyQueue,
persistent: this.persistent,
...options,
headers: this.mergeHeaders(options?.headers),
correlationId,
},
);
this.channel
.sendToQueue(
this.queue,
Buffer.from(JSON.stringify(serializedPacket)),
{
replyTo: this.replyQueue,
persistent: this.persistent,
...options,
headers: this.mergeHeaders(options?.headers),
correlationId,
},
)
.catch(err => callback({ err }));
return () => this.responseEmitter.removeListener(correlationId, listener);
} catch (err) {
callback({ err });

View File

@@ -1,6 +1,6 @@
/*
* Nest @microservices
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

View File

@@ -4,10 +4,10 @@ import { MetadataScanner } from '@nestjs/core/metadata-scanner';
import {
CLIENT_CONFIGURATION_METADATA,
CLIENT_METADATA,
PATTERN_EXTRAS_METADATA,
PATTERN_HANDLER_METADATA,
PATTERN_METADATA,
TRANSPORT_METADATA,
PATTERN_EXTRAS_METADATA,
} from './constants';
import { Transport } from './enums';
import { PatternHandler } from './enums/pattern-handler.enum';
@@ -37,12 +37,10 @@ export class ListenerMetadataExplorer {
public explore(instance: Controller): EventOrMessageListenerDefinition[] {
const instancePrototype = Object.getPrototypeOf(instance);
return this.metadataScanner.scanFromPrototype<
Controller,
EventOrMessageListenerDefinition
>(instance, instancePrototype, method =>
this.exploreMethodMetadata(instancePrototype, method),
);
return this.metadataScanner
.getAllMethodNames(instancePrototype)
.map(method => this.exploreMethodMetadata(instancePrototype, method))
.filter(metadata => metadata);
}
public exploreMethodMetadata(

View File

@@ -14,7 +14,15 @@ import { Module } from '@nestjs/core/injector/module';
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
import { REQUEST_CONTEXT_ID } from '@nestjs/core/router/request/request-constants';
import { connectable, Observable, Subject } from 'rxjs';
import {
forkJoin,
from as fromPromise,
isObservable,
mergeMap,
Observable,
ObservedValueOf,
of,
} from 'rxjs';
import { IClientProxyFactory } from './client/client-proxy-factory';
import { ClientsContainer } from './container';
import { ExceptionFiltersContext } from './context/exception-filters-context';
@@ -111,20 +119,18 @@ export class ListenersController {
defaultCallMetadata,
);
if (isEventHandler) {
const eventHandler: MessageHandler = (...args: unknown[]) => {
const eventHandler: MessageHandler = async (...args: unknown[]) => {
const originalArgs = args;
const [dataOrContextHost] = originalArgs;
if (dataOrContextHost instanceof RequestContextHost) {
args = args.slice(1, args.length);
}
const originalReturnValue = proxy(...args);
const returnedValueWrapper = eventHandler.next?.(
...(originalArgs as Parameters<MessageHandler>),
const returnValue = proxy(...args);
return this.forkJoinHandlersIfAttached(
returnValue,
originalArgs,
eventHandler,
);
returnedValueWrapper?.then(returnedValue =>
this.connectIfStream(returnedValue as Observable<unknown>),
);
return originalReturnValue;
};
return server.addHandler(
pattern,
@@ -143,6 +149,7 @@ export class ListenersController {
moduleKey,
methodKey,
defaultCallMetadata,
isEventHandler,
);
server.addHandler(pattern, asyncHandler, isEventHandler, extras);
});
@@ -174,6 +181,23 @@ export class ListenersController {
);
}
public forkJoinHandlersIfAttached(
currentReturnValue: Promise<unknown> | Observable<unknown>,
originalArgs: unknown[],
handlerRef: MessageHandler,
) {
if (handlerRef.next) {
const returnedValueWrapper = handlerRef.next(
...(originalArgs as Parameters<MessageHandler>),
);
return forkJoin({
current: this.transformToObservable(currentReturnValue),
next: this.transformToObservable(returnedValueWrapper),
});
}
return currentReturnValue;
}
public assignClientsToProperties(instance: Controller | Injectable) {
for (const {
property,
@@ -201,17 +225,20 @@ export class ListenersController {
moduleKey: string,
methodKey: string,
defaultCallMetadata: Record<string, any> = DEFAULT_CALLBACK_METADATA,
isEventHandler = false,
) {
const collection = moduleRef.controllers;
const { instance } = wrapper;
const isTreeDurable = wrapper.isDependencyTreeDurable();
const requestScopedHandler: MessageHandler = async (...args: unknown[]) => {
try {
let contextId: ContextId;
let [dataOrContextHost] = args;
if (dataOrContextHost instanceof RequestContextHost) {
contextId = this.getContextId(dataOrContextHost);
contextId = this.getContextId(dataOrContextHost, isTreeDurable);
args.shift();
} else {
const [data, reqCtx] = args;
@@ -220,13 +247,10 @@ export class ListenersController {
data,
reqCtx as BaseRpcContext,
);
contextId = this.getContextId(request);
this.container.registerRequestProvider(
contextId.getParent ? contextId.payload : request,
contextId,
);
contextId = this.getContextId(request, isTreeDurable);
dataOrContextHost = request;
}
const contextInstance = await this.injector.loadPerContext(
instance,
moduleRef,
@@ -242,14 +266,16 @@ export class ListenersController {
wrapper.id,
defaultCallMetadata,
);
const returnedValueWrapper = requestScopedHandler.next?.(
dataOrContextHost,
...args,
);
returnedValueWrapper?.then(returnedValue =>
this.connectIfStream(returnedValue as Observable<unknown>),
);
return proxy(...args);
const returnValue = proxy(...args);
if (isEventHandler) {
return this.forkJoinHandlersIfAttached(
returnValue,
[dataOrContextHost, ...args],
requestScopedHandler,
);
}
return returnValue;
} catch (err) {
let exceptionFilter = this.exceptionFiltersCache.get(
instance[methodKey],
@@ -270,7 +296,10 @@ export class ListenersController {
return requestScopedHandler;
}
private getContextId<T extends RequestContext = any>(request: T): ContextId {
private getContextId<T extends RequestContext = any>(
request: T,
isTreeDurable: boolean,
): ContextId {
const contextId = ContextIdFactory.getByRequest(request);
if (!request[REQUEST_CONTEXT_ID as any]) {
Object.defineProperty(request, REQUEST_CONTEXT_ID, {
@@ -279,22 +308,32 @@ export class ListenersController {
writable: false,
configurable: false,
});
this.container.registerRequestProvider(
contextId.getParent ? contextId.payload : request,
contextId,
);
const requestProviderValue = isTreeDurable ? contextId.payload : request;
this.container.registerRequestProvider(requestProviderValue, contextId);
}
return contextId;
}
private connectIfStream(source: Observable<unknown>) {
if (!source) {
return;
public transformToObservable<T>(
resultOrDeferred: Observable<T> | Promise<T>,
): Observable<T>;
public transformToObservable<T>(
resultOrDeferred: T,
): never extends Observable<ObservedValueOf<T>>
? Observable<T>
: Observable<ObservedValueOf<T>>;
public transformToObservable(resultOrDeferred: any) {
if (resultOrDeferred instanceof Promise) {
return fromPromise(resultOrDeferred).pipe(
mergeMap(val => (isObservable(val) ? val : of(val))),
);
}
const connectableSource = connectable(source, {
connector: () => new Subject(),
resetOnDisconnect: false,
});
connectableSource.connect();
if (isObservable(resultOrDeferred)) {
return resultOrDeferred;
}
return of(resultOrDeferred);
}
}

View File

@@ -79,7 +79,7 @@ export class MicroservicesModule<
if (!this.listenersController) {
throw new RuntimeException();
}
if (this.appOptions.preview) {
if (this.appOptions?.preview) {
return;
}
const modules = container.getModules();

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/microservices",
"version": "9.3.0-beta.3",
"version": "9.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@microservices)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -18,11 +18,11 @@
},
"dependencies": {
"iterare": "1.2.1",
"tslib": "2.4.1"
"tslib": "2.5.0"
},
"devDependencies": {
"@nestjs/common": "^9.3.0-beta.3",
"@nestjs/core": "^9.3.0-beta.3"
"@nestjs/common": "9.3.3",
"@nestjs/core": "9.3.3"
},
"peerDependencies": {
"@grpc/grpc-js": "*",

View File

@@ -191,7 +191,7 @@ export class ServerKafka extends Server implements CustomTransportStrategy {
}
const response$ = this.transformToObservable(
await handler(packet.data, kafkaContext),
handler(packet.data, kafkaContext),
);
const replayStream$ = new ReplaySubject();
@@ -218,6 +218,8 @@ export class ServerKafka extends Server implements CustomTransportStrategy {
if (err instanceof KafkaRetriableException && !isPromiseResolved) {
isPromiseResolved = true;
reject(err);
} else {
resolve();
}
replayStream$.error(err);
},

View File

@@ -1,5 +1,4 @@
import { isUndefined, isObject } from '@nestjs/common/utils/shared.utils';
import { Observable } from 'rxjs';
import { NATS_DEFAULT_URL, NO_MESSAGE_HANDLER } from '../constants';
import { NatsContext } from '../ctx-host/nats.context';
import { NatsRequestJSONDeserializer } from '../deserializers/nats-request-json.deserializer';
@@ -137,17 +136,28 @@ export class ServerNats extends Server implements CustomTransportStrategy {
status.data && isObject(status.data)
? JSON.stringify(status.data)
: status.data;
if (status.type === 'disconnect' || status.type === 'error') {
this.logger.error(
`NatsError: type: "${status.type}", data: "${data}".`,
);
} else {
const message = `NatsStatus: type: "${status.type}", data: "${data}".`;
if (status.type === 'pingTimer') {
this.logger.debug(message);
} else {
this.logger.log(message);
}
switch (status.type) {
case 'error':
case 'disconnect':
this.logger.error(
`NatsError: type: "${status.type}", data: "${data}".`,
);
break;
case 'pingTimer':
if (this.options.debug) {
this.logger.debug(
`NatsStatus: type: "${status.type}", data: "${data}".`,
);
}
break;
default:
this.logger.log(
`NatsStatus: type: "${status.type}", data: "${data}".`,
);
break;
}
}
}

View File

@@ -11,7 +11,7 @@ import {
Subject,
Subscription,
} from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { catchError, finalize, mergeMap } from 'rxjs/operators';
import { NO_EVENT_HANDLER } from '../constants';
import { BaseRpcContext } from '../ctx-host/base-rpc.context';
import { IncomingRequestDeserializer } from '../deserializers/incoming-request.deserializer';
@@ -133,7 +133,9 @@ export abstract class Server {
: Observable<ObservedValueOf<T>>;
public transformToObservable(resultOrDeferred: any) {
if (resultOrDeferred instanceof Promise) {
return fromPromise(resultOrDeferred);
return fromPromise(resultOrDeferred).pipe(
mergeMap(val => (isObservable(val) ? val : of(val))),
);
}
if (isObservable(resultOrDeferred)) {

View File

@@ -11,7 +11,6 @@ import {
} from '../../external/kafka.interface';
describe('ClientKafka', () => {
// static
const topic = 'test.topic';
const partition = 0;
const replyTopic = 'test.topic.reply';
@@ -25,7 +24,6 @@ describe('ClientKafka', () => {
const heartbeat = async () => {};
const pause = () => () => {};
// message
const message: KafkaMessage = {
key: Buffer.from(key),
offset,
@@ -35,7 +33,6 @@ describe('ClientKafka', () => {
attributes,
};
// deserialized message
const deserializedMessage: any = {
key,
offset,
@@ -47,7 +44,6 @@ describe('ClientKafka', () => {
partition,
};
// payloads
const payload: EachMessagePayload = {
topic,
partition,
@@ -152,26 +148,6 @@ describe('ClientKafka', () => {
pause,
};
const deserializedPayloadError: EachMessagePayload = {
topic,
partition,
message: Object.assign(
{
headers: {
[KafkaHeaders.CORRELATION_ID]: correlationId,
[KafkaHeaders.NEST_ERR]: NO_MESSAGE_HANDLER,
},
},
deserializedMessage,
{
size: 0,
value: null,
},
),
heartbeat,
pause,
};
let client: ClientKafka;
let callback: sinon.SinonSpy;
let connect: sinon.SinonSpy;
@@ -182,7 +158,7 @@ describe('ClientKafka', () => {
let consumerStub: sinon.SinonStub;
let producerStub: sinon.SinonStub;
let createClientStub: sinon.SinonStub;
let kafkaClient;
let kafkaClient: any;
beforeEach(() => {
client = new ClientKafka({});
@@ -322,7 +298,8 @@ describe('ClientKafka', () => {
});
it('should expect the connection to be reused', async () => {
(client as any).client = kafkaClient;
(client as any).initialized = Promise.resolve({});
await client.connect();
expect(createClientStub.calledOnce).to.be.false;
@@ -368,7 +345,8 @@ describe('ClientKafka', () => {
});
it('should expect the connection to be reused', async () => {
(client as any).client = kafkaClient;
(client as any).initialized = Promise.resolve({});
await client.connect();
expect(createClientStub.calledOnce).to.be.false;

View File

@@ -15,7 +15,6 @@ describe('ClientRMQ', function () {
let createClientStub: sinon.SinonStub;
let handleErrorsSpy: sinon.SinonSpy;
let connect$Stub: sinon.SinonStub;
let mergeDisconnectEvent: sinon.SinonStub;
beforeEach(async () => {
client = new ClientRMQ({});
@@ -33,7 +32,7 @@ describe('ClientRMQ', function () {
return this;
},
}));
mergeDisconnectEvent = sinon
sinon
.stub(client, 'mergeDisconnectEvent')
.callsFake((_, source) => source);
});
@@ -173,7 +172,7 @@ describe('ClientRMQ', function () {
const pattern = 'test';
let msg: ReadPacket;
let connectSpy: sinon.SinonSpy,
sendToQueueSpy: sinon.SinonSpy,
sendToQueueStub: sinon.SinonStub,
eventSpy: sinon.SinonSpy;
beforeEach(() => {
@@ -181,10 +180,10 @@ describe('ClientRMQ', function () {
msg = { pattern, data: 'data' };
connectSpy = sinon.spy(client, 'connect');
eventSpy = sinon.spy();
sendToQueueSpy = sinon.spy();
sendToQueueStub = sinon.stub().callsFake(() => ({ catch: sinon.spy() }));
client['channel'] = {
sendToQueue: sendToQueueSpy,
sendToQueue: sendToQueueStub,
};
client['responseEmitter'] = new EventEmitter();
client['responseEmitter'].on(pattern, eventSpy);
@@ -196,15 +195,15 @@ describe('ClientRMQ', function () {
it('should send message to a proper queue', () => {
client['publish'](msg, () => {
expect(sendToQueueSpy.called).to.be.true;
expect(sendToQueueSpy.getCall(0).args[0]).to.be.eql(client['queue']);
expect(sendToQueueStub.called).to.be.true;
expect(sendToQueueStub.getCall(0).args[0]).to.be.eql(client['queue']);
});
});
it('should send buffer from stringified message', () => {
client['publish'](msg, () => {
expect(sendToQueueSpy.called).to.be.true;
expect(sendToQueueSpy.getCall(1).args[1]).to.be.eql(
expect(sendToQueueStub.called).to.be.true;
expect(sendToQueueStub.getCall(1).args[1]).to.be.eql(
Buffer.from(JSON.stringify(msg)),
);
});
@@ -231,7 +230,7 @@ describe('ClientRMQ', function () {
describe('headers', () => {
it('should not generate headers if none are configured', () => {
client['publish'](msg, () => {
expect(sendToQueueSpy.getCall(0).args[2].headers).to.be.undefined;
expect(sendToQueueStub.getCall(0).args[2].headers).to.be.undefined;
});
});
@@ -240,7 +239,7 @@ describe('ClientRMQ', function () {
msg.data = new RmqRecord('data', { headers: requestHeaders });
client['publish'](msg, () => {
expect(sendToQueueSpy.getCall(0).args[2].headers).to.eql(
expect(sendToQueueStub.getCall(0).args[2].headers).to.eql(
requestHeaders,
);
});
@@ -254,7 +253,7 @@ describe('ClientRMQ', function () {
msg.data = new RmqRecord('data', { headers: requestHeaders });
client['publish'](msg, () => {
expect(sendToQueueSpy.getCall(0).args[2].headers).to.eql({
expect(sendToQueueStub.getCall(0).args[2].headers).to.eql({
...staticHeaders,
...requestHeaders,
});
@@ -269,7 +268,7 @@ describe('ClientRMQ', function () {
msg.data = new RmqRecord('data', { headers: requestHeaders });
client['publish'](msg, () => {
expect(sendToQueueSpy.getCall(0).args[2].headers).to.eql(
expect(sendToQueueStub.getCall(0).args[2].headers).to.eql(
requestHeaders,
);
});

View File

@@ -52,17 +52,16 @@ describe('ListenerMetadataExplorer', () => {
instance = new ListenerMetadataExplorer(scanner);
});
describe('explore', () => {
let scanFromPrototype: sinon.SinonSpy;
let getAllMethodNames: sinon.SinonSpy;
beforeEach(() => {
scanFromPrototype = sinon.spy(scanner, 'scanFromPrototype');
getAllMethodNames = sinon.spy(scanner, 'getAllMethodNames');
});
it(`should call "scanFromPrototype" with expected arguments`, () => {
const obj = new Test();
instance.explore(obj);
const { args } = scanFromPrototype.getCall(0);
expect(args[0]).to.be.eql(obj);
expect(args[1]).to.be.eql(Object.getPrototypeOf(obj));
const { args } = getAllMethodNames.getCall(0);
expect(args[0]).to.be.eql(Object.getPrototypeOf(obj));
});
});
describe('exploreMethodMetadata', () => {

View File

@@ -1,11 +1,12 @@
import type { Server } from 'net';
import {
HttpStatus,
InternalServerErrorException,
Logger,
RequestMethod,
StreamableFile,
VersioningType,
VersioningOptions,
VersioningType,
VERSION_NEUTRAL,
} from '@nestjs/common';
import { VersionValue } from '@nestjs/common/interfaces';
@@ -29,11 +30,13 @@ import {
OptionsUrlencoded,
urlencoded as bodyParserUrlencoded,
} from 'body-parser';
import * as bodyparser from 'body-parser';
import * as cors from 'cors';
import * as express from 'express';
import * as http from 'http';
import * as https from 'https';
import { Duplex, pipeline } from 'stream';
import { NestExpressBodyParserOptions } from '../interfaces/nest-express-body-parser-options.interface';
import { ServeStaticOptions } from '../interfaces/serve-static-options.interface';
import { getBodyParserOptions } from './utils/get-body-parser-options.util';
@@ -46,6 +49,9 @@ type VersionedRoute = <
next: () => void,
) => any;
/**
* @publicApi
*/
export class ExpressAdapter extends AbstractHttpAdapter {
private readonly routerMethodFactory = new RouterMethodFactory();
private readonly logger = new Logger(ExpressAdapter.name);
@@ -94,6 +100,16 @@ export class ExpressAdapter extends AbstractHttpAdapter {
},
);
}
if (
response.getHeader('Content-Type') !== undefined &&
response.getHeader('Content-Type') !== 'application/json' &&
body?.statusCode >= HttpStatus.BAD_REQUEST
) {
this.logger.warn(
"Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses",
);
response.setHeader('Content-Type', 'application/json');
}
return isObject(body) ? response.json(body) : response.send(String(body));
}
@@ -235,6 +251,19 @@ export class ExpressAdapter extends AbstractHttpAdapter {
.forEach(parserKey => this.use(parserMiddleware[parserKey]));
}
public useBodyParser<Options extends bodyparser.Options = bodyparser.Options>(
type: keyof bodyparser.BodyParser,
rawBody: boolean,
options?: NestExpressBodyParserOptions<Options>,
): this {
const parserOptions = getBodyParserOptions(rawBody, options || {});
const parser = bodyparser[type](parserOptions);
this.use(parser);
return this;
}
public setLocal(key: string, value: any) {
this.instance.locals[key] = value;
return this;

View File

@@ -1,6 +1,6 @@
/*
* Nest @platform-express
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

View File

@@ -1 +1,2 @@
export * from './nest-express-application.interface';
export * from './nest-express-body-parser-options.interface';

View File

@@ -1,5 +1,7 @@
import { Server } from 'net';
import { INestApplication } from '@nestjs/common';
import * as bodyparser from 'body-parser';
import { NestExpressBodyParserOptions } from './nest-express-body-parser-options.interface';
import { ServeStaticOptions } from './serve-static-options.interface';
/**
@@ -73,6 +75,24 @@ export interface NestExpressApplication extends INestApplication {
*/
useStaticAssets(path: string, options?: ServeStaticOptions): this;
/**
* Register Express body parsers on the fly. Will respect
* the application's `rawBody` option.
*
* @example
* const app = await NestFactory.create<NestExpressApplication>(
* AppModule,
* { rawBody: true }
* );
* app.useBodyParser('json', { limit: '50mb' });
*
* @returns {this}
*/
useBodyParser<Options extends bodyparser.Options = bodyparser.Options>(
parser: keyof bodyparser.BodyParser,
options?: NestExpressBodyParserOptions<Options>,
): this;
/**
* Sets one or multiple base directories for templates (views).
*

View File

@@ -0,0 +1,6 @@
import type { Options } from 'body-parser';
export type NestExpressBodyParserOptions<T extends Options = Options> = Omit<
T,
'verify'
>;

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-express",
"version": "9.3.0-beta.3",
"version": "9.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -21,11 +21,11 @@
"cors": "2.8.5",
"express": "4.18.2",
"multer": "1.4.4-lts.1",
"tslib": "2.4.1"
"tslib": "2.5.0"
},
"devDependencies": {
"@nestjs/common": "^9.3.0-beta.3",
"@nestjs/core": "^9.3.0-beta.3"
"@nestjs/common": "9.3.3",
"@nestjs/core": "9.3.3"
},
"peerDependencies": {
"@nestjs/common": "^9.0.0",

View File

@@ -18,9 +18,10 @@ import { isString, isUndefined } from '@nestjs/common/utils/shared.utils';
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
import {
fastify,
FastifyBaseLogger,
FastifyBodyParser,
FastifyInstance,
FastifyBaseLogger,
FastifyListenOptions,
FastifyPluginAsync,
FastifyPluginCallback,
FastifyRegister,
@@ -45,6 +46,7 @@ import {
} from 'light-my-request';
// `querystring` is used internally in fastify for registering urlencoded body parser.
import { parse as querystringParse } from 'querystring';
import { NestFastifyBodyParserOptions } from '../interfaces';
import {
FastifyStaticOptions,
FastifyViewOptions,
@@ -91,6 +93,9 @@ type VersionedRoute<TRequest, TResponse> = ((
type FastifyRawRequest<TServer extends RawServerBase> =
RawRequestDefaultExpression<TServer> & { originalUrl?: string };
/**
* @publicApi
*/
export class FastifyAdapter<
TServer extends RawServerBase = RawServerDefault,
TRawRequest extends FastifyRawRequest<TServer> = FastifyRawRequest<TServer>,
@@ -227,12 +232,27 @@ export class FastifyAdapter<
hostname: string,
callback?: () => void,
): void;
public listen(port: string | number, ...args: any[]): void {
public listen(
listenOptions: string | number | FastifyListenOptions,
...args: any[]
): void {
const isFirstArgTypeofFunction = typeof args[0] === 'function';
const callback = isFirstArgTypeofFunction ? args[0] : args[1];
const options: Record<string, any> = {
port: +port,
};
let options: Record<string, any>;
if (
typeof listenOptions === 'object' &&
(listenOptions.host !== undefined ||
listenOptions.port !== undefined ||
listenOptions.path !== undefined)
) {
// First parameter is an object with a path, port and/or host attributes
options = listenOptions;
} else {
options = {
port: +listenOptions,
};
}
if (!isFirstArgTypeofFunction) {
options.host = args[0];
}
@@ -326,6 +346,17 @@ export class FastifyAdapter<
}
body = body.getStream();
}
if (
fastifyReply.getHeader('Content-Type') !== undefined &&
fastifyReply.getHeader('Content-Type') !== 'application/json' &&
body?.statusCode >= HttpStatus.BAD_REQUEST
) {
Logger.warn(
"Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses",
FastifyAdapter.name,
);
fastifyReply.header('Content-Type', 'application/json');
}
return fastifyReply.send(body);
}
@@ -465,6 +496,43 @@ export class FastifyAdapter<
this._isParserRegistered = true;
}
public useBodyParser(
type: string | string[] | RegExp,
rawBody: boolean,
options?: NestFastifyBodyParserOptions,
parser?: FastifyBodyParser<Buffer, TServer>,
) {
const parserOptions = {
...(options || {}),
parseAs: 'buffer' as const,
};
this.getInstance().addContentTypeParser<Buffer>(
type,
parserOptions,
(
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
body: Buffer,
done,
) => {
if (rawBody === true && Buffer.isBuffer(body)) {
req.rawBody = body;
}
if (parser) {
parser(req, body, done);
return;
}
done(null, body);
},
);
// To avoid the Nest application init to override our custom
// body parser, we mark the parsers as registered.
this._isParserRegistered = true;
}
public async createMiddlewareFactory(
requestMethod: RequestMethod,
): Promise<(path: string, callback: Function) => any> {
@@ -510,20 +578,15 @@ export class FastifyAdapter<
}
private registerJsonContentParser(rawBody?: boolean) {
const contentType = 'application/json';
const withRawBody = !!rawBody;
const { bodyLimit } = this.getInstance().initialConfig;
this.getInstance().addContentTypeParser<Buffer>(
'application/json',
{ parseAs: 'buffer', bodyLimit },
(
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
body: Buffer,
done,
) => {
if (rawBody === true && Buffer.isBuffer(body)) {
req.rawBody = body;
}
this.useBodyParser(
contentType,
withRawBody,
{ bodyLimit },
(req, body, done) => {
const { onProtoPoisoning, onConstructorPoisoning } =
this.instance.initialConfig;
const defaultJsonParser = this.instance.getDefaultJsonParser(
@@ -536,20 +599,15 @@ export class FastifyAdapter<
}
private registerUrlencodedContentParser(rawBody?: boolean) {
const contentType = 'application/x-www-form-urlencoded';
const withRawBody = !!rawBody;
const { bodyLimit } = this.getInstance().initialConfig;
this.getInstance().addContentTypeParser<Buffer>(
'application/x-www-form-urlencoded',
{ parseAs: 'buffer', bodyLimit },
(
req: RawBodyRequest<FastifyRequest<unknown, TServer, TRawRequest>>,
body: Buffer,
done,
) => {
if (rawBody === true && Buffer.isBuffer(body)) {
req.rawBody = body;
}
this.useBodyParser(
contentType,
withRawBody,
{ bodyLimit },
(_req, body, done) => {
done(null, querystringParse(body.toString()));
},
);

View File

@@ -1,6 +1,6 @@
/*
* Nest @platform-fastify
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

View File

@@ -1,6 +1,7 @@
/**
* "fastify-static" interfaces
* @see https://github.com/fastify/fastify-static/blob/master/types/index.d.ts
* @publicApi
*/
import { Stats } from 'fs';

View File

@@ -1,6 +1,7 @@
/**
* "fastify/view" interfaces
* @see https://github.com/fastify/point-of-view/blob/master/types/index.d.ts
* @publicApi
*/
export interface FastifyViewOptions {
engine: {

View File

@@ -1 +1,2 @@
export * from './nest-fastify-application.interface';
export * from './nest-fastify-body-parser-options.interface';

View File

@@ -1,10 +1,12 @@
import { INestApplication } from '@nestjs/common';
import {
FastifyBodyParser,
FastifyInstance,
FastifyPluginAsync,
FastifyPluginCallback,
FastifyPluginOptions,
FastifyRegisterOptions,
RawServerBase,
} from 'fastify';
import {
Chain as LightMyRequestChain,
@@ -12,7 +14,11 @@ import {
Response as LightMyRequestResponse,
} from 'light-my-request';
import { FastifyStaticOptions, FastifyViewOptions } from './external';
import { NestFastifyBodyParserOptions } from './nest-fastify-body-parser-options.interface';
/**
* @publicApi
*/
export interface NestFastifyApplication extends INestApplication {
/**
* A wrapper function around native `fastify.register()` method.
@@ -28,6 +34,27 @@ export interface NestFastifyApplication extends INestApplication {
opts?: FastifyRegisterOptions<Options>,
): Promise<FastifyInstance>;
/**
* Register Fastify body parsers on the fly. Will respect
* the application's `rawBody` option.
*
* @example
* const app = await NestFactory.create<NestFastifyApplication>(
* AppModule,
* new FastifyAdapter(),
* { rawBody: true }
* );
* // enable the json parser with a parser limit of 50mb
* app.useBodyParser('application/json', { bodyLimit: 50 * 1000 * 1024 });
*
* @returns {this}
*/
useBodyParser<TServer extends RawServerBase = RawServerBase>(
type: string | string[] | RegExp,
options?: NestFastifyBodyParserOptions,
parser?: FastifyBodyParser<Buffer, TServer>,
): this;
/**
* Sets a base directory for public assets.
* Example `app.useStaticAssets({ root: 'public' })`

View File

@@ -0,0 +1,6 @@
import type { AddContentTypeParser } from 'fastify';
export type NestFastifyBodyParserOptions = Omit<
Parameters<AddContentTypeParser>[1],
'parseAs'
>;

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-fastify",
"version": "9.3.0-beta.3",
"version": "9.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-fastify)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -23,7 +23,7 @@
"fastify": "4.12.0",
"light-my-request": "5.8.0",
"path-to-regexp": "3.2.0",
"tslib": "2.4.1"
"tslib": "2.5.0"
},
"peerDependencies": {
"@fastify/static": "^6.0.0",

View File

@@ -8,6 +8,9 @@ import { fromEvent, Observable } from 'rxjs';
import { filter, first, map, mergeMap, share, takeUntil } from 'rxjs/operators';
import { Server, ServerOptions, Socket } from 'socket.io';
/**
* @publicApi
*/
export class IoAdapter extends AbstractWsAdapter {
public create(
port: number,

View File

@@ -1,6 +1,6 @@
/*
* Nest @platform-socket.io
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

View File

@@ -1,6 +1,6 @@
{
"name": "@nestjs/platform-socket.io",
"version": "9.3.0-beta.3",
"version": "9.3.3",
"description": "Nest - modern, fast, powerful node.js web framework (@platform-socket.io)",
"author": "Kamil Mysliwiec",
"license": "MIT",
@@ -18,7 +18,7 @@
},
"dependencies": {
"socket.io": "4.5.4",
"tslib": "2.4.1"
"tslib": "2.5.0"
},
"peerDependencies": {
"@nestjs/common": "^9.0.0",

View File

@@ -27,6 +27,9 @@ type WsServerRegistryEntry = any[];
const UNDERLYING_HTTP_SERVER_PORT = 0;
/**
* @publicApi
*/
export class WsAdapter extends AbstractWsAdapter {
protected readonly logger = new Logger(WsAdapter.name);
protected readonly httpServersRegistry = new Map<

View File

@@ -1,6 +1,6 @@
/*
* Nest @platform-ws
* Copyright(c) 2017 - 2022 Kamil Mysliwiec
* Copyright(c) 2017 - 2023 Kamil Mysliwiec
* https://nestjs.com
* MIT Licensed
*/

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