@travetto/web 6.0.2 → 7.0.0-rc.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.
@@ -0,0 +1,185 @@
1
+ import { RegistryAdapter } from '@travetto/registry';
2
+ import { AppError, asFull, castTo, Class, RetainPrimitiveFields, safeAssign } from '@travetto/runtime';
3
+ import { WebHeaders } from '@travetto/web';
4
+ import { SchemaParameterConfig, SchemaRegistryIndex } from '@travetto/schema';
5
+
6
+ import { ControllerConfig, EndpointConfig, EndpointParameterConfig, EndpointParamLocation } from './types';
7
+ import type { WebInterceptor } from '../types/interceptor.ts';
8
+
9
+ function combineCommon<T extends ControllerConfig | EndpointConfig>(base: T, override: Partial<T>): T {
10
+ base.filters = [...(base.filters ?? []), ...(override.filters ?? [])];
11
+ base.interceptorConfigs = [...(base.interceptorConfigs ?? []), ...(override.interceptorConfigs ?? [])];
12
+ base.interceptorExclude = base.interceptorExclude ?? override.interceptorExclude;
13
+ base.responseHeaders = { ...override.responseHeaders, ...base.responseHeaders };
14
+ base.responseContext = { ...override.responseContext, ...base.responseContext };
15
+ return base;
16
+ }
17
+
18
+ function combineClassConfigs(base: ControllerConfig, ...overrides: Partial<ControllerConfig>[]): ControllerConfig {
19
+ for (const override of overrides) {
20
+ combineCommon(base, override);
21
+ Object.assign(base, {
22
+ basePath: override.basePath || base.basePath,
23
+ contextParams: { ...base.contextParams, ...override.contextParams },
24
+ });
25
+ }
26
+ // Ensure we have full path
27
+ if (!base.basePath.startsWith('/')) {
28
+ base.basePath = `/${base.basePath}`;
29
+ }
30
+ return base;
31
+ }
32
+
33
+ function combineEndpointConfigs(ctrl: ControllerConfig, base: EndpointConfig, ...overrides: Partial<EndpointConfig>[]): EndpointConfig {
34
+ for (const override of overrides) {
35
+ combineCommon(base, override);
36
+ Object.assign(
37
+ base,
38
+ {
39
+ cacheable: override.cacheable ?? base.cacheable,
40
+ httpMethod: override.httpMethod ?? base.httpMethod,
41
+ allowsBody: override.allowsBody ?? base.allowsBody,
42
+ path: override.path || base.path,
43
+ parameters: (override.parameters ?? base.parameters).map(x => ({ ...x })),
44
+ responseFinalizer: override.responseFinalizer ?? base.responseFinalizer,
45
+ }
46
+ );
47
+ }
48
+ // Ensure we have full path
49
+ if (!base.path.startsWith('/')) {
50
+ base.path = `/${base.path}`;
51
+ }
52
+ return base;
53
+ }
54
+
55
+ /**
56
+ * Compute the location of a parameter within an endpoint
57
+ */
58
+ function computeParameterLocation(ep: EndpointConfig, schema: SchemaParameterConfig): EndpointParamLocation {
59
+ const name = schema?.name;
60
+ if (!SchemaRegistryIndex.has(schema.type)) {
61
+ if ((schema.type === String || schema.type === Number) && name && ep.path.includes(`:${name.toString()}`)) {
62
+ return 'path';
63
+ } else if (schema.type === Blob || schema.type === File || schema.type === ArrayBuffer || schema.type === Uint8Array) {
64
+ return 'body';
65
+ }
66
+ return 'query';
67
+ } else {
68
+ return ep.allowsBody ? 'body' : 'query';
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Adapter for controller registry
74
+ */
75
+ export class ControllerRegistryAdapter implements RegistryAdapter<ControllerConfig> {
76
+ #config: ControllerConfig;
77
+ #endpoints: Map<string | symbol, EndpointConfig> = new Map();
78
+ #cls: Class;
79
+ #finalizeHandlers: Function[] = [];
80
+
81
+ constructor(cls: Class) {
82
+ this.#cls = cls;
83
+ }
84
+
85
+ register(...data: Partial<ControllerConfig>[]): ControllerConfig {
86
+ this.#config ??= {
87
+ class: this.#cls,
88
+ filters: [],
89
+ interceptorConfigs: [],
90
+ basePath: '',
91
+ externalName: this.#cls.name.replace(/(Controller|Web|Service)$/, ''),
92
+ endpoints: [],
93
+ contextParams: {},
94
+ responseHeaders: {}
95
+ };
96
+ combineClassConfigs(this.#config, ...data);
97
+ return this.#config;
98
+ }
99
+
100
+ registerEndpoint(method: string | symbol, ...data: Partial<EndpointConfig>[]): EndpointConfig {
101
+ this.register();
102
+
103
+ if (!this.#endpoints.has(method)) {
104
+ const endpointConfig = asFull<EndpointConfig>({
105
+ path: '/',
106
+ fullPath: '/',
107
+ cacheable: false,
108
+ allowsBody: false,
109
+ class: this.#cls,
110
+ filters: [],
111
+ endpoint: this.#cls.prototype[method],
112
+ methodName: method.toString(),
113
+ id: `${this.#cls.name}#${method.toString()}`,
114
+ parameters: [],
115
+ interceptorConfigs: [],
116
+ responseHeaders: {},
117
+ finalizedResponseHeaders: new WebHeaders(),
118
+ responseFinalizer: undefined
119
+ });
120
+ this.#config.endpoints.push(endpointConfig);
121
+ this.#endpoints.set(method, endpointConfig);
122
+ }
123
+
124
+ combineEndpointConfigs(this.#config, this.#endpoints.get(method)!, ...data);
125
+ return this.#endpoints.get(method)!;
126
+ }
127
+
128
+ registerEndpointParameter(method: string | symbol, index: number, ...config: Partial<EndpointParameterConfig>[]): EndpointParameterConfig {
129
+ const ep = this.registerEndpoint(method);
130
+ ep.parameters[index] ??= { index, location: 'query' };
131
+ safeAssign(ep.parameters[index], ...config);
132
+ return ep.parameters[index];
133
+ }
134
+
135
+ finalize(): void {
136
+ // Merge into controller
137
+ for (const ep of this.#config.endpoints) {
138
+ // Store full path from base for use in other contexts
139
+ ep.fullPath = `/${this.#config.basePath}/${ep.path}`.replace(/[/]{1,4}/g, '/').replace(/(.)[/]$/, (_, a) => a);
140
+ ep.finalizedResponseHeaders = new WebHeaders({ ...this.#config.responseHeaders, ...ep.responseHeaders });
141
+ ep.responseContext = { ...this.#config.responseContext, ...ep.responseContext };
142
+ for (const schema of SchemaRegistryIndex.getMethodConfig(this.#cls, ep.methodName).parameters) {
143
+ ep.parameters[schema.index!] ??= { index: schema.index!, location: undefined! };
144
+ ep.parameters[schema.index!].location ??= computeParameterLocation(ep, schema);
145
+ }
146
+ }
147
+ for (const item of this.#finalizeHandlers) {
148
+ item();
149
+ }
150
+ this.#finalizeHandlers = [];
151
+ }
152
+
153
+ get(): ControllerConfig {
154
+ return this.#config;
155
+ }
156
+
157
+ getEndpointConfig(method: string | symbol): EndpointConfig {
158
+ const endpoint = this.#endpoints.get(method);
159
+ if (!endpoint) {
160
+ throw new AppError(`Endpoint not registered: ${String(method)} on ${this.#cls.name}`);
161
+ }
162
+ return endpoint;
163
+ }
164
+
165
+ registerInterceptorConfig<T extends WebInterceptor>(
166
+ cls: Class<T>,
167
+ config: Partial<RetainPrimitiveFields<T['config']>>,
168
+ extra?: Partial<EndpointConfig & ControllerConfig>
169
+ ): ControllerConfig {
170
+ return this.register({ interceptorConfigs: [[cls, castTo(config)]], ...extra });
171
+ }
172
+
173
+ registerEndpointInterceptorConfig<T extends WebInterceptor>(
174
+ property: string | symbol,
175
+ cls: Class<T>,
176
+ config: Partial<RetainPrimitiveFields<T['config']>>,
177
+ extra?: Partial<EndpointConfig>
178
+ ): EndpointConfig {
179
+ return this.registerEndpoint(property, { interceptorConfigs: [[cls, castTo(config)]], ...extra });
180
+ }
181
+
182
+ registerFinalizeHandler(fn: () => void): void {
183
+ this.#finalizeHandlers.push(fn);
184
+ }
185
+ }
@@ -0,0 +1,110 @@
1
+ import { ChangeEvent, RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
2
+ import { Class, ClassInstance, getClass, RetainPrimitiveFields } from '@travetto/runtime';
3
+ import { DependencyRegistryIndex } from '@travetto/di';
4
+ import { SchemaRegistryIndex } from '@travetto/schema';
5
+
6
+ import { ControllerRegistryAdapter } from './registry-adapter';
7
+ import { ControllerConfig, EndpointConfig, EndpointDecorator } from './types';
8
+ import { WebAsyncContext } from '../context';
9
+ import type { WebInterceptor } from '../types/interceptor.ts';
10
+
11
+ const isClass = (property: unknown, target: unknown): target is Class => !property;
12
+
13
+ export class ControllerRegistryIndex implements RegistryIndex {
14
+
15
+ static #instance = Registry.registerIndex(this);
16
+
17
+ static getClasses(): Class[] {
18
+ return this.#instance.store.getClasses();
19
+ }
20
+
21
+ static getForRegister(cls: Class): ControllerRegistryAdapter {
22
+ return this.#instance.store.getForRegister(cls);
23
+ }
24
+
25
+ static getConfig(cls: Class): ControllerConfig {
26
+ return this.#instance.store.get(cls).get();
27
+ }
28
+
29
+ static getEndpointConfigById(id: string): EndpointConfig | undefined {
30
+ return this.#instance.getEndpointById(id);
31
+ }
32
+
33
+ static bindContextParamsOnPostConstruct(cls: Class): void {
34
+ this.#instance.bindContextParamsOnPostConstruct(cls);
35
+ }
36
+
37
+ /**
38
+ * Register a controller/endpoint with specific config for an interceptor
39
+ * @param cls The interceptor to register data for
40
+ * @param cfg The partial config override
41
+ */
42
+ static createInterceptorConfigDecorator<T extends WebInterceptor>(
43
+ cls: Class<T>,
44
+ cfg: Partial<RetainPrimitiveFields<T['config']>>,
45
+ extra?: Partial<EndpointConfig & ControllerConfig>
46
+ ): EndpointDecorator {
47
+ return (instanceOrCls: Class | ClassInstance, property?: symbol | string): void => {
48
+ const adapter = ControllerRegistryIndex.getForRegister(getClass(instanceOrCls));
49
+ if (isClass(property, instanceOrCls)) {
50
+ adapter.registerInterceptorConfig(cls, cfg, extra);
51
+ } else {
52
+ adapter.registerEndpointInterceptorConfig(property!, cls, cfg, extra);
53
+ }
54
+ };
55
+ }
56
+
57
+ #endpointsById = new Map<string, EndpointConfig>();
58
+
59
+ store = new RegistryIndexStore(ControllerRegistryAdapter);
60
+
61
+ async #bindContextParams<T>(instance: ClassInstance<T>): Promise<void> {
62
+ const ctx = await DependencyRegistryIndex.getInstance(WebAsyncContext);
63
+ const cls = getClass(instance);
64
+ const map = this.getController(cls).contextParams;
65
+ for (const field of Object.keys(map)) {
66
+ const { type } = SchemaRegistryIndex.getFieldMap(cls)[field];
67
+ Object.defineProperty(instance, field, { get: ctx.getSource(type) });
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Register a controller to bind context params on post construct
73
+ * @param target Controller class
74
+ */
75
+ bindContextParamsOnPostConstruct(cls: Class): void {
76
+ DependencyRegistryIndex.registerClassMetadata(cls, {
77
+ postConstruct: {
78
+ ContextParam: (instance: ClassInstance) => this.#bindContextParams(instance)
79
+ }
80
+ });
81
+ }
82
+
83
+ getController(cls: Class): ControllerConfig {
84
+ return this.store.get(cls).get();
85
+ }
86
+
87
+ getEndpoint(cls: Class, method: string | symbol): EndpointConfig {
88
+ return this.getController(cls).endpoints.find(e => e.methodName === method)!;
89
+ }
90
+
91
+ getEndpointById(id: string): EndpointConfig | undefined {
92
+ return this.#endpointsById.get(id.replace(':', '#'));
93
+ }
94
+
95
+ process(events: ChangeEvent<Class>[]): void {
96
+ for (const evt of events) {
97
+ if ('curr' in evt) {
98
+ for (const ep of this.getController(evt.curr).endpoints) {
99
+ this.#endpointsById.set(`${evt.curr.name}#${ep.methodName.toString()}`, ep);
100
+ }
101
+ } else {
102
+ // Match by name
103
+ const toDelete = [...this.#endpointsById.values()].filter(x => x.class.name === evt.prev.name);
104
+ for (const ep of toDelete) {
105
+ this.#endpointsById.delete(ep.id);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Any, Class, TypedFunction } from '@travetto/runtime';
2
- import type { FieldConfig, ClassConfig } from '@travetto/schema';
2
+ import type { SchemaClassConfig, SchemaParameterConfig } from '@travetto/schema';
3
3
 
4
4
  import type { WebInterceptor } from '../types/interceptor.ts';
5
5
  import type { WebChainedFilter, WebFilter } from '../types/filter.ts';
@@ -16,35 +16,10 @@ export type EndpointFunctionDescriptor = TypedPropertyDescriptor<EndpointFunctio
16
16
  */
17
17
  export type EndpointDecorator = (
18
18
  (<T extends Class>(target: T) => void) &
19
- (<U>(target: U, prop: string, descriptor?: EndpointFunctionDescriptor) => void)
19
+ (<U>(target: U, prop: string | symbol, descriptor?: EndpointFunctionDescriptor) => void)
20
20
  );
21
21
 
22
- /**
23
- * Endpoint type
24
- */
25
- export type EndpointIOType = {
26
- type: Class;
27
- array?: boolean;
28
- description?: string;
29
- };
30
-
31
- /**
32
- * Describable elements
33
- */
34
- export interface DescribableConfig {
35
- /**
36
- * The title
37
- */
38
- title?: string;
39
- /**
40
- * Description
41
- */
42
- description?: string;
43
- /**
44
- * Is the resource documented
45
- */
46
- documented?: boolean;
47
- }
22
+ export type EndpointParamLocation = 'path' | 'query' | 'body' | 'header';
48
23
 
49
24
  /**
50
25
  * Core configuration for endpoints and controllers
@@ -85,21 +60,17 @@ interface CoreConfig {
85
60
  }
86
61
 
87
62
  /**
88
- * Endpoint param configuration
63
+ * Endpoint parameter configuration
89
64
  */
90
- export interface EndpointParamConfig {
91
- /**
92
- * Name of the parameter
93
- */
94
- name?: string;
65
+ export interface EndpointParameterConfig {
95
66
  /**
96
- * Raw text of parameter at source
67
+ * Index of parameter
97
68
  */
98
- sourceText?: string;
69
+ index: number;
99
70
  /**
100
71
  * Location of the parameter
101
72
  */
102
- location: 'path' | 'query' | 'body' | 'header';
73
+ location: EndpointParamLocation;
103
74
  /**
104
75
  * Resolves the value by executing with req/res as input
105
76
  */
@@ -108,7 +79,7 @@ export interface EndpointParamConfig {
108
79
  * Extract the value from request
109
80
  * @param context The http context with the endpoint param config
110
81
  */
111
- extract?: (ctx: WebRequest, config: EndpointParamConfig) => unknown;
82
+ extract?: (ctx: WebRequest, config: EndpointParameterConfig) => unknown;
112
83
  /**
113
84
  * Input prefix for parameter
114
85
  */
@@ -118,15 +89,15 @@ export interface EndpointParamConfig {
118
89
  /**
119
90
  * Endpoint configuration
120
91
  */
121
- export interface EndpointConfig extends CoreConfig, DescribableConfig {
92
+ export interface EndpointConfig extends CoreConfig {
122
93
  /**
123
94
  * Unique identifier
124
95
  */
125
96
  id: string;
126
97
  /**
127
- * The name of the method
98
+ * Name of the endpoint (method name)
128
99
  */
129
- name: string;
100
+ methodName: string | symbol;
130
101
  /**
131
102
  * Instance the endpoint is for
132
103
  */
@@ -158,15 +129,7 @@ export interface EndpointConfig extends CoreConfig, DescribableConfig {
158
129
  /**
159
130
  * List of params for the endpoint
160
131
  */
161
- params: EndpointParamConfig[];
162
- /**
163
- * The response type
164
- */
165
- responseType?: EndpointIOType;
166
- /**
167
- * The request type
168
- */
169
- requestType?: EndpointIOType;
132
+ parameters: EndpointParameterConfig[];
170
133
  /**
171
134
  * Full path including controller
172
135
  */
@@ -184,7 +147,7 @@ export interface EndpointConfig extends CoreConfig, DescribableConfig {
184
147
  /**
185
148
  * Controller configuration
186
149
  */
187
- export interface ControllerConfig extends CoreConfig, DescribableConfig {
150
+ export interface ControllerConfig extends CoreConfig {
188
151
  /**
189
152
  * The base path of the controller
190
153
  */
@@ -200,13 +163,13 @@ export interface ControllerConfig extends CoreConfig, DescribableConfig {
200
163
  /**
201
164
  * Context parameters to bind at create
202
165
  */
203
- contextParams: Record<string, Class>;
166
+ contextParams: Record<string | symbol, boolean>;
204
167
  }
205
168
 
206
169
  /**
207
170
  * Controller visitor options
208
171
  */
209
- export type ControllerVisitorOptions = { skipUndocumented?: boolean };
172
+ export type ControllerVisitorOptions = { skipPrivate?: boolean };
210
173
 
211
174
  /**
212
175
  * Controller visitor pattern
@@ -218,10 +181,10 @@ export interface ControllerVisitor<T = unknown> {
218
181
  onControllerStart?(controller: ControllerConfig): Promise<unknown> | unknown;
219
182
  onControllerEnd?(controller: ControllerConfig): Promise<unknown> | unknown;
220
183
 
221
- onEndpointStart?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: FieldConfig[]): Promise<unknown> | unknown;
222
- onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: FieldConfig[]): Promise<unknown> | unknown;
184
+ onEndpointStart?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: SchemaParameterConfig[]): Promise<unknown> | unknown;
185
+ onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: SchemaParameterConfig[]): Promise<unknown> | unknown;
223
186
 
224
- onSchema?(schema: ClassConfig): Promise<unknown> | unknown;
187
+ onSchema?(schema: SchemaClassConfig): Promise<unknown> | unknown;
225
188
 
226
189
  onControllerAdd?(cls: Class): Promise<unknown> | unknown;
227
190
  onControllerRemove?(cls: Class): Promise<unknown> | unknown;
@@ -1,8 +1,8 @@
1
1
  import { Class } from '@travetto/runtime';
2
- import { SchemaRegistry } from '@travetto/schema';
2
+ import { SchemaRegistryIndex } from '@travetto/schema';
3
3
 
4
4
  import { ControllerVisitor, ControllerVisitorOptions } from './types.ts';
5
- import { ControllerRegistry } from './controller.ts';
5
+ import { ControllerRegistryIndex } from './registry-index.ts';
6
6
 
7
7
  /**
8
8
  * Supports visiting the controller structure
@@ -10,7 +10,7 @@ import { ControllerRegistry } from './controller.ts';
10
10
  export class ControllerVisitUtil {
11
11
 
12
12
  static #onSchemaEvent(visitor: ControllerVisitor, type?: Class): unknown | Promise<unknown> {
13
- return type && SchemaRegistry.has(type) ? visitor.onSchema?.(SchemaRegistry.get(type)) : undefined;
13
+ return type && SchemaRegistryIndex.has(type) ? visitor.onSchema?.(SchemaRegistryIndex.getConfig(type)) : undefined;
14
14
  }
15
15
 
16
16
  static async visitController(visitor: ControllerVisitor, cls: Class, options: ControllerVisitorOptions = {}): Promise<void> {
@@ -18,23 +18,26 @@ export class ControllerVisitUtil {
18
18
  options = Object.assign(visitor.getOptions(), options);
19
19
  }
20
20
 
21
- options.skipUndocumented ??= true;
21
+ options.skipPrivate ??= true;
22
22
 
23
- const controller = ControllerRegistry.get(cls);
24
- if (!controller || controller.documented === false && options.skipUndocumented) {
23
+ const controller = ControllerRegistryIndex.getConfig(cls);
24
+ const schema = SchemaRegistryIndex.getConfig(cls);
25
+ if (schema.private === true && options.skipPrivate) {
25
26
  return;
26
27
  }
27
28
 
28
29
  await visitor.onControllerStart?.(controller);
29
30
  for (const endpoint of controller.endpoints) {
30
- if (endpoint.documented === false && options.skipUndocumented) {
31
+ const endpointSchema = SchemaRegistryIndex.getMethodConfig(cls, endpoint.methodName);
32
+ if (endpointSchema.private === true && options.skipPrivate) {
31
33
  continue;
32
34
  }
33
35
 
34
- const params = SchemaRegistry.getMethodSchema(cls, endpoint.name);
36
+ const { parameters: params, returnType } = SchemaRegistryIndex.getMethodConfig(cls, endpoint.methodName);
35
37
  await visitor.onEndpointStart?.(endpoint, controller, params);
36
- await this.#onSchemaEvent(visitor, endpoint.responseType?.type);
37
- await this.#onSchemaEvent(visitor, endpoint.requestType?.type);
38
+ if (returnType) {
39
+ await this.#onSchemaEvent(visitor, returnType.type);
40
+ }
38
41
  for (const param of params) {
39
42
  await this.#onSchemaEvent(visitor, param.type);
40
43
  }
@@ -44,7 +47,7 @@ export class ControllerVisitUtil {
44
47
  }
45
48
 
46
49
  static async visit<T = unknown>(visitor: ControllerVisitor<T>, options: ControllerVisitorOptions = {}): Promise<T> {
47
- for (const cls of ControllerRegistry.getClasses()) {
50
+ for (const cls of ControllerRegistryIndex.getClasses()) {
48
51
  await this.visitController(visitor, cls, options);
49
52
  }
50
53
  return await visitor.onComplete?.() ?? undefined!;
@@ -1,9 +1,9 @@
1
1
  import { Class, toConcrete } from '@travetto/runtime';
2
- import { DependencyRegistry } from '@travetto/di';
2
+ import { DependencyRegistryIndex, Injectable } from '@travetto/di';
3
+ import { ControllerRegistryIndex } from '@travetto/web';
4
+ import { Registry } from '@travetto/registry';
3
5
 
4
6
  import { ControllerConfig, EndpointConfig } from '../registry/types.ts';
5
- import { ControllerRegistry } from '../registry/controller.ts';
6
-
7
7
  import type { WebRouter } from '../types/dispatch.ts';
8
8
  import { WebInterceptor } from '../types/interceptor.ts';
9
9
  import { WebResponse } from '../types/response.ts';
@@ -14,13 +14,14 @@ import { EndpointUtil } from '../util/endpoint.ts';
14
14
  /**
15
15
  * Supports the base pattern for the most common web router implementations
16
16
  */
17
+ @Injectable()
17
18
  export abstract class BaseWebRouter implements WebRouter {
18
19
 
19
20
  #cleanup = new Map<string, Function>();
20
21
  #interceptors: WebInterceptor[];
21
22
 
22
23
  async #register(c: Class): Promise<void> {
23
- const config = ControllerRegistry.get(c);
24
+ const config = ControllerRegistryIndex.getConfig(c);
24
25
 
25
26
  let endpoints = await EndpointUtil.getBoundEndpoints(c);
26
27
  endpoints = EndpointUtil.orderEndpoints(endpoints);
@@ -29,7 +30,6 @@ export abstract class BaseWebRouter implements WebRouter {
29
30
  ep.filter = EndpointUtil.createEndpointHandler(this.#interceptors, ep, config);
30
31
  }
31
32
 
32
- console.debug('Registering Controller Instance', { id: config.class.Ⲑid, path: config.basePath, endpointCount: endpoints.length });
33
33
  const fn = await this.register(endpoints, config);
34
34
  this.#cleanup.set(c.Ⲑid, fn);
35
35
  };
@@ -39,27 +39,29 @@ export abstract class BaseWebRouter implements WebRouter {
39
39
  */
40
40
  async postConstruct(): Promise<void> {
41
41
 
42
- this.#interceptors = await DependencyRegistry.getCandidateInstances(toConcrete<WebInterceptor>());
42
+ this.#interceptors = await DependencyRegistryIndex.getInstances(toConcrete<WebInterceptor>());
43
43
  this.#interceptors = EndpointUtil.orderInterceptors(this.#interceptors);
44
44
  const names = this.#interceptors.map(x => x.constructor.name);
45
45
  console.debug('Sorting interceptors', { count: names.length, names });
46
46
 
47
47
  // Register all active
48
- for (const c of ControllerRegistry.getClasses()) {
48
+ for (const c of ControllerRegistryIndex.getClasses()) {
49
49
  await this.#register(c);
50
50
  }
51
51
 
52
52
  // Listen for updates
53
- ControllerRegistry.on(async e => {
54
- console.debug('Registry event', { type: e.type, target: (e.curr ?? e.prev)?.Ⲑid });
55
- if (e.prev && ControllerRegistry.hasExpired(e.prev)) {
53
+ Registry.onClassChange(async e => {
54
+ const targetCls = 'curr' in e ? e.curr : e.prev;
55
+ console.debug('Registry event', { type: e.type, target: targetCls.Ⲑid });
56
+
57
+ if ('prev' in e) {
56
58
  this.#cleanup.get(e.prev.Ⲑid)?.();
57
59
  this.#cleanup.delete(e.prev.Ⲑid);
58
60
  }
59
- if (e.curr) {
61
+ if ('curr' in e) {
60
62
  await this.#register(e.curr);
61
63
  }
62
- });
64
+ }, ControllerRegistryIndex);
63
65
  }
64
66
 
65
67
  abstract register(endpoints: EndpointConfig[], controller: ControllerConfig): Promise<() => void>;
@@ -15,7 +15,7 @@ export class WebHeaders extends Headers {
15
15
 
16
16
  if (o && !passed) {
17
17
  for (const [k, v] of (Array.isArray(o) ? o : Object.entries(o))) {
18
- if (v !== undefined && v !== null) {
18
+ if (v !== undefined && v !== null && !k.startsWith(':')) {
19
19
  this.append(k, castTo(v));
20
20
  }
21
21
  }
@@ -24,13 +24,13 @@ const ERROR_CATEGORY_STATUS: Record<ErrorCategory, number> = {
24
24
  unavailable: 503,
25
25
  };
26
26
 
27
- export class WebCommonUtil {
28
- static #unitMapping: Record<string, number> = {
29
- kb: 2 ** 10,
30
- mb: 2 ** 20,
31
- gb: 2 ** 30,
32
- };
27
+ const UNIT_MAPPING: Record<string, number> = {
28
+ kb: 2 ** 10,
29
+ mb: 2 ** 20,
30
+ gb: 2 ** 30,
31
+ };
33
32
 
33
+ export class WebCommonUtil {
34
34
  static #convert(rule: string): RegExp {
35
35
  const core = (rule.endsWith('/*') || !rule.includes('/')) ?
36
36
  `${rule.replace(/[/].{0,20}$/, '')}\/.*` : rule;
@@ -148,6 +148,6 @@ export class WebCommonUtil {
148
148
  return input;
149
149
  }
150
150
  const [, num, unit] = input.toLowerCase().split(/(\d+)/);
151
- return parseInt(num, 10) * (this.#unitMapping[unit] ?? 1);
151
+ return parseInt(num, 10) * (UNIT_MAPPING[unit] ?? 1);
152
152
  }
153
153
  }