@fluojs/http 1.1.2 → 3.0.0

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 (125) hide show
  1. package/README.ko.md +430 -15
  2. package/README.md +437 -15
  3. package/dist/access-log-observer.d.ts +77 -0
  4. package/dist/access-log-observer.d.ts.map +1 -0
  5. package/dist/access-log-observer.js +171 -0
  6. package/dist/adapter.d.ts +9 -0
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/adapter.js +15 -1
  9. package/dist/adapters/binding.d.ts.map +1 -1
  10. package/dist/adapters/binding.js +18 -16
  11. package/dist/adapters/dto-binding-plan.d.ts +2 -2
  12. package/dist/adapters/dto-binding-plan.d.ts.map +1 -1
  13. package/dist/adapters/dto-binding-plan.js +13 -4
  14. package/dist/adapters/dto-validation-adapter.d.ts +2 -1
  15. package/dist/adapters/dto-validation-adapter.d.ts.map +1 -1
  16. package/dist/adapters/dto-validation-adapter.js +2 -2
  17. package/dist/byte-range-response.d.ts +71 -0
  18. package/dist/byte-range-response.d.ts.map +1 -0
  19. package/dist/byte-range-response.js +347 -0
  20. package/dist/client-identity.d.ts +8 -4
  21. package/dist/client-identity.d.ts.map +1 -1
  22. package/dist/client-identity.js +12 -93
  23. package/dist/compiled-route-identity.d.ts +17 -0
  24. package/dist/compiled-route-identity.d.ts.map +1 -0
  25. package/dist/compiled-route-identity.js +29 -0
  26. package/dist/connection.d.ts +67 -0
  27. package/dist/connection.d.ts.map +1 -0
  28. package/dist/connection.js +480 -0
  29. package/dist/context/request-context-node-store.d.ts +6 -0
  30. package/dist/context/request-context-node-store.d.ts.map +1 -1
  31. package/dist/context/request-context-node-store.js +14 -0
  32. package/dist/context/request-context.d.ts +5 -5
  33. package/dist/context/request-context.d.ts.map +1 -1
  34. package/dist/context/request-context.js +8 -83
  35. package/dist/context/request-id.d.ts +10 -0
  36. package/dist/context/request-id.d.ts.map +1 -0
  37. package/dist/context/request-id.js +27 -0
  38. package/dist/context/sse.d.ts +13 -0
  39. package/dist/context/sse.d.ts.map +1 -1
  40. package/dist/context/sse.js +36 -6
  41. package/dist/cookie-helpers.d.ts +47 -0
  42. package/dist/cookie-helpers.d.ts.map +1 -0
  43. package/dist/cookie-helpers.js +166 -0
  44. package/dist/decorators.d.ts +52 -19
  45. package/dist/decorators.d.ts.map +1 -1
  46. package/dist/decorators.js +57 -10
  47. package/dist/dispatch/conditional-request-policy.d.ts +67 -0
  48. package/dist/dispatch/conditional-request-policy.d.ts.map +1 -0
  49. package/dist/dispatch/conditional-request-policy.js +297 -0
  50. package/dist/dispatch/dispatch-content-negotiation.d.ts +8 -0
  51. package/dist/dispatch/dispatch-content-negotiation.d.ts.map +1 -1
  52. package/dist/dispatch/dispatch-content-negotiation.js +89 -42
  53. package/dist/dispatch/dispatch-error-negotiation.d.ts +26 -0
  54. package/dist/dispatch/dispatch-error-negotiation.d.ts.map +1 -0
  55. package/dist/dispatch/dispatch-error-negotiation.js +110 -0
  56. package/dist/dispatch/dispatch-error-policy.d.ts +1 -10
  57. package/dist/dispatch/dispatch-error-policy.d.ts.map +1 -1
  58. package/dist/dispatch/dispatch-error-policy.js +1 -33
  59. package/dist/dispatch/dispatch-error-representation.d.ts +18 -0
  60. package/dist/dispatch/dispatch-error-representation.d.ts.map +1 -0
  61. package/dist/dispatch/dispatch-error-representation.js +125 -0
  62. package/dist/dispatch/dispatch-response-metadata.d.ts +10 -0
  63. package/dist/dispatch/dispatch-response-metadata.d.ts.map +1 -0
  64. package/dist/dispatch/dispatch-response-metadata.js +12 -0
  65. package/dist/dispatch/dispatch-response-policy.d.ts +33 -5
  66. package/dist/dispatch/dispatch-response-policy.d.ts.map +1 -1
  67. package/dist/dispatch/dispatch-response-policy.js +149 -18
  68. package/dist/dispatch/dispatcher.d.ts +5 -1
  69. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  70. package/dist/dispatch/dispatcher.js +178 -107
  71. package/dist/dispatch/fast-path/debug-visibility.d.ts.map +1 -1
  72. package/dist/dispatch/fast-path/debug-visibility.js +6 -3
  73. package/dist/dispatch/fast-path/dispatcher-state.d.ts +10 -0
  74. package/dist/dispatch/fast-path/dispatcher-state.d.ts.map +1 -0
  75. package/dist/dispatch/fast-path/dispatcher-state.js +52 -0
  76. package/dist/dispatch/fast-path/eligibility-checker.d.ts.map +1 -1
  77. package/dist/dispatch/fast-path/eligibility-checker.js +27 -15
  78. package/dist/dispatch/fast-path/eligibility.d.ts +16 -10
  79. package/dist/dispatch/fast-path/eligibility.d.ts.map +1 -1
  80. package/dist/dispatch/fast-path/eligibility.js +10 -4
  81. package/dist/dispatch/fast-path/fast-path-executor.d.ts +14 -1
  82. package/dist/dispatch/fast-path/fast-path-executor.d.ts.map +1 -1
  83. package/dist/dispatch/fast-path/fast-path-executor.js +23 -6
  84. package/dist/dispatch/request-abort.d.ts +22 -0
  85. package/dist/dispatch/request-abort.d.ts.map +1 -0
  86. package/dist/dispatch/request-abort.js +30 -0
  87. package/dist/dispatch/response-integration.d.ts +55 -0
  88. package/dist/dispatch/response-integration.d.ts.map +1 -0
  89. package/dist/dispatch/response-integration.js +57 -0
  90. package/dist/errors.d.ts +10 -0
  91. package/dist/errors.d.ts.map +1 -1
  92. package/dist/errors.js +19 -0
  93. package/dist/guards.d.ts +1 -1
  94. package/dist/guards.d.ts.map +1 -1
  95. package/dist/header-helpers.d.ts +59 -0
  96. package/dist/header-helpers.d.ts.map +1 -0
  97. package/dist/header-helpers.js +185 -0
  98. package/dist/index.d.ts +1 -16
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +4 -15
  101. package/dist/index.portable.d.ts +25 -0
  102. package/dist/index.portable.d.ts.map +1 -0
  103. package/dist/index.portable.js +21 -0
  104. package/dist/internal.d.ts +3 -1
  105. package/dist/internal.d.ts.map +1 -1
  106. package/dist/internal.js +2 -0
  107. package/dist/mapping.d.ts.map +1 -1
  108. package/dist/mapping.js +100 -15
  109. package/dist/middleware/correlation.d.ts +9 -1
  110. package/dist/middleware/correlation.d.ts.map +1 -1
  111. package/dist/middleware/correlation.js +25 -14
  112. package/dist/middleware/cors.d.ts.map +1 -1
  113. package/dist/middleware/cors.js +7 -8
  114. package/dist/middleware/middleware.d.ts +3 -3
  115. package/dist/middleware/middleware.d.ts.map +1 -1
  116. package/dist/middleware/rate-limit.d.ts +13 -2
  117. package/dist/middleware/rate-limit.d.ts.map +1 -1
  118. package/dist/middleware/rate-limit.js +1 -0
  119. package/dist/static-assets.d.ts +77 -0
  120. package/dist/static-assets.d.ts.map +1 -0
  121. package/dist/static-assets.js +270 -0
  122. package/dist/types.d.ts +225 -17
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/types.js +71 -1
  125. package/package.json +19 -9
