@nestjs/core 11.1.18 → 11.1.20

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,18 @@ 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);
76
78
  }
77
79
  catch (err) {
78
- wrapper.removeInstanceByContextId(this.getContextId(contextId, wrapper), inquirerId);
80
+ wrapper.removeInstanceByContextId(this.getContextId(resolutionContext.contextId, wrapper), inquirerId);
79
81
  settlementSignal.error(err);
80
82
  throw err;
81
83
  }
@@ -87,21 +89,24 @@ class Injector {
87
89
  return;
88
90
  }
89
91
  targetWrapper.instance = Object.create(metatype.prototype);
90
- await this.loadInstance(wrapper, collection, moduleRef, contextId, inquirer || wrapper);
92
+ await this.loadInstance(wrapper, collection, moduleRef, this.createResolutionContext(contextId, inquirer || wrapper));
91
93
  }
92
94
  async loadController(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT) {
93
95
  const controllers = moduleRef.controllers;
94
- await this.loadInstance(wrapper, controllers, moduleRef, contextId, wrapper);
96
+ await this.loadInstance(wrapper, controllers, moduleRef, this.createResolutionContext(contextId, wrapper));
95
97
  await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
96
98
  }
97
99
  async loadInjectable(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
98
100
  const injectables = moduleRef.injectables;
99
- await this.loadInstance(wrapper, injectables, moduleRef, contextId, inquirer);
101
+ await this.loadInstance(wrapper, injectables, moduleRef, this.createResolutionContext(contextId, inquirer));
100
102
  }
101
- async loadProvider(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
103
+ async loadProvider(wrapper, moduleRef, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
104
+ if (this.shouldSkipProviderLoading(wrapper, resolutionContext)) {
105
+ return;
106
+ }
102
107
  const providers = moduleRef.providers;
103
- await this.loadInstance(wrapper, providers, moduleRef, contextId, inquirer);
104
- await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
108
+ await this.loadInstance(wrapper, providers, moduleRef, resolutionContext);
109
+ await this.loadEnhancersPerContext(wrapper, resolutionContext.contextId, wrapper);
105
110
  }
106
111
  applySettlementSignal(instancePerContext, host) {
107
112
  const settlementSignal = new settlement_signal_1.SettlementSignal();
@@ -110,10 +115,10 @@ class Injector {
110
115
  host.settlementSignal = settlementSignal;
111
116
  return settlementSignal;
112
117
  }
113
- async resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId = constants_2.STATIC_CONTEXT, inquirer, parentInquirer) {
118
+ async resolveConstructorParams(wrapper, moduleRef, inject, callback, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, parentInquirer) {
114
119
  const metadata = wrapper.getCtorMetadata();
115
- if (metadata && contextId !== constants_2.STATIC_CONTEXT) {
116
- const deps = await this.loadCtorMetadata(metadata, contextId, inquirer, parentInquirer);
120
+ if (metadata && resolutionContext.contextId !== constants_2.STATIC_CONTEXT) {
121
+ const deps = await this.loadCtorMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer, parentInquirer);
117
122
  return callback(deps);
118
123
  }
119
124
  const isFactoryProvider = !(0, shared_utils_1.isNil)(inject);
@@ -131,22 +136,23 @@ class Injector {
131
136
  paramBarrier.signal();
132
137
  return parentInquirer && parentInquirer.instance;
133
138
  }
134
- if (inquirer?.isTransient && parentInquirer) {
139
+ if (resolutionContext.inquirer?.isTransient && parentInquirer) {
135
140
  // When `inquirer` is transient too, inherit the parent inquirer
136
141
  // This is required to ensure that transient providers are only resolved
137
142
  // when requested
138
- inquirer.attachRootInquirer(parentInquirer);
143
+ resolutionContext.inquirer.attachRootInquirer(parentInquirer);
139
144
  }
140
- const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, contextId, inquirer, index);
145
+ const nestedResolutionContext = this.getStaticTransientResolutionContext(resolutionContext, parentInquirer);
146
+ const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, nestedResolutionContext, index);
141
147
  /*
142
148
  * Ensure that all instance wrappers are resolved at this point before we continue.
143
149
  * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
144
150
  * and result in undefined / null injection.
145
151
  */
146
152
  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));
153
+ const effectiveResolutionContext = this.getEffectiveResolutionContext(paramWrapper, resolutionContext, parentInquirer);
154
+ const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, effectiveResolutionContext);
155
+ const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(effectiveResolutionContext.contextId, paramWrapperWithInstance), effectiveResolutionContext.effectiveInquirerId);
150
156
  if (!instanceHost.isResolved && !paramWrapperWithInstance.forwardRef) {
151
157
  isResolved = false;
152
158
  }
