@fluojs/platform-express 1.0.0-beta.4 → 1.0.0-beta.6

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/README.ko.md CHANGED
@@ -73,7 +73,7 @@ const adapter = createExpressAdapter(
73
73
  ### 안전한 fallback을 포함한 Native Route Registration
74
74
  이제 어댑터는 의미 보존이 가능한 명시적 HTTP 메서드 라우트를 Express Router에 사전 등록하면서도, 실제 요청 처리는 계속 공유 fluo dispatcher를 통해 수행합니다.
75
75
 
76
- 의미 보존이 가능한 unversioned route에서는 Express가 미리 고른 descriptor와 params를 공유 dispatcher에 전달하므로 duplicate route matching 건너뛰면서도 guards, interceptors, observers, body parsing, raw body 캡처, SSE, 오류 응답은 기존과 같은 framework-owned 실행 경로를 유지합니다.
76
+ 의미 보존이 가능한 unversioned route에서는 Express가 미리 고른 descriptor와 params를 공유 dispatcher에 전달하므로 singleton-safe handler는 dispatcher fast path에서 완료될 수 있고 그 밖의 handler는 duplicate route matching 없이 fallback되며, guards, interceptors, observers, body parsing, raw body 캡처, SSE, 오류 응답은 기존과 같은 framework-owned 실행 경로를 유지합니다.
77
77
 
78
78
  어댑터가 native handoff를 붙인 뒤 app middleware가 framework request의 method 또는 path를 rewrite하면 dispatcher는 그 handoff를 stale로 보고 원래 Express match를 재사용하지 않고 rewrite된 요청을 다시 매칭합니다.
79
79
 
package/README.md CHANGED
@@ -73,7 +73,7 @@ const adapter = createExpressAdapter(
73
73
  ### Native Route Registration with Safe Fallback
74
74
  The adapter now pre-registers semantically safe Express Router handlers for explicit HTTP methods and still dispatches those requests through the shared fluo dispatcher.
75
75
 
76
- For semantically safe unversioned routes, Express hands the pre-matched descriptor and params to the shared dispatcher so duplicate route matching is skipped while guards, interceptors, observers, body parsing, raw body capture, SSE, and error responses stay on the same framework-owned execution path.
76
+ For semantically safe unversioned routes, Express hands the pre-matched descriptor and params to the shared dispatcher so eligible singleton-safe handlers can complete on the dispatcher fast path and other handlers can fall back without duplicate route matching, while guards, interceptors, observers, body parsing, raw body capture, SSE, and error responses stay on the same framework-owned execution path.
77
77
 
78
78
  If app middleware rewrites the framework request method or path after the adapter attaches a native handoff, the dispatcher treats that handoff as stale and rematches the rewritten request instead of reusing the original Express match.
79
79
 
package/dist/adapter.d.ts CHANGED
@@ -89,6 +89,7 @@ export declare class ExpressHttpApplicationAdapter implements HttpApplicationAda
89
89
  private listenWithRetry;
90
90
  private registerNativeRoutes;
91
91
  private handleRequest;
92
+ private handleNativeRouteRequest;
92
93
  }
93
94
  /**
94
95
  * Create express adapter.
@@ -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
@@ -1,11 +1,11 @@
1
1
  import { createServer as createHttpServer } from 'node:http';
2
2
  import { createServer as createHttpsServer } from 'node:https';
3
3
  import { Readable } from 'node:stream';
4
- import { URL } from 'node:url';
5
4
  import express from 'express';
6
5
  import { BadRequestException, createServerBackedHttpAdapterRealtimeCapability, createErrorResponse, HttpException, InternalServerErrorException, PayloadTooLargeException } from '@fluojs/http';
7
6
  import { attachFrameworkRequestNativeRouteHandoff, bindRawRequestNativeRouteHandoff, consumeRawRequestNativeRouteHandoff, isRoutePathNormalizationSensitive } from '@fluojs/http/internal';
8
7
  import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals } from '@fluojs/runtime/node';
8
+ import { cloneRequestHeaders, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createRequestSignal, normalizePrimaryContentType, parseQueryParamsFromSearch, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from '@fluojs/runtime/internal-node';
9
9
  import { parseMultipart } from '@fluojs/runtime/web';
10
10
  import { bootstrapHttpAdapterApplication, runHttpAdapterApplication } from '@fluojs/runtime/internal/http-adapter';
11
11
  import { dispatchWithRequestResponseFactory } from '@fluojs/runtime/internal/request-response-factory';
@@ -131,14 +131,12 @@ export class ExpressHttpApplicationAdapter {
131
131
  return;
132
132
  }
133
133
  const nativeMethod = request.method.toUpperCase();
134
- const requestPath = new URL(request.originalUrl || request.url || '/', 'http://localhost').pathname;
134
+ const requestPath = splitRawRequestUrl(request.originalUrl || request.url || '/').path;
135
135
  const descriptor = route.descriptorsByMethod[nativeMethod];
136
136
  const params = normalizeNativeRouteParams(request.params);
137
137
  if (descriptor && !isRoutePathNormalizationSensitive(requestPath) && !hasNativeRouteParamSeparators(params)) {
138
- bindRawRequestNativeRouteHandoff(request, {
139
- descriptor,
140
- params
141
- });
138
+ void this.handleNativeRouteRequest(descriptor, params, request, response);
139
+ return;
142
140
  }
143
141
  void this.handleRequest(request, response);
144
142
  });
@@ -154,6 +152,42 @@ export class ExpressHttpApplicationAdapter {
154
152
  rawResponse: response
155
153
  });
156
154
  }
155
+ async handleNativeRouteRequest(descriptor, params, request, response) {
156
+ const dispatcher = this.dispatcher;
157
+ if (!dispatcher?.dispatchNativeRoute) {
158
+ bindRawRequestNativeRouteHandoff(request, {
159
+ descriptor,
160
+ params
161
+ });
162
+ await this.handleRequest(request, response);
163
+ return;
164
+ }
165
+ const factory = this.requestResponseFactory;
166
+ const frameworkResponse = factory.createResponse(response, request);
167
+ const signal = factory.createRequestSignal(response);
168
+ try {
169
+ const frameworkRequest = attachFrameworkRequestNativeRouteHandoff(await factory.createRequest(request, signal), {
170
+ descriptor,
171
+ params
172
+ });
173
+ await factory.materializeRequest?.(frameworkRequest);
174
+ const handled = await dispatcher.dispatchNativeRoute({
175
+ descriptor,
176
+ params
177
+ }, frameworkRequest, frameworkResponse);
178
+ if (!handled && !frameworkResponse.committed) {
179
+ await dispatcher.dispatch(frameworkRequest, frameworkResponse);
180
+ }
181
+ if (!frameworkResponse.committed) {
182
+ await frameworkResponse.send(undefined);
183
+ }
184
+ } catch (error) {
185
+ if (signal.aborted || frameworkResponse.committed) {
186
+ return;
187
+ }
188
+ await factory.writeErrorResponse(error, frameworkResponse, factory.resolveRequestId(request));
189
+ }
190
+ }
157
191
  }
158
192
  function resolveDispatcherRouteDescriptors(dispatcher) {
159
193
  return dispatcher.describeRoutes?.() ?? [];
@@ -228,6 +262,9 @@ function createExpressRequestResponseFactory(multipartOptions, maxBodySize = DEF
228
262
  createResponse(response) {
229
263
  return createFrameworkResponse(response);
230
264
  },
265
+ async materializeRequest(request) {
266
+ await materializeFrameworkRequestBody(request);
267
+ },
231
268
  resolveRequestId(request) {
232
269
  return resolveRequestIdFromHeaders(request.headers);
233
270
  },
@@ -374,43 +411,40 @@ function createFrameworkResponseStream(response) {
374
411
  }
375
412
  async function createFrameworkRequest(request, signal, multipartOptions, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false) {
376
413
  const rawUrl = request.originalUrl || request.url || '/';
377
- const url = new URL(rawUrl, 'http://localhost');
378
- const headers = normalizeHeaders(request.headers);
379
- const contentType = readPrimaryHeaderValue(headers['content-type']);
380
- const isMultipart = typeof contentType === 'string' && contentType.includes('multipart/form-data');
381
- let body;
382
- let files;
383
- let rawBody;
384
- if (isMultipart) {
385
- const parsed = await parseMultipartRequest(request, {
386
- ...multipartOptions,
387
- maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
388
- });
389
- body = parsed.fields;
390
- files = parsed.files;
391
- } else {
414
+ const urlParts = splitRawRequestUrl(rawUrl);
415
+ const headers = normalizeHeaders(cloneRequestHeaders(request.headers));
416
+ const querySnapshot = snapshotSimpleQueryRecord(request.query);
417
+ const contentType = normalizePrimaryContentType(headers['content-type']);
418
+ const isMultipart = contentType === 'multipart/form-data';
419
+ let frameworkRequest;
420
+ const materializeBody = createMemoizedAsyncValue(async () => {
421
+ if (isMultipart) {
422
+ const parsed = await parseMultipartRequest(request, {
423
+ ...multipartOptions,
424
+ maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
425
+ });
426
+ frameworkRequest.body = parsed.fields;
427
+ frameworkRequest.files = parsed.files;
428
+ return;
429
+ }
392
430
  const bodyResult = await readRequestBody(request, headers['content-type'], maxBodySize, preserveRawBody);
393
- body = bodyResult.body;
394
- rawBody = bodyResult.rawBody;
395
- }
396
- const frameworkRequest = {
397
- body,
398
- cookies: parseCookieHeader(Array.isArray(headers.cookie) ? headers.cookie[0] : headers.cookie),
431
+ frameworkRequest.body = bodyResult.body;
432
+ if (bodyResult.rawBody) {
433
+ frameworkRequest.rawBody = bodyResult.rawBody;
434
+ }
435
+ });
436
+ frameworkRequest = createDeferredFrameworkRequestShell({
437
+ cookieHeader: headers.cookie,
399
438
  headers,
439
+ materializeBody,
400
440
  method: request.method,
401
- params: {},
402
- path: url.pathname,
403
- query: parseQueryParams(url.searchParams),
441
+ path: urlParts.path,
442
+ query: querySnapshot,
443
+ queryFactory: () => parseQueryParamsFromSearch(urlParts.search),
404
444
  raw: request,
405
445
  signal,
406
- url: url.pathname + url.search
407
- };
408
- if (files) {
409
- frameworkRequest.files = files;
410
- }
411
- if (rawBody) {
412
- frameworkRequest.rawBody = rawBody;
413
- }
446
+ url: urlParts.path + urlParts.search
447
+ });
414
448
  const nativeRouteHandoff = consumeRawRequestNativeRouteHandoff(request);
415
449
  return nativeRouteHandoff ? attachFrameworkRequestNativeRouteHandoff(frameworkRequest, nativeRouteHandoff) : frameworkRequest;
416
450
  }
@@ -446,7 +480,7 @@ async function parseMultipartRequest(request, options = {}) {
446
480
  body: Readable.toWeb(request),
447
481
  headers: normalizeHeaders(request.headers),
448
482
  method: request.method,
449
- url: new URL(request.url ?? '/', 'http://localhost').toString()
483
+ url: resolveAbsoluteRequestUrl(request.url)
450
484
  }, options);
451
485
  } catch (error) {
452
486
  if (isExpressMultipartTooLargeError(error)) {
@@ -503,57 +537,6 @@ function normalizeHeaders(headers) {
503
537
  }
504
538
  return normalized;
505
539
  }
506
- function parseQueryParams(searchParams) {
507
- const query = {};
508
- for (const [key, value] of searchParams.entries()) {
509
- const current = query[key];
510
- if (current === undefined) {
511
- query[key] = value;
512
- continue;
513
- }
514
- if (Array.isArray(current)) {
515
- current.push(value);
516
- continue;
517
- }
518
- query[key] = [current, value];
519
- }
520
- return query;
521
- }
522
- function parseCookieHeader(cookieHeader) {
523
- if (!cookieHeader) {
524
- return {};
525
- }
526
- return Object.fromEntries(cookieHeader.split(';').map(pair => pair.trim()).filter(Boolean).map(pair => {
527
- const index = pair.indexOf('=');
528
- if (index === -1) {
529
- return [pair.trim(), ''];
530
- }
531
- const rawValue = pair.slice(index + 1).trim();
532
- try {
533
- return [pair.slice(0, index).trim(), decodeURIComponent(rawValue)];
534
- } catch {
535
- return [pair.slice(0, index).trim(), rawValue];
536
- }
537
- }));
538
- }
539
- function createRequestSignal(response) {
540
- const controller = new AbortController();
541
- const abort = reason => {
542
- if (!controller.signal.aborted) {
543
- controller.abort(new Error(reason));
544
- }
545
- };
546
- response.once('close', () => {
547
- if (!response.writableEnded) {
548
- abort('Response closed before response commit.');
549
- }
550
- });
551
- return controller.signal;
552
- }
553
- function resolveRequestIdFromHeaders(headers) {
554
- const requestId = headers['x-request-id'] ?? headers['x-correlation-id'];
555
- return Array.isArray(requestId) ? requestId[0] : requestId;
556
- }
557
540
  function createExpressServer(httpsOptions, app) {
558
541
  return httpsOptions ? createHttpsServer(httpsOptions, app) : createHttpServer(app);
559
542
  }
@@ -724,8 +707,8 @@ async function readRequestBody(request, contentType, maxBodySize = DEFAULT_MAX_B
724
707
  rawBody: preserveRawBody ? rawBody : undefined
725
708
  };
726
709
  }
727
- const primaryContentType = readPrimaryHeaderValue(contentType);
728
- if (typeof primaryContentType === 'string' && primaryContentType.includes('application/json')) {
710
+ const primaryContentType = normalizePrimaryContentType(contentType);
711
+ if (primaryContentType === 'application/json') {
729
712
  try {
730
713
  return {
731
714
  body: JSON.parse(bodyText),
@@ -735,7 +718,7 @@ async function readRequestBody(request, contentType, maxBodySize = DEFAULT_MAX_B
735
718
  throw new BadRequestException('Request body contains invalid JSON.');
736
719
  }
737
720
  }
738
- if (typeof primaryContentType === 'string' && primaryContentType.includes('application/x-www-form-urlencoded')) {
721
+ if (primaryContentType === 'application/x-www-form-urlencoded') {
739
722
  return {
740
723
  body: parseUrlEncodedBody(bodyText),
741
724
  rawBody: preserveRawBody ? rawBody : undefined
@@ -754,11 +737,9 @@ function parseUrlEncodedBody(bodyText) {
754
737
  }
755
738
  return fields;
756
739
  }
757
- function readPrimaryHeaderValue(headerValue) {
758
- if (Array.isArray(headerValue)) {
759
- return headerValue[0];
760
- }
761
- return headerValue;
740
+ async function materializeFrameworkRequestBody(request) {
741
+ await request.materializeBody?.();
742
+ delete request.materializeBody;
762
743
  }
763
744
  function setMultiValue(target, key, value) {
764
745
  const existing = target[key];
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "platform",
9
9
  "server"
10
10
  ],
11
- "version": "1.0.0-beta.4",
11
+ "version": "1.0.0-beta.6",
12
12
  "private": false,
13
13
  "license": "MIT",
14
14
  "repository": {
@@ -36,8 +36,8 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "express": "^5.1.0",
39
- "@fluojs/http": "^1.0.0-beta.4",
40
- "@fluojs/runtime": "^1.0.0-beta.5"
39
+ "@fluojs/http": "^1.0.0-beta.7",
40
+ "@fluojs/runtime": "^1.0.0-beta.8"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/express": "^5.0.3",