@@ -1,15 +1,22 @@
1
1
  import { getCompiledDtoBindingPlan } from '../adapters/dto-binding-plan.js';
2
2
  import { createRequestContext, runWithRequestContext } from '../context/request-context.js';
3
- import { SseResponse, isSseMessage } from '../context/sse.js';
3
+ import { resolveRequestId } from '../context/request-id.js';
4
+ import { isSseMessage, SseResponse, waitForSseResponseCompletion } from '../context/sse.js';
4
5
  import { RequestAbortedError } from '../errors.js';
5
6
  import { runGuardChain } from '../guards.js';
6
7
  import { runInterceptorChain } from '../interceptors.js';
7
8
  import { isMiddlewareRouteConfig, matchRoutePattern, runMiddlewareChain } from '../middleware/middleware.js';
9
+ import { initializeCorrelationMiddlewareRequestContext } from '../middleware/correlation.js';
10
+ import { resolveConditionalRequest } from './conditional-request-policy.js';
11
+ import { isContentNegotiationNotAcceptableException } from './dispatch-content-negotiation.js';
8
12
  import { invokeControllerHandler } from './dispatch-handler-policy.js';
9
- import { resolveContentNegotiation, writeErrorResponse, writeSuccessResponse } from './dispatch-response-policy.js';
13
+ import { resolveResponsePolicy, resolveContentNegotiation, writeErrorResponse, writeSuccessResponse } from './dispatch-response-policy.js';
10
14
  import { matchHandlerOrThrow, updateRequestParams } from './dispatch-routing-policy.js';
15
+ import { createDispatcherFastPathState } from './fast-path/dispatcher-state.js';
16
+ import { addPathDebugHeader, createPathDebugInfo, executeFastPath, FAST_PATH_STATS_SYMBOL, shouldUseFastPathForRequest } from './fast-path/index.js';
11
17
  import { attachFrameworkRequestNativeRouteHandoff, readFrameworkRequestNativeRouteHandoff } from './native-route-handoff.js';
12
- import { compileFastPathEligibility, getHandlerFastPathEligibility, setHandlerFastPathEligibility, FAST_PATH_STATS_SYMBOL, addPathDebugHeader, createFastPathStats, createPathDebugInfo, executeFastPath, shouldUseFastPathForRequest } from './fast-path/index.js';
18
+ import { isRequestAborted } from './request-abort.js';
19
+ import { FRAMEWORK_RESPONSE_VALUE_FINALIZER } from './response-integration.js';
13
20
  export { FAST_PATH_ELIGIBILITY_SYMBOL, FAST_PATH_STATS_SYMBOL } from './fast-path/index.js';
