@nestjs/core 11.1.19 → 11.1.21

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-end and back-end 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
 
@@ -39,6 +39,11 @@ export interface InjectorDependencyContext {
39
39
  */
40
40
  dependencies?: InjectorDependency[];
41
41
  }
42
+ interface ResolutionContext {
43
+ contextId: ContextId;
44
+ inquirer?: InstanceWrapper;
45
+ effectiveInquirerId?: string;
46
+ }
42
47
  export declare class Injector {
43
48
  private readonly options?;
44
49
  private logger;
@@ -48,40 +53,48 @@ export declare class Injector {
48
53
  * Whether to enable preview mode.
49
54
  */
50
55
  preview: boolean;
56
+ /**
57
+ * Whether to enable deterministic graph snapshot generation.
58
+ */
59
+ snapshot?: boolean;
51
60
  /**
52
61
  * Function to decorate a freshly created instance.
53
62
  */
54
63
  instanceDecorator?: (target: unknown) => unknown;
55
64
  } | undefined);
56
65
  loadPrototype<T>({ token }: InstanceWrapper<T>, collection: Map<InjectionToken, InstanceWrapper<T>>, contextId?: ContextId): void;
57
- loadInstance<T>(wrapper: InstanceWrapper<T>, collection: Map<InjectionToken, InstanceWrapper>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
66
+ loadInstance<T>(wrapper: InstanceWrapper<T>, collection: Map<InjectionToken, InstanceWrapper>, moduleRef: Module, resolutionContext?: ResolutionContext): Promise<void>;
58
67
  loadMiddleware(wrapper: InstanceWrapper, collection: Map<InjectionToken, InstanceWrapper>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
59
68
  loadController(wrapper: InstanceWrapper<Controller>, moduleRef: Module, contextId?: ContextId): Promise<void>;
60
69
  loadInjectable<T = any>(wrapper: InstanceWrapper<T>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
61
- loadProvider(wrapper: InstanceWrapper<Injectable>, moduleRef: Module, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<void>;
70
+ loadProvider(wrapper: InstanceWrapper<Injectable>, moduleRef: Module, resolutionContext?: ResolutionContext): Promise<void>;
62
71
  applySettlementSignal<T>(instancePerContext: InstancePerContext<T>, host: InstanceWrapper<T>): SettlementSignal;
63
- resolveConstructorParams<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject: InjectorDependency[] | undefined, callback: (args: unknown[]) => void | Promise<void>, contextId?: ContextId, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper): Promise<void>;
72
+ resolveConstructorParams<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject: InjectorDependency[] | undefined, callback: (args: unknown[]) => void | Promise<void>, resolutionContext?: ResolutionContext, parentInquirer?: InstanceWrapper): Promise<void>;
64
73
  getClassDependencies<T>(wrapper: InstanceWrapper<T>): [InjectorDependency[], number[]];
65
74
  getFactoryProviderDependencies<T>(wrapper: InstanceWrapper<T>): [InjectorDependency[], number[]];
66
75
  reflectConstructorParams<T>(type: Type<T>): any[];
67
76
  reflectOptionalParams<T>(type: Type<T>): any[];
68
77
  reflectSelfParams<T>(type: Type<T>): any[];
69
- 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>>;
78
+ resolveSingleParam<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol, dependencyContext: InjectorDependencyContext, moduleRef: Module, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper<any>>;
70
79
  resolveParamToken<T>(wrapper: InstanceWrapper<T>, param: Type<any> | string | symbol | ForwardReference): any;
71
- resolveComponentWrapper<T>(moduleRef: Module, token: InjectionToken, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper>;
72
- resolveComponentHost<T>(moduleRef: Module, instanceWrapper: InstanceWrapper<T | Promise<T>>, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<InstanceWrapper>;
73
- 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>>;
74
- lookupComponentInParentModules<T = any>(dependencyContext: InjectorDependencyContext, moduleRef: Module, wrapper: InstanceWrapper<T>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number): Promise<any>;
75
- lookupComponentInImports(moduleRef: Module, name: InjectionToken, wrapper: InstanceWrapper, moduleRegistry?: Set<string>, contextId?: ContextId, inquirer?: InstanceWrapper, keyOrIndex?: symbol | string | number, isTraversing?: boolean): Promise<any>;
76
- resolveProperties<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject?: InjectorDependency[], contextId?: ContextId, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper): Promise<PropertyDependency[]>;
80
+ resolveComponentWrapper<T>(moduleRef: Module, token: InjectionToken, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<InstanceWrapper>;
81
+ resolveComponentHost<T>(moduleRef: Module, instanceWrapper: InstanceWrapper<T | Promise<T>>, resolutionContext?: ResolutionContext): Promise<InstanceWrapper>;
82
+ 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>>;
83
+ lookupComponentInParentModules<T = any>(dependencyContext: InjectorDependencyContext, moduleRef: Module, wrapper: InstanceWrapper<T>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number): Promise<any>;
84
+ lookupComponentInImports(moduleRef: Module, name: InjectionToken, wrapper: InstanceWrapper, moduleRegistry?: Set<string>, resolutionContext?: ResolutionContext, keyOrIndex?: symbol | string | number, isTraversing?: boolean): Promise<any>;
85
+ resolveProperties<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject?: InjectorDependency[], resolutionContext?: ResolutionContext, parentInquirer?: InstanceWrapper): Promise<PropertyDependency[]>;
77
86
  reflectProperties<T>(type: Type<T>): PropertyDependency[];
78
87
  applyProperties<T = any>(instance: T, properties: PropertyDependency[]): void;
79
- instantiateClass<T = any>(instances: any[], wrapper: InstanceWrapper, targetMetatype: InstanceWrapper, contextId?: ContextId, inquirer?: InstanceWrapper): Promise<T>;
88
+ instantiateClass<T = any>(instances: any[], wrapper: InstanceWrapper, targetMetatype: InstanceWrapper, resolutionContext?: ResolutionContext): Promise<T>;
80
89
  loadPerContext<T = any>(instance: T, moduleRef: Module, collection: Map<InjectionToken, InstanceWrapper>, ctx: ContextId, wrapper?: InstanceWrapper): Promise<T>;
81
90
  loadEnhancersPerContext(wrapper: InstanceWrapper, ctx: ContextId, inquirer?: InstanceWrapper): Promise<void>;
82
91
  loadCtorMetadata(metadata: InstanceWrapper<any>[], contextId: ContextId, inquirer?: InstanceWrapper, parentInquirer?: InstanceWrapper): Promise<any[]>;
83
92
  loadPropertiesMetadata(metadata: PropertyMetadata[], contextId: ContextId, inquirer?: InstanceWrapper): Promise<PropertyDependency[]>;
84
93
  private getInquirerId;
94
+ private createResolutionContext;
95
+ private getContextInquirerId;
96
+ private isInContext;
97
+ private shouldSkipProviderLoading;
85
98
  /**
86
99
  * For nested TRANSIENT dependencies (TRANSIENT -> TRANSIENT) in non-static contexts,
87
100
  * returns parentInquirer to ensure each parent TRANSIENT gets its own instance.
@@ -90,6 +103,9 @@ export declare class Injector {
90
103
  * For non-TRANSIENT -> TRANSIENT, returns inquirer (current wrapper being created).
91
104
  */
92
105
  private getEffectiveInquirer;
106
+ private getEffectiveInquirerId;
107
+ private getStaticTransientResolutionContext;
108
+ private getEffectiveResolutionContext;
93
109
  private resolveScopedComponentHost;
94
110
  private isInquirerRequest;
95
111
  private isInquirer;
@@ -102,3 +118,4 @@ export declare class Injector {
102
118
  private getContextId;
103
119
  private getNowTimestamp;
104
120
  }
121
+ export {};
@@ -39,12 +39,13 @@ class Injector {
39
39
  collection.set(token, wrapper);
40
40
  }
41
41
  }
42
- async loadInstance(wrapper, collection, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
43
- const inquirerId = this.getInquirerId(inquirer);
44
- const instanceHost = wrapper.getInstanceByContextId(this.getContextId(contextId, wrapper), inquirerId);
42
+ async loadInstance(wrapper, collection, moduleRef, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
43
+ const inquirerId = this.getContextInquirerId(resolutionContext);
44
+ const instanceHost = wrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, wrapper), inquirerId);
45
45
  if (instanceHost.isPending) {
46
46
  const settlementSignal = wrapper.settlementSignal;
47
- if (inquirer && settlementSignal?.isCycle(inquirer.id)) {
47
+ if (resolutionContext.inquirer &&
48
+ settlementSignal?.isCycle(resolutionContext.inquirer.id)) {
48
49
  throw new exceptions_1.CircularDependencyException(`"${wrapper.name}"`);
49
50
  }
50
51
  return instanceHost.donePromise.then((err) => {
@@ -65,17 +66,21 @@ class Injector {
65
66
  }
66
67
  try {
67
68
  const t0 = this.getNowTimestamp();
69
+ const localResolutionContext = this.createResolutionContext(resolutionContext.contextId, wrapper, inquirerId);
68
70
  const callback = async (instances) => {
69
- const properties = await this.resolveProperties(wrapper, moduleRef, inject, contextId, wrapper, inquirer);
70
- const instance = await this.instantiateClass(instances, wrapper, targetWrapper, contextId, inquirer);
71
+ const properties = await this.resolveProperties(wrapper, moduleRef, inject, localResolutionContext, resolutionContext.inquirer);
72
+ const instance = await this.instantiateClass(instances, wrapper, targetWrapper, wrapper.isTransient ? localResolutionContext : resolutionContext);
71
73
  this.applyProperties(instance, properties);
72
74
  wrapper.initTime = this.getNowTimestamp() - t0;
73
75
  settlementSignal.complete();
74
76
  };
75
- await this.resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId, wrapper, inquirer);
77
+ await this.resolveConstructorParams(wrapper, moduleRef, inject, callback, localResolutionContext, resolutionContext.inquirer);
78
+ if (!instanceHost.isResolved) {
79
+ settlementSignal.complete();
80
+ }
76
81
  }
77
82
  catch (err) {
78
- wrapper.removeInstanceByContextId(this.getContextId(contextId, wrapper), inquirerId);
83
+ wrapper.removeInstanceByContextId(this.getContextId(resolutionContext.contextId, wrapper), inquirerId);
79
84
  settlementSignal.error(err);
80
85
  throw err;
81
86
  }
@@ -87,21 +92,24 @@ class Injector {
87
92
  return;
88
93
  }
89
94
  targetWrapper.instance = Object.create(metatype.prototype);
90
- await this.loadInstance(wrapper, collection, moduleRef, contextId, inquirer || wrapper);
95
+ await this.loadInstance(wrapper, collection, moduleRef, this.createResolutionContext(contextId, inquirer || wrapper));
91
96
  }
92
97
  async loadController(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT) {
93
98
  const controllers = moduleRef.controllers;
94
- await this.loadInstance(wrapper, controllers, moduleRef, contextId, wrapper);
99
+ await this.loadInstance(wrapper, controllers, moduleRef, this.createResolutionContext(contextId, wrapper));
95
100
  await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
96
101
  }
97
102
  async loadInjectable(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
98
103
  const injectables = moduleRef.injectables;
99
- await this.loadInstance(wrapper, injectables, moduleRef, contextId, inquirer);
104
+ await this.loadInstance(wrapper, injectables, moduleRef, this.createResolutionContext(contextId, inquirer));
100
105
  }
101
- async loadProvider(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
106
+ async loadProvider(wrapper, moduleRef, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
107
+ if (this.shouldSkipProviderLoading(wrapper, resolutionContext)) {
108
+ return;
109
+ }
102
110
  const providers = moduleRef.providers;
103
- await this.loadInstance(wrapper, providers, moduleRef, contextId, inquirer);
104
- await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
111
+ await this.loadInstance(wrapper, providers, moduleRef, resolutionContext);
112
+ await this.loadEnhancersPerContext(wrapper, resolutionContext.contextId, wrapper);
105
113
  }
106
114
  applySettlementSignal(instancePerContext, host) {
107
115
  const settlementSignal = new settlement_signal_1.SettlementSignal();
@@ -110,10 +118,10 @@ class Injector {
110
118
  host.settlementSignal = settlementSignal;
111
119
  return settlementSignal;
112
120
  }
113
- async resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId = constants_2.STATIC_CONTEXT, inquirer, parentInquirer) {
121
+ async resolveConstructorParams(wrapper, moduleRef, inject, callback, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, parentInquirer) {
114
122
  const metadata = wrapper.getCtorMetadata();
115
- if (metadata && contextId !== constants_2.STATIC_CONTEXT) {
116
- const deps = await this.loadCtorMetadata(metadata, contextId, inquirer, parentInquirer);
123
+ if (metadata && resolutionContext.contextId !== constants_2.STATIC_CONTEXT) {
124
+ const deps = await this.loadCtorMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer, parentInquirer);
117
125
  return callback(deps);
118
126
  }
119
127
  const isFactoryProvider = !(0, shared_utils_1.isNil)(inject);
@@ -131,22 +139,23 @@ class Injector {
131
139
  paramBarrier.signal();
132
140
  return parentInquirer && parentInquirer.instance;
133
141
  }
134
- if (inquirer?.isTransient && parentInquirer) {
142
+ if (resolutionContext.inquirer?.isTransient && parentInquirer) {
135
143
  // When `inquirer` is transient too, inherit the parent inquirer
136
144
  // This is required to ensure that transient providers are only resolved
137
145
  // when requested
138
- inquirer.attachRootInquirer(parentInquirer);
146
+ resolutionContext.inquirer.attachRootInquirer(parentInquirer);
139
147
  }
140
- const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, contextId, inquirer, index);
148
+ const nestedResolutionContext = this.getStaticTransientResolutionContext(resolutionContext, parentInquirer);
149
+ const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, nestedResolutionContext, index);
141
150
  /*
142
151
  * Ensure that all instance wrappers are resolved at this point before we continue.
143
152
  * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
144
153
  * and result in undefined / null injection.
145
154
  */
146
155
  await paramBarrier.signalAndWait();
147
- const effectiveInquirer = this.getEffectiveInquirer(paramWrapper, inquirer, parentInquirer, contextId);
148
- const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, contextId, effectiveInquirer);
149
- const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance), this.getInquirerId(effectiveInquirer));
156
+ const effectiveResolutionContext = this.getEffectiveResolutionContext(paramWrapper, resolutionContext, parentInquirer);
157
+ const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, effectiveResolutionContext);
158
+ const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(effectiveResolutionContext.contextId, paramWrapperWithInstance), effectiveResolutionContext.effectiveInquirerId);
150
159
  if (!instanceHost.isResolved && !paramWrapperWithInstance.forwardRef) {
151
160
  isResolved = false;
152
161
  }
@@ -218,13 +227,13 @@ class Injector {
218
227
  reflectSelfParams(type) {
219
228
  return Reflect.getMetadata(constants_1.SELF_DECLARED_DEPS_METADATA, type) || [];
220
229
  }
221
- async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
230
+ async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
222
231
  if ((0, shared_utils_1.isUndefined)(param)) {
223
232
  this.logger.log('Nest encountered an undefined dependency. This may be due to a circular import or a missing dependency declaration.');
224
233
  throw new undefined_dependency_exception_1.UndefinedDependencyException(wrapper.name, dependencyContext, moduleRef);
225
234
  }
226
235
  const token = this.resolveParamToken(wrapper, param);
227
- return this.resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, contextId, inquirer, keyOrIndex);
236
+ return this.resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, resolutionContext, keyOrIndex);
228
237
  }
