@fluojs/platform-nodejs 1.0.6 → 2.0.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.
Files changed (57) hide show
  1. package/README.ko.md +41 -11
  2. package/README.md +41 -11
  3. package/dist/index.d.ts +10 -55
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +10 -55
  6. package/dist/internal.d.ts +3 -0
  7. package/dist/internal.d.ts.map +1 -0
  8. package/dist/internal.js +2 -0
  9. package/dist/node/internal-node-compression.d.ts +22 -0
  10. package/dist/node/internal-node-compression.d.ts.map +1 -0
  11. package/dist/node/internal-node-compression.js +121 -0
  12. package/dist/node/internal-node-early-hints.d.ts +11 -0
  13. package/dist/node/internal-node-early-hints.d.ts.map +1 -0
  14. package/dist/node/internal-node-early-hints.js +99 -0
  15. package/dist/node/internal-node-listen.d.ts +24 -0
  16. package/dist/node/internal-node-listen.d.ts.map +1 -0
  17. package/dist/node/internal-node-listen.js +167 -0
  18. package/dist/node/internal-node-request.d.ts +163 -0
  19. package/dist/node/internal-node-request.d.ts.map +1 -0
  20. package/dist/node/internal-node-request.js +498 -0
  21. package/dist/node/internal-node-response-stream.d.ts +10 -0
  22. package/dist/node/internal-node-response-stream.d.ts.map +1 -0
  23. package/dist/node/internal-node-response-stream.js +69 -0
  24. package/dist/node/internal-node-response.d.ts +28 -0
  25. package/dist/node/internal-node-response.d.ts.map +1 -0
  26. package/dist/node/internal-node-response.js +181 -0
  27. package/dist/node/internal-node-shutdown.d.ts +34 -0
  28. package/dist/node/internal-node-shutdown.d.ts.map +1 -0
  29. package/dist/node/internal-node-shutdown.js +83 -0
  30. package/dist/node/internal-node.d.ts +114 -0
  31. package/dist/node/internal-node.d.ts.map +1 -0
  32. package/dist/node/internal-node.js +262 -0
  33. package/dist/node/json-logger.d.ts +8 -0
  34. package/dist/node/json-logger.d.ts.map +1 -0
  35. package/dist/node/json-logger.js +45 -0
  36. package/dist/node/logger.d.ts +37 -0
  37. package/dist/node/logger.d.ts.map +1 -0
  38. package/dist/node/logger.js +103 -0
  39. package/dist/node/node-compression.d.ts +2 -0
  40. package/dist/node/node-compression.d.ts.map +1 -0
  41. package/dist/node/node-compression.js +1 -0
  42. package/dist/node/node-request.d.ts +2 -0
  43. package/dist/node/node-request.d.ts.map +1 -0
  44. package/dist/node/node-request.js +1 -0
  45. package/dist/node/node-response.d.ts +2 -0
  46. package/dist/node/node-response.d.ts.map +1 -0
  47. package/dist/node/node-response.js +1 -0
  48. package/dist/node/node-shutdown.d.ts +2 -0
  49. package/dist/node/node-shutdown.d.ts.map +1 -0
  50. package/dist/node/node-shutdown.js +1 -0
  51. package/dist/node/node-static-assets.d.ts +26 -0
  52. package/dist/node/node-static-assets.d.ts.map +1 -0
  53. package/dist/node/node-static-assets.js +244 -0
  54. package/dist/node/node.d.ts +2 -0
  55. package/dist/node/node.d.ts.map +1 -0
  56. package/dist/node/node.js +1 -0
  57. package/package.json +17 -6