@@ -218,13 +224,13 @@ class Injector {
218
224
  reflectSelfParams(type) {
219
225
  return Reflect.getMetadata(constants_1.SELF_DECLARED_DEPS_METADATA, type) || [];
220
226
  }
221
- async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
227
+ async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
222
228
  if ((0, shared_utils_1.isUndefined)(param)) {
223
229
  this.logger.log('Nest encountered an undefined dependency. This may be due to a circular import or a missing dependency declaration.');
224
230
  throw new undefined_dependency_exception_1.UndefinedDependencyException(wrapper.name, dependencyContext, moduleRef);
225
231
  }
226
232
  const token = this.resolveParamToken(wrapper, param);
227
- return this.resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, contextId, inquirer, keyOrIndex);
233
+ return this.resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, resolutionContext, keyOrIndex);
228
234
  }
229
235
  resolveParamToken(wrapper, param) {
230
236
  if (typeof param === 'object' && 'forwardRef' in param) {
@@ -233,22 +239,22 @@ class Injector {
233
239
  }
234
240
  return param;
235
241
  }
236
- async resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
237
- this.printResolvingDependenciesLog(token, inquirer);
242
+ async resolveComponentWrapper(moduleRef, token, dependencyContext, wrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
243
+ this.printResolvingDependenciesLog(token, resolutionContext.inquirer);
238
244
  this.printLookingForProviderLog(token, moduleRef);
239
245
  const providers = moduleRef.providers;
240
- return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, contextId, inquirer, keyOrIndex);
246
+ return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, resolutionContext, keyOrIndex);
241
247
  }
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);
248
+ async resolveComponentHost(moduleRef, instanceWrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
249
+ const inquirerId = this.getContextInquirerId(resolutionContext);
250
+ const instanceHost = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
245
251
  if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
246
- inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
247
- await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, contextId, inquirer);
252
+ resolutionContext.inquirer?.settlementSignal?.insertRef(instanceWrapper.id);
253
+ await this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, resolutionContext);
248
254
  }
249
255
  else if (!instanceHost.isResolved &&
250
256
  instanceWrapper.forwardRef &&
251
- (contextId !== constants_2.STATIC_CONTEXT || !!inquirerId)) {
257
+ (resolutionContext.contextId !== constants_2.STATIC_CONTEXT || !!inquirerId)) {
252
258
  /**
253
259
  * When circular dependency has been detected between
254
260
  * either request/transient providers, we have to asynchronously
@@ -258,19 +264,19 @@ class Injector {
258
264
  */
259
265
  instanceHost.donePromise &&
260
266
  void instanceHost.donePromise
261
- .then(() => this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer))
267
+ .then(() => this.loadProvider(instanceWrapper, moduleRef, resolutionContext))
262
268
  .catch(err => {
263
269
  instanceWrapper.settlementSignal?.error(err);
264
270
  });
265
271
  }
266
272
  if (instanceWrapper.async) {
267
- const host = instanceWrapper.getInstanceByContextId(this.getContextId(contextId, instanceWrapper), inquirerId);
273
+ const host = instanceWrapper.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapper), inquirerId);
268
274
  host.instance = await host.instance;
269
- instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
275
+ instanceWrapper.setInstanceByContextId(resolutionContext.contextId, host, inquirerId);
270
276
  }
271
277
  return instanceWrapper;
272
278
  }
273
- async lookupComponent(providers, moduleRef, dependencyContext, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
279
+ async lookupComponent(providers, moduleRef, dependencyContext, wrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
274
280
  const token = wrapper.token || wrapper.name;
275
281
  const { name } = dependencyContext;
276
282
  if (wrapper && token === name) {
@@ -282,16 +288,16 @@ class Injector {
282
288
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapper);
283
289
  return instanceWrapper;
284
290
  }
285
- return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId, inquirer, keyOrIndex);
291
+ return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext, keyOrIndex);
286
292
  }
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);
293
+ async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex) {
294
+ const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(), resolutionContext, keyOrIndex);
289
295
  if ((0, shared_utils_1.isNil)(instanceWrapper)) {
290
296
  throw new unknown_dependencies_exception_1.UnknownDependenciesException(wrapper.name, dependencyContext, moduleRef, { id: wrapper.id });
291
297
  }
292
298
  return instanceWrapper;
293
299
  }