229
238
  resolveParamToken(wrapper, param) {
230
239
  if (typeof param === 'object' && 'forwardRef' in param) {
@@ -233,22 +242,22 @@ class Injector {
233
242
  }
234
243
  return param;
235
244
  }
236
- async resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
237
- this.printResolvingDependenciesLog(token, inquirer);
245
+ async resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
246
+ this.printResolvingDependenciesLog(token, resolutionContext.inquirer);
238
247
  this.printLookingForProviderLog(token, moduleRef);
239
248
  const providers = moduleRef.providers;
240
- return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, contextId, inquirer, keyOrIndex);
249
+ return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, resolutionContext, keyOrIndex);
241
250
  }
242
- async resolveComponentHost(moduleRef, instanceWrapper, contextId = constants_2.STATIC_CONTEXT, inquirer) {
243
- const inquirerId = this.getInquirerId(inquirer);
244
- const instanceHost = instanceWrapper.getInstanceByContextId(this.getContextId(contextId, instanceWrapper), inquirerId);
251
+ async resolveComponentHost(moduleRef, instanceWrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
252
+ const inquirerId = this.getContextInquirerId(resolutionContext);
253
+ const instanceHost = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
245
254
  if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
246
- inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
247
- await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, contextId, inquirer);
255
+ resolutionContext.inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
256
+ await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, resolutionContext);
248
257
  }
