@nestjs/core 12.0.0-alpha.5 → 12.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/Readme.md +25 -46
  2. package/adapters/http-adapter.d.ts +2 -0
  3. package/adapters/http-adapter.js +3 -0
  4. package/exceptions/base-exception-filter.js +8 -3
  5. package/helpers/barrier.js +4 -1
  6. package/helpers/handler-metadata-storage.d.ts +1 -0
  7. package/helpers/router-method-factory.d.ts +1 -0
  8. package/helpers/router-method-factory.js +1 -0
  9. package/helpers/safe-instance-decorator.d.ts +10 -0
  10. package/helpers/safe-instance-decorator.js +20 -0
  11. package/hooks/before-app-shutdown.hook.js +11 -2
  12. package/hooks/on-app-shutdown.hook.js +11 -2
  13. package/hooks/on-module-destroy.hook.js +11 -2
  14. package/injector/container.js +2 -2
  15. package/injector/injector.js +20 -12
  16. package/injector/internal-core-module/internal-core-module-factory.js +1 -1
  17. package/injector/module.d.ts +6 -0
  18. package/injector/module.js +12 -1
  19. package/interceptors/interceptors-consumer.js +32 -7
  20. package/internal.d.ts +10 -6
  21. package/internal.js +10 -6
  22. package/middleware/builder.js +5 -1
  23. package/nest-application-context.js +1 -1
  24. package/nest-application.d.ts +2 -1
  25. package/nest-application.js +28 -6
  26. package/package.json +3 -14
  27. package/router/legacy-route-converter.d.ts +1 -1
  28. package/router/legacy-route-converter.js +24 -13
  29. package/router/router-execution-context.d.ts +1 -0
  30. package/router/router-execution-context.js +26 -3
  31. package/router/router-response-controller.d.ts +1 -0
  32. package/router/router-response-controller.js +126 -49
  33. package/router/sse-stream.d.ts +1 -0
  34. package/router/sse-stream.js +24 -13
  35. package/scanner.js +4 -1
package/Readme.md CHANGED
@@ -59,11 +59,11 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
59
59
 
60
60
  <table style="text-align:center;">
61
61
  <tr>
62
- <td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/trilon.svg" width="200" valign="middle" /></a></td>
63
- <td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/logos/microsoft-logo.png" width="180" valign="middle" /></a></td>
64
- <td><a href="https://mojam.co" target="_blank"><img src="https://nestjs.com/img/logos/mojam-logo.png" width="80" valign="middle" /></a></td>
62
+ <td><a href="https://trilon.io" target="_blank"><img src="https://nestjs.com/img/github/trilon-logo.svg" width="200" valign="middle" /></a></td>
63
+ <td><a href="https://microsoft.com/" target="_blank"><img src="https://nestjs.com/img/github/microsoft-logo.png" width="180" valign="middle" /></a></td>
64
+ <td><a href="https://mojam.co" target="_blank"><img src="https://images.opencollective.com/mojamco/981e9de/logo/256.png?height=256" width="80" valign="middle" /></a></td>
65
65
  <td><a href="https://valor-software.com/" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/valor-software.png" width="170" valign="middle" /></a></td>
66
- <td><a href="https://serpapi.com/" target="_blank"><img src="https://nestjs.com/img/logos/serpapi-logo.png" width="150" valign="middle" /></a></td>
66
+ <td><a href="https://serpapi.com/" target="_blank"><img src="https://nestjs.com/img/github/serpapi-logo.png" width="150" valign="middle" /></a></td>
67
67
  </tr>
68
68
  </table>
69
69
 
@@ -71,15 +71,15 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
71
71
 
72
72
  <table style="text-align:center;">
73
73
  <tr>
74
- <td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/logos/red-hat-logo.svg" width="200" valign="middle" /></a></td>
75
- <td><a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="180" valign="middle" /></a></td>
76
- <td><a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/logos/nx-logo.png" height="45" valign="middle" /></a></td>
77
- <td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/logos/intrinisic-logo.png" width="210" valign="middle" /></a></td>
78
- <td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/logos/jetbrains-logo.svg" width="90" valign="middle" /></a></td>
74
+ <td><a href="https://www.redhat.com" target="_blank"><img src="https://nestjs.com/img/github/red-hat-logo.svg" width="150" valign="middle" /></a></td>
75
+ <td><a href="https://github.com/Sanofi-IADC" target="_blank"><img src="https://docs.nestjs.com/assets/sponsors/sanofi.png" width="120" valign="middle" /></a></td>
76
+ <td><a href="https://nx.dev" target="_blank"><img src="https://nestjs.com/img/github/nx-logo.png" height="45" valign="middle" /></a></td>
77
+ <td><a href="https://intrinsic.ventures/" target="_blank"><img src="https://nestjs.com/img/github/intrinisic-logo.png" width="150" valign="middle" /></a></td>
78
+ <td><a href="https://jetbrains.com/" target="_blank"><img src="https://nestjs.com/img/github/jetbrains-logo.svg" width="70" valign="middle" /></a></td>
79
79
  </tr>
