@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,45 @@
1
+ /**
2
+ * Represents a single uploaded multipart file buffered in memory.
3
+ */
4
+ export interface UploadedFile {
5
+ fieldname: string;
6
+ originalname: string;
7
+ mimetype: string;
8
+ buffer: Buffer;
9
+ size: number;
10
+ }
11
+ /**
12
+ * Configures multipart parsing limits for file size, file count, and total payload size.
13
+ */
14
+ export interface MultipartOptions {
15
+ maxFileSize?: number;
16
+ maxFiles?: number;
17
+ maxTotalSize?: number;
18
+ }
19
+ /**
20
+ * Contains parsed multipart fields and uploaded files.
21
+ */
22
+ export interface MultipartResult {
23
+ fields: Record<string, string | string[]>;
24
+ files: UploadedFile[];
25
+ }
26
+ /**
27
+ * Describes request-like multipart inputs accepted by the runtime parsers.
28
+ */
29
+ export interface MultipartRequestLike {
30
+ body?: AsyncIterable<Uint8Array> | BodyInit | null;
31
+ headers: Headers | Readonly<Record<string, string | string[] | undefined>>;
32
+ method?: string;
33
+ url?: string;
34
+ [Symbol.asyncIterator]?(): AsyncIterator<Uint8Array>;
35
+ }
36
+ /**
37
+ * Parses a multipart request into string fields and in-memory uploaded files.
38
+ *
39
+ * @param request - Web `Request` or request-like input carrying a multipart body.
40
+ * @param options - Multipart limits for file size, file count, and total payload size.
41
+ * @returns Parsed string fields plus uploaded files buffered in memory.
42
+ * @throws {PayloadTooLargeException} When the multipart payload, file count, or file size exceeds the configured limits.
43
+ */
44
+ export declare function parseMultipart(request: Request | MultipartRequestLike, options?: MultipartOptions): Promise<MultipartResult>;
45
+ //# sourceMappingURL=multipart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../src/multipart.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC;IACnD,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,aAAa,CAAC,UAAU,CAAC,CAAC;CACtD;AAOD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,GAAG,oBAAoB,EACvC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CA8C1B"}
@@ -0,0 +1,195 @@
1
+ import { PayloadTooLargeException } from '@fluojs/http';
2
+
3
+ /**
4
+ * Represents a single uploaded multipart file buffered in memory.
5
+ */
6
+
7
+ /**
8
+ * Configures multipart parsing limits for file size, file count, and total payload size.
9
+ */
10
+
11
+ /**
12
+ * Contains parsed multipart fields and uploaded files.
13
+ */
14
+
15
+ /**
16
+ * Describes request-like multipart inputs accepted by the runtime parsers.
17
+ */
18
+
19
+ const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
20
+ const DEFAULT_MAX_FILES = 10;
21
+ const DEFAULT_MAX_TOTAL_SIZE = 10 * 1024 * 1024;
22
+ const MULTIPART_BODY_LIMIT_MESSAGE = 'Multipart body exceeds the maximum size of';
23
+
24
+ /**
25
+ * Parses a multipart request into string fields and in-memory uploaded files.
26
+ *
27
+ * @param request - Web `Request` or request-like input carrying a multipart body.
28
+ * @param options - Multipart limits for file size, file count, and total payload size.
29
+ * @returns Parsed string fields plus uploaded files buffered in memory.
30
+ * @throws {PayloadTooLargeException} When the multipart payload, file count, or file size exceeds the configured limits.
31
+ */
32
+ export async function parseMultipart(request, options = {}) {
33
+ const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
34
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
35
+ const maxTotalSize = options.maxTotalSize ?? DEFAULT_MAX_TOTAL_SIZE;
36
+ const formData = await toWebRequest(request, maxTotalSize).formData();
37
+ const fields = {};
38
+ const files = [];
39
+ let totalSize = 0;
40
+ for (const [fieldname, value] of formData.entries()) {
41
+ if (typeof value === 'string') {
42
+ totalSize += Buffer.byteLength(value, 'utf8');
43
+ if (totalSize > maxTotalSize) {
44
+ throw new PayloadTooLargeException(`${MULTIPART_BODY_LIMIT_MESSAGE} ${String(maxTotalSize)} bytes.`);
45
+ }
46
+ appendMultipartField(fields, fieldname, value);
47
+ continue;
48
+ }
49
+ totalSize += value.size;
50
+ if (totalSize > maxTotalSize) {
51
+ throw new PayloadTooLargeException(`${MULTIPART_BODY_LIMIT_MESSAGE} ${String(maxTotalSize)} bytes.`);
52
+ }
53
+ if (files.length >= maxFiles) {
54
+ throw new PayloadTooLargeException(`Exceeded maximum file count of ${String(maxFiles)}.`);
55
+ }
56
+ if (value.size > maxFileSize) {
57
+ throw new PayloadTooLargeException(`File "${fieldname}" exceeds the maximum size of ${String(maxFileSize)} bytes.`);
58
+ }
59
+ files.push({
60
+ buffer: Buffer.from(await value.arrayBuffer()),
61
+ fieldname,
62
+ mimetype: value.type,
63
+ originalname: value.name,
64
+ size: value.size
65
+ });
66
+ }
67
+ return {
68
+ fields,
69
+ files
70
+ };
71
+ }
72
+ function appendMultipartField(fields, name, value) {
73
+ const existing = fields[name];
74
+ if (existing === undefined) {
75
+ fields[name] = value;
76
+ return;
77
+ }
78
+ if (Array.isArray(existing)) {
79
+ existing.push(value);
80
+ return;
81
+ }
82
+ fields[name] = [existing, value];
83
+ }
84
+ function toWebRequest(request, maxTotalSize) {
85
+ if (request instanceof Request) {
86
+ return createMultipartRequest(request.url, request.method, new Headers(request.headers), request.body, maxTotalSize);
87
+ }
88
+ const method = request.method ?? 'POST';
89
+ const body = supportsRequestBody(method) ? resolveRequestBody(request) : undefined;
90
+ return createMultipartRequest(request.url ?? 'http://localhost/', method, normalizeRequestHeaders(request.headers), body, maxTotalSize);
91
+ }
92
+ function resolveRequestBody(request) {
93
+ if (request.body !== undefined) {
94
+ return isAsyncIterableBody(request.body) ? createReadableStreamFromAsyncIterable(request.body) : request.body;
95
+ }
96
+ if (typeof request[Symbol.asyncIterator] === 'function') {
97
+ return createReadableStreamFromAsyncIterable(request);
98
+ }
99
+ return undefined;
100
+ }
101
+ function supportsRequestBody(method) {
102
+ return method !== 'GET' && method !== 'HEAD';
103
+ }
104
+ function isAsyncIterableBody(body) {
105
+ return body !== null && typeof body === 'object' && Symbol.asyncIterator in body;
106
+ }
107
+ function isStreamingBody(body) {
108
+ return body instanceof ReadableStream || isAsyncIterableBody(body);
109
+ }
110
+ function createReadableStreamFromAsyncIterable(source) {
111
+ const iterator = source[Symbol.asyncIterator]();
112
+ return new ReadableStream({
113
+ async cancel() {
114
+ await iterator.return?.();
115
+ },
116
+ async pull(controller) {
117
+ const {
118
+ done,
119
+ value
120
+ } = await iterator.next();
121
+ if (done) {
122
+ controller.close();
123
+ return;
124
+ }
125
+ controller.enqueue(value);
126
+ }
127
+ });
128
+ }
129
+ function createMultipartRequest(url, method, headers, body, maxTotalSize) {
130
+ const contentLength = headers.get('content-length');
131
+ if (contentLength !== null) {
132
+ const parsedContentLength = Number(contentLength);
133
+ if (Number.isFinite(parsedContentLength) && parsedContentLength > maxTotalSize) {
134
+ throw new PayloadTooLargeException(`${MULTIPART_BODY_LIMIT_MESSAGE} ${String(maxTotalSize)} bytes.`);
135
+ }
136
+ }
137
+ const init = {
138
+ headers,
139
+ method
140
+ };
141
+ if (body !== undefined) {
142
+ const limitedBody = body !== null && isStreamingBody(body) ? createByteLimitedReadableStream(body, maxTotalSize) : body;
143
+ init.body = limitedBody;
144
+ if (limitedBody instanceof ReadableStream) {
145
+ init.duplex = 'half';
146
+ }
147
+ }
148
+ return new Request(url, init);
149
+ }
150
+ function createByteLimitedReadableStream(stream, maxTotalSize) {
151
+ const source = stream instanceof ReadableStream ? stream : createReadableStreamFromAsyncIterable(stream);
152
+ const reader = source.getReader();
153
+ let totalSize = 0;
154
+ return new ReadableStream({
155
+ async cancel(reason) {
156
+ await reader.cancel(reason);
157
+ },
158
+ async pull(controller) {
159
+ const {
160
+ done,
161
+ value
162
+ } = await reader.read();
163
+ if (done) {
164
+ controller.close();
165
+ return;
166
+ }
167
+ totalSize += value.byteLength;
168
+ if (totalSize > maxTotalSize) {
169
+ const error = new PayloadTooLargeException(`${MULTIPART_BODY_LIMIT_MESSAGE} ${String(maxTotalSize)} bytes.`);
170
+ await reader.cancel(error.message);
171
+ controller.error(error);
172
+ return;
173
+ }
174
+ controller.enqueue(value);
175
+ }
176
+ });
177
+ }
178
+ function normalizeRequestHeaders(headers) {
179
+ if (headers instanceof Headers) {
180
+ return new Headers(headers);
181
+ }
182
+ const normalized = new Headers();
183
+ for (const [name, value] of Object.entries(headers)) {
184
+ if (Array.isArray(value)) {
185
+ for (const headerValue of value) {
186
+ normalized.append(name, headerValue);
187
+ }
188
+ continue;
189
+ }
190
+ if (value !== undefined) {
191
+ normalized.set(name, value);
192
+ }
193
+ }
194
+ return normalized;
195
+ }
@@ -0,0 +1,7 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ import type { FrameworkResponseCompression } from '@fluojs/http';
3
+ type Encoding = 'br' | 'gzip' | 'identity';
4
+ export declare function createNodeResponseCompression(response: ServerResponse, acceptEncoding: string | undefined): FrameworkResponseCompression | undefined;
5
+ export declare function compressNodeResponse(response: ServerResponse, body: Uint8Array, encoding: Exclude<Encoding, 'identity'>): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=internal-node-compression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-node-compression.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-compression.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EACV,4BAA4B,EAE7B,MAAM,cAAc,CAAC;AAiBtB,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC;AAE3C,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,4BAA4B,GAAG,SAAS,CAqB1C;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAcf"}
@@ -0,0 +1,68 @@
1
+ import { createBrotliCompress, createGzip } from 'node:zlib';
2
+ const COMPRESS_THRESHOLD = 1024;
3
+ const SKIP_CONTENT_TYPES = new Set(['image/', 'audio/', 'video/', 'application/zip', 'application/gzip', 'application/x-bzip', 'application/x-bzip2', 'application/x-xz', 'application/zstd', 'application/octet-stream']);
4
+ export function createNodeResponseCompression(response, acceptEncoding) {
5
+ if (!acceptEncoding) {
6
+ return undefined;
7
+ }
8
+ return {
9
+ async write(body, options = {}) {
10
+ if (body.byteLength < COMPRESS_THRESHOLD || shouldSkipContentType(options.contentType)) {
11
+ return false;
12
+ }
13
+ const encoding = selectEncoding(acceptEncoding);
14
+ if (encoding === 'identity') {
15
+ return false;
16
+ }
17
+ await compressNodeResponse(response, body, encoding);
18
+ return true;
19
+ }
20
+ };
21
+ }
22
+ export function compressNodeResponse(response, body, encoding) {
23
+ const stream = encoding === 'br' ? createBrotliCompress() : createGzip();
24
+ response.setHeader('Content-Encoding', encoding);
25
+ response.removeHeader('Content-Length');
26
+ return new Promise((resolve, reject) => {
27
+ stream.on('error', reject);
28
+ stream.pipe(response);
29
+ stream.on('finish', resolve);
30
+ stream.end(body);
31
+ });
32
+ }
33
+ function shouldSkipContentType(contentType) {
34
+ if (!contentType) {
35
+ return false;
36
+ }
37
+ const lower = contentType.toLowerCase();
38
+ for (const prefix of SKIP_CONTENT_TYPES) {
39
+ if (lower.startsWith(prefix)) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ function selectEncoding(acceptEncoding) {
46
+ if (!acceptEncoding) {
47
+ return 'identity';
48
+ }
49
+ const entries = acceptEncoding.split(',').map(entry => {
50
+ const [enc, qPart] = entry.trim().split(';');
51
+ const q = qPart ? parseFloat(qPart.replace('q=', '')) : 1;
52
+ return {
53
+ enc: enc?.trim() ?? '',
54
+ q: Number.isNaN(q) ? 1 : q
55
+ };
56
+ }).filter(entry => entry.q > 0).sort((a, b) => b.q - a.q);
57
+ for (const {
58
+ enc
59
+ } of entries) {
60
+ if (enc === 'br') {
61
+ return 'br';
62
+ }
63
+ if (enc === 'gzip') {
64
+ return 'gzip';
65
+ }
66
+ }
67
+ return 'identity';
68
+ }
@@ -0,0 +1,34 @@
1
+ import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';
2
+ import { PayloadTooLargeException, type FrameworkRequest } from '@fluojs/http';
3
+ import { type MultipartOptions } from '../multipart.js';
4
+ export declare class NodeRequestPayloadTooLargeException extends PayloadTooLargeException {
5
+ private readonly request;
6
+ constructor(request: IncomingMessage);
7
+ prepareResponse(response: ServerResponse): void;
8
+ }
9
+ /**
10
+ * Creates a framework request from a raw Node incoming message.
11
+ *
12
+ * @param request - Raw Node request carrying headers, URL, and body stream.
13
+ * @param signal - Abort signal tied to the response lifecycle.
14
+ * @param multipartOptions - Multipart parser options applied to multipart requests.
15
+ * @param maxBodySize - Maximum allowed non-multipart body size in bytes.
16
+ * @param preserveRawBody - Whether to retain the raw request body bytes.
17
+ * @returns The normalized framework request used by the dispatcher.
18
+ */
19
+ export declare function createFrameworkRequest(request: IncomingMessage, signal: AbortSignal, multipartOptions?: MultipartOptions, maxBodySize?: number, preserveRawBody?: boolean): Promise<FrameworkRequest>;
20
+ /**
21
+ * Creates an abort signal that fires when the Node response closes unexpectedly.
22
+ *
23
+ * @param response - Raw Node server response associated with the request.
24
+ * @returns An abort signal for downstream request cancellation handling.
25
+ */
26
+ export declare function createRequestSignal(response: ServerResponse): AbortSignal;
27
+ /**
28
+ * Resolves the request identifier from the preferred inbound headers.
29
+ *
30
+ * @param headers - Raw Node request headers.
31
+ * @returns The request identifier when present.
32
+ */
33
+ export declare function resolveRequestIdFromHeaders(headers: IncomingHttpHeaders): string | undefined;
34
+ //# sourceMappingURL=internal-node-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-node-request.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AAInB,OAAO,EAEL,wBAAwB,EACxB,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AAOzB,qBAAa,mCAAoC,SAAQ,wBAAwB;IACnE,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,eAAe;IAIrD,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;CAahD;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,WAAW,SAAkB,EAC7B,eAAe,UAAQ,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAqD3B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,WAAW,CAezE;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAG5F"}
@@ -0,0 +1,195 @@
1
+ import { Readable } from 'node:stream';
2
+ import { URL } from 'node:url';
3
+ import { BadRequestException, PayloadTooLargeException } from '@fluojs/http';
4
+ import { parseMultipart } from '../multipart.js';
5
+ export class NodeRequestPayloadTooLargeException extends PayloadTooLargeException {
6
+ constructor(request) {
7
+ super('Request body exceeds the size limit.');
8
+ this.request = request;
9
+ }
10
+ prepareResponse(response) {
11
+ response.setHeader('Connection', 'close');
12
+ const destroyRequest = () => {
13
+ if (!this.request.destroyed) {
14
+ this.request.destroy();
15
+ }
16
+ };
17
+ response.once('finish', destroyRequest);
18
+ response.once('close', destroyRequest);
19
+ this.request.pause();
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Creates a framework request from a raw Node incoming message.
25
+ *
26
+ * @param request - Raw Node request carrying headers, URL, and body stream.
27
+ * @param signal - Abort signal tied to the response lifecycle.
28
+ * @param multipartOptions - Multipart parser options applied to multipart requests.
29
+ * @param maxBodySize - Maximum allowed non-multipart body size in bytes.
30
+ * @param preserveRawBody - Whether to retain the raw request body bytes.
31
+ * @returns The normalized framework request used by the dispatcher.
32
+ */
33
+ export async function createFrameworkRequest(request, signal, multipartOptions, maxBodySize = 1 * 1024 * 1024, preserveRawBody = false) {
34
+ const url = new URL(request.url ?? '/', 'http://localhost');
35
+ const headers = cloneRequestHeaders(request.headers);
36
+ const contentType = readPrimaryHeaderValue(headers['content-type']);
37
+ const isMultipart = typeof contentType === 'string' && contentType.includes('multipart/form-data');
38
+ let body;
39
+ let files;
40
+ let rawBody;
41
+ if (isMultipart) {
42
+ const result = await parseMultipart({
43
+ body: Readable.toWeb(request),
44
+ headers,
45
+ method: request.method,
46
+ url: url.toString()
47
+ }, {
48
+ ...multipartOptions,
49
+ maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
50
+ });
51
+ body = result.fields;
52
+ files = result.files;
53
+ } else {
54
+ const bodyResult = await readRequestBody(request, headers['content-type'], maxBodySize, preserveRawBody);
55
+ body = bodyResult.body;
56
+ rawBody = bodyResult.rawBody;
57
+ }
58
+ const frameworkRequest = {
59
+ body,
60
+ cookies: parseCookieHeader(headers.cookie),
61
+ headers,
62
+ method: request.method ?? 'GET',
63
+ params: {},
64
+ path: url.pathname,
65
+ query: parseQueryParams(url.searchParams),
66
+ raw: request,
67
+ signal,
68
+ url: url.pathname + url.search
69
+ };
70
+ if (files) {
71
+ frameworkRequest.files = files;
72
+ }
73
+ if (rawBody) {
74
+ frameworkRequest.rawBody = rawBody;
75
+ }
76
+ return frameworkRequest;
77
+ }
78
+
79
+ /**
80
+ * Creates an abort signal that fires when the Node response closes unexpectedly.
81
+ *
82
+ * @param response - Raw Node server response associated with the request.
83
+ * @returns An abort signal for downstream request cancellation handling.
84
+ */
85
+ export function createRequestSignal(response) {
86
+ const controller = new AbortController();
87
+ const abort = reason => {
88
+ if (!controller.signal.aborted) {
89
+ controller.abort(new Error(reason));
90
+ }
91
+ };
92
+ response.once('close', () => {
93
+ if (!response.writableEnded) {
94
+ abort('Response closed before response commit.');
95
+ }
96
+ });
97
+ return controller.signal;
98
+ }
99
+
100
+ /**
101
+ * Resolves the request identifier from the preferred inbound headers.
102
+ *
103
+ * @param headers - Raw Node request headers.
104
+ * @returns The request identifier when present.
105
+ */
106
+ export function resolveRequestIdFromHeaders(headers) {
107
+ const requestId = headers['x-request-id'] ?? headers['x-correlation-id'];
108
+ return Array.isArray(requestId) ? requestId[0] : requestId;
109
+ }
110
+ function parseQueryParams(searchParams) {
111
+ const query = {};
112
+ for (const [key, value] of searchParams.entries()) {
113
+ const current = query[key];
114
+ if (current === undefined) {
115
+ query[key] = value;
116
+ continue;
117
+ }
118
+ if (Array.isArray(current)) {
119
+ current.push(value);
120
+ continue;
121
+ }
122
+ query[key] = [current, value];
123
+ }
124
+ return query;
125
+ }
126
+ function cloneRequestHeaders(headers) {
127
+ const clonedEntries = Object.entries(headers).map(([name, value]) => [name, Array.isArray(value) ? [...value] : value]);
128
+ return Object.fromEntries(clonedEntries);
129
+ }
130
+ function readPrimaryHeaderValue(headerValue) {
131
+ if (Array.isArray(headerValue)) {
132
+ return headerValue[0];
133
+ }
134
+ return headerValue;
135
+ }
136
+ function decodeCookieValue(raw) {
137
+ try {
138
+ return decodeURIComponent(raw);
139
+ } catch {
140
+ return raw;
141
+ }
142
+ }
143
+ function parseCookieHeader(cookieHeader) {
144
+ const normalizedCookieHeader = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader;
145
+ if (!normalizedCookieHeader) {
146
+ return {};
147
+ }
148
+ return Object.fromEntries(normalizedCookieHeader.split(';').map(pair => pair.trim()).filter(Boolean).map(pair => {
149
+ const index = pair.indexOf('=');
150
+ if (index === -1) {
151
+ return [pair.trim(), ''];
152
+ }
153
+ return [pair.slice(0, index).trim(), decodeCookieValue(pair.slice(index + 1).trim())];
154
+ }));
155
+ }
156
+ async function readRequestBody(request, contentType, maxBodySize = 1 * 1024 * 1024, preserveRawBody = false) {
157
+ const chunks = [];
158
+ let totalSize = 0;
159
+ for await (const chunk of request) {
160
+ const buf = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
161
+ totalSize += buf.byteLength;
162
+ if (totalSize > maxBodySize) {
163
+ throw new NodeRequestPayloadTooLargeException(request);
164
+ }
165
+ chunks.push(buf);
166
+ }
167
+ if (chunks.length === 0) {
168
+ return {
169
+ body: undefined
170
+ };
171
+ }
172
+ const rawBody = Buffer.concat(chunks);
173
+ const bodyText = rawBody.toString('utf8');
174
+ if (bodyText.length === 0) {
175
+ return {
176
+ body: undefined,
177
+ rawBody: preserveRawBody ? rawBody : undefined
178
+ };
179
+ }
180
+ const primaryContentType = Array.isArray(contentType) ? contentType[0] : contentType;
181
+ if (typeof primaryContentType === 'string' && primaryContentType.includes('application/json')) {
182
+ try {
183
+ return {
184
+ body: JSON.parse(bodyText),
185
+ rawBody: preserveRawBody ? rawBody : undefined
186
+ };
187
+ } catch {
188
+ throw new BadRequestException('Request body contains invalid JSON.');
189
+ }
190
+ }
191
+ return {
192
+ body: bodyText,
193
+ rawBody: preserveRawBody ? rawBody : undefined
194
+ };
195
+ }
@@ -0,0 +1,8 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ import { type FrameworkResponseCompression, type FrameworkResponse } from '@fluojs/http';
3
+ export type MutableFrameworkResponse = FrameworkResponse & {
4
+ statusSet?: boolean;
5
+ };
6
+ export declare function createFrameworkResponse(response: ServerResponse, compression?: FrameworkResponseCompression): MutableFrameworkResponse;
7
+ export declare function writeNodeAdapterErrorResponse(error: unknown, response: FrameworkResponse, requestId?: string): Promise<void>;
8
+ //# sourceMappingURL=internal-node-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-node-response.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAIL,KAAK,4BAA4B,EACjC,KAAK,iBAAiB,EAEvB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,wBAAwB,GAAG,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA6CnF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,cAAc,EACxB,WAAW,CAAC,EAAE,4BAA4B,GACzC,wBAAwB,CA+F1B;AAED,wBAAsB,6BAA6B,CACjD,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAIf"}