294
- async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = new Set(), contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex, isTraversing) {
300
+ async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = new Set(), resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, keyOrIndex, isTraversing) {
295
301
  let instanceWrapperRef = null;
296
302
  const imports = moduleRef.imports || new Set();
297
303
  const identity = (item) => item;
@@ -308,7 +314,7 @@ class Injector {
308
314
  moduleRegistry.add(relatedModule.id);
309
315
  const { providers, exports } = relatedModule;
310
316
  if (!exports.has(name) || !providers.has(name)) {
311
- const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, contextId, inquirer, keyOrIndex, true);
317
+ const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, resolutionContext, keyOrIndex, true);
312
318
  if (instanceRef) {
313
319
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceRef);
314
320
  return instanceRef;
@@ -318,8 +324,8 @@ class Injector {
318
324
  this.printFoundInModuleLog(name, relatedModule);
319
325
  instanceWrapperRef = providers.get(name);
320
326
  this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapperRef);
321
- const inquirerId = this.getInquirerId(inquirer);
322
- const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(contextId, instanceWrapperRef), inquirerId);
327
+ const inquirerId = this.getContextInquirerId(resolutionContext);
328
+ const instanceHost = instanceWrapperRef.getInstanceByContextId(this.getContextId(resolutionContext.contextId, instanceWrapperRef), inquirerId);
323
329
  if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
324
330
  /*
325
331
  * Provider will be loaded shortly in resolveComponentHost() once we pass the current
@@ -331,13 +337,13 @@ class Injector {
331
337
  }
332
338
  return instanceWrapperRef;
333
339
  }
334
- async resolveProperties(wrapper, moduleRef, inject, contextId = constants_2.STATIC_CONTEXT, inquirer, parentInquirer) {
340
+ async resolveProperties(wrapper, moduleRef, inject, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }, parentInquirer) {
335
341
  if (!(0, shared_utils_1.isNil)(inject)) {
336
342
  return [];
337
343
  }
338
344
  const metadata = wrapper.getPropertiesMetadata();
339
- if (metadata && contextId !== constants_2.STATIC_CONTEXT) {
340
- return this.loadPropertiesMetadata(metadata, contextId, inquirer);
345
+ if (metadata && resolutionContext.contextId !== constants_2.STATIC_CONTEXT) {
346
+ return this.loadPropertiesMetadata(metadata, resolutionContext.contextId, resolutionContext.inquirer);
341
347
  }
342
348
  const properties = this.reflectProperties(wrapper.metatype);
343
349
  const propertyBarrier = new barrier_1.Barrier(properties.length);
@@ -354,19 +360,20 @@ class Injector {
354
360
  propertyBarrier.signal();
355
361
  return parentInquirer && parentInquirer.instance;
356
362
  }
357
- const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, contextId, inquirer, item.key);
363
+ const nestedResolutionContext = this.getStaticTransientResolutionContext(resolutionContext, parentInquirer);
364
+ const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, nestedResolutionContext, item.key);
358
365
  /*
359
366
  * Ensure that all instance wrappers are resolved at this point before we continue.
360
367
  * Otherwise the staticity of `wrapper`'s dependency tree may be evaluated incorrectly
361
368
  * and result in undefined / null injection.
362
369
  */
363
370
  await propertyBarrier.signalAndWait();
364
- const effectivePropertyInquirer = this.getEffectiveInquirer(paramWrapper, inquirer, parentInquirer, contextId);
365
- const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, contextId, effectivePropertyInquirer);
371
+ const effectivePropertyResolutionContext = this.getEffectiveResolutionContext(paramWrapper, resolutionContext, parentInquirer);
372
+ const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper, effectivePropertyResolutionContext);
366
373
  if (!paramWrapperWithInstance) {
367
374
  return undefined;
368
375
  }
369
- const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance), this.getInquirerId(effectivePropertyInquirer));
376
+ const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(effectivePropertyResolutionContext.contextId, paramWrapperWithInstance), effectivePropertyResolutionContext.effectiveInquirerId);
370
377
  return instanceHost.instance;
371
378
  }