80
80
  <tr>
81
- <td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/logos/snyk-logo-black.png" width="185" valign="middle" /></a></td>
82
- <td><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://nestjs.com/img/logos/movavi-logo.svg" width="105" valign="middle" /></a></td>
81
+ <td><a href="https://snyk.co/nestjs" target="_blank"><img src="https://nestjs.com/img/github/snyk-logo-black.png" width="125" valign="middle" /></a></td>
82
+ <td align="center"><a href="https://www.movavi.com/imovie-for-windows.html" target="_blank"><img src="https://images.opencollective.com/movavi/1fb15e5/avatar/256.png?height=256" width="60" valign="middle" /></a></td>
83
83
  </tr>
84
84
  </table>
85
85
 
@@ -87,15 +87,15 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
87
87
 
88
88
  <table style="text-align:center;">
89
89
  <tr>
90
- <td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/logos/mercedes-logo.png" width="100" valign="middle" /></a></td>
91
- <td><a href="https://www.dinii.jp/" target="_blank"><img src="https://nestjs.com/img/logos/dinii-logo.png" width="65" valign="middle" /></a></td>
92
- <td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/logos/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
93
- <td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/logos/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
94
- <td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/arcjet-logo.svg" width="170" valign="middle" /></a></td>
95
- <td align="center" valign="middle"><a href="https://crawljobs.com" target="_blank"><img src="https://nestjs.com/img/logos/crawljobs-logo.svg" width="130" valign="middle" /></a></td>
90
+ <td><a href="https://www.mercedes-benz.com/" target="_blank"><img src="https://nestjs.com/img/github/mercedes-logo.png" width="100" valign="middle" /></a></td>
91
+ <td><a href="https://handsontable.com/docs/react-data-grid/?utm_source=NestJS_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024" target="_blank"><img src="https://nestjs.com/img/github/handsontable-dark-logo.svg#2" width="150" valign="middle" /></a></td>
92
+ <td align="center" valign="middle"><a href="https://www.itflashcards.com/" target="_blank"><img src="https://nestjs.com/img/github/it_flashcards-logo.png" width="170" valign="middle" /></a></td>
93
+ <td align="center" valign="middle"><a href="https://arcjet.com/?ref=nestjs" target="_blank"><img src="https://nestjs.com/img/github/arcjet-logo.svg" width="170" valign="middle" /></a></td>
94
+ <td align="center" valign="middle"><a href="https://pandektes.com" target="_blank"><img src="https://nestjs.com/img/github/pandektes-logo.png" width="65" valign="middle" /></a></td>
96
95
  </tr><tr>
97
- <td align="center" valign="middle"><a href="https://pandektes.com" target="_blank"><img src="https://nestjs.com/img/logos/pandektes-logo.png" width="65" valign="middle" /></a></td>
98
- <td align="center" valign="middle"><a href="https://www.fintechcrafts.com/" target="_blank"><img src="https://nestjs.com/img/logos/fintechcrafts-logo.svg" width="65" valign="middle" /></a></td>
96
+ <td align="center" valign="middle"><a href="https://www.fintechcrafts.com/" target="_blank"><img src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/935813df-48a9-4b41-b923-7d70b5621c6b/2026-05-06%2010%2021%2033.png" width="65" valign="middle" /></a></td>
97
+ <td align="center" valign="middle"><a href="https://talordata.com/?campaignid=Ya5Xpw2gmhyee6Tg&utm_source=nestjs&utm_term=nestjs" target="_blank"><img src="https://images.opencollective.com/elowen/9ccac7f/avatar/512.png" width="65" valign="middle" /></a></td>
98
+ <td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="65" valign="middle" /></a></td>
99
99
  </tr>
100
100
  </table>
101
101
 
@@ -103,33 +103,12 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
103
103
 
104
104
  <table>
105
105
  <tr>
