@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 +2 -1
- package/injector/injector.d.ts +28 -11
- package/injector/injector.js +136 -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,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,
|
|
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);
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
148
|
-
const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper,
|
|
149
|
-
const instanceHost = paramWrapperWithInstance.getInstanceByContextId(this.getContextId(contextId, paramWrapperWithInstance),
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
246
|
+
return this.lookupComponent(providers, moduleRef, { ...dependencyContext, name: token }, wrapper, resolutionContext, keyOrIndex);
|
|
241
247
|
}
|
|
242
|
-
async resolveComponentHost(moduleRef, instanceWrapper,
|
|
243
|
-
const inquirerId = this.
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
291
|
+
return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, resolutionContext, keyOrIndex);
|
|
286
292
|
}
|
|
287
|
-
async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper,
|
|
288
|
-
const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, new Set(),
|
|
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(),
|
|
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,
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
365
|
-
const paramWrapperWithInstance = await this.resolveComponentHost(moduleRef, paramWrapper,
|
|
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),
|
|
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,
|
|
414
|
+
async instantiateClass(instances, wrapper, targetMetatype, resolutionContext = { contextId: constants_2.STATIC_CONTEXT }) {
|
|
408
415
|
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);
|
|
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
|
-
|
|
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
|
|
458
|
-
return item?.getInstanceByContextId(this.getContextId(contextId, item),
|
|
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,
|
|
484
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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,
|
|
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;
|
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.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.
|
|
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": "
|
|
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
|
-
|
|
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
|
}
|