@fluojs/runtime 1.0.0-beta.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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +182 -0
  3. package/README.md +182 -0
  4. package/dist/abort.d.ts +19 -0
  5. package/dist/abort.d.ts.map +1 -0
  6. package/dist/abort.js +39 -0
  7. package/dist/adapters/internal-http-adapter.d.ts +2 -0
  8. package/dist/adapters/internal-http-adapter.d.ts.map +1 -0
  9. package/dist/adapters/internal-http-adapter.js +1 -0
  10. package/dist/adapters/internal-request-response-factory.d.ts +2 -0
  11. package/dist/adapters/internal-request-response-factory.d.ts.map +1 -0
  12. package/dist/adapters/internal-request-response-factory.js +1 -0
  13. package/dist/adapters/request-response-factory.d.ts +17 -0
  14. package/dist/adapters/request-response-factory.d.ts.map +1 -0
  15. package/dist/adapters/request-response-factory.js +27 -0
  16. package/dist/bootstrap.d.ts +73 -0
  17. package/dist/bootstrap.d.ts.map +1 -0
  18. package/dist/bootstrap.js +870 -0
  19. package/dist/errors.d.ts +39 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +88 -0
  22. package/dist/health/diagnostics.d.ts +56 -0
  23. package/dist/health/diagnostics.d.ts.map +1 -0
  24. package/dist/health/diagnostics.js +155 -0
  25. package/dist/health/health.d.ts +18 -0
  26. package/dist/health/health.d.ts.map +1 -0
  27. package/dist/health/health.js +82 -0
  28. package/dist/http-adapter-shared.d.ts +88 -0
  29. package/dist/http-adapter-shared.d.ts.map +1 -0
  30. package/dist/http-adapter-shared.js +199 -0
  31. package/dist/index.d.ts +11 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +8 -0
  34. package/dist/internal-http-adapter.d.ts +2 -0
  35. package/dist/internal-http-adapter.d.ts.map +1 -0
  36. package/dist/internal-http-adapter.js +1 -0
  37. package/dist/internal-node.d.ts +2 -0
  38. package/dist/internal-node.d.ts.map +1 -0
  39. package/dist/internal-node.js +1 -0
  40. package/dist/internal-request-response-factory.d.ts +2 -0
  41. package/dist/internal-request-response-factory.d.ts.map +1 -0
  42. package/dist/internal-request-response-factory.js +1 -0
  43. package/dist/internal.d.ts +2 -0
  44. package/dist/internal.d.ts.map +1 -0
  45. package/dist/internal.js +1 -0
  46. package/dist/logging/json-logger.d.ts +3 -0
  47. package/dist/logging/json-logger.d.ts.map +1 -0
  48. package/dist/logging/json-logger.js +39 -0
  49. package/dist/logging/logger.d.ts +3 -0
  50. package/dist/logging/logger.d.ts.map +1 -0
  51. package/dist/logging/logger.js +36 -0
  52. package/dist/module-graph.d.ts +26 -0
  53. package/dist/module-graph.d.ts.map +1 -0
  54. package/dist/module-graph.js +248 -0
  55. package/dist/multipart.d.ts +45 -0
  56. package/dist/multipart.d.ts.map +1 -0
  57. package/dist/multipart.js +195 -0
  58. package/dist/node/internal-node-compression.d.ts +7 -0
  59. package/dist/node/internal-node-compression.d.ts.map +1 -0
  60. package/dist/node/internal-node-compression.js +68 -0
  61. package/dist/node/internal-node-request.d.ts +34 -0
  62. package/dist/node/internal-node-request.d.ts.map +1 -0
  63. package/dist/node/internal-node-request.js +195 -0
  64. package/dist/node/internal-node-response.d.ts +8 -0
  65. package/dist/node/internal-node-response.d.ts.map +1 -0
  66. package/dist/node/internal-node-response.js +166 -0
  67. package/dist/node/internal-node-shutdown.d.ts +34 -0
  68. package/dist/node/internal-node-shutdown.d.ts.map +1 -0
  69. package/dist/node/internal-node-shutdown.js +83 -0
  70. package/dist/node/internal-node.d.ts +80 -0
  71. package/dist/node/internal-node.d.ts.map +1 -0
  72. package/dist/node/internal-node.js +209 -0
  73. package/dist/node/node-compression.d.ts +2 -0
  74. package/dist/node/node-compression.d.ts.map +1 -0
  75. package/dist/node/node-compression.js +1 -0
  76. package/dist/node/node-request.d.ts +2 -0
  77. package/dist/node/node-request.d.ts.map +1 -0
  78. package/dist/node/node-request.js +1 -0
  79. package/dist/node/node-response.d.ts +2 -0
  80. package/dist/node/node-response.d.ts.map +1 -0
  81. package/dist/node/node-response.js +1 -0
  82. package/dist/node/node-shutdown.d.ts +2 -0
  83. package/dist/node/node-shutdown.d.ts.map +1 -0
  84. package/dist/node/node-shutdown.js +1 -0
  85. package/dist/node/node.d.ts +2 -0
  86. package/dist/node/node.d.ts.map +1 -0
  87. package/dist/node/node.js +1 -0
  88. package/dist/node.d.ts +5 -0
  89. package/dist/node.d.ts.map +1 -0
  90. package/dist/node.js +3 -0
  91. package/dist/platform-contract.d.ts +140 -0
  92. package/dist/platform-contract.d.ts.map +1 -0
  93. package/dist/platform-contract.js +1 -0
  94. package/dist/platform-shell.d.ts +45 -0
  95. package/dist/platform-shell.d.ts.map +1 -0
  96. package/dist/platform-shell.js +368 -0
  97. package/dist/request-transaction.d.ts +17 -0
  98. package/dist/request-transaction.d.ts.map +1 -0
  99. package/dist/request-transaction.js +39 -0
  100. package/dist/tokens.d.ts +27 -0
  101. package/dist/tokens.d.ts.map +1 -0
  102. package/dist/tokens.js +24 -0
  103. package/dist/types.d.ts +161 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +1 -0
  106. package/dist/web.d.ts +58 -0
  107. package/dist/web.d.ts.map +1 -0
  108. package/dist/web.js +431 -0
  109. package/package.json +86 -0
