@nestjs/core 12.0.0-alpha.2 → 12.0.0-alpha.4

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.
package/Readme.md CHANGED
@@ -29,7 +29,7 @@ Nest is a framework for building efficient, scalable <a href="https://nodejs.org
29
29
 
30
30
  ## Philosophy
31
31
 
32
- <p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front and backend applications, giving rise to awesome projects like <a href="https://angular.io/" target="_blank">Angular</a>, <a href="https://github.com/facebook/react" target="_blank">React</a>, and <a href="https://github.com/vuejs/vue" target="_blank">Vue</a>, which improve developer productivity and enable the construction of fast, testable, and extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers, and tools for Node, none of them effectively solve the main problem - the architecture.</p>
32
+ <p>In recent years, thanks to Node.js, JavaScript has become the “lingua franca” of the web for both front-end and back-end applications, giving rise to awesome projects like <a href="https://angular.dev/" target="_blank">Angular</a>, <a href="https://react.dev/" target="_blank">React</a>, and <a href="https://vuejs.org/" target="_blank">Vue</a>, which improve developer productivity and enable the construction of fast, testable, and extensible frontend applications. However, on the server-side, while there are a lot of superb libraries, helpers, and tools for Node, none of them effectively solve the main problem - the architecture.</p>
33
33
  <p>Nest aims to provide an application architecture out of the box which allows for effortless creation of highly testable, scalable, and loosely coupled and easily maintainable applications. The architecture is heavily inspired by Angular.</p>
34
34
 
35
35
  ## Getting started
@@ -95,6 +95,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
95
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>
96
96
  </tr><tr>
97
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>
98
99
  </tr>
99
100
  </table>
100
101
 
@@ -96,9 +96,12 @@ For more common dependency resolution issues, see: https://docs.nestjs.com/faq/c
96
96
  }
97
97
  const dependenciesName = (dependencies || []).map(dependencyName => getDependencyName(dependencyName, '+', false));
98
98
  dependenciesName[index] = '?';
99
+ const tokenFragment = !isImportTypeIssue && name !== undefined ? ` ${dependencyName}` : '';
100
+ const contextLabel = isImportTypeIssue ? 'current' : moduleName;
99
101
  message += ` (`;
100
102
  message += dependenciesName.join(', ');
101
- message += `). Please make sure that the argument ${isImportTypeIssue ? 'dependency' : dependencyName} at index [${index}] is available in the ${isImportTypeIssue ? 'current' : moduleName} context.`;
103
+ message += `). Please make sure that the argument${tokenFragment} at index [${index}]`;
104
+ message += ` is available in the ${contextLabel} module.`;
102
105
  message += potentialSolutions;
103
106
  return message;
104
107
  };
package/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export * from './metadata-scanner.js';
11
11
  export * from './middleware/index.js';
12
12
  export * from './nest-application.js';
13
13
  export * from './nest-application-context.js';
14
- export { NestFactory } from './nest-factory.js';
14
+ export { IEntryNestModule, NestFactory } from './nest-factory.js';
15
15
  export * from './repl/index.js';
16
16
  export * from './router/index.js';
17
17
  export * from './services/index.js';
@@ -40,6 +40,11 @@ export interface InjectorDependencyContext {
40
40
  */
41
41
  dependencies?: InjectorDependency[];
42
42
  }
43
+ interface ResolutionContext {
44
+ contextId: ContextId;
45
+ inquirer?: InstanceWrapper;
46
+ effectiveInquirerId?: string;
47
+ }
43
48
  export declare class Injector {
44
49
  private readonly options?;
45
50
  private logger;
@@ -49,40 +54,48 @@ export declare class Injector {
49
54
  * Whether to enable preview mode.
50
55
  */
51
56
  preview: boolean;
57
+ /**
58
+ * Whether to enable deterministic graph snapshot generation.
59
+ */
60
+ snapshot?: boolean;
52
61
  /**
53
62
  * Function to decorate a freshly created instance.
54
63
  */
55
64
  instanceDecorator?: (target: unknown) => unknown;
56
65
  } | undefined);
57
66
  loadPrototype<T>({ token }: InstanceWrapper<T>, collection: Map<InjectionToken, InstanceWrapper<T>>, contextId?: ContextId): void;