14
21
 
15
22
  /** Type definition for a global HTTP error handler function. */
@@ -18,6 +25,26 @@ export { FAST_PATH_ELIGIBILITY_SYMBOL, FAST_PATH_STATS_SYMBOL } from './fast-pat
18
25
 
19
26
  const EMPTY_NATIVE_FAST_PATH_HANDLER_EXECUTION_PLANS = new WeakMap();
20
27
  const EMPTY_NATIVE_FAST_PATH_OBSERVERS = [];
28
+ class ManagedSseCleanupError extends Error {
29
+ cleanupError;
30
+ constructor(cleanupError) {
31
+ super('Managed SSE iterator cleanup failed.', {
32
+ cause: cleanupError
33
+ });
34
+ this.name = 'ManagedSseCleanupError';
35
+ this.cleanupError = cleanupError;
36
+ }
37
+ }
38
+ class ManagedSseOperationError extends Error {
39
+ operationError;
40
+ constructor(operationError) {
41
+ super('Managed SSE operation failed.', {
42
+ cause: operationError
43
+ });
44
+ this.name = 'ManagedSseOperationError';
45
+ this.operationError = operationError;
46
+ }
47
+ }
21
48
  function logDispatchFailure(logger, message, error) {
22
49
  if (logger) {
23
50
  logger.error(message, error, 'HttpDispatcher');
@@ -37,6 +64,7 @@ function createDispatchRequest(request) {
37
64
  return request.query;
38
65
  },
39
66
  body: request.body,
67
+ connection: request.connection,
40
68
  method: request.method,
41
69
  params: {
42
70
  ...request.params
@@ -60,41 +88,11 @@ function createDispatchRequest(request) {
60
88
  }
61
89
  return nativeRouteHandoff ? attachFrameworkRequestNativeRouteHandoff(dispatchRequest, nativeRouteHandoff) : dispatchRequest;
62
90
  }
63
- function cloneHandlerDescriptor(descriptor) {
64
- const cloned = {
65
- ...descriptor,
66
- metadata: {
67
- ...descriptor.metadata,
68
- moduleMiddleware: [...descriptor.metadata.moduleMiddleware],
69
- pathParams: [...descriptor.metadata.pathParams]
70
- },
71
- route: {
72
- ...descriptor.route,
73
- guards: descriptor.route.guards ? [...descriptor.route.guards] : undefined,
74
- headers: descriptor.route.headers?.map(header => ({
75
- ...header
76
- })),
77
- interceptors: descriptor.route.interceptors ? [...descriptor.route.interceptors] : undefined,
78
- produces: descriptor.route.produces ? [...descriptor.route.produces] : undefined,
79
- redirect: descriptor.route.redirect ? {
80
- ...descriptor.route.redirect
81
- } : undefined
82
- }
83
- };
84
- const eligibility = getHandlerFastPathEligibility(descriptor);
85
- if (eligibility) {
86
- setHandlerFastPathEligibility(cloned, eligibility);
87
- }
88
- return cloned;
89
- }
90
91
  function readRequestId(request) {
91
92
  if (request.requestId) {
92
93
  return request.requestId;
93
94
  }
94
- const raw = request.headers['x-request-id'] ?? request.headers['X-Request-Id'];
95
- const value = Array.isArray(raw) ? raw[0] : raw;
96
- const normalized = value?.trim();
97
- return normalized ? normalized : undefined;
95
+ return resolveRequestId(request, false);
98
96
  }
99
97
  function createDispatchContext(request, response, container, promoteOnContainerAccess) {
100
98
  const context = createRequestContext({
@@ -122,7 +120,7 @@ function createDispatchContext(request, response, container, promoteOnContainerA
122
120
  };
123
121
  const getWrappedContainer = () => {
124
122
  if (!wrappedContainer) {
125
- wrappedContainer = {
123
+ const wrapped = {
126
124
  async resolve(token) {
127
125
  const targetContainer = ensurePromoted();
128
126
  return targetContainer.resolve(token);
@@ -137,6 +135,16 @@ function createDispatchContext(request, response, container, promoteOnContainerA
137
135
  return activeContainer.dispose();
138
136
  }
139
137
  };
138
+ const presenceAwareContainer = activeContainer;
139
+ if (typeof presenceAwareContainer.has === 'function') {
140
+ Object.assign(wrapped, {
141
+ has(token) {
142
+ const targetContainer = activeContainer;
143
+ return targetContainer.has?.(token) ?? false;
144
+ }
145
+ });
146
+ }
147
+ wrappedContainer = wrapped;
140
148
  }
141
149
  return wrappedContainer;
142
150
  };
@@ -253,9 +261,6 @@ function ensureRequestNotAborted(request) {
253
261
  throw new RequestAbortedError();
254
262
  }
255
263
  }
256
- function isRequestAborted(request) {
257
- return request.isAborted?.() ?? request.signal?.aborted === true;
258
- }
259
264
  function isSseRoute(handler) {
260
265
  return handler.route.produces?.some(mediaType => mediaType.toLowerCase().startsWith('text/event-stream')) === true;
261
266
  }
@@ -345,16 +350,16 @@ function resolveManagedSseFrame(value) {
345
350
  options: {}
346
351
  };
347
352
  }
348
- function closeAsyncIteratorEventually(iterator) {
349
- void iterator.return?.().catch(() => undefined);
353
+ async function closeAsyncIterator(iterator) {
354
+ await iterator.return?.();
350
355
  }
351
- async function readManagedSseNext(request, stream, iterator) {
356
+ async function waitForManagedSseOperation(request, stream, operation) {
352
357
  const abort = createManagedSseStopPromise(request, stream);
353
358
  if (!abort) {
354
- return iterator.next();
359
+ return operation;
355
360
  }
356
361
  try {
357
- return await Promise.race([iterator.next(), abort.promise]);
362
+ return await Promise.race([operation, abort.promise]);
358
363
  } finally {
359
364
  abort.cleanup();
360
365
  }
@@ -363,17 +368,23 @@ async function writeManagedSseIterable(handler, requestContext, source) {
363
368
  if (!isSseRoute(handler)) {
364
369
  return false;
365
370
  }
366
- const sse = new SseResponse(requestContext);
367
371
  const stream = requestContext.response.stream;
368
372
  if (!stream) {
369
- return true;
373
+ throw new Error('Managed SSE requires adapter-provided response.stream support.');
370
374
  }
375
+ const sse = new SseResponse(requestContext);
371
376
  const iterator = source[Symbol.asyncIterator]();
377
+ let iteratorCleanup;
378
+ let iteratorCleanupFailure;
372
379
  try {
373
- while (!isRequestAborted(requestContext.request)) {
374
- const next = await readManagedSseNext(requestContext.request, stream, iterator);
380
+ while (true) {
381
+ if (isRequestAborted(requestContext.request)) {
382
+ iteratorCleanup ??= closeAsyncIterator(iterator);
383
+ break;
384
+ }
385
+ const next = await waitForManagedSseOperation(requestContext.request, stream, iterator.next());
375
386
  if (next === 'aborted') {
376
- closeAsyncIteratorEventually(iterator);
387
+ iteratorCleanup ??= closeAsyncIterator(iterator);
377
388
  break;
378
389
  }
379
390
  if (next.done === true) {
@@ -381,12 +392,28 @@ async function writeManagedSseIterable(handler, requestContext, source) {
381
392
  }
382
393
  const frame = resolveManagedSseFrame(next.value);
383
394
  const accepted = sse.send(frame.data, frame.options);
384
- if (!accepted) {
385
- await requestContext.response.stream?.waitForDrain?.();
395
+ if (!accepted && stream.waitForDrain) {
396
+ const drain = await waitForManagedSseOperation(requestContext.request, stream, stream.waitForDrain());
397
+ if (drain === 'aborted') {
398
+ iteratorCleanup ??= closeAsyncIterator(iterator);
399
+ break;
400
+ }
386
401
  }
387
402
  }
403
+ } catch (error) {
404
+ throw new ManagedSseOperationError(error);
388
405
  } finally {
389
406
  sse.close();
407
+ if (iteratorCleanup) {
408
+ try {
409
+ await iteratorCleanup;
410
+ } catch (error) {
411
+ iteratorCleanupFailure = new ManagedSseCleanupError(error);
412
+ }
413
+ }
414
+ }
415
+ if (iteratorCleanupFailure) {
416
+ throw iteratorCleanupFailure;
390
417
  }
391
418
  return true;
392
419
  }
@@ -402,19 +429,6 @@ function resolveFastPathHandlerRuntimeCache(handler, cache) {
402
429
  cache.set(handler, compiled);
403
430
  return compiled;
404
431
  }
405
- function resolveFastPathController(handler, controllerContainer, runtimeCache) {
406
- if (runtimeCache.controller) {
407
- return runtimeCache.controller;
408
- }
409
- runtimeCache.controllerPromise ??= controllerContainer.resolve(handler.controllerToken).then(controller => {
410
- runtimeCache.controller = controller;
411
- return controller;
412
- });
413
- return runtimeCache.controllerPromise;
414
- }
415
- function isPromiseLike(value) {
416
- return typeof value === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
417
- }
418
432
  function isRequestObserver(value) {
419
433
  return typeof value === 'object' && value !== null;
420
434
  }
@@ -424,24 +438,21 @@ async function resolveRequestObserver(definition, requestContext) {
424
438
  }
425
439
  return requestContext.container.resolve(definition);
426
440
  }
427
- async function notifyObservers(observers, requestContext, callback, handler) {
441
+ async function notifyObserversSafely(observers, requestContext, callback, logger, handler) {
442
+ if (observers.length === 0) {
443
+ return;
444
+ }
428
445
  const context = {
429
446
  handler,
430
447
  requestContext
431
448
  };
432
449
  for (const definition of observers) {
433
- const observer = await resolveRequestObserver(definition, requestContext);
434
- await callback(observer, context);
435
- }
436
- }
437
- async function notifyObserversSafely(observers, requestContext, callback, logger, handler) {
438
- if (observers.length === 0) {
439
- return;
440
- }
441
- try {
442
- await notifyObservers(observers, requestContext, callback, handler);
443
- } catch (error) {
444
- logDispatchFailure(logger, 'Request observer threw an unhandled error.', error);
450
+ try {
451
+ const observer = await resolveRequestObserver(definition, requestContext);
452
+ await callback(observer, context);
453
+ } catch (error) {
454
+ logDispatchFailure(logger, 'Request observer threw an unhandled error.', error);
455
+ }
445
456
  }
446
457
  }
447
458
  function mergeInterceptors(globalInterceptors, routeInterceptors) {
@@ -453,7 +464,7 @@ function mergeInterceptors(globalInterceptors, routeInterceptors) {
453
464
  }
454
465
  return [...globalInterceptors, ...routeInterceptors];
455
466
  }
456
- async function dispatchMatchedHandler(handler, executionPlan, requestContext, controllerContainer, observers, contentNegotiation, binder, logger) {
467
+ async function dispatchMatchedHandler(handler, executionPlan, requestContext, controllerContainer, contentNegotiation, binder, conditionalRequest) {
457
468
  const routeGuards = executionPlan.routeGuards;
458
469
  if (routeGuards.length > 0) {
459
470
  const guardContext = {
@@ -465,19 +476,47 @@ async function dispatchMatchedHandler(handler, executionPlan, requestContext, co
465
476
  if (requestContext.response.committed) {
466
477
  return;
467
478
  }
479
+ if (contentNegotiation && handler.route.produces?.length && handler.route.redirect === undefined && typeof requestContext.metadata[FRAMEWORK_RESPONSE_VALUE_FINALIZER] !== 'function') {
480
+ resolveResponsePolicy(handler, requestContext.request, contentNegotiation);
481
+ }
482
+ let conditionalOutcome;
483
+ let conditionalValidators;
484
+ if (conditionalRequest) {
485
+ const resolved = await resolveConditionalRequest(conditionalRequest, {
486
+ handler,
487
+ request: requestContext.request
488
+ });
489
+ conditionalValidators = resolved.validators;
490
+ if (resolved.outcome !== 'proceed') {
491
+ conditionalOutcome = resolved.outcome;
492
+ }
493
+ }
494
+ if (conditionalOutcome !== undefined && !requiresResultFirstConditionalClassification(handler, requestContext)) {
495
+ await writeSuccessResponse(handler, requestContext.request, requestContext.response, undefined, contentNegotiation, requestContext, conditionalValidators, conditionalOutcome);
496
+ return {
497
+ result: undefined
498
+ };
499
+ }
468
500
  const result = executionPlan.mergedInterceptors.length === 0 ? await invokeControllerHandler(handler, requestContext, binder, controllerContainer) : await runInterceptorChain(executionPlan.mergedInterceptors, {
469
501
  handler,
470
502
  requestContext
471
503
  }, async () => invokeControllerHandler(handler, requestContext, binder, controllerContainer));
472
504
  ensureRequestNotAborted(requestContext.request);
473
- if (isAsyncIterable(result) && (await writeManagedSseIterable(handler, requestContext, result))) {
505
+ if (conditionalOutcome === undefined && result instanceof SseResponse) {
506
+ await waitForSseResponseCompletion(result);
507
+ ensureRequestNotAborted(requestContext.request);
508
+ } else if (conditionalOutcome === undefined && isAsyncIterable(result) && (await writeManagedSseIterable(handler, requestContext, result))) {
474
509
  // Managed SSE streams are already committed and closed by writeManagedSseIterable.
475
- } else if (!(result instanceof SseResponse) && !requestContext.response.committed) {
476
- await writeSuccessResponse(handler, requestContext.request, requestContext.response, result, contentNegotiation);
510
+ } else if (!requestContext.response.committed) {
511
+ await writeSuccessResponse(handler, requestContext.request, requestContext.response, result, contentNegotiation, requestContext, conditionalValidators, conditionalOutcome);
477
512
  }
478
- await notifyObserversSafely(observers, requestContext, async (observer, context) => {
479
- await observer.onRequestSuccess?.(context, result);
480
- }, logger, handler);
513
+ return {
514
+ result
515
+ };
516
+ }
517
+ function requiresResultFirstConditionalClassification(handler, requestContext) {
518
+ const method = requestContext.request.method.toUpperCase();
519
+ return (method === 'GET' || method === 'HEAD') && (handler.route.redirect !== undefined || typeof requestContext.metadata[FRAMEWORK_RESPONSE_VALUE_FINALIZER] === 'function');
481
520
  }
482
521
  function resolveHandlerExecutionPlan(handler, executionPlans, options) {
483
522
  const cached = executionPlans.get(handler);
@@ -488,8 +527,11 @@ function resolveHandlerExecutionPlan(handler, executionPlans, options) {
488
527
  executionPlans.set(handler, compiled);
489
528
  return compiled;
490
529
  }
491
- async function dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathRuntimeCache) {
492
- const eligibility = getHandlerFastPathEligibility(match.descriptor);
530
+ async function dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathState, fastPathRuntimeCache) {
531
+ if (options.conditionalRequest || (options.observers?.length ?? 0) > 0) {
532
+ return false;
533
+ }
534
+ const eligibility = fastPathState.getEligibility(match.descriptor);
493
535
  if (!shouldUseFastPathForRequest(eligibility, request)) {
494
536
  return false;
495
537
  }
@@ -511,6 +553,7 @@ async function dispatchNativeFastRoute(match, request, response, options, conten
511
553
  phaseContext = {
512
554
  contentNegotiation,
513
555
  dispatchScope,
556
+ fastPathState,
514
557
  fastPathRuntimeCache,
515
558
  handlerExecutionPlans: EMPTY_NATIVE_FAST_PATH_HANDLER_EXECUTION_PLANS,
516
559
  observers: EMPTY_NATIVE_FAST_PATH_OBSERVERS,
@@ -555,6 +598,11 @@ async function notifyHandlerMatched(context, descriptor) {
555
598
  await observer.onHandlerMatched?.(observationContext);
556
599
  }, context.options.logger, descriptor);
557
600
  }
601
+ async function notifyRequestSuccess(context, result) {
602
+ await notifyObserversSafely(context.observers, context.requestContext, async (observer, observationContext) => {
603
+ await observer.onRequestSuccess?.(observationContext, result);
604
+ }, context.options.logger, context.matchedHandler);
605
+ }
558
606
  async function notifyRequestError(context, error) {
559
607
  await notifyObserversSafely(context.observers, context.requestContext, async (observer, observationContext) => {
560
608
  await observer.onRequestError?.(observationContext, error);
@@ -566,7 +614,7 @@ async function notifyRequestFinish(context) {
566
614
  }, context.options.logger, context.matchedHandler);
567
615
  }
568
616
  async function tryFastPathExecution(handler, context) {
569
- const eligibility = getHandlerFastPathEligibility(handler);
617
+ const eligibility = context.fastPathState.getEligibility(handler);
570
618
  if (!eligibility || eligibility.executionPath !== 'fast') {
571
619
  return false;
572
620
  }
@@ -574,8 +622,7 @@ async function tryFastPathExecution(handler, context) {
574
622
  ensureRequestScope(context);
575
623
  }
576
624
  const runtimeCache = resolveFastPathHandlerRuntimeCache(handler, context.fastPathRuntimeCache);
577
- const controllerOrPromise = resolveFastPathController(handler, context.dispatchScope.container, runtimeCache);
578
- const controller = isPromiseLike(controllerOrPromise) ? await controllerOrPromise : controllerOrPromise;
625
+ const controller = await context.dispatchScope.container.resolve(handler.controllerToken);
579
626
  const fastPathResult = await executeFastPath({
580
627
  binder: context.options.binder,
581
628
  contentNegotiation: context.contentNegotiation,
@@ -597,6 +644,7 @@ async function tryFastPathExecution(handler, context) {
597
644
  }
598
645
  async function runDispatchPipeline(context) {
599
646
  ensureRequestNotAborted(context.requestContext.request);
647
+ let handlerResult;
600
648
  const appMiddlewareContext = {
601
649
  request: context.requestContext.request,
602
650
  requestContext: context.requestContext,
@@ -609,12 +657,12 @@ async function runDispatchPipeline(context) {
609
657
  const match = readFrameworkRequestNativeRouteHandoff(appMiddlewareContext.request) ?? matchHandlerOrThrow(context.options.handlerMapping, appMiddlewareContext.request);
610
658
  context.matchedHandler = match.descriptor;
611
659
  updateRequestParams(context.requestContext, match.params);
612
- const eligibility = getHandlerFastPathEligibility(match.descriptor);
660
+ const eligibility = context.fastPathState.getEligibility(match.descriptor);
613
661
  if (context.options.fastPathDebugHeaders === true && eligibility && !context.response.committed) {
614
662
  const debugInfo = createPathDebugInfo(eligibility);
615
663
  addPathDebugHeader(context.response.setHeader.bind(context.response), debugInfo);
616
664
  }
617
- if (shouldUseFastPathForRequest(eligibility, appMiddlewareContext.request)) {
665
+ if (!context.options.conditionalRequest && shouldUseFastPathForRequest(eligibility, appMiddlewareContext.request)) {
618
666
  const fastPathSuccess = await tryFastPathExecution(match.descriptor, context);
619
667
  if (fastPathSuccess) {
620
668
  return;
@@ -631,26 +679,48 @@ async function runDispatchPipeline(context) {
631
679
  response: context.response
632
680
  };
633
681
  await runMiddlewareChain(match.descriptor.metadata.moduleMiddleware ?? [], moduleMiddlewareContext, async () => {
634
- await dispatchMatchedHandler(match.descriptor, executionPlan, context.requestContext, context.dispatchScope.container, context.observers, context.contentNegotiation, context.options.binder, context.options.logger);
682
+ handlerResult = await dispatchMatchedHandler(match.descriptor, executionPlan, context.requestContext, context.dispatchScope.container, context.contentNegotiation, context.options.binder, context.options.conditionalRequest);
635
683
  });
636
684
  };
637
685
  const appMiddleware = context.options.appMiddleware ?? [];
638
686
  if (appMiddleware.length === 0) {
639
687
  await dispatchMatchedRoute();
640
- return;
688
+ } else {
689
+ await runMiddlewareChain(appMiddleware, appMiddlewareContext, dispatchMatchedRoute);
690
+ }
691
+ if (handlerResult) {
692
+ await notifyRequestSuccess(context, handlerResult.result);
641
693
  }
642
- await runMiddlewareChain(appMiddleware, appMiddlewareContext, dispatchMatchedRoute);
643
694
  }
644
695
  async function handleDispatchError(context, error) {
645
- if (error instanceof RequestAbortedError || isRequestAborted(context.requestContext.request)) {
696
+ const managedSseCleanupFailed = error instanceof ManagedSseCleanupError;
697
+ const managedSseOperationFailed = error instanceof ManagedSseOperationError;
698
+ const dispatchError = managedSseCleanupFailed ? error.cleanupError : managedSseOperationFailed ? error.operationError : error;
699
+ if (!managedSseCleanupFailed && !managedSseOperationFailed && (error instanceof RequestAbortedError || isRequestAborted(context.requestContext.request))) {
646
700
  return;
647
701
  }
648
- await notifyRequestError(context, error);
649
- const handled = await context.options.onError?.(error, context.requestContext.request, context.response, context.requestContext.requestId);
702
+ await notifyRequestError(context, dispatchError);
703
+ const handled = await context.options.onError?.(dispatchError, context.requestContext.request, context.response, context.requestContext.requestId);
650
704
  if (handled) {
651
705
  return;
652
706
  }
653
- await writeErrorResponse(error, context.response, context.requestContext.requestId);
707
+ if (managedSseCleanupFailed) {
708
+ logDispatchFailure(context.options.logger, 'Managed SSE iterator cleanup threw an error.', dispatchError);
709
+ }
710
+ await writeErrorResponse(dispatchError, context.requestContext, {
711
+ ...(context.matchedHandler === undefined ? {} : {
712
+ handler: context.matchedHandler
713
+ }),
714
+ ...(context.options.logger === undefined ? {} : {
715
+ logger: context.options.logger
716
+ }),
717
+ ...(context.options.errorRepresentation === undefined ? {} : {
718
+ representation: context.options.errorRepresentation
719
+ }),
720
+ ...(isContentNegotiationNotAcceptableException(dispatchError) ? {
721
+ varyAccept: true
722
+ } : {})
723
+ });
654
724
  }
655
725
 
656
726
  /**
@@ -667,22 +737,16 @@ export function createDispatcher(options) {
667
737
  const fastPathRuntimeCache = new WeakMap();
668
738
  const handlerExecutionPlans = new WeakMap();
669
739
  const adapter = options.adapter ?? 'default';
670
- const fastPathEligibilities = [];
740
+ const fastPathState = createDispatcherFastPathState(options.handlerMapping.descriptors, options, adapter);
671
741
  for (const descriptor of options.handlerMapping.descriptors) {
672
742
  handlerExecutionPlans.set(descriptor, compileHandlerExecutionPlan(descriptor, options));
673
- const {
674
- eligibility
675
- } = compileFastPathEligibility(descriptor, options, adapter);
676
- setHandlerFastPathEligibility(descriptor, eligibility);
677
- fastPathEligibilities.push(eligibility);
678
743
  }
679
- const fastPathStats = createFastPathStats(fastPathEligibilities);
680
744
  const dispatcher = {
681
745
  describeRoutes() {
682
- return options.handlerMapping.descriptors.map(descriptor => cloneHandlerDescriptor(descriptor));
746
+ return fastPathState.describeRoutes();
683
747
  },
684
748
  async dispatchNativeRoute(match, request, response) {
685
- return dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathRuntimeCache);
749
+ return dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathState, fastPathRuntimeCache);
686
750
  },
687
751
  async dispatch(request, response) {
688
752
  const dispatchRequest = createDispatchRequest(request);
@@ -699,6 +763,7 @@ export function createDispatcher(options) {
699
763
  phaseContext = {
700
764
  contentNegotiation,
701
765
  dispatchScope,
766
+ fastPathState,
702
767
  fastPathRuntimeCache,
703
768
  handlerExecutionPlans,
704
769
  observers,
@@ -706,6 +771,7 @@ export function createDispatcher(options) {
706
771
  requestContext,
707
772
  response
708
773
  };
774
+ initializeCorrelationMiddlewareRequestContext(options.appMiddleware ?? [], requestContext);
709
775
  await runWithRequestContext(phaseContext.requestContext, async () => {
710
776
  try {
711
777
  if (observers.length > 0) {
@@ -733,7 +799,12 @@ export function createDispatcher(options) {
733
799
  });
734
800
  }
735
801
  };
736
- dispatcher[FAST_PATH_STATS_SYMBOL] = fastPathStats;
802
+ Object.defineProperty(dispatcher, FAST_PATH_STATS_SYMBOL, {
803
+ configurable: false,
804
+ enumerable: false,
805
+ value: fastPathState.stats,
806
+ writable: false
807
+ });
737
808
  return dispatcher;
738
809
  }
739
810
 
@@ -1 +1 @@
1
- {"version":3,"file":"debug-visibility.d.ts","sourceRoot":"","sources":["../../../src/dispatch/fast-path/debug-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAI3E,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,GAAG,aAAa,CAMnF;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,EAChD,IAAI,EAAE,aAAa,GAClB,IAAI,CAMN;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,GAAG,aAAa,CAShG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAuBhE"}
1
+ {"version":3,"file":"debug-visibility.d.ts","sourceRoot":"","sources":["../../../src/dispatch/fast-path/debug-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAI3E,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,GAAG,aAAa,CAMnF;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,EAChD,IAAI,EAAE,aAAa,GAClB,IAAI,CAMN;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,GAAG,aAAa,CAUhG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAuBhE"}
@@ -12,12 +12,15 @@ export function addPathDebugHeader(setHeader, info) {
12
12
  }
13
13
  export function createFastPathStats(eligibilities) {
14
14
  const fastPathRoutes = eligibilities.filter(e => e.executionPath === 'fast').length;
15
- return {
15
+ const routes = Object.freeze(eligibilities.map(eligibility => Object.freeze({
16
+ ...eligibility
17
+ })));
18
+ return Object.freeze({
16
19
  fastPathRoutes,
17
20
  fullPathRoutes: eligibilities.length - fastPathRoutes,
18
- routes: eligibilities,
21
+ routes,
19
22
  totalRoutes: eligibilities.length
20
- };
23
+ });
21
24
  }
22
25
 
23
26
  /**
@@ -0,0 +1,10 @@
1
+ import type { HandlerDescriptor } from '../../types.js';
2
+ import type { CreateDispatcherOptions } from '../dispatcher.js';
3
+ import type { FastPathEligibility, FastPathStats } from './eligibility.js';
4
+ export interface DispatcherFastPathState {
5
+ readonly stats: FastPathStats;
6
+ describeRoutes(): readonly HandlerDescriptor[];
7
+ getEligibility(descriptor: HandlerDescriptor): FastPathEligibility | undefined;
8
+ }
9
+ export declare function createDispatcherFastPathState(descriptors: readonly HandlerDescriptor[], options: CreateDispatcherOptions, adapter: string): DispatcherFastPathState;
10
+ //# sourceMappingURL=dispatcher-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher-state.d.ts","sourceRoot":"","sources":["../../../src/dispatch/fast-path/dispatcher-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE3E,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,cAAc,IAAI,SAAS,iBAAiB,EAAE,CAAC;IAC/C,cAAc,CAAC,UAAU,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,SAAS,CAAC;CAChF;AAqBD,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,SAAS,iBAAiB,EAAE,EACzC,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,MAAM,GACd,uBAAuB,CA6BzB"}
@@ -0,0 +1,52 @@
1
+ import { createFastPathStats } from './debug-visibility.js';
2
+ import { compileFastPathEligibility, setHandlerFastPathEligibility } from './eligibility-checker.js';
3
+ function cloneHandlerDescriptor(descriptor) {
4
+ return {
5
+ ...descriptor,
6
+ metadata: {
7
+ ...descriptor.metadata,
8
+ moduleMiddleware: [...descriptor.metadata.moduleMiddleware],
9
+ pathParams: [...descriptor.metadata.pathParams]
10
+ },
11
+ route: {
12
+ ...descriptor.route,
13
+ guards: descriptor.route.guards ? [...descriptor.route.guards] : undefined,
14
+ headers: descriptor.route.headers?.map(header => ({
15
+ ...header
16
+ })),
17
+ interceptors: descriptor.route.interceptors ? [...descriptor.route.interceptors] : undefined,
18
+ produces: descriptor.route.produces ? [...descriptor.route.produces] : undefined,
19
+ redirect: descriptor.route.redirect ? {
20
+ ...descriptor.route.redirect
21
+ } : undefined
22
+ }
23
+ };
24
+ }
25
+ export function createDispatcherFastPathState(descriptors, options, adapter) {
26
+ const eligibilities = new WeakMap();
27
+ const compiledEligibilities = [];
28
+ for (const descriptor of descriptors) {
29
+ const {
30
+ eligibility
31
+ } = compileFastPathEligibility(descriptor, options, adapter);
32
+ eligibilities.set(descriptor, eligibility);
33
+ compiledEligibilities.push(eligibility);
34
+ }
35
+ return Object.freeze({
36
+ describeRoutes() {
37
+ return descriptors.map(descriptor => {
38
+ const cloned = cloneHandlerDescriptor(descriptor);
39
+ const eligibility = eligibilities.get(descriptor);
40
+ if (eligibility) {
41
+ setHandlerFastPathEligibility(cloned, eligibility);
42
+ eligibilities.set(cloned, eligibility);
43
+ }
44
+ return cloned;
45
+ });
46
+ },
47
+ getEligibility(descriptor) {
48
+ return eligibilities.get(descriptor);
49
+ },
50
+ stats: createFastPathStats(compiledEligibilities)
51
+ });
52
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"eligibility-checker.d.ts","sourceRoot":"","sources":["../../../src/dispatch/fast-path/eligibility-checker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EAElB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,mBAAmB,EAAgC,MAAM,kBAAkB,CAAC;AAM1F,UAAU,uBAAuB;IAC/B,WAAW,EAAE,mBAAmB,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;CACrB;AA0DD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,MAAM,GACd,uBAAuB,CAiEzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,iBAAiB,GACzB,mBAAmB,GAAG,SAAS,CAIjC;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,iBAAiB,EAC1B,WAAW,EAAE,mBAAmB,GAC/B,IAAI,CAGN;AAED,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,SAAS,CAAC;CAC1B;AAED,oEAAoE;AACpE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
1
+ {"version":3,"file":"eligibility-checker.d.ts","sourceRoot":"","sources":["../../../src/dispatch/fast-path/eligibility-checker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EAElB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,KAAK,mBAAmB,EAAgC,MAAM,kBAAkB,CAAC;AAM1F,UAAU,uBAAuB;IAC/B,WAAW,EAAE,mBAAmB,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;CACrB;AA0DD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,MAAM,GACd,uBAAuB,CAsEzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,iBAAiB,GACzB,mBAAmB,GAAG,SAAS,CAIjC;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,iBAAiB,EAC1B,WAAW,EAAE,mBAAmB,GAC/B,IAAI,CAON;AAED,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,SAAS,CAAC;CAC1B;AAED,oEAAoE;AACpE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}