@nestjs/core 11.1.28 → 11.2.0
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/adapters/http-adapter.d.ts +2 -0
- package/adapters/http-adapter.js +3 -0
- package/helpers/barrier.js +4 -1
- package/helpers/router-method-factory.d.ts +1 -0
- package/helpers/router-method-factory.js +1 -0
- package/injector/container.js +2 -2
- package/injector/injector.js +6 -10
- package/injector/internal-core-module/internal-core-module-factory.js +1 -1
- package/injector/module-ref.js +14 -12
- package/injector/module.d.ts +6 -0
- package/injector/module.js +8 -0
- package/interceptors/interceptors-consumer.js +4 -6
- package/middleware/builder.js +5 -1
- package/nest-application-context.js +4 -11
- package/nest-application.d.ts +1 -0
- package/nest-application.js +15 -1
- package/package.json +3 -3
- package/router/legacy-route-converter.js +6 -4
- package/router/router-execution-context.d.ts +1 -0
- package/router/router-execution-context.js +21 -0
- package/router/router-response-controller.d.ts +1 -0
- package/router/router-response-controller.js +33 -10
- package/router/sse-stream.d.ts +1 -0
- package/router/sse-stream.js +22 -11
- package/scanner.js +4 -1
|
@@ -41,6 +41,8 @@ export declare abstract class AbstractHttpAdapter<TServer = any, TRequest = any,
|
|
|
41
41
|
all(path: any, handler: RequestHandler): any;
|
|
42
42
|
search(handler: RequestHandler): any;
|
|
43
43
|
search(path: any, handler: RequestHandler): any;
|
|
44
|
+
query(handler: RequestHandler): any;
|
|
45
|
+
query(path: any, handler: RequestHandler): any;
|
|
44
46
|
options(handler: RequestHandler): any;
|
|
45
47
|
options(path: any, handler: RequestHandler): any;
|
|
46
48
|
listen(port: string | number, callback?: () => void): any;
|
package/adapters/http-adapter.js
CHANGED
package/helpers/barrier.js
CHANGED
|
@@ -11,6 +11,9 @@ class Barrier {
|
|
|
11
11
|
this.promise = new Promise(resolve => {
|
|
12
12
|
this.resolve = resolve;
|
|
13
13
|
});
|
|
14
|
+
if (targetCount === 0) {
|
|
15
|
+
this.resolve();
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
16
19
|
* Signal that a participant has reached the barrier.
|
|
@@ -19,7 +22,7 @@ class Barrier {
|
|
|
19
22
|
*/
|
|
20
23
|
signal() {
|
|
21
24
|
this.currentCount += 1;
|
|
22
|
-
if (this.currentCount
|
|
25
|
+
if (this.currentCount >= this.targetCount) {
|
|
23
26
|
this.resolve();
|
|
24
27
|
}
|
|
25
28
|
}
|
|
@@ -19,6 +19,7 @@ exports.REQUEST_METHOD_MAP = {
|
|
|
19
19
|
[request_method_enum_1.RequestMethod.MOVE]: 'move',
|
|
20
20
|
[request_method_enum_1.RequestMethod.LOCK]: 'lock',
|
|
21
21
|
[request_method_enum_1.RequestMethod.UNLOCK]: 'unlock',
|
|
22
|
+
[request_method_enum_1.RequestMethod.QUERY]: 'query',
|
|
22
23
|
};
|
|
23
24
|
class RouterMethodFactory {
|
|
24
25
|
get(target, requestMethod) {
|
package/injector/container.js
CHANGED
|
@@ -65,7 +65,7 @@ class NestContainer {
|
|
|
65
65
|
if (this.modules.has(token)) {
|
|
66
66
|
return {
|
|
67
67
|
moduleRef: this.modules.get(token),
|
|
68
|
-
inserted:
|
|
68
|
+
inserted: false,
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
return {
|
|
@@ -91,7 +91,7 @@ class NestContainer {
|
|
|
91
91
|
type,
|
|
92
92
|
dynamicMetadata,
|
|
93
93
|
}, scope),
|
|
94
|
-
inserted:
|
|
94
|
+
inserted: true,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
async setModule({ token, dynamicMetadata, type }, scope) {
|
package/injector/injector.js
CHANGED
|
@@ -328,16 +328,12 @@ class Injector {
|
|
|
328
328
|
this.printFoundInModuleLog(name, relatedModule);
|
|
329
329
|
instanceWrapperRef = providers.get(name);
|
|
330
330
|
this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapperRef);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
* staticity of the dependency tree and lead to undefined / null injection.
|
|
338
|
-
*/
|
|
339
|
-
break;
|
|
340
|
-
}
|
|
331
|
+
/*
|
|
332
|
+
* Stop at the first direct export match. Continuing when the provider is
|
|
333
|
+
* already resolved would let a later import (e.g. a global forRoot module)
|
|
334
|
+
* override an explicit forFeature import for the same token.
|
|
335
|
+
*/
|
|
336
|
+
break;
|
|
341
337
|
}
|
|
342
338
|
return instanceWrapperRef;
|
|
343
339
|
}
|
|
@@ -18,7 +18,7 @@ class InternalCoreModuleFactory {
|
|
|
18
18
|
timestamp: false,
|
|
19
19
|
});
|
|
20
20
|
const injector = new injector_1.Injector({
|
|
21
|
-
preview: container.contextOptions?.preview,
|
|
21
|
+
preview: container.contextOptions?.preview ?? false,
|
|
22
22
|
instanceDecorator: container.contextOptions?.instrument?.instanceDecorator,
|
|
23
23
|
});
|
|
24
24
|
const instanceLoader = new instance_loader_1.InstanceLoader(container, injector, graphInspector, logger);
|
package/injector/module-ref.js
CHANGED
|
@@ -56,9 +56,9 @@ class ModuleRef extends abstract_instance_resolver_1.AbstractInstanceResolver {
|
|
|
56
56
|
isPending: false,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
return new Promise(
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const callback = async (instances) => {
|
|
61
|
+
try {
|
|
62
62
|
const properties = await this.injector.resolveProperties(wrapper, moduleRef, undefined, {
|
|
63
63
|
contextId: contextId ?? constants_1.STATIC_CONTEXT,
|
|
64
64
|
inquirer: wrapper,
|
|
@@ -66,15 +66,17 @@ class ModuleRef extends abstract_instance_resolver_1.AbstractInstanceResolver {
|
|
|
66
66
|
const instance = new type(...instances);
|
|
67
67
|
this.injector.applyProperties(instance, properties);
|
|
68
68
|
resolve(instance);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
reject(err);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
this.injector
|
|
75
|
+
.resolveConstructorParams(wrapper, moduleRef, undefined, callback, {
|
|
76
|
+
contextId: contextId ?? constants_1.STATIC_CONTEXT,
|
|
77
|
+
inquirer: wrapper,
|
|
78
|
+
})
|
|
79
|
+
.catch(reject);
|
|
78
80
|
});
|
|
79
81
|
}
|
|
80
82
|
}
|
package/injector/module.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare class Module {
|
|
|
35
35
|
get entryProviders(): Array<InstanceWrapper<Injectable>>;
|
|
36
36
|
get exports(): Set<InjectionToken>;
|
|
37
37
|
get instance(): NestModule;
|
|
38
|
+
/**
|
|
39
|
+
* Modules are registered in the container before they are scanned (dynamic
|
|
40
|
+
* `imports` are pre-registered by `NestContainer#addDynamicMetadata`), so
|
|
41
|
+
* registration alone does not imply the module was ever instantiated.
|
|
42
|
+
*/
|
|
43
|
+
get isInstantiated(): boolean;
|
|
38
44
|
get metatype(): Type<any>;
|
|
39
45
|
get distance(): number;
|
|
40
46
|
set distance(value: number);
|
package/injector/module.js
CHANGED
|
@@ -84,6 +84,14 @@ class Module {
|
|
|
84
84
|
const moduleRef = this._providers.get(this._metatype);
|
|
85
85
|
return moduleRef.instance;
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Modules are registered in the container before they are scanned (dynamic
|
|
89
|
+
* `imports` are pre-registered by `NestContainer#addDynamicMetadata`), so
|
|
90
|
+
* registration alone does not imply the module was ever instantiated.
|
|
91
|
+
*/
|
|
92
|
+
get isInstantiated() {
|
|
93
|
+
return !(0, shared_utils_1.isNil)(this._providers.get(this._metatype)?.instance);
|
|
94
|
+
}
|
|
87
95
|
get metatype() {
|
|
88
96
|
return this._metatype;
|
|
89
97
|
}
|
|
@@ -39,12 +39,10 @@ class InterceptorsConsumer {
|
|
|
39
39
|
.then(res => {
|
|
40
40
|
if (subscriber.closed) {
|
|
41
41
|
// The outer subscription was torn down (e.g. an SSE client disconnect)
|
|
42
|
-
// before the async handler resolved.
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sub.unsubscribe();
|
|
47
|
-
}
|
|
42
|
+
// before the async handler resolved. Do not subscribe the producer
|
|
43
|
+
// Observable after the consumer has already gone away — subscribing
|
|
44
|
+
// only to unsubscribe in the same tick would start producer side
|
|
45
|
+
// effects just to immediately abort them.
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
const isDeferred = res instanceof Promise || res instanceof rxjs_1.Observable;
|
package/middleware/builder.js
CHANGED
|
@@ -73,7 +73,11 @@ MiddlewareBuilder.ConfigProxy = class {
|
|
|
73
73
|
.map(route => ({
|
|
74
74
|
method: route.method,
|
|
75
75
|
path: route.path,
|
|
76
|
-
|
|
76
|
+
// No `g` flag: each regex is reused across every route below, and a
|
|
77
|
+
// global regex advances `lastIndex` on a match, so the next `test()`
|
|
78
|
+
// would resume mid-string and fail the `^` anchor. The pattern is
|
|
79
|
+
// anchored and only used with `test()`, so `g` buys nothing anyway.
|
|
80
|
+
regex: new RegExp('^(' + route.path.replace(regexMatchParams, wildcard) + ')$'),
|
|
77
81
|
}));
|
|
78
82
|
return routes.filter(route => {
|
|
79
83
|
const isOverlapped = (item) => {
|
|
@@ -104,17 +104,10 @@ class NestApplicationContext extends abstract_instance_resolver_1.AbstractInstan
|
|
|
104
104
|
if (this.isInitialized) {
|
|
105
105
|
return this;
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
await this.callBootstrapHook();
|
|
112
|
-
resolve();
|
|
113
|
-
}
|
|
114
|
-
catch (err) {
|
|
115
|
-
reject(err);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
107
|
+
this.initializationPromise = (async () => {
|
|
108
|
+
await this.callInitHook();
|
|
109
|
+
await this.callBootstrapHook();
|
|
110
|
+
})();
|
|
118
111
|
await this.initializationPromise;
|
|
119
112
|
this.isInitialized = true;
|
|
120
113
|
return this;
|
package/nest-application.d.ts
CHANGED
package/nest-application.js
CHANGED
|
@@ -151,7 +151,7 @@ class NestApplication extends nest_application_context_1.NestApplicationContext
|
|
|
151
151
|
return this;
|
|
152
152
|
}
|
|
153
153
|
use(...args) {
|
|
154
|
-
this.httpAdapter.use(...args);
|
|
154
|
+
this.httpAdapter.use(...this.applyFunctionDecoratorIfRegistered(args));
|
|
155
155
|
return this;
|
|
156
156
|
}
|
|
157
157
|
useBodyParser(...args) {
|
|
@@ -329,5 +329,19 @@ class NestApplication extends nest_application_context_1.NestApplicationContext
|
|
|
329
329
|
}
|
|
330
330
|
return instances;
|
|
331
331
|
}
|
|
332
|
+
applyFunctionDecoratorIfRegistered(args) {
|
|
333
|
+
if (!this.appOptions.instrument?.instanceDecorator) {
|
|
334
|
+
return args;
|
|
335
|
+
}
|
|
336
|
+
const [firstArg, secondArg] = args;
|
|
337
|
+
return [
|
|
338
|
+
(0, shared_utils_1.isFunction)(firstArg)
|
|
339
|
+
? this.appOptions.instrument.instanceDecorator(firstArg)
|
|
340
|
+
: firstArg,
|
|
341
|
+
(0, shared_utils_1.isFunction)(secondArg)
|
|
342
|
+
? this.appOptions.instrument.instanceDecorator(secondArg)
|
|
343
|
+
: secondArg,
|
|
344
|
+
];
|
|
345
|
+
}
|
|
332
346
|
}
|
|
333
347
|
exports.NestApplication = NestApplication;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/core",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.0",
|
|
4
4
|
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
|
|
5
5
|
"author": "Kamil Mysliwiec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"uid": "2.0.2"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@nestjs/common": "11.
|
|
31
|
+
"@nestjs/common": "11.2.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@nestjs/common": "^11.0.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"optional": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "f2a7e4b1f57193cbbfe34e988276fc0eaf0b6eb5"
|
|
53
53
|
}
|
|
@@ -45,10 +45,12 @@ class LegacyRouteConverter {
|
|
|
45
45
|
}
|
|
46
46
|
// When route includes any wildcard segments in the middle.
|
|
47
47
|
if (normalizedRoute.includes('/*/')) {
|
|
48
|
-
// Replace each
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
// Replace each "*" segment with a named parameter, using a different name
|
|
49
|
+
// for each. Match "/*" with a lookahead for the following "/" so the
|
|
50
|
+
// trailing slash is not consumed. Consuming it made two adjacent "/*/*/"
|
|
51
|
+
// segments share a slash, so only the first one got converted and the
|
|
52
|
+
// second was left as an unnamed "*" that path-to-regexp still rejects.
|
|
53
|
+
const convertedRoute = route.replaceAll(/\/\*(?=\/)/g, (match, offset) => `/*path${offset}`);
|
|
52
54
|
printWarning(route, convertedRoute);
|
|
53
55
|
return convertedRoute;
|
|
54
56
|
}
|
|
@@ -51,4 +51,5 @@ export declare class RouterExecutionContext {
|
|
|
51
51
|
})[]): (<TRequest, TResponse>(args: any[], req: TRequest, res: TResponse, next: Function) => Promise<void>) | null;
|
|
52
52
|
createHandleResponseFn(callback: (...args: unknown[]) => unknown, isResponseHandled: boolean, redirectResponse?: RedirectResponse, httpStatusCode?: number): HandleResponseFn;
|
|
53
53
|
private isResponseHandled;
|
|
54
|
+
private attachSseAbortSignal;
|
|
54
55
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RouterExecutionContext = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
const constants_1 = require("@nestjs/common/constants");
|
|
6
|
+
const sse_signal_decorator_1 = require("@nestjs/common/decorators/http/sse-signal.decorator");
|
|
6
7
|
const route_paramtypes_enum_1 = require("@nestjs/common/enums/route-paramtypes.enum");
|
|
7
8
|
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
|
8
9
|
const guards_1 = require("../guards");
|
|
@@ -43,6 +44,13 @@ class RouterExecutionContext {
|
|
|
43
44
|
this.responseController.setStatus(res, httpStatusCode);
|
|
44
45
|
hasCustomHeaders &&
|
|
45
46
|
this.responseController.setHeaders(res, responseHeaders);
|
|
47
|
+
if (isSseHandler) {
|
|
48
|
+
// Attach a per-request AbortController before the handler runs so async
|
|
49
|
+
// @Sse() handlers can observe client disconnects via @SseSignal() during
|
|
50
|
+
// their setup. The controller is aborted in RouterResponseController.sse()
|
|
51
|
+
// when the underlying connection closes.
|
|
52
|
+
this.attachSseAbortSignal(req);
|
|
53
|
+
}
|
|
46
54
|
const resultOrDeferred = this.interceptorsConsumer.intercept(interceptors, [req, res, next], instance, callback, handler(args, req, res, next), contextType);
|
|
47
55
|
const result = isSseHandler ? resultOrDeferred : await resultOrDeferred;
|
|
48
56
|
await fnHandleResponse(result, res, req);
|
|
@@ -190,5 +198,18 @@ class RouterExecutionContext {
|
|
|
190
198
|
const isPassthroughEnabled = this.contextUtils.reflectPassthrough(instance, methodName);
|
|
191
199
|
return hasResponseOrNextDecorator && !isPassthroughEnabled;
|
|
192
200
|
}
|
|
201
|
+
attachSseAbortSignal(req) {
|
|
202
|
+
const carrier = req;
|
|
203
|
+
// Attach to both the framework request and its raw form (when present), since
|
|
204
|
+
// @SseSignal() reads from the execution-context request while
|
|
205
|
+
// RouterResponseController.sse() operates on the raw request.
|
|
206
|
+
if (!carrier[sse_signal_decorator_1.SSE_ABORT_CONTROLLER]) {
|
|
207
|
+
carrier[sse_signal_decorator_1.SSE_ABORT_CONTROLLER] = new AbortController();
|
|
208
|
+
}
|
|
209
|
+
if (carrier.raw && !carrier.raw[sse_signal_decorator_1.SSE_ABORT_CONTROLLER]) {
|
|
210
|
+
carrier.raw[sse_signal_decorator_1.SSE_ABORT_CONTROLLER] =
|
|
211
|
+
carrier[sse_signal_decorator_1.SSE_ABORT_CONTROLLER];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
193
214
|
}
|
|
194
215
|
exports.RouterExecutionContext = RouterExecutionContext;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RouterResponseController = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
|
+
const sse_signal_decorator_1 = require("@nestjs/common/decorators/http/sse-signal.decorator");
|
|
5
6
|
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
|
6
7
|
const rxjs_1 = require("rxjs");
|
|
7
8
|
const operators_1 = require("rxjs/operators");
|
|
@@ -57,12 +58,27 @@ class RouterResponseController {
|
|
|
57
58
|
const statusCode = options?.statusCode ??
|
|
58
59
|
response.statusCode ??
|
|
59
60
|
200;
|
|
61
|
+
// Create a per-request AbortController and expose its signal on the request
|
|
62
|
+
// object so async @Sse() handlers can observe client disconnects (via the
|
|
63
|
+
// @SseSignal() parameter decorator) and stop/clean up in-flight setup work.
|
|
64
|
+
// The controller is reused if one was already attached upstream (e.g. when the
|
|
65
|
+
// handler is wrapped by interceptors and the signal was created earlier).
|
|
66
|
+
const abortController = this.getOrCreateAbortController(request);
|
|
60
67
|
return new Promise((resolve, reject) => {
|
|
61
68
|
let settled = false;
|
|
62
69
|
let closeRequested = false;
|
|
63
70
|
let subscription;
|
|
64
71
|
const disconnectSource = request.socket ?? response;
|
|
65
|
-
|
|
72
|
+
// Ends the request-scoped lifetime: stops listening for disconnects and
|
|
73
|
+
// aborts the signal handed to the route handler. Every terminal path of
|
|
74
|
+
// the SSE lifecycle (disconnect, completion, error) funnels through here,
|
|
75
|
+
// so a handler that ties its resources to the signal releases them once,
|
|
76
|
+
// regardless of how the stream ended. `abort()` is idempotent, so paths
|
|
77
|
+
// that already aborted on disconnect are unaffected.
|
|
78
|
+
const finalize = () => {
|
|
79
|
+
disconnectSource.removeListener('close', onClose);
|
|
80
|
+
abortController.abort();
|
|
81
|
+
};
|
|
66
82
|
const endStream = () => {
|
|
67
83
|
if (!stream.writableEnded) {
|
|
68
84
|
stream.end();
|
|
@@ -74,11 +90,11 @@ class RouterResponseController {
|
|
|
74
90
|
}
|
|
75
91
|
closeRequested = true;
|
|
76
92
|
if (!subscription) {
|
|
77
|
-
|
|
93
|
+
finalize();
|
|
78
94
|
return;
|
|
79
95
|
}
|
|
80
96
|
settled = true;
|
|
81
|
-
|
|
97
|
+
finalize();
|
|
82
98
|
subscription?.unsubscribe();
|
|
83
99
|
endStream();
|
|
84
100
|
response.end();
|
|
@@ -92,10 +108,10 @@ class RouterResponseController {
|
|
|
92
108
|
}
|
|
93
109
|
this.assertObservable(observableResult);
|
|
94
110
|
if (closeRequested) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
// The client disconnected while the async handler was resolving.
|
|
112
|
+
// Do not subscribe the producer Observable after the consumer has
|
|
113
|
+
// already gone away — subscribing only to abort it in the same tick
|
|
114
|
+
// would start producer side effects just to immediately cancel them.
|
|
99
115
|
settled = true;
|
|
100
116
|
endStream();
|
|
101
117
|
response.end();
|
|
@@ -130,7 +146,7 @@ class RouterResponseController {
|
|
|
130
146
|
return;
|
|
131
147
|
}
|
|
132
148
|
settled = true;
|
|
133
|
-
|
|
149
|
+
finalize();
|
|
134
150
|
endStream();
|
|
135
151
|
reject(err);
|
|
136
152
|
},
|
|
@@ -139,7 +155,7 @@ class RouterResponseController {
|
|
|
139
155
|
return;
|
|
140
156
|
}
|
|
141
157
|
settled = true;
|
|
142
|
-
|
|
158
|
+
finalize();
|
|
143
159
|
endStream();
|
|
144
160
|
resolve();
|
|
145
161
|
},
|
|
@@ -167,7 +183,7 @@ class RouterResponseController {
|
|
|
167
183
|
return;
|
|
168
184
|
}
|
|
169
185
|
settled = true;
|
|
170
|
-
|
|
186
|
+
finalize();
|
|
171
187
|
endStream();
|
|
172
188
|
reject(err);
|
|
173
189
|
});
|
|
@@ -178,5 +194,12 @@ class RouterResponseController {
|
|
|
178
194
|
throw new ReferenceError('You must return an Observable stream to use Server-Sent Events (SSE).');
|
|
179
195
|
}
|
|
180
196
|
}
|
|
197
|
+
getOrCreateAbortController(request) {
|
|
198
|
+
const carrier = request;
|
|
199
|
+
if (!carrier[sse_signal_decorator_1.SSE_ABORT_CONTROLLER]) {
|
|
200
|
+
carrier[sse_signal_decorator_1.SSE_ABORT_CONTROLLER] = new AbortController();
|
|
201
|
+
}
|
|
202
|
+
return carrier[sse_signal_decorator_1.SSE_ABORT_CONTROLLER];
|
|
203
|
+
}
|
|
181
204
|
}
|
|
182
205
|
exports.RouterResponseController = RouterResponseController;
|
package/router/sse-stream.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type HeaderStream = WritableHeaderStream & ReadHeaders;
|
|
|
22
22
|
* - type
|
|
23
23
|
* - id
|
|
24
24
|
* - retry
|
|
25
|
+
* - comment
|
|
25
26
|
*
|
|
26
27
|
* If constructed with a HTTP Request, it will optimise the socket for streaming.
|
|
27
28
|
* If this stream is piped to an HTTP Response, it will set appropriate headers.
|
package/router/sse-stream.js
CHANGED
|
@@ -3,14 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SseStream = void 0;
|
|
4
4
|
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
|
5
5
|
const stream_1 = require("stream");
|
|
6
|
+
function serializeSseLines(value, prefix) {
|
|
7
|
+
return value
|
|
8
|
+
.split(/\r\n|\r|\n/)
|
|
9
|
+
.map(line => `${prefix}${line}\n`)
|
|
10
|
+
.join('');
|
|
11
|
+
}
|
|
6
12
|
function toDataString(data) {
|
|
7
13
|
if ((0, shared_utils_1.isObject)(data)) {
|
|
8
14
|
return toDataString(JSON.stringify(data));
|
|
9
15
|
}
|
|
10
|
-
return data
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
return serializeSseLines(data, 'data: ');
|
|
17
|
+
}
|
|
18
|
+
function toCommentString(comment) {
|
|
19
|
+
return serializeSseLines(comment, ': ');
|
|
20
|
+
}
|
|
21
|
+
function isCommentOnly(message) {
|
|
22
|
+
return (!(0, shared_utils_1.isNil)(message.comment) &&
|
|
23
|
+
(0, shared_utils_1.isUndefined)(message.data) &&
|
|
24
|
+
(0, shared_utils_1.isUndefined)(message.type) &&
|
|
25
|
+
(0, shared_utils_1.isUndefined)(message.retry));
|
|
14
26
|
}
|
|
15
27
|
/**
|
|
16
28
|
* Adapted from https://raw.githubusercontent.com/EventSource/node-ssestream
|
|
@@ -21,6 +33,7 @@ function toDataString(data) {
|
|
|
21
33
|
* - type
|
|
22
34
|
* - id
|
|
23
35
|
* - retry
|
|
36
|
+
* - comment
|
|
24
37
|
*
|
|
25
38
|
* If constructed with a HTTP Request, it will optimise the socket for streaming.
|
|
26
39
|
* If this stream is piped to an HTTP Response, it will set appropriate headers.
|
|
@@ -84,12 +97,10 @@ class SseStream extends stream_1.Transform {
|
|
|
84
97
|
this.commitHeaders();
|
|
85
98
|
const sanitize = (val) => String(val).replace(/[\r\n]/g, '');
|
|
86
99
|
let data = message.type ? `event: ${sanitize(message.type)}\n` : '';
|
|
87
|
-
data +=
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
data += message.retry ? `retry: ${sanitize(message.retry)}\n` : '';
|
|
92
|
-
data += message.data ? toDataString(message.data) : '';
|
|
100
|
+
data += !(0, shared_utils_1.isNil)(message.id) ? `id: ${sanitize(message.id)}\n` : '';
|
|
101
|
+
data += !(0, shared_utils_1.isNil)(message.retry) ? `retry: ${sanitize(message.retry)}\n` : '';
|
|
102
|
+
data += !(0, shared_utils_1.isNil)(message.comment) ? toCommentString(message.comment) : '';
|
|
103
|
+
data += !(0, shared_utils_1.isNil)(message.data) ? toDataString(message.data) : '';
|
|
93
104
|
data += '\n';
|
|
94
105
|
this.push(data);
|
|
95
106
|
callback();
|
|
@@ -98,7 +109,7 @@ class SseStream extends stream_1.Transform {
|
|
|
98
109
|
* Calls `.write` but handles the drain if needed
|
|
99
110
|
*/
|
|
100
111
|
writeMessage(message, cb) {
|
|
101
|
-
if (message.id
|
|
112
|
+
if ((0, shared_utils_1.isNil)(message.id) && !isCommentOnly(message)) {
|
|
102
113
|
this.lastEventId++;
|
|
103
114
|
message.id = this.lastEventId.toString();
|
|
104
115
|
}
|
package/scanner.js
CHANGED
|
@@ -40,6 +40,9 @@ class DependenciesScanner {
|
|
|
40
40
|
async scanForModules({ moduleDefinition, lazy, scope = [], ctxRegistry = [], overrides = [], }) {
|
|
41
41
|
const { moduleRef: moduleInstance, inserted: moduleInserted } = (await this.insertOrOverrideModule(moduleDefinition, overrides, scope)) ??
|
|
42
42
|
{};
|
|
43
|
+
if (lazy && !moduleInserted && moduleInstance?.isInstantiated) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
43
46
|
moduleDefinition =
|
|
44
47
|
this.getOverrideModuleByModule(moduleDefinition, overrides)?.newModule ??
|
|
45
48
|
moduleDefinition;
|
|
@@ -81,7 +84,7 @@ class DependenciesScanner {
|
|
|
81
84
|
if (!moduleInstance) {
|
|
82
85
|
return registeredModuleRefs;
|
|
83
86
|
}
|
|
84
|
-
if (lazy
|
|
87
|
+
if (lazy) {
|
|
85
88
|
this.container.bindGlobalsToImports(moduleInstance);
|
|
86
89
|
}
|
|
87
90
|
return [moduleInstance].concat(registeredModuleRefs);
|