58
- loadInstance<T>(wrapper: InstanceWrapper<T>, collection: Map<InjectionToken, InstanceWrapper>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
67
+ loadInstance<T>(wrapper: InstanceWrapper<T>, collection: Map<InjectionToken, InstanceWrapper>, moduleRef: Module, resolutionContext?: ResolutionContext): Promise<void>;
59
68
  loadMiddleware(wrapper: InstanceWrapper, collection: Map<InjectionToken, InstanceWrapper>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
60
69
  loadController(wrapper: InstanceWrapper<Controller>, moduleRef: Module, contextId?: ContextId): Promise<void>;
61
70
  loadInjectable<T = any>(wrapper: InstanceWrapper<T>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
62
- loadProvider(wrapper: InstanceWrapper<Injectable>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
71
+ loadProvider(wrapper: InstanceWrapper<Injectable>, moduleRef: Module, resolutionContext?: ResolutionContext): Promise<void>;
63
72
  applySettlementSignal<T>(instancePerContext: InstancePerContext<T>, host: InstanceWrapper<T>): SettlementSignal;
64
- resolveConstructorParams<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject: InjectorDependency[] | undefined, callback: (args: unknown[], depth?: number) => void | Promise<void>, contextId?: ContextId, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper): Promise<void>;
73
+ resolveConstructorParams<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject: InjectorDependency[] | undefined, callback: (args: unknown[], depth?: number) => void | Promise<void>, resolutionContext?: ResolutionContext, parentInquirer?: InstanceWrapper): Promise<void>;
65
74
  getClassDependencies<T>(wrapper: InstanceWrapper<T>): [InjectorDependency[], number[]];
66
75
  getFactoryProviderDependencies<T>(wrapper: InstanceWrapper<T>): [InjectorDependency[], number[]];
67
76
  reflectConstructorParams<T>(type: Type<T>): any[];
68
77
  reflectOptionalParams<T>(type: Type<T>): any[];
69
78
  reflectSelfParams<T>(type: Type<T>): any[];
70
- resolveSingleParam<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol, dependencyContext: InjectorDependencyContext, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper<any>>;
79
+ resolveSingleParam<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol, dependencyContext: InjectorDependencyContext, moduleRef: Module, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper<any>>;
71
80
  resolveParamToken<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol | ForwardReference): any;
72
- resolveComponentWrapper<T>(moduleRef: Module, token: InjectionToken, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper>;
73
- resolveComponentHost<T>(moduleRef: Module, instanceWrapper: InstanceWrapper<T | Promise<T>>, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<InstanceWrapper>;
74
- lookupComponent<T = any>(providers: Map<Function | string | symbol, InstanceWrapper>, moduleRef: Module, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper<T>>;
75
- lookupComponentInParentModules<T = any>(dependencyContext: InjectorDependencyContext, moduleRef: Module, wrapper: InstanceWrapper<T>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number): Promise<any>;
76
- lookupComponentInImports(moduleRef: Module, name: InjectionToken, wrapper: InstanceWrapper, moduleRegistry?: Set<string>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number, isTraversing?: boolean): Promise<any>;
77
- resolveProperties<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject?: InjectorDependency[], contextId?: ContextId, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper): Promise<PropertyDependency[]>;
81
+ resolveComponentWrapper<T>(moduleRef: Module, token: InjectionToken, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper>;
82
+ resolveComponentHost<T>(moduleRef: Module, instanceWrapper: InstanceWrapper<T | Promise<T>>, resolutionContext?: ResolutionContext): Promise<InstanceWrapper>;
83
+ lookupComponent<T = any>(providers: Map<Function | string | symbol, InstanceWrapper>, moduleRef: Module, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper<T>>;
84
+ lookupComponentInParentModules<T = any>(dependencyContext: InjectorDependencyContext, moduleRef: Module, wrapper: InstanceWrapper<T>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<any>;
85
+ lookupComponentInImports(moduleRef: Module, name: InjectionToken, wrapper: InstanceWrapper, moduleRegistry?: Set<string>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number, isTraversing?: boolean): Promise<any>;
86
+ resolveProperties<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject?: InjectorDependency[], resolutionContext?: ResolutionContext, parentInquirer?: InstanceWrapper): Promise<PropertyDependency[]>;
78
87
  reflectProperties<T>(type: Type<T>): PropertyDependency[];
79
88
  applyProperties<T = any>(instance: T, properties: PropertyDependency[]): void;
80
- instantiateClass<T = any>(instances: any[], wrapper: InstanceWrapper, targetMetatype: InstanceWrapper, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<T>;
89
+ instantiateClass<T = any>(instances: any[], wrapper: InstanceWrapper, targetMetatype: InstanceWrapper, resolutionContext?: ResolutionContext): Promise<T>;
81
90
  loadPerContext<T = any>(instance: T, moduleRef: Module, collection: Map<InjectionToken, InstanceWrapper>, ctx: ContextId, wrapper?: InstanceWrapper): Promise<T>;
82
91
  loadEnhancersPerContext(wrapper: InstanceWrapper, ctx: ContextId, inquirer?: InstanceWrapper): Promise<void>;
83
92
  loadCtorMetadata(metadata: InstanceWrapper<any>[], contextId: ContextId, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper): Promise<any[]>;
84
93
  loadPropertiesMetadata(metadata: PropertyMetadata[], contextId: ContextId, inquirer?: InstanceWrapper): Promise<PropertyDependency[]>;
85
94
  private getInquirerId;
95
+ private createResolutionContext;
96
+ private getContextInquirerId;
97
+ private isInContext;
98
+ private shouldSkipProviderLoading;
86
99
  /**
87
100
  * For nested TRANSIENT dependencies (TRANSIENT -> TRANSIENT) in non-static contexts,
88
101
  * returns parentInquirer to ensure each parent TRANSIENT gets its own instance.
@@ -91,6 +104,9 @@ export declare class Injector {
91
104
  * For non-TRANSIENT -> TRANSIENT, returns inquirer (current wrapper being created).
92
105
  */
93
106
  private getEffectiveInquirer;
107
+ private getEffectiveInquirerId;
108
+ private getStaticTransientResolutionContext;
109
+ private getEffectiveResolutionContext;
94
110
  private resolveScopedComponentHost;
95
111
  private isInquirerRequest;
96
112
  private isInquirer;
@@ -103,3 +119,4 @@ export declare class Injector {
103
119
  private getContextId;
104
120
  private getNowTimestamp;
105
121
  }
122
+ export {};
@@ -1,4 +1,4 @@
1
- import { Logger, } from '@nestjs/common';
1
+ import { Logger, Scope, } from '@nestjs/common';
2
2
  import { OPTIONAL_DEPS_METADATA, OPTIONAL_PROPERTY_DEPS_METADATA, PARAMTYPES_METADATA, PROPERTY_DEPS_METADATA, SELF_DECLARED_DEPS_METADATA, clc, isFunction, isNil, isObject, isString, isSymbol, isUndefined, } from '@nestjs/common/internal';
3
3
  import { iterate } from 'iterare';
4
4
  import { performance } from 'perf_hooks';
@@ -35,12 +35,13 @@ export class Injector {
35
35
  collection.set(token, wrapper);
36
36
  }
37
37
  }
38
- async loadInstance(wrapper, collection, moduleRef, contextId = STATIC_CONTEXT, inquirer) {
39
- const inquirerId = this.getInquirerId(inquirer);
40
- const instanceHost = wrapper.getInstanceByContextId(this.getContextId(contextId, wrapper), inquirerId);
38
+ async loadInstance(wrapper, collection, moduleRef, resolutionContext = { contextId: STATIC_CONTEXT }) {
39
+ const inquirerId = this.getContextInquirerId(resolutionContext);
40
+ const instanceHost = wrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, wrapper), inquirerId);
41
41
  if (instanceHost.isPending) {
42
42
  const settlementSignal = wrapper.settlementSignal;
43
- if (inquirer && settlementSignal?.isCycle(inquirer.id)) {
43
+ if (resolutionContext.inquirer &&
44
+ settlementSignal?.isCycle(resolutionContext.inquirer.id)) {
44
45
  throw new CircularDependencyException(`"${wrapper.name}"`);
45
46
  }
46
47
  return instanceHost.donePromise.then((err) => {
@@ -61,18 +62,19 @@ export class Injector {
61
62
  }
62
63
  try {
63
64
  const t0 = this.getNowTimestamp();
65
+ const localResolutionContext = this.createResolutionContext(resolutionContext.contextId, wrapper, inquirerId);
64
66
  const callback = async (instances, depth = 0) => {
65
- const properties = await this.resolveProperties(wrapper, moduleRef, inject, contextId, wrapper, inquirer);
66
- const instance = await this.instantiateClass(instances, wrapper, targetWrapper, contextId, inquirer);
67
+ const properties = await this.resolveProperties(wrapper, moduleRef, inject, localResolutionContext, resolutionContext.inquirer);
68
+ const instance = await this.instantiateClass(instances, wrapper, targetWrapper, wrapper.isTransient ? localResolutionContext : resolutionContext);
67
69
  this.applyProperties(instance, properties);
68
70
  wrapper.hierarchyLevel = depth + 1;
69
71
  wrapper.initTime = this.getNowTimestamp() - t0;
70
72
  settlementSignal.complete();
71
73
  };
72
- await this.resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId, wrapper, inquirer);
74
+ await this.resolveConstructorParams(wrapper, moduleRef, inject, callback, localResolutionContext, resolutionContext.inquirer);
73
75
  }
74
76
  catch (err) {
75
- wrapper.removeInstanceByContextId(this.getContextId(contextId, wrapper), inquirerId);
77
+ wrapper.removeInstanceByContextId(this.getContextId(resolutionContext.contextId, wrapper), inquirerId);
76
78
  settlementSignal.error(err);
77
79
  throw err;
78
80
  }
@@ -84,21 +86,24 @@ export class Injector {
84
86
  return;
85
87
  }
86
88
  targetWrapper.instance = Object.create(metatype.prototype);
87
- await this.loadInstance(wrapper, collection, moduleRef, contextId, inquirer || wrapper);
89
+ await this.loadInstance(wrapper, collection, moduleRef, this.createResolutionContext(contextId, inquirer || wrapper));
88
90
  }
89
91
  async loadController(wrapper, moduleRef, contextId = STATIC_CONTEXT) {
90
92
  const controllers = moduleRef.controllers;
91
- await this.loadInstance(wrapper, controllers, moduleRef, contextId, wrapper);
93
+ await this.loadInstance(wrapper, controllers, moduleRef, this.createResolutionContext(contextId, wrapper));
92
94
  await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
93
95
  }
94
96
  async loadInjectable(wrapper, moduleRef, contextId = STATIC_CONTEXT, inquirer) {
95
97
  const injectables = moduleRef.injectables;
96
- await this.loadInstance(wrapper, injectables, moduleRef, contextId, inquirer);
98
+ await this.loadInstance(wrapper, injectables, moduleRef, this.createResolutionContext(contextId, inquirer));
97
99
  }
98
- async loadProvider(wrapper, moduleRef, contextId = STATIC_CONTEXT, inquirer) {
100
+ async loadProvider(wrapper, moduleRef, resolutionContext = { contextId: STATIC_CONTEXT }) {
101
+ if (this.shouldSkipProviderLoading(wrapper, resolutionContext)) {
102
+ return;
103
+ }
99
104
  const providers = moduleRef.providers;
100
- await this.loadInstance(wrapper, providers, moduleRef, contextId, inquirer);
101
- await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
105
+ await this.loadInstance(wrapper, providers, moduleRef, resolutionContext);
106
+ await this.loadEnhancersPerContext(wrapper, resolutionContext.contextId, wrapper);
102
107
  }
103
108
  applySettlementSignal(instancePerContext, host) {
104
109
  const settlementSignal = new SettlementSignal();
@@ -107,10 +112,10 @@ export class Injector {
107
112
  host.settlementSignal = settlementSignal;
108
113
  return settlementSignal;
109
114
  }
110
- async resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId = STATIC_CONTEXT, inquirer, parentInquirer) {
115
+ async resolveConstructorParams(wrapper, moduleRef, inject, callback, resolutionContext = { contextId: STATIC_CONTEXT }, parentInquirer) {
111
116
  const metadata = wrapper.getCtorMetadata();
112
- if (metadata && contextId !== STATIC_CONTEXT) {
113
- const deps = await this.loadCtorMetadata(metadata, contextId, inquirer, parentInquirer);
117
+ if (metadata && resolutionContext.contextId !== STATIC_CONTEXT) {
118
+ const deps = await this.loadCtorMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer, parentInquirer);
114
119
  return callback(deps);
115
120
  }
116
121
  const isFactoryProvider = !isNil(inject);
@@ -129,25 +134,26 @@ export class Injector {
129
134
  paramBarrier.signal();
130
135
  return parentInquirer && parentInquirer.instance;
131
136
  }
132
- if (inquirer?.isTransient && parentInquirer) {
137
+ if (resolutionContext.inquirer?.isTransient && parentInquirer) {
133
138
  // When `inquirer` is transient too, inherit the parent inquirer
134
139
  // This is required to ensure that transient providers are only resolved
135
140
  // when requested
136
- inquirer.attachRootInquirer(parentInquirer);
141
+ resolutionContext.inquirer.attachRootInquirer(parentInquirer);
137
142
  }
138
- const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, contextId, inquirer, index);
143
+ const nestedResolutionContext = this.getStaticTransientResolutionContext(resolutionContext, parentInquirer);
144
+ const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, nestedResolutionContext, index);
139
145
  /*
140
146
  * Ensure that all instance wrappers are resolved at this point before we continue.
141
147
  * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
142
148
  * and result in undefined / null injection.
143
149
  */
144
150
  await paramBarrier.signalAndWait();
145
- const effectiveInquirer = this.getEffectiveInquirer(paramWrapper, inquirer, parentInquirer, contextId);
146
- const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, contextId, effectiveInquirer);
151
+ const effectiveResolutionContext = this.getEffectiveResolutionContext(paramWrapper, resolutionContext, parentInquirer);
152
+ const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, effectiveResolutionContext);
147
153
  if (paramWrapperWithInstance.hierarchyLevel > depth) {
148
154
  depth = paramWrapperWithInstance.hierarchyLevel;
149
155
  }
150
- const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance), this.getInquirerId(effectiveInquirer));
156
+ const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(effectiveResolutionContext.contextId, paramWrapperWithInstance), effectiveResolutionContext.effectiveInquirerId);
151
157
  if (!instanceHost.isResolved && !paramWrapperWithInstance.forwardRef) {
152
158
  isResolved = false;
153
159
  }
@@ -211,7 +217,7 @@ export class Injector {
211
217
  ];
212
218
  const selfParams = this.reflectSelfParams(type);
213
219
  selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
214
- return paramtypes;
220
+ return Array.from(paramtypes);
215
221
  }
216
222
  reflectOptionalParams(type) {
217
223
  return Reflect.getOwnMetadata(OPTIONAL_DEPS_METADATA, type) || [];
@@ -219,13 +225,13 @@ export class Injector {
219
225
  reflectSelfParams(type) {
220
226
  return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
221
227
  }
222
- async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, contextId = STATIC_CONTEXT, inquirer, keyOrIndex) {
228
+ async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, resolutionContext = { contextId: STATIC_CONTEXT }, keyOrIndex) {
223
229
  if (isUndefined(param)) {
224
230
  this.logger.log('Nest encountered an undefined dependency. This may be due to a circular import or a missing dependency declaration.');
225
231
  throw new UndefinedDependencyException(wrapper.name, dependencyContext, moduleRef);
226
232
  }
227
233
  const token = this.resolveParamToken(wrapper, param);
228
- return this.resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, contextId, inquirer, keyOrIndex);
234
+ return this.resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, resolutionContext, keyOrIndex);
229
235
  }
230
236
  resolveParamToken(wrapper, param) {
231
237
  if (typeof param === 'object' && 'forwardRef' in param) {
@@ -234,22 +240,22 @@ export class Injector {
234
240
  }
235
241
  return param;
236
242
  }
237
- async resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, contextId = STATIC_CONTEXT, inquirer, keyOrIndex) {
238
- this.printResolvingDependenciesLog(token, inquirer);
243
+ async resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, resolutionContext = { contextId: STATIC_CONTEXT }, keyOrIndex) {
244
+ this.printResolvingDependenciesLog(token, resolutionContext.inquirer);
239
245
  this.printLookingForProviderLog(token, moduleRef);
240
246
  const providers = moduleRef.providers;
241
- return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, contextId, inquirer, keyOrIndex);
247
+ return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, resolutionContext, keyOrIndex);
242
248
  }
