@fluojs/platform-fastify 1.0.0-beta.7 → 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":"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"}
package/dist/adapter.js CHANGED
@@ -19,6 +19,7 @@ import { dispatchWithRequestResponseFactory } from '@fluojs/runtime/internal/req
19
19
  const DEFAULT_MAX_BODY_SIZE = 1 * 1024 * 1024;
20
20
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
21
21
  const FASTIFY_NATIVE_ROUTE_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'];
22
+ const EMPTY_NATIVE_ROUTE_PARAMS = Object.freeze({});
22
23
 
23
24
  /**
24
25
  * Bootstrap options for creating a Fastify-backed application without
@@ -104,13 +105,11 @@ export class FastifyHttpApplicationAdapter {
104
105
  for (const route of createFastifyNativeRoutes(descriptors)) {
105
106
  this.app.route({
106
107
  handler: async (request, reply) => {
107
- const requestPath = readRequestPathFromRawUrl(request.raw.url);
108
+ const urlParts = splitRawRequestUrl(request.raw.url ?? '/');
108
109
  const params = normalizeNativeRouteParams(request.params);
109
- if (!isRoutePathNormalizationSensitive(requestPath) && !hasNativeRouteParamSeparators(params)) {
110
- bindRawRequestNativeRouteHandoff(request.raw, {
111
- descriptor: route.descriptor,
112
- params
113
- });
110
+ if (!isRoutePathNormalizationSensitive(urlParts.path) && !hasNativeRouteParamSeparators(params)) {
111
+ await this.handleNativeRouteRequest(route.descriptor, params, urlParts, request, reply);
112
+ return;
114
113
  }
115
114
  await this.handleRequest(request, reply);
116
115
  },
@@ -149,6 +148,128 @@ export class FastifyHttpApplicationAdapter {
149
148
  rawResponse: reply
150
149
  });
151
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;
152
273
  }
153
274
  function createFastifyRequestResponseFactory(multipartOptions, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false) {
154
275
  return {
@@ -271,65 +392,71 @@ export async function runFastifyApplication(rootModule, options) {
271
392
  shutdownRegistration: createNodeShutdownSignalRegistration(options.shutdownSignals ?? defaultNodeShutdownSignals())
272
393
  }, adapter);
273
394
  }
274
- function createFrameworkResponse(reply) {
275
- let activeStream;
276
- return {
277
- committed: reply.sent,
278
- headers: {},
279
- raw: reply,
280
- get stream() {
281
- activeStream ??= createFrameworkResponseStream(reply);
282
- return activeStream;
283
- },
284
- redirect(status, location) {
285
- this.setStatus(status);
286
- this.setHeader('Location', location);
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) {
287
419
  this.committed = true;
288
- reply.redirect(location, status);
289
- },
290
- async send(body) {
291
- if (reply.sent) {
292
- this.committed = true;
293
- return;
294
- }
295
- const existingContentType = reply.getHeader('content-type');
296
- const serialized = serializeResponseBody(body, typeof existingContentType === 'string' ? existingContentType : undefined);
297
- if (!reply.hasHeader('content-type') && serialized.defaultContentType) {
298
- reply.header('content-type', serialized.defaultContentType);
299
- }
300
- this.committed = true;
301
- await reply.send(serialized.payload);
302
- },
303
- async sendSimpleJson(body) {
304
- if (reply.sent) {
305
- this.committed = true;
306
- return;
307
- }
308
- if (!reply.hasHeader('content-type')) {
309
- reply.header('content-type', 'application/json; charset=utf-8');
310
- }
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) {
311
432
  this.committed = true;
312
- await reply.send(JSON.stringify(body));
313
- },
314
- setHeader(name, value) {
315
- const lowerName = name.toLowerCase();
316
- if (lowerName === 'set-cookie') {
317
- const merged = mergeSetCookieHeader(reply.getHeader(name), value);
318
- reply.header(name, merged);
319
- this.headers[name] = merged;
320
- return;
321
- }
322
- reply.header(name, value);
323
- this.headers[name] = value;
324
- },
325
- setStatus(code) {
326
- reply.status(code);
327
- this.statusCode = code;
328
- this.statusSet = true;
329
- },
330
- statusCode: undefined,
331
- statusSet: false
332
- };
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);
333
460
  }
334
461
  function createFrameworkResponseStream(reply) {
335
462
  let hijacked = false;
@@ -399,7 +526,8 @@ function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBo
399
526
  const querySnapshot = snapshotSimpleQueryRecord(request.query);
400
527
  const isMultipart = isMultipartRequestContentType(headerSnapshot['content-type']);
401
528
  let frameworkRequest;
402
- const materializeBody = createMemoizedAsyncValue(async () => {
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,7 +548,7 @@ function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBo
420
548
  frameworkRequest.rawBody = rawBodyValue;
421
549
  }
422
550
  }
423
- });
551
+ }) : undefined;
424
552
  frameworkRequest = createDeferredFrameworkRequestShell({
425
553
  cookieHeader: cloneHeaderValue(headerSnapshot.cookie),
426
554
  headersFactory: headers,
@@ -430,9 +558,13 @@ function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBo
430
558
  query: querySnapshot,
431
559
  queryFactory: () => parseQueryParamsFromSearch(urlParts.search),
432
560
  raw: request.raw,
561
+ requestId: resolvePrimaryRequestIdFromHeaders(headerSnapshot),
433
562
  signal,
434
563
  url: urlParts.path + urlParts.search
435
564
  });
565
+ if (!needsDeferredBodyMaterialization) {
566
+ frameworkRequest.body = request.body;
567
+ }
436
568
  const nativeRouteHandoff = consumeRawRequestNativeRouteHandoff(request.raw);
437
569
  return nativeRouteHandoff ? attachFrameworkRequestNativeRouteHandoff(frameworkRequest, nativeRouteHandoff) : frameworkRequest;
438
570
  }
@@ -442,12 +574,29 @@ async function materializeFrameworkRequestBody(request) {
442
574
  }
443
575
  function normalizeNativeRouteParams(params) {
444
576
  if (typeof params !== 'object' || params === null) {
445
- return {};
577
+ return EMPTY_NATIVE_ROUTE_PARAMS;
578
+ }
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);
446
590
  }
447
- return Object.fromEntries(Object.entries(params).flatMap(([key, value]) => typeof value === 'string' ? [[key, value]] : value === undefined ? [] : [[key, String(value)]]));
591
+ return normalized ?? EMPTY_NATIVE_ROUTE_PARAMS;
448
592
  }
449
593
  function hasNativeRouteParamSeparators(params) {
450
- 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;
451
600
  }
452
601
  function collectVersionSensitiveRouteKeys(descriptors) {
453
602
  const grouped = new Map();
@@ -466,9 +615,6 @@ function collectVersionSensitiveRouteKeys(descriptors) {
466
615
  }
467
616
  return new Set([...grouped.entries()].filter(([, current]) => current.count > 1 || current.hasVersioned).map(([routeKey]) => routeKey));
468
617
  }
469
- function readRequestPathFromRawUrl(rawUrl) {
470
- return splitRawRequestUrl(rawUrl ?? '/').path;
471
- }
472
618
  async function parseMultipartRequest(request, options = {}) {
473
619
  const fields = {};
474
620
  const files = [];
@@ -595,6 +741,10 @@ function resolveRequestIdFromHeaders(headers) {
595
741
  const requestId = headers['x-request-id'] ?? headers['x-correlation-id'];
596
742
  return Array.isArray(requestId) ? requestId[0] : requestId;
597
743
  }
744
+ function resolvePrimaryRequestIdFromHeaders(headers) {
745
+ const requestId = headers['x-request-id'];
746
+ return Array.isArray(requestId) ? requestId[0] : requestId;
747
+ }
598
748
  function createFastifyApp(httpsOptions, maxBodySize) {
599
749
  if (httpsOptions) {
600
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.7",
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.5",
42
- "@fluojs/runtime": "^1.0.0-beta.7"
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",