106
- <td align="center" valign="middle"><a href="https://www.swingdev.io" target="_blank"><img src="https://nestjs.com/img/logos/swingdev-logo.svg#1" width="110" valign="middle" /></a></td>
107
- <td align="center" valign="middle"><a href="https://www.novologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/novologic.png" width="110" valign="middle" /></a></td>
108
- <td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/logos/mantro-logo.svg" width="95" valign="middle" /></a></td>
109
- <td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/logos/triplebyte.png" width="107" valign="middle" /></a></td>
110
- <td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/logos/nearpod-logo.svg" width="100" valign="middle" /></a></td>
111
- <td align="center" valign="middle"><a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/logos/genuinebee.svg" width="97" valign="middle" /></a></td>
112
- </tr>
113
- <tr>
114
- <td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/logos/vpn-review-logo.png" width="85" valign="middle" /></a></td>
115
- <td align="center" valign="middle"><a href="https://lambda-it.ch/" target="_blank"><img src="https://nestjs.com/img/logos/lambda-it-logo.svg" width="115" valign="middle" /></a></td>
116
- <td align="center" valign="middle"><a href="https://rocketech.it/cases/?utm_source=google&utm_medium=badge&utm_campaign=nestjs" target="_blank"><img src="https://nestjs.com/img/logos/rocketech-logo.svg" width="110" valign="middle" /></a></td>
117
- <td align="center" valign="middle"><a href="https://www.anonymistic.com/" target="_blank"><img src="https://nestjs.com/img/logos/anonymistic-logo.png" width="125" valign="middle" /></a></td>
118
- <td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/logos/naologic-logo.svg" width="125" valign="middle" /></a></td>
119
- <td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/logos/triplecore-logo.svg" width="50" valign="middle" /></a></td>
120
- </tr>
121
- <tr>
122
- <td align="center" valign="middle"><a href="https://polygon-software.ch/" target="_blank"><img src="https://nestjs.com/img/logos/polygon-logo.svg" width="120" valign="middle" /></a></td>
123
- <td align="center" valign="middle"><a href="https://boringowl.io/" target="_blank"><img src="https://nestjs.com/img/logos/boringowl-logo.svg" width="120" valign="middle" /></a></td>
124
- <td align="center" valign="middle"><a href="https://nordbot.app/" target="_blank"><img src="https://nestjs.com/img/logos/nordbot-logo.png" width="120" valign="middle" /></a></td>
125
- <td align="center" valign="middle"><a href="https://doppio.sh/" target="_blank"><img src="https://nestjs.com/img/logos/dopiosh-logo.png" width="50" valign="middle" /></a></td>
126
- <td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/logos/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
127
- <td align="center" valign="middle"><a href="https://www.tripoffice.com/" target="_blank"><img src="https://nestjs.com/img/logos/tripoffice-logo.png" width="140" valign="middle" /></a></td>
128
- </tr>
129
- <tr>
130
- <td align="center" valign="middle"><a href="https://solcellsforetag.se/" target="_blank"><img src="https://nestjs.com/img/logos/solcellsforetag-logo.svg" width="140" valign="middle" /></a></td>
131
- <td align="center" valign="middle"><a href="https://www.route4me.com/" target="_blank"><img src="https://nestjs.com/img/logos/route4me-logo.svg" width="100" valign="middle" /></a></td>
132
- <td align="center" valign="middle"><a href="https://memory2.co/" target="_blank"><img src="https://images.opencollective.com/memory-squared/bbe37f5/avatar/256.png?height=50" width="50" valign="middle" /></a></td>
106
+ <td align="center" valign="middle"><a href="https://mantro.net/" target="_blank"><img src="https://nestjs.com/img/github/mantro-logo.svg" width="95" valign="middle" /></a></td>
107
+ <td align="center" valign="middle"><a href="https://triplebyte.com/" target="_blank"><img src="https://nestjs.com/img/github/triplebyte-logo.png" width="107" valign="middle" /></a></td>
108
+ <td align="center" valign="middle"><a href="https://nearpod.com/" target="_blank"><img src="https://nestjs.com/img/github/nearpod-logo.svg" width="100" valign="middle" /></a></td>
109
+ <td align="center" valign="middle"><a href="https://www.naologic.com/" target="_blank"><img src="https://nestjs.com/img/github/naologic-logo.svg" width="125" valign="middle" /></a></td>
110
+ <td align="center" valign="middle"><a href="https://triplecore.io" target="_blank"><img src="https://nestjs.com/img/github/triplecore-logo.svg" width="50" valign="middle" /></a>
111
+ <td align="center" valign="middle"><a href="https://www.hingehealth.com/" target="_blank"><img src="https://nestjs.com/img/github/hinge-health-logo.svg" width="100" valign="middle" /></a></td>
133
112
  </tr>
134
113
  </table>
135
114
 
@@ -41,6 +41,8 @@ export declare abstract class AbstractHttpAdapter<TServer = any, TRequest = any,
41
41
  all(path: any, handler: RequestHandler): any;
42
42
  search(handler: RequestHandler): any;
43
43
  search(path: any, handler: RequestHandler): any;
44
+ query(handler: RequestHandler): any;
45
+ query(path: any, handler: RequestHandler): any;
44
46
  options(handler: RequestHandler): any;
45
47
  options(path: any, handler: RequestHandler): any;
46
48
  listen(port: string | number, callback?: () => void): any;