243
- async resolveComponentHost(moduleRef, instanceWrapper, contextId = STATIC_CONTEXT, inquirer) {
244
- const inquirerId = this.getInquirerId(inquirer);
245
- const instanceHost = instanceWrapper.getInstanceByContextId(this.getContextId(contextId, instanceWrapper), inquirerId);
249
+ async resolveComponentHost(moduleRef, instanceWrapper, resolutionContext = { contextId: STATIC_CONTEXT }) {
250
+ const inquirerId = this.getContextInquirerId(resolutionContext);
251
+ const instanceHost = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
246
252
  if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
247
- inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
248
- await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, contextId, inquirer);
253
+ resolutionContext.inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
254
+ await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, resolutionContext);
249
255
  }
250
256
  else if (!instanceHost.isResolved &&
251
257
  instanceWrapper.forwardRef &&
252
- (contextId !== STATIC_CONTEXT || !!inquirerId)) {
258
+ (resolutionContext.contextId !== STATIC_CONTEXT || !!inquirerId)) {
253
259
  /**
254
260
  * When circular dependency has been detected between
255
261
  * either request/transient providers, we have to asynchronously
@@ -259,19 +265,19 @@ export class Injector {
259
265
  */
260
266
  instanceHost.donePromise &&
261
267
  void instanceHost.donePromise
262
- .then(() => this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer))
268
+ .then(() => this.loadProvider(instanceWrapper, moduleRef, resolutionContext))
263
269
  .catch(err => {
264
270
  instanceWrapper.settlementSignal?.error(err);
265
271
  });
