@fluojs/http 2.0.1 → 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 +417 -16
  2. package/README.md +424 -16
  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 +30 -3
  66. package/dist/dispatch/dispatch-response-policy.d.ts.map +1 -1
  67. package/dist/dispatch/dispatch-response-policy.js +104 -15
  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 +140 -100
  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 +1 -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 +8 -5
  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 +5 -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 +209 -16
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/types.js +69 -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. */
@@ -28,6 +35,16 @@ class ManagedSseCleanupError extends Error {
28
35
  this.cleanupError = cleanupError;
29
36
  }
30
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
+ }
31
48
  function logDispatchFailure(logger, message, error) {
32
49
  if (logger) {
33
50
  logger.error(message, error, 'HttpDispatcher');
@@ -47,6 +64,7 @@ function createDispatchRequest(request) {
47
64
  return request.query;
48
65
  },
49
66
  body: request.body,
67
+ connection: request.connection,
50
68
  method: request.method,
51
69
  params: {
52
70
  ...request.params
@@ -70,41 +88,11 @@ function createDispatchRequest(request) {
70
88
  }
71
89
  return nativeRouteHandoff ? attachFrameworkRequestNativeRouteHandoff(dispatchRequest, nativeRouteHandoff) : dispatchRequest;
72
90
  }
73
- function cloneHandlerDescriptor(descriptor) {
74
- const cloned = {
75
- ...descriptor,
76
- metadata: {
77
- ...descriptor.metadata,
78
- moduleMiddleware: [...descriptor.metadata.moduleMiddleware],
79
- pathParams: [...descriptor.metadata.pathParams]
80
- },
81
- route: {
82
- ...descriptor.route,
83
- guards: descriptor.route.guards ? [...descriptor.route.guards] : undefined,
84
- headers: descriptor.route.headers?.map(header => ({
85
- ...header
86
- })),
87
- interceptors: descriptor.route.interceptors ? [...descriptor.route.interceptors] : undefined,
88
- produces: descriptor.route.produces ? [...descriptor.route.produces] : undefined,
89
- redirect: descriptor.route.redirect ? {
90
- ...descriptor.route.redirect
91
- } : undefined
92
- }
93
- };
94
- const eligibility = getHandlerFastPathEligibility(descriptor);
95
- if (eligibility) {
96
- setHandlerFastPathEligibility(cloned, eligibility);
97
- }
98
- return cloned;
99
- }
100
91
  function readRequestId(request) {
101
92
  if (request.requestId) {
102
93
  return request.requestId;
103
94
  }
104
- const raw = request.headers['x-request-id'] ?? request.headers['X-Request-Id'];
105
- const value = Array.isArray(raw) ? raw[0] : raw;
106
- const normalized = value?.trim();
107
- return normalized ? normalized : undefined;
95
+ return resolveRequestId(request, false);
108
96
  }
109
97
  function createDispatchContext(request, response, container, promoteOnContainerAccess) {
110
98
  const context = createRequestContext({
@@ -132,7 +120,7 @@ function createDispatchContext(request, response, container, promoteOnContainerA
132
120
  };
133
121
  const getWrappedContainer = () => {
134
122
  if (!wrappedContainer) {
135
- wrappedContainer = {
123
+ const wrapped = {
136
124
  async resolve(token) {
137
125
  const targetContainer = ensurePromoted();
138
126
  return targetContainer.resolve(token);
@@ -147,6 +135,16 @@ function createDispatchContext(request, response, container, promoteOnContainerA
147
135
  return activeContainer.dispose();
148
136
  }
149
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;
150
148
  }
151
149
  return wrappedContainer;
152
150
  };
@@ -263,9 +261,6 @@ function ensureRequestNotAborted(request) {
263
261
  throw new RequestAbortedError();
264
262
  }
265
263
  }
266
- function isRequestAborted(request) {
267
- return request.isAborted?.() ?? request.signal?.aborted === true;
268
- }
269
264
  function isSseRoute(handler) {
270
265
  return handler.route.produces?.some(mediaType => mediaType.toLowerCase().startsWith('text/event-stream')) === true;
271
266
  }
@@ -358,13 +353,13 @@ function resolveManagedSseFrame(value) {
358
353
  async function closeAsyncIterator(iterator) {
359
354
  await iterator.return?.();
360
355
  }
361
- async function readManagedSseNext(request, stream, iterator) {
356
+ async function waitForManagedSseOperation(request, stream, operation) {
362
357
  const abort = createManagedSseStopPromise(request, stream);
363
358
  if (!abort) {
364
- return iterator.next();
359
+ return operation;
365
360
  }
366
361
  try {
367
- return await Promise.race([iterator.next(), abort.promise]);
362
+ return await Promise.race([operation, abort.promise]);
368
363
  } finally {
369
364
  abort.cleanup();
370
365
  }
@@ -387,7 +382,7 @@ async function writeManagedSseIterable(handler, requestContext, source) {
387
382
  iteratorCleanup ??= closeAsyncIterator(iterator);
388
383
  break;
389
384
  }
390
- const next = await readManagedSseNext(requestContext.request, stream, iterator);
385
+ const next = await waitForManagedSseOperation(requestContext.request, stream, iterator.next());
391
386
  if (next === 'aborted') {
392
387
  iteratorCleanup ??= closeAsyncIterator(iterator);
393
388
  break;
@@ -397,10 +392,16 @@ async function writeManagedSseIterable(handler, requestContext, source) {
397
392
  }
398
393
  const frame = resolveManagedSseFrame(next.value);
399
394
  const accepted = sse.send(frame.data, frame.options);
400
- if (!accepted) {
401
- 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
+ }
402
401
  }
403
402
  }
403
+ } catch (error) {
404
+ throw new ManagedSseOperationError(error);
404
405
  } finally {
405
406
  sse.close();
406
407
  if (iteratorCleanup) {
@@ -428,19 +429,6 @@ function resolveFastPathHandlerRuntimeCache(handler, cache) {
428
429
  cache.set(handler, compiled);
429
430
  return compiled;
430
431
  }
431
- function resolveFastPathController(handler, controllerContainer, runtimeCache) {
432
- if (runtimeCache.controller) {
433
- return runtimeCache.controller;
434
- }
435
- runtimeCache.controllerPromise ??= controllerContainer.resolve(handler.controllerToken).then(controller => {
436
- runtimeCache.controller = controller;
437
- return controller;
438
- });
439
- return runtimeCache.controllerPromise;
440
- }
441
- function isPromiseLike(value) {
442
- return typeof value === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
443
- }
444
432
  function isRequestObserver(value) {
445
433
  return typeof value === 'object' && value !== null;
446
434
  }
@@ -450,24 +438,21 @@ async function resolveRequestObserver(definition, requestContext) {
450
438
  }
451
439
  return requestContext.container.resolve(definition);
452
440
  }
453
- async function notifyObservers(observers, requestContext, callback, handler) {
441
+ async function notifyObserversSafely(observers, requestContext, callback, logger, handler) {
442
+ if (observers.length === 0) {
443
+ return;
444
+ }
454
445
  const context = {
455
446
  handler,
456
447
  requestContext
457
448
  };
458
449
  for (const definition of observers) {
459
- const observer = await resolveRequestObserver(definition, requestContext);
460
- await callback(observer, context);
461
- }
462
- }
463
- async function notifyObserversSafely(observers, requestContext, callback, logger, handler) {
464
- if (observers.length === 0) {
465
- return;
466
- }
467
- try {
468
- await notifyObservers(observers, requestContext, callback, handler);
469
- } catch (error) {
470
- 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
+ }
471
456
  }
472
457
  }
473
458
  function mergeInterceptors(globalInterceptors, routeInterceptors) {
@@ -479,7 +464,7 @@ function mergeInterceptors(globalInterceptors, routeInterceptors) {
479
464
  }
480
465
  return [...globalInterceptors, ...routeInterceptors];
481
466
  }
482
- async function dispatchMatchedHandler(handler, executionPlan, requestContext, controllerContainer, observers, contentNegotiation, binder, logger) {
467
+ async function dispatchMatchedHandler(handler, executionPlan, requestContext, controllerContainer, contentNegotiation, binder, conditionalRequest) {
483
468
  const routeGuards = executionPlan.routeGuards;
484
469
  if (routeGuards.length > 0) {
485
470
  const guardContext = {
@@ -491,19 +476,47 @@ async function dispatchMatchedHandler(handler, executionPlan, requestContext, co
491
476
  if (requestContext.response.committed) {
492
477
  return;
493
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
+ }
494
500
  const result = executionPlan.mergedInterceptors.length === 0 ? await invokeControllerHandler(handler, requestContext, binder, controllerContainer) : await runInterceptorChain(executionPlan.mergedInterceptors, {
495
501
  handler,
496
502
  requestContext
497
503
  }, async () => invokeControllerHandler(handler, requestContext, binder, controllerContainer));
498
504
  ensureRequestNotAborted(requestContext.request);
499
- 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))) {
500
509
  // Managed SSE streams are already committed and closed by writeManagedSseIterable.
501
- } else if (!(result instanceof SseResponse) && !requestContext.response.committed) {
502
- await writeSuccessResponse(handler, requestContext.request, requestContext.response, result, contentNegotiation, requestContext);
510
+ } else if (!requestContext.response.committed) {
511
+ await writeSuccessResponse(handler, requestContext.request, requestContext.response, result, contentNegotiation, requestContext, conditionalValidators, conditionalOutcome);
503
512
  }
504
- await notifyObserversSafely(observers, requestContext, async (observer, context) => {
505
- await observer.onRequestSuccess?.(context, result);
506
- }, 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');
507
520
  }
508
521
  function resolveHandlerExecutionPlan(handler, executionPlans, options) {
509
522
  const cached = executionPlans.get(handler);
@@ -514,8 +527,11 @@ function resolveHandlerExecutionPlan(handler, executionPlans, options) {
514
527
  executionPlans.set(handler, compiled);
515
528
  return compiled;
516
529
  }
517
- async function dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathRuntimeCache) {
518
- 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);
519
535
  if (!shouldUseFastPathForRequest(eligibility, request)) {
520
536
  return false;
521
537
  }
@@ -537,6 +553,7 @@ async function dispatchNativeFastRoute(match, request, response, options, conten
537
553
  phaseContext = {
538
554
  contentNegotiation,
539
555
  dispatchScope,
556
+ fastPathState,
540
557
  fastPathRuntimeCache,
541
558
  handlerExecutionPlans: EMPTY_NATIVE_FAST_PATH_HANDLER_EXECUTION_PLANS,
542
559
  observers: EMPTY_NATIVE_FAST_PATH_OBSERVERS,
@@ -581,6 +598,11 @@ async function notifyHandlerMatched(context, descriptor) {
581
598
  await observer.onHandlerMatched?.(observationContext);
582
599
  }, context.options.logger, descriptor);
583
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
+ }
584
606
  async function notifyRequestError(context, error) {
585
607
  await notifyObserversSafely(context.observers, context.requestContext, async (observer, observationContext) => {
586
608
  await observer.onRequestError?.(observationContext, error);
@@ -592,7 +614,7 @@ async function notifyRequestFinish(context) {
592
614
  }, context.options.logger, context.matchedHandler);
593
615
  }
594
616
  async function tryFastPathExecution(handler, context) {
595
- const eligibility = getHandlerFastPathEligibility(handler);
617
+ const eligibility = context.fastPathState.getEligibility(handler);
596
618
  if (!eligibility || eligibility.executionPath !== 'fast') {
597
619
  return false;
598
620
  }
@@ -600,8 +622,7 @@ async function tryFastPathExecution(handler, context) {
600
622
  ensureRequestScope(context);
601
623
  }
602
624
  const runtimeCache = resolveFastPathHandlerRuntimeCache(handler, context.fastPathRuntimeCache);
603
- const controllerOrPromise = resolveFastPathController(handler, context.dispatchScope.container, runtimeCache);
604
- const controller = isPromiseLike(controllerOrPromise) ? await controllerOrPromise : controllerOrPromise;
625
+ const controller = await context.dispatchScope.container.resolve(handler.controllerToken);
605
626
  const fastPathResult = await executeFastPath({
606
627
  binder: context.options.binder,
607
628
  contentNegotiation: context.contentNegotiation,
@@ -623,6 +644,7 @@ async function tryFastPathExecution(handler, context) {
623
644
  }
624
645
  async function runDispatchPipeline(context) {
625
646
  ensureRequestNotAborted(context.requestContext.request);
647
+ let handlerResult;
626
648
  const appMiddlewareContext = {
627
649
  request: context.requestContext.request,
628
650
  requestContext: context.requestContext,
@@ -635,12 +657,12 @@ async function runDispatchPipeline(context) {
635
657
  const match = readFrameworkRequestNativeRouteHandoff(appMiddlewareContext.request) ?? matchHandlerOrThrow(context.options.handlerMapping, appMiddlewareContext.request);
636
658
  context.matchedHandler = match.descriptor;
637
659
  updateRequestParams(context.requestContext, match.params);
638
- const eligibility = getHandlerFastPathEligibility(match.descriptor);
660
+ const eligibility = context.fastPathState.getEligibility(match.descriptor);
639
661
  if (context.options.fastPathDebugHeaders === true && eligibility && !context.response.committed) {
640
662
  const debugInfo = createPathDebugInfo(eligibility);
641
663
  addPathDebugHeader(context.response.setHeader.bind(context.response), debugInfo);
642
664
  }
643
- if (shouldUseFastPathForRequest(eligibility, appMiddlewareContext.request)) {
665
+ if (!context.options.conditionalRequest && shouldUseFastPathForRequest(eligibility, appMiddlewareContext.request)) {
644
666
  const fastPathSuccess = await tryFastPathExecution(match.descriptor, context);
645
667
  if (fastPathSuccess) {
646
668
  return;
@@ -657,20 +679,24 @@ async function runDispatchPipeline(context) {
657
679
  response: context.response
658
680
  };
659
681
  await runMiddlewareChain(match.descriptor.metadata.moduleMiddleware ?? [], moduleMiddlewareContext, async () => {
660
- 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);
661
683
  });
662
684
  };
663
685
  const appMiddleware = context.options.appMiddleware ?? [];
664
686
  if (appMiddleware.length === 0) {
665
687
  await dispatchMatchedRoute();
666
- return;
688
+ } else {
689
+ await runMiddlewareChain(appMiddleware, appMiddlewareContext, dispatchMatchedRoute);
690
+ }
691
+ if (handlerResult) {
692
+ await notifyRequestSuccess(context, handlerResult.result);
667
693
  }
668
- await runMiddlewareChain(appMiddleware, appMiddlewareContext, dispatchMatchedRoute);
669
694
  }
670
695
  async function handleDispatchError(context, error) {
671
696
  const managedSseCleanupFailed = error instanceof ManagedSseCleanupError;
672
- const dispatchError = managedSseCleanupFailed ? error.cleanupError : error;
673
- if (!managedSseCleanupFailed && (error instanceof RequestAbortedError || isRequestAborted(context.requestContext.request))) {
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))) {
674
700
  return;
675
701
  }
676
702
  await notifyRequestError(context, dispatchError);
@@ -681,7 +707,20 @@ async function handleDispatchError(context, error) {
681
707
  if (managedSseCleanupFailed) {
682
708
  logDispatchFailure(context.options.logger, 'Managed SSE iterator cleanup threw an error.', dispatchError);
683
709
  }
684
- await writeErrorResponse(dispatchError, context.response, context.requestContext.requestId);
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
+ });
685
724
  }
686
725
 
687
726
  /**
@@ -698,22 +737,16 @@ export function createDispatcher(options) {
698
737
  const fastPathRuntimeCache = new WeakMap();
699
738
  const handlerExecutionPlans = new WeakMap();
700
739
  const adapter = options.adapter ?? 'default';
701
- const fastPathEligibilities = [];
740
+ const fastPathState = createDispatcherFastPathState(options.handlerMapping.descriptors, options, adapter);
702
741
  for (const descriptor of options.handlerMapping.descriptors) {
703
742
  handlerExecutionPlans.set(descriptor, compileHandlerExecutionPlan(descriptor, options));
704
- const {
705
- eligibility
706
- } = compileFastPathEligibility(descriptor, options, adapter);
707
- setHandlerFastPathEligibility(descriptor, eligibility);
708
- fastPathEligibilities.push(eligibility);
709
743
  }
710
- const fastPathStats = createFastPathStats(fastPathEligibilities);
711
744
  const dispatcher = {
712
745
  describeRoutes() {
713
- return options.handlerMapping.descriptors.map(descriptor => cloneHandlerDescriptor(descriptor));
746
+ return fastPathState.describeRoutes();
714
747
  },
715
748
  async dispatchNativeRoute(match, request, response) {
716
- return dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathRuntimeCache);
749
+ return dispatchNativeFastRoute(match, request, response, options, contentNegotiation, fastPathState, fastPathRuntimeCache);
717
750
  },
718
751
  async dispatch(request, response) {
719
752
  const dispatchRequest = createDispatchRequest(request);
@@ -730,6 +763,7 @@ export function createDispatcher(options) {
730
763
  phaseContext = {
731
764
  contentNegotiation,
732
765
  dispatchScope,
766
+ fastPathState,
733
767
  fastPathRuntimeCache,
734
768
  handlerExecutionPlans,
735
769
  observers,
@@ -737,6 +771,7 @@ export function createDispatcher(options) {
737
771
  requestContext,
738
772
  response
739
773
  };
774
+ initializeCorrelationMiddlewareRequestContext(options.appMiddleware ?? [], requestContext);
740
775
  await runWithRequestContext(phaseContext.requestContext, async () => {
741
776
  try {
742
777
  if (observers.length > 0) {
@@ -764,7 +799,12 @@ export function createDispatcher(options) {
764
799
  });
765
800
  }
766
801
  };
767
- 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
+ });
768
808
  return dispatcher;
769
809
  }
770
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"}
@@ -58,10 +58,10 @@ export function compileFastPathEligibility(handler, options, adapter) {
58
58
  const hasRequestScopedDI = determineRequestScopeRequirement(handler, options);
59
59
  const hasMiddleware = determineMiddlewareRequirement(handler, options.appMiddleware ?? []);
60
60
  const hasContentNegotiation = options.contentNegotiation?.formatters !== undefined && options.contentNegotiation.formatters.length > 0;
61
+ const hasConditionalRequest = options.conditionalRequest !== undefined;
61
62
  const isSseRoute = handler.route.produces?.some(mediaType => mediaType.toLowerCase().startsWith('text/event-stream')) === true;
62
- const eligibility = {
63
+ const eligibilityBase = {
63
64
  adapter,
64
- executionPath: 'full',
65
65
  hasAdapterPluginInfluence: false,
66
66
  hasCustomBodyParser: options.binder !== undefined,
67
67
  hasCustomErrorFilter: options.onError !== undefined,
@@ -74,39 +74,44 @@ export function compileFastPathEligibility(handler, options, adapter) {
74
74
  routeId
75
75
  };
76
76
  const blockingReasons = [];
77
- if (eligibility.hasGuard) {
77
+ if (eligibilityBase.hasGuard) {
78
78
  blockingReasons.push('guards');
79
79
  }
80
- if (eligibility.hasInterceptor) {
80
+ if (eligibilityBase.hasInterceptor) {
81
81
  blockingReasons.push('interceptors');
82
82
  }
83
- if (eligibility.hasRequestScopedDI) {
83
+ if (eligibilityBase.hasRequestScopedDI) {
84
84
  blockingReasons.push('request-scoped DI');
85
85
  }
86
- if (eligibility.hasMiddleware) {
86
+ if (eligibilityBase.hasMiddleware) {
87
87
  blockingReasons.push('middleware');
88
88
  }
89
- if (eligibility.hasGlobalHook) {
89
+ if (eligibilityBase.hasGlobalHook) {
90
90
  blockingReasons.push('request observers');
91
91
  }
92
- if (eligibility.hasCustomErrorFilter) {
92
+ if (eligibilityBase.hasCustomErrorFilter) {
93
93
  blockingReasons.push('custom error filter');
94
94
  }
95
- if (eligibility.hasCustomBodyParser) {
95
+ if (eligibilityBase.hasCustomBodyParser) {
96
96
  blockingReasons.push('custom binder');
97
97
  }
98
98
  if (hasContentNegotiation) {
99
99
  blockingReasons.push('content negotiation');
100
100
  }
101
+ if (hasConditionalRequest) {
102
+ blockingReasons.push('conditional requests');
103
+ }
101
104
  if (isSseRoute) {
102
105
  blockingReasons.push('SSE streaming');
103
106
  }
104
107
  const isEligible = blockingReasons.length === 0;
105
- if (!isEligible) {
106
- eligibility.fallbackReason = `Full path required due to: ${blockingReasons.join(', ')}`;
107
- } else {
108
- eligibility.executionPath = 'fast';
109
- }
108
+ const eligibility = Object.freeze({
109
+ ...eligibilityBase,
110
+ executionPath: isEligible ? 'fast' : 'full',
111
+ ...(isEligible ? {} : {
112
+ fallbackReason: `Full path required due to: ${blockingReasons.join(', ')}`
113
+ })
114
+ });
110
115
  return {
111
116
  eligibility,
112
117
  isEligible
@@ -130,7 +135,14 @@ export function getHandlerFastPathEligibility(handler) {
130
135
  * @param eligibility Eligibility metadata to expose through dispatcher observability.
131
136
  */
132
137
  export function setHandlerFastPathEligibility(handler, eligibility) {
133
- handler[FAST_PATH_ELIGIBILITY_SYMBOL] = eligibility;
138
+ Object.defineProperty(handler, FAST_PATH_ELIGIBILITY_SYMBOL, {
139
+ configurable: false,
140
+ enumerable: false,
141
+ value: Object.freeze({
142
+ ...eligibility
143
+ }),
144
+ writable: false
145
+ });
134
146
  }
135
147
 
136
148
  /** Options shared by fast-path executor helpers. */