@fluojs/http 1.0.0-beta.4 → 1.0.0-beta.5
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/dist/adapters/binding.d.ts.map +1 -1
- package/dist/adapters/binding.js +37 -7
- package/dist/adapters/dto-binding-plan.d.ts +3 -0
- package/dist/adapters/dto-binding-plan.d.ts.map +1 -1
- package/dist/adapters/dto-binding-plan.js +34 -2
- package/dist/adapters/dto-validation-adapter.d.ts +1 -1
- package/dist/adapters/dto-validation-adapter.d.ts.map +1 -1
- package/dist/adapters/dto-validation-adapter.js +7 -14
- package/dist/dispatch/dispatch-handler-policy.d.ts.map +1 -1
- package/dist/dispatch/dispatch-handler-policy.js +5 -3
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +70 -32
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/adapters/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAc,MAAM,cAAc,CAAC;AAI5E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAoB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/adapters/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAc,MAAM,cAAc,CAAC;AAI5E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAoB,MAAM,aAAa,CAAC;AAkDhI;;GAEG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IAChD,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO;CAG3D;AAyDD;;GAEG;AACH,qBAAa,aAAc,YAAW,MAAM;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,GAAE,SAAS,aAAa,EAAO;IAEhE,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;CAuGjF"}
|
package/dist/adapters/binding.js
CHANGED
|
@@ -3,6 +3,7 @@ import { BadRequestException } from '../exceptions.js';
|
|
|
3
3
|
import { toInputErrorDetail } from '../input-error-detail.js';
|
|
4
4
|
import { getCompiledDtoBindingPlan } from './dto-binding-plan.js';
|
|
5
5
|
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
6
|
+
const NO_CONVERTERS = [];
|
|
6
7
|
function isPlainObject(value) {
|
|
7
8
|
if (typeof value !== 'object' || value === null) {
|
|
8
9
|
return false;
|
|
@@ -107,13 +108,40 @@ export class DefaultBinder {
|
|
|
107
108
|
}
|
|
108
109
|
async bind(dto, context) {
|
|
109
110
|
const plan = getCompiledDtoBindingPlan(dto);
|
|
111
|
+
const request = context.requestContext.request;
|
|
110
112
|
const value = new dto();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
if (request.body !== undefined && request.body !== null) {
|
|
114
|
+
validateBodyKeys(request, plan.bodyKeys);
|
|
115
|
+
}
|
|
114
116
|
const details = [];
|
|
117
|
+
if (this.converters.length === 0 && !plan.hasFieldConverters) {
|
|
118
|
+
for (const entry of plan.entries) {
|
|
119
|
+
const rawValue = entry.read(request);
|
|
120
|
+
if (rawValue === undefined) {
|
|
121
|
+
if (entry.optional) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
details.push(toInputErrorDetail({
|
|
125
|
+
code: 'MISSING_FIELD',
|
|
126
|
+
field: entry.fieldName,
|
|
127
|
+
message: `Missing required ${entry.source} field ${entry.sourceKey}.`,
|
|
128
|
+
source: entry.source
|
|
129
|
+
}));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
value[entry.propertyKey] = rawValue;
|
|
133
|
+
}
|
|
134
|
+
if (details.length > 0) {
|
|
135
|
+
throw new BadRequestException('Request binding failed.', {
|
|
136
|
+
details
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
const converterCache = new Map();
|
|
142
|
+
const globalConverters = this.converters.length === 0 ? NO_CONVERTERS : (await Promise.all(this.converters.map(converter => resolveConverter(converter, context, converterCache)))).filter(converter => Boolean(converter));
|
|
115
143
|
for (const entry of plan.entries) {
|
|
116
|
-
const rawValue = entry.read(
|
|
144
|
+
const rawValue = entry.read(request);
|
|
117
145
|
if (rawValue === undefined) {
|
|
118
146
|
if (entry.optional) {
|
|
119
147
|
continue;
|
|
@@ -138,9 +166,11 @@ export class DefaultBinder {
|
|
|
138
166
|
for (const converter of globalConverters) {
|
|
139
167
|
convertedValue = await converter.convert(convertedValue, target);
|
|
140
168
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
if (entry.converter !== undefined) {
|
|
170
|
+
const fieldConverter = await resolveConverter(entry.converter, context, converterCache);
|
|
171
|
+
if (fieldConverter) {
|
|
172
|
+
convertedValue = await fieldConverter.convert(convertedValue, target);
|
|
173
|
+
}
|
|
144
174
|
}
|
|
145
175
|
value[entry.propertyKey] = convertedValue;
|
|
146
176
|
}
|
|
@@ -13,7 +13,10 @@ export interface CompiledDtoBindingPlanEntry {
|
|
|
13
13
|
export interface CompiledDtoBindingPlan {
|
|
14
14
|
readonly bodyKeys: ReadonlySet<string>;
|
|
15
15
|
readonly entries: readonly CompiledDtoBindingPlanEntry[];
|
|
16
|
+
readonly hasFieldConverters: boolean;
|
|
17
|
+
readonly needsValidation: boolean;
|
|
16
18
|
readonly propertyKeys: readonly MetadataPropertyKey[];
|
|
19
|
+
readonly toValidationValue: (value: unknown) => unknown;
|
|
17
20
|
}
|
|
18
21
|
export declare function getCompiledDtoBindingPlan(dto: Constructor): CompiledDtoBindingPlan;
|
|
19
22
|
//# sourceMappingURL=dto-binding-plan.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dto-binding-plan.d.ts","sourceRoot":"","sources":["../../src/adapters/dto-binding-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC/F,OAAO,
|
|
1
|
+
{"version":3,"file":"dto-binding-plan.d.ts","sourceRoot":"","sources":["../../src/adapters/dto-binding-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC/F,OAAO,EAKL,KAAK,uBAAuB,EAE7B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAcpD,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,uBAAuB,CAAC,WAAW,CAAC,CAAC;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC;IACtD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAC;IACzD,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACtD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CACzD;AAkDD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,WAAW,GAAG,sBAAsB,CA4ClF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDtoBindingSchema } from '@fluojs/core/internal';
|
|
1
|
+
import { getClassValidationRules, getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/internal';
|
|
2
2
|
function toFieldName(propertyKey) {
|
|
3
3
|
return typeof propertyKey === 'string' ? propertyKey : String(propertyKey);
|
|
4
4
|
}
|
|
@@ -25,6 +25,27 @@ function createSourceReader(source, sourceKey) {
|
|
|
25
25
|
return () => undefined;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
function identityValidationValue(value) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function createValidationValueFilter(propertyKeys) {
|
|
32
|
+
return value => {
|
|
33
|
+
if (typeof value !== 'object' || value === null) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
const source = value;
|
|
37
|
+
const filtered = Object.create(Object.getPrototypeOf(value));
|
|
38
|
+
for (const propertyKey of propertyKeys) {
|
|
39
|
+
if (Object.hasOwn(source, propertyKey)) {
|
|
40
|
+
filtered[propertyKey] = source[propertyKey];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return filtered;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function isDtoAwareValidationRule(rule) {
|
|
47
|
+
return rule.kind === 'custom' || rule.kind === 'validateIf';
|
|
48
|
+
}
|
|
28
49
|
export function getCompiledDtoBindingPlan(dto) {
|
|
29
50
|
const cached = dtoBindingPlanCache.get(dto);
|
|
30
51
|
if (cached) {
|
|
@@ -44,10 +65,21 @@ export function getCompiledDtoBindingPlan(dto) {
|
|
|
44
65
|
sourceKey
|
|
45
66
|
};
|
|
46
67
|
});
|
|
68
|
+
const propertyKeys = entries.map(entry => entry.propertyKey);
|
|
69
|
+
const boundPropertyKeys = new Set(propertyKeys);
|
|
70
|
+
const validationSchema = getDtoValidationSchema(dto);
|
|
71
|
+
const validationPropertyKeys = validationSchema.map(entry => entry.propertyKey);
|
|
72
|
+
const hasClassValidationRules = getClassValidationRules(dto).length > 0;
|
|
73
|
+
const hasDtoAwareValidationRules = validationSchema.some(entry => entry.rules.some(rule => isDtoAwareValidationRule(rule)));
|
|
74
|
+
const needsValidation = hasClassValidationRules || validationPropertyKeys.length > 0;
|
|
75
|
+
const requiresValidationFilter = hasClassValidationRules || hasDtoAwareValidationRules || validationPropertyKeys.some(propertyKey => !boundPropertyKeys.has(propertyKey));
|
|
47
76
|
const next = {
|
|
48
77
|
bodyKeys: new Set(entries.filter(entry => entry.source === 'body').map(entry => entry.sourceKey)),
|
|
49
78
|
entries,
|
|
50
|
-
|
|
79
|
+
hasFieldConverters: entries.some(entry => entry.converter !== undefined),
|
|
80
|
+
needsValidation,
|
|
81
|
+
propertyKeys,
|
|
82
|
+
toValidationValue: requiresValidationFilter ? createValidationValueFilter(propertyKeys) : identityValidationValue
|
|
51
83
|
};
|
|
52
84
|
dtoBindingPlanCache.set(dto, next);
|
|
53
85
|
return next;
|
|
@@ -6,7 +6,7 @@ import type { Validator } from '../types.js';
|
|
|
6
6
|
export declare class HttpDtoValidationAdapter implements Validator {
|
|
7
7
|
private readonly validator;
|
|
8
8
|
private throwBadRequestForValidationError;
|
|
9
|
-
private
|
|
9
|
+
private getValidationValue;
|
|
10
10
|
validate(value: unknown, target: Constructor): Promise<void>;
|
|
11
11
|
materialize<T>(value: unknown, target: Constructor<T>): Promise<T>;
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dto-validation-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/dto-validation-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,aAAa,CAAC;AAG9D;;GAEG;AACH,qBAAa,wBAAyB,YAAW,SAAS;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IAExD,OAAO,CAAC,iCAAiC;IAMzC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"dto-validation-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/dto-validation-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,aAAa,CAAC;AAG9D;;GAEG;AACH,qBAAa,wBAAyB,YAAW,SAAS;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IAExD,OAAO,CAAC,iCAAiC;IAMzC,OAAO,CAAC,kBAAkB;IAIpB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5D,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAWzE"}
|
|
@@ -13,23 +13,16 @@ export class HttpDtoValidationAdapter {
|
|
|
13
13
|
details: error.issues.map(issue => toInputErrorDetail(issue))
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
const source = value;
|
|
21
|
-
const filtered = Object.create(Object.getPrototypeOf(value));
|
|
22
|
-
for (const propertyKey of getCompiledDtoBindingPlan(target).propertyKeys) {
|
|
23
|
-
if (Object.hasOwn(source, propertyKey)) {
|
|
24
|
-
filtered[propertyKey] = source[propertyKey];
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return filtered;
|
|
16
|
+
getValidationValue(value, target) {
|
|
17
|
+
return getCompiledDtoBindingPlan(target).toValidationValue(value);
|
|
28
18
|
}
|
|
29
19
|
async validate(value, target) {
|
|
30
20
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
21
|
+
const plan = getCompiledDtoBindingPlan(target);
|
|
22
|
+
if (!plan.needsValidation) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await this.validator.validate(this.getValidationValue(value, target), target);
|
|
33
26
|
} catch (error) {
|
|
34
27
|
if (error instanceof DtoValidationError) {
|
|
35
28
|
this.throwBadRequestForValidationError(error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-handler-policy.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-handler-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch-handler-policy.d.ts","sourceRoot":"","sources":["../../src/dispatch/dispatch-handler-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKxD,OAAO,KAAK,EAA2B,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKtG;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,iBAAiB,EAC1B,cAAc,EAAE,cAAc,EAC9B,MAAM,GAAE,MAAsB,EAC9B,mBAAmB,GAAE,qBAAgD,GACpE,OAAO,CAAC,OAAO,CAAC,CAwBlB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InvariantError } from '@fluojs/core';
|
|
2
2
|
import { DefaultBinder } from '../adapters/binding.js';
|
|
3
|
+
import { getCompiledDtoBindingPlan } from '../adapters/dto-binding-plan.js';
|
|
3
4
|
import { HttpDtoValidationAdapter } from '../adapters/dto-validation-adapter.js';
|
|
4
5
|
const defaultBinder = new DefaultBinder();
|
|
5
6
|
const defaultValidator = new HttpDtoValidationAdapter();
|
|
@@ -23,9 +24,10 @@ export async function invokeControllerHandler(handler, requestContext, binder =
|
|
|
23
24
|
handler,
|
|
24
25
|
requestContext
|
|
25
26
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const requestDto = handler.route.request;
|
|
28
|
+
const input = requestDto ? await binder.bind(requestDto, argumentResolverContext) : undefined;
|
|
29
|
+
if (requestDto && getCompiledDtoBindingPlan(requestDto).needsValidation) {
|
|
30
|
+
await defaultValidator.validate(input, requestDto);
|
|
29
31
|
}
|
|
30
32
|
return method.call(controller, input, requestContext);
|
|
31
33
|
}
|
|
@@ -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;
|
|
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,EAIjB,cAAc,EACd,eAAe,EAEf,cAAc,EAId,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAMrB;;GAEG;AACH,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;;GAEG;AACH,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,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,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,qDAAqD;IACrD,aAAa,EAAE,SAAS,CAAC;CAC1B;AAugBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,UAAU,CA+D7E"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { readFrameworkRequestNativeRouteHandoff } from './native-route-handoff.js';
|
|
2
|
-
import { invokeControllerHandler } from './dispatch-handler-policy.js';
|
|
3
|
-
import { resolveContentNegotiation, writeErrorResponse, writeSuccessResponse } from './dispatch-response-policy.js';
|
|
4
|
-
import { matchHandlerOrThrow, updateRequestParams } from './dispatch-routing-policy.js';
|
|
5
1
|
import { getCompiledDtoBindingPlan } from '../adapters/dto-binding-plan.js';
|
|
2
|
+
import { createRequestContext, runWithRequestContext } from '../context/request-context.js';
|
|
3
|
+
import { SseResponse } from '../context/sse.js';
|
|
6
4
|
import { RequestAbortedError } from '../errors.js';
|
|
7
5
|
import { runGuardChain } from '../guards.js';
|
|
8
6
|
import { runInterceptorChain } from '../interceptors.js';
|
|
9
7
|
import { isMiddlewareRouteConfig, matchRoutePattern, runMiddlewareChain } from '../middleware/middleware.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { invokeControllerHandler } from './dispatch-handler-policy.js';
|
|
9
|
+
import { resolveContentNegotiation, writeErrorResponse, writeSuccessResponse } from './dispatch-response-policy.js';
|
|
10
|
+
import { matchHandlerOrThrow, updateRequestParams } from './dispatch-routing-policy.js';
|
|
11
|
+
import { readFrameworkRequestNativeRouteHandoff } from './native-route-handoff.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Type definition for a global HTTP error handler function.
|
|
@@ -106,6 +106,25 @@ function activeMiddlewareMayRequireRequestScope(definitions, request) {
|
|
|
106
106
|
return definition.routes.length === 0 || definition.routes.some(route => matchRoutePattern(route, request.path));
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
+
function compileMiddlewareScopePlan(definitions) {
|
|
110
|
+
const conditionalDefinitions = [];
|
|
111
|
+
for (const definition of definitions) {
|
|
112
|
+
if (!isMiddlewareRouteConfig(definition) || definition.routes.length === 0) {
|
|
113
|
+
return {
|
|
114
|
+
alwaysRequiresRequestScope: true,
|
|
115
|
+
conditionalDefinitions: []
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
conditionalDefinitions.push(definition);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
alwaysRequiresRequestScope: false,
|
|
122
|
+
conditionalDefinitions
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function compiledMiddlewareMayRequireRequestScope(plan, request) {
|
|
126
|
+
return plan.alwaysRequiresRequestScope || activeMiddlewareMayRequireRequestScope(plan.conditionalDefinitions, request);
|
|
127
|
+
}
|
|
109
128
|
function requestDtoMayRequireRequestScope(handler, options) {
|
|
110
129
|
if (!handler.route.request) {
|
|
111
130
|
return false;
|
|
@@ -126,26 +145,29 @@ function handlerMethodMayUseRequestContext(handler) {
|
|
|
126
145
|
function hasRequestScopeInspector(container) {
|
|
127
146
|
return typeof container === 'object' && container !== null && 'hasRequestScopedDependency' in container && typeof container.hasRequestScopedDependency === 'function';
|
|
128
147
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return
|
|
148
|
+
function compileHandlerExecutionPlan(handler, options) {
|
|
149
|
+
const routeGuards = handler.route.guards ?? [];
|
|
150
|
+
const requestScope = compileMiddlewareScopePlan(handler.metadata.moduleMiddleware);
|
|
151
|
+
const mergedInterceptors = mergeInterceptors(options.interceptors ?? [], handler.route.interceptors ?? []);
|
|
152
|
+
return {
|
|
153
|
+
mergedInterceptors,
|
|
154
|
+
requestScope,
|
|
155
|
+
requiresRequestScope: routeGuards.length > 0 || mergedInterceptors.length > 0 || requestScope.alwaysRequiresRequestScope || requestDtoMayRequireRequestScope(handler, options) || handlerMethodMayUseRequestContext(handler) || (hasRequestScopeInspector(options.rootContainer) ? options.rootContainer.hasRequestScopedDependency(handler.controllerToken) : true),
|
|
156
|
+
routeGuards
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function handlerMayRequireRequestScope(plan, request) {
|
|
160
|
+
return plan.requiresRequestScope || compiledMiddlewareMayRequireRequestScope(plan.requestScope, request);
|
|
161
|
+
}
|
|
162
|
+
function compileDispatchStartPlan(observers, appMiddleware) {
|
|
163
|
+
const requestScope = compileMiddlewareScopePlan(appMiddleware);
|
|
164
|
+
return {
|
|
165
|
+
requestScope,
|
|
166
|
+
requiresRequestScope: observers.length > 0 || requestScope.alwaysRequiresRequestScope
|
|
167
|
+
};
|
|
146
168
|
}
|
|
147
|
-
function dispatchStartMayRequireRequestScope(
|
|
148
|
-
return
|
|
169
|
+
function dispatchStartMayRequireRequestScope(plan, request) {
|
|
170
|
+
return plan.requiresRequestScope || compiledMiddlewareMayRequireRequestScope(plan.requestScope, request);
|
|
149
171
|
}
|
|
150
172
|
function ensureRequestScope(context) {
|
|
151
173
|
if (context.dispatchScope.requestScoped) {
|
|
@@ -197,8 +219,8 @@ function mergeInterceptors(globalInterceptors, routeInterceptors) {
|
|
|
197
219
|
}
|
|
198
220
|
return [...globalInterceptors, ...routeInterceptors];
|
|
199
221
|
}
|
|
200
|
-
async function dispatchMatchedHandler(handler, requestContext, controllerContainer, observers, contentNegotiation, binder,
|
|
201
|
-
const routeGuards =
|
|
222
|
+
async function dispatchMatchedHandler(handler, executionPlan, requestContext, controllerContainer, observers, contentNegotiation, binder, logger) {
|
|
223
|
+
const routeGuards = executionPlan.routeGuards;
|
|
202
224
|
if (routeGuards.length > 0) {
|
|
203
225
|
const guardContext = {
|
|
204
226
|
handler,
|
|
@@ -209,8 +231,7 @@ async function dispatchMatchedHandler(handler, requestContext, controllerContain
|
|
|
209
231
|
if (requestContext.response.committed) {
|
|
210
232
|
return;
|
|
211
233
|
}
|
|
212
|
-
const
|
|
213
|
-
const result = globalInterceptors.length === 0 && routeInterceptors.length === 0 ? await invokeControllerHandler(handler, requestContext, binder, controllerContainer) : await runInterceptorChain(mergeInterceptors(globalInterceptors, routeInterceptors), {
|
|
234
|
+
const result = executionPlan.mergedInterceptors.length === 0 ? await invokeControllerHandler(handler, requestContext, binder, controllerContainer) : await runInterceptorChain(executionPlan.mergedInterceptors, {
|
|
214
235
|
handler,
|
|
215
236
|
requestContext
|
|
216
237
|
}, async () => invokeControllerHandler(handler, requestContext, binder, controllerContainer));
|
|
@@ -222,6 +243,15 @@ async function dispatchMatchedHandler(handler, requestContext, controllerContain
|
|
|
222
243
|
await observer.onRequestSuccess?.(context, result);
|
|
223
244
|
}, logger, handler);
|
|
224
245
|
}
|
|
246
|
+
function resolveHandlerExecutionPlan(handler, executionPlans, options) {
|
|
247
|
+
const cached = executionPlans.get(handler);
|
|
248
|
+
if (cached) {
|
|
249
|
+
return cached;
|
|
250
|
+
}
|
|
251
|
+
const compiled = compileHandlerExecutionPlan(handler, options);
|
|
252
|
+
executionPlans.set(handler, compiled);
|
|
253
|
+
return compiled;
|
|
254
|
+
}
|
|
225
255
|
async function notifyRequestStart(context) {
|
|
226
256
|
await notifyObserversSafely(context.observers, context.requestContext, async (observer, observationContext) => {
|
|
227
257
|
await observer.onRequestStart?.(observationContext);
|
|
@@ -255,7 +285,8 @@ async function runDispatchPipeline(context) {
|
|
|
255
285
|
}
|
|
256
286
|
const match = readFrameworkRequestNativeRouteHandoff(appMiddlewareContext.request) ?? matchHandlerOrThrow(context.options.handlerMapping, appMiddlewareContext.request);
|
|
257
287
|
context.matchedHandler = match.descriptor;
|
|
258
|
-
|
|
288
|
+
const executionPlan = resolveHandlerExecutionPlan(match.descriptor, context.handlerExecutionPlans, context.options);
|
|
289
|
+
if (handlerMayRequireRequestScope(executionPlan, appMiddlewareContext.request)) {
|
|
259
290
|
ensureRequestScope(context);
|
|
260
291
|
}
|
|
261
292
|
updateRequestParams(context.requestContext, match.params);
|
|
@@ -266,7 +297,7 @@ async function runDispatchPipeline(context) {
|
|
|
266
297
|
response: context.response
|
|
267
298
|
};
|
|
268
299
|
await runMiddlewareChain(match.descriptor.metadata.moduleMiddleware ?? [], moduleMiddlewareContext, async () => {
|
|
269
|
-
await dispatchMatchedHandler(match.descriptor, context.requestContext, context.dispatchScope.container, context.observers, context.contentNegotiation, context.options.binder, context.options.
|
|
300
|
+
await dispatchMatchedHandler(match.descriptor, executionPlan, context.requestContext, context.dispatchScope.container, context.observers, context.contentNegotiation, context.options.binder, context.options.logger);
|
|
270
301
|
});
|
|
271
302
|
});
|
|
272
303
|
}
|
|
@@ -291,13 +322,19 @@ async function handleDispatchError(context, error) {
|
|
|
291
322
|
export function createDispatcher(options) {
|
|
292
323
|
const contentNegotiation = resolveContentNegotiation(options.contentNegotiation);
|
|
293
324
|
const observers = options.observers ?? [];
|
|
325
|
+
const appMiddleware = options.appMiddleware ?? [];
|
|
326
|
+
const dispatchStartPlan = compileDispatchStartPlan(observers, appMiddleware);
|
|
327
|
+
const handlerExecutionPlans = new WeakMap();
|
|
328
|
+
for (const descriptor of options.handlerMapping.descriptors) {
|
|
329
|
+
handlerExecutionPlans.set(descriptor, compileHandlerExecutionPlan(descriptor, options));
|
|
330
|
+
}
|
|
294
331
|
const dispatcher = {
|
|
295
332
|
describeRoutes() {
|
|
296
333
|
return options.handlerMapping.descriptors.map(descriptor => cloneHandlerDescriptor(descriptor));
|
|
297
334
|
},
|
|
298
335
|
async dispatch(request, response) {
|
|
299
336
|
const dispatchRequest = createDispatchRequest(request);
|
|
300
|
-
const dispatchScope = dispatchStartMayRequireRequestScope(
|
|
337
|
+
const dispatchScope = dispatchStartMayRequireRequestScope(dispatchStartPlan, dispatchRequest) ? createRequestDispatchScope(options.rootContainer) : createRootDispatchScope(options.rootContainer);
|
|
301
338
|
let phaseContext;
|
|
302
339
|
let containerPromotionOpen = true;
|
|
303
340
|
const requestContext = createDispatchContext(dispatchRequest, response, dispatchScope.container, () => {
|
|
@@ -310,6 +347,7 @@ export function createDispatcher(options) {
|
|
|
310
347
|
phaseContext = {
|
|
311
348
|
contentNegotiation,
|
|
312
349
|
dispatchScope,
|
|
350
|
+
handlerExecutionPlans,
|
|
313
351
|
observers,
|
|
314
352
|
options,
|
|
315
353
|
requestContext,
|