@fluojs/runtime 1.0.0-beta.6 → 1.0.0-beta.7

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.
@@ -1,6 +1,24 @@
1
1
  import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { PayloadTooLargeException, type FrameworkRequest } from '@fluojs/http';
3
3
  import { type MultipartOptions } from '../multipart.js';
4
+ type MemoizedValue<T> = () => T;
5
+ type QueryRecord = Record<string, string | string[] | undefined>;
6
+ /**
7
+ * Options for creating a deferred framework request shell from a Node-backed adapter.
8
+ */
9
+ export interface DeferredFrameworkRequestShellOptions<RawRequest> {
10
+ cookieHeader?: string | string[] | undefined;
11
+ headers?: FrameworkRequest['headers'];
12
+ headersFactory?: () => FrameworkRequest['headers'];
13
+ materializeBody?: () => Promise<void>;
14
+ method?: string;
15
+ path: string;
16
+ query?: QueryRecord;
17
+ queryFactory?: () => QueryRecord;
18
+ raw: RawRequest;
19
+ signal: AbortSignal;
20
+ url: string;
21
+ }
4
22
  /**
5
23
  * HTTP payload-size error that closes the underlying Node request stream after the response commits.
6
24
  */
@@ -31,6 +49,13 @@ export declare function createFrameworkRequest(request: IncomingMessage, signal:
31
49
  * @returns The framework request shell with metadata snapshotted and body materialization deferred.
32
50
  */
33
51
  export declare function createDeferredFrameworkRequest(request: IncomingMessage, signal: AbortSignal, multipartOptions?: MultipartOptions, maxBodySize?: number, preserveRawBody?: boolean): FrameworkRequest;
52
+ /**
53
+ * Creates a framework request shell from already-snapshotted Node adapter metadata.
54
+ *
55
+ * @param options - Raw request, metadata factories, and deferred body materialization hooks.
56
+ * @returns A framework request with lazy headers, cookies, query values, and optional body materialization.
57
+ */
58
+ export declare function createDeferredFrameworkRequestShell<RawRequest>({ cookieHeader, headers, headersFactory, materializeBody, method, path, query, queryFactory, raw, signal, url, }: DeferredFrameworkRequestShellOptions<RawRequest>): FrameworkRequest;
34
59
  /**
35
60
  * Materializes a deferred Node framework request body exactly once.
36
61
  *
@@ -38,6 +63,20 @@ export declare function createDeferredFrameworkRequest(request: IncomingMessage,
38
63
  * @returns A promise that settles after body, rawBody, and files fields are populated when applicable.
39
64
  */
40
65
  export declare function materializeFrameworkRequestBody(request: FrameworkRequest): Promise<void>;
66
+ /**
67
+ * Creates a synchronous memoized value resolver.
68
+ *
69
+ * @param factory - Function that computes the value on first access.
70
+ * @returns A stable resolver that returns the cached value after the first call.
71
+ */
72
+ export declare function createMemoizedValue<T>(factory: () => T): MemoizedValue<T>;
73
+ /**
74
+ * Creates an async memoized side-effect resolver.
75
+ *
76
+ * @param factory - Async function to run at most once.
77
+ * @returns A resolver that returns the same in-flight or completed promise for every call.
78
+ */
79
+ export declare function createMemoizedAsyncValue(factory: () => Promise<void>): () => Promise<void>;
41
80
  /**
42
81
  * Creates an abort signal that fires when the Node response closes unexpectedly.
43
82
  *
@@ -52,4 +91,71 @@ export declare function createRequestSignal(response: ServerResponse): AbortSign
52
91
  * @returns The request identifier when present.
53
92
  */
54
93
  export declare function resolveRequestIdFromHeaders(headers: IncomingHttpHeaders): string | undefined;
94
+ /**
95
+ * Parses a raw URL search string into the framework query shape.
96
+ *
97
+ * @param search - Raw search string, with or without a leading question mark.
98
+ * @returns Query values where repeated keys become string arrays.
99
+ */
100
+ export declare function parseQueryParamsFromSearch(search: string): Record<string, string | string[]>;
101
+ /**
102
+ * Snapshots host-parsed query values when they already match framework semantics.
103
+ *
104
+ * @param query - Host query object exposed by a Node-backed adapter.
105
+ * @returns A cloned query record when all values are strings or string arrays; otherwise `undefined` for raw URL fallback.
106
+ */
107
+ export declare function snapshotSimpleQueryRecord(query: unknown): QueryRecord | undefined;
108
+ /**
109
+ * Clones Node request headers into the framework header record shape.
110
+ *
111
+ * @param headers - Raw Node incoming headers.
112
+ * @returns A shallow header snapshot with array values cloned.
113
+ */
114
+ export declare function cloneRequestHeaders(headers: IncomingHttpHeaders): FrameworkRequest['headers'];
115
+ /**
116
+ * Clones a single Node header value when it is array-backed.
117
+ *
118
+ * @param value - Header value to snapshot.
119
+ * @returns The original scalar value or a cloned array value.
120
+ */
121
+ export declare function cloneHeaderValue<T extends string | string[] | undefined>(value: T): T;
122
+ /**
123
+ * Reads the primary value from a Node header value.
124
+ *
125
+ * @param headerValue - Header value that may contain multiple entries.
126
+ * @returns The first header value when present.
127
+ */
128
+ export declare function readPrimaryHeaderValue(headerValue: string | string[] | undefined): string | undefined;
129
+ /**
130
+ * Normalizes a Node content-type header to its primary media type.
131
+ *
132
+ * @param headerValue - Raw content-type header value.
133
+ * @returns Lowercase primary media type without parameters, or `undefined` when absent.
134
+ */
135
+ export declare function normalizePrimaryContentType(headerValue: string | string[] | undefined): string | undefined;
136
+ /**
137
+ * Parses a Node cookie header into framework cookie values.
138
+ *
139
+ * @param cookieHeader - Raw cookie header value or values.
140
+ * @returns Cookie name/value pairs with percent-decoded values when possible.
141
+ */
142
+ export declare function parseCookieHeader(cookieHeader: string | string[] | undefined): Record<string, string>;
143
+ /**
144
+ * Splits a raw Node request URL into path and search components.
145
+ *
146
+ * @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
147
+ * @returns The pathname and search string used by framework request matching and query parsing.
148
+ */
149
+ export declare function splitRawRequestUrl(rawUrl: string | undefined): {
150
+ path: string;
151
+ search: string;
152
+ };
153
+ /**
154
+ * Resolves a raw Node request URL into an absolute URL string.
155
+ *
156
+ * @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
157
+ * @returns An absolute URL suitable for Web-standard parsers.
158
+ */
159
+ export declare function resolveAbsoluteRequestUrl(rawUrl: string | undefined): string;
160
+ export {};
55
161
  //# sourceMappingURL=internal-node-request.d.ts.map
@@ -1 +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;AAUzB;;GAEG;AACH,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,CAW3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,WAAW,SAAkB,EAC7B,eAAe,UAAQ,GACtB,gBAAgB,CA2DlB;AAoBD;;;;;GAKG;AACH,wBAAsB,+BAA+B,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9F;AAyBD;;;;;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"}
1
+ {"version":3,"file":"internal-node-request.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-request.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,11 @@ import { Readable } from 'node:stream';
2
2
  import { URL } from 'node:url';
3
3
  import { BadRequestException, PayloadTooLargeException } from '@fluojs/http';
4
4
  import { parseMultipart } from '../multipart.js';
5
+
6
+ /**
7
+ * Options for creating a deferred framework request shell from a Node-backed adapter.
8
+ */
9
+
5
10
  /**
6
11
  * HTTP payload-size error that closes the underlying Node request stream after the response commits.
7
12
  */
@@ -50,21 +55,19 @@ export async function createFrameworkRequest(request, signal, multipartOptions,
50
55
  * @returns The framework request shell with metadata snapshotted and body materialization deferred.
51
56
  */
52
57
  export function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBodySize = 1 * 1024 * 1024, preserveRawBody = false) {
53
- const url = new URL(request.url ?? '/', 'http://localhost');
58
+ const rawUrl = request.url ?? '/';
59
+ const urlParts = splitRawRequestUrl(rawUrl);
54
60
  const headers = cloneRequestHeaders(request.headers);
55
- const cookieHeader = cloneHeaderValue(headers.cookie);
56
- const searchParams = new URLSearchParams(url.searchParams);
57
- const cookies = createMemoizedValue(() => parseCookieHeader(cookieHeader));
58
- const query = createMemoizedValue(() => parseQueryParams(searchParams));
59
61
  const contentType = normalizePrimaryContentType(headers['content-type']);
60
62
  const isMultipart = contentType === 'multipart/form-data';
63
+ let frameworkRequest;
61
64
  const materializeBody = createMemoizedAsyncValue(async () => {
62
65
  if (isMultipart) {
63
66
  const result = await parseMultipart({
64
67
  body: Readable.toWeb(request),
65
68
  headers,
66
69
  method: request.method,
67
- url: url.toString()
70
+ url: resolveAbsoluteRequestUrl(rawUrl)
68
71
  }, {
69
72
  ...multipartOptions,
70
73
  maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
@@ -83,22 +86,62 @@ export function createDeferredFrameworkRequest(request, signal, multipartOptions
83
86
  frameworkRequest.rawBody = bodyResult.rawBody;
84
87
  }
85
88
  });
89
+ frameworkRequest = createDeferredFrameworkRequestShell({
90
+ cookieHeader: cloneHeaderValue(headers.cookie),
91
+ headers,
92
+ materializeBody,
93
+ method: request.method,
94
+ path: urlParts.path,
95
+ queryFactory: () => parseQueryParamsFromSearch(urlParts.search),
96
+ raw: request,
97
+ signal,
98
+ url: urlParts.path + urlParts.search
99
+ });
100
+ return frameworkRequest;
101
+ }
102
+
103
+ /**
104
+ * Creates a framework request shell from already-snapshotted Node adapter metadata.
105
+ *
106
+ * @param options - Raw request, metadata factories, and deferred body materialization hooks.
107
+ * @returns A framework request with lazy headers, cookies, query values, and optional body materialization.
108
+ */
109
+ export function createDeferredFrameworkRequestShell({
110
+ cookieHeader,
111
+ headers,
112
+ headersFactory,
113
+ materializeBody,
114
+ method,
115
+ path,
116
+ query,
117
+ queryFactory,
118
+ raw,
119
+ signal,
120
+ url
121
+ }) {
122
+ const resolveHeaders = headersFactory ? createMemoizedValue(headersFactory) : () => headers ?? {};
123
+ const resolveCookies = createMemoizedValue(() => parseCookieHeader(cookieHeader ?? resolveHeaders().cookie));
124
+ const resolveQuery = createMemoizedValue(() => query ?? queryFactory?.() ?? {});
86
125
  const frameworkRequest = {
87
126
  get cookies() {
88
- return cookies();
127
+ return resolveCookies();
89
128
  },
90
- headers,
91
- method: request.method ?? 'GET',
129
+ get headers() {
130
+ return resolveHeaders();
131
+ },
132
+ method: method ?? 'GET',
92
133
  params: {},
93
- path: url.pathname,
134
+ path,
94
135
  get query() {
95
- return query();
136
+ return resolveQuery();
96
137
  },
97
- raw: request,
138
+ raw,
98
139
  signal,
99
- url: url.pathname + url.search,
100
- materializeBody
140
+ url
101
141
  };
142
+ if (materializeBody) {
143
+ frameworkRequest.materializeBody = materializeBody;
144
+ }
102
145
  return frameworkRequest;
103
146
  }
104
147
  function hasNodeRequestBody(request) {
@@ -125,7 +168,14 @@ export async function materializeFrameworkRequestBody(request) {
125
168
  await request.materializeBody?.();
126
169
  delete request.materializeBody;
127
170
  }
128
- function createMemoizedValue(factory) {
171
+
172
+ /**
173
+ * Creates a synchronous memoized value resolver.
174
+ *
175
+ * @param factory - Function that computes the value on first access.
176
+ * @returns A stable resolver that returns the cached value after the first call.
177
+ */
178
+ export function createMemoizedValue(factory) {
129
179
  let initialized = false;
130
180
  let value;
131
181
  return () => {
@@ -136,7 +186,14 @@ function createMemoizedValue(factory) {
136
186
  return value;
137
187
  };
138
188
  }
139
- function createMemoizedAsyncValue(factory) {
189
+
190
+ /**
191
+ * Creates an async memoized side-effect resolver.
192
+ *
193
+ * @param factory - Async function to run at most once.
194
+ * @returns A resolver that returns the same in-flight or completed promise for every call.
195
+ */
196
+ export function createMemoizedAsyncValue(factory) {
140
197
  let promise;
141
198
  return () => {
142
199
  promise ??= factory();
@@ -175,6 +232,16 @@ export function resolveRequestIdFromHeaders(headers) {
175
232
  const requestId = headers['x-request-id'] ?? headers['x-correlation-id'];
176
233
  return Array.isArray(requestId) ? requestId[0] : requestId;
177
234
  }
235
+
236
+ /**
237
+ * Parses a raw URL search string into the framework query shape.
238
+ *
239
+ * @param search - Raw search string, with or without a leading question mark.
240
+ * @returns Query values where repeated keys become string arrays.
241
+ */
242
+ export function parseQueryParamsFromSearch(search) {
243
+ return parseQueryParams(new URLSearchParams(search));
244
+ }
178
245
  function parseQueryParams(searchParams) {
179
246
  const query = {};
180
247
  for (const [key, value] of searchParams.entries()) {
@@ -191,20 +258,73 @@ function parseQueryParams(searchParams) {
191
258
  }
192
259
  return query;
193
260
  }
194
- function cloneRequestHeaders(headers) {
261
+
262
+ /**
263
+ * Snapshots host-parsed query values when they already match framework semantics.
264
+ *
265
+ * @param query - Host query object exposed by a Node-backed adapter.
266
+ * @returns A cloned query record when all values are strings or string arrays; otherwise `undefined` for raw URL fallback.
267
+ */
268
+ export function snapshotSimpleQueryRecord(query) {
269
+ if (typeof query !== 'object' || query === null) {
270
+ return undefined;
271
+ }
272
+ const snapshot = {};
273
+ for (const [key, value] of Object.entries(query)) {
274
+ if (typeof value === 'string') {
275
+ snapshot[key] = value;
276
+ continue;
277
+ }
278
+ if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
279
+ snapshot[key] = [...value];
280
+ continue;
281
+ }
282
+ return undefined;
283
+ }
284
+ return snapshot;
285
+ }
286
+
287
+ /**
288
+ * Clones Node request headers into the framework header record shape.
289
+ *
290
+ * @param headers - Raw Node incoming headers.
291
+ * @returns A shallow header snapshot with array values cloned.
292
+ */
293
+ export function cloneRequestHeaders(headers) {
195
294
  const clonedEntries = Object.entries(headers).map(([name, value]) => [name, cloneHeaderValue(value)]);
196
295
  return Object.fromEntries(clonedEntries);
197
296
  }
198
- function cloneHeaderValue(value) {
297
+
298
+ /**
299
+ * Clones a single Node header value when it is array-backed.
300
+ *
301
+ * @param value - Header value to snapshot.
302
+ * @returns The original scalar value or a cloned array value.
303
+ */
304
+ export function cloneHeaderValue(value) {
199
305
  return Array.isArray(value) ? [...value] : value;
200
306
  }
201
- function readPrimaryHeaderValue(headerValue) {
307
+
308
+ /**
309
+ * Reads the primary value from a Node header value.
310
+ *
311
+ * @param headerValue - Header value that may contain multiple entries.
312
+ * @returns The first header value when present.
313
+ */
314
+ export function readPrimaryHeaderValue(headerValue) {
202
315
  if (Array.isArray(headerValue)) {
203
316
  return headerValue[0];
204
317
  }
205
318
  return headerValue;
206
319
  }
207
- function normalizePrimaryContentType(headerValue) {
320
+
321
+ /**
322
+ * Normalizes a Node content-type header to its primary media type.
323
+ *
324
+ * @param headerValue - Raw content-type header value.
325
+ * @returns Lowercase primary media type without parameters, or `undefined` when absent.
326
+ */
327
+ export function normalizePrimaryContentType(headerValue) {
208
328
  const primaryHeaderValue = readPrimaryHeaderValue(headerValue);
209
329
  if (typeof primaryHeaderValue !== 'string') {
210
330
  return undefined;
@@ -220,7 +340,14 @@ function decodeCookieValue(raw) {
220
340
  return raw;
221
341
  }
222
342
  }
223
- function parseCookieHeader(cookieHeader) {
343
+
344
+ /**
345
+ * Parses a Node cookie header into framework cookie values.
346
+ *
347
+ * @param cookieHeader - Raw cookie header value or values.
348
+ * @returns Cookie name/value pairs with percent-decoded values when possible.
349
+ */
350
+ export function parseCookieHeader(cookieHeader) {
224
351
  const normalizedCookieHeader = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader;
225
352
  if (!normalizedCookieHeader) {
226
353
  return {};
@@ -272,4 +399,51 @@ async function readRequestBody(request, contentType, maxBodySize = 1 * 1024 * 10
272
399
  body: bodyText,
273
400
  rawBody: preserveRawBody ? rawBody : undefined
274
401
  };
402
+ }
403
+
404
+ /**
405
+ * Splits a raw Node request URL into path and search components.
406
+ *
407
+ * @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
408
+ * @returns The pathname and search string used by framework request matching and query parsing.
409
+ */
410
+ export function splitRawRequestUrl(rawUrl) {
411
+ const resolvedRawUrl = rawUrl ?? '/';
412
+ if (resolvedRawUrl.startsWith('http://') || resolvedRawUrl.startsWith('https://')) {
413
+ const url = new URL(resolvedRawUrl);
414
+ return {
415
+ path: url.pathname,
416
+ search: url.search
417
+ };
418
+ }
419
+ const queryStart = resolvedRawUrl.indexOf('?');
420
+ const hashStart = resolvedRawUrl.indexOf('#');
421
+ const pathEndCandidates = [queryStart, hashStart].filter(index => index >= 0);
422
+ const pathEnd = pathEndCandidates.length > 0 ? Math.min(...pathEndCandidates) : resolvedRawUrl.length;
423
+ const path = resolvedRawUrl.slice(0, pathEnd) || '/';
424
+ if (queryStart === -1) {
425
+ return {
426
+ path,
427
+ search: ''
428
+ };
429
+ }
430
+ const searchEnd = hashStart >= 0 && hashStart > queryStart ? hashStart : resolvedRawUrl.length;
431
+ return {
432
+ path,
433
+ search: resolvedRawUrl.slice(queryStart, searchEnd)
434
+ };
435
+ }
436
+
437
+ /**
438
+ * Resolves a raw Node request URL into an absolute URL string.
439
+ *
440
+ * @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
441
+ * @returns An absolute URL suitable for Web-standard parsers.
442
+ */
443
+ export function resolveAbsoluteRequestUrl(rawUrl) {
444
+ const resolvedRawUrl = rawUrl ?? '/';
445
+ if (resolvedRawUrl.startsWith('http://') || resolvedRawUrl.startsWith('https://')) {
446
+ return resolvedRawUrl;
447
+ }
448
+ return new URL(resolvedRawUrl, 'http://localhost').toString();
275
449
  }
@@ -2,6 +2,7 @@ import { createServer as createHttpServer } from 'node:http';
2
2
  import { createServer as createHttpsServer, type ServerOptions as HttpsServerOptions } from 'node:https';
3
3
  import { type CorsOptions, type Dispatcher, type HttpApplicationAdapter, type MiddlewareLike, type SecurityHeadersOptions } from '@fluojs/http';
4
4
  import { createNodeResponseCompression, compressNodeResponse } from './internal-node-compression.js';
5
+ import { cloneHeaderValue, cloneRequestHeaders, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, createRequestSignal, readPrimaryHeaderValue, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from './internal-node-request.js';
5
6
  import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
6
7
  import type { MultipartOptions, UploadedFile } from '../multipart.js';
7
8
  import type { Application, ApplicationLogger, CreateApplicationOptions, ModuleType } from '../types.js';
@@ -112,5 +113,5 @@ export declare function bootstrapNodeApplication(rootModule: ModuleType, options
112
113
  * @returns The run node application result.
113
114
  */
114
115
  export declare function runNodeApplication(rootModule: ModuleType, options: RunNodeApplicationOptions): Promise<Application>;
115
- export { compressNodeResponse, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals, };
116
+ export { cloneHeaderValue, cloneRequestHeaders, compressNodeResponse, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createRequestSignal, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, registerShutdownSignals, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl, };
116
117
  //# sourceMappingURL=internal-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal-node.d.ts","sourceRoot":"","sources":["../../src/node/internal-node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"internal-node.d.ts","sourceRoot":"","sources":["../../src/node/internal-node.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { createServer as createHttpsServer } from 'node:https';
3
3
  import { createServerBackedHttpAdapterRealtimeCapability } from '@fluojs/http';
4
4
  import { bootstrapHttpAdapterApplication, runHttpAdapterApplication } from '../http-adapter-shared.js';
5
5
  import { createNodeResponseCompression, compressNodeResponse } from './internal-node-compression.js';
6
- import { createDeferredFrameworkRequest, NodeRequestPayloadTooLargeException, createRequestSignal, materializeFrameworkRequestBody, resolveRequestIdFromHeaders } from './internal-node-request.js';
6
+ import { cloneHeaderValue, cloneRequestHeaders, createDeferredFrameworkRequestShell, createDeferredFrameworkRequest, createMemoizedAsyncValue, createMemoizedValue, NodeRequestPayloadTooLargeException, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, createRequestSignal, materializeFrameworkRequestBody, readPrimaryHeaderValue, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from './internal-node-request.js';
7
7
  import { createFrameworkResponse, writeNodeAdapterErrorResponse } from './internal-node-response.js';
8
8
  import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
9
9
  import { dispatchWithRequestResponseFactory } from '../adapters/request-response-factory.js';
@@ -156,7 +156,7 @@ export async function runNodeApplication(rootModule, options) {
156
156
  shutdownRegistration: createNodeShutdownSignalRegistration(options.shutdownSignals ?? defaultNodeShutdownSignals())
157
157
  }, adapter);
158
158
  }
159
- export { compressNodeResponse, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals };
159
+ export { cloneHeaderValue, cloneRequestHeaders, compressNodeResponse, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createRequestSignal, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, registerShutdownSignals, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl };
160
160
  function createNodeServer(httpsOptions, handler) {
161
161
  return httpsOptions ? createHttpsServer(httpsOptions, handler) : createHttpServer(handler);
162
162
  }
package/dist/web.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAMtB,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAOD;;GAEG;AACH,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,sCAAsC;IACvF,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CAAC;IACzF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,UAAU,IAAI,QAAQ,CAAC;CACxB;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAiNhD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CA6BhF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,yBAAiG,EACjG,OAAO,EACP,OAAO,EACP,GAAG,OAAO,EACX,EAAE,yBAAyB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAU/C;AAED;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,WAAW,SAAwB,EACnC,eAAe,UAAQ,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAW3B"}
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAMtB,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAOD;;GAEG;AACH,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,sCAAsC;IACvF,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CAAC;IACzF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,UAAU,IAAI,QAAQ,CAAC;CACxB;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgNhD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CA6BhF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,yBAAiG,EACjG,OAAO,EACP,OAAO,EACP,GAAG,OAAO,EACX,EAAE,yBAAyB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAU/C;AAED;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,WAAW,SAAwB,EACnC,eAAe,UAAQ,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAW3B"}
package/dist/web.js CHANGED
@@ -146,8 +146,7 @@ class MutableWebFrameworkResponse {
146
146
  headers: toResponseHeaders(this.headers),
147
147
  status: this.statusCode ?? 200
148
148
  };
149
- const responseBody = this.responseBody instanceof Uint8Array ? this.responseBody.slice().buffer : this.responseBody;
150
- const nativeResponseBody = isResponseBodyForbidden(init.status) ? undefined : responseBody ?? '';
149
+ const nativeResponseBody = isResponseBodyForbidden(init.status) ? undefined : this.responseBody === undefined ? '' : this.responseBody;
151
150
  this.finalizedResponse = this.streamActive ? new Response(this.getOrCreateResponseStream().readable, init) : new Response(nativeResponseBody, init);
152
151
  this.raw = this.finalizedResponse;
153
152
  this.committed = true;
@@ -244,23 +243,21 @@ export async function createWebFrameworkRequest(request, signal, multipartOption
244
243
  */
245
244
  function createDeferredWebFrameworkRequest(request, signal, multipartOptions, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false) {
246
245
  const url = new URL(request.url);
247
- const headerEntries = Array.from(request.headers.entries());
246
+ const requestHeaders = new Headers(request.headers);
248
247
  const method = request.method;
249
- const cookieHeader = request.headers.get('cookie') ?? undefined;
250
- const searchParams = new URLSearchParams(url.searchParams);
251
- const headers = createMemoizedValue(() => cloneWebHeaders(headerEntries));
252
- const cookies = createMemoizedValue(() => parseCookieHeader(cookieHeader));
253
- const query = createMemoizedValue(() => parseQueryParams(searchParams));
254
- const contentType = request.headers.get('content-type') ?? undefined;
248
+ const headers = createMemoizedValue(() => cloneWebHeaders(requestHeaders));
249
+ const cookies = createMemoizedValue(() => parseCookieHeader(requestHeaders.get('cookie') ?? undefined));
250
+ const query = createMemoizedValue(() => parseQueryString(url.search));
251
+ const contentType = requestHeaders.get('content-type') ?? undefined;
255
252
  const isMultipart = typeof contentType === 'string' && contentType.includes('multipart/form-data');
256
253
  const materializeBody = createMemoizedAsyncValue(async () => {
257
254
  if (isMultipart) {
258
255
  const materializedRequest = request.clone();
259
256
  const result = await parseMultipart({
260
257
  body: materializedRequest.body,
261
- headers: new Headers(headerEntries),
258
+ headers: requestHeaders,
262
259
  method,
263
- url: url.toString()
260
+ url: request.url
264
261
  }, {
265
262
  ...multipartOptions,
266
263
  maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
@@ -340,25 +337,56 @@ function createMemoizedAsyncValue(factory) {
340
337
  return promise;
341
338
  };
342
339
  }
343
- function parseQueryParams(searchParams) {
340
+ function parseQueryString(search) {
344
341
  const query = {};
345
- for (const [key, value] of searchParams.entries()) {
346
- const current = query[key];
347
- if (current === undefined) {
348
- query[key] = value;
349
- continue;
342
+ if (search.length <= 1) {
343
+ return query;
344
+ }
345
+ let index = search.charCodeAt(0) === 63 ? 1 : 0;
346
+ while (index <= search.length) {
347
+ let nextDelimiter = search.indexOf('&', index);
348
+ if (nextDelimiter === -1) {
349
+ nextDelimiter = search.length;
350
350
  }
351
- if (Array.isArray(current)) {
352
- current.push(value);
353
- continue;
351
+ const entry = search.slice(index, nextDelimiter);
352
+ if (entry.length > 0) {
353
+ const separatorIndex = entry.indexOf('=');
354
+ const rawKey = separatorIndex === -1 ? entry : entry.slice(0, separatorIndex);
355
+ const rawValue = separatorIndex === -1 ? '' : entry.slice(separatorIndex + 1);
356
+ const key = decodeQueryComponent(rawKey, 'key');
357
+ const value = decodeQueryComponent(rawValue, 'value');
358
+ const current = query[key];
359
+ if (current === undefined) {
360
+ query[key] = value;
361
+ } else if (Array.isArray(current)) {
362
+ current.push(value);
363
+ } else {
364
+ query[key] = [current, value];
365
+ }
354
366
  }
355
- query[key] = [current, value];
367
+ index = nextDelimiter + 1;
356
368
  }
357
369
  return query;
358
370
  }
371
+ function decodeQueryComponent(value, kind) {
372
+ const normalizedValue = value.includes('+') ? value.replaceAll('+', ' ') : value;
373
+ try {
374
+ return decodeURIComponent(normalizedValue);
375
+ } catch {
376
+ return decodeQueryComponentLikeUrlSearchParams(value, kind);
377
+ }
378
+ }
379
+ function decodeQueryComponentLikeUrlSearchParams(value, kind) {
380
+ if (kind === 'key') {
381
+ const params = new URLSearchParams(`${value}=`);
382
+ return params.keys().next().value ?? '';
383
+ }
384
+ const params = new URLSearchParams(`x=${value}`);
385
+ return params.get('x') ?? '';
386
+ }
359
387
  function cloneWebHeaders(headers) {
360
388
  const clonedHeaders = {};
361
- for (const [name, value] of headers) {
389
+ for (const [name, value] of headers.entries()) {
362
390
  clonedHeaders[name] = value;
363
391
  }
364
392
  return clonedHeaders;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "module-graph",
10
10
  "orchestration"
11
11
  ],
12
- "version": "1.0.0-beta.6",
12
+ "version": "1.0.0-beta.7",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -70,7 +70,7 @@
70
70
  "@fluojs/config": "^1.0.0-beta.3",
71
71
  "@fluojs/core": "^1.0.0-beta.2",
72
72
  "@fluojs/di": "^1.0.0-beta.5",
73
- "@fluojs/http": "^1.0.0-beta.4"
73
+ "@fluojs/http": "^1.0.0-beta.5"
74
74
  },
75
75
  "devDependencies": {
76
76
  "vitest": "^3.2.4",