266
272
  }
267
273
  if (instanceWrapper.async) {
268
- const host = instanceWrapper.getInstanceByContextId(this.getContextId(contextId, instanceWrapper), inquirerId);
274
+ const host = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
269
275
  host.instance = await host.instance;
270
- instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
276
+ instanceWrapper.setInstanceByContextId(resolutionContext.contextId, host, inquirerId);
271
277
  }
272
278
  return instanceWrapper;
273
279
  }
274
- async lookupComponent(providers, moduleRef, dependencyContext, wrapper, contextId = STATIC_CONTEXT, inquirer, keyOrIndex) {
280
+ async lookupComponent(providers, moduleRef, dependencyContext, wrapper, resolutionContext = { contextId: STATIC_CONTEXT }, keyOrIndex) {
275
281
  const token = wrapper.token || wrapper.name;
276
282
  const { name } = dependencyContext;
277
283
  if (wrapper && token === name) {
@@ -283,16 +289,16 @@ export class Injector {
283
289
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapper);
284
290
  return instanceWrapper;
285
291
  }
286
- return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId, inquirer, keyOrIndex);
292
+ return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext, keyOrIndex);
287
293
  }
288
- async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId = STATIC_CONTEXT, inquirer, keyOrIndex) {
289
- const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(), contextId, inquirer, keyOrIndex);
294
+ async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext = { contextId: STATIC_CONTEXT }, keyOrIndex) {
295
+ const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(), resolutionContext, keyOrIndex);
290
296
  if (isNil(instanceWrapper)) {
291
297
  throw new UnknownDependenciesException(wrapper.name, dependencyContext, moduleRef, { id: wrapper.id });
292
298
  }
293
299
  return instanceWrapper;
294
300
  }
295
- async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = new Set(), contextId = STATIC_CONTEXT, inquirer, keyOrIndex, isTraversing) {
301
+ async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = new Set(), resolutionContext = { contextId: STATIC_CONTEXT }, keyOrIndex, isTraversing) {
296
302
  let instanceWrapperRef = null;
297
303
  const imports = moduleRef.imports || new Set();
298
304
  const identity = (item) => item;
@@ -309,7 +315,7 @@ export class Injector {
309
315
  moduleRegistry.add(relatedModule.id);
310
316
  const { providers, exports } = relatedModule;
311
317
  if (!exports.has(name) || !providers.has(name)) {
312
- const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, contextId, inquirer, keyOrIndex, true);
318
+ const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, resolutionContext, keyOrIndex, true);
313
319
  if (instanceRef) {
314
320
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceRef);
315
321
  return instanceRef;
@@ -319,8 +325,8 @@ export class Injector {
319
325
  this.printFoundInModuleLog(name, relatedModule);
320
326
  instanceWrapperRef = providers.get(name);
321
327
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapperRef);
322
- const inquirerId = this.getInquirerId(inquirer);
323
- const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(contextId, instanceWrapperRef), inquirerId);
328
+ const inquirerId = this.getContextInquirerId(resolutionContext);
329
+ const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapperRef), inquirerId);
324
330
  if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
325
331
  /*
326
332
  * Provider will be loaded shortly in resolveComponentHost() once we pass the current
@@ -332,13 +338,13 @@ export class Injector {
332
338
  }
333
339
  return instanceWrapperRef;
334
340
  }
335
- async resolveProperties(wrapper, moduleRef, inject, contextId = STATIC_CONTEXT, inquirer, parentInquirer) {
341
+ async resolveProperties(wrapper, moduleRef, inject, resolutionContext = { contextId: STATIC_CONTEXT }, parentInquirer) {
336
342
  if (!isNil(inject)) {
337
343
  return [];
338
344
  }
339
345
  const metadata = wrapper.getPropertiesMetadata();
340
- if (metadata && contextId !== STATIC_CONTEXT) {
341
- return this.loadPropertiesMetadata(metadata, contextId, inquirer);
346
+ if (metadata && resolutionContext.contextId !== STATIC_CONTEXT) {
347
+ return this.loadPropertiesMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer);
342
348
  }
343
349
  const properties = this.reflectProperties(wrapper.metatype);
344
350
  const propertyBarrier = new Barrier(properties.length);
@@ -355,19 +361,20 @@ export class Injector {
355
361
  propertyBarrier.signal();
356
362
  return parentInquirer && parentInquirer.instance;
357
363
  }
358
- const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, contextId, inquirer, item.key);
364
+ const nestedResolutionContext = this.getStaticTransientResolutionContext(resolutionContext, parentInquirer);
365
+ const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, nestedResolutionContext, item.key);
359
366
  /*
360
367
  * Ensure that all instance wrappers are resolved at this point before we continue.
361
368
  * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
362
369
  * and result in undefined / null injection.
363
370
  */
364
371
  await propertyBarrier.signalAndWait();
365
- const effectivePropertyInquirer = this.getEffectiveInquirer(paramWrapper, inquirer, parentInquirer, contextId);
366
- const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, contextId, effectivePropertyInquirer);
372
+ const effectivePropertyResolutionContext = this.getEffectiveResolutionContext(paramWrapper, resolutionContext, parentInquirer);
373
+ const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, effectivePropertyResolutionContext);
367
374
  if (!paramWrapperWithInstance) {
368
375
  return undefined;
369
376
  }