372
379
  catch (err) {
@@ -404,14 +411,11 @@ class Injector {
404
411
  .filter(item => !(0, shared_utils_1.isNil)(item.instance))
405
412
  .forEach(item => (instance[item.key] = item.instance));
406
413
  }
407
- async instantiateClass(instances, wrapper, targetMetatype, contextId = constants_2.STATIC_CONTEXT, inquirer) {
414
+ async instantiateClass(instances, wrapper, targetMetatype, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
408
415
  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);
416
+ const inquirerId = this.getContextInquirerId(resolutionContext);
417
+ const instanceHost = targetMetatype.getInstanceByContextId(this.getContextId(resolutionContext.contextId, targetMetatype), inquirerId);
418
+ const isInContext = this.isInContext(wrapper, resolutionContext);
415
419
  if (this.options?.preview && !wrapper.host?.initOnPreview) {
416
420
  instanceHost.isResolved = true;
417
421
  return instanceHost.instance;
@@ -437,16 +441,22 @@ class Injector {
437
441
  const injectionToken = instance.constructor;
438
442
  wrapper = collection.get(injectionToken);
439
443
  }
440
- await this.loadInstance(wrapper, collection, moduleRef, ctx, wrapper);
444
+ else {
445
+ wrapper = collection.get(wrapper.token) ?? wrapper;
446
+ }
447
+ await this.loadInstance(wrapper, collection, moduleRef, this.createResolutionContext(ctx, wrapper));
441
448
  await this.loadEnhancersPerContext(wrapper, ctx, wrapper);
442
449
  const host = wrapper.getInstanceByContextId(this.getContextId(ctx, wrapper), wrapper.id);
443
450
  return host && host.instance;
444
451
  }
445
452
  async loadEnhancersPerContext(wrapper, ctx, inquirer) {
453
+ if (ctx === constants_2.STATIC_CONTEXT) {
454
+ return;
455
+ }
446
456
  const enhancers = wrapper.getEnhancersMetadata() || [];
447
457
  const loadEnhancer = (item) => {
448
458
  const hostModule = item.host;
449
- return this.loadInstance(item, hostModule.injectables, hostModule, ctx, inquirer);
459
+ return this.loadInstance(item, hostModule.injectables, hostModule, this.createResolutionContext(ctx, inquirer));
450
460
  };
451
461
  await Promise.all(enhancers.map(loadEnhancer));
452
462
  }
@@ -454,14 +464,14 @@ class Injector {
454
464
  const hosts = await Promise.all(metadata.map(async (item) => this.resolveScopedComponentHost(item, contextId, inquirer, parentInquirer)));
455
465
  return hosts.map((item, index) => {
456
466
  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;
467
+ const effectiveInquirerId = this.getEffectiveInquirerId(dependency, this.createResolutionContext(contextId, inquirer), parentInquirer);
468
+ return item?.getInstanceByContextId(this.getContextId(contextId, item), effectiveInquirerId).instance;
459
469
  });
460
470
  }
461
471
  async loadPropertiesMetadata(metadata, contextId, inquirer) {
462
472
  const dependenciesHosts = await Promise.all(metadata.map(async ({ wrapper: item, key }) => ({
463
473
  key,
464
- host: await this.resolveComponentHost(item.host, item, contextId, inquirer),
474
+ host: await this.resolveComponentHost(item.host, item, this.createResolutionContext(contextId, inquirer)),
465
475
  })));
466
476
  const inquirerId = this.getInquirerId(inquirer);
467
477
  return dependenciesHosts.map(({ key, host }) => ({
@@ -473,6 +483,34 @@ class Injector {
473
483
  getInquirerId(inquirer) {
474
484
  return inquirer ? inquirer.id : undefined;
475
485
  }
486
+ createResolutionContext(contextId, inquirer, effectiveInquirerId) {
487
+ return {
488
+ contextId,
489
+ inquirer,
490
+ effectiveInquirerId,
491
+ };
492
+ }
493
+ getContextInquirerId({ inquirer, effectiveInquirerId, }) {
494
+ return effectiveInquirerId ?? this.getInquirerId(inquirer);
495
+ }
496
+ isInContext(wrapper, resolutionContext) {
497
+ return (wrapper.isStatic(resolutionContext.contextId, resolutionContext.inquirer) ||
498
+ wrapper.isInRequestScope(resolutionContext.contextId, resolutionContext.inquirer) ||
499
+ wrapper.isLazyTransient(resolutionContext.contextId, resolutionContext.inquirer) ||
500
+ wrapper.isExplicitlyRequested(resolutionContext.contextId, resolutionContext.inquirer));
501
+ }
502
+ shouldSkipProviderLoading(wrapper, resolutionContext) {
503
+ const isSnapshotGraphCompilation = !!this.options?.snapshot;
504
+ const isStaticContext = resolutionContext.contextId === constants_2.STATIC_CONTEXT;
505
+ const hasNoInquirer = !resolutionContext.inquirer;
506
+ const isTopLevelStaticTransientOrRequestProvider = hasNoInquirer && (wrapper.isTransient || wrapper.scope === common_1.Scope.REQUEST);
507
+ const isStaticInquirerOutsideResolutionContext = !!resolutionContext.inquirer &&
508
+ !this.isInContext(resolutionContext.inquirer, this.createResolutionContext(resolutionContext.contextId, resolutionContext.inquirer));
509
+ const shouldSkipForStaticBootstrap = isStaticContext &&
510
+ (isTopLevelStaticTransientOrRequestProvider ||
511
+ isStaticInquirerOutsideResolutionContext);
512
+ return !isSnapshotGraphCompilation && shouldSkipForStaticBootstrap;
513
+ }
476
514
  /**
477
515
  * For nested TRANSIENT dependencies (TRANSIENT -> TRANSIENT) in non-static contexts,
478
516
  * returns parentInquirer to ensure each parent TRANSIENT gets its own instance.
@@ -480,18 +518,45 @@ class Injector {
480
518
  * can be used by multiple parents, causing nested TRANSIENTs to be shared incorrectly.
481
519
  * For non-TRANSIENT -> TRANSIENT, returns inquirer (current wrapper being created).
482
520
  */
483
- getEffectiveInquirer(dependency, inquirer, parentInquirer, contextId) {
484
- return dependency?.isTransient &&
521
+ getEffectiveInquirer(dependency, resolutionContext, parentInquirer) {
522
+ const { inquirer, contextId } = resolutionContext;
523
+ if (dependency?.isTransient && inquirer?.isTransient && parentInquirer) {
524
+ if (contextId === constants_2.STATIC_CONTEXT) {
525
+ return inquirer.getRootInquirer() ?? parentInquirer;
526
+ }
527
+ return parentInquirer;
528
+ }
529
+ return inquirer;
530
+ }
531
+ getEffectiveInquirerId(dependency, resolutionContext, parentInquirer) {
532
+ const { contextId, inquirer, effectiveInquirerId } = resolutionContext;
533
+ if (contextId === constants_2.STATIC_CONTEXT &&
534
+ dependency?.isTransient &&
485
535
  inquirer?.isTransient &&
486
- parentInquirer &&
487
- contextId !== constants_2.STATIC_CONTEXT
488
- ? parentInquirer
489
- : inquirer;
536
+ parentInquirer) {
537
+ const baseInquirerId = effectiveInquirerId ?? this.getInquirerId(parentInquirer);
538
+ return `${baseInquirerId}:${inquirer.id}`;
539
+ }
540
+ const effectiveInquirer = this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer);
541
+ return this.getInquirerId(effectiveInquirer);
542
+ }
543
+ getStaticTransientResolutionContext(resolutionContext, parentInquirer) {
544
+ const { contextId, inquirer, effectiveInquirerId } = resolutionContext;
545
+ if (contextId === constants_2.STATIC_CONTEXT &&
546
+ inquirer?.isTransient &&
547
+ parentInquirer) {
548
+ const baseInquirerId = effectiveInquirerId ?? this.getInquirerId(parentInquirer);
549
+ return this.createResolutionContext(contextId, inquirer, `${baseInquirerId}:${inquirer.id}`);
550
+ }
551
+ return resolutionContext;
552
+ }
553
+ getEffectiveResolutionContext(dependency, resolutionContext, parentInquirer) {
554
+ return this.createResolutionContext(resolutionContext.contextId, this.getEffectiveInquirer(dependency, resolutionContext, parentInquirer), this.getEffectiveInquirerId(dependency, resolutionContext, parentInquirer));
490
555
  }
491
556
  resolveScopedComponentHost(item, contextId, inquirer, parentInquirer) {
492
557
  return this.isInquirerRequest(item, parentInquirer)
493
558
  ? parentInquirer
494
- : this.resolveComponentHost(item.host, item, contextId, this.getEffectiveInquirer(item, inquirer, parentInquirer, contextId));
559
+ : this.resolveComponentHost(item.host, item, this.getEffectiveResolutionContext(item, this.createResolutionContext(contextId, inquirer), parentInquirer));
495
560
  }
496
561
  isInquirerRequest(item, parentInquirer) {
497
562
  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.18",
3
+ "version": "11.1.20",
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.18"
42
+ "@nestjs/common": "11.1.20"
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": "3c1cc5f91e95fcec27c3694cd42a08a50e85cc5f"
63
+ "gitHead": "7caeb3fb70de81085c4c3e8502a2a0e62e4f8eda"
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
  }