@travetto/web 6.0.3 → 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.
- package/README.md +21 -21
- package/__index__.ts +2 -1
- package/package.json +10 -10
- package/src/common/global.ts +5 -4
- package/src/decorator/common.ts +26 -21
- package/src/decorator/controller.ts +8 -5
- package/src/decorator/endpoint.ts +21 -39
- package/src/decorator/param.ts +39 -32
- package/src/interceptor/body.ts +4 -4
- package/src/registry/registry-adapter.ts +185 -0
- package/src/registry/registry-index.ts +110 -0
- package/src/registry/types.ts +19 -56
- package/src/registry/visitor.ts +14 -11
- package/src/router/base.ts +14 -12
- package/src/util/endpoint.ts +61 -26
- package/support/test/suite/base.ts +6 -4
- package/support/test/suite/schema.ts +26 -20
- package/support/test/suite/standard.ts +2 -3
- package/src/registry/controller.ts +0 -292
- package/support/transformer.web.ts +0 -212
|
@@ -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
|
+
}
|
package/src/registry/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Any, Class, TypedFunction } from '@travetto/runtime';
|
|
2
|
-
import type {
|
|
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
|
|
63
|
+
* Endpoint parameter configuration
|
|
89
64
|
*/
|
|
90
|
-
export interface
|
|
91
|
-
/**
|
|
92
|
-
* Name of the parameter
|
|
93
|
-
*/
|
|
94
|
-
name?: string;
|
|
65
|
+
export interface EndpointParameterConfig {
|
|
95
66
|
/**
|
|
96
|
-
*
|
|
67
|
+
* Index of parameter
|
|
97
68
|
*/
|
|
98
|
-
|
|
69
|
+
index: number;
|
|
99
70
|
/**
|
|
100
71
|
* Location of the parameter
|
|
101
72
|
*/
|
|
102
|
-
location:
|
|
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:
|
|
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
|
|
92
|
+
export interface EndpointConfig extends CoreConfig {
|
|
122
93
|
/**
|
|
123
94
|
* Unique identifier
|
|
124
95
|
*/
|
|
125
96
|
id: string;
|
|
126
97
|
/**
|
|
127
|
-
*
|
|
98
|
+
* Name of the endpoint (method name)
|
|
128
99
|
*/
|
|
129
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
166
|
+
contextParams: Record<string | symbol, boolean>;
|
|
204
167
|
}
|
|
205
168
|
|
|
206
169
|
/**
|
|
207
170
|
* Controller visitor options
|
|
208
171
|
*/
|
|
209
|
-
export type ControllerVisitorOptions = {
|
|
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:
|
|
222
|
-
onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig, methodParams:
|
|
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:
|
|
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;
|
package/src/registry/visitor.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Class } from '@travetto/runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { ControllerVisitor, ControllerVisitorOptions } from './types.ts';
|
|
5
|
-
import {
|
|
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 &&
|
|
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.
|
|
21
|
+
options.skipPrivate ??= true;
|
|
22
22
|
|
|
23
|
-
const controller =
|
|
24
|
-
|
|
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
|
-
|
|
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 =
|
|
36
|
+
const { parameters: params, returnType } = SchemaRegistryIndex.getMethodConfig(cls, endpoint.methodName);
|
|
35
37
|
await visitor.onEndpointStart?.(endpoint, controller, params);
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
50
|
+
for (const cls of ControllerRegistryIndex.getClasses()) {
|
|
48
51
|
await this.visitController(visitor, cls, options);
|
|
49
52
|
}
|
|
50
53
|
return await visitor.onComplete?.() ?? undefined!;
|
package/src/router/base.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Class, toConcrete } from '@travetto/runtime';
|
|
2
|
-
import {
|
|
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 =
|
|
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
|
|
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
|
|
48
|
+
for (const c of ControllerRegistryIndex.getClasses()) {
|
|
49
49
|
await this.#register(c);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Listen for updates
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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>;
|