@nestjs/core 12.0.0-alpha.4 → 12.0.0-alpha.6
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 +25 -45
- package/adapters/http-adapter.d.ts +2 -0
- package/adapters/http-adapter.js +3 -0
- package/application-config.d.ts +8 -2
- package/application-config.js +14 -0
- package/errors/exceptions/index.d.ts +1 -0
- package/errors/exceptions/index.js +1 -0
- package/errors/exceptions/invalid-class-module.exception.d.ts +1 -1
- package/errors/exceptions/invalid-class-module.exception.js +2 -2
- package/errors/exceptions/invalid-module.exception.d.ts +1 -1
- package/errors/exceptions/invalid-module.exception.js +2 -2
- package/errors/exceptions/route-conflict.exception.d.ts +4 -0
- package/errors/exceptions/route-conflict.exception.js +7 -0
- package/errors/messages.d.ts +5 -2
- package/errors/messages.js +37 -4
- package/exceptions/base-exception-filter.js +8 -3
- package/helpers/barrier.js +4 -1
- package/helpers/handler-metadata-storage.d.ts +1 -0
- package/helpers/router-method-factory.d.ts +1 -0
- package/helpers/router-method-factory.js +1 -0
- package/hooks/before-app-shutdown.hook.js +11 -2
- package/hooks/on-app-shutdown.hook.js +11 -2
- package/hooks/on-module-destroy.hook.js +11 -2
- package/injector/container.js +2 -2
- package/injector/helpers/transient-instances.d.ts +12 -0
- package/injector/helpers/transient-instances.js +28 -0
- package/injector/injector.d.ts +4 -3
- package/injector/injector.js +34 -13
- package/injector/instance-wrapper.d.ts +2 -0
- package/injector/instance-wrapper.js +25 -0
- package/injector/internal-core-module/internal-core-module-factory.js +1 -1
- package/injector/module.d.ts +6 -0
- package/injector/module.js +8 -0
- package/interceptors/interceptors-consumer.js +32 -7
- package/middleware/builder.js +5 -1
- package/nest-application-context.js +1 -1
- package/nest-application.d.ts +3 -1
- package/nest-application.js +96 -6
- package/package.json +3 -14
- package/router/interfaces/index.d.ts +3 -0
- package/router/interfaces/index.js +3 -0
- package/router/interfaces/resolved-route.interface.d.ts +32 -0
- package/router/interfaces/resolved-route.interface.js +1 -0
- package/router/interfaces/resolver.interface.d.ts +5 -1
- package/router/interfaces/route-conflict.interface.d.ts +14 -0
- package/router/interfaces/route-conflict.interface.js +1 -0
- package/router/interfaces/route-resolution-options.interface.d.ts +24 -0
- package/router/interfaces/route-resolution-options.interface.js +1 -0
- package/router/legacy-route-converter.d.ts +1 -1
- package/router/legacy-route-converter.js +24 -13
- package/router/route-conflict-detector.d.ts +71 -0
- package/router/route-conflict-detector.js +276 -0
- package/router/route-specificity-sorter.d.ts +23 -0
- package/router/route-specificity-sorter.js +59 -0
- package/router/router-execution-context.d.ts +1 -0
- package/router/router-execution-context.js +26 -3
- package/router/router-explorer.d.ts +11 -2
- package/router/router-explorer.js +61 -19
- package/router/router-response-controller.d.ts +1 -0
- package/router/router-response-controller.js +126 -49
- package/router/routes-resolver.d.ts +7 -4
- package/router/routes-resolver.js +9 -7
- package/router/sse-stream.d.ts +1 -0
- package/router/sse-stream.js +24 -13
- package/scanner.js +13 -6
|
@@ -45,10 +45,10 @@ export class RouterExplorer {
|
|
|
45
45
|
const interceptorsConsumer = new InterceptorsConsumer();
|
|
46
46
|
this.executionContextCreator = new RouterExecutionContext(routeParamsFactory, pipesContextCreator, pipesConsumer, guardsContextCreator, guardsConsumer, interceptorsContextCreator, interceptorsConsumer, container.getHttpAdapterRef());
|
|
47
47
|
}
|
|
48
|
-
explore(instanceWrapper, moduleKey, httpAdapterRef, host, routePathMetadata) {
|
|
48
|
+
explore(instanceWrapper, moduleKey, httpAdapterRef, host, routePathMetadata, options = {}) {
|
|
49
49
|
const { instance } = instanceWrapper;
|
|
50
50
|
const routerPaths = this.pathsExplorer.scanForPaths(instance);
|
|
51
|
-
this.applyPathsToRouterProxy(httpAdapterRef, routerPaths, instanceWrapper, moduleKey, routePathMetadata, host);
|
|
51
|
+
this.applyPathsToRouterProxy(httpAdapterRef, routerPaths, instanceWrapper, moduleKey, routePathMetadata, host, options);
|
|
52
52
|
}
|
|
53
53
|
extractRouterPath(metatype) {
|
|
54
54
|
const path = Reflect.getMetadata(PATH_METADATA, metatype);
|
|
@@ -60,14 +60,15 @@ export class RouterExplorer {
|
|
|
60
60
|
}
|
|
61
61
|
return [addLeadingSlash(path)];
|
|
62
62
|
}
|
|
63
|
-
applyPathsToRouterProxy(router, routeDefinitions, instanceWrapper, moduleKey, routePathMetadata, host) {
|
|
63
|
+
applyPathsToRouterProxy(router, routeDefinitions, instanceWrapper, moduleKey, routePathMetadata, host, options = {}) {
|
|
64
64
|
(routeDefinitions || []).forEach(routeDefinition => {
|
|
65
65
|
const { version: methodVersion } = routeDefinition;
|
|
66
66
|
routePathMetadata.methodVersion = methodVersion;
|
|
67
|
-
this.applyCallbackToRouter(router, routeDefinition, instanceWrapper, moduleKey, routePathMetadata, host);
|
|
67
|
+
this.applyCallbackToRouter(router, routeDefinition, instanceWrapper, moduleKey, routePathMetadata, host, options);
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
-
applyCallbackToRouter(router, routeDefinition, instanceWrapper, moduleKey, routePathMetadata, host) {
|
|
70
|
+
applyCallbackToRouter(router, routeDefinition, instanceWrapper, moduleKey, routePathMetadata, host, options = {}) {
|
|
71
|
+
const { onRouteResolved, deferRegistration = false } = options;
|
|
71
72
|
const { path: paths, requestMethod, targetCallback, methodName, } = routeDefinition;
|
|
72
73
|
const { instance } = instanceWrapper;
|
|
73
74
|
const routerMethodRef = this.routerMethodFactory
|
|
@@ -90,6 +91,9 @@ export class RouterExplorer {
|
|
|
90
91
|
routePathMetadata.methodPath = path;
|
|
91
92
|
const pathsToRegister = this.routePathFactory.create(routePathMetadata, requestMethod);
|
|
92
93
|
pathsToRegister.forEach(path => {
|
|
94
|
+
const normalizedPath = router.normalizePath
|
|
95
|
+
? router.normalizePath(path)
|
|
96
|
+
: path;
|
|
93
97
|
const entrypointDefinition = {
|
|
94
98
|
type: 'http-endpoint',
|
|
95
99
|
methodName,
|
|
@@ -103,21 +107,34 @@ export class RouterExplorer {
|
|
|
103
107
|
controllerVersion: routePathMetadata.controllerVersion,
|
|
104
108
|
},
|
|
105
109
|
};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
routerMethodRef(normalizedPath, routeHandler);
|
|
110
|
+
if (!deferRegistration) {
|
|
111
|
+
this.copyMetadataToCallback(targetCallback, routeHandler);
|
|
112
|
+
const httpAdapter = this.container.getHttpAdapterRef();
|
|
113
|
+
const onRouteTriggered = httpAdapter.getOnRouteTriggered?.();
|
|
114
|
+
if (onRouteTriggered) {
|
|
115
|
+
routerMethodRef(normalizedPath, (...args) => {
|
|
116
|
+
onRouteTriggered(requestMethod, path);
|
|
117
|
+
return routeHandler(...args);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
routerMethodRef(normalizedPath, routeHandler);
|
|
122
|
+
}
|
|
120
123
|
}
|
|
124
|
+
onRouteResolved?.({
|
|
125
|
+
method: requestMethod,
|
|
126
|
+
path: normalizedPath,
|
|
127
|
+
rawPath: path,
|
|
128
|
+
host,
|
|
129
|
+
version: routePathMetadata.methodVersion ??
|
|
130
|
+
routePathMetadata.controllerVersion,
|
|
131
|
+
methodVersion: routePathMetadata.methodVersion,
|
|
132
|
+
controllerVersion: routePathMetadata.controllerVersion,
|
|
133
|
+
handler: routeHandler,
|
|
134
|
+
targetCallback,
|
|
135
|
+
methodName,
|
|
136
|
+
instanceWrapper,
|
|
137
|
+
});
|
|
121
138
|
this.graphInspector.insertEntrypointDefinition(entrypointDefinition, instanceWrapper.id);
|
|
122
139
|
});
|
|
123
140
|
const pathsToLog = this.routePathFactory.create({
|
|
@@ -135,6 +152,31 @@ export class RouterExplorer {
|
|
|
135
152
|
});
|
|
136
153
|
});
|
|
137
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Registers a previously resolved route on the underlying HTTP adapter.
|
|
157
|
+
* Used when route registration has been deferred (e.g. when sorting
|
|
158
|
+
* routes by specificity) so the caller can choose the order in which
|
|
159
|
+
* routes are installed on the adapter.
|
|
160
|
+
*/
|
|
161
|
+
registerResolvedRoute(router, route) {
|
|
162
|
+
const routerMethodRef = this.routerMethodFactory
|
|
163
|
+
.get(router, route.method)
|
|
164
|
+
.bind(router);
|
|
165
|
+
this.copyMetadataToCallback(route.targetCallback, route.handler);
|
|
166
|
+
const normalizedPath = route.path;
|
|
167
|
+
const rawPath = route.rawPath ?? route.path;
|
|
168
|
+
const httpAdapter = this.container.getHttpAdapterRef();
|
|
169
|
+
const onRouteTriggered = httpAdapter.getOnRouteTriggered?.();
|
|
170
|
+
if (onRouteTriggered) {
|
|
171
|
+
routerMethodRef(normalizedPath, (...args) => {
|
|
172
|
+
onRouteTriggered(route.method, rawPath);
|
|
173
|
+
return route.handler(...args);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
routerMethodRef(normalizedPath, route.handler);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
138
180
|
applyHostFilter(host, handler) {
|
|
139
181
|
if (!host) {
|
|
140
182
|
return handler;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HttpStatus, Logger, RequestMethod, } from '@nestjs/common';
|
|
1
|
+
import { HttpStatus, Logger, RequestMethod, SSE_ABORT_CONTROLLER, } from '@nestjs/common';
|
|
2
2
|
import { EMPTY, lastValueFrom, isObservable } from 'rxjs';
|
|
3
3
|
import { catchError, concatMap, map } from 'rxjs/operators';
|
|
4
4
|
import { SseStream, } from './sse-stream.js';
|
|
@@ -49,76 +49,146 @@ export class RouterResponseController {
|
|
|
49
49
|
async sse(result, response, request, options) {
|
|
50
50
|
// It's possible that we sent headers already so don't use a stream
|
|
51
51
|
if (response.writableEnded) {
|
|
52
|
+
// The response is already gone: abort the request-scoped signal so
|
|
53
|
+
// handler cleanup wired to @SseSignal() still runs, and swallow late
|
|
54
|
+
// handler rejections that can no longer be delivered to the client.
|
|
55
|
+
this.getOrCreateAbortController(request).abort();
|
|
56
|
+
Promise.resolve(result).catch((err) => this.logger.error(err));
|
|
52
57
|
return;
|
|
53
58
|
}
|
|
54
|
-
const observableResult = await Promise.resolve(result);
|
|
55
|
-
this.assertObservable(observableResult);
|
|
56
59
|
const stream = new SseStream(request);
|
|
57
60
|
const statusCode = options?.statusCode ??
|
|
58
61
|
response.statusCode ??
|
|
59
62
|
200;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
// Create a per-request AbortController and expose its signal on the request
|
|
64
|
+
// object so async @Sse() handlers can observe client disconnects (via the
|
|
65
|
+
// @SseSignal() parameter decorator) and stop/clean up in-flight setup work.
|
|
66
|
+
// The controller is reused if one was already attached upstream (e.g. when the
|
|
67
|
+
// handler is wrapped by interceptors and the signal was created earlier).
|
|
68
|
+
const abortController = this.getOrCreateAbortController(request);
|
|
64
69
|
return new Promise((resolve, reject) => {
|
|
65
70
|
let settled = false;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
let closeRequested = false;
|
|
72
|
+
let subscription;
|
|
73
|
+
const disconnectSource = request.socket ?? response;
|
|
74
|
+
// Ends the request-scoped lifetime: stops listening for disconnects and
|
|
75
|
+
// aborts the signal handed to the route handler. Every terminal path of
|
|
76
|
+
// the SSE lifecycle (disconnect, completion, error) funnels through here,
|
|
77
|
+
// so a handler that ties its resources to the signal releases them once,
|
|
78
|
+
// regardless of how the stream ended. `abort()` is idempotent, so paths
|
|
79
|
+
// that already aborted on disconnect are unaffected.
|
|
80
|
+
const finalize = () => {
|
|
81
|
+
disconnectSource.removeListener('close', onClose);
|
|
82
|
+
abortController.abort();
|
|
83
|
+
};
|
|
84
|
+
const endStream = () => {
|
|
69
85
|
if (!stream.writableEnded) {
|
|
70
86
|
stream.end();
|
|
71
87
|
}
|
|
88
|
+
};
|
|
89
|
+
const onClose = () => {
|
|
90
|
+
if (settled || closeRequested) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
closeRequested = true;
|
|
94
|
+
if (!subscription) {
|
|
95
|
+
finalize();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
settled = true;
|
|
99
|
+
finalize();
|
|
100
|
+
subscription?.unsubscribe();
|
|
101
|
+
endStream();
|
|
72
102
|
response.end();
|
|
73
103
|
resolve();
|
|
74
104
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
105
|
+
disconnectSource.once('close', onClose);
|
|
106
|
+
Promise.resolve(result)
|
|
107
|
+
.then(observableResult => {
|
|
108
|
+
if (settled) {
|
|
109
|
+
return;
|
|
79
110
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
this.assertObservable(observableResult);
|
|
112
|
+
if (closeRequested) {
|
|
113
|
+
// The client disconnected while the async handler was resolving.
|
|
114
|
+
// Do not subscribe the producer Observable after the consumer has
|
|
115
|
+
// already gone away — subscribing only to abort it in the same tick
|
|
116
|
+
// would start producer side effects just to immediately cancel them.
|
|
117
|
+
settled = true;
|
|
118
|
+
endStream();
|
|
119
|
+
response.end();
|
|
120
|
+
resolve();
|
|
121
|
+
return;
|
|
84
122
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
123
|
+
stream.pipe(response, {
|
|
124
|
+
additionalHeaders: options?.additionalHeaders,
|
|
125
|
+
statusCode,
|
|
126
|
+
});
|
|
127
|
+
subscription = observableResult
|
|
128
|
+
.pipe(map((message) => {
|
|
129
|
+
if (isObject(message)) {
|
|
130
|
+
return message;
|
|
131
|
+
}
|
|
132
|
+
return { data: message };
|
|
133
|
+
}), concatMap(message => new Promise(resolve => stream.writeMessage(message, () => resolve()))), catchError(err => {
|
|
134
|
+
if (!stream.headersCommitted) {
|
|
135
|
+
throw err;
|
|
89
136
|
}
|
|
137
|
+
const data = err instanceof Error ? err.message : err;
|
|
138
|
+
stream.writeMessage({ type: 'error', data }, writeError => {
|
|
139
|
+
if (writeError) {
|
|
140
|
+
this.logger.error(writeError);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return EMPTY;
|
|
144
|
+
}))
|
|
145
|
+
.subscribe({
|
|
146
|
+
error: err => {
|
|
147
|
+
if (settled) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
settled = true;
|
|
151
|
+
finalize();
|
|
152
|
+
endStream();
|
|
153
|
+
reject(err);
|
|
154
|
+
},
|
|
155
|
+
complete: () => {
|
|
156
|
+
if (settled) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
settled = true;
|
|
160
|
+
finalize();
|
|
161
|
+
endStream();
|
|
162
|
+
resolve();
|
|
163
|
+
},
|
|
90
164
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!
|
|
98
|
-
stream.
|
|
165
|
+
// Commit SSE headers on the next macrotask. Pipe validation errors
|
|
166
|
+
// propagate through microtasks (which complete before macrotasks),
|
|
167
|
+
// so if the lifecycle errored, `settled` is already true and we
|
|
168
|
+
// skip the write. Otherwise headers are sent immediately rather
|
|
169
|
+
// than waiting for the first Observable emission.
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
if (!settled) {
|
|
172
|
+
stream.commitHeaders();
|
|
99
173
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
174
|
+
}, 0);
|
|
175
|
+
})
|
|
176
|
+
.catch(err => {
|
|
177
|
+
if (settled) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (closeRequested) {
|
|
103
181
|
settled = true;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
stream.end();
|
|
107
|
-
}
|
|
182
|
+
endStream();
|
|
183
|
+
response.end();
|
|
108
184
|
resolve();
|
|
109
|
-
|
|
110
|
-
});
|
|
111
|
-
// Commit SSE headers on the next macrotask. Pipe validation errors
|
|
112
|
-
// propagate through microtasks (which complete before macrotasks),
|
|
113
|
-
// so if the lifecycle errored, `settled` is already true and we
|
|
114
|
-
// skip the write. Otherwise headers are sent immediately rather
|
|
115
|
-
// than waiting for the first Observable emission.
|
|
116
|
-
setTimeout(() => {
|
|
117
|
-
if (!settled) {
|
|
118
|
-
stream.commitHeaders();
|
|
185
|
+
return;
|
|
119
186
|
}
|
|
120
|
-
|
|
121
|
-
|
|
187
|
+
settled = true;
|
|
188
|
+
finalize();
|
|
189
|
+
endStream();
|
|
190
|
+
reject(err);
|
|
191
|
+
});
|
|
122
192
|
});
|
|
123
193
|
}
|
|
124
194
|
assertObservable(value) {
|
|
@@ -126,4 +196,11 @@ export class RouterResponseController {
|
|
|
126
196
|
throw new ReferenceError('You must return an Observable stream to use Server-Sent Events (SSE).');
|
|
127
197
|
}
|
|
128
198
|
}
|
|
199
|
+
getOrCreateAbortController(request) {
|
|
200
|
+
const carrier = request;
|
|
201
|
+
if (!carrier[SSE_ABORT_CONTROLLER]) {
|
|
202
|
+
carrier[SSE_ABORT_CONTROLLER] = new AbortController();
|
|
203
|
+
}
|
|
204
|
+
return carrier[SSE_ABORT_CONTROLLER];
|
|
205
|
+
}
|
|
129
206
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { type HttpServer } from '@nestjs/common';
|
|
2
|
+
import { type Controller } from '@nestjs/common/internal';
|
|
1
3
|
import { ApplicationConfig } from '../application-config.js';
|
|
2
4
|
import { NestContainer } from '../injector/container.js';
|
|
3
5
|
import { Injector } from '../injector/injector.js';
|
|
4
6
|
import { InstanceWrapper } from '../injector/instance-wrapper.js';
|
|
5
7
|
import { GraphInspector } from '../inspector/graph-inspector.js';
|
|
8
|
+
import { ResolvedRoute } from './interfaces/resolved-route.interface.js';
|
|
6
9
|
import { Resolver } from './interfaces/resolver.interface.js';
|
|
7
|
-
import {
|
|
8
|
-
import { type HttpServer } from '@nestjs/common';
|
|
10
|
+
import { RouteResolutionOptions } from './interfaces/route-resolution-options.interface.js';
|
|
9
11
|
export declare class RoutesResolver implements Resolver {
|
|
10
12
|
private readonly container;
|
|
11
13
|
private readonly applicationConfig;
|
|
@@ -16,8 +18,9 @@ export declare class RoutesResolver implements Resolver {
|
|
|
16
18
|
private readonly routerExceptionsFilter;
|
|
17
19
|
private readonly routerExplorer;
|
|
18
20
|
constructor(container: NestContainer, applicationConfig: ApplicationConfig, injector: Injector, graphInspector: GraphInspector);
|
|
19
|
-
resolve<T extends HttpServer>(applicationRef: T, globalPrefix: string): void;
|
|
20
|
-
|
|
21
|
+
resolve<T extends HttpServer>(applicationRef: T, globalPrefix: string, options?: RouteResolutionOptions): void;
|
|
22
|
+
registerResolvedRoute<T extends HttpServer>(applicationRef: T, route: ResolvedRoute): void;
|
|
23
|
+
registerRouters(routes: Map<string | symbol | Function, InstanceWrapper<Controller>>, moduleName: string, globalPrefix: string, modulePath: string, applicationRef: HttpServer, options?: RouteResolutionOptions): void;
|
|
21
24
|
registerNotFoundHandler(): void;
|
|
22
25
|
registerExceptionHandler(): void;
|
|
23
26
|
private getModulePathMetadata;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { NotFoundException, } from '@nestjs/common';
|
|
1
|
+
import { Logger, NotFoundException, } from '@nestjs/common';
|
|
2
|
+
import { HOST_METADATA, MODULE_PATH, VERSION_METADATA, } from '@nestjs/common/internal';
|
|
2
3
|
import { CONTROLLER_MAPPING_MESSAGE, VERSIONED_CONTROLLER_MAPPING_MESSAGE, } from '../helpers/messages.js';
|
|
3
4
|
import { MetadataScanner } from '../metadata-scanner.js';
|
|
4
5
|
import { RoutePathFactory } from './route-path-factory.js';
|
|
5
6
|
import { RouterExceptionFilters } from './router-exception-filters.js';
|
|
6
7
|
import { RouterExplorer } from './router-explorer.js';
|
|
7
8
|
import { RouterProxy } from './router-proxy.js';
|
|
8
|
-
import { HOST_METADATA, MODULE_PATH, VERSION_METADATA, } from '@nestjs/common/internal';
|
|
9
|
-
import { Logger } from '@nestjs/common';
|
|
10
9
|
export class RoutesResolver {
|
|
11
10
|
container;
|
|
12
11
|
applicationConfig;
|
|
@@ -28,14 +27,17 @@ export class RoutesResolver {
|
|
|
28
27
|
const metadataScanner = new MetadataScanner();
|
|
29
28
|
this.routerExplorer = new RouterExplorer(metadataScanner, this.container, this.injector, this.routerProxy, this.routerExceptionsFilter, this.applicationConfig, this.routePathFactory, graphInspector);
|
|
30
29
|
}
|
|
31
|
-
resolve(applicationRef, globalPrefix) {
|
|
30
|
+
resolve(applicationRef, globalPrefix, options = {}) {
|
|
32
31
|
const modules = this.container.getModules();
|
|
33
32
|
modules.forEach(({ controllers, metatype }, moduleName) => {
|
|
34
33
|
const modulePath = this.getModulePathMetadata(metatype);
|
|
35
|
-
this.registerRouters(controllers, moduleName, globalPrefix, modulePath, applicationRef);
|
|
34
|
+
this.registerRouters(controllers, moduleName, globalPrefix, modulePath, applicationRef, options);
|
|
36
35
|
});
|
|
37
36
|
}
|
|
38
|
-
|
|
37
|
+
registerResolvedRoute(applicationRef, route) {
|
|
38
|
+
this.routerExplorer.registerResolvedRoute(applicationRef, route);
|
|
39
|
+
}
|
|
40
|
+
registerRouters(routes, moduleName, globalPrefix, modulePath, applicationRef, options = {}) {
|
|
39
41
|
routes.forEach(instanceWrapper => {
|
|
40
42
|
const { metatype } = instanceWrapper;
|
|
41
43
|
const host = this.getHostMetadata(metatype);
|
|
@@ -68,7 +70,7 @@ export class RoutesResolver {
|
|
|
68
70
|
controllerVersion,
|
|
69
71
|
versioningOptions,
|
|
70
72
|
};
|
|
71
|
-
this.routerExplorer.explore(instanceWrapper, moduleName, applicationRef, host, routePathMetadata);
|
|
73
|
+
this.routerExplorer.explore(instanceWrapper, moduleName, applicationRef, host, routePathMetadata, options);
|
|
72
74
|
});
|
|
73
75
|
});
|
|
74
76
|
}
|
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
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { Transform } from 'stream';
|
|
2
|
-
import { isObject } from '@nestjs/common/internal';
|
|
2
|
+
import { isNil, isObject, isUndefined } from '@nestjs/common/internal';
|
|
3
|
+
function serializeSseLines(value, prefix) {
|
|
4
|
+
return value
|
|
5
|
+
.split(/\r\n|\r|\n/)
|
|
6
|
+
.map(line => `${prefix}${line}\n`)
|
|
7
|
+
.join('');
|
|
8
|
+
}
|
|
3
9
|
function toDataString(data) {
|
|
4
10
|
if (isObject(data)) {
|
|
5
11
|
return toDataString(JSON.stringify(data));
|
|
6
12
|
}
|
|
7
|
-
return data
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
return serializeSseLines(data, 'data: ');
|
|
14
|
+
}
|
|
15
|
+
function toCommentString(comment) {
|
|
16
|
+
return serializeSseLines(comment, ': ');
|
|
17
|
+
}
|
|
18
|
+
function isCommentOnly(message) {
|
|
19
|
+
return (!isNil(message.comment) &&
|
|
20
|
+
isUndefined(message.data) &&
|
|
21
|
+
isUndefined(message.type) &&
|
|
22
|
+
isUndefined(message.retry));
|
|
11
23
|
}
|
|
12
24
|
/**
|
|
13
25
|
* Adapted from https://raw.githubusercontent.com/EventSource/node-ssestream
|
|
@@ -18,6 +30,7 @@ function toDataString(data) {
|
|
|
18
30
|
* - type
|
|
19
31
|
* - id
|
|
20
32
|
* - retry
|
|
33
|
+
* - comment
|
|
21
34
|
*
|
|
22
35
|
* If constructed with a HTTP Request, it will optimise the socket for streaming.
|
|
23
36
|
* If this stream is piped to an HTTP Response, it will set appropriate headers.
|
|
@@ -64,7 +77,7 @@ export class SseStream extends Transform {
|
|
|
64
77
|
if (this._destination.writeHead) {
|
|
65
78
|
this._destination.writeHead(statusCode, {
|
|
66
79
|
...additionalHeaders,
|
|
67
|
-
// See https://github.com/dunglas/mercure/blob/
|
|
80
|
+
// See https://github.com/dunglas/mercure/blob/main/subscribe.go#L347-L362
|
|
68
81
|
'Content-Type': 'text/event-stream',
|
|
69
82
|
Connection: 'keep-alive',
|
|
70
83
|
// Disable cache, even for old browsers and proxies
|
|
@@ -82,12 +95,10 @@ export class SseStream extends Transform {
|
|
|
82
95
|
this.commitHeaders();
|
|
83
96
|
const sanitize = (val) => String(val).replace(/[\r\n]/g, '');
|
|
84
97
|
let data = message.type ? `event: ${sanitize(message.type)}\n` : '';
|
|
85
|
-
data +=
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
data += message.retry ? `retry: ${sanitize(message.retry)}\n` : '';
|
|
90
|
-
data += message.data ? toDataString(message.data) : '';
|
|
98
|
+
data += !isNil(message.id) ? `id: ${sanitize(message.id)}\n` : '';
|
|
99
|
+
data += !isNil(message.retry) ? `retry: ${sanitize(message.retry)}\n` : '';
|
|
100
|
+
data += !isNil(message.comment) ? toCommentString(message.comment) : '';
|
|
101
|
+
data += !isNil(message.data) ? toDataString(message.data) : '';
|
|
91
102
|
data += '\n';
|
|
92
103
|
this.push(data);
|
|
93
104
|
callback();
|
|
@@ -96,7 +107,7 @@ export class SseStream extends Transform {
|
|
|
96
107
|
* Calls `.write` but handles the drain if needed
|
|
97
108
|
*/
|
|
98
109
|
writeMessage(message, cb) {
|
|
99
|
-
if (message.id
|
|
110
|
+
if (isNil(message.id) && !isCommentOnly(message)) {
|
|
100
111
|
this.lastEventId++;
|
|
101
112
|
message.id = this.lastEventId.toString();
|
|
102
113
|
}
|
package/scanner.js
CHANGED
|
@@ -40,6 +40,9 @@ export 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;
|
|
@@ -64,7 +67,7 @@ export class DependenciesScanner {
|
|
|
64
67
|
throw new UndefinedModuleException(moduleDefinition, index, scope);
|
|
65
68
|
}
|
|
66
69
|
if (!innerModule) {
|
|
67
|
-
throw new InvalidModuleException(moduleDefinition, index, scope);
|
|
70
|
+
throw new InvalidModuleException(moduleDefinition, index, scope, innerModule);
|
|
68
71
|
}
|
|
69
72
|
if (ctxRegistry.includes(innerModule)) {
|
|
70
73
|
continue;
|
|
@@ -81,7 +84,7 @@ export 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);
|
|
@@ -90,10 +93,14 @@ export class DependenciesScanner {
|
|
|
90
93
|
const moduleToAdd = this.isForwardReference(moduleDefinition)
|
|
91
94
|
? moduleDefinition.forwardRef()
|
|
92
95
|
: moduleDefinition;
|
|
93
|
-
if (this.isInjectable(moduleToAdd)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
if (this.isInjectable(moduleToAdd)) {
|
|
97
|
+
throw new InvalidClassModuleException(moduleDefinition, scope, 'provider');
|
|
98
|
+
}
|
|
99
|
+
if (this.isController(moduleToAdd)) {
|
|
100
|
+
throw new InvalidClassModuleException(moduleDefinition, scope, 'controller');
|
|
101
|
+
}
|
|
102
|
+
if (this.isExceptionFilter(moduleToAdd)) {
|
|
103
|
+
throw new InvalidClassModuleException(moduleDefinition, scope, 'filter');
|
|
97
104
|
}
|
|
98
105
|
return this.container.addModule(moduleToAdd, scope);
|
|
99
106
|
}
|