@fluojs/http 1.0.0-beta.9 → 1.1.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 +78 -11
- package/README.md +78 -11
- package/dist/context/request-context-node-store.d.ts +25 -0
- package/dist/context/request-context-node-store.d.ts.map +1 -0
- package/dist/context/request-context-node-store.js +32 -0
- package/dist/context/request-context-stack-store.d.ts +8 -0
- package/dist/context/request-context-stack-store.d.ts.map +1 -0
- package/dist/context/request-context-stack-store.js +29 -0
- package/dist/context/request-context-store.d.ts +7 -0
- package/dist/context/request-context-store.d.ts.map +1 -0
- package/dist/context/request-context-store.js +1 -0
- package/dist/context/request-context.d.ts +7 -3
- package/dist/context/request-context.d.ts.map +1 -1
- package/dist/context/request-context.js +17 -5
- package/dist/context/sse.d.ts +20 -0
- package/dist/context/sse.d.ts.map +1 -1
- package/dist/context/sse.js +19 -0
- package/dist/decorators.d.ts +10 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +279 -53
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +142 -2
- package/dist/dispatch/fast-path/eligibility-checker.d.ts +22 -0
- package/dist/dispatch/fast-path/eligibility-checker.d.ts.map +1 -1
- package/dist/dispatch/fast-path/eligibility-checker.js +32 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/middleware/correlation.d.ts.map +1 -1
- package/dist/middleware/correlation.js +8 -2
- package/package.json +4 -4
package/dist/decorators.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { getStandardMetadataBag as readStandardMetadataBag
|
|
1
|
+
import { defineControllerMetadata, defineDtoFieldBindingMetadata, defineRouteMetadata, ensureMetadataSymbol, getControllerMetadata, getDtoFieldBindingMetadata, getRouteMetadata, getStandardMetadataBag as readStandardMetadataBag } from '@fluojs/core/internal';
|
|
2
2
|
import { validateRoutePath } from './route-path.js';
|
|
3
3
|
const standardControllerMetadataKey = Symbol.for('fluo.standard.controller');
|
|
4
4
|
const standardRouteMetadataKey = Symbol.for('fluo.standard.route');
|
|
5
5
|
const standardDtoBindingMetadataKey = Symbol.for('fluo.standard.dto-binding');
|
|
6
|
+
const legacyRouteMetadataStore = new WeakMap();
|
|
7
|
+
const legacyDtoBindingMetadataStore = new WeakMap();
|
|
8
|
+
ensureMetadataSymbol();
|
|
6
9
|
function normalizeProducesMediaTypes(mediaTypes) {
|
|
7
10
|
const normalized = [];
|
|
8
11
|
for (const mediaType of mediaTypes) {
|
|
@@ -24,8 +27,115 @@ function mergeUnique(existing, values) {
|
|
|
24
27
|
return merged;
|
|
25
28
|
}
|
|
26
29
|
function getStandardMetadataBag(metadata) {
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
return typeof metadata === 'object' && metadata !== null ? metadata : {};
|
|
31
|
+
}
|
|
32
|
+
function isStandardDecoratorContext(value) {
|
|
33
|
+
return typeof value === 'object' && value !== null && 'kind' in value && 'name' in value;
|
|
34
|
+
}
|
|
35
|
+
function isMetadataPropertyKey(value) {
|
|
36
|
+
return typeof value === 'string' || typeof value === 'symbol';
|
|
37
|
+
}
|
|
38
|
+
function getLegacyRouteRecord(target, propertyKey) {
|
|
39
|
+
let routeMap = legacyRouteMetadataStore.get(target);
|
|
40
|
+
if (!routeMap) {
|
|
41
|
+
routeMap = new Map();
|
|
42
|
+
legacyRouteMetadataStore.set(target, routeMap);
|
|
43
|
+
}
|
|
44
|
+
let record = routeMap.get(propertyKey);
|
|
45
|
+
if (!record) {
|
|
46
|
+
record = {};
|
|
47
|
+
routeMap.set(propertyKey, record);
|
|
48
|
+
}
|
|
49
|
+
return record;
|
|
50
|
+
}
|
|
51
|
+
function flushLegacyRouteMetadata(target, propertyKey, record) {
|
|
52
|
+
if (!record.method || record.path === undefined) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const existing = getRouteMetadata(target, propertyKey);
|
|
56
|
+
const metadata = {
|
|
57
|
+
guards: mergeUnique(existing?.guards, record.guards ?? []),
|
|
58
|
+
headers: record.headers ?? existing?.headers,
|
|
59
|
+
interceptors: mergeUnique(existing?.interceptors, record.interceptors ?? []),
|
|
60
|
+
method: record.method,
|
|
61
|
+
path: record.path,
|
|
62
|
+
redirect: record.redirect ?? existing?.redirect,
|
|
63
|
+
request: record.request ?? existing?.request,
|
|
64
|
+
successStatus: record.successStatus ?? existing?.successStatus,
|
|
65
|
+
version: record.version ?? existing?.version
|
|
66
|
+
};
|
|
67
|
+
defineRouteMetadata(target, propertyKey, metadata);
|
|
68
|
+
}
|
|
69
|
+
function mergeLegacyRouteMetadata(target, propertyKey, partial) {
|
|
70
|
+
const record = getLegacyRouteRecord(target, propertyKey);
|
|
71
|
+
Object.assign(record, partial);
|
|
72
|
+
flushLegacyRouteMetadata(target, propertyKey, record);
|
|
73
|
+
}
|
|
74
|
+
function defineLegacyControllerMetadata(target, partial) {
|
|
75
|
+
const existing = getControllerMetadata(target);
|
|
76
|
+
defineControllerMetadata(target, {
|
|
77
|
+
basePath: partial.basePath ?? existing?.basePath ?? '',
|
|
78
|
+
guards: partial.guards ?? existing?.guards,
|
|
79
|
+
interceptors: partial.interceptors ?? existing?.interceptors,
|
|
80
|
+
version: partial.version ?? existing?.version
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function getLegacyDtoBindingRecord(target, propertyKey) {
|
|
84
|
+
let bindingMap = legacyDtoBindingMetadataStore.get(target);
|
|
85
|
+
if (!bindingMap) {
|
|
86
|
+
bindingMap = new Map();
|
|
87
|
+
legacyDtoBindingMetadataStore.set(target, bindingMap);
|
|
88
|
+
}
|
|
89
|
+
let record = bindingMap.get(propertyKey);
|
|
90
|
+
if (!record) {
|
|
91
|
+
record = {};
|
|
92
|
+
bindingMap.set(propertyKey, record);
|
|
93
|
+
}
|
|
94
|
+
return record;
|
|
95
|
+
}
|
|
96
|
+
function mergeLegacyDtoBinding(target, propertyKey, partial) {
|
|
97
|
+
const record = getLegacyDtoBindingRecord(target, propertyKey);
|
|
98
|
+
Object.assign(record, partial);
|
|
99
|
+
const source = record.source ?? getDtoFieldBindingMetadata(target, propertyKey)?.source;
|
|
100
|
+
if (!source) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
defineDtoFieldBindingMetadata(target, propertyKey, {
|
|
104
|
+
converter: record.converter,
|
|
105
|
+
key: record.key,
|
|
106
|
+
optional: record.optional,
|
|
107
|
+
source
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function appendLegacyRouteHeader(target, propertyKey, name, value) {
|
|
111
|
+
const record = getLegacyRouteRecord(target, propertyKey);
|
|
112
|
+
record.headers = [...(record.headers ?? []), {
|
|
113
|
+
name,
|
|
114
|
+
value
|
|
115
|
+
}];
|
|
116
|
+
flushLegacyRouteMetadata(target, propertyKey, record);
|
|
117
|
+
}
|
|
118
|
+
function appendLegacyControllerGuards(target, guards) {
|
|
119
|
+
const existing = getControllerMetadata(target);
|
|
120
|
+
defineLegacyControllerMetadata(target, {
|
|
121
|
+
guards: mergeUnique(existing?.guards, guards)
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function appendLegacyControllerInterceptors(target, interceptors) {
|
|
125
|
+
const existing = getControllerMetadata(target);
|
|
126
|
+
defineLegacyControllerMetadata(target, {
|
|
127
|
+
interceptors: mergeUnique(existing?.interceptors, interceptors)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function appendLegacyRouteGuards(target, propertyKey, guards) {
|
|
131
|
+
const record = getLegacyRouteRecord(target, propertyKey);
|
|
132
|
+
record.guards = mergeUnique(record.guards, guards);
|
|
133
|
+
flushLegacyRouteMetadata(target, propertyKey, record);
|
|
134
|
+
}
|
|
135
|
+
function appendLegacyRouteInterceptors(target, propertyKey, interceptors) {
|
|
136
|
+
const record = getLegacyRouteRecord(target, propertyKey);
|
|
137
|
+
record.interceptors = mergeUnique(record.interceptors, interceptors);
|
|
138
|
+
flushLegacyRouteMetadata(target, propertyKey, record);
|
|
29
139
|
}
|
|
30
140
|
function getStandardControllerRecord(metadata) {
|
|
31
141
|
const bag = getStandardMetadataBag(metadata);
|
|
@@ -74,32 +184,65 @@ function mergeStandardDtoBinding(metadata, propertyKey, partial) {
|
|
|
74
184
|
...partial
|
|
75
185
|
});
|
|
76
186
|
}
|
|
77
|
-
function createRouteDecorator(method) {
|
|
187
|
+
function createRouteDecorator(method, produces) {
|
|
78
188
|
return path => {
|
|
79
189
|
validateRoutePath(path, `@${method}() path`);
|
|
80
|
-
const decorator = (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
190
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
191
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
192
|
+
const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name);
|
|
193
|
+
route.method = method;
|
|
194
|
+
route.path = path;
|
|
195
|
+
if (produces) {
|
|
196
|
+
route.produces = normalizeProducesMediaTypes(produces);
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
201
|
+
const routeMetadata = {
|
|
202
|
+
method,
|
|
203
|
+
path
|
|
204
|
+
};
|
|
205
|
+
if (produces) {
|
|
206
|
+
routeMetadata.produces = normalizeProducesMediaTypes(produces);
|
|
207
|
+
}
|
|
208
|
+
mergeLegacyRouteMetadata(valueOrTarget, contextOrPropertyKey, routeMetadata);
|
|
209
|
+
}
|
|
84
210
|
};
|
|
85
211
|
return decorator;
|
|
86
212
|
};
|
|
87
213
|
}
|
|
88
214
|
function createRouteValueDecorator(apply) {
|
|
89
215
|
return value => {
|
|
90
|
-
const decorator = (
|
|
91
|
-
|
|
216
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
217
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
218
|
+
apply(getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name), value);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
222
|
+
const record = getLegacyRouteRecord(valueOrTarget, contextOrPropertyKey);
|
|
223
|
+
apply(record, value);
|
|
224
|
+
flushLegacyRouteMetadata(valueOrTarget, contextOrPropertyKey, record);
|
|
225
|
+
}
|
|
92
226
|
};
|
|
93
227
|
return decorator;
|
|
94
228
|
};
|
|
95
229
|
}
|
|
96
230
|
function createDtoFieldDecorator(source) {
|
|
97
231
|
return key => {
|
|
98
|
-
const decorator = (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
232
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
233
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
234
|
+
mergeStandardDtoBinding(contextOrPropertyKey.metadata, contextOrPropertyKey.name, {
|
|
235
|
+
key,
|
|
236
|
+
source
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (isMetadataPropertyKey(contextOrPropertyKey) && valueOrTarget && typeof valueOrTarget === 'object') {
|
|
241
|
+
mergeLegacyDtoBinding(valueOrTarget, contextOrPropertyKey, {
|
|
242
|
+
key,
|
|
243
|
+
source
|
|
244
|
+
});
|
|
245
|
+
}
|
|
103
246
|
};
|
|
104
247
|
return decorator;
|
|
105
248
|
};
|
|
@@ -113,8 +256,14 @@ function createDtoFieldDecorator(source) {
|
|
|
113
256
|
*/
|
|
114
257
|
export function Controller(basePath = '') {
|
|
115
258
|
validateRoutePath(basePath, '@Controller() base path');
|
|
116
|
-
const decorator = (
|
|
117
|
-
|
|
259
|
+
const decorator = (target, context) => {
|
|
260
|
+
if (isStandardDecoratorContext(context)) {
|
|
261
|
+
getStandardControllerRecord(context.metadata).basePath = basePath;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
defineLegacyControllerMetadata(target, {
|
|
265
|
+
basePath
|
|
266
|
+
});
|
|
118
267
|
};
|
|
119
268
|
return decorator;
|
|
120
269
|
}
|
|
@@ -126,12 +275,26 @@ export function Controller(basePath = '') {
|
|
|
126
275
|
* @returns A decorator that applies version metadata at class or method scope.
|
|
127
276
|
*/
|
|
128
277
|
export function Version(version) {
|
|
129
|
-
const decorator = (
|
|
130
|
-
if (
|
|
131
|
-
|
|
278
|
+
const decorator = (target, contextOrPropertyKey) => {
|
|
279
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
280
|
+
if (contextOrPropertyKey.kind === 'class') {
|
|
281
|
+
getStandardControllerRecord(contextOrPropertyKey.metadata).version = version;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name).version = version;
|
|
132
285
|
return;
|
|
133
286
|
}
|
|
134
|
-
|
|
287
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
288
|
+
mergeLegacyRouteMetadata(target, contextOrPropertyKey, {
|
|
289
|
+
version
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (typeof target === 'function') {
|
|
294
|
+
defineLegacyControllerMetadata(target, {
|
|
295
|
+
version
|
|
296
|
+
});
|
|
297
|
+
}
|
|
135
298
|
};
|
|
136
299
|
return decorator;
|
|
137
300
|
}
|
|
@@ -143,6 +306,16 @@ export function Version(version) {
|
|
|
143
306
|
* @returns A method decorator that registers a `GET` handler mapping.
|
|
144
307
|
*/
|
|
145
308
|
export const Get = createRouteDecorator('GET');
|
|
309
|
+
/**
|
|
310
|
+
* Registers a server-sent events route handler as `GET` with `text/event-stream` produces metadata.
|
|
311
|
+
*
|
|
312
|
+
* @param path Route path relative to the controller base path.
|
|
313
|
+
* @returns A method decorator that registers a `GET` SSE handler mapping.
|
|
314
|
+
*
|
|
315
|
+
* @remarks
|
|
316
|
+
* Handlers may return `SseResponse` for manual control or `AsyncIterable<SseMessage<T> | T>` for managed dispatcher streaming.
|
|
317
|
+
*/
|
|
318
|
+
export const Sse = createRouteDecorator('GET', ['text/event-stream']);
|
|
146
319
|
/**
|
|
147
320
|
* Registers a `POST` route handler.
|
|
148
321
|
*
|
|
@@ -235,7 +408,7 @@ export const HttpCode = createRouteValueDecorator((record, status) => {
|
|
|
235
408
|
export function getRouteProducesMetadata(controllerToken, propertyKey) {
|
|
236
409
|
const bag = readStandardMetadataBag(controllerToken);
|
|
237
410
|
const routeMap = bag?.[standardRouteMetadataKey];
|
|
238
|
-
const produces = routeMap?.get(propertyKey)?.produces;
|
|
411
|
+
const produces = routeMap?.get(propertyKey)?.produces ?? legacyRouteMetadataStore.get(controllerToken.prototype)?.get(propertyKey)?.produces;
|
|
239
412
|
return produces ? [...produces] : undefined;
|
|
240
413
|
}
|
|
241
414
|
|
|
@@ -281,10 +454,18 @@ export const FromBody = createDtoFieldDecorator('body');
|
|
|
281
454
|
* @returns A field decorator that marks the DTO binding as optional.
|
|
282
455
|
*/
|
|
283
456
|
export function Optional() {
|
|
284
|
-
const decorator = (
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
457
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
458
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
459
|
+
mergeStandardDtoBinding(contextOrPropertyKey.metadata, contextOrPropertyKey.name, {
|
|
460
|
+
optional: true
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (isMetadataPropertyKey(contextOrPropertyKey) && valueOrTarget && typeof valueOrTarget === 'object') {
|
|
465
|
+
mergeLegacyDtoBinding(valueOrTarget, contextOrPropertyKey, {
|
|
466
|
+
optional: true
|
|
467
|
+
});
|
|
468
|
+
}
|
|
288
469
|
};
|
|
289
470
|
return decorator;
|
|
290
471
|
}
|
|
@@ -296,10 +477,18 @@ export function Optional() {
|
|
|
296
477
|
* @returns A field decorator that stores converter metadata for the DTO field.
|
|
297
478
|
*/
|
|
298
479
|
export function Convert(converter) {
|
|
299
|
-
const decorator = (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
480
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
481
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
482
|
+
mergeStandardDtoBinding(contextOrPropertyKey.metadata, contextOrPropertyKey.name, {
|
|
483
|
+
converter
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (isMetadataPropertyKey(contextOrPropertyKey) && valueOrTarget && typeof valueOrTarget === 'object') {
|
|
488
|
+
mergeLegacyDtoBinding(valueOrTarget, contextOrPropertyKey, {
|
|
489
|
+
converter
|
|
490
|
+
});
|
|
491
|
+
}
|
|
303
492
|
};
|
|
304
493
|
return decorator;
|
|
305
494
|
}
|
|
@@ -312,12 +501,18 @@ export function Convert(converter) {
|
|
|
312
501
|
* @returns A method decorator that appends route-level response-header metadata.
|
|
313
502
|
*/
|
|
314
503
|
export function Header(name, value) {
|
|
315
|
-
const decorator = (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
504
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
505
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
506
|
+
const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name);
|
|
507
|
+
route.headers = [...(route.headers ?? []), {
|
|
508
|
+
name,
|
|
509
|
+
value
|
|
510
|
+
}];
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
514
|
+
appendLegacyRouteHeader(valueOrTarget, contextOrPropertyKey, name, value);
|
|
515
|
+
}
|
|
321
516
|
};
|
|
322
517
|
return decorator;
|
|
323
518
|
}
|
|
@@ -330,11 +525,22 @@ export function Header(name, value) {
|
|
|
330
525
|
* @returns A method decorator that writes redirect metadata for the route.
|
|
331
526
|
*/
|
|
332
527
|
export function Redirect(url, statusCode) {
|
|
333
|
-
const decorator = (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
528
|
+
const decorator = (valueOrTarget, contextOrPropertyKey) => {
|
|
529
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
530
|
+
getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name).redirect = {
|
|
531
|
+
url,
|
|
532
|
+
statusCode
|
|
533
|
+
};
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
537
|
+
mergeLegacyRouteMetadata(valueOrTarget, contextOrPropertyKey, {
|
|
538
|
+
redirect: {
|
|
539
|
+
url,
|
|
540
|
+
statusCode
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
338
544
|
};
|
|
339
545
|
return decorator;
|
|
340
546
|
}
|
|
@@ -346,14 +552,24 @@ export function Redirect(url, statusCode) {
|
|
|
346
552
|
* @returns A decorator applicable to classes and methods.
|
|
347
553
|
*/
|
|
348
554
|
export function UseGuards(...guards) {
|
|
349
|
-
const decorator = (
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
555
|
+
const decorator = (target, contextOrPropertyKey) => {
|
|
556
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
557
|
+
if (contextOrPropertyKey.kind === 'class') {
|
|
558
|
+
const controller = getStandardControllerRecord(contextOrPropertyKey.metadata);
|
|
559
|
+
controller.guards = mergeUnique(controller.guards, guards);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name);
|
|
563
|
+
route.guards = mergeUnique(route.guards, guards);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
567
|
+
appendLegacyRouteGuards(target, contextOrPropertyKey, guards);
|
|
353
568
|
return;
|
|
354
569
|
}
|
|
355
|
-
|
|
356
|
-
|
|
570
|
+
if (typeof target === 'function') {
|
|
571
|
+
appendLegacyControllerGuards(target, guards);
|
|
572
|
+
}
|
|
357
573
|
};
|
|
358
574
|
return decorator;
|
|
359
575
|
}
|
|
@@ -365,14 +581,24 @@ export function UseGuards(...guards) {
|
|
|
365
581
|
* @returns A decorator applicable to classes and methods.
|
|
366
582
|
*/
|
|
367
583
|
export function UseInterceptors(...interceptors) {
|
|
368
|
-
const decorator = (
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
584
|
+
const decorator = (target, contextOrPropertyKey) => {
|
|
585
|
+
if (isStandardDecoratorContext(contextOrPropertyKey)) {
|
|
586
|
+
if (contextOrPropertyKey.kind === 'class') {
|
|
587
|
+
const controller = getStandardControllerRecord(contextOrPropertyKey.metadata);
|
|
588
|
+
controller.interceptors = mergeUnique(controller.interceptors, interceptors);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name);
|
|
592
|
+
route.interceptors = mergeUnique(route.interceptors, interceptors);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (isMetadataPropertyKey(contextOrPropertyKey)) {
|
|
596
|
+
appendLegacyRouteInterceptors(target, contextOrPropertyKey, interceptors);
|
|
372
597
|
return;
|
|
373
598
|
}
|
|
374
|
-
|
|
375
|
-
|
|
599
|
+
if (typeof target === 'function') {
|
|
600
|
+
appendLegacyControllerInterceptors(target, interceptors);
|
|
601
|
+
}
|
|
376
602
|
};
|
|
377
603
|
return decorator;
|
|
378
604
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,YAAY,CAAC;AAQnE,OAAO,KAAK,EACV,MAAM,EACN,yBAAyB,EACzB,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,YAAY,CAAC;AAQnE,OAAO,KAAK,EACV,MAAM,EACN,yBAAyB,EACzB,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EAKjB,cAAc,EAEd,eAAe,EAEf,cAAc,EAId,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAKL,KAAK,aAAa,EAOnB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE5F,gEAAgE;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;AAEpK,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,aAAa,CAAC,EAAE,cAAc,EAAE,CAAC;IACjC,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;IAC/C,sDAAsD;IACtD,cAAc,EAAE,cAAc,CAAC;IAC/B,2DAA2D;IAC3D,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;IACjC,0DAA0D;IAC1D,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,+DAA+D;IAC/D,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qCAAqC;IACrC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,sEAAsE;IACtE,YAAY,CAAC,EAAE;QACb,wDAAwD;QACxD,oBAAoB,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;KACjD,CAAC;IACF,qDAAqD;IACrD,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,qDAAqD;IACrD,aAAa,EAAE,SAAS,CAAC;IACzB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAy8BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,UAAU,CAuF7E;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,UAAU,GAAG,aAAa,GAAG,SAAS,CAE5F;AAED,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getCompiledDtoBindingPlan } from '../adapters/dto-binding-plan.js';
|
|
2
2
|
import { createRequestContext, runWithRequestContext } from '../context/request-context.js';
|
|
3
|
-
import { SseResponse } from '../context/sse.js';
|
|
3
|
+
import { SseResponse, isSseMessage } from '../context/sse.js';
|
|
4
4
|
import { RequestAbortedError } from '../errors.js';
|
|
5
5
|
import { runGuardChain } from '../guards.js';
|
|
6
6
|
import { runInterceptorChain } from '../interceptors.js';
|
|
@@ -255,6 +255,140 @@ function ensureRequestNotAborted(request) {
|
|
|
255
255
|
function isRequestAborted(request) {
|
|
256
256
|
return request.isAborted?.() ?? request.signal?.aborted === true;
|
|
257
257
|
}
|
|
258
|
+
function isSseRoute(handler) {
|
|
259
|
+
return handler.route.produces?.some(mediaType => mediaType.toLowerCase().startsWith('text/event-stream')) === true;
|
|
260
|
+
}
|
|
261
|
+
function isAsyncIterable(value) {
|
|
262
|
+
return typeof value === 'object' && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === 'function';
|
|
263
|
+
}
|
|
264
|
+
function createAbortPromise(request) {
|
|
265
|
+
if (!request.signal) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
if (request.signal.aborted) {
|
|
269
|
+
return {
|
|
270
|
+
cleanup: () => undefined,
|
|
271
|
+
promise: Promise.resolve('aborted')
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
let listener;
|
|
275
|
+
const promise = new Promise(resolve => {
|
|
276
|
+
listener = () => resolve('aborted');
|
|
277
|
+
request.signal?.addEventListener('abort', listener, {
|
|
278
|
+
once: true
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
cleanup: () => {
|
|
283
|
+
if (listener) {
|
|
284
|
+
request.signal?.removeEventListener('abort', listener);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
promise
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function createStreamClosePromise(stream) {
|
|
291
|
+
if (stream.closed) {
|
|
292
|
+
return {
|
|
293
|
+
cleanup: () => undefined,
|
|
294
|
+
promise: Promise.resolve('aborted')
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (!stream.onClose) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
let cleanup;
|
|
301
|
+
const promise = new Promise(resolve => {
|
|
302
|
+
cleanup = stream.onClose?.(() => resolve('aborted')) ?? undefined;
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
cleanup: () => {
|
|
306
|
+
cleanup?.();
|
|
307
|
+
},
|
|
308
|
+
promise
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function createManagedSseStopPromise(request, stream) {
|
|
312
|
+
const stops = [createAbortPromise(request), createStreamClosePromise(stream)].filter(entry => entry !== undefined);
|
|
313
|
+
if (stops.length === 0) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
cleanup: () => {
|
|
318
|
+
for (const stop of stops) {
|
|
319
|
+
stop.cleanup();
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
promise: Promise.race(stops.map(stop => stop.promise))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function resolveManagedSseFrame(value) {
|
|
326
|
+
if (isSseMessage(value)) {
|
|
327
|
+
const {
|
|
328
|
+
data,
|
|
329
|
+
event,
|
|
330
|
+
id,
|
|
331
|
+
retry
|
|
332
|
+
} = value;
|
|
333
|
+
return {
|
|
334
|
+
data,
|
|
335
|
+
options: {
|
|
336
|
+
event,
|
|
337
|
+
id,
|
|
338
|
+
retry
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
data: value,
|
|
344
|
+
options: {}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function closeAsyncIteratorEventually(iterator) {
|
|
348
|
+
void iterator.return?.().catch(() => undefined);
|
|
349
|
+
}
|
|
350
|
+
async function readManagedSseNext(request, stream, iterator) {
|
|
351
|
+
const abort = createManagedSseStopPromise(request, stream);
|
|
352
|
+
if (!abort) {
|
|
353
|
+
return iterator.next();
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
return await Promise.race([iterator.next(), abort.promise]);
|
|
357
|
+
} finally {
|
|
358
|
+
abort.cleanup();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function writeManagedSseIterable(handler, requestContext, source) {
|
|
362
|
+
if (!isSseRoute(handler)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
const sse = new SseResponse(requestContext);
|
|
366
|
+
const stream = requestContext.response.stream;
|
|
367
|
+
if (!stream) {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
371
|
+
try {
|
|
372
|
+
while (!isRequestAborted(requestContext.request)) {
|
|
373
|
+
const next = await readManagedSseNext(requestContext.request, stream, iterator);
|
|
374
|
+
if (next === 'aborted') {
|
|
375
|
+
closeAsyncIteratorEventually(iterator);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (next.done === true) {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
const frame = resolveManagedSseFrame(next.value);
|
|
382
|
+
const accepted = sse.send(frame.data, frame.options);
|
|
383
|
+
if (!accepted) {
|
|
384
|
+
await requestContext.response.stream?.waitForDrain?.();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} finally {
|
|
388
|
+
sse.close();
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
258
392
|
function resolveFastPathHandlerRuntimeCache(handler, cache) {
|
|
259
393
|
const cached = cache.get(handler);
|
|
260
394
|
if (cached) {
|
|
@@ -335,7 +469,9 @@ async function dispatchMatchedHandler(handler, executionPlan, requestContext, co
|
|
|
335
469
|
requestContext
|
|
336
470
|
}, async () => invokeControllerHandler(handler, requestContext, binder, controllerContainer));
|
|
337
471
|
ensureRequestNotAborted(requestContext.request);
|
|
338
|
-
if (
|
|
472
|
+
if (isAsyncIterable(result) && (await writeManagedSseIterable(handler, requestContext, result))) {
|
|
473
|
+
// Managed SSE streams are already committed and closed by writeManagedSseIterable.
|
|
474
|
+
} else if (!(result instanceof SseResponse) && !requestContext.response.committed) {
|
|
339
475
|
await writeSuccessResponse(handler, requestContext.request, requestContext.response, result, contentNegotiation);
|
|
340
476
|
}
|
|
341
477
|
await notifyObserversSafely(observers, requestContext, async (observer, context) => {
|
|
@@ -356,6 +492,10 @@ async function dispatchNativeFastRoute(match, request, response, options, conten
|
|
|
356
492
|
if (!shouldUseFastPathForRequest(eligibility, request)) {
|
|
357
493
|
return false;
|
|
358
494
|
}
|
|
495
|
+
if (options.fastPathDebugHeaders === true && eligibility && !response.committed) {
|
|
496
|
+
const debugInfo = createPathDebugInfo(eligibility);
|
|
497
|
+
addPathDebugHeader(response.setHeader.bind(response), debugInfo);
|
|
498
|
+
}
|
|
359
499
|
const dispatchRequest = request;
|
|
360
500
|
const dispatchScope = createRootDispatchScope(options.rootContainer);
|
|
361
501
|
let phaseContext;
|
|
@@ -6,13 +6,35 @@ interface CompiledEligibilityPlan {
|
|
|
6
6
|
eligibility: FastPathEligibility;
|
|
7
7
|
isEligible: boolean;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Compiles the conservative fast-path eligibility decision for one handler.
|
|
11
|
+
*
|
|
12
|
+
* @param handler Handler descriptor being analyzed.
|
|
13
|
+
* @param options Dispatcher options that can introduce full-path requirements.
|
|
14
|
+
* @param adapter Human-readable adapter label used in observability metadata.
|
|
15
|
+
* @returns The compiled eligibility metadata and boolean eligibility flag.
|
|
16
|
+
*/
|
|
9
17
|
export declare function compileFastPathEligibility(handler: HandlerDescriptor, options: CreateDispatcherOptions, adapter: string): CompiledEligibilityPlan;
|
|
18
|
+
/**
|
|
19
|
+
* Reads fast-path eligibility metadata attached to a handler descriptor.
|
|
20
|
+
*
|
|
21
|
+
* @param handler Handler descriptor previously analyzed by the dispatcher.
|
|
22
|
+
* @returns The attached eligibility metadata, when present.
|
|
23
|
+
*/
|
|
10
24
|
export declare function getHandlerFastPathEligibility(handler: HandlerDescriptor): FastPathEligibility | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Attaches fast-path eligibility metadata to a handler descriptor.
|
|
27
|
+
*
|
|
28
|
+
* @param handler Handler descriptor to annotate.
|
|
29
|
+
* @param eligibility Eligibility metadata to expose through dispatcher observability.
|
|
30
|
+
*/
|
|
11
31
|
export declare function setHandlerFastPathEligibility(handler: HandlerDescriptor, eligibility: FastPathEligibility): void;
|
|
32
|
+
/** Options shared by fast-path executor helpers. */
|
|
12
33
|
export interface FastPathExecutorOptions {
|
|
13
34
|
binder?: Binder;
|
|
14
35
|
rootContainer: Container;
|
|
15
36
|
}
|
|
37
|
+
/** Result returned after attempting fast-path handler execution. */
|
|
16
38
|
export interface FastPathExecutionResult {
|
|
17
39
|
executed: boolean;
|
|
18
40
|
result?: unknown;
|
|
@@ -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,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,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"}
|