@@ -0,0 +1,181 @@
1
+ import { createErrorResponse, HttpException, InternalServerErrorException } from '@fluojs/http';
2
+ import { createNodeEarlyHintsCapability } from './internal-node-early-hints.js';
3
+ import { createFrameworkResponseStream } from './internal-node-response-stream.js';
4
+
5
+ /**
6
+ * Defines the mutable framework response type.
7
+ */
8
+
9
+ /**
10
+ * Create framework response.
11
+ *
12
+ * @param response The response.
13
+ * @param compression The compression.
14
+ * @returns The create framework response result.
15
+ */
16
+ export function createFrameworkResponse(response, compression) {
17
+ let activeStream;
18
+ const resolveCompression = (() => {
19
+ const factory = typeof compression === 'function' ? compression : () => compression;
20
+ let resolved = false;
21
+ let value;
22
+ return () => {
23
+ if (!resolved) {
24
+ value = factory();
25
+ resolved = true;
26
+ }
27
+ return value;
28
+ };
29
+ })();
30
+ const mergeSetCookieHeader = (current, incoming) => {
31
+ const nextValues = Array.isArray(incoming) ? incoming : [incoming];
32
+ if (current === undefined) {
33
+ return nextValues.length === 1 ? nextValues[0] : [...nextValues];
34
+ }
35
+ if (typeof current === 'number') {
36
+ return nextValues.length === 1 ? nextValues[0] : [...nextValues];
37
+ }
38
+ const currentValues = Array.isArray(current) ? current : [current];
39
+ const merged = [...currentValues, ...nextValues];
40
+ return merged.length === 1 ? merged[0] : merged;
41
+ };
42
+ let frameworkResponse;
43
+ frameworkResponse = {
44
+ committed: response.headersSent || response.writableEnded,
45
+ earlyHints: createNodeEarlyHintsCapability(response, () => frameworkResponse.committed),
46
+ headers: {},
47
+ raw: response,
48
+ get stream() {
49
+ activeStream ??= createFrameworkResponseStream(response);
50
+ return activeStream;
51
+ },
52
+ redirect(status, location) {
53
+ this.setStatus(status);
54
+ this.setHeader('Location', location);
55
+ void this.send(undefined);
56
+ },
57
+ send(body, options) {
58
+ if (response.writableEnded) {
59
+ this.committed = true;
60
+ return;
61
+ }
62
+ const hasContentType = response.hasHeader('Content-Type');
63
+ const existingContentType = response.getHeader('Content-Type');
64
+ const serialized = serializeResponseBody(body, typeof existingContentType === 'string' ? existingContentType : undefined);
65
+ const adapterDefaultContentType = hasContentType ? undefined : serialized.defaultContentType;
66
+ if (adapterDefaultContentType) {
67
+ response.setHeader('Content-Type', adapterDefaultContentType);
68
+ }
69
+ const contentType = response.getHeader('Content-Type');
70
+ const payload = typeof serialized.payload === 'string' ? Buffer.from(serialized.payload, 'utf8') : serialized.payload;
71
+ if (options?.compression !== false && response.statusCode !== 206 && !response.hasHeader('Content-Range')) {
72
+ const activeCompression = resolveCompression();
73
+ if (activeCompression) {
74
+ this.committed = true;
75
+ return Promise.resolve().then(() => activeCompression.write(payload, {
76
+ contentType
77
+ })).then(handled => {
78
+ if (!handled && !response.writableEnded) {
79
+ response.end(payload);
80
+ }
81
+ }).catch(error => {
82
+ if (response.headersSent || response.writableEnded || response.destroyed) {
83
+ if (!response.writableEnded && !response.destroyed) {
84
+ response.destroy();
85
+ }
86
+ } else {
87
+ response.removeHeader('Content-Encoding');
88
+ if (adapterDefaultContentType) {
89
+ response.removeHeader('Content-Type');
90
+ }
91
+ this.committed = false;
92
+ }
93
+ throw error;
94
+ });
95
+ }
96
+ }
97
+ response.end(payload);
98
+ this.committed = true;
99
+ },
100
+ setHeader(name, value) {
101
+ const headers = this.headers;
102
+ const lowerName = name.toLowerCase();
103
+ if (lowerName === 'set-cookie') {
104
+ const merged = mergeSetCookieHeader(response.getHeader(name), value);
105
+ response.setHeader(name, merged);
106
+ headers[name] = merged;
107
+ return;
108
+ }
109
+ response.setHeader(name, value);
110
+ headers[name] = value;
111
+ },
112
+ setStatus(code) {
113
+ response.statusCode = code;
114
+ this.statusCode = code;
115
+ this.statusSet = true;
116
+ },
117
+ statusCode: undefined,
118
+ statusSet: false
119
+ };
120
+ return frameworkResponse;
121
+ }
122
+
123
+ /**
124
+ * Write node adapter error response.
125
+ *
126
+ * @param error The error.
127
+ * @param response The response.
128
+ * @param requestId The request id.
129
+ * @returns The write node adapter error response result.
130
+ */
131
+ export async function writeNodeAdapterErrorResponse(error, response, requestId) {
132
+ const httpError = toHttpException(error);
133
+ response.setStatus(httpError.status);
134
+ await response.send(createErrorResponse(httpError, requestId));
135
+ }
136
+ function serializeResponseBody(body, contentType) {
137
+ if (body === undefined) {
138
+ return {
139
+ payload: ''
140
+ };
141
+ }
142
+ if (Buffer.isBuffer(body)) {
143
+ return {
144
+ defaultContentType: 'application/octet-stream',
145
+ payload: body
146
+ };
147
+ }
148
+ if (body instanceof Uint8Array) {
149
+ return {
150
+ defaultContentType: 'application/octet-stream',
151
+ payload: Buffer.from(body)
152
+ };
153
+ }
154
+ if (body instanceof ArrayBuffer) {
155
+ return {
156
+ defaultContentType: 'application/octet-stream',
157
+ payload: Buffer.from(body)
158
+ };
159
+ }
160
+ if (typeof body === 'string') {
161
+ return {
162
+ defaultContentType: isJsonContentType(contentType) ? undefined : 'text/plain; charset=utf-8',
163
+ payload: isJsonContentType(contentType) ? JSON.stringify(body) : body
164
+ };
165
+ }
166
+ return {
167
+ defaultContentType: 'application/json; charset=utf-8',
168
+ payload: JSON.stringify(body)
169
+ };
170
+ }
171
+ function isJsonContentType(contentType) {
172
+ return typeof contentType === 'string' && contentType.toLowerCase().includes('application/json');
173
+ }
174
+ function toHttpException(error) {
175
+ if (error instanceof HttpException) {
176
+ return error;
177
+ }
178
+ return new InternalServerErrorException('Internal server error.', {
179
+ cause: error
180
+ });
181
+ }
@@ -0,0 +1,34 @@
1
+ import type { Application, ApplicationLogger } from '@fluojs/runtime';
2
+ import type { HttpAdapterShutdownRegistration } from '@fluojs/runtime/internal/http-adapter';
3
+ type NodeShutdownSignal = 'SIGINT' | 'SIGTERM';
4
+ /**
5
+ * Returns the default POSIX shutdown signals used by Node-hosted runtime helpers.
6
+ *
7
+ * @returns The ordered list of signals that trigger graceful shutdown registration.
8
+ */
9
+ export declare function defaultNodeShutdownSignals(): readonly NodeShutdownSignal[];
10
+ /**
11
+ * Creates shutdown registration logic for Node-hosted adapters.
12
+ *
13
+ * The returned registration preserves graceful shutdown semantics while leaving
14
+ * final process termination ownership to the surrounding host/runtime.
15
+ *
16
+ * @param signals Signals to register, or `false` to disable signal handling.
17
+ * @returns Registration callback consumed by HTTP adapter startup helpers.
18
+ */
19
+ export declare function createNodeShutdownSignalRegistration(signals?: false | readonly NodeShutdownSignal[]): HttpAdapterShutdownRegistration;
20
+ /**
21
+ * Registers process signal handlers that attempt graceful shutdown.
22
+ *
23
+ * When the shutdown timeout elapses, the helper records failure via logging and
24
+ * `process.exitCode` but does not terminate the host process directly.
25
+ *
26
+ * @param app Application instance to close when a signal arrives.
27
+ * @param logger Logger used for shutdown diagnostics.
28
+ * @param signals Signals to bind, or `false` to skip registration.
29
+ * @param forceExitTimeoutMs Timeout window used to mark shutdown as failed.
30
+ * @returns Unregister callback that removes the installed signal handlers.
31
+ */
32
+ export declare function registerShutdownSignals(app: Application, logger: ApplicationLogger, signals: false | readonly NodeShutdownSignal[], forceExitTimeoutMs?: number): () => void;
33
+ export {};
34
+ //# sourceMappingURL=internal-node-shutdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-node-shutdown.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,uCAAuC,CAAC;AAE7F,KAAK,kBAAkB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAI/C;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,SAAS,kBAAkB,EAAE,CAE1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,GAAE,KAAK,GAAG,SAAS,kBAAkB,EAAiC,GAC5E,+BAA+B,CAOjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,KAAK,GAAG,SAAS,kBAAkB,EAAE,EAC9C,kBAAkB,GAAE,MAAsC,GACzD,MAAM,IAAI,CAqBZ"}
@@ -0,0 +1,83 @@
1
+ const DEFAULT_FORCE_EXIT_TIMEOUT_MS = 30_000;
2
+
3
+ /**
4
+ * Returns the default POSIX shutdown signals used by Node-hosted runtime helpers.
5
+ *
6
+ * @returns The ordered list of signals that trigger graceful shutdown registration.
7
+ */
8
+ export function defaultNodeShutdownSignals() {
9
+ return ['SIGINT', 'SIGTERM'];
10
+ }
11
+
12
+ /**
13
+ * Creates shutdown registration logic for Node-hosted adapters.
14
+ *
15
+ * The returned registration preserves graceful shutdown semantics while leaving
16
+ * final process termination ownership to the surrounding host/runtime.
17
+ *
18
+ * @param signals Signals to register, or `false` to disable signal handling.
19
+ * @returns Registration callback consumed by HTTP adapter startup helpers.
20
+ */
21
+ export function createNodeShutdownSignalRegistration(signals = defaultNodeShutdownSignals()) {
22
+ return (app, logger, forceExitTimeoutMs) => registerShutdownSignals(app, logger, signals, forceExitTimeoutMs);
23
+ }
24
+
25
+ /**
26
+ * Registers process signal handlers that attempt graceful shutdown.
27
+ *
28
+ * When the shutdown timeout elapses, the helper records failure via logging and
29
+ * `process.exitCode` but does not terminate the host process directly.
30
+ *
31
+ * @param app Application instance to close when a signal arrives.
32
+ * @param logger Logger used for shutdown diagnostics.
33
+ * @param signals Signals to bind, or `false` to skip registration.
34
+ * @param forceExitTimeoutMs Timeout window used to mark shutdown as failed.
35
+ * @returns Unregister callback that removes the installed signal handlers.
36
+ */
37
+ export function registerShutdownSignals(app, logger, signals, forceExitTimeoutMs = DEFAULT_FORCE_EXIT_TIMEOUT_MS) {
38
+ if (signals === false) {
39
+ return () => {};
40
+ }
41
+ const bindings = [];
42
+ for (const signal of signals) {
43
+ const handler = () => {
44
+ void closeFromSignal(app, logger, signal, forceExitTimeoutMs);
45
+ };
46
+ bindings.push({
47
+ signal,
48
+ handler
49
+ });
50
+ process.once(signal, handler);
51
+ }
52
+ return () => {
53
+ for (const binding of bindings) {
54
+ process.off(binding.signal, binding.handler);
55
+ }
56
+ };
57
+ }
58
+ async function closeFromSignal(app, logger, signal, forceExitTimeoutMs) {
59
+ if (app.state === 'closed') {
60
+ process.exitCode = 0;
61
+ return;
62
+ }
63
+ let timedOut = false;
64
+ const forceExitTimer = setTimeout(() => {
65
+ timedOut = true;
66
+ logger.error(`Shutdown timeout exceeded after ${String(forceExitTimeoutMs)}ms; leaving process termination to the host.`, undefined, 'FluoFactory');
67
+ process.exitCode = 1;
68
+ }, forceExitTimeoutMs);
69
+ if (forceExitTimer.unref) {
70
+ forceExitTimer.unref();
71
+ }
72
+ try {
73
+ await app.close(signal);
74
+ clearTimeout(forceExitTimer);
75
+ if (!timedOut) {
76
+ process.exitCode = 0;
77
+ }
78
+ } catch (error) {
79
+ clearTimeout(forceExitTimer);
80
+ logger.error('Failed to shut down the application cleanly.', error, 'FluoFactory');
81
+ process.exitCode = 1;
82
+ }
83
+ }
@@ -0,0 +1,114 @@
1
+ import { createServer as createHttpServer, type ServerOptions as HttpServerOptions } from 'node:http';
2
+ import { createServer as createHttpsServer, type ServerOptions as HttpsServerOptions } from 'node:https';
3
+ import { type CorsOptions, type Dispatcher, type HttpApplicationAdapter, type MiddlewareLike, type SecurityHeadersOptions } from '@fluojs/http';
4
+ import type { Application, ApplicationLogger, CreateApplicationOptions, ModuleType, MultipartOptions } from '@fluojs/runtime';
5
+ import { compressNodeResponse, createNodeResponseCompression } from './internal-node-compression.js';
6
+ import { cloneHeaderValue, cloneRequestHeaders, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createRequestSignal, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from './internal-node-request.js';
7
+ import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
8
+ /**
9
+ * Describes the node http adapter options contract.
10
+ */
11
+ export interface NodeHttpAdapterOptions {
12
+ host?: string;
13
+ http?: HttpServerOptions;
14
+ https?: HttpsServerOptions;
15
+ maxBodySize?: number;
16
+ port?: number;
17
+ rawBody?: boolean;
18
+ retryDelayMs?: number;
19
+ retryLimit?: number;
20
+ shutdownTimeoutMs?: number;
21
+ }
22
+ /**
23
+ * Defines the node application signal type.
24
+ */
25
+ export type NodeApplicationSignal = 'SIGINT' | 'SIGTERM';
26
+ /**
27
+ * Defines the cors input type.
28
+ */
29
+ export type CorsInput = false | string | string[] | CorsOptions;
30
+ /**
31
+ * Describes the bootstrap node application options contract.
32
+ */
33
+ export interface BootstrapNodeApplicationOptions extends Omit<CreateApplicationOptions, 'adapter' | 'logger' | 'middleware'> {
34
+ compression?: boolean;
35
+ cors?: CorsInput;
36
+ globalPrefix?: string;
37
+ globalPrefixExclude?: readonly string[];
38
+ host?: string;
39
+ http?: HttpServerOptions;
40
+ https?: HttpsServerOptions;
41
+ logger?: ApplicationLogger;
42
+ maxBodySize?: number;
43
+ middleware?: MiddlewareLike[];
44
+ multipart?: MultipartOptions;
45
+ port?: number;
46
+ rawBody?: boolean;
47
+ retryDelayMs?: number;
48
+ retryLimit?: number;
49
+ securityHeaders?: false | SecurityHeadersOptions;
50
+ shutdownTimeoutMs?: number;
51
+ }
52
+ /**
53
+ * Describes the run node application options contract.
54
+ */
55
+ export interface RunNodeApplicationOptions extends BootstrapNodeApplicationOptions {
56
+ forceExitTimeoutMs?: number;
57
+ shutdownSignals?: false | readonly NodeApplicationSignal[];
58
+ }
59
+ interface NodeListenTarget {
60
+ bindTarget: string;
61
+ url: string;
62
+ }
63
+ type NodeServer = ReturnType<typeof createHttpServer> | ReturnType<typeof createHttpsServer>;
64
+ /**
65
+ * Represents the node http application adapter.
66
+ */
67
+ export declare class NodeHttpApplicationAdapter implements HttpApplicationAdapter {
68
+ private readonly port;
69
+ private readonly host;
70
+ private readonly retryDelayMs;
71
+ private readonly retryLimit;
72
+ private readonly httpsOptions;
73
+ private readonly shutdownTimeoutMs;
74
+ private readonly httpOptions?;
75
+ private readonly server;
76
+ private readonly listenLifecycle;
77
+ private dispatcher?;
78
+ private readonly requestResponseFactory;
79
+ private readonly sockets;
80
+ constructor(port: number, host: string | undefined, retryDelayMs: number | undefined, retryLimit: number | undefined, compression: boolean | undefined, httpsOptions: HttpsServerOptions | undefined, multipartOptions?: MultipartOptions, maxBodySize?: number, preserveRawBody?: boolean, shutdownTimeoutMs?: number, httpOptions?: HttpServerOptions | undefined);
81
+ getServer(): NodeServer;
82
+ getRealtimeCapability(): import("@fluojs/http").ServerBackedHttpAdapterRealtimeCapability;
83
+ getListenTarget(): NodeListenTarget;
84
+ listen(dispatcher: Dispatcher): Promise<void>;
85
+ close(): Promise<void>;
86
+ private handleRequest;
87
+ }
88
+ /**
89
+ * Create node http adapter.
90
+ *
91
+ * @param options The options.
92
+ * @param compression The compression.
93
+ * @param multipartOptions The multipart options.
94
+ * @returns The create node http adapter result.
95
+ */
96
+ export declare function createNodeHttpAdapter(options?: NodeHttpAdapterOptions, compression?: boolean, multipartOptions?: MultipartOptions): HttpApplicationAdapter;
97
+ /**
98
+ * Bootstrap node application.
99
+ *
100
+ * @param rootModule The root module.
101
+ * @param options The options.
102
+ * @returns The bootstrap node application result.
103
+ */
104
+ export declare function bootstrapNodeApplication(rootModule: ModuleType, options: BootstrapNodeApplicationOptions): Promise<Application>;
105
+ /**
106
+ * Run node application.
107
+ *
108
+ * @param rootModule The root module.
109
+ * @param options The options.
110
+ * @returns The run node application result.
111
+ */
112
+ export declare function runNodeApplication(rootModule: ModuleType, options: RunNodeApplicationOptions): Promise<Application>;
113
+ export { cloneHeaderValue, cloneRequestHeaders, compressNodeResponse, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createNodeResponseCompression, createNodeShutdownSignalRegistration, createRequestSignal, defaultNodeShutdownSignals, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, registerShutdownSignals, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl, };
114
+ //# sourceMappingURL=internal-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-node.d.ts","sourceRoot":"","sources":["../../src/node/internal-node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,262 @@
1
+ import { createServer as createHttpServer } from 'node:http';
2
+ import { createServer as createHttpsServer } from 'node:https';
3
+ import { createServerBackedHttpAdapterRealtimeCapability } from '@fluojs/http';
4
+ import { bootstrapHttpAdapterApplication, runHttpAdapterApplication } from '@fluojs/runtime/internal/http-adapter';
5
+ import { dispatchWithRequestResponseFactory } from '@fluojs/runtime/internal/request-response-factory';
6
+ import { compressNodeResponse, createNodeResponseCompression } from './internal-node-compression.js';
7
+ import { NodeListenLifecycle } from './internal-node-listen.js';
8
+ import { cloneHeaderValue, cloneRequestHeaders, createDeferredFrameworkRequest, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createRequestSignal, materializeFrameworkRequestBody, NodeRequestPayloadTooLargeException, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from './internal-node-request.js';
9
+ import { createFrameworkResponse, writeNodeAdapterErrorResponse } from './internal-node-response.js';
10
+ import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
11
+ import { createConsoleApplicationLogger } from './logger.js';
12
+
13
+ /**
14
+ * Describes the node http adapter options contract.
15
+ */
16
+
17
+ /**
18
+ * Defines the node application signal type.
19
+ */
20
+
21
+ /**
22
+ * Defines the cors input type.
23
+ */
24
+
25
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
26
+
27
+ /**
28
+ * Describes the bootstrap node application options contract.
29
+ */
30
+
31
+ /**
32
+ * Describes the run node application options contract.
33
+ */
34
+
35
+ /**
36
+ * Represents the node http application adapter.
37
+ */
38
+ export class NodeHttpApplicationAdapter {
39
+ server;
40
+ listenLifecycle;
41
+ dispatcher;
42
+ requestResponseFactory;
43
+ sockets = new Set();
44
+ constructor(port, host, retryDelayMs = 150, retryLimit = 20, compression = false, httpsOptions, multipartOptions, maxBodySize = 1 * 1024 * 1024, preserveRawBody = false, shutdownTimeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS, httpOptions) {
45
+ this.port = port;
46
+ this.host = host;
47
+ this.retryDelayMs = retryDelayMs;
48
+ this.retryLimit = retryLimit;
49
+ this.httpsOptions = httpsOptions;
50
+ this.shutdownTimeoutMs = shutdownTimeoutMs;
51
+ this.httpOptions = httpOptions;
52
+ validateNodeLifecycleOptions({
53
+ retryDelayMs: this.retryDelayMs,
54
+ retryLimit: this.retryLimit,
55
+ shutdownTimeoutMs: this.shutdownTimeoutMs
56
+ });
57
+ this.requestResponseFactory = createNodeRequestResponseFactory(compression, multipartOptions, maxBodySize, preserveRawBody);
58
+ this.server = createNodeServer(this.httpOptions, this.httpsOptions, (request, response) => {
59
+ void this.handleRequest(request, response);
60
+ });
61
+ this.listenLifecycle = new NodeListenLifecycle(this.server, {
62
+ host: this.host,
63
+ port: this.port,
64
+ retryDelayMs: this.retryDelayMs,
65
+ retryLimit: this.retryLimit
66
+ });
67
+ this.server.on('connection', socket => {
68
+ this.sockets.add(socket);
69
+ socket.once('close', () => {
70
+ this.sockets.delete(socket);
71
+ });
72
+ });
73
+ }
74
+ getServer() {
75
+ return this.server;
76
+ }
77
+ getRealtimeCapability() {
78
+ return createServerBackedHttpAdapterRealtimeCapability(this.server);
79
+ }
80
+ getListenTarget() {
81
+ return resolveNodeListenTarget(this.server.address() ?? null, this.port, this.host, this.httpsOptions !== undefined);
82
+ }
83
+ async listen(dispatcher) {
84
+ await this.listenLifecycle.listen(() => {
85
+ this.dispatcher = dispatcher;
86
+ });
87
+ }
88
+ async close() {
89
+ const server = this.server;
90
+ await this.listenLifecycle.close(async () => {
91
+ if (server.listening) {
92
+ await closeNodeServerWithDrain(server, this.sockets, this.shutdownTimeoutMs);
93
+ }
94
+ });
95
+ this.dispatcher = undefined;
96
+ }
97
+ async handleRequest(request, response) {
98
+ await dispatchWithRequestResponseFactory({
99
+ dispatcher: this.dispatcher,
100
+ dispatcherNotReadyMessage: 'Node HTTP adapter received a request before dispatcher binding completed.',
101
+ factory: this.requestResponseFactory,
102
+ rawRequest: request,
103
+ rawResponse: response
104
+ });
105
+ }
106
+ }
107
+ function createNodeRequestResponseFactory(compression, multipartOptions, maxBodySize, preserveRawBody) {
108
+ return {
109
+ async createRequest(request, signal) {
110
+ return createDeferredFrameworkRequest(request, signal, multipartOptions, maxBodySize, preserveRawBody);
111
+ },
112
+ materializeRequest(request) {
113
+ return materializeFrameworkRequestBody(request);
114
+ },
115
+ createRequestSignal(response) {
116
+ return createRequestSignal(response);
117
+ },
118
+ createResponse(response, request) {
119
+ return createFrameworkResponse(response, compression ? () => createNodeResponseCompression(response, request.headers['accept-encoding']) : undefined);
120
+ },
121
+ resolveRequestId(request) {
122
+ return resolveRequestIdFromHeaders(request.headers);
123
+ },
124
+ async writeErrorResponse(error, response, requestId) {
125
+ if (error instanceof NodeRequestPayloadTooLargeException) {
126
+ error.prepareResponse(response.raw);
127
+ }
128
+ await writeNodeAdapterErrorResponse(error, response, requestId);
129
+ }
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Create node http adapter.
135
+ *
136
+ * @param options The options.
137
+ * @param compression The compression.
138
+ * @param multipartOptions The multipart options.
139
+ * @returns The create node http adapter result.
140
+ */
141
+ export function createNodeHttpAdapter(options = {}, compression = false, multipartOptions) {
142
+ return new NodeHttpApplicationAdapter(resolveNodePort(options.port), options.host, options.retryDelayMs, options.retryLimit, compression, options.https, multipartOptions, resolveNodeMaxBodySize(options.maxBodySize), options.rawBody, options.shutdownTimeoutMs, options.http);
143
+ }
144
+
145
+ /**
146
+ * Bootstrap node application.
147
+ *
148
+ * @param rootModule The root module.
149
+ * @param options The options.
150
+ * @returns The bootstrap node application result.
151
+ */
152
+ export async function bootstrapNodeApplication(rootModule, options) {
153
+ const logger = options.logger ?? createConsoleApplicationLogger();
154
+ return bootstrapHttpAdapterApplication(rootModule, options, createNodeHttpAdapter(options, options.compression ?? false, options.multipart), logger);
155
+ }
156
+
157
+ /**
158
+ * Run node application.
159
+ *
160
+ * @param rootModule The root module.
161
+ * @param options The options.
162
+ * @returns The run node application result.
163
+ */
164
+ export async function runNodeApplication(rootModule, options) {
165
+ const logger = options.logger ?? createConsoleApplicationLogger();
166
+ const adapter = createNodeHttpAdapter(options, options.compression ?? false, options.multipart);
167
+ return runHttpAdapterApplication(rootModule, {
168
+ ...options,
169
+ shutdownRegistration: createNodeShutdownSignalRegistration(options.shutdownSignals ?? defaultNodeShutdownSignals())
170
+ }, adapter, logger);
171
+ }
172
+ export { cloneHeaderValue, cloneRequestHeaders, compressNodeResponse, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createNodeResponseCompression, createNodeShutdownSignalRegistration, createRequestSignal, defaultNodeShutdownSignals, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, registerShutdownSignals, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl };
173
+ function createNodeServer(httpOptions, httpsOptions, handler) {
174
+ if (httpOptions && httpsOptions) {
175
+ throw new Error('Plain HTTP and HTTPS server options cannot be used together.');
176
+ }
177
+ if (httpOptions) {
178
+ return createHttpServer(httpOptions, handler);
179
+ }
180
+ return httpsOptions ? createHttpsServer(httpsOptions, handler) : createHttpServer(handler);
181
+ }
182
+ function closeNodeServerWithDrain(server, sockets, shutdownTimeoutMs) {
183
+ return new Promise((resolve, reject) => {
184
+ let settled = false;
185
+ const timeout = setTimeout(() => {
186
+ forceCloseConnections(server, sockets);
187
+ }, shutdownTimeoutMs);
188
+ const finish = error => {
189
+ if (settled) {
190
+ return;
191
+ }
192
+ settled = true;
193
+ clearTimeout(timeout);
194
+ if (error) {
195
+ reject(error);
196
+ return;
197
+ }
198
+ resolve();
199
+ };
200
+ server.close(error => {
201
+ finish(error);
202
+ });
203
+ closeIdleConnections(server);
204
+ });
205
+ }
206
+ function resolveNodeListenTarget(address, port, host, useHttps) {
207
+ const protocol = useHttps ? 'https' : 'http';
208
+ const resolvedPort = typeof address === 'object' && address !== null ? address.port : port;
209
+ const bindHost = typeof address === 'object' && address !== null ? address.address : host ?? '0.0.0.0';
210
+ const publicHost = resolvePublicHost(host ?? bindHost);
211
+ const bindTarget = `${formatHostForAuthority(bindHost)}:${String(resolvedPort)}`;
212
+ const url = `${protocol}://${formatHostForAuthority(publicHost)}:${String(resolvedPort)}`;
213
+ return {
214
+ bindTarget,
215
+ url
216
+ };
217
+ }
218
+ function resolvePublicHost(host) {
219
+ return isWildcardHost(host) ? 'localhost' : host;
220
+ }
221
+ function isWildcardHost(host) {
222
+ return host === '0.0.0.0' || host === '::' || host === '[::]';
223
+ }
224
+ function formatHostForAuthority(host) {
225
+ return host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
226
+ }
227
+ function closeIdleConnections(server) {
228
+ server.closeIdleConnections?.();
229
+ }
230
+ function validateNodeLifecycleOptions(options) {
231
+ validateNonNegativeIntegerOption('retryDelayMs', options.retryDelayMs);
232
+ validateNonNegativeIntegerOption('retryLimit', options.retryLimit);
233
+ validateNonNegativeIntegerOption('shutdownTimeoutMs', options.shutdownTimeoutMs);
234
+ }
235
+ function validateNonNegativeIntegerOption(name, value) {
236
+ if (!Number.isInteger(value) || value < 0) {
237
+ throw new Error(`Invalid ${name} value: ${String(value)}. Expected a non-negative integer.`);
238
+ }
239
+ }
240
+ function forceCloseConnections(server, sockets) {
241
+ if (typeof server.closeAllConnections === 'function') {
242
+ server.closeAllConnections();
243
+ return;
244
+ }
245
+ for (const socket of sockets) {
246
+ socket.destroy();
247
+ }
248
+ }
249
+ function resolveNodePort(value) {
250
+ const port = value ?? 3000;
251
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
252
+ throw new Error(`Invalid PORT value: ${String(value ?? 3000)}.`);
253
+ }
254
+ return port;
255
+ }
256
+ function resolveNodeMaxBodySize(value) {
257
+ const maxBodySize = value ?? 1 * 1024 * 1024;
258
+ if (!Number.isInteger(maxBodySize) || maxBodySize < 0) {
259
+ throw new Error(`Invalid maxBodySize value: ${String(value ?? 1 * 1024 * 1024)}. Expected a non-negative integer number of bytes.`);
260
+ }
261
+ return maxBodySize;
262
+ }