@@ -0,0 +1,166 @@
1
+ import { createErrorResponse, HttpException, InternalServerErrorException } from '@fluojs/http';
2
+ function createFrameworkResponseStream(response) {
3
+ return {
4
+ close() {
5
+ if (!response.writableEnded) {
6
+ response.end();
7
+ }
8
+ },
9
+ get closed() {
10
+ return response.writableEnded;
11
+ },
12
+ flush() {
13
+ response.flushHeaders?.();
14
+ },
15
+ onClose(listener) {
16
+ response.on('close', listener);
17
+ return () => {
18
+ response.removeListener('close', listener);
19
+ };
20
+ },
21
+ waitForDrain() {
22
+ if (response.writableEnded || response.destroyed) {
23
+ return Promise.resolve();
24
+ }
25
+ return new Promise(resolve => {
26
+ const settle = () => {
27
+ response.removeListener('drain', settle);
28
+ response.removeListener('close', settle);
29
+ response.removeListener('error', settle);
30
+ resolve();
31
+ };
32
+ response.once('drain', settle);
33
+ response.once('close', settle);
34
+ response.once('error', settle);
35
+ });
36
+ },
37
+ write(chunk) {
38
+ return response.write(chunk);
39
+ }
40
+ };
41
+ }
42
+ export function createFrameworkResponse(response, compression) {
43
+ const mergeSetCookieHeader = (current, incoming) => {
44
+ const nextValues = Array.isArray(incoming) ? incoming : [incoming];
45
+ if (current === undefined) {
46
+ return nextValues.length === 1 ? nextValues[0] : [...nextValues];
47
+ }
48
+ if (typeof current === 'number') {
49
+ return nextValues.length === 1 ? nextValues[0] : [...nextValues];
50
+ }
51
+ const currentValues = Array.isArray(current) ? current : [current];
52
+ const merged = [...currentValues, ...nextValues];
53
+ return merged.length === 1 ? merged[0] : merged;
54
+ };
55
+ const frameworkResponse = {
56
+ committed: response.headersSent || response.writableEnded,
57
+ headers: {},
58
+ raw: response,
59
+ stream: createFrameworkResponseStream(response),
60
+ redirect(status, location) {
61
+ this.setStatus(status);
62
+ this.setHeader('Location', location);
63
+ void this.send(undefined);
64
+ },
65
+ send(body) {
66
+ if (response.writableEnded) {
67
+ this.committed = true;
68
+ return;
69
+ }
70
+ const existingContentType = response.getHeader('Content-Type');
71
+ const serialized = serializeResponseBody(body, typeof existingContentType === 'string' ? existingContentType : undefined);
72
+ if (!response.hasHeader('Content-Type') && serialized.defaultContentType) {
73
+ response.setHeader('Content-Type', serialized.defaultContentType);
74
+ }
75
+ const contentType = response.getHeader('Content-Type');
76
+ const payload = typeof serialized.payload === 'string' ? Buffer.from(serialized.payload, 'utf8') : serialized.payload;
77
+ if (compression) {
78
+ this.committed = true;
79
+ return Promise.resolve(compression.write(payload, {
80
+ contentType
81
+ })).then(handled => {
82
+ if (!handled && !response.writableEnded) {
83
+ response.end(payload);
84
+ }
85
+ }).catch(() => {
86
+ if (!response.writableEnded) {
87
+ response.end();
88
+ }
89
+ });
90
+ }
91
+ response.end(payload);
92
+ this.committed = true;
93
+ },
94
+ setHeader(name, value) {
95
+ const headers = this.headers;
96
+ const lowerName = name.toLowerCase();
97
+ if (lowerName === 'set-cookie') {
98
+ const merged = mergeSetCookieHeader(response.getHeader(name), value);
99
+ response.setHeader(name, merged);
100
+ headers[name] = merged;
101
+ return;
102
+ }
103
+ response.setHeader(name, value);
104
+ headers[name] = value;
105
+ },
106
+ setStatus(code) {
107
+ response.statusCode = code;
108
+ this.statusCode = code;
109
+ this.statusSet = true;
110
+ },
111
+ statusCode: undefined,
112
+ statusSet: false
113
+ };
114
+ return frameworkResponse;
115
+ }
116
+ export async function writeNodeAdapterErrorResponse(error, response, requestId) {
117
+ const httpError = toHttpException(error);
118
+ response.setStatus(httpError.status);
119
+ await response.send(createErrorResponse(httpError, requestId));
120
+ }
121
+ function serializeResponseBody(body, contentType) {
122
+ if (body === undefined) {
123
+ return {
124
+ payload: ''
125
+ };
126
+ }
127
+ if (Buffer.isBuffer(body)) {
128
+ return {
129
+ defaultContentType: 'application/octet-stream',
130
+ payload: body
131
+ };
132
+ }
133
+ if (body instanceof Uint8Array) {
134
+ return {
135
+ defaultContentType: 'application/octet-stream',
136
+ payload: Buffer.from(body)
137
+ };
138
+ }
139
+ if (body instanceof ArrayBuffer) {
140
+ return {
141
+ defaultContentType: 'application/octet-stream',
142
+ payload: Buffer.from(body)
143
+ };
144
+ }
145
+ if (typeof body === 'string') {
146
+ return {
147
+ defaultContentType: isJsonContentType(contentType) ? undefined : 'text/plain; charset=utf-8',
148
+ payload: isJsonContentType(contentType) ? JSON.stringify(body) : body
149
+ };
150
+ }
151
+ return {
152
+ defaultContentType: 'application/json; charset=utf-8',
153
+ payload: JSON.stringify(body)
154
+ };
155
+ }
156
+ function isJsonContentType(contentType) {
157
+ return typeof contentType === 'string' && contentType.toLowerCase().includes('application/json');
158
+ }
159
+ function toHttpException(error) {
160
+ if (error instanceof HttpException) {
161
+ return error;
162
+ }
163
+ return new InternalServerErrorException('Internal server error.', {
164
+ cause: error
165
+ });
166
+ }
@@ -0,0 +1,34 @@
1
+ import type { Application, ApplicationLogger } from '../types.js';
2
+ import type { HttpAdapterShutdownRegistration } from '../http-adapter-shared.js';
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,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAC;AAEjF,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,80 @@
1
+ import { createServer as createHttpServer } 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 { createNodeResponseCompression, compressNodeResponse } from './internal-node-compression.js';
5
+ import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
6
+ import type { MultipartOptions, UploadedFile } from '../multipart.js';
7
+ import type { Application, ApplicationLogger, CreateApplicationOptions, ModuleType } from '../types.js';
8
+ declare module '@fluojs/http' {
9
+ interface FrameworkRequest {
10
+ files?: UploadedFile[];
11
+ rawBody?: Uint8Array;
12
+ }
13
+ }
14
+ export interface NodeHttpAdapterOptions {
15
+ host?: string;
16
+ https?: HttpsServerOptions;
17
+ maxBodySize?: number;
18
+ port?: number;
19
+ rawBody?: boolean;
20
+ retryDelayMs?: number;
21
+ retryLimit?: number;
22
+ shutdownTimeoutMs?: number;
23
+ }
24
+ export type NodeApplicationSignal = 'SIGINT' | 'SIGTERM';
25
+ export type CorsInput = false | string | string[] | CorsOptions;
26
+ export interface BootstrapNodeApplicationOptions extends Omit<CreateApplicationOptions, 'adapter' | 'logger' | 'middleware'> {
27
+ compression?: boolean;
28
+ cors?: CorsInput;
29
+ globalPrefix?: string;
30
+ globalPrefixExclude?: readonly string[];
31
+ host?: string;
32
+ https?: HttpsServerOptions;
33
+ logger?: ApplicationLogger;
34
+ maxBodySize?: number;
35
+ middleware?: MiddlewareLike[];
36
+ multipart?: MultipartOptions;
37
+ port?: number;
38
+ rawBody?: boolean;
39
+ retryDelayMs?: number;
40
+ retryLimit?: number;
41
+ securityHeaders?: false | SecurityHeadersOptions;
42
+ shutdownTimeoutMs?: number;
43
+ }
44
+ export interface RunNodeApplicationOptions extends BootstrapNodeApplicationOptions {
45
+ forceExitTimeoutMs?: number;
46
+ shutdownSignals?: false | readonly NodeApplicationSignal[];
47
+ }
48
+ interface NodeListenTarget {
49
+ bindTarget: string;
50
+ url: string;
51
+ }
52
+ type NodeServer = ReturnType<typeof createHttpServer> | ReturnType<typeof createHttpsServer>;
53
+ export declare class NodeHttpApplicationAdapter implements HttpApplicationAdapter {
54
+ private readonly port;
55
+ private readonly host;
56
+ private readonly retryDelayMs;
57
+ private readonly retryLimit;
58
+ private readonly compression;
59
+ private readonly httpsOptions;
60
+ private readonly multipartOptions?;
61
+ private readonly maxBodySize;
62
+ private readonly preserveRawBody;
63
+ private readonly shutdownTimeoutMs;
64
+ private readonly server;
65
+ private dispatcher?;
66
+ private readonly requestResponseFactory;
67
+ private readonly sockets;
68
+ constructor(port: number, host: string | undefined, retryDelayMs: number | undefined, retryLimit: number | undefined, compression: boolean | undefined, httpsOptions: HttpsServerOptions | undefined, multipartOptions?: MultipartOptions | undefined, maxBodySize?: number, preserveRawBody?: boolean, shutdownTimeoutMs?: number);
69
+ getServer(): NodeServer;
70
+ getRealtimeCapability(): import("@fluojs/http").ServerBackedHttpAdapterRealtimeCapability;
71
+ getListenTarget(): NodeListenTarget;
72
+ listen(dispatcher: Dispatcher): Promise<void>;
73
+ close(): Promise<void>;
74
+ private handleRequest;
75
+ }
76
+ export declare function createNodeHttpAdapter(options?: NodeHttpAdapterOptions, compression?: boolean, multipartOptions?: MultipartOptions): HttpApplicationAdapter;
77
+ export declare function bootstrapNodeApplication(rootModule: ModuleType, options: BootstrapNodeApplicationOptions): Promise<Application>;
78
+ export declare function runNodeApplication(rootModule: ModuleType, options: RunNodeApplicationOptions): Promise<Application>;
79
+ export { compressNodeResponse, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals, };
80
+ //# 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,209 @@
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 '../http-adapter-shared.js';
5
+ import { createNodeResponseCompression, compressNodeResponse } from './internal-node-compression.js';
6
+ import { createFrameworkRequest, NodeRequestPayloadTooLargeException, createRequestSignal, resolveRequestIdFromHeaders } from './internal-node-request.js';
7
+ import { createFrameworkResponse, writeNodeAdapterErrorResponse } from './internal-node-response.js';
8
+ import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
9
+ import { dispatchWithRequestResponseFactory } from '../adapters/request-response-factory.js';
10
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
11
+ export class NodeHttpApplicationAdapter {
12
+ server;
13
+ dispatcher;
14
+ requestResponseFactory;
15
+ sockets = new Set();
16
+ constructor(port, host, retryDelayMs = 150, retryLimit = 20, compression = false, httpsOptions, multipartOptions, maxBodySize = 1 * 1024 * 1024, preserveRawBody = false, shutdownTimeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS) {
17
+ this.port = port;
18
+ this.host = host;
19
+ this.retryDelayMs = retryDelayMs;
20
+ this.retryLimit = retryLimit;
21
+ this.compression = compression;
22
+ this.httpsOptions = httpsOptions;
23
+ this.multipartOptions = multipartOptions;
24
+ this.maxBodySize = maxBodySize;
25
+ this.preserveRawBody = preserveRawBody;
26
+ this.shutdownTimeoutMs = shutdownTimeoutMs;
27
+ this.requestResponseFactory = createNodeRequestResponseFactory(compression, multipartOptions, maxBodySize, preserveRawBody);
28
+ this.server = createNodeServer(this.httpsOptions, (request, response) => {
29
+ void this.handleRequest(request, response);
30
+ });
31
+ this.server.on('connection', socket => {
32
+ this.sockets.add(socket);
33
+ socket.once('close', () => {
34
+ this.sockets.delete(socket);
35
+ });
36
+ });
37
+ }
38
+ getServer() {
39
+ return this.server;
40
+ }
41
+ getRealtimeCapability() {
42
+ return createServerBackedHttpAdapterRealtimeCapability(this.server);
43
+ }
44
+ getListenTarget() {
45
+ return resolveNodeListenTarget(this.server.address() ?? null, this.port, this.host, this.httpsOptions !== undefined);
46
+ }
47
+ async listen(dispatcher) {
48
+ this.dispatcher = dispatcher;
49
+ await listenNodeServerWithRetry(this.server, {
50
+ host: this.host,
51
+ port: this.port,
52
+ retryDelayMs: this.retryDelayMs,
53
+ retryLimit: this.retryLimit
54
+ });
55
+ }
56
+ async close() {
57
+ const server = this.server;
58
+ if (!server.listening) {
59
+ this.dispatcher = undefined;
60
+ return;
61
+ }
62
+ await closeNodeServerWithDrain(server, this.sockets, this.shutdownTimeoutMs);
63
+ this.dispatcher = undefined;
64
+ }
65
+ async handleRequest(request, response) {
66
+ await dispatchWithRequestResponseFactory({
67
+ dispatcher: this.dispatcher,
68
+ dispatcherNotReadyMessage: 'Node HTTP adapter received a request before dispatcher binding completed.',
69
+ factory: this.requestResponseFactory,
70
+ rawRequest: request,
71
+ rawResponse: response
72
+ });
73
+ }
74
+ }
75
+ function createNodeRequestResponseFactory(compression, multipartOptions, maxBodySize, preserveRawBody) {
76
+ return {
77
+ async createRequest(request, signal) {
78
+ return createFrameworkRequest(request, signal, multipartOptions, maxBodySize, preserveRawBody);
79
+ },
80
+ createRequestSignal(response) {
81
+ return createRequestSignal(response);
82
+ },
83
+ createResponse(response, request) {
84
+ return createFrameworkResponse(response, compression ? createNodeResponseCompression(response, request.headers['accept-encoding']) : undefined);
85
+ },
86
+ resolveRequestId(request) {
87
+ return resolveRequestIdFromHeaders(request.headers);
88
+ },
89
+ async writeErrorResponse(error, response, requestId) {
90
+ if (error instanceof NodeRequestPayloadTooLargeException) {
91
+ error.prepareResponse(response.raw);
92
+ }
93
+ await writeNodeAdapterErrorResponse(error, response, requestId);
94
+ }
95
+ };
96
+ }
97
+ export function createNodeHttpAdapter(options = {}, compression = false, multipartOptions) {
98
+ return new NodeHttpApplicationAdapter(resolveNodePort(options.port), options.host, options.retryDelayMs, options.retryLimit, compression, options.https, multipartOptions, options.maxBodySize, options.rawBody, options.shutdownTimeoutMs);
99
+ }
100
+ export async function bootstrapNodeApplication(rootModule, options) {
101
+ return bootstrapHttpAdapterApplication(rootModule, options, createNodeHttpAdapter(options, options.compression ?? false, options.multipart));
102
+ }
103
+ export async function runNodeApplication(rootModule, options) {
104
+ const adapter = createNodeHttpAdapter(options, options.compression ?? false, options.multipart);
105
+ return runHttpAdapterApplication(rootModule, {
106
+ ...options,
107
+ shutdownRegistration: createNodeShutdownSignalRegistration(options.shutdownSignals ?? defaultNodeShutdownSignals())
108
+ }, adapter);
109
+ }
110
+ export { compressNodeResponse, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals };
111
+ function createNodeServer(httpsOptions, handler) {
112
+ return httpsOptions ? createHttpsServer(httpsOptions, handler) : createHttpServer(handler);
113
+ }
114
+ function listenNodeServerWithRetry(server, options) {
115
+ return new Promise((resolve, reject) => {
116
+ const tryListen = attempt => {
117
+ const onError = error => {
118
+ server.off('listening', onListening);
119
+ if (error.code === 'EADDRINUSE' && attempt < options.retryLimit) {
120
+ scheduleNodeListenRetry(server, attempt, options.retryDelayMs, tryListen);
121
+ return;
122
+ }
123
+ reject(error);
124
+ };
125
+ const onListening = () => {
126
+ server.off('error', onError);
127
+ resolve();
128
+ };
129
+ server.once('error', onError);
130
+ server.once('listening', onListening);
131
+ server.listen({
132
+ host: options.host,
133
+ port: options.port
134
+ });
135
+ };
136
+ tryListen(0);
137
+ });
138
+ }
139
+ function scheduleNodeListenRetry(server, attempt, retryDelayMs, tryListen) {
140
+ server.close(() => {
141
+ setTimeout(() => {
142
+ tryListen(attempt + 1);
143
+ }, retryDelayMs);
144
+ });
145
+ }
146
+ function closeNodeServerWithDrain(server, sockets, shutdownTimeoutMs) {
147
+ return new Promise((resolve, reject) => {
148
+ let settled = false;
149
+ const timeout = setTimeout(() => {
150
+ forceCloseConnections(server, sockets);
151
+ }, shutdownTimeoutMs);
152
+ const finish = error => {
153
+ if (settled) {
154
+ return;
155
+ }
156
+ settled = true;
157
+ clearTimeout(timeout);
158
+ if (error) {
159
+ reject(error);
160
+ return;
161
+ }
162
+ resolve();
163
+ };
164
+ server.close(error => {
165
+ finish(error);
166
+ });
167
+ closeIdleConnections(server);
168
+ });
169
+ }
170
+ function resolveNodeListenTarget(address, port, host, useHttps) {
171
+ const protocol = useHttps ? 'https' : 'http';
172
+ const resolvedPort = typeof address === 'object' && address !== null ? address.port : port;
173
+ const bindHost = typeof address === 'object' && address !== null ? address.address : host ?? '0.0.0.0';
174
+ const publicHost = resolvePublicHost(host ?? bindHost);
175
+ const bindTarget = `${formatHostForAuthority(bindHost)}:${String(resolvedPort)}`;
176
+ const url = `${protocol}://${formatHostForAuthority(publicHost)}:${String(resolvedPort)}`;
177
+ return {
178
+ bindTarget,
179
+ url
180
+ };
181
+ }
182
+ function resolvePublicHost(host) {
183
+ return isWildcardHost(host) ? 'localhost' : host;
184
+ }
185
+ function isWildcardHost(host) {
186
+ return host === '0.0.0.0' || host === '::' || host === '[::]';
187
+ }
188
+ function formatHostForAuthority(host) {
189
+ return host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
190
+ }
191
+ function closeIdleConnections(server) {
192
+ server.closeIdleConnections?.();
193
+ }
194
+ function forceCloseConnections(server, sockets) {
195
+ if (typeof server.closeAllConnections === 'function') {
196
+ server.closeAllConnections();
197
+ return;
198
+ }
199
+ for (const socket of sockets) {
200
+ socket.destroy();
201
+ }
202
+ }
203
+ function resolveNodePort(value) {
204
+ const port = value ?? 3000;
205
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
206
+ throw new Error(`Invalid PORT value: ${String(value ?? 3000)}.`);
207
+ }
208
+ return port;
209
+ }
@@ -0,0 +1,2 @@
1
+ export { compressNodeResponse, createNodeResponseCompression, } from './internal-node-compression.js';
2
+ //# sourceMappingURL=node-compression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-compression.d.ts","sourceRoot":"","sources":["../../src/node/node-compression.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,6BAA6B,GAC9B,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1 @@
1
+ export { compressNodeResponse, createNodeResponseCompression } from './internal-node-compression.js';
@@ -0,0 +1,2 @@
1
+ export { createFrameworkRequest, createRequestSignal, resolveRequestIdFromHeaders, } from './internal-node-request.js';
2
+ //# sourceMappingURL=node-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-request.d.ts","sourceRoot":"","sources":["../../src/node/node-request.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1 @@
1
+ export { createFrameworkRequest, createRequestSignal, resolveRequestIdFromHeaders } from './internal-node-request.js';
@@ -0,0 +1,2 @@
1
+ export { createFrameworkResponse, type MutableFrameworkResponse, writeNodeAdapterErrorResponse, } from './internal-node-response.js';
2
+ //# sourceMappingURL=node-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-response.d.ts","sourceRoot":"","sources":["../../src/node/node-response.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,6BAA6B,GAC9B,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1 @@
1
+ export { createFrameworkResponse, writeNodeAdapterErrorResponse } from './internal-node-response.js';
@@ -0,0 +1,2 @@
1
+ export { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals, } from './internal-node-shutdown.js';
2
+ //# sourceMappingURL=node-shutdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-shutdown.d.ts","sourceRoot":"","sources":["../../src/node/node-shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1 @@
1
+ export { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
@@ -0,0 +1,2 @@
1
+ export * from './internal-node.js';
2
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node/node.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './internal-node.js';
package/dist/node.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './logging/json-logger.js';
2
+ export * from './logging/logger.js';
3
+ export { bootstrapNodeApplication, createNodeHttpAdapter, NodeHttpApplicationAdapter, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals, runNodeApplication, } from './node/internal-node.js';
4
+ export type { BootstrapNodeApplicationOptions, CorsInput, NodeApplicationSignal, NodeHttpAdapterOptions, RunNodeApplicationOptions, } from './node/internal-node.js';
5
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,oCAAoC,EACpC,0BAA0B,EAC1B,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,+BAA+B,EAC/B,SAAS,EACT,qBAAqB,EACrB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './logging/json-logger.js';
2
+ export * from './logging/logger.js';
3
+ export { bootstrapNodeApplication, createNodeHttpAdapter, NodeHttpApplicationAdapter, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals, runNodeApplication } from './node/internal-node.js';