@fluojs/platform-fastify 1.0.0-beta.6 → 1.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/adapter.d.ts CHANGED
@@ -88,6 +88,7 @@ export declare class FastifyHttpApplicationAdapter implements HttpApplicationAda
88
88
  private registerWildcardFallbackRoute;
89
89
  private listenWithRetry;
90
90
  private handleRequest;
91
+ private handleNativeRouteRequest;
91
92
  }
92
93
  /**
93
94
  * Create the recommended Fastify adapter for `FluoFactory.create(...)`.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,IAAI,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAOtE,OAAO,EAOL,KAAK,WAAW,EAChB,KAAK,UAAU,EAIf,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAOtB,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAuBzB,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC5D,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,WAAW,CAAC;AAahE;;;GAGG;AACH,MAAM,WAAW,kCAAmC,SAAQ,IAAI,CAAC,wBAAwB,EAAE,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC7H,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,KAAK,GAAG,sBAAsB,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,kCAAkC;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,KAAK,GAAG,SAAS,wBAAwB,EAAE,CAAC;CAC/D;AAED,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AA4BD;;;;;GAKG;AACH,qBAAa,6BAA8B,YAAW,sBAAsB;IAYxE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAnBpC,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAIrC;gBAGiB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,oBAAM,EAClB,UAAU,oBAAK,EACf,YAAY,EAAE,kBAAkB,GAAG,SAAS,EAC5C,gBAAgB,CAAC,EAAE,gBAAgB,YAAA,EACnC,WAAW,SAAwB,EACnC,eAAe,UAAQ,EACvB,iBAAiB,SAA8B;IAUlE,SAAS,IAAI,OAAO;IAIpB,qBAAqB;IAIrB,eAAe,IAAI,mBAAmB;IAIhC,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAyBd,wBAAwB;IAiBtC,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,6BAA6B;YAMvB,eAAe;YAkBf,aAAa;YAUb,wBAAwB;CAoDvC;AAoOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,qBAA0B,EACnC,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,sBAAsB,CAYxB;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,WAAW,CAAC,CAMtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,WAAW,CAAC,CAQtB;AAoWD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgBvE"}
package/dist/adapter.js CHANGED
@@ -4,6 +4,7 @@ import fastify from 'fastify';
4
4
  import { createServerBackedHttpAdapterRealtimeCapability, createErrorResponse, HttpException, InternalServerErrorException, PayloadTooLargeException } from '@fluojs/http';
5
5
  import { attachFrameworkRequestNativeRouteHandoff, bindRawRequestNativeRouteHandoff, consumeRawRequestNativeRouteHandoff, isRoutePathNormalizationSensitive } from '@fluojs/http/internal';
6
6
  import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals } from '@fluojs/runtime/node';
7
+ import { cloneHeaderValue, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, parseQueryParamsFromSearch, snapshotSimpleQueryRecord, splitRawRequestUrl } from '@fluojs/runtime/internal-node';
7
8
  import { bootstrapHttpAdapterApplication, runHttpAdapterApplication } from '@fluojs/runtime/internal/http-adapter';
8
9
  import { dispatchWithRequestResponseFactory } from '@fluojs/runtime/internal/request-response-factory';
9
10
 
@@ -18,6 +19,7 @@ import { dispatchWithRequestResponseFactory } from '@fluojs/runtime/internal/req
18
19
  const DEFAULT_MAX_BODY_SIZE = 1 * 1024 * 1024;
19
20
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
20
21
  const FASTIFY_NATIVE_ROUTE_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'];
22
+ const EMPTY_NATIVE_ROUTE_PARAMS = Object.freeze({});
21
23
 
22
24
  /**
23
25
  * Bootstrap options for creating a Fastify-backed application without
@@ -103,13 +105,11 @@ export class FastifyHttpApplicationAdapter {
103
105
  for (const route of createFastifyNativeRoutes(descriptors)) {
104
106
  this.app.route({
105
107
  handler: async (request, reply) => {
106
- const requestPath = readRequestPathFromRawUrl(request.raw.url);
108
+ const urlParts = splitRawRequestUrl(request.raw.url ?? '/');
107
109
  const params = normalizeNativeRouteParams(request.params);
108
- if (!isRoutePathNormalizationSensitive(requestPath) && !hasNativeRouteParamSeparators(params)) {
109
- bindRawRequestNativeRouteHandoff(request.raw, {
110
- descriptor: route.descriptor,
111
- params
112
- });
110
+ if (!isRoutePathNormalizationSensitive(urlParts.path) && !hasNativeRouteParamSeparators(params)) {
111
+ await this.handleNativeRouteRequest(route.descriptor, params, urlParts, request, reply);
112
+ return;
113
113
  }
114
114
  await this.handleRequest(request, reply);
115
115
  },
@@ -148,6 +148,128 @@ export class FastifyHttpApplicationAdapter {
148
148
  rawResponse: reply
149
149
  });
150
150
  }
151
+ async handleNativeRouteRequest(descriptor, params, urlParts, request, reply) {
152
+ if (isMultipartRequestContentType(request.raw.headers['content-type'])) {
153
+ bindRawRequestNativeRouteHandoff(request.raw, {
154
+ descriptor,
155
+ params
156
+ });
157
+ await this.handleRequest(request, reply);
158
+ return;
159
+ }
160
+ const dispatcher = this.dispatcher;
161
+ if (!dispatcher?.dispatchNativeRoute) {
162
+ bindRawRequestNativeRouteHandoff(request.raw, {
163
+ descriptor,
164
+ params
165
+ });
166
+ await this.handleRequest(request, reply);
167
+ return;
168
+ }
169
+ const factory = this.requestResponseFactory;
170
+ const frameworkResponse = factory.createResponse(reply, request);
171
+ const lazySignal = createLazyFastifyRequestSignal(reply, factory.createRequestSignal);
172
+ try {
173
+ const frameworkRequest = createNativeFastFrameworkRequest(request, lazySignal, urlParts, this.maxBodySize, this.preserveRawBody) ?? (await factory.createRequest(request, lazySignal.signal()));
174
+ if (!isNativeFastFrameworkRequest(frameworkRequest)) {
175
+ await factory.materializeRequest?.(frameworkRequest);
176
+ }
177
+ const handled = await dispatcher.dispatchNativeRoute({
178
+ descriptor,
179
+ params
180
+ }, frameworkRequest, frameworkResponse);
181
+ if (!handled) {
182
+ bindRawRequestNativeRouteHandoff(request.raw, {
183
+ descriptor,
184
+ params
185
+ });
186
+ await this.handleRequest(request, reply);
187
+ return;
188
+ }
189
+ if (!frameworkResponse.committed) {
190
+ await frameworkResponse.send(undefined);
191
+ }
192
+ } catch (error) {
193
+ if (lazySignal.isAborted() || frameworkResponse.committed) {
194
+ return;
195
+ }
196
+ await factory.writeErrorResponse(error, frameworkResponse, factory.resolveRequestId(request));
197
+ }
198
+ }
199
+ }
200
+ function createNativeFastFrameworkRequest(request, lazySignal, urlParts, maxBodySize, preserveRawBody) {
201
+ const contentType = request.headers['content-type'];
202
+ if (preserveRawBody || isFastifyMultipartRequest(request) || isMultipartRequestContentType(contentType)) {
203
+ return undefined;
204
+ }
205
+ const contentLength = Number(request.headers['content-length']);
206
+ if (Number.isFinite(contentLength) && contentLength > maxBodySize) {
207
+ throw new PayloadTooLargeException('Request body exceeds the size limit.');
208
+ }
209
+ const frameworkRequest = createDeferredFrameworkRequestShell({
210
+ cookieHeader: cloneHeaderValue(request.headers.cookie),
211
+ headersFactory: () => normalizeHeaders(cloneRequestHeaders(request.headers)),
212
+ method: request.method,
213
+ path: urlParts.path,
214
+ query: readSimpleQueryRecord(request.query),
215
+ queryFactory: () => parseQueryParamsFromSearch(urlParts.search),
216
+ raw: request.raw,
217
+ requestId: resolvePrimaryRequestIdFromHeaders(request.raw.headers),
218
+ signal: lazySignal.signal,
219
+ url: urlParts.path + urlParts.search
220
+ });
221
+ frameworkRequest.body = request.body;
222
+ frameworkRequest.isAborted = lazySignal.isAborted;
223
+ markNativeFastFrameworkRequest(frameworkRequest);
224
+ return frameworkRequest;
225
+ }
226
+ function createLazyFastifyRequestSignal(reply, signalFactory) {
227
+ let signal;
228
+ return {
229
+ isAborted() {
230
+ return signal?.aborted ?? isFastifyReplyAborted(reply);
231
+ },
232
+ signal() {
233
+ if (!signal) {
234
+ signal = isFastifyReplyAborted(reply) ? AbortSignal.abort(new Error('Response closed before response commit.')) : signalFactory(reply);
235
+ }
236
+ return signal;
237
+ }
238
+ };
239
+ }
240
+ function isFastifyReplyAborted(reply) {
241
+ return reply.raw.destroyed && !reply.raw.writableEnded;
242
+ }
243
+ const NATIVE_FAST_FRAMEWORK_REQUEST = Symbol('fluo.fastify.nativeFastFrameworkRequest');
244
+ function markNativeFastFrameworkRequest(request) {
245
+ request[NATIVE_FAST_FRAMEWORK_REQUEST] = true;
246
+ }
247
+ function isNativeFastFrameworkRequest(request) {
248
+ return request[NATIVE_FAST_FRAMEWORK_REQUEST] === true;
249
+ }
250
+ function isFastifyMultipartRequest(request) {
251
+ const probe = request.isMultipart;
252
+ return typeof probe === 'function' && probe.call(request) === true;
253
+ }
254
+ function readSimpleQueryRecord(query) {
255
+ if (typeof query !== 'object' || query === null) {
256
+ return undefined;
257
+ }
258
+ const record = query;
259
+ for (const key in record) {
260
+ if (!Object.prototype.hasOwnProperty.call(record, key)) {
261
+ continue;
262
+ }
263
+ const value = record[key];
264
+ if (typeof value === 'string') {
265
+ continue;
266
+ }
267
+ if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
268
+ continue;
269
+ }
270
+ return undefined;
271
+ }
272
+ return record;
151
273
  }
152
274
  function createFastifyRequestResponseFactory(multipartOptions, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false) {
153
275
  return {
@@ -270,65 +392,71 @@ export async function runFastifyApplication(rootModule, options) {
270
392
  shutdownRegistration: createNodeShutdownSignalRegistration(options.shutdownSignals ?? defaultNodeShutdownSignals())
271
393
  }, adapter);
272
394
  }
273
- function createFrameworkResponse(reply) {
274
- let activeStream;
275
- return {
276
- committed: reply.sent,
277
- headers: {},
278
- raw: reply,
279
- get stream() {
280
- activeStream ??= createFrameworkResponseStream(reply);
281
- return activeStream;
282
- },
283
- redirect(status, location) {
284
- this.setStatus(status);
285
- this.setHeader('Location', location);
286
- this.committed = true;
287
- reply.redirect(location, status);
288
- },
289
- async send(body) {
290
- if (reply.sent) {
291
- this.committed = true;
292
- return;
293
- }
294
- const existingContentType = reply.getHeader('content-type');
295
- const serialized = serializeResponseBody(body, typeof existingContentType === 'string' ? existingContentType : undefined);
296
- if (!reply.hasHeader('content-type') && serialized.defaultContentType) {
297
- reply.header('content-type', serialized.defaultContentType);
298
- }
395
+ class MutableFastifyFrameworkResponse {
396
+ committed;
397
+ headers = {};
398
+ raw;
399
+ statusCode;
400
+ statusSet = false;
401
+ activeStream;
402
+ constructor(reply) {
403
+ this.reply = reply;
404
+ this.committed = reply.sent;
405
+ this.raw = reply;
406
+ }
407
+ get stream() {
408
+ this.activeStream ??= createFrameworkResponseStream(this.reply);
409
+ return this.activeStream;
410
+ }
411
+ redirect(status, location) {
412
+ this.setStatus(status);
413
+ this.setHeader('Location', location);
414
+ this.committed = true;
415
+ this.reply.redirect(location, status);
416
+ }
417
+ send(body) {
418
+ if (this.reply.sent) {
299
419
  this.committed = true;
300
- await reply.send(serialized.payload);
301
- },
302
- async sendSimpleJson(body) {
303
- if (reply.sent) {
304
- this.committed = true;
305
- return;
306
- }
307
- if (!reply.hasHeader('content-type')) {
308
- reply.header('content-type', 'application/json; charset=utf-8');
309
- }
420
+ return;
421
+ }
422
+ const existingContentType = this.reply.getHeader('content-type');
423
+ const serialized = serializeResponseBody(body, typeof existingContentType === 'string' ? existingContentType : undefined);
424
+ if (!this.reply.hasHeader('content-type') && serialized.defaultContentType) {
425
+ this.reply.header('content-type', serialized.defaultContentType);
426
+ }
427
+ this.committed = true;
428
+ void this.reply.send(serialized.payload);
429
+ }
430
+ sendSimpleJson(body) {
431
+ if (this.reply.sent) {
310
432
  this.committed = true;
311
- await reply.send(JSON.stringify(body));
312
- },
313
- setHeader(name, value) {
314
- const lowerName = name.toLowerCase();
315
- if (lowerName === 'set-cookie') {
316
- const merged = mergeSetCookieHeader(reply.getHeader(name), value);
317
- reply.header(name, merged);
318
- this.headers[name] = merged;
319
- return;
320
- }
321
- reply.header(name, value);
322
- this.headers[name] = value;
323
- },
324
- setStatus(code) {
325
- reply.status(code);
326
- this.statusCode = code;
327
- this.statusSet = true;
328
- },
329
- statusCode: undefined,
330
- statusSet: false
331
- };
433
+ return;
434
+ }
435
+ if (!this.reply.hasHeader('content-type')) {
436
+ this.reply.header('content-type', 'application/json; charset=utf-8');
437
+ }
438
+ this.committed = true;
439
+ void this.reply.send(JSON.stringify(body));
440
+ }
441
+ setHeader(name, value) {
442
+ const lowerName = name.toLowerCase();
443
+ if (lowerName === 'set-cookie') {
444
+ const merged = mergeSetCookieHeader(this.reply.getHeader(name), value);
445
+ this.reply.header(name, merged);
446
+ this.headers[name] = merged;
447
+ return;
448
+ }
449
+ this.reply.header(name, value);
450
+ this.headers[name] = value;
451
+ }
452
+ setStatus(code) {
453
+ this.reply.status(code);
454
+ this.statusCode = code;
455
+ this.statusSet = true;
456
+ }
457
+ }
458
+ function createFrameworkResponse(reply) {
459
+ return new MutableFastifyFrameworkResponse(reply);
332
460
  }
333
461
  function createFrameworkResponseStream(reply) {
334
462
  let hijacked = false;
@@ -395,11 +523,11 @@ function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBo
395
523
  const urlParts = splitRawRequestUrl(rawUrl);
396
524
  const headerSnapshot = cloneRequestHeaders(request.headers);
397
525
  const headers = createMemoizedValue(() => normalizeHeaders(headerSnapshot));
398
- const cookieHeader = cloneHeaderValue(headerSnapshot.cookie);
399
- const cookies = createMemoizedValue(() => parseCookieHeader(cookieHeader));
400
- const query = createMemoizedValue(() => parseQueryParamsFromSearch(urlParts.search));
526
+ const querySnapshot = snapshotSimpleQueryRecord(request.query);
401
527
  const isMultipart = isMultipartRequestContentType(headerSnapshot['content-type']);
402
- const materializeBody = createMemoizedAsyncValue(async () => {
528
+ let frameworkRequest;
529
+ const needsDeferredBodyMaterialization = isMultipart || preserveRawBody;
530
+ const materializeBody = needsDeferredBodyMaterialization ? createMemoizedAsyncValue(async () => {
403
531
  let body = request.body;
404
532
  let files;
405
533
  if (isMultipart) {
@@ -420,25 +548,23 @@ function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBo
420
548
  frameworkRequest.rawBody = rawBodyValue;
421
549
  }
422
550
  }
423
- });
424
- const frameworkRequest = {
425
- get cookies() {
426
- return cookies();
427
- },
428
- get headers() {
429
- return headers();
430
- },
551
+ }) : undefined;
552
+ frameworkRequest = createDeferredFrameworkRequestShell({
553
+ cookieHeader: cloneHeaderValue(headerSnapshot.cookie),
554
+ headersFactory: headers,
555
+ materializeBody,
431
556
  method: request.method,
432
- params: {},
433
557
  path: urlParts.path,
434
- get query() {
435
- return query();
436
- },
558
+ query: querySnapshot,
559
+ queryFactory: () => parseQueryParamsFromSearch(urlParts.search),
437
560
  raw: request.raw,
561
+ requestId: resolvePrimaryRequestIdFromHeaders(headerSnapshot),
438
562
  signal,
439
- url: urlParts.path + urlParts.search,
440
- materializeBody
441
- };
563
+ url: urlParts.path + urlParts.search
564
+ });
565
+ if (!needsDeferredBodyMaterialization) {
566
+ frameworkRequest.body = request.body;
567
+ }
442
568
  const nativeRouteHandoff = consumeRawRequestNativeRouteHandoff(request.raw);
443
569
  return nativeRouteHandoff ? attachFrameworkRequestNativeRouteHandoff(frameworkRequest, nativeRouteHandoff) : frameworkRequest;
444
570
  }
@@ -448,12 +574,29 @@ async function materializeFrameworkRequestBody(request) {
448
574
  }
449
575
  function normalizeNativeRouteParams(params) {
450
576
  if (typeof params !== 'object' || params === null) {
451
- return {};
577
+ return EMPTY_NATIVE_ROUTE_PARAMS;
452
578
  }
453
- return Object.fromEntries(Object.entries(params).flatMap(([key, value]) => typeof value === 'string' ? [[key, value]] : value === undefined ? [] : [[key, String(value)]]));
579
+ let normalized;
580
+ for (const key in params) {
581
+ if (!Object.prototype.hasOwnProperty.call(params, key)) {
582
+ continue;
583
+ }
584
+ const value = params[key];
585
+ if (value === undefined) {
586
+ continue;
587
+ }
588
+ normalized ??= {};
589
+ normalized[key] = typeof value === 'string' ? value : String(value);
590
+ }
591
+ return normalized ?? EMPTY_NATIVE_ROUTE_PARAMS;
454
592
  }
455
593
  function hasNativeRouteParamSeparators(params) {
456
- return Object.values(params).some(value => value.includes('/'));
594
+ for (const key in params) {
595
+ if (Object.prototype.hasOwnProperty.call(params, key) && params[key]?.includes('/')) {
596
+ return true;
597
+ }
598
+ }
599
+ return false;
457
600
  }
458
601
  function collectVersionSensitiveRouteKeys(descriptors) {
459
602
  const grouped = new Map();
@@ -472,9 +615,6 @@ function collectVersionSensitiveRouteKeys(descriptors) {
472
615
  }
473
616
  return new Set([...grouped.entries()].filter(([, current]) => current.count > 1 || current.hasVersioned).map(([routeKey]) => routeKey));
474
617
  }
475
- function readRequestPathFromRawUrl(rawUrl) {
476
- return splitRawRequestUrl(rawUrl ?? '/').path;
477
- }
478
618
  async function parseMultipartRequest(request, options = {}) {
479
619
  const fields = {};
480
620
  const files = [];
@@ -568,92 +708,9 @@ function normalizeHeaders(headers) {
568
708
  }
569
709
  return normalized;
570
710
  }
571
- function parseQueryParamsFromSearch(search) {
572
- return parseQueryParams(new URLSearchParams(search));
573
- }
574
- function parseQueryParams(searchParams) {
575
- const query = {};
576
- for (const [key, value] of searchParams.entries()) {
577
- const current = query[key];
578
- if (current === undefined) {
579
- query[key] = value;
580
- continue;
581
- }
582
- if (Array.isArray(current)) {
583
- current.push(value);
584
- continue;
585
- }
586
- query[key] = [current, value];
587
- }
588
- return query;
589
- }
590
- function parseCookieHeader(cookieHeader) {
591
- const normalizedCookieHeader = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader;
592
- if (!normalizedCookieHeader) {
593
- return {};
594
- }
595
- return Object.fromEntries(normalizedCookieHeader.split(';').map(pair => pair.trim()).filter(Boolean).map(pair => {
596
- const index = pair.indexOf('=');
597
- if (index === -1) {
598
- return [pair.trim(), ''];
599
- }
600
- const rawValue = pair.slice(index + 1).trim();
601
- try {
602
- return [pair.slice(0, index).trim(), decodeURIComponent(rawValue)];
603
- } catch {
604
- return [pair.slice(0, index).trim(), rawValue];
605
- }
606
- }));
607
- }
608
- function createMemoizedValue(factory) {
609
- let initialized = false;
610
- let value;
611
- return () => {
612
- if (!initialized) {
613
- value = factory();
614
- initialized = true;
615
- }
616
- return value;
617
- };
618
- }
619
- function createMemoizedAsyncValue(factory) {
620
- let promise;
621
- return () => {
622
- promise ??= factory();
623
- return promise;
624
- };
625
- }
626
- function cloneHeaderValue(value) {
627
- return Array.isArray(value) ? [...value] : value;
628
- }
629
711
  function cloneRequestHeaders(headers) {
630
712
  return Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, cloneHeaderValue(value)]));
631
713
  }
632
- function splitRawRequestUrl(rawUrl) {
633
- if (rawUrl.startsWith('http://') || rawUrl.startsWith('https://')) {
634
- const url = new URL(rawUrl);
635
- return {
636
- path: url.pathname,
637
- search: url.search
638
- };
639
- }
640
- const queryStart = rawUrl.indexOf('?');
641
- const hashStart = rawUrl.indexOf('#');
642
- const pathEndCandidates = [queryStart, hashStart].filter(index => index >= 0);
643
- const pathEnd = pathEndCandidates.length > 0 ? Math.min(...pathEndCandidates) : rawUrl.length;
644
- const path = rawUrl.slice(0, pathEnd) || '/';
645
- if (queryStart === -1) {
646
- return {
647
- path,
648
- search: ''
649
- };
650
- }
651
- const searchEnd = hashStart >= 0 && hashStart > queryStart ? hashStart : rawUrl.length;
652
- return {
653
- path,
654
- search: rawUrl.slice(queryStart, searchEnd)
655
- };
656
- }
657
714
  function setMultiValue(target, key, value) {
658
715
  const existing = target[key];
659
716
  if (existing === undefined) {
@@ -684,6 +741,10 @@ function resolveRequestIdFromHeaders(headers) {
684
741
  const requestId = headers['x-request-id'] ?? headers['x-correlation-id'];
685
742
  return Array.isArray(requestId) ? requestId[0] : requestId;
686
743
  }
744
+ function resolvePrimaryRequestIdFromHeaders(headers) {
745
+ const requestId = headers['x-request-id'];
746
+ return Array.isArray(requestId) ? requestId[0] : requestId;
747
+ }
687
748
  function createFastifyApp(httpsOptions, maxBodySize) {
688
749
  if (httpsOptions) {
689
750
  return fastify({
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "platform",
9
9
  "server"
10
10
  ],
11
- "version": "1.0.0-beta.6",
11
+ "version": "1.0.0-beta.8",
12
12
  "private": false,
13
13
  "license": "MIT",
14
14
  "repository": {
@@ -38,8 +38,8 @@
38
38
  "@fastify/multipart": "^9.2.1",
39
39
  "fastify": "^5.8.5",
40
40
  "fastify-raw-body": "^5.0.0",
41
- "@fluojs/http": "^1.0.0-beta.4",
42
- "@fluojs/runtime": "^1.0.0-beta.6"
41
+ "@fluojs/http": "^1.0.0-beta.6",
42
+ "@fluojs/runtime": "^1.0.0-beta.8"
43
43
  },
44
44
  "devDependencies": {
45
45
  "vitest": "^3.2.4",