249
258
  else if (!instanceHost.isResolved &&
250
259
  instanceWrapper.forwardRef &&
251
- (contextId !== constants_2.STATIC_CONTEXT || !!inquirerId)) {
260
+ (resolutionContext.contextId !== constants_2.STATIC_CONTEXT || !!inquirerId)) {
252
261
  /**
253
262
  * When circular dependency has been detected between
254
263
  * either request/transient providers, we have to asynchronously
@@ -258,19 +267,19 @@ class Injector {
258
267
  */
259
268
  instanceHost.donePromise &&
260
269
  void instanceHost.donePromise
261
- .then(() => this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer))
270
+ .then(() => this.loadProvider(instanceWrapper, moduleRef, resolutionContext))
262
271
  .catch(err => {
263
272
  instanceWrapper.settlementSignal?.error(err);
264
273
  });
265
274
  }
266
275
  if (instanceWrapper.async) {
267
- const host = instanceWrapper.getInstanceByContextId(this.getContextId(contextId, instanceWrapper), inquirerId);
276
+ const host = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
268
277
  host.instance = await host.instance;
269
- instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
278
+ instanceWrapper.setInstanceByContextId(resolutionContext.contextId, host, inquirerId);
270
279
  }
271
280
  return instanceWrapper;
272
281
  }
