@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.
- package/README.ko.md +417 -16
- package/README.md +424 -16
- package/dist/access-log-observer.d.ts +77 -0
- package/dist/access-log-observer.d.ts.map +1 -0
- package/dist/access-log-observer.js +171 -0
- package/dist/adapter.d.ts +9 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +15 -1
- package/dist/adapters/binding.d.ts.map +1 -1
- package/dist/adapters/binding.js +18 -16
- package/dist/adapters/dto-binding-plan.d.ts +2 -2
- package/dist/adapters/dto-binding-plan.d.ts.map +1 -1
- package/dist/adapters/dto-binding-plan.js +13 -4
- package/dist/adapters/dto-validation-adapter.d.ts +2 -1
- package/dist/adapters/dto-validation-adapter.d.ts.map +1 -1
- package/dist/adapters/dto-validation-adapter.js +2 -2
- package/dist/byte-range-response.d.ts +71 -0
- package/dist/byte-range-response.d.ts.map +1 -0
- package/dist/byte-range-response.js +347 -0
- package/dist/client-identity.d.ts +8 -4
- package/dist/client-identity.d.ts.map +1 -1
- package/dist/client-identity.js +12 -93
- package/dist/compiled-route-identity.d.ts +17 -0
- package/dist/compiled-route-identity.d.ts.map +1 -0
- package/dist/compiled-route-identity.js +29 -0
- package/dist/connection.d.ts +67 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +480 -0
- package/dist/context/request-context-node-store.d.ts +6 -0
- package/dist/context/request-context-node-store.d.ts.map +1 -1
- package/dist/context/request-context-node-store.js +14 -0
- package/dist/context/request-context.d.ts +5 -5
- package/dist/context/request-context.d.ts.map +1 -1
- package/dist/context/request-context.js +8 -83
- package/dist/context/request-id.d.ts +10 -0
- package/dist/context/request-id.d.ts.map +1 -0
- package/dist/context/request-id.js +27 -0
- package/dist/context/sse.d.ts +13 -0
- package/dist/context/sse.d.ts.map +1 -1
- package/dist/context/sse.js +36 -6
- package/dist/cookie-helpers.d.ts +47 -0
- package/dist/cookie-helpers.d.ts.map +1 -0
- package/dist/cookie-helpers.js +166 -0
- package/dist/decorators.d.ts +52 -19
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +57 -10
- package/dist/dispatch/conditional-request-policy.d.ts +67 -0
- package/dist/dispatch/conditional-request-policy.d.ts.map +1 -0
- package/dist/dispatch/conditional-request-policy.js +297 -0
- package/dist/dispatch/dispatch-content-negotiation.d.ts +8 -0
- package/dist/dispatch/dispatch-content-negotiation.d.ts.map +1 -1
- package/dist/dispatch/dispatch-content-negotiation.js +89 -42
- package/dist/dispatch/dispatch-error-negotiation.d.ts +26 -0
- package/dist/dispatch/dispatch-error-negotiation.d.ts.map +1 -0
- package/dist/dispatch/dispatch-error-negotiation.js +110 -0
- package/dist/dispatch/dispatch-error-policy.d.ts +1 -10
- package/dist/dispatch/dispatch-error-policy.d.ts.map +1 -1
- package/dist/dispatch/dispatch-error-policy.js +1 -33
- package/dist/dispatch/dispatch-error-representation.d.ts +18 -0
- package/dist/dispatch/dispatch-error-representation.d.ts.map +1 -0
- package/dist/dispatch/dispatch-error-representation.js +125 -0
- package/dist/dispatch/dispatch-response-metadata.d.ts +10 -0
- package/dist/dispatch/dispatch-response-metadata.d.ts.map +1 -0
- package/dist/dispatch/dispatch-response-metadata.js +12 -0
- package/dist/dispatch/dispatch-response-policy.d.ts +30 -3
- package/dist/dispatch/dispatch-response-policy.d.ts.map +1 -1
- package/dist/dispatch/dispatch-response-policy.js +104 -15
- package/dist/dispatch/dispatcher.d.ts +5 -1
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +140 -100
- package/dist/dispatch/fast-path/debug-visibility.d.ts.map +1 -1
- package/dist/dispatch/fast-path/debug-visibility.js +6 -3
- package/dist/dispatch/fast-path/dispatcher-state.d.ts +10 -0
- package/dist/dispatch/fast-path/dispatcher-state.d.ts.map +1 -0
- package/dist/dispatch/fast-path/dispatcher-state.js +52 -0
- package/dist/dispatch/fast-path/eligibility-checker.d.ts.map +1 -1
- package/dist/dispatch/fast-path/eligibility-checker.js +27 -15
- package/dist/dispatch/fast-path/eligibility.d.ts +16 -10
- package/dist/dispatch/fast-path/eligibility.d.ts.map +1 -1
- package/dist/dispatch/fast-path/eligibility.js +10 -4
- package/dist/dispatch/fast-path/fast-path-executor.d.ts +1 -1
- package/dist/dispatch/fast-path/fast-path-executor.d.ts.map +1 -1
- package/dist/dispatch/fast-path/fast-path-executor.js +8 -5
- package/dist/dispatch/request-abort.d.ts +22 -0
- package/dist/dispatch/request-abort.d.ts.map +1 -0
- package/dist/dispatch/request-abort.js +30 -0
- package/dist/dispatch/response-integration.d.ts +55 -0
- package/dist/dispatch/response-integration.d.ts.map +1 -0
- package/dist/dispatch/response-integration.js +57 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +19 -0
- package/dist/guards.d.ts +1 -1
- package/dist/guards.d.ts.map +1 -1
- package/dist/header-helpers.d.ts +59 -0
- package/dist/header-helpers.d.ts.map +1 -0
- package/dist/header-helpers.js +185 -0
- package/dist/index.d.ts +1 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -15
- package/dist/index.portable.d.ts +25 -0
- package/dist/index.portable.d.ts.map +1 -0
- package/dist/index.portable.js +21 -0
- package/dist/internal.d.ts +3 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +100 -15
- package/dist/middleware/correlation.d.ts +9 -1
- package/dist/middleware/correlation.d.ts.map +1 -1
- package/dist/middleware/correlation.js +25 -14
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/cors.js +5 -8
- package/dist/middleware/middleware.d.ts +3 -3
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/rate-limit.d.ts +13 -2
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +1 -0
- package/dist/static-assets.d.ts +77 -0
- package/dist/static-assets.d.ts.map +1 -0
- package/dist/static-assets.js +270 -0
- package/dist/types.d.ts +209 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +69 -1
- 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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
356
|
+
async function waitForManagedSseOperation(request, stream, operation) {
|
|
362
357
|
const abort = createManagedSseStopPromise(request, stream);
|
|
363
358
|
if (!abort) {
|
|
364
|
-
return
|
|
359
|
+
return operation;
|
|
365
360
|
}
|
|
366
361
|
try {
|
|
367
|
-
return await Promise.race([
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
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,
|
|
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 (
|
|
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 (!
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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
|
|
673
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 (
|
|
77
|
+
if (eligibilityBase.hasGuard) {
|
|
78
78
|
blockingReasons.push('guards');
|
|
79
79
|
}
|
|
80
|
-
if (
|
|
80
|
+
if (eligibilityBase.hasInterceptor) {
|
|
81
81
|
blockingReasons.push('interceptors');
|
|
82
82
|
}
|
|
83
|
-
if (
|
|
83
|
+
if (eligibilityBase.hasRequestScopedDI) {
|
|
84
84
|
blockingReasons.push('request-scoped DI');
|
|
85
85
|
}
|
|
86
|
-
if (
|
|
86
|
+
if (eligibilityBase.hasMiddleware) {
|
|
87
87
|
blockingReasons.push('middleware');
|
|
88
88
|
}
|
|
89
|
-
if (
|
|
89
|
+
if (eligibilityBase.hasGlobalHook) {
|
|
90
90
|
blockingReasons.push('request observers');
|
|
91
91
|
}
|
|
92
|
-
if (
|
|
92
|
+
if (eligibilityBase.hasCustomErrorFilter) {
|
|
93
93
|
blockingReasons.push('custom error filter');
|
|
94
94
|
}
|
|
95
|
-
if (
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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. */
|