@@ -57,6 +57,9 @@ export class AbstractHttpAdapter {
57
57
  search(...args) {
58
58
  return this.instance.search(...args);
59
59
  }
60
+ query(...args) {
61
+ return this.instance.query(...args);
62
+ }
60
63
  options(...args) {
61
64
  return this.instance.options(...args);
62
65
  }
@@ -63,16 +63,21 @@ export class BaseExceptionFilter {
63
63
  if (!err || typeof err !== 'object') {
64
64
  return false;
65
65
  }
66
- if (err.constructor.name === 'FastifyError' &&
66
+ if (err.constructor?.name === 'FastifyError' &&
67
67
  typeof err.code === 'string' &&
68
68
  typeof err.statusCode === 'number') {
69
69
  return true;
70
70
  }
71
71
  // "http-errors" error signature
72
- return (typeof err.expose === 'boolean' &&
72
+ if (typeof err.expose === 'boolean' &&
73
73
  typeof err.statusCode === 'number' &&
74
74
  err.status === err.statusCode &&
75
- err instanceof Error);
75
+ err instanceof Error) {
76
+ return true;
77
+ }
78
+ // Plain "http error"-shaped values (e.g. objects thrown by third-party
79
+ // middleware) that carry a status code and a message
80
+ return !!(err.statusCode && err.message);
76
81
  }
77
82
  }
78
83
  __decorate([
@@ -12,6 +12,9 @@ export class Barrier {
12
12
  this.promise = new Promise(resolve => {
13
13
  this.resolve = resolve;
14
14
  });
15
+ if (targetCount === 0) {
16
+ this.resolve();
17
+ }
15
18
  }
16
19
  /**
17
20
  * Signal that a participant has reached the barrier.
@@ -20,7 +23,7 @@ export class Barrier {
20
23
  */
21
24
  signal() {
22
25
  this.currentCount += 1;
23
- if (this.currentCount === this.targetCount) {
26
+ if (this.currentCount >= this.targetCount) {
24
27
  this.resolve();
25
28
  }
26
29
  }
@@ -10,6 +10,7 @@ export type HandlerResponseBasicFn = <TResult, TResponse>(result: TResult, res:
10
10
  export type HandleSseResponseFn = <TResult extends Observable<unknown> = any, TResponse extends HeaderStream = any, TRequest extends IncomingMessage = any>(result: TResult, res: TResponse, req: TRequest) => any;
11
11
  export interface HandlerMetadata {
12
12
  argsLength: number;
13
+ isSseHandler: boolean;
13
14
  paramtypes: any[];
14
15
  httpStatusCode: number;
15
16
  responseHeaders: any[];
@@ -17,6 +17,7 @@ export declare const REQUEST_METHOD_MAP: {
17
17
  readonly 13: "move";
18
18
  readonly 14: "lock";
19
19
  readonly 15: "unlock";
20
+ readonly 16: "query";
20
21
  };
21
22
  export declare class RouterMethodFactory {
22
23
  get(target: HttpServer, requestMethod: RequestMethod): Function;
@@ -16,6 +16,7 @@ export const REQUEST_METHOD_MAP = {
16
16
  [RequestMethod.MOVE]: 'move',
17
17
  [RequestMethod.LOCK]: 'lock',
18
18
  [RequestMethod.UNLOCK]: 'unlock',
19
+ [RequestMethod.QUERY]: 'query',
19
20
  };
20
21
  export class RouterMethodFactory {
21
22
  get(target, requestMethod) {
@@ -0,0 +1,10 @@
1
+ type InstanceDecorator = (target: unknown) => unknown;
2
+ /**
3
+ * Wraps an `instrument.instanceDecorator` so that a decorator throwing on a
4
+ * given instance (e.g. when inspecting a Proxy whose traps throw outside of
5
+ * their intended context, such as `nestjs-cls` proxy providers) does not
6
+ * crash the application bootstrap. The original, undecorated instance is
7
+ * used instead and a warning is logged.
8
+ */
9
+ export declare function makeSafeInstanceDecorator(decorator: InstanceDecorator): InstanceDecorator;
10
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Logger } from '@nestjs/common';
2
+ const logger = new Logger('InstrumentLogger');
3
+ /**
4
+ * Wraps an `instrument.instanceDecorator` so that a decorator throwing on a
5
+ * given instance (e.g. when inspecting a Proxy whose traps throw outside of
6
+ * their intended context, such as `nestjs-cls` proxy providers) does not
7
+ * crash the application bootstrap. The original, undecorated instance is
8
+ * used instead and a warning is logged.
9
+ */
10
+ export function makeSafeInstanceDecorator(decorator) {
11
+ return (target) => {
12
+ try {
13
+ return decorator(target);
14
+ }
15
+ catch (err) {
16
+ logger.warn(`The "instanceDecorator" function threw an error while decorating an instance (${err?.message ?? err}). The undecorated instance will be used instead.`);
17
+ return target;
18
+ }
19
+ };
20
+ }
@@ -1,3 +1,4 @@
1
+ import { Logger } from '@nestjs/common';
1
2
  import { isFunction, isNil } from '@nestjs/common/internal';
2
3
  import { iterate } from 'iterare';
3
4
  import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level.js';
@@ -33,12 +34,20 @@ export async function callBeforeAppShutdownHook(moduleRef, signal) {
33
34
  const groupedInstances = getInstancesGroupedByHierarchyLevel(moduleRef.controllers, moduleRef.injectables, moduleRef.middlewares, providers);
34
35
  const levels = getSortedHierarchyLevels(groupedInstances, 'DESC');
35
36
  for (const level of levels) {
36
- await Promise.all(callOperator(groupedInstances.get(level), signal));
37
+ const results = await Promise.allSettled(callOperator(groupedInstances.get(level), signal));
38
+ results
39
+ .filter((result) => result.status === 'rejected')
40
+ .forEach(result => Logger.error(result.reason, result.reason?.stack));
37
41
  }
38
42
  const moduleClassInstance = moduleClassHost.instance;
39
43
  if (moduleClassInstance &&
40
44
  hasBeforeApplicationShutdownHook(moduleClassInstance) &&
41
45
  moduleClassHost.isDependencyTreeStatic()) {
42
- await moduleClassInstance.beforeApplicationShutdown(signal);
46
+ try {
47
+ await moduleClassInstance.beforeApplicationShutdown(signal);
48
+ }
49
+ catch (err) {
50
+ Logger.error(err, err?.stack);
51
+ }
43
52
  }
44
53
  }
@@ -1,3 +1,4 @@
1
+ import { Logger } from '@nestjs/common';
1
2
  import { isFunction, isNil } from '@nestjs/common/internal';
2
3
  import { iterate } from 'iterare';
3
4
  import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level.js';
@@ -35,13 +36,21 @@ export async function callAppShutdownHook(moduleRef, signal) {
35
36
  const groupedInstances = getInstancesGroupedByHierarchyLevel(moduleRef.controllers, moduleRef.injectables, moduleRef.middlewares, providers);
36
37
  const levels = getSortedHierarchyLevels(groupedInstances, 'DESC');
37
38
  for (const level of levels) {
38
- await Promise.all(callOperator(groupedInstances.get(level), signal));
39
+ const results = await Promise.allSettled(callOperator(groupedInstances.get(level), signal));
40
+ results
41
+ .filter((result) => result.status === 'rejected')
42
+ .forEach(result => Logger.error(result.reason, result.reason?.stack));
39
43
  }
40
44
  // Call the instance itself
41
45
  const moduleClassInstance = moduleClassHost.instance;
42
46
  if (moduleClassInstance &&
43
47
  hasOnAppShutdownHook(moduleClassInstance) &&
44
48
  moduleClassHost.isDependencyTreeStatic()) {
45
- await moduleClassInstance.onApplicationShutdown(signal);
49
+ try {
50
+ await moduleClassInstance.onApplicationShutdown(signal);
51
+ }
52
+ catch (err) {
53
+ Logger.error(err, err?.stack);
54
+ }
46
55
  }
47
56
  }
@@ -1,3 +1,4 @@
1
+ import { Logger } from '@nestjs/common';
1
2
  import { isFunction, isNil } from '@nestjs/common/internal';
2
3
  import { iterate } from 'iterare';
3
4
  import { getInstancesGroupedByHierarchyLevel } from './utils/get-instances-grouped-by-hierarchy-level.js';
@@ -34,13 +35,21 @@ export async function callModuleDestroyHook(moduleRef) {
34
35
  const groupedInstances = getInstancesGroupedByHierarchyLevel(moduleRef.controllers, moduleRef.injectables, moduleRef.middlewares, providers);
35
36
  const levels = getSortedHierarchyLevels(groupedInstances, 'DESC');
36
37
  for (const level of levels) {
37
- await Promise.all(callOperator(groupedInstances.get(level)));
38
+ const results = await Promise.allSettled(callOperator(groupedInstances.get(level)));
39
+ results
40
+ .filter((result) => result.status === 'rejected')
41
+ .forEach(result => Logger.error(result.reason, result.reason?.stack));
38
42
  }
39
43
  // Call the module instance itself
40
44
  const moduleClassInstance = moduleClassHost.instance;
41
45
  if (moduleClassInstance &&
42
46
  hasOnModuleDestroyHook(moduleClassInstance) &&
43
47
  moduleClassHost.isDependencyTreeStatic()) {
44
- await moduleClassInstance.onModuleDestroy();
48
+ try {
49
+ await moduleClassInstance.onModuleDestroy();
50
+ }
51
+ catch (err) {
52
+ Logger.error(err, err?.stack);
53
+ }
45
54
  }
46
55
  }
@@ -66,7 +66,7 @@ export class NestContainer {
66
66
  if (this.modules.has(token)) {
67
67
  return {
68
68
  moduleRef: this.modules.get(token),
69
- inserted: true,
69
+ inserted: false,
70
70
  };
71
71
  }
72
72
  return {
@@ -92,7 +92,7 @@ export class NestContainer {
92
92
  type,
93
93
  dynamicMetadata,
94
94
  }, scope),
95
- inserted: false,
95
+ inserted: true,
96
96
  };
97
97
  }
98
98
  async setModule({ token, dynamicMetadata, type }, scope) {
@@ -7,6 +7,7 @@ import { RuntimeException } from '../errors/exceptions/runtime.exception.js';
7
7
  import { UndefinedDependencyException } from '../errors/exceptions/undefined-dependency.exception.js';
8
8
  import { UnknownDependenciesException } from '../errors/exceptions/unknown-dependencies.exception.js';
9
9
  import { Barrier } from '../helpers/barrier.js';
10
+ import { makeSafeInstanceDecorator } from '../helpers/safe-instance-decorator.js';
10
11
  import { STATIC_CONTEXT } from './constants.js';
11
12
  import { INQUIRER } from './inquirer/index.js';
12
13
  import { InstanceWrapper, } from './instance-wrapper.js';
@@ -18,7 +19,7 @@ export class Injector {
18
19
  constructor(options) {
19
20
  this.options = options;
20
21
  if (options?.instanceDecorator) {
21
- this.instanceDecorator = options.instanceDecorator;
22
+ this.instanceDecorator = makeSafeInstanceDecorator(options.instanceDecorator);
22
23
  }
23
24
  }
24
25
  loadPrototype({ token }, collection, contextId = STATIC_CONTEXT) {
@@ -267,12 +268,23 @@ export class Injector {
267
268
  * that eventual lazily created instance will be merged with the prototype
268
269
  * instantiated beforehand.
269
270
  */
270
- instanceHost.donePromise &&
271
+ if (instanceHost.donePromise) {
271
272
  void instanceHost.donePromise
272
273
  .then(() => this.loadProvider(instanceWrapper, moduleRef, resolutionContext))
273
274
  .catch(err => {
274
275
  instanceWrapper.settlementSignal?.error(err);
275
276
  });
277
+ }
278
+ else {
279
+ /**
280
+ * No load has ever been scheduled for this context (e.g., request-scoped
281
+ * providers are no longer instantiated during static bootstrap, so a fresh
282
+ * durable/request sub-tree host has no inherited `donePromise`).
283
+ * Load it now; if a circular dependency is truly in-flight, the nested
284
+ * lookup will find this host pending and defer through its `donePromise`.
285
+ */
286
+ await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, resolutionContext);
287
+ }
276
288
  }
277
289
  if (instanceWrapper.async) {
278
290
  const host = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
@@ -329,16 +341,12 @@ export class Injector {
329
341
  this.printFoundInModuleLog(name, relatedModule);
330
342
  instanceWrapperRef = providers.get(name);
331
343
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapperRef);
332
- const inquirerId = this.getContextInquirerId(resolutionContext);
333
- const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapperRef), inquirerId);
334
- if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
335
- /*
336
- * Provider will be loaded shortly in resolveComponentHost() once we pass the current
337
- * Barrier. We cannot load it here because doing so could incorrectly evaluate the
338
- * staticity of the dependency tree and lead to undefined / null injection.
339
- */
340
- break;
341
- }
344
+ /*
345
+ * Stop at the first direct export match. Continuing when the provider is
346
+ * already resolved would let a later import (e.g. a global forRoot module)
347
+ * override an explicit forFeature import for the same token.
348
+ */
349
+ break;
342
350
  }
343
351
  return instanceWrapperRef;
344
352
  }
@@ -15,7 +15,7 @@ export class InternalCoreModuleFactory {
15
15
  timestamp: false,
16
16
  });
17
17
  const injector = new Injector({
18
- preview: container.contextOptions?.preview,
18
+ preview: container.contextOptions?.preview ?? false,
19
19
  instanceDecorator: container.contextOptions?.instrument?.instanceDecorator,
20
20
  });
21
21
  const instanceLoader = new InstanceLoader(container, injector, graphInspector, logger);
@@ -35,6 +35,12 @@ export declare class Module {
35
35
  get entryProviders(): Array<InstanceWrapper<Injectable>>;
36
36
  get exports(): Set<InjectionToken>;
37
37
  get instance(): NestModule;
38
+ /**
39
+ * Modules are registered in the container before they are scanned (dynamic
40
+ * `imports` are pre-registered by `NestContainer#addDynamicMetadata`), so
41
+ * registration alone does not imply the module was ever instantiated.
42
+ */
43
+ get isInstantiated(): boolean;
38
44
  get metatype(): Type<any>;
39
45
  get distance(): number;
40
46
  set distance(value: number);
@@ -4,6 +4,7 @@ import { InvalidClassException, RuntimeException, UnknownExportException, } from
4
4
  import { createContextId } from '../helpers/context-id-factory.js';
5
5
  import { getClassScope } from '../helpers/get-class-scope.js';
6
6
  import { isDurable } from '../helpers/is-durable.js';
7
+ import { makeSafeInstanceDecorator } from '../helpers/safe-instance-decorator.js';
7
8
  import { UuidFactory } from '../inspector/uuid-factory.js';
8
9
  import { CONTROLLER_ID_KEY } from './constants.js';
9
10
  import { InstanceWrapper } from './instance-wrapper.js';
@@ -83,6 +84,14 @@ export class Module {
83
84
  const moduleRef = this._providers.get(this._metatype);
84
85
  return moduleRef.instance;
85
86
  }
87
+ /**
88
+ * Modules are registered in the container before they are scanned (dynamic
89
+ * `imports` are pre-registered by `NestContainer#addDynamicMetadata`), so
90
+ * registration alone does not imply the module was ever instantiated.
91
+ */
92
+ get isInstantiated() {
93
+ return !isNil(this._providers.get(this._metatype)?.instance);
94
+ }
86
95
  get metatype() {
87
96
  return this._metatype;
88
97
  }
@@ -243,7 +252,9 @@ export class Module {
243
252
  token: providerToken,
244
253
  name: providerToken?.name || providerToken,
245
254
  metatype: null,
246
- instance: instanceDecorator ? instanceDecorator(value) : value,
255
+ instance: instanceDecorator
256
+ ? makeSafeInstanceDecorator(instanceDecorator)(value)
257
+ : value,
247
258
  isResolved: true,
248
259
  async: value instanceof Promise,
249
260
  host: this,
@@ -1,11 +1,11 @@
1
1
  import { AsyncResource } from 'async_hooks';
2
- import { Observable, defer, from as fromPromise } from 'rxjs';
3
- import { mergeAll, switchMap } from 'rxjs/operators';
2
+ import { Observable, defer, from } from 'rxjs';
3
+ import { mergeAll } from 'rxjs/operators';
4
4
  import { ExecutionContextHost } from '../helpers/execution-context-host.js';
5
5
  import { isEmptyArray } from '@nestjs/common/internal';
6
6
  export class InterceptorsConsumer {
7
7
  async intercept(interceptors, args, instance, callback, next, type) {
8
- if (isEmptyArray(interceptors)) {
8
+ if (!interceptors || isEmptyArray(interceptors)) {
9
9
  return next();
10
10
  }
11
11
  const context = this.createContext(args, instance, callback);
@@ -25,9 +25,34 @@ export class InterceptorsConsumer {
25
25
  return new ExecutionContextHost(args, instance.constructor, callback);
26
26
  }
27
27
  transformDeferred(next) {
28
- return fromPromise(next()).pipe(switchMap(res => {
29
- const isDeferred = res instanceof Promise || res instanceof Observable;
30
- return isDeferred ? res : Promise.resolve(res);
31
- }));
28
+ // Call next() eagerly here — this method is invoked inside
29
+ // defer(AsyncResource.bind(...)), so the async context (e.g. AsyncLocalStorage)
30
+ // is correctly inherited. Deferring next() into the subscriber function would
31
+ // lose that context because the subscriber is called outside the bound scope.
32
+ const nextPromise = next();
33
+ return new Observable(subscriber => {
34
+ let innerSub;
35
+ nextPromise
36
+ .then(res => {
37
+ if (subscriber.closed) {
38
+ // The outer subscription was torn down (e.g. an SSE client disconnect)
39
+ // before the async handler resolved. Do not subscribe the producer
40
+ // Observable after the consumer has already gone away — subscribing
41
+ // only to unsubscribe in the same tick would start producer side
42
+ // effects just to immediately abort them.
43
+ return;
44
+ }
45
+ const isDeferred = res instanceof Promise || res instanceof Observable;
46
+ innerSub = from(isDeferred ? res : Promise.resolve(res)).subscribe(subscriber);
47
+ })
48
+ .catch(err => {
49
+ if (!subscriber.closed) {
50
+ subscriber.error(err);
51
+ }
52
+ });
53
+ return () => {
54
+ innerSub?.unsubscribe();
55
+ };
56
+ });
32
57
  }
33
58
  }
package/internal.d.ts CHANGED
@@ -5,28 +5,32 @@
5
5
  * @internal
6
6
  * @module
7
7
  */
8
- export { RuntimeException } from './errors/exceptions/runtime.exception.js';
8
+ export * from './errors/exceptions/index.js';
9
9
  export { InvalidExceptionFilterException } from './errors/exceptions/invalid-exception-filter.exception.js';
10
+ export { RuntimeException } from './errors/exceptions/runtime.exception.js';
10
11
  export { MESSAGES } from './constants.js';
11
12
  export { DependenciesScanner } from './scanner.js';
13
+ export { STATIC_CONTEXT } from './injector/constants.js';
12
14
  export { Injector, InjectorDependencyContext } from './injector/injector.js';
13
15
  export { InstanceLoader } from './injector/instance-loader.js';
14
16
  export { InstanceWrapper } from './injector/instance-wrapper.js';
17
+ export { InternalCoreModule } from './injector/internal-core-module/index.js';
15
18
  export { Module } from './injector/module.js';
16
- export { STATIC_CONTEXT } from './injector/constants.js';
17
- export { ExecutionContextHost } from './helpers/execution-context-host.js';
19
+ export * from './inspector/index.js';
18
20
  export { ContextUtils, ParamProperties } from './helpers/context-utils.js';
21
+ export { ExecutionContextHost } from './helpers/execution-context-host.js';
19
22
  export { HandlerMetadataStorage } from './helpers/handler-metadata-storage.js';
20
- export { RouterMethodFactory } from './helpers/router-method-factory.js';
21
23
  export { loadAdapter } from './helpers/load-adapter.js';
22
24
  export { optionalRequire } from './helpers/optional-require.js';
25
+ export { RouterMethodFactory } from './helpers/router-method-factory.js';
26
+ export { makeSafeInstanceDecorator } from './helpers/safe-instance-decorator.js';
23
27
  export { ParamsMetadata } from './helpers/interfaces/index.js';
28
+ export { FORBIDDEN_MESSAGE } from './guards/constants.js';
24
29
  export { GuardsConsumer } from './guards/guards-consumer.js';
25
30
  export { GuardsContextCreator } from './guards/guards-context-creator.js';
26
- export { FORBIDDEN_MESSAGE } from './guards/constants.js';
31
+ export { ParamsTokenFactory } from './pipes/params-token-factory.js';
27
32
  export { PipesConsumer } from './pipes/pipes-consumer.js';
28
33
  export { PipesContextCreator } from './pipes/pipes-context-creator.js';
29
- export { ParamsTokenFactory } from './pipes/params-token-factory.js';
30
34
  export { InterceptorsConsumer } from './interceptors/interceptors-consumer.js';
31
35
  export { InterceptorsContextCreator } from './interceptors/interceptors-context-creator.js';
32
36
  export { BaseExceptionFilterContext } from './exceptions/base-exception-filter-context.js';
package/internal.js CHANGED
@@ -6,33 +6,37 @@
6
6
  * @module
7
7
  */
8
8
  // Errors
9
- export { RuntimeException } from './errors/exceptions/runtime.exception.js';
9
+ export * from './errors/exceptions/index.js';
10
10
  export { InvalidExceptionFilterException } from './errors/exceptions/invalid-exception-filter.exception.js';
11
+ export { RuntimeException } from './errors/exceptions/runtime.exception.js';
11
12
  // Constants
12
13
  export { MESSAGES } from './constants.js';
13
14
  // Scanner
14
15
  export { DependenciesScanner } from './scanner.js';
15
16
  // Injector
17
+ export { STATIC_CONTEXT } from './injector/constants.js';
16
18
  export { Injector } from './injector/injector.js';
17
19
  export { InstanceLoader } from './injector/instance-loader.js';
18
20
  export { InstanceWrapper } from './injector/instance-wrapper.js';
21
+ export { InternalCoreModule } from './injector/internal-core-module/index.js';
19
22
  export { Module } from './injector/module.js';
20
- export { STATIC_CONTEXT } from './injector/constants.js';
23
+ export * from './inspector/index.js';
21
24
  // Helpers
22
- export { ExecutionContextHost } from './helpers/execution-context-host.js';
23
25
  export { ContextUtils } from './helpers/context-utils.js';
26
+ export { ExecutionContextHost } from './helpers/execution-context-host.js';
24
27
  export { HandlerMetadataStorage } from './helpers/handler-metadata-storage.js';
25
- export { RouterMethodFactory } from './helpers/router-method-factory.js';
26
28
  export { loadAdapter } from './helpers/load-adapter.js';
27
29
  export { optionalRequire } from './helpers/optional-require.js';
30
+ export { RouterMethodFactory } from './helpers/router-method-factory.js';
31
+ export { makeSafeInstanceDecorator } from './helpers/safe-instance-decorator.js';
28
32
  // Guards
33
+ export { FORBIDDEN_MESSAGE } from './guards/constants.js';
29
34
  export { GuardsConsumer } from './guards/guards-consumer.js';
30
35
  export { GuardsContextCreator } from './guards/guards-context-creator.js';
31
- export { FORBIDDEN_MESSAGE } from './guards/constants.js';
32
36
  // Pipes
37
+ export { ParamsTokenFactory } from './pipes/params-token-factory.js';
33
38
  export { PipesConsumer } from './pipes/pipes-consumer.js';
34
39
  export { PipesContextCreator } from './pipes/pipes-context-creator.js';
35
- export { ParamsTokenFactory } from './pipes/params-token-factory.js';
36
40
  // Interceptors
37
41
  export { InterceptorsConsumer } from './interceptors/interceptors-consumer.js';
38
42
  export { InterceptorsContextCreator } from './interceptors/interceptors-context-creator.js';