273
- async lookupComponent(providers, moduleRef, dependencyContext, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
282
+ async lookupComponent(providers, moduleRef, dependencyContext, wrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
274
283
  const token = wrapper.token || wrapper.name;
275
284
  const { name } = dependencyContext;
276
285
  if (wrapper && token === name) {
@@ -282,16 +291,16 @@ class Injector {
282
291
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapper);
283
292
  return instanceWrapper;
284
293
  }
285
- return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId, inquirer, keyOrIndex);
294
+ return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext, keyOrIndex);
286
295
  }
287
- async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
288
- const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(), contextId, inquirer, keyOrIndex);
296
+ async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
297
+ const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(), resolutionContext, keyOrIndex);
289
298
  if ((0, shared_utils_1.isNil)(instanceWrapper)) {
290
299
  throw new unknown_dependencies_exception_1.UnknownDependenciesException(wrapper.name, dependencyContext, moduleRef, { id: wrapper.id });
291
300
  }
292
301
  return instanceWrapper;
293
302
  }
294
- async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = new Set(), contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex, isTraversing) {
303
+ async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = new Set(), resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex, isTraversing) {
295
304
  let instanceWrapperRef = null;
296
305
  const imports = moduleRef.imports || new Set();
297
306
  const identity = (item) => item;
@@ -308,7 +317,7 @@ class Injector {
308
317
  moduleRegistry.add(relatedModule.id);
309
318
  const { providers, exports } = relatedModule;
310
319
  if (!exports.has(name) || !providers.has(name)) {
311
- const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, contextId, inquirer, keyOrIndex, true);
320
+ const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, resolutionContext, keyOrIndex, true);
312
321
  if (instanceRef) {
313
322
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceRef);
314
323
  return instanceRef;
@@ -318,8 +327,8 @@ class Injector {
318
327
  this.printFoundInModuleLog(name, relatedModule);
319
328
  instanceWrapperRef = providers.get(name);
320
329
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapperRef);
321
- const inquirerId = this.getInquirerId(inquirer);
322
- const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(contextId, instanceWrapperRef), inquirerId);
330
+ const inquirerId = this.getContextInquirerId(resolutionContext);
331
+ const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapperRef), inquirerId);
323
332
  if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
324
333
  /*
325
334
  * Provider will be loaded shortly in resolveComponentHost() once we pass the current
@@ -331,13 +340,13 @@ class Injector {
331
340
  }
332
341
  return instanceWrapperRef;
333
342
  }
334
- async resolveProperties(wrapper, moduleRef, inject, contextId = constants_2.STATIC_CONTEXT, inquirer, parentInquirer) {
343
+ async resolveProperties(wrapper, moduleRef, inject, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, parentInquirer) {
335
344
  if (!(0, shared_utils_1.isNil)(inject)) {
336
345
  return [];
337
346
  }
338
347
  const metadata = wrapper.getPropertiesMetadata();
339
- if (metadata && contextId !== constants_2.STATIC_CONTEXT) {
340
- return this.loadPropertiesMetadata(metadata, contextId, inquirer);
348
+ if (metadata && resolutionContext.contextId !== constants_2.STATIC_CONTEXT) {
349
+ return this.loadPropertiesMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer);
341
350
  }
342
351
  const properties = this.reflectProperties(wrapper.metatype);
343
352
  const propertyBarrier = new barrier_1.Barrier(properties.length);
@@ -354,19 +363,20 @@ class Injector {
354
363
  propertyBarrier.signal();
355
364
  return parentInquirer && parentInquirer.instance;
356
365
  }
357
- const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, contextId, inquirer, item.key);
366
+ const nestedResolutionContext = this.getStaticTransientResolutionContext(resolutionContext, parentInquirer);
367
+ const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, nestedResolutionContext, item.key);
358
368
  /*
359
369
  * Ensure that all instance wrappers are resolved at this point before we continue.
360
370
  * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
361
371
  * and result in undefined / null injection.
362
372
  */