370
- const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance), this.getInquirerId(effectivePropertyInquirer));
377
+ const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(effectivePropertyResolutionContext.contextId, paramWrapperWithInstance), effectivePropertyResolutionContext.effectiveInquirerId);
371
378
  return instanceHost.instance;
372
379
  }
373
380
  catch (err) {
@@ -405,14 +412,11 @@ export class Injector {
405
412
  .filter(item => !isNil(item.instance))
406
413
  .forEach(item => (instance[item.key] = item.instance));
407
414
  }
408
- async instantiateClass(instances, wrapper, targetMetatype, contextId = STATIC_CONTEXT, inquirer) {
415
+ async instantiateClass(instances, wrapper, targetMetatype, resolutionContext = { contextId: STATIC_CONTEXT }) {
409
416
  const { metatype, inject } = wrapper;
410
- const inquirerId = this.getInquirerId(inquirer);
411
- const instanceHost = targetMetatype.getInstanceByContextId(this.getContextId(contextId, targetMetatype), inquirerId);
412
- const isInContext = wrapper.isStatic(contextId, inquirer) ||
413
- wrapper.isInRequestScope(contextId, inquirer) ||
414
- wrapper.isLazyTransient(contextId, inquirer) ||
415
- wrapper.isExplicitlyRequested(contextId, inquirer);
417
+ const inquirerId = this.getContextInquirerId(resolutionContext);
418
+ const instanceHost = targetMetatype.getInstanceByContextId(this.getContextId(resolutionContext.contextId, targetMetatype), inquirerId);
419
+ const isInContext = this.isInContext(wrapper, resolutionContext);
416
420
  if (this.options?.preview && !wrapper.host?.initOnPreview) {
417
421
  instanceHost.isResolved = true;
418
422
  return instanceHost.instance;
@@ -438,16 +442,22 @@ export class Injector {
438
442
  const injectionToken = instance.constructor;
439
443
  wrapper = collection.get(injectionToken);
440
444
  }
441
- await this.loadInstance(wrapper, collection, moduleRef, ctx, wrapper);
445
+ else {
446
+ wrapper = collection.get(wrapper.token) ?? wrapper;
447
+ }
448
+ await this.loadInstance(wrapper, collection, moduleRef, this.createResolutionContext(ctx, wrapper));
442
449
  await this.loadEnhancersPerContext(wrapper, ctx, wrapper);
443
450
  const host = wrapper.getInstanceByContextId(this.getContextId(ctx, wrapper), wrapper.id);
444
451
  return host && host.instance;
445
452
  }
446
453
  async loadEnhancersPerContext(wrapper, ctx, inquirer) {
454
+ if (ctx === STATIC_CONTEXT) {
455
+ return;
456
+ }
447
457
  const enhancers = wrapper.getEnhancersMetadata() || [];
448
458
  const loadEnhancer = (item) => {
449
459
  const hostModule = item.host;
450
- return this.loadInstance(item, hostModule.injectables, hostModule, ctx, inquirer);
460
+ return this.loadInstance(item, hostModule.injectables, hostModule, this.createResolutionContext(ctx, inquirer));
451
461
  };
452
462
  await Promise.all(enhancers.map(loadEnhancer));
453
463
  }
@@ -455,14 +465,14 @@ export class Injector {
455
465
  const hosts = await Promise.all(metadata.map(async (item) => this.resolveScopedComponentHost(item, contextId, inquirer, parentInquirer)));
456
466
  return hosts.map((item, index) => {
457
467
  const dependency = metadata[index];
458
- const effectiveInquirer = this.getEffectiveInquirer(dependency, inquirer, parentInquirer, contextId);
459
- return item?.getInstanceByContextId(this.getContextId(contextId, item), this.getInquirerId(effectiveInquirer)).instance;
468
+ const effectiveInquirerId = this.getEffectiveInquirerId(dependency, this.createResolutionContext(contextId, inquirer), parentInquirer);
469
+ return item?.getInstanceByContextId(this.getContextId(contextId, item), effectiveInquirerId).instance;
460
470
  });
461
471
  }
462
472
  async loadPropertiesMetadata(metadata, contextId, inquirer) {
463
473
  const dependenciesHosts = await Promise.all(metadata.map(async ({ wrapper: item, key }) => ({
464
474
  key,
465
- host: await this.resolveComponentHost(item.host, item, contextId, inquirer),
475
+ host: await this.resolveComponentHost(item.host, item, this.createResolutionContext(contextId, inquirer)),
466
476
  })));
467
477
  const inquirerId = this.getInquirerId(inquirer);
468
478
  return dependenciesHosts.map(({ key, host }) => ({
@@ -474,6 +484,34 @@ export class Injector {
474
484
  getInquirerId(inquirer) {
475
485
  return inquirer ? inquirer.id : undefined;
476
486
  }
487
+ createResolutionContext(contextId, inquirer, effectiveInquirerId) {
488
+ return {
489
+ contextId,
490
+ inquirer,
491
+ effectiveInquirerId,
492
+ };
493
+ }
494
+ getContextInquirerId({ inquirer, effectiveInquirerId, }) {
495
+ return effectiveInquirerId ?? this.getInquirerId(inquirer);
496
+ }
497
+ isInContext(wrapper, resolutionContext) {
498
+ return (wrapper.isStatic(resolutionContext.contextId, resolutionContext.inquirer) ||
499
+ wrapper.isInRequestScope(resolutionContext.contextId, resolutionContext.inquirer) ||
500
+ wrapper.isLazyTransient(resolutionContext.contextId, resolutionContext.inquirer) ||
501
+ wrapper.isExplicitlyRequested(resolutionContext.contextId, resolutionContext.inquirer));
502
+ }
503
+ shouldSkipProviderLoading(wrapper, resolutionContext) {
504
+ const isSnapshotGraphCompilation = !!this.options?.snapshot;
505
+ const isStaticContext = resolutionContext.contextId === STATIC_CONTEXT;
506
+ const hasNoInquirer = !resolutionContext.inquirer;
507
+ const isTopLevelStaticTransientOrRequestProvider = hasNoInquirer && (wrapper.isTransient || wrapper.scope === Scope.REQUEST);
508
+ const isStaticInquirerOutsideResolutionContext = !!resolutionContext.inquirer &&
509
+ !this.isInContext(resolutionContext.inquirer, this.createResolutionContext(resolutionContext.contextId, resolutionContext.inquirer));
510
+ const shouldSkipForStaticBootstrap = isStaticContext &&
511
+ (isTopLevelStaticTransientOrRequestProvider ||
512
+ isStaticInquirerOutsideResolutionContext);
513
+ return !isSnapshotGraphCompilation && shouldSkipForStaticBootstrap;
514
+ }
477
515
  /**
478
516
  * For nested TRANSIENT dependencies (TRANSIENT -> TRANSIENT) in non-static contexts,
479
517
  * returns parentInquirer to ensure each parent TRANSIENT gets its own instance.
@@ -481,18 +519,45 @@ export class Injector {
481
519
  * can be used by multiple parents, causing nested TRANSIENTs to be shared incorrectly.
482
520
  * For non-TRANSIENT -> TRANSIENT, returns inquirer (current wrapper being created).
483
521
  */
484
- getEffectiveInquirer(dependency, inquirer, parentInquirer, contextId) {
485
- return dependency?.isTransient &&
522
+ getEffectiveInquirer(dependency, resolutionContext, parentInquirer) {
523
+ const { inquirer, contextId } = resolutionContext;
524
+ if (dependency?.isTransient && inquirer?.isTransient && parentInquirer) {
525
+ if (contextId === STATIC_CONTEXT) {
526
+ return inquirer.getRootInquirer() ?? parentInquirer;
527
+ }
528
+ return parentInquirer;
529
+ }
530
+ return inquirer;
531
+ }
532
+ getEffectiveInquirerId(dependency, resolutionContext, parentInquirer) {
533
+ const { contextId, inquirer, effectiveInquirerId } = resolutionContext;
534
+ if (contextId === STATIC_CONTEXT &&
535
+ dependency?.isTransient &&
486
536
  inquirer?.isTransient &&
487
- parentInquirer &&
488
- contextId !== STATIC_CONTEXT
489
- ? parentInquirer
490
- : inquirer;
537
+ parentInquirer) {
538
+ const baseInquirerId = effectiveInquirerId ?? this.getInquirerId(parentInquirer);
539
+ return `${baseInquirerId}:${inquirer.id}`;
540
+ }
541
+ const effectiveInquirer = this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer);
542
+ return this.getInquirerId(effectiveInquirer);
543
+ }
544
+ getStaticTransientResolutionContext(resolutionContext, parentInquirer) {
545
+ const { contextId, inquirer, effectiveInquirerId } = resolutionContext;
546
+ if (contextId === STATIC_CONTEXT &&
547
+ inquirer?.isTransient &&
548
+ parentInquirer) {
549
+ const baseInquirerId = effectiveInquirerId ?? this.getInquirerId(parentInquirer);
550
+ return this.createResolutionContext(contextId, inquirer, `${baseInquirerId}:${inquirer.id}`);
551
+ }
552
+ return resolutionContext;
553
+ }
554
+ getEffectiveResolutionContext(dependency, resolutionContext, parentInquirer) {
555
+ return this.createResolutionContext(resolutionContext.contextId, this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer), this.getEffectiveInquirerId(dependency, resolutionContext, parentInquirer));
491
556
  }
492
557
  resolveScopedComponentHost(item, contextId, inquirer, parentInquirer) {
493
558
  return this.isInquirerRequest(item, parentInquirer)
494
559
  ? parentInquirer
495
- : this.resolveComponentHost(item.host, item, contextId, this.getEffectiveInquirer(item, inquirer, parentInquirer, contextId));
560
+ : this.resolveComponentHost(item.host, item, this.getEffectiveResolutionContext(item, this.createResolutionContext(contextId, inquirer), parentInquirer));
496
561
  }
497
562
  isInquirerRequest(item, parentInquirer) {
498
563
  return item.isTransient && item.name === INQUIRER && parentInquirer;
@@ -2,6 +2,7 @@ import { Scope } from '@nestjs/common';
2
2
  import { getClassScope } from '../helpers/get-class-scope.js';
3
3
  import { isDurable } from '../helpers/is-durable.js';
4
4
  import { AbstractInstanceResolver } from './abstract-instance-resolver.js';
5
+ import { STATIC_CONTEXT } from './constants.js';
5
6
  import { Injector } from './injector.js';
6
7
  import { InstanceLinksHost } from './instance-links-host.js';
7
8
  import { InstanceWrapper } from './instance-wrapper.js';
@@ -18,9 +19,11 @@ export class ModuleRef extends AbstractInstanceResolver {
18
19
  constructor(container) {
19
20
  super();
20
21
  this.container = container;
22
+ const contextOptions = container.contextOptions;
21
23
  this.injector = new Injector({
22
- preview: container.contextOptions?.preview,
23
- instanceDecorator: container.contextOptions?.instrument?.instanceDecorator,
24
+ preview: contextOptions?.preview ?? false,
25
+ snapshot: contextOptions?.snapshot,
26
+ instanceDecorator: contextOptions?.instrument?.instanceDecorator,
24
27
  });
25
28
  }
26
29
  introspect(token) {
@@ -46,20 +49,30 @@ export class ModuleRef extends AbstractInstanceResolver {
46
49
  durable: isDurable(type),
47
50
  host: moduleRef,
48
51
  });
49
- /* eslint-disable-next-line no-async-promise-executor */
50
- return new Promise(async (resolve, reject) => {
51
- try {
52
+ if (type?.prototype) {
53
+ wrapper.setInstanceByContextId(contextId ?? STATIC_CONTEXT, {
54
+ instance: Object.create(type.prototype),
55
+ isResolved: false,
56
+ isPending: false,
57
+ });
58
+ }
59
+ return new Promise((resolve, reject) => {
60
+ const loadInstance = async () => {
52
61
  const callback = async (instances) => {
53
- const properties = await this.injector.resolveProperties(wrapper, moduleRef, undefined, contextId);
62
+ const properties = await this.injector.resolveProperties(wrapper, moduleRef, undefined, {
63
+ contextId: contextId ?? STATIC_CONTEXT,
64
+ inquirer: wrapper,
65
+ });
54
66
  const instance = new type(...instances);
55
67
  this.injector.applyProperties(instance, properties);
56
68
  resolve(instance);
57
69
  };
58
- await this.injector.resolveConstructorParams(wrapper, moduleRef, undefined, callback, contextId);
59
- }
60
- catch (err) {
61
- reject(err);
62
- }
70
+ await this.injector.resolveConstructorParams(wrapper, moduleRef, undefined, callback, {
71
+ contextId: contextId ?? STATIC_CONTEXT,
72
+ inquirer: wrapper,
73
+ });
74
+ };
75
+ void loadInstance().catch(reject);
63
76
  });
64
77
  }
65
78
  }
@@ -178,7 +178,10 @@ export class MiddlewareModule {
178
178
  const middlewareFunction = isMethodAll
179
179
  ? proxy
180
180
  : (req, res, next) => {
181
- if (applicationRef.getRequestMethod?.(req) === requestMethod) {
181
+ const actualRequestMethod = applicationRef.getRequestMethod?.(req);
182
+ if (actualRequestMethod === requestMethod ||
183
+ (actualRequestMethod === RequestMethod[RequestMethod.HEAD] &&
184
+ requestMethod === RequestMethod[RequestMethod.GET])) {
182
185
  return proxy(req, res, next);
183
186
  }
184
187
  return next();
package/nest-factory.d.ts CHANGED
@@ -2,7 +2,12 @@ import type { DynamicModule, ForwardReference, INestApplication, INestApplicatio
2
2
  import { AbstractHttpAdapter } from './adapters/http-adapter.js';
3
3
  import { type NestMicroserviceOptions, type NestApplicationContextOptions } from '@nestjs/common/internal';
4
4
  import { type NestApplicationOptions } from '@nestjs/common';
5
- type IEntryNestModule = Type<any> | DynamicModule | ForwardReference | Promise<IEntryNestModule>;
5
+ /**
6
+ * Represents the entry (root) module type accepted by the NestFactory methods.
7
+ *
8
+ * @publicApi
9
+ */
10
+ export type IEntryNestModule = Type<any> | DynamicModule | ForwardReference | Promise<IEntryNestModule>;
6
11
  /**
7
12
  * @publicApi
8
13
  */
@@ -79,4 +84,3 @@ export declare class NestFactoryStatic {
79
84
  * @publicApi
80
85
  */
81
86
  export declare const NestFactory: NestFactoryStatic;
82
- export {};
package/nest-factory.js CHANGED
@@ -91,6 +91,7 @@ export class NestFactoryStatic {
91
91
  : UuidFactoryMode.Random;
92
92
  const injector = new Injector({
93
93
  preview: options.preview,
94
+ snapshot: options.snapshot,
94
95
  instanceDecorator: options.instrument?.instanceDecorator,
95
96
  });
96
97
  const instanceLoader = new InstanceLoader(container, injector, graphInspector);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/core",
3
- "version": "12.0.0-alpha.2",
3
+ "version": "12.0.0-alpha.4",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@core)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -42,12 +42,12 @@
42
42
  "@nuxt/opencollective": "0.4.1",
43
43
  "fast-safe-stringify": "2.1.1",
44
44
  "iterare": "1.2.1",
45
- "path-to-regexp": "8.3.0",
45
+ "path-to-regexp": "8.4.2",
46
46
  "tslib": "2.8.1",
47
47
  "uid": "2.0.2"
48
48
  },
49
49
  "devDependencies": {
50
- "@nestjs/common": "^12.0.0-alpha.2"
50
+ "@nestjs/common": "^12.0.0-alpha.4"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@nestjs/common": "^11.0.0",
@@ -68,5 +68,5 @@
68
68
  "optional": true
69
69
  }
70
70
  },
71
- "gitHead": "011bff66014e24e82b25f7acf9302b9efb70b02b"
71
+ "gitHead": "415e4d33707e74998d8456be7bd305cedb6508e3"
72
72
  }
@@ -176,7 +176,12 @@ export class RouterExecutionContext {
176
176
  const isSseHandler = !!this.reflectSse(callback);
177
177
  if (isSseHandler) {
178
178
  return async (result, res, req) => {
179
- await this.responseController.sse(result, res.raw || res, req.raw || req, { additionalHeaders: res.getHeaders?.() });
179
+ const rawResponse = res.raw ?? res;
180
+ await this.responseController.sse(result, rawResponse, req.raw || req, {
181
+ additionalHeaders: res.getHeaders?.(),
182
+ statusCode: res.statusCode ??
183
+ rawResponse.statusCode,
184
+ });
180
185
  };
181
186
  }
182
187
  return async (result, res) => {
@@ -22,7 +22,8 @@ export declare class RouterResponseController {
22
22
  setHeaders<TResponse = unknown>(response: TResponse, headers: CustomHeader[]): void;
23
23
  setStatus<TResponse = unknown>(response: TResponse, statusCode: number): void;
24
24
  sse<TInput extends Observable<unknown> = any, TResponse extends WritableHeaderStream = any, TRequest extends IncomingMessage = any>(result: TInput | Promise<TInput>, response: TResponse, request: TRequest, options?: {
25
- additionalHeaders: AdditionalHeaders;
25
+ additionalHeaders?: AdditionalHeaders;
26
+ statusCode?: number;
26
27
  }): Promise<void>;
27
28
  private assertObservable;
28
29
  }
@@ -54,37 +54,71 @@ export class RouterResponseController {
54
54
  const observableResult = await Promise.resolve(result);
55
55
  this.assertObservable(observableResult);
56
56
  const stream = new SseStream(request);
57
- // Extract custom status code from response if it was set
58
- const customStatusCode = response.statusCode;
59
- const pipeOptions = typeof customStatusCode !== 'undefined'
60
- ? { ...options, statusCode: customStatusCode }
61
- : options;
62
- stream.pipe(response, pipeOptions);
63
- const subscription = observableResult
64
- .pipe(map((message) => {
65
- if (isObject(message)) {
66
- return message;
67
- }
68
- return { data: message };
69
- }), concatMap(message => new Promise(resolve => stream.writeMessage(message, () => resolve()))), catchError(err => {
70
- const data = err instanceof Error ? err.message : err;
71
- stream.writeMessage({ type: 'error', data }, writeError => {
72
- if (writeError) {
73
- this.logger.error(writeError);
57
+ const statusCode = options?.statusCode ??
58
+ response.statusCode ??
59
+ 200;
60
+ stream.pipe(response, {
61
+ additionalHeaders: options?.additionalHeaders,
62
+ statusCode,
63
+ });
64
+ return new Promise((resolve, reject) => {
65
+ let settled = false;
66
+ const onClose = () => {
67
+ settled = true;
68
+ subscription.unsubscribe();
69
+ if (!stream.writableEnded) {
70
+ stream.end();
74
71
  }
75
- });
76
- return EMPTY;
77
- }))
78
- .subscribe({
79
- complete: () => {
80
72
  response.end();
81
- },
82
- });
83
- request.on('close', () => {
84
- subscription.unsubscribe();
85
- if (!stream.writableEnded) {
86
- stream.end();
87
- }
73
+ resolve();
74
+ };
75
+ const subscription = observableResult
76
+ .pipe(map((message) => {
77
+ if (isObject(message)) {
78
+ return message;
79
+ }
80
+ return { data: message };
81
+ }), concatMap(message => new Promise(resolve => stream.writeMessage(message, () => resolve()))), catchError(err => {
82
+ if (!stream.headersCommitted) {
83
+ throw err;
84
+ }
85
+ const data = err instanceof Error ? err.message : err;
86
+ stream.writeMessage({ type: 'error', data }, writeError => {
87
+ if (writeError) {
88
+ this.logger.error(writeError);
89
+ }
90
+ });
91
+ return EMPTY;
92
+ }))
93
+ .subscribe({
94
+ error: err => {
95
+ settled = true;
96
+ request.removeListener('close', onClose);
97
+ if (!stream.writableEnded) {
98
+ stream.end();
99
+ }
100
+ reject(err);
101
+ },
102
+ complete: () => {
103
+ settled = true;
104
+ request.removeListener('close', onClose);
105
+ if (!stream.writableEnded) {
106
+ stream.end();
107
+ }
108
+ resolve();
109
+ },
110
+ });
111
+ // Commit SSE headers on the next macrotask. Pipe validation errors
112
+ // propagate through microtasks (which complete before macrotasks),
113
+ // so if the lifecycle errored, `settled` is already true and we
114
+ // skip the write. Otherwise headers are sent immediately rather
115
+ // than waiting for the first Observable emission.
116
+ setTimeout(() => {
117
+ if (!settled) {
118
+ stream.commitHeaders();
119
+ }
120
+ }, 0);
121
+ request.on('close', onClose);
88
122
  });
89
123
  }
90
124
  assertObservable(value) {
@@ -28,12 +28,24 @@ export type HeaderStream = WritableHeaderStream & ReadHeaders;
28
28
  */
29
29
  export declare class SseStream extends Transform {
30
30
  private lastEventId;
31
+ private _headersCommitted;
32
+ private _destination;
33
+ private _statusCode;
34
+ private _additionalHeaders;
31
35
  constructor(req?: IncomingMessage);
36
+ get headersCommitted(): boolean;
32
37
  pipe<T extends WritableHeaderStream>(destination: T, options?: {
33
38
  additionalHeaders?: AdditionalHeaders;
34
39
  statusCode?: number;
35
40
  end?: boolean;
36
41
  }): T;
42
+ /**
43
+ * Writes SSE headers to the destination if they have not been sent yet.
44
+ * Headers are deferred until the first message so that, if the observable
45
+ * errors before any data is emitted, the HTTP status code can still be
46
+ * changed by an exception filter.
47
+ */
48
+ commitHeaders(): void;
37
49
  _transform(message: MessageEvent, encoding: string, callback: (error?: Error | null, data?: any) => void): void;
38
50
  /**
39
51
  * Calls `.write` but handles the drain if needed
@@ -24,6 +24,10 @@ function toDataString(data) {
24
24
  */
25
25
  export class SseStream extends Transform {
26
26
  lastEventId = null;
27
+ _headersCommitted = false;
28
+ _destination = null;
29
+ _statusCode = 200;
30
+ _additionalHeaders;
27
31
  constructor(req) {
28
32
  super({ objectMode: true });
29
33
  if (req && req.socket) {
@@ -32,11 +36,34 @@ export class SseStream extends Transform {
32
36
  req.socket.setTimeout(0);
33
37
  }
34
38
  }
39
+ get headersCommitted() {
40
+ return this._headersCommitted;
41
+ }
35
42
  pipe(destination, options) {
36
- if (destination.writeHead) {
37
- const statusCode = options?.statusCode ?? 200;
38
- destination.writeHead(statusCode, {
39
- ...options?.additionalHeaders,
43
+ this._destination = destination;
44
+ this._statusCode = options?.statusCode ?? 200;
45
+ this._additionalHeaders = options?.additionalHeaders;
46
+ return super.pipe(destination, options);
47
+ }
48
+ /**
49
+ * Writes SSE headers to the destination if they have not been sent yet.
50
+ * Headers are deferred until the first message so that, if the observable
51
+ * errors before any data is emitted, the HTTP status code can still be
52
+ * changed by an exception filter.
53
+ */
54
+ commitHeaders() {
55
+ if (this._headersCommitted || !this._destination) {
56
+ return;
57
+ }
58
+ if (this._destination.writableEnded) {
59
+ return;
60
+ }
61
+ this._headersCommitted = true;
62
+ const statusCode = this._statusCode ?? 200;
63
+ const additionalHeaders = this._additionalHeaders;
64
+ if (this._destination.writeHead) {
65
+ this._destination.writeHead(statusCode, {
66
+ ...additionalHeaders,
40
67
  // See https://github.com/dunglas/mercure/blob/master/hub/subscribe.go#L124-L130
41
68
  'Content-Type': 'text/event-stream',
42
69
  Connection: 'keep-alive',
@@ -47,15 +74,19 @@ export class SseStream extends Transform {
47
74
  // NGINX support https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-buffering
48
75
  'X-Accel-Buffering': 'no',
49
76
  });
50
- destination.flushHeaders?.();
77
+ this._destination.flushHeaders?.();
51
78
  }
52
- destination.write('\n');
53
- return super.pipe(destination, options);
79
+ this._destination.write('\n');
54
80
  }
55
81
  _transform(message, encoding, callback) {
56
- let data = message.type ? `event: ${message.type}\n` : '';
57
- data += message.id ? `id: ${message.id}\n` : '';
58
- data += message.retry ? `retry: ${message.retry}\n` : '';
82
+ this.commitHeaders();
83
+ const sanitize = (val) => String(val).replace(/[\r\n]/g, '');
84
+ let data = message.type ? `event: ${sanitize(message.type)}\n` : '';
85
+ data +=
86
+ message.id !== undefined && message.id !== null
87
+ ? `id: ${sanitize(message.id)}\n`
88
+ : '';
89
+ data += message.retry ? `retry: ${sanitize(message.retry)}\n` : '';
59
90
  data += message.data ? toDataString(message.data) : '';
60
91
  data += '\n';
61
92
  this.push(data);
@@ -65,7 +96,7 @@ export class SseStream extends Transform {
65
96
  * Calls `.write` but handles the drain if needed
66
97
  */
67
98
  writeMessage(message, cb) {
68
- if (!message.id) {
99
+ if (message.id === undefined || message.id === null) {
69
100
  this.lastEventId++;
70
101
  message.id = this.lastEventId.toString();
71
102
  }
@@ -1,12 +0,0 @@
1
- import { InjectionToken } from '@nestjs/common';
2
- import { InstanceWrapper } from '../instance-wrapper';
3
- /**
4
- * Returns the instances which are transient
5
- * @param instances The instances which should be checked whether they are transient
6
- */
7
- export declare function getTransientInstances(instances: [InjectionToken, InstanceWrapper][]): InstanceWrapper[];
8
- /**
9
- * Returns the instances which are not transient
10
- * @param instances The instances which should be checked whether they are transient
11
- */
12
- export declare function getNonTransientInstances(instances: [InjectionToken, InstanceWrapper][]): InstanceWrapper[];
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTransientInstances = getTransientInstances;
4
- exports.getNonTransientInstances = getNonTransientInstances;
5
- const iterare_1 = require("iterare");
6
- /**
7
- * Returns the instances which are transient
8
- * @param instances The instances which should be checked whether they are transient
9
- */
10
- function getTransientInstances(instances) {
11
- return (0, iterare_1.iterate)(instances)
12
- .filter(([_, wrapper]) => wrapper.isDependencyTreeStatic())
13
- .map(([_, wrapper]) => wrapper.getStaticTransientInstances())
14
- .flatten()
15
- .filter(item => !!item)
16
- .map(({ instance }) => instance)
17
- .toArray();
18
- }
19
- /**
20
- * Returns the instances which are not transient
21
- * @param instances The instances which should be checked whether they are transient
22
- */
23
- function getNonTransientInstances(instances) {
24
- return (0, iterare_1.iterate)(instances)
25
- .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic() && !wrapper.isTransient)
26
- .map(([key, { instance }]) => instance)
27
- .toArray();
28
- }