@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 +2 -1
- package/injector/injector.d.ts +28 -11
- package/injector/injector.js +139 -71
- package/injector/module-ref.js +12 -5
- package/nest-factory.js +1 -0
- package/package.json +3 -3
- package/router/router-execution-context.js +6 -1
- package/router/router-response-controller.d.ts +2 -1
- package/router/router-response-controller.js +63 -29
- package/router/sse-stream.d.ts +12 -0
- package/router/sse-stream.js +38 -9
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.
|
|
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
|
|
package/injector/injector.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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>,
|
|
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,
|
|
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>,
|
|
72
|
-
resolveComponentHost<T>(moduleRef: Module, instanceWrapper: InstanceWrapper<T | Promise<T>>,
|
|
73
|
-
lookupComponent<T = any>(providers: Map<Function | string | symbol, InstanceWrapper>, moduleRef: Module, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper<T>,
|
|
74
|
-
lookupComponentInParentModules<T = any>(dependencyContext: InjectorDependencyContext, moduleRef: Module, wrapper: InstanceWrapper<T>,
|
|
75
|
-
lookupComponentInImports(moduleRef: Module, name: InjectionToken, wrapper: InstanceWrapper, moduleRegistry?: Set<string>,
|
|
76
|
-
resolveProperties<T>(wrapper: InstanceWrapper<T>, moduleRef: Module, inject?: InjectorDependency[],
|
|
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,
|
|
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 {};
|
package/injector/injector.js
CHANGED
|
@@ -39,12 +39,13 @@ class Injector {
|
|
|
39
39
|
collection.set(token, wrapper);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
async loadInstance(wrapper, collection, moduleRef,
|
|
43
|
-
const inquirerId = this.
|
|
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 &&
|
|
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,
|
|
70
|
-
const instance = await this.instantiateClass(instances, wrapper, targetWrapper,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
148
|
-
const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper,
|
|
149
|
-
const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance),
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
249
|
+
return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, resolutionContext, keyOrIndex);
|
|
241
250
|
}
|
|
242
|
-
async resolveComponentHost(moduleRef, instanceWrapper,
|
|
243
|
-
const inquirerId = this.
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
294
|
+
return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext, keyOrIndex);
|
|
286
295
|
}
|
|
287
|
-
async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper,
|
|
288
|
-
const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(),
|
|
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(),
|
|
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,
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
365
|
-
const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper,
|
|
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),
|
|
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,
|
|
417
|
+
async instantiateClass(instances, wrapper, targetMetatype, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
|
|
408
418
|
const { metatype, inject } = wrapper;
|
|
409
|
-
const inquirerId = this.
|
|
410
|
-
const instanceHost = targetMetatype.getInstanceByContextId(this.getContextId(contextId, targetMetatype), inquirerId);
|
|
411
|
-
const isInContext =
|
|
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
|
-
|
|
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
|
|
458
|
-
return item?.getInstanceByContextId(this.getContextId(contextId, item),
|
|
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,
|
|
484
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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,
|
|
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;
|
package/injector/module-ref.js
CHANGED
|
@@ -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:
|
|
24
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
: options
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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) {
|
package/router/sse-stream.d.ts
CHANGED
|
@@ -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
|
package/router/sse-stream.js
CHANGED
|
@@ -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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
79
|
+
this._destination.flushHeaders?.();
|
|
54
80
|
}
|
|
55
|
-
|
|
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 +=
|
|
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 (
|
|
101
|
+
if (message.id === undefined || message.id === null) {
|
|
73
102
|
this.lastEventId++;
|
|
74
103
|
message.id = this.lastEventId.toString();
|
|
75
104
|
}
|