363
373
  await propertyBarrier.signalAndWait();
364
- const effectivePropertyInquirer = this.getEffectiveInquirer(paramWrapper, inquirer, parentInquirer, contextId);
365
- const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, contextId, effectivePropertyInquirer);
374
+ const effectivePropertyResolutionContext = this.getEffectiveResolutionContext(paramWrapper, resolutionContext, parentInquirer);
375
+ const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, effectivePropertyResolutionContext);
366
376
  if (!paramWrapperWithInstance) {
367
377
  return undefined;
368
378
  }
369
- const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance), this.getInquirerId(effectivePropertyInquirer));
379
+ const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(effectivePropertyResolutionContext.contextId, paramWrapperWithInstance), effectivePropertyResolutionContext.effectiveInquirerId);
370
380
  return instanceHost.instance;
371
381
  }
372
382
  catch (err) {
@@ -404,14 +414,11 @@ class Injector {
404
414
  .filter(item => !(0, shared_utils_1.isNil)(item.instance))
405
415
  .forEach(item => (instance[item.key] = item.instance));
406
416
  }
407
- async instantiateClass(instances, wrapper, targetMetatype, contextId = constants_2.STATIC_CONTEXT, inquirer) {
417
+ async instantiateClass(instances, wrapper, targetMetatype, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
408
418
  const { metatype, inject } = wrapper;
409
- const inquirerId = this.getInquirerId(inquirer);
410
- const instanceHost = targetMetatype.getInstanceByContextId(this.getContextId(contextId, targetMetatype), inquirerId);
411
- const isInContext = wrapper.isStatic(contextId, inquirer) ||
412
- wrapper.isInRequestScope(contextId, inquirer) ||
413
- wrapper.isLazyTransient(contextId, inquirer) ||
414
- wrapper.isExplicitlyRequested(contextId, inquirer);
419
+ const inquirerId = this.getContextInquirerId(resolutionContext);
420
+ const instanceHost = targetMetatype.getInstanceByContextId(this.getContextId(resolutionContext.contextId, targetMetatype), inquirerId);
421
+ const isInContext = this.isInContext(wrapper, resolutionContext);
415
422
  if (this.options?.preview && !wrapper.host?.initOnPreview) {
416
423
  instanceHost.isResolved = true;
417
424
  return instanceHost.instance;
@@ -437,16 +444,22 @@ class Injector {
437
444
  const injectionToken = instance.constructor;
438
445
  wrapper = collection.get(injectionToken);
439
446
  }
440
- await this.loadInstance(wrapper, collection, moduleRef, ctx, wrapper);
447
+ else {
448
+ wrapper = collection.get(wrapper.token) ?? wrapper;
449
+ }
450
+ await this.loadInstance(wrapper, collection, moduleRef, this.createResolutionContext(ctx, wrapper));
441
451
  await this.loadEnhancersPerContext(wrapper, ctx, wrapper);
442
452
  const host = wrapper.getInstanceByContextId(this.getContextId(ctx, wrapper), wrapper.id);
443
453
  return host && host.instance;
444
454
  }
445
455
  async loadEnhancersPerContext(wrapper, ctx, inquirer) {
456
+ if (ctx === constants_2.STATIC_CONTEXT) {
457
+ return;
458
+ }
446
459
  const enhancers = wrapper.getEnhancersMetadata() || [];
447
460
  const loadEnhancer = (item) => {
448
461
  const hostModule = item.host;
449
- return this.loadInstance(item, hostModule.injectables, hostModule, ctx, inquirer);
462
+ return this.loadInstance(item, hostModule.injectables, hostModule, this.createResolutionContext(ctx, inquirer));
450
463
  };
451
464
  await Promise.all(enhancers.map(loadEnhancer));
452
465
  }
@@ -454,14 +467,14 @@ class Injector {
454
467
  const hosts = await Promise.all(metadata.map(async (item) => this.resolveScopedComponentHost(item, contextId, inquirer, parentInquirer)));
455
468
  return hosts.map((item, index) => {
456
469
  const dependency = metadata[index];
457
- const effectiveInquirer = this.getEffectiveInquirer(dependency, inquirer, parentInquirer, contextId);
458
- return item?.getInstanceByContextId(this.getContextId(contextId, item), this.getInquirerId(effectiveInquirer)).instance;
470
+ const effectiveInquirerId = this.getEffectiveInquirerId(dependency, this.createResolutionContext(contextId, inquirer), parentInquirer);
471
+ return item?.getInstanceByContextId(this.getContextId(contextId, item), effectiveInquirerId).instance;
459
472
  });
460
473
  }
461
474
  async loadPropertiesMetadata(metadata, contextId, inquirer) {
462
475
  const dependenciesHosts = await Promise.all(metadata.map(async ({ wrapper: item, key }) => ({
463
476
  key,
464
- host: await this.resolveComponentHost(item.host, item, contextId, inquirer),
477
+ host: await this.resolveComponentHost(item.host, item, this.createResolutionContext(contextId, inquirer)),
465
478
  })));
466
479
  const inquirerId = this.getInquirerId(inquirer);
467
480
  return dependenciesHosts.map(({ key, host }) => ({
@@ -473,6 +486,34 @@ class Injector {
473
486
  getInquirerId(inquirer) {
474
487
  return inquirer ? inquirer.id : undefined;
475
488
  }
489
+ createResolutionContext(contextId, inquirer, effectiveInquirerId) {
490
+ return {
491
+ contextId,
492
+ inquirer,
493
+ effectiveInquirerId,
494
+ };
495
+ }
496
+ getContextInquirerId({ inquirer, effectiveInquirerId, }) {
497
+ return effectiveInquirerId ?? this.getInquirerId(inquirer);
498
+ }
499
+ isInContext(wrapper, resolutionContext) {
500
+ return (wrapper.isStatic(resolutionContext.contextId, resolutionContext.inquirer) ||
501
+ wrapper.isInRequestScope(resolutionContext.contextId, resolutionContext.inquirer) ||
502
+ wrapper.isLazyTransient(resolutionContext.contextId, resolutionContext.inquirer) ||
503
+ wrapper.isExplicitlyRequested(resolutionContext.contextId, resolutionContext.inquirer));
504
+ }
505
+ shouldSkipProviderLoading(wrapper, resolutionContext) {
506
+ const isSnapshotGraphCompilation = !!this.options?.snapshot;
507
+ const isStaticContext = resolutionContext.contextId === constants_2.STATIC_CONTEXT;
508
+ const hasNoInquirer = !resolutionContext.inquirer;
509
+ const isTopLevelStaticTransientOrRequestProvider = hasNoInquirer && (wrapper.isTransient || wrapper.scope === common_1.Scope.REQUEST);
510
+ const isStaticInquirerOutsideResolutionContext = !!resolutionContext.inquirer &&
511
+ !this.isInContext(resolutionContext.inquirer, this.createResolutionContext(resolutionContext.contextId, resolutionContext.inquirer));
512
+ const shouldSkipForStaticBootstrap = isStaticContext &&
513
+ (isTopLevelStaticTransientOrRequestProvider ||
514
+ isStaticInquirerOutsideResolutionContext);
515
+ return !isSnapshotGraphCompilation && shouldSkipForStaticBootstrap;
516
+ }
476
517
  /**
477
518
  * For nested TRANSIENT dependencies (TRANSIENT -> TRANSIENT) in non-static contexts,
478
519
  * returns parentInquirer to ensure each parent TRANSIENT gets its own instance.
@@ -480,18 +521,45 @@ class Injector {
480
521
  * can be used by multiple parents, causing nested TRANSIENTs to be shared incorrectly.
481
522
  * For non-TRANSIENT -> TRANSIENT, returns inquirer (current wrapper being created).
482
523
  */
483
- getEffectiveInquirer(dependency, inquirer, parentInquirer, contextId) {
484
- return dependency?.isTransient &&
524
+ getEffectiveInquirer(dependency, resolutionContext, parentInquirer) {
525
+ const { inquirer, contextId } = resolutionContext;
526
+ if (dependency?.isTransient && inquirer?.isTransient && parentInquirer) {
527
+ if (contextId === constants_2.STATIC_CONTEXT) {
528
+ return inquirer.getRootInquirer() ?? parentInquirer;
529
+ }
530
+ return parentInquirer;
531
+ }
532
+ return inquirer;
533
+ }
534
+ getEffectiveInquirerId(dependency, resolutionContext, parentInquirer) {
535
+ const { contextId, inquirer, effectiveInquirerId } = resolutionContext;
536
+ if (contextId === constants_2.STATIC_CONTEXT &&
537
+ dependency?.isTransient &&
485
538
  inquirer?.isTransient &&
486
- parentInquirer &&
487
- contextId !== constants_2.STATIC_CONTEXT
488
- ? parentInquirer
489
- : inquirer;
539
+ parentInquirer) {
540
+ const baseInquirerId = effectiveInquirerId ?? this.getInquirerId(parentInquirer);
541
+ return `${baseInquirerId}:${inquirer.id}`;
542
+ }
543
+ const effectiveInquirer = this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer);
544
+ return this.getInquirerId(effectiveInquirer);
545
+ }
546
+ getStaticTransientResolutionContext(resolutionContext, parentInquirer) {
547
+ const { contextId, inquirer, effectiveInquirerId } = resolutionContext;
548
+ if (contextId === constants_2.STATIC_CONTEXT &&
549
+ inquirer?.isTransient &&
550
+ parentInquirer) {
551
+ const baseInquirerId = effectiveInquirerId ?? this.getInquirerId(parentInquirer);
552
+ return this.createResolutionContext(contextId, inquirer, `${baseInquirerId}:${inquirer.id}`);
553
+ }
554
+ return resolutionContext;
555
+ }
556
+ getEffectiveResolutionContext(dependency, resolutionContext, parentInquirer) {
557
+ return this.createResolutionContext(resolutionContext.contextId, this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer), this.getEffectiveInquirerId(dependency, resolutionContext, parentInquirer));
490
558
  }
491
559
  resolveScopedComponentHost(item, contextId, inquirer, parentInquirer) {
492
560
  return this.isInquirerRequest(item, parentInquirer)
493
561
  ? parentInquirer
494
- : this.resolveComponentHost(item.host, item, contextId, this.getEffectiveInquirer(item, inquirer, parentInquirer, contextId));
562
+ : this.resolveComponentHost(item.host, item, this.getEffectiveResolutionContext(item, this.createResolutionContext(contextId, inquirer), parentInquirer));
495
563
  }
496
564
  isInquirerRequest(item, parentInquirer) {
497
565
  return item.isTransient && item.name === inquirer_1.INQUIRER && parentInquirer;
@@ -19,9 +19,11 @@ class ModuleRef extends abstract_instance_resolver_1.AbstractInstanceResolver {
19
19
  constructor(container) {
20
20
  super();
21
21
  this.container = container;
22
+ const contextOptions = container.contextOptions;
22
23
  this.injector = new injector_1.Injector({
23
- preview: container.contextOptions?.preview,
24
- instanceDecorator: container.contextOptions?.instrument?.instanceDecorator,
24
+ preview: contextOptions?.preview ?? false,
25
+ snapshot: contextOptions?.snapshot,
26
+ instanceDecorator: contextOptions?.instrument?.instanceDecorator,
25
27
  });
26
28
  }
27
29
  introspect(token) {
@@ -54,16 +56,21 @@ class ModuleRef extends abstract_instance_resolver_1.AbstractInstanceResolver {
54
56
  isPending: false,
55
57
  });
56
58
  }
57
- /* eslint-disable-next-line no-async-promise-executor */
58
59
  return new Promise(async (resolve, reject) => {
59
60
  try {
60
61
  const callback = async (instances) => {
61
- const properties = await this.injector.resolveProperties(wrapper, moduleRef, undefined, contextId, wrapper);
62
+ const properties = await this.injector.resolveProperties(wrapper, moduleRef, undefined, {
63
+ contextId: contextId ?? constants_1.STATIC_CONTEXT,
64
+ inquirer: wrapper,
65
+ });
62
66
  const instance = new type(...instances);
63
67
  this.injector.applyProperties(instance, properties);
64
68
  resolve(instance);
65
69
  };
66
- await this.injector.resolveConstructorParams(wrapper, moduleRef, undefined, callback, contextId, wrapper);
70
+ await this.injector.resolveConstructorParams(wrapper, moduleRef, undefined, callback, {
71
+ contextId: contextId ?? constants_1.STATIC_CONTEXT,
72
+ inquirer: wrapper,
73
+ });
67
74
  }
68
75
  catch (err) {
69
76
  reject(err);
package/nest-factory.js CHANGED
@@ -97,6 +97,7 @@ class NestFactoryStatic {
97
97
  : uuid_factory_1.UuidFactoryMode.Random;
98
98
  const injector = new injector_1.Injector({
99
99
  preview: options.preview,
100
+ snapshot: options.snapshot,
100
101
  instanceDecorator: options.instrument?.instanceDecorator,
101
102
  });
102
103
  const instanceLoader = new instance_loader_1.InstanceLoader(container, injector, graphInspector);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/core",
3
- "version": "11.1.19",
3
+ "version": "11.1.21",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@core)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "uid": "2.0.2"
40
40
  },
41
41
  "devDependencies": {
42
- "@nestjs/common": "11.1.19"
42
+ "@nestjs/common": "11.1.21"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@nestjs/common": "^11.0.0",
@@ -60,5 +60,5 @@
60
60
  "optional": true
61
61
  }
62
62
  },
63
- "gitHead": "67309956821c0626c050fe6725c90645d2577e3d"
63
+ "gitHead": "983dd52c4927753be3421162fc43e4fde8d3fcde"
64
64
  }
@@ -167,7 +167,12 @@ class RouterExecutionContext {
167
167
  const isSseHandler = !!this.reflectSse(callback);
168
168
  if (isSseHandler) {
169
169
  return async (result, res, req) => {
170
- await this.responseController.sse(result, res.raw || res, req.raw || req, { additionalHeaders: res.getHeaders?.() });
170
+ const rawResponse = res.raw ?? res;
171
+ await this.responseController.sse(result, rawResponse, req.raw || req, {
172
+ additionalHeaders: res.getHeaders?.(),
173
+ statusCode: res.statusCode ??
174
+ rawResponse.statusCode,
175
+ });
171
176
  };
172
177
  }
173
178
  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
  }
@@ -56,37 +56,71 @@ class RouterResponseController {
56
56
  const observableResult = await Promise.resolve(result);
57
57
  this.assertObservable(observableResult);
58
58
  const stream = new sse_stream_1.SseStream(request);
59
- // Extract custom status code from response if it was set
60
- const customStatusCode = response.statusCode;
61
- const pipeOptions = typeof customStatusCode !== 'undefined'
62
- ? { ...options, statusCode: customStatusCode }
63
- : options;
64
- stream.pipe(response, pipeOptions);
65
- const subscription = observableResult
66
- .pipe((0, operators_1.map)((message) => {
67
- if ((0, shared_utils_1.isObject)(message)) {
68
- return message;
69
- }
70
- return { data: message };
71
- }), (0, operators_1.concatMap)(message => new Promise(resolve => stream.writeMessage(message, () => resolve()))), (0, operators_1.catchError)(err => {
72
- const data = err instanceof Error ? err.message : err;
73
- stream.writeMessage({ type: 'error', data }, writeError => {
74
- if (writeError) {
75
- this.logger.error(writeError);
59
+ const statusCode = options?.statusCode ??
60
+ response.statusCode ??
61
+ 200;
62
+ stream.pipe(response, {
63
+ additionalHeaders: options?.additionalHeaders,
64
+ statusCode,
65
+ });
66
+ return new Promise((resolve, reject) => {
67
+ let settled = false;
68
+ const onClose = () => {
69
+ settled = true;
70
+ subscription.unsubscribe();
71
+ if (!stream.writableEnded) {
72
+ stream.end();
76
73
  }
77
- });
78
- return rxjs_1.EMPTY;
79
- }))
80
- .subscribe({
81
- complete: () => {
82
74
  response.end();
83
- },
84
- });
85
- request.on('close', () => {
86
- subscription.unsubscribe();
87
- if (!stream.writableEnded) {
88
- stream.end();
89
- }
75
+ resolve();
76
+ };
77
+ const subscription = observableResult
78
+ .pipe((0, operators_1.map)((message) => {
79
+ if ((0, shared_utils_1.isObject)(message)) {
80
+ return message;
81
+ }
82
+ return { data: message };
83
+ }), (0, operators_1.concatMap)(message => new Promise(resolve => stream.writeMessage(message, () => resolve()))), (0, operators_1.catchError)(err => {
84
+ if (!stream.headersCommitted) {
85
+ throw err;
86
+ }
87
+ const data = err instanceof Error ? err.message : err;
88
+ stream.writeMessage({ type: 'error', data }, writeError => {
89
+ if (writeError) {
90
+ this.logger.error(writeError);
91
+ }
92
+ });
93
+ return rxjs_1.EMPTY;
94
+ }))
95
+ .subscribe({
96
+ error: err => {
97
+ settled = true;
98
+ request.removeListener('close', onClose);
99
+ if (!stream.writableEnded) {
100
+ stream.end();
101
+ }
102
+ reject(err);
103
+ },
104
+ complete: () => {
105
+ settled = true;
106
+ request.removeListener('close', onClose);
107
+ if (!stream.writableEnded) {
108
+ stream.end();
109
+ }
110
+ resolve();
111
+ },
112
+ });
113
+ // Commit SSE headers on the next macrotask. Pipe validation errors
114
+ // propagate through microtasks (which complete before macrotasks),
115
+ // so if the lifecycle errored, `settled` is already true and we
116
+ // skip the write. Otherwise headers are sent immediately rather
117
+ // than waiting for the first Observable emission.
118
+ setTimeout(() => {
119
+ if (!settled) {
120
+ stream.commitHeaders();
121
+ }
122
+ }, 0);
123
+ request.on('close', onClose);
90
124
  });
91
125
  }
92
126
  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
@@ -29,17 +29,43 @@ class SseStream extends stream_1.Transform {
29
29
  constructor(req) {
30
30
  super({ objectMode: true });
31
31
  this.lastEventId = null;
32
+ this._headersCommitted = false;
33
+ this._destination = null;
34
+ this._statusCode = 200;
32
35
  if (req && req.socket) {
33
36
  req.socket.setKeepAlive(true);
34
37
  req.socket.setNoDelay(true);
35
38
  req.socket.setTimeout(0);
36
39
  }
37
40
  }
41
+ get headersCommitted() {
42
+ return this._headersCommitted;
43
+ }
38
44
  pipe(destination, options) {
39
- if (destination.writeHead) {
40
- const statusCode = options?.statusCode ?? 200;
41
- destination.writeHead(statusCode, {
42
- ...options?.additionalHeaders,
45
+ this._destination = destination;
46
+ this._statusCode = options?.statusCode ?? 200;
47
+ this._additionalHeaders = options?.additionalHeaders;
48
+ return super.pipe(destination, options);
49
+ }
50
+ /**
51
+ * Writes SSE headers to the destination if they have not been sent yet.
52
+ * Headers are deferred until the first message so that, if the observable
53
+ * errors before any data is emitted, the HTTP status code can still be
54
+ * changed by an exception filter.
55
+ */
56
+ commitHeaders() {
57
+ if (this._headersCommitted || !this._destination) {
58
+ return;
59
+ }
60
+ if (this._destination.writableEnded) {
61
+ return;
62
+ }
63
+ this._headersCommitted = true;
64
+ const statusCode = this._statusCode ?? 200;
65
+ const additionalHeaders = this._additionalHeaders;
66
+ if (this._destination.writeHead) {
67
+ this._destination.writeHead(statusCode, {
68
+ ...additionalHeaders,
43
69
  // See https://github.com/dunglas/mercure/blob/master/hub/subscribe.go#L124-L130
44
70
  'Content-Type': 'text/event-stream',
45
71
  Connection: 'keep-alive',
@@ -50,15 +76,18 @@ class SseStream extends stream_1.Transform {
50
76
  // NGINX support https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-buffering
51
77
  'X-Accel-Buffering': 'no',
52
78
  });
53
- destination.flushHeaders?.();
79
+ this._destination.flushHeaders?.();
54
80
  }
55
- destination.write('\n');
56
- return super.pipe(destination, options);
81
+ this._destination.write('\n');
57
82
  }
58
83
  _transform(message, encoding, callback) {
84
+ this.commitHeaders();
59
85
  const sanitize = (val) => String(val).replace(/[\r\n]/g, '');
60
86
  let data = message.type ? `event: ${sanitize(message.type)}\n` : '';
61
- data += message.id ? `id: ${sanitize(message.id)}\n` : '';
87
+ data +=
88
+ message.id !== undefined && message.id !== null
89
+ ? `id: ${sanitize(message.id)}\n`
90
+ : '';
62
91
  data += message.retry ? `retry: ${sanitize(message.retry)}\n` : '';
63
92
  data += message.data ? toDataString(message.data) : '';
64
93
  data += '\n';
@@ -69,7 +98,7 @@ class SseStream extends stream_1.Transform {
69
98
  * Calls `.write` but handles the drain if needed
70
99
  */
71
100
  writeMessage(message, cb) {
72
- if (!message.id) {
101
+ if (message.id === undefined || message.id === null) {
73
102
  this.lastEventId++;
74
103
  message.id = this.lastEventId.toString();
75
104
  }