@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.
@@ -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;AAiDhI;;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;CA+DjF"}
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"}
@@ -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
- const converterCache = new Map();
112
- const globalConverters = (await Promise.all(this.converters.map(converter => resolveConverter(converter, context, converterCache)))).filter(converter => Boolean(converter));
113
- validateBodyKeys(context.requestContext.request, plan.bodyKeys);
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(context.requestContext.request);
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
- const fieldConverter = await resolveConverter(entry.converter, context, converterCache);
142
- if (fieldConverter) {
143
- convertedValue = await fieldConverter.convert(convertedValue, target);
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,EAAmD,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEtH,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,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACvD;AAqBD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,WAAW,GAAG,sBAAsB,CA4BlF"}
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
- propertyKeys: entries.map(entry => entry.propertyKey)
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 filterUnboundRequestDtoFields;
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,6BAA6B;IAiB/B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5D,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAWzE"}
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
- filterUnboundRequestDtoFields(value, target) {
17
- if (typeof value !== 'object' || value === null) {
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 filteredValue = this.filterUnboundRequestDtoFields(value, target);
32
- await this.validator.validate(filteredValue, target);
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;AAIxD,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,CAuBlB"}
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 input = handler.route.request ? await binder.bind(handler.route.request, argumentResolverContext) : undefined;
27
- if (handler.route.request) {
28
- await defaultValidator.validate(input, handler.route.request);
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;AAanE,OAAO,KAAK,EACV,MAAM,EACN,yBAAyB,EACzB,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EAGjB,cAAc,EACd,eAAe,EAEf,cAAc,EAId,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB;;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;AA8bD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,UAAU,CAuD7E"}
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 { createRequestContext, runWithRequestContext } from '../context/request-context.js';
11
- import { SseResponse } from '../context/sse.js';
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 handlerMayRequireRequestScope(handler, request, options) {
130
- if (handler.route.guards && handler.route.guards.length > 0) {
131
- return true;
132
- }
133
- if ((options.interceptors ?? []).length > 0 || (handler.route.interceptors ?? []).length > 0) {
134
- return true;
135
- }
136
- if (activeMiddlewareMayRequireRequestScope(handler.metadata.moduleMiddleware, request)) {
137
- return true;
138
- }
139
- if (requestDtoMayRequireRequestScope(handler, options)) {
140
- return true;
141
- }
142
- if (handlerMethodMayUseRequestContext(handler)) {
143
- return true;
144
- }
145
- return hasRequestScopeInspector(options.rootContainer) ? options.rootContainer.hasRequestScopedDependency(handler.controllerToken) : true;
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(request, observers, options) {
148
- return observers.length > 0 || activeMiddlewareMayRequireRequestScope(options.appMiddleware ?? [], request);
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, globalInterceptors, logger) {
201
- const routeGuards = handler.route.guards ?? [];
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 routeInterceptors = handler.route.interceptors ?? [];
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
- if (handlerMayRequireRequestScope(match.descriptor, appMiddlewareContext.request, context.options)) {
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.interceptors ?? [], context.options.logger);
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(dispatchRequest, observers, options) ? createRequestDispatchScope(options.rootContainer) : createRootDispatchScope(options.rootContainer);
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,
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "controller",
11
11
  "rest"
12
12
  ],
13
- "version": "1.0.0-beta.4",
13
+ "version": "1.0.0-beta.5",
14
14
  "private": false,
15
15
  "license": "MIT",
16
16
  "repository": {