@nestjs/core 7.4.2 → 7.5.1
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 +1 -1
- package/helpers/handler-metadata-storage.d.ts +8 -1
- package/helpers/rethrow.d.ts +1 -0
- package/helpers/rethrow.js +6 -0
- package/index.js +1 -0
- package/injector/container.d.ts +2 -2
- package/injector/container.js +5 -5
- package/injector/module-token-factory.js +5 -1
- package/middleware/middleware-module.js +12 -12
- package/nest-application-context.d.ts +2 -2
- package/nest-factory.d.ts +3 -0
- package/nest-factory.js +20 -2
- package/package.json +4 -4
- package/router/request/index.js +1 -0
- package/router/router-execution-context.d.ts +3 -2
- package/router/router-execution-context.js +10 -1
- package/router/router-response-controller.d.ts +6 -0
- package/router/router-response-controller.js +24 -0
- package/router/sse-stream.d.ts +37 -0
- package/router/sse-stream.js +81 -0
package/Readme.md
CHANGED
|
@@ -89,7 +89,7 @@ Nest is an MIT-licensed open source project. It can grow thanks to the sponsors
|
|
|
89
89
|
<a href="https://sparkfabrik.com/" target="_blank"><img src="https://nestjs.com/img/sparkfabrik-logo.png" width="120" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://www.thebigphonestore.co.uk/" target="_blank"><img src="https://nestjs.com/img/the-big-phone-store-logo.png" width="65" valign="middle" /></a></td>
|
|
90
90
|
<td align="center" valign="middle">
|
|
91
91
|
<a href="https://genuinebee.com/" target="_blank"><img src="https://nestjs.com/img/genuinebee.svg" width="97" valign="middle" /></a> </td></tr>
|
|
92
|
-
<tr><td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td>
|
|
92
|
+
<tr><td align="center" valign="middle"><a href="https://sanyodigital.com/" target="_blank"><img src="https://nestjs.com/img/sanyo-digital.png" width="130" valign="middle" /></a></td><td align="center" valign="middle"><a href="https://vpn-review.com/vpn-for-torrenting" target="_blank"><img src="https://nestjs.com/img/vpn-review-logo.png" width="85" valign="middle" /></a></td>
|
|
93
93
|
</tr></table>
|
|
94
94
|
|
|
95
95
|
## Backers
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import { Type } from '@nestjs/common';
|
|
3
|
+
import { IncomingMessage } from 'http';
|
|
4
|
+
import { Observable } from 'rxjs';
|
|
2
5
|
import { ContextId } from '../injector/instance-wrapper';
|
|
6
|
+
import { HeaderStream } from '../router/sse-stream';
|
|
3
7
|
import { ParamProperties } from './context-utils';
|
|
4
8
|
export declare const HANDLER_METADATA_SYMBOL: unique symbol;
|
|
9
|
+
export declare type HandleResponseFn = HandlerResponseBasicFn | HandleSseResponseFn;
|
|
10
|
+
export declare type HandlerResponseBasicFn = <TResult, TResponse>(result: TResult, res: TResponse, req?: any) => any;
|
|
11
|
+
export declare type HandleSseResponseFn = <TResult extends Observable<unknown> = any, TResponse extends HeaderStream = any, TRequest extends IncomingMessage = any>(result: TResult, res: TResponse, req: TRequest) => any;
|
|
5
12
|
export interface HandlerMetadata {
|
|
6
13
|
argsLength: number;
|
|
7
14
|
paramtypes: any[];
|
|
@@ -11,7 +18,7 @@ export interface HandlerMetadata {
|
|
|
11
18
|
getParamsMetadata: (moduleKey: string, contextId?: ContextId, inquirerId?: string) => (ParamProperties & {
|
|
12
19
|
metatype?: any;
|
|
13
20
|
})[];
|
|
14
|
-
fnHandleResponse:
|
|
21
|
+
fnHandleResponse: HandleResponseFn;
|
|
15
22
|
}
|
|
16
23
|
export declare class HandlerMetadataStorage<TValue = HandlerMetadata, TKey extends Type<unknown> = any> {
|
|
17
24
|
private readonly [HANDLER_METADATA_SYMBOL];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const rethrow: (err: unknown) => never;
|
package/index.js
CHANGED
package/injector/container.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export declare class NestContainer {
|
|
|
20
20
|
setHttpAdapter(httpAdapter: any): void;
|
|
21
21
|
getHttpAdapterRef(): import("..").AbstractHttpAdapter<any, any, any>;
|
|
22
22
|
addModule(metatype: Type<any> | DynamicModule | Promise<DynamicModule>, scope: Type<any>[]): Promise<Module>;
|
|
23
|
-
addDynamicMetadata(token: string, dynamicModuleMetadata: Partial<DynamicModule>, scope: Type<any>[]): void
|
|
24
|
-
addDynamicModules(modules: any[], scope: Type<any>[]): void
|
|
23
|
+
addDynamicMetadata(token: string, dynamicModuleMetadata: Partial<DynamicModule>, scope: Type<any>[]): Promise<void>;
|
|
24
|
+
addDynamicModules(modules: any[], scope: Type<any>[]): Promise<void>;
|
|
25
25
|
isGlobalModule(metatype: Type<any>, dynamicMetadata?: Partial<DynamicModule>): boolean;
|
|
26
26
|
addGlobalModule(module: Module): void;
|
|
27
27
|
getModules(): ModulesContainer;
|
package/injector/container.js
CHANGED
|
@@ -50,25 +50,25 @@ class NestContainer {
|
|
|
50
50
|
}
|
|
51
51
|
const moduleRef = new module_1.Module(type, this);
|
|
52
52
|
this.modules.set(token, moduleRef);
|
|
53
|
-
this.addDynamicMetadata(token, dynamicMetadata, [].concat(scope, type));
|
|
53
|
+
await this.addDynamicMetadata(token, dynamicMetadata, [].concat(scope, type));
|
|
54
54
|
if (this.isGlobalModule(type, dynamicMetadata)) {
|
|
55
55
|
this.addGlobalModule(moduleRef);
|
|
56
56
|
}
|
|
57
57
|
return moduleRef;
|
|
58
58
|
}
|
|
59
|
-
addDynamicMetadata(token, dynamicModuleMetadata, scope) {
|
|
59
|
+
async addDynamicMetadata(token, dynamicModuleMetadata, scope) {
|
|
60
60
|
if (!dynamicModuleMetadata) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
this.dynamicModulesMetadata.set(token, dynamicModuleMetadata);
|
|
64
64
|
const { imports } = dynamicModuleMetadata;
|
|
65
|
-
this.addDynamicModules(imports, scope);
|
|
65
|
+
await this.addDynamicModules(imports, scope);
|
|
66
66
|
}
|
|
67
|
-
addDynamicModules(modules, scope) {
|
|
67
|
+
async addDynamicModules(modules, scope) {
|
|
68
68
|
if (!modules) {
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
modules.
|
|
71
|
+
await Promise.all(modules.map(module => this.addModule(module, scope)));
|
|
72
72
|
}
|
|
73
73
|
isGlobalModule(metatype, dynamicMetadata) {
|
|
74
74
|
if (dynamicMetadata && dynamicMetadata.global) {
|
|
@@ -39,7 +39,11 @@ class ModuleTokenFactory {
|
|
|
39
39
|
}
|
|
40
40
|
replacer(key, value) {
|
|
41
41
|
if (typeof value === 'function') {
|
|
42
|
-
|
|
42
|
+
const isClass = /^class\s/.test(Function.prototype.toString.call(value));
|
|
43
|
+
if (isClass) {
|
|
44
|
+
return value.name;
|
|
45
|
+
}
|
|
46
|
+
return hash(value.toString(), { ignoreUnknown: true });
|
|
43
47
|
}
|
|
44
48
|
return value;
|
|
45
49
|
}
|
|
@@ -53,36 +53,36 @@ class MiddlewareModule {
|
|
|
53
53
|
}
|
|
54
54
|
async registerMiddleware(middlewareContainer, applicationRef) {
|
|
55
55
|
const configs = middlewareContainer.getConfigurations();
|
|
56
|
-
const registerAllConfigs = (moduleKey, middlewareConfig) =>
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
const registerAllConfigs = async (moduleKey, middlewareConfig) => {
|
|
57
|
+
for (const config of middlewareConfig) {
|
|
58
|
+
await this.registerMiddlewareConfig(middlewareContainer, config, moduleKey, applicationRef);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
59
61
|
const entriesSortedByDistance = [...configs.entries()].sort(([moduleA], [moduleB]) => {
|
|
60
62
|
return (this.container.getModuleByKey(moduleA).distance -
|
|
61
63
|
this.container.getModuleByKey(moduleB).distance);
|
|
62
64
|
});
|
|
63
|
-
const
|
|
64
|
-
await
|
|
65
|
-
}
|
|
66
|
-
await Promise.all(entriesSortedByDistance.map(registerModuleConfigs));
|
|
65
|
+
for (const [moduleRef, moduleConfigurations] of entriesSortedByDistance) {
|
|
66
|
+
await registerAllConfigs(moduleRef, [...moduleConfigurations]);
|
|
67
|
+
}
|
|
67
68
|
}
|
|
68
69
|
async registerMiddlewareConfig(middlewareContainer, config, moduleKey, applicationRef) {
|
|
69
70
|
const { forRoutes } = config;
|
|
70
|
-
const
|
|
71
|
+
for (const routeInfo of forRoutes) {
|
|
71
72
|
await this.registerRouteMiddleware(middlewareContainer, routeInfo, config, moduleKey, applicationRef);
|
|
72
|
-
}
|
|
73
|
-
await Promise.all(forRoutes.map(registerRouteMiddleware));
|
|
73
|
+
}
|
|
74
74
|
}
|
|
75
75
|
async registerRouteMiddleware(middlewareContainer, routeInfo, config, moduleKey, applicationRef) {
|
|
76
76
|
const middlewareCollection = [].concat(config.middleware);
|
|
77
77
|
const moduleRef = this.container.getModuleByKey(moduleKey);
|
|
78
|
-
|
|
78
|
+
for (const metatype of middlewareCollection) {
|
|
79
79
|
const collection = middlewareContainer.getMiddlewareCollection(moduleKey);
|
|
80
80
|
const instanceWrapper = collection.get(metatype.name);
|
|
81
81
|
if (shared_utils_1.isUndefined(instanceWrapper)) {
|
|
82
82
|
throw new runtime_exception_1.RuntimeException();
|
|
83
83
|
}
|
|
84
84
|
await this.bindHandler(instanceWrapper, applicationRef, routeInfo.method, routeInfo.path, moduleRef, collection);
|
|
85
|
-
}
|
|
85
|
+
}
|
|
86
86
|
}
|
|
87
87
|
async bindHandler(wrapper, applicationRef, method, path, moduleRef, collection) {
|
|
88
88
|
const { instance, metatype } = wrapper;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INestApplicationContext, LoggerService, ShutdownSignal } from '@nestjs/common';
|
|
1
|
+
import { INestApplicationContext, LoggerService, LogLevel, ShutdownSignal } from '@nestjs/common';
|
|
2
2
|
import { Abstract } from '@nestjs/common/interfaces';
|
|
3
3
|
import { Type } from '@nestjs/common/interfaces/type.interface';
|
|
4
4
|
import { ContextId } from './injector';
|
|
@@ -36,7 +36,7 @@ export declare class NestApplicationContext implements INestApplicationContext {
|
|
|
36
36
|
*/
|
|
37
37
|
init(): Promise<this>;
|
|
38
38
|
close(): Promise<void>;
|
|
39
|
-
useLogger(logger: LoggerService): void;
|
|
39
|
+
useLogger(logger: LoggerService | LogLevel[] | false): void;
|
|
40
40
|
/**
|
|
41
41
|
* Enables the usage of shutdown hooks. Will call the
|
|
42
42
|
* `onApplicationShutdown` function of a provider if the
|
package/nest-factory.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { AbstractHttpAdapter } from './adapters/http-adapter';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class NestFactoryStatic {
|
|
10
10
|
private readonly logger;
|
|
11
|
+
private abortOnError;
|
|
11
12
|
/**
|
|
12
13
|
* Creates an instance of NestApplication.
|
|
13
14
|
*
|
|
@@ -52,12 +53,14 @@ export declare class NestFactoryStatic {
|
|
|
52
53
|
createApplicationContext(module: any, options?: NestApplicationContextOptions): Promise<INestApplicationContext>;
|
|
53
54
|
private createNestInstance;
|
|
54
55
|
private initialize;
|
|
56
|
+
private handleInitializationError;
|
|
55
57
|
private createProxy;
|
|
56
58
|
private createExceptionProxy;
|
|
57
59
|
private createExceptionZone;
|
|
58
60
|
private applyLogger;
|
|
59
61
|
private createHttpAdapter;
|
|
60
62
|
private isHttpServer;
|
|
63
|
+
private setAbortOnError;
|
|
61
64
|
private createAdapterProxy;
|
|
62
65
|
}
|
|
63
66
|
/**
|
package/nest-factory.js
CHANGED
|
@@ -8,6 +8,7 @@ const application_config_1 = require("./application-config");
|
|
|
8
8
|
const constants_1 = require("./constants");
|
|
9
9
|
const exceptions_zone_1 = require("./errors/exceptions-zone");
|
|
10
10
|
const load_adapter_1 = require("./helpers/load-adapter");
|
|
11
|
+
const rethrow_1 = require("./helpers/rethrow");
|
|
11
12
|
const container_1 = require("./injector/container");
|
|
12
13
|
const instance_loader_1 = require("./injector/instance-loader");
|
|
13
14
|
const metadata_scanner_1 = require("./metadata-scanner");
|
|
@@ -20,6 +21,7 @@ const scanner_1 = require("./scanner");
|
|
|
20
21
|
class NestFactoryStatic {
|
|
21
22
|
constructor() {
|
|
22
23
|
this.logger = new logger_service_1.Logger('NestFactory', true);
|
|
24
|
+
this.abortOnError = true;
|
|
23
25
|
}
|
|
24
26
|
async create(module, serverOrOptions, options) {
|
|
25
27
|
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
|
|
@@ -27,6 +29,7 @@ class NestFactoryStatic {
|
|
|
27
29
|
: [this.createHttpAdapter(), serverOrOptions];
|
|
28
30
|
const applicationConfig = new application_config_1.ApplicationConfig();
|
|
29
31
|
const container = new container_1.NestContainer(applicationConfig);
|
|
32
|
+
this.setAbortOnError(serverOrOptions, options);
|
|
30
33
|
this.applyLogger(appOptions);
|
|
31
34
|
await this.initialize(module, container, applicationConfig, httpServer);
|
|
32
35
|
const instance = new nest_application_1.NestApplication(container, httpServer, applicationConfig, appOptions);
|
|
@@ -46,6 +49,7 @@ class NestFactoryStatic {
|
|
|
46
49
|
const { NestMicroservice } = load_package_util_1.loadPackage('@nestjs/microservices', 'NestFactory', () => require('@nestjs/microservices'));
|
|
47
50
|
const applicationConfig = new application_config_1.ApplicationConfig();
|
|
48
51
|
const container = new container_1.NestContainer(applicationConfig);
|
|
52
|
+
this.setAbortOnError(options);
|
|
49
53
|
this.applyLogger(options);
|
|
50
54
|
await this.initialize(module, container, applicationConfig);
|
|
51
55
|
return this.createNestInstance(new NestMicroservice(container, options, applicationConfig));
|
|
@@ -61,6 +65,7 @@ class NestFactoryStatic {
|
|
|
61
65
|
*/
|
|
62
66
|
async createApplicationContext(module, options) {
|
|
63
67
|
const container = new container_1.NestContainer();
|
|
68
|
+
this.setAbortOnError(options);
|
|
64
69
|
this.applyLogger(options);
|
|
65
70
|
await this.initialize(module, container);
|
|
66
71
|
const modules = container.getModules().values();
|
|
@@ -76,6 +81,7 @@ class NestFactoryStatic {
|
|
|
76
81
|
const metadataScanner = new metadata_scanner_1.MetadataScanner();
|
|
77
82
|
const dependenciesScanner = new scanner_1.DependenciesScanner(container, metadataScanner, config);
|
|
78
83
|
container.setHttpAdapter(httpServer);
|
|
84
|
+
const teardown = this.abortOnError === false ? rethrow_1.rethrow : undefined;
|
|
79
85
|
await (httpServer === null || httpServer === void 0 ? void 0 : httpServer.init());
|
|
80
86
|
try {
|
|
81
87
|
this.logger.log(constants_1.MESSAGES.APPLICATION_START);
|
|
@@ -83,11 +89,17 @@ class NestFactoryStatic {
|
|
|
83
89
|
await dependenciesScanner.scan(module);
|
|
84
90
|
await instanceLoader.createInstancesOfDependencies();
|
|
85
91
|
dependenciesScanner.applyApplicationProviders();
|
|
86
|
-
});
|
|
92
|
+
}, teardown);
|
|
87
93
|
}
|
|
88
94
|
catch (e) {
|
|
95
|
+
this.handleInitializationError(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
handleInitializationError(err) {
|
|
99
|
+
if (this.abortOnError) {
|
|
89
100
|
process.abort();
|
|
90
101
|
}
|
|
102
|
+
rethrow_1.rethrow(err);
|
|
91
103
|
}
|
|
92
104
|
createProxy(target) {
|
|
93
105
|
const proxy = this.createExceptionProxy();
|
|
@@ -108,11 +120,12 @@ class NestFactoryStatic {
|
|
|
108
120
|
};
|
|
109
121
|
}
|
|
110
122
|
createExceptionZone(receiver, prop) {
|
|
123
|
+
const teardown = this.abortOnError === false ? rethrow_1.rethrow : undefined;
|
|
111
124
|
return (...args) => {
|
|
112
125
|
let result;
|
|
113
126
|
exceptions_zone_1.ExceptionsZone.run(() => {
|
|
114
127
|
result = receiver[prop](...args);
|
|
115
|
-
});
|
|
128
|
+
}, teardown);
|
|
116
129
|
return result;
|
|
117
130
|
};
|
|
118
131
|
}
|
|
@@ -129,6 +142,11 @@ class NestFactoryStatic {
|
|
|
129
142
|
isHttpServer(serverOrOptions) {
|
|
130
143
|
return !!(serverOrOptions && serverOrOptions.patch);
|
|
131
144
|
}
|
|
145
|
+
setAbortOnError(serverOrOptions, options) {
|
|
146
|
+
this.abortOnError = this.isHttpServer(serverOrOptions)
|
|
147
|
+
? !(options && options.abortOnError === false)
|
|
148
|
+
: !(serverOrOptions && serverOrOptions.abortOnError === false);
|
|
149
|
+
}
|
|
132
150
|
createAdapterProxy(app, adapter) {
|
|
133
151
|
return new Proxy(app, {
|
|
134
152
|
get: (receiver, prop) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.1",
|
|
4
4
|
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
|
|
5
5
|
"author": "Kamil Mysliwiec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"iterare": "1.2.1",
|
|
33
33
|
"object-hash": "2.0.3",
|
|
34
34
|
"path-to-regexp": "3.2.0",
|
|
35
|
-
"tslib": "2.0.
|
|
36
|
-
"uuid": "8.3.
|
|
35
|
+
"tslib": "2.0.3",
|
|
36
|
+
"uuid": "8.3.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@nestjs/common": "7.
|
|
39
|
+
"@nestjs/common": "7.5.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@nestjs/common": "^7.0.0",
|
package/router/request/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REQUEST = void 0;
|
|
3
4
|
var request_constants_1 = require("./request-constants");
|
|
4
5
|
Object.defineProperty(exports, "REQUEST", { enumerable: true, get: function () { return request_constants_1.REQUEST; } });
|
|
@@ -5,7 +5,7 @@ import { ContextType, Controller } from '@nestjs/common/interfaces';
|
|
|
5
5
|
import { GuardsConsumer } from '../guards/guards-consumer';
|
|
6
6
|
import { GuardsContextCreator } from '../guards/guards-context-creator';
|
|
7
7
|
import { ExecutionContextHost } from '../helpers/execution-context-host';
|
|
8
|
-
import { HandlerMetadata } from '../helpers/handler-metadata-storage';
|
|
8
|
+
import { HandleResponseFn, HandlerMetadata } from '../helpers/handler-metadata-storage';
|
|
9
9
|
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
|
|
10
10
|
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
|
|
11
11
|
import { PipesConsumer } from '../pipes/pipes-consumer';
|
|
@@ -38,6 +38,7 @@ export declare class RouterExecutionContext {
|
|
|
38
38
|
reflectHttpStatusCode(callback: (...args: unknown[]) => unknown): number;
|
|
39
39
|
reflectRenderTemplate(callback: (...args: unknown[]) => unknown): string;
|
|
40
40
|
reflectResponseHeaders(callback: (...args: unknown[]) => unknown): CustomHeader[];
|
|
41
|
+
reflectSse(callback: (...args: unknown[]) => unknown): string;
|
|
41
42
|
exchangeKeysForValues(keys: string[], metadata: Record<number, RouteParamMetadata>, moduleContext: string, contextId?: import("..").ContextId, inquirerId?: string, contextFactory?: (args: unknown[]) => ExecutionContextHost): ParamProperties[];
|
|
42
43
|
getParamValue<T>(value: T, { metatype, type, data, }: {
|
|
43
44
|
metatype: unknown;
|
|
@@ -49,5 +50,5 @@ export declare class RouterExecutionContext {
|
|
|
49
50
|
createPipesFn(pipes: PipeTransform[], paramsOptions: (ParamProperties & {
|
|
50
51
|
metatype?: any;
|
|
51
52
|
})[]): <TRequest, TResponse>(args: any[], req: TRequest, res: TResponse, next: Function) => Promise<void>;
|
|
52
|
-
createHandleResponseFn(callback: (...args: unknown[]) => unknown, isResponseHandled: boolean, redirectResponse?: RedirectResponse, httpStatusCode?: number):
|
|
53
|
+
createHandleResponseFn(callback: (...args: unknown[]) => unknown, isResponseHandled: boolean, redirectResponse?: RedirectResponse, httpStatusCode?: number): HandleResponseFn;
|
|
53
54
|
}
|
|
@@ -44,7 +44,7 @@ class RouterExecutionContext {
|
|
|
44
44
|
hasCustomHeaders &&
|
|
45
45
|
this.responseController.setHeaders(res, responseHeaders);
|
|
46
46
|
const result = await this.interceptorsConsumer.intercept(interceptors, [req, res, next], instance, callback, handler(args, req, res, next), contextType);
|
|
47
|
-
await fnHandleResponse(result, res);
|
|
47
|
+
await fnHandleResponse(result, res, req);
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
getMetadata(instance, callback, methodName, moduleKey, requestMethod, contextType) {
|
|
@@ -92,6 +92,9 @@ class RouterExecutionContext {
|
|
|
92
92
|
reflectResponseHeaders(callback) {
|
|
93
93
|
return Reflect.getMetadata(constants_1.HEADERS_METADATA, callback) || [];
|
|
94
94
|
}
|
|
95
|
+
reflectSse(callback) {
|
|
96
|
+
return Reflect.getMetadata(constants_1.SSE_METADATA, callback);
|
|
97
|
+
}
|
|
95
98
|
exchangeKeysForValues(keys, metadata, moduleContext, contextId = constants_3.STATIC_CONTEXT, inquirerId, contextFactory) {
|
|
96
99
|
this.pipesContextCreator.setModuleContext(moduleContext);
|
|
97
100
|
return keys.map(key => {
|
|
@@ -158,6 +161,12 @@ class RouterExecutionContext {
|
|
|
158
161
|
await this.responseController.redirect(result, res, redirectResponse);
|
|
159
162
|
};
|
|
160
163
|
}
|
|
164
|
+
const isSseHandler = !!this.reflectSse(callback);
|
|
165
|
+
if (isSseHandler) {
|
|
166
|
+
return async (result, res, req) => {
|
|
167
|
+
await this.responseController.sse(result, res, req);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
161
170
|
return async (result, res) => {
|
|
162
171
|
result = await this.responseController.transformToResult(result);
|
|
163
172
|
!isResponseHandled &&
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import { HttpServer, RequestMethod } from '@nestjs/common';
|
|
3
|
+
import { IncomingMessage } from 'http';
|
|
4
|
+
import { Observable } from 'rxjs';
|
|
5
|
+
import { HeaderStream } from './sse-stream';
|
|
2
6
|
export interface CustomHeader {
|
|
3
7
|
name: string;
|
|
4
8
|
value: string;
|
|
@@ -17,4 +21,6 @@ export declare class RouterResponseController {
|
|
|
17
21
|
getStatusByMethod(requestMethod: RequestMethod): number;
|
|
18
22
|
setHeaders<TResponse = unknown>(response: TResponse, headers: CustomHeader[]): void;
|
|
19
23
|
setStatus<TResponse = unknown>(response: TResponse, statusCode: number): void;
|
|
24
|
+
sse<TInput extends Observable<unknown> = any, TResponse extends HeaderStream = any, TRequest extends IncomingMessage = any>(result: TInput, response: TResponse, request: TRequest): Promise<void>;
|
|
25
|
+
private assertObservable;
|
|
20
26
|
}
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RouterResponseController = void 0;
|
|
4
4
|
const common_1 = require("@nestjs/common");
|
|
5
5
|
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
|
6
|
+
const operators_1 = require("rxjs/operators");
|
|
7
|
+
const sse_stream_1 = require("./sse-stream");
|
|
6
8
|
class RouterResponseController {
|
|
7
9
|
constructor(applicationRef) {
|
|
8
10
|
this.applicationRef = applicationRef;
|
|
@@ -44,5 +46,27 @@ class RouterResponseController {
|
|
|
44
46
|
setStatus(response, statusCode) {
|
|
45
47
|
this.applicationRef.status(response, statusCode);
|
|
46
48
|
}
|
|
49
|
+
async sse(result, response, request) {
|
|
50
|
+
this.assertObservable(result);
|
|
51
|
+
const stream = new sse_stream_1.SseStream(request);
|
|
52
|
+
stream.pipe(response);
|
|
53
|
+
const subscription = result
|
|
54
|
+
.pipe(operators_1.debounce((message) => new Promise(resolve => {
|
|
55
|
+
if (!shared_utils_1.isObject(message)) {
|
|
56
|
+
message = { data: message };
|
|
57
|
+
}
|
|
58
|
+
stream.writeMessage(message, resolve);
|
|
59
|
+
})))
|
|
60
|
+
.subscribe();
|
|
61
|
+
request.on('close', () => {
|
|
62
|
+
response.end();
|
|
63
|
+
subscription.unsubscribe();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
assertObservable(result) {
|
|
67
|
+
if (!shared_utils_1.isFunction(result.subscribe)) {
|
|
68
|
+
throw new ReferenceError('You must return an Observable stream to use Server-Sent Events (SSE).');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
47
71
|
}
|
|
48
72
|
exports.RouterResponseController = RouterResponseController;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="vinyl-fs" />
|
|
3
|
+
import { MessageEvent } from '@nestjs/common/interfaces';
|
|
4
|
+
import { IncomingMessage, OutgoingHttpHeaders } from 'http';
|
|
5
|
+
import { Transform } from 'stream';
|
|
6
|
+
interface WriteHeaders {
|
|
7
|
+
writeHead?(statusCode: number, reasonPhrase?: string, headers?: OutgoingHttpHeaders): void;
|
|
8
|
+
writeHead?(statusCode: number, headers?: OutgoingHttpHeaders): void;
|
|
9
|
+
flushHeaders?(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare type HeaderStream = NodeJS.WritableStream & WriteHeaders;
|
|
12
|
+
/**
|
|
13
|
+
* Adapted from https://raw.githubusercontent.com/EventSource/node-ssestream
|
|
14
|
+
* Transforms "messages" to W3C event stream content.
|
|
15
|
+
* See https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
16
|
+
* A message is an object with one or more of the following properties:
|
|
17
|
+
* - data (String or object, which gets turned into JSON)
|
|
18
|
+
* - type
|
|
19
|
+
* - id
|
|
20
|
+
* - retry
|
|
21
|
+
*
|
|
22
|
+
* If constructed with a HTTP Request, it will optimise the socket for streaming.
|
|
23
|
+
* If this stream is piped to an HTTP Response, it will set appropriate headers.
|
|
24
|
+
*/
|
|
25
|
+
export declare class SseStream extends Transform {
|
|
26
|
+
private lastEventId;
|
|
27
|
+
constructor(req?: IncomingMessage);
|
|
28
|
+
pipe<T extends HeaderStream>(destination: T, options?: {
|
|
29
|
+
end?: boolean;
|
|
30
|
+
}): T;
|
|
31
|
+
_transform(message: MessageEvent, encoding: string, callback: (error?: Error | null, data?: any) => void): void;
|
|
32
|
+
/**
|
|
33
|
+
* Calls `.write` but handles the drain if needed
|
|
34
|
+
*/
|
|
35
|
+
writeMessage(message: MessageEvent, cb: (error: Error | null | undefined) => void): void;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SseStream = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
function toDataString(data) {
|
|
6
|
+
if (typeof data === 'object') {
|
|
7
|
+
return toDataString(JSON.stringify(data));
|
|
8
|
+
}
|
|
9
|
+
return data
|
|
10
|
+
.split(/\r\n|\r|\n/)
|
|
11
|
+
.map(line => `data: ${line}\n`)
|
|
12
|
+
.join('');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adapted from https://raw.githubusercontent.com/EventSource/node-ssestream
|
|
16
|
+
* Transforms "messages" to W3C event stream content.
|
|
17
|
+
* See https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
18
|
+
* A message is an object with one or more of the following properties:
|
|
19
|
+
* - data (String or object, which gets turned into JSON)
|
|
20
|
+
* - type
|
|
21
|
+
* - id
|
|
22
|
+
* - retry
|
|
23
|
+
*
|
|
24
|
+
* If constructed with a HTTP Request, it will optimise the socket for streaming.
|
|
25
|
+
* If this stream is piped to an HTTP Response, it will set appropriate headers.
|
|
26
|
+
*/
|
|
27
|
+
class SseStream extends stream_1.Transform {
|
|
28
|
+
constructor(req) {
|
|
29
|
+
super({ objectMode: true });
|
|
30
|
+
this.lastEventId = null;
|
|
31
|
+
if (req && req.socket) {
|
|
32
|
+
req.socket.setKeepAlive(true);
|
|
33
|
+
req.socket.setNoDelay(true);
|
|
34
|
+
req.socket.setTimeout(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
pipe(destination, options) {
|
|
38
|
+
if (destination.writeHead) {
|
|
39
|
+
destination.writeHead(200, {
|
|
40
|
+
// See https://github.com/dunglas/mercure/blob/master/hub/subscribe.go#L124-L130
|
|
41
|
+
'Content-Type': 'text/event-stream',
|
|
42
|
+
Connection: 'keep-alive',
|
|
43
|
+
// Disable cache, even for old browsers and proxies
|
|
44
|
+
'Cache-Control': 'private, no-cache, no-store, must-revalidate, max-age=0',
|
|
45
|
+
'Transfer-Encoding': 'identity',
|
|
46
|
+
Pragma: 'no-cache',
|
|
47
|
+
Expire: '0',
|
|
48
|
+
// NGINX support https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-buffering
|
|
49
|
+
'X-Accel-Buffering': 'no',
|
|
50
|
+
});
|
|
51
|
+
destination.flushHeaders();
|
|
52
|
+
}
|
|
53
|
+
destination.write(':\n');
|
|
54
|
+
return super.pipe(destination, options);
|
|
55
|
+
}
|
|
56
|
+
_transform(message, encoding, callback) {
|
|
57
|
+
let data = message.type ? `event: ${message.type}\n` : '';
|
|
58
|
+
data += message.id ? `id: ${message.id}\n` : '';
|
|
59
|
+
data += message.retry ? `retry: ${message.retry}\n` : '';
|
|
60
|
+
data += message.data ? toDataString(message.data) : '';
|
|
61
|
+
data += '\n';
|
|
62
|
+
this.push(data);
|
|
63
|
+
callback();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calls `.write` but handles the drain if needed
|
|
67
|
+
*/
|
|
68
|
+
writeMessage(message, cb) {
|
|
69
|
+
if (!message.id) {
|
|
70
|
+
this.lastEventId++;
|
|
71
|
+
message.id = this.lastEventId.toString();
|
|
72
|
+
}
|
|
73
|
+
if (!this.write(message, 'utf-8', cb)) {
|
|
74
|
+
this.once('drain', cb);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
process.nextTick(cb);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.SseStream = SseStream;
|