@travetto/web 6.0.3 → 7.0.0-rc.1

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,184 @@
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
+ methodName: method.toString(),
112
+ id: `${this.#cls.name}#${method.toString()}`,
113
+ parameters: [],
114
+ interceptorConfigs: [],
115
+ responseHeaders: {},
116
+ finalizedResponseHeaders: new WebHeaders(),
117
+ responseFinalizer: undefined
118
+ });
119
+ this.#config.endpoints.push(endpointConfig);
120
+ this.#endpoints.set(method, endpointConfig);
121
+ }
122
+
123
+ combineEndpointConfigs(this.#config, this.#endpoints.get(method)!, ...data);
124
+ return this.#endpoints.get(method)!;
125
+ }
126
+
127
+ registerEndpointParameter(method: string | symbol, index: number, ...config: Partial<EndpointParameterConfig>[]): EndpointParameterConfig {
128
+ const ep = this.registerEndpoint(method);
129
+ ep.parameters[index] ??= { index, location: 'query' };
130
+ safeAssign(ep.parameters[index], ...config);
131
+ return ep.parameters[index];
132
+ }
133
+
134
+ finalize(): void {
135
+ // Merge into controller
136
+ for (const ep of this.#config.endpoints) {
137
+ // Store full path from base for use in other contexts
138
+ ep.fullPath = `/${this.#config.basePath}/${ep.path}`.replace(/[/]{1,4}/g, '/').replace(/(.)[/]$/, (_, a) => a);
139
+ ep.finalizedResponseHeaders = new WebHeaders({ ...this.#config.responseHeaders, ...ep.responseHeaders });
140
+ ep.responseContext = { ...this.#config.responseContext, ...ep.responseContext };
141
+ for (const schema of SchemaRegistryIndex.getMethodConfig(this.#cls, ep.methodName).parameters) {
142
+ ep.parameters[schema.index!] ??= { index: schema.index!, location: undefined! };
143
+ ep.parameters[schema.index!].location ??= computeParameterLocation(ep, schema);
144
+ }
145
+ }
146
+ for (const item of this.#finalizeHandlers) {
147
+ item();
148
+ }
149
+ this.#finalizeHandlers = [];
150
+ }
151
+
152
+ get(): ControllerConfig {
153
+ return this.#config;
154
+ }
155
+
156
+ getEndpointConfig(method: string | symbol): EndpointConfig {
157
+ const endpoint = this.#endpoints.get(method);
158
+ if (!endpoint) {
159
+ throw new AppError(`Endpoint not registered: ${String(method)} on ${this.#cls.name}`);
160
+ }
161
+ return endpoint;
162
+ }
163
+
164
+ registerInterceptorConfig<T extends WebInterceptor>(
165
+ cls: Class<T>,
166
+ config: Partial<RetainPrimitiveFields<T['config']>>,
167
+ extra?: Partial<EndpointConfig & ControllerConfig>
168
+ ): ControllerConfig {
169
+ return this.register({ interceptorConfigs: [[cls, castTo(config)]], ...extra });
170
+ }
171
+
172
+ registerEndpointInterceptorConfig<T extends WebInterceptor>(
173
+ property: string | symbol,
174
+ cls: Class<T>,
175
+ config: Partial<RetainPrimitiveFields<T['config']>>,
176
+ extra?: Partial<EndpointConfig>
177
+ ): EndpointConfig {
178
+ return this.registerEndpoint(property, { interceptorConfigs: [[cls, castTo(config)]], ...extra });
179
+ }
180
+
181
+ registerFinalizeHandler(fn: () => void): void {
182
+ this.#finalizeHandlers.push(fn);
183
+ }
184
+ }
@@ -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 {
65
+ export interface EndpointParameterConfig {
91
66
  /**
92
- * Name of the parameter
67
+ * Index of parameter
93
68
  */
94
- name?: string;
95
- /**
96
- * Raw text of parameter at source
97
- */
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
  */
@@ -147,10 +118,6 @@ export interface EndpointConfig extends CoreConfig, DescribableConfig {
147
118
  * The path of the endpoint
148
119
  */
149
120
  path: string;
150
- /**
151
- * The function the endpoint will call
152
- */
153
- endpoint: EndpointFunction;
154
121
  /**
155
122
  * The compiled and finalized handler
156
123
  */
@@ -158,15 +125,7 @@ export interface EndpointConfig extends CoreConfig, DescribableConfig {
158
125
  /**
159
126
  * List of params for the endpoint
160
127
  */
161
- params: EndpointParamConfig[];
162
- /**
163
- * The response type
164
- */
165
- responseType?: EndpointIOType;
166
- /**
167
- * The request type
168
- */
169
- requestType?: EndpointIOType;
128
+ parameters: EndpointParameterConfig[];
170
129
  /**
171
130
  * Full path including controller
172
131
  */
@@ -184,7 +143,7 @@ export interface EndpointConfig extends CoreConfig, DescribableConfig {
184
143
  /**
185
144
  * Controller configuration
186
145
  */
187
- export interface ControllerConfig extends CoreConfig, DescribableConfig {
146
+ export interface ControllerConfig extends CoreConfig {
188
147
  /**
189
148
  * The base path of the controller
190
149
  */
@@ -200,13 +159,13 @@ export interface ControllerConfig extends CoreConfig, DescribableConfig {
200
159
  /**
201
160
  * Context parameters to bind at create
202
161
  */
203
- contextParams: Record<string, Class>;
162
+ contextParams: Record<string | symbol, boolean>;
204
163
  }
205
164
 
206
165
  /**
207
166
  * Controller visitor options
208
167
  */
209
- export type ControllerVisitorOptions = { skipUndocumented?: boolean };
168
+ export type ControllerVisitorOptions = { skipPrivate?: boolean };
210
169
 
211
170
  /**
212
171
  * Controller visitor pattern
@@ -218,10 +177,10 @@ export interface ControllerVisitor<T = unknown> {
218
177
  onControllerStart?(controller: ControllerConfig): Promise<unknown> | unknown;
219
178
  onControllerEnd?(controller: ControllerConfig): Promise<unknown> | unknown;
220
179
 
221
- onEndpointStart?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: FieldConfig[]): Promise<unknown> | unknown;
222
- onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: FieldConfig[]): Promise<unknown> | unknown;
180
+ onEndpointStart?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: SchemaParameterConfig[]): Promise<unknown> | unknown;
181
+ onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams: SchemaParameterConfig[]): Promise<unknown> | unknown;
223
182
 
224
- onSchema?(schema: ClassConfig): Promise<unknown> | unknown;
183
+ onSchema?(schema: SchemaClassConfig): Promise<unknown> | unknown;
225
184
 
226
185
  onControllerAdd?(cls: Class): Promise<unknown> | unknown;
227
186
  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>;