@travetto/web 7.0.0-rc.2 → 7.0.0-rc.3
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 +4 -4
- package/package.json +10 -10
- package/src/decorator/common.ts +4 -4
- package/src/decorator/endpoint.ts +5 -5
- package/src/decorator/param.ts +3 -3
- package/src/interceptor/accept.ts +5 -5
- package/src/interceptor/body.ts +2 -2
- package/src/interceptor/compress.ts +2 -2
- package/src/interceptor/cookie.ts +1 -1
- package/src/interceptor/cors.ts +4 -4
- package/src/interceptor/logging.ts +4 -4
- package/src/interceptor/respond.ts +5 -5
- package/src/registry/registry-adapter.ts +26 -26
- package/src/registry/registry-index.ts +14 -14
- package/src/registry/types.ts +6 -6
- package/src/router/base.ts +17 -17
- package/src/router/standard.ts +6 -6
- package/src/types/core.ts +2 -2
- package/src/types/headers.ts +16 -16
- package/src/types/message.ts +4 -4
- package/src/util/body.ts +19 -19
- package/src/util/common.ts +20 -20
- package/src/util/cookie.ts +9 -9
- package/src/util/endpoint.ts +51 -52
- package/src/util/header.ts +40 -37
- package/src/util/keygrip.ts +1 -1
- package/src/util/net.ts +15 -15
- package/support/test/suite/base.ts +2 -2
package/src/types/headers.ts
CHANGED
|
@@ -9,14 +9,14 @@ export type WebHeadersInit = Headers | Record<string, undefined | null | HeaderV
|
|
|
9
9
|
*/
|
|
10
10
|
export class WebHeaders extends Headers {
|
|
11
11
|
|
|
12
|
-
constructor(
|
|
13
|
-
const passed = (
|
|
14
|
-
super(passed ?
|
|
12
|
+
constructor(input?: WebHeadersInit) {
|
|
13
|
+
const passed = (input instanceof Headers);
|
|
14
|
+
super(passed ? input : undefined);
|
|
15
15
|
|
|
16
|
-
if (
|
|
17
|
-
for (const [
|
|
18
|
-
if (
|
|
19
|
-
this.append(
|
|
16
|
+
if (input && !passed) {
|
|
17
|
+
for (const [key, value] of (Array.isArray(input) ? input : Object.entries(input))) {
|
|
18
|
+
if (value !== undefined && value !== null && !key.startsWith(':')) {
|
|
19
|
+
this.append(key, castTo(value));
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -33,21 +33,21 @@ export class WebHeaders extends Headers {
|
|
|
33
33
|
* Get a header value as a list, breaking on commas except for cookies
|
|
34
34
|
*/
|
|
35
35
|
getList(key: string): string[] | undefined {
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
36
|
+
const value = this.get(key);
|
|
37
|
+
if (!value) {
|
|
38
38
|
return;
|
|
39
|
-
} else if (
|
|
39
|
+
} else if (value.toLowerCase() === 'set-cookie') {
|
|
40
40
|
return this.getSetCookie();
|
|
41
41
|
}
|
|
42
|
-
return
|
|
42
|
+
return value.split(key === 'cookie' ? /\s{0,3};\s{0,3}/ : /\s{0,3},\s{0,3}/);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// @ts-expect-error
|
|
46
|
-
forEach(set: (
|
|
47
|
-
forEach(set: (
|
|
48
|
-
forEach(set: (
|
|
49
|
-
for (const [
|
|
50
|
-
set(
|
|
46
|
+
forEach(set: (value: string | string[], key: string, headers: WebHeaders) => void): void;
|
|
47
|
+
forEach(set: (value: Any, key: string, headers: WebHeaders) => void): void;
|
|
48
|
+
forEach(set: (value: string | string[], key: string, headers: WebHeaders) => void): void {
|
|
49
|
+
for (const [key, value] of this.entries()) {
|
|
50
|
+
set(key === 'set-cookie' ? this.getSetCookie() : value, key, this);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
package/src/types/message.ts
CHANGED
|
@@ -25,9 +25,9 @@ export class BaseWebMessage<B = unknown, C = unknown> implements WebMessage<B, C
|
|
|
25
25
|
readonly headers: WebHeaders;
|
|
26
26
|
body?: B;
|
|
27
27
|
|
|
28
|
-
constructor(
|
|
29
|
-
this.context =
|
|
30
|
-
this.headers = new WebHeaders(
|
|
31
|
-
this.body =
|
|
28
|
+
constructor(input: WebMessageInit<B, C> = {}) {
|
|
29
|
+
this.context = input.context ?? castTo<C>({});
|
|
30
|
+
this.headers = new WebHeaders(input.headers);
|
|
31
|
+
this.body = input.body;
|
|
32
32
|
}
|
|
33
33
|
}
|
package/src/util/body.ts
CHANGED
|
@@ -19,13 +19,13 @@ export class WebBodyUtil {
|
|
|
19
19
|
*/
|
|
20
20
|
static async * buildMultiPartBody(form: FormData, boundary: string): AsyncIterable<string | Buffer> {
|
|
21
21
|
const nl = '\r\n';
|
|
22
|
-
for (const [
|
|
23
|
-
const data =
|
|
22
|
+
for (const [key, value] of form.entries()) {
|
|
23
|
+
const data = value.slice();
|
|
24
24
|
const filename = data instanceof File ? data.name : undefined;
|
|
25
25
|
const size = data instanceof Blob ? data.size : data.length;
|
|
26
26
|
const type = data instanceof Blob ? data.type : undefined;
|
|
27
27
|
yield `--${boundary}${nl}`;
|
|
28
|
-
yield `Content-Disposition: form-data; name="${
|
|
28
|
+
yield `Content-Disposition: form-data; name="${key}"; filename="${filename ?? key}"${nl}`;
|
|
29
29
|
yield `Content-Length: ${size}${nl}`;
|
|
30
30
|
if (type) {
|
|
31
31
|
yield `Content-Type: ${type}${nl}`;
|
|
@@ -66,7 +66,7 @@ export class WebBodyUtil {
|
|
|
66
66
|
toAdd.push(['Content-disposition', `attachment; filename="${value.name}"`]);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
return toAdd.filter((
|
|
69
|
+
return toAdd.filter((pair): pair is [string, string] => !!pair[1]);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -97,8 +97,8 @@ export class WebBodyUtil {
|
|
|
97
97
|
|
|
98
98
|
const out: Omit<WebMessage<WebBinaryBody>, 'context'> = { headers: new WebHeaders(message.headers), body: null! };
|
|
99
99
|
if (body instanceof Blob) {
|
|
100
|
-
for (const [
|
|
101
|
-
out.headers.set(
|
|
100
|
+
for (const [key, value] of this.getBlobHeaders(body)) {
|
|
101
|
+
out.headers.set(key, value);
|
|
102
102
|
}
|
|
103
103
|
out.body = Readable.fromWeb(body.stream());
|
|
104
104
|
} else if (body instanceof FormData) {
|
|
@@ -139,29 +139,29 @@ export class WebBodyUtil {
|
|
|
139
139
|
/**
|
|
140
140
|
* Set body and mark as unprocessed
|
|
141
141
|
*/
|
|
142
|
-
static markRaw(
|
|
143
|
-
if (
|
|
144
|
-
Object.defineProperty(
|
|
142
|
+
static markRaw(body: WebBinaryBody | undefined): typeof body {
|
|
143
|
+
if (body) {
|
|
144
|
+
Object.defineProperty(body, WebRawStreamSymbol, { value: body });
|
|
145
145
|
}
|
|
146
|
-
return
|
|
146
|
+
return body;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Is the input raw
|
|
151
151
|
*/
|
|
152
|
-
static isRaw(
|
|
152
|
+
static isRaw(body: unknown): body is WebBinaryBody {
|
|
153
153
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
154
|
-
return !!
|
|
154
|
+
return !!body && ((Buffer.isBuffer(body) || BinaryUtil.isReadable(body)) && (body as Any)[WebRawStreamSymbol] === body);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
158
|
* Simple parse support
|
|
159
159
|
*/
|
|
160
|
-
static parseBody(type: string,
|
|
160
|
+
static parseBody(type: string, body: string): unknown {
|
|
161
161
|
switch (type) {
|
|
162
|
-
case 'text': return
|
|
163
|
-
case 'json': return JSON.parse(
|
|
164
|
-
case 'form': return Object.fromEntries(new URLSearchParams(
|
|
162
|
+
case 'text': return body;
|
|
163
|
+
case 'json': return JSON.parse(body);
|
|
164
|
+
case 'form': return Object.fromEntries(new URLSearchParams(body));
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -199,11 +199,11 @@ export class WebBodyUtil {
|
|
|
199
199
|
}
|
|
200
200
|
all.push(decoder.decode(Buffer.alloc(0), { stream: false }));
|
|
201
201
|
return { text: all.join(''), read: received };
|
|
202
|
-
} catch (
|
|
203
|
-
if (
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
204
204
|
throw WebError.for('Request Aborted', 400, { received });
|
|
205
205
|
} else {
|
|
206
|
-
throw
|
|
206
|
+
throw error;
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
}
|
package/src/util/common.ts
CHANGED
|
@@ -38,18 +38,18 @@ export class WebCommonUtil {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
static #buildEdgeMap<T, U extends OrderedState<T>>(items: List<U>): Map<T, Set<T>> {
|
|
41
|
-
const edgeMap = new Map(items.map(
|
|
41
|
+
const edgeMap = new Map(items.map(item => [item.key, new Set(item.after ?? [])]));
|
|
42
42
|
|
|
43
43
|
// Build out edge map
|
|
44
44
|
for (const input of items) {
|
|
45
|
-
for (const
|
|
46
|
-
if (edgeMap.has(
|
|
47
|
-
edgeMap.get(
|
|
45
|
+
for (const item of input.before ?? []) {
|
|
46
|
+
if (edgeMap.has(item)) {
|
|
47
|
+
edgeMap.get(item)!.add(input.key);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
const afterSet = edgeMap.get(input.key)!;
|
|
51
|
-
for (const
|
|
52
|
-
afterSet.add(
|
|
51
|
+
for (const item of input.after ?? []) {
|
|
52
|
+
afterSet.add(item);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
return edgeMap;
|
|
@@ -64,7 +64,7 @@ export class WebCommonUtil {
|
|
|
64
64
|
rules,
|
|
65
65
|
this.#convert.bind(this),
|
|
66
66
|
(regex, mime) => regex.test(mime),
|
|
67
|
-
|
|
67
|
+
key => key
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -94,8 +94,8 @@ export class WebCommonUtil {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const inputMap = new Map(items.map(
|
|
98
|
-
return keys.map(
|
|
97
|
+
const inputMap = new Map(items.map(item => [item.key, item]));
|
|
98
|
+
return keys.map(key => inputMap.get(key)!);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -110,18 +110,18 @@ export class WebCommonUtil {
|
|
|
110
110
|
/**
|
|
111
111
|
* From catch value
|
|
112
112
|
*/
|
|
113
|
-
static catchResponse(
|
|
114
|
-
if (
|
|
115
|
-
return
|
|
113
|
+
static catchResponse(error: unknown): WebResponse<Error> {
|
|
114
|
+
if (error instanceof WebResponse) {
|
|
115
|
+
return error;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const body =
|
|
119
|
-
(!!
|
|
120
|
-
new AppError(
|
|
121
|
-
new AppError(`${
|
|
118
|
+
const body = error instanceof Error ? error :
|
|
119
|
+
(!!error && typeof error === 'object' && ('message' in error && typeof error.message === 'string')) ?
|
|
120
|
+
new AppError(error.message, { details: error }) :
|
|
121
|
+
new AppError(`${error}`);
|
|
122
122
|
|
|
123
|
-
const
|
|
124
|
-
const statusCode =
|
|
123
|
+
const webError: Error & Partial<WebError> = body;
|
|
124
|
+
const statusCode = webError.details?.statusCode ?? ERROR_CATEGORY_STATUS[webError.category!] ?? 500;
|
|
125
125
|
|
|
126
126
|
return new WebResponse({ body, context: { httpStatusCode: statusCode } });
|
|
127
127
|
}
|
|
@@ -147,7 +147,7 @@ export class WebCommonUtil {
|
|
|
147
147
|
if (typeof input === 'number') {
|
|
148
148
|
return input;
|
|
149
149
|
}
|
|
150
|
-
const [,
|
|
151
|
-
return parseInt(
|
|
150
|
+
const [, value, unit] = input.toLowerCase().split(/(\d+)/);
|
|
151
|
+
return parseInt(value, 10) * (UNIT_MAPPING[unit] ?? 1);
|
|
152
152
|
}
|
|
153
153
|
}
|
package/src/util/cookie.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { Cookie, CookieGetOptions, CookieSetOptions } from '../types/cookie.ts';
|
|
|
4
4
|
import { KeyGrip } from './keygrip.ts';
|
|
5
5
|
import { WebHeaderUtil } from './header.ts';
|
|
6
6
|
|
|
7
|
-
const pairText = (
|
|
8
|
-
const pair = (
|
|
7
|
+
const pairText = (cookie: Cookie): string => `${cookie.name}=${cookie.value}`;
|
|
8
|
+
const pair = (key: string, value: unknown): string => `${key}=${value}`;
|
|
9
9
|
|
|
10
10
|
type CookieJarOptions = { keys?: string[] } & CookieSetOptions;
|
|
11
11
|
|
|
@@ -66,13 +66,13 @@ export class CookieJar {
|
|
|
66
66
|
return this;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
has(name: string,
|
|
70
|
-
const needSigned =
|
|
69
|
+
has(name: string, options: CookieGetOptions = {}): boolean {
|
|
70
|
+
const needSigned = options.signed ?? this.#setOptions.signed;
|
|
71
71
|
return name in this.#cookies && this.#cookies[name].signed === needSigned;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
get(name: string,
|
|
75
|
-
if (this.has(name,
|
|
74
|
+
get(name: string, options: CookieGetOptions = {}): string | undefined {
|
|
75
|
+
if (this.has(name, options)) {
|
|
76
76
|
return this.#cookies[name]?.value;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -111,12 +111,12 @@ export class CookieJar {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
exportCookieHeader(): string {
|
|
114
|
-
return this.getAll().flatMap(
|
|
114
|
+
return this.getAll().flatMap(cookie => this.#exportCookie(cookie)).join('; ');
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
exportSetCookieHeader(): string[] {
|
|
118
118
|
return this.getAll()
|
|
119
|
-
.filter(
|
|
120
|
-
.flatMap(
|
|
119
|
+
.filter(cookie => cookie.response)
|
|
120
|
+
.flatMap(cookie => this.#exportCookie(cookie, true));
|
|
121
121
|
}
|
|
122
122
|
}
|
package/src/util/endpoint.ts
CHANGED
|
@@ -65,12 +65,12 @@ export class EndpointUtil {
|
|
|
65
65
|
|
|
66
66
|
const inputByClass = Map.groupBy(
|
|
67
67
|
[...controller?.interceptorConfigs ?? [], ...endpoint.interceptorConfigs ?? []],
|
|
68
|
-
|
|
68
|
+
entry => entry[0]
|
|
69
69
|
);
|
|
70
70
|
|
|
71
71
|
const configs = new Map<Class, unknown>(interceptors.map(inst => {
|
|
72
72
|
const cls = asConstructable<WebInterceptor>(inst).constructor;
|
|
73
|
-
const inputs = (inputByClass.get(cls) ?? []).map(
|
|
73
|
+
const inputs = (inputByClass.get(cls) ?? []).map(entry => entry[1]);
|
|
74
74
|
const config = Object.assign({}, inst.config, ...inputs);
|
|
75
75
|
return [cls, inst.finalizeConfig?.({ config, endpoint }, castTo(inputs)) ?? config];
|
|
76
76
|
}));
|
|
@@ -96,8 +96,8 @@ export class EndpointUtil {
|
|
|
96
96
|
case 'header': return isArray ? request.headers.getList(name) : request.headers.get(name);
|
|
97
97
|
case 'query': {
|
|
98
98
|
const withQuery: typeof request & { [WebQueryExpandedSymbol]?: Record<string, unknown> } = request;
|
|
99
|
-
const
|
|
100
|
-
return
|
|
99
|
+
const query = withQuery[WebQueryExpandedSymbol] ??= BindUtil.expandPaths(request.context.httpQuery ?? {});
|
|
100
|
+
return query[name];
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -114,33 +114,32 @@ export class EndpointUtil {
|
|
|
114
114
|
} else if (param.location === 'query') {
|
|
115
115
|
// TODO: Revisit this logic?
|
|
116
116
|
const withQuery: typeof request & { [WebQueryExpandedSymbol]?: Record<string, unknown> } = request;
|
|
117
|
-
const
|
|
117
|
+
const query = withQuery[WebQueryExpandedSymbol] ??= BindUtil.expandPaths(request.context.httpQuery ?? {});
|
|
118
118
|
if (param.prefix) { // Has a prefix provided
|
|
119
|
-
return
|
|
119
|
+
return query[param.prefix];
|
|
120
120
|
} else if (input.type.Ⲑid) { // Is a full type
|
|
121
|
-
return
|
|
121
|
+
return query;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
let
|
|
126
|
-
for (let i = 0;
|
|
127
|
-
|
|
125
|
+
let result = this.extractParameterValue(request, param, input.name!, input.array) ?? undefined;
|
|
126
|
+
for (let i = 0; result === undefined && input.aliases && i < input.aliases.length; i += 1) {
|
|
127
|
+
result = this.extractParameterValue(request, param, input.aliases[i], input.array) ?? undefined;
|
|
128
128
|
}
|
|
129
|
-
return
|
|
129
|
+
return result;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Extract all parameters for a given endpoint/request/response combo
|
|
134
134
|
* @param endpoint The endpoint to extract for
|
|
135
|
-
* @param
|
|
136
|
-
* @param res The response
|
|
135
|
+
* @param request The request
|
|
137
136
|
*/
|
|
138
137
|
static async extractParameters(endpoint: EndpointConfig, request: WebRequest): Promise<unknown[]> {
|
|
139
138
|
const cls = endpoint.class;
|
|
140
139
|
const vals = WebCommonUtil.getRequestParams(request);
|
|
141
140
|
const { parameters } = SchemaRegistryIndex.get(cls).getMethod(endpoint.methodName);
|
|
142
|
-
const combined = parameters.map((
|
|
143
|
-
({ schema:
|
|
141
|
+
const combined = parameters.map((config) =>
|
|
142
|
+
({ schema: config, param: endpoint.parameters[config.index], value: vals?.[config.index] }));
|
|
144
143
|
|
|
145
144
|
try {
|
|
146
145
|
const extracted = combined.map(({ param, schema, value }) =>
|
|
@@ -149,20 +148,20 @@ export class EndpointUtil {
|
|
|
149
148
|
this.extractParameter(request, param, schema)
|
|
150
149
|
);
|
|
151
150
|
const params = BindUtil.coerceMethodParams(cls, endpoint.methodName, extracted);
|
|
152
|
-
await SchemaValidator.validateMethod(cls, endpoint.methodName, params, endpoint.parameters.map(
|
|
151
|
+
await SchemaValidator.validateMethod(cls, endpoint.methodName, params, endpoint.parameters.map(paramConfig => paramConfig.prefix));
|
|
153
152
|
return params;
|
|
154
|
-
} catch (
|
|
155
|
-
if (
|
|
156
|
-
for (const
|
|
157
|
-
if (
|
|
158
|
-
const config = combined.find(
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof ValidationResultError) {
|
|
155
|
+
for (const validationError of error.details?.errors ?? []) {
|
|
156
|
+
if (validationError.kind === 'required') {
|
|
157
|
+
const config = combined.find(paramConfig => paramConfig.schema.name === validationError.path);
|
|
159
158
|
if (config) {
|
|
160
|
-
|
|
159
|
+
validationError.message = `Missing ${config.param.location} value: ${config.schema.name}`;
|
|
161
160
|
}
|
|
162
161
|
}
|
|
163
162
|
}
|
|
164
163
|
}
|
|
165
|
-
throw
|
|
164
|
+
throw error;
|
|
166
165
|
}
|
|
167
166
|
}
|
|
168
167
|
|
|
@@ -176,7 +175,7 @@ export class EndpointUtil {
|
|
|
176
175
|
const headers = endpoint.finalizedResponseHeaders;
|
|
177
176
|
let response: WebResponse;
|
|
178
177
|
if (body instanceof WebResponse) {
|
|
179
|
-
for (const [
|
|
178
|
+
for (const [key, value] of headers) { body.headers.setIfAbsent(key, value); }
|
|
180
179
|
// Rewrite context
|
|
181
180
|
Object.assign(body.context, { ...endpoint.responseContext, ...body.context });
|
|
182
181
|
response = body;
|
|
@@ -184,8 +183,8 @@ export class EndpointUtil {
|
|
|
184
183
|
response = new WebResponse({ body, headers, context: { ...endpoint.responseContext } });
|
|
185
184
|
}
|
|
186
185
|
return endpoint.responseFinalizer?.(response) ?? response;
|
|
187
|
-
} catch (
|
|
188
|
-
throw WebCommonUtil.catchResponse(
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw WebCommonUtil.catchResponse(error);
|
|
189
188
|
}
|
|
190
189
|
}
|
|
191
190
|
|
|
@@ -203,7 +202,7 @@ export class EndpointUtil {
|
|
|
203
202
|
|
|
204
203
|
// Filter interceptors if needed
|
|
205
204
|
for (const filter of [controller?.interceptorExclude, endpoint.interceptorExclude]) {
|
|
206
|
-
interceptors = filter ? interceptors.filter(
|
|
205
|
+
interceptors = filter ? interceptors.filter(interceptor => !filter(interceptor)) : interceptors;
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
const interceptorFilters =
|
|
@@ -214,7 +213,7 @@ export class EndpointUtil {
|
|
|
214
213
|
const endpointFilters = [
|
|
215
214
|
...(controller?.filters ?? []).map(fn => fn.bind(controller?.instance)),
|
|
216
215
|
...(endpoint.filters ?? []).map(fn => fn.bind(endpoint.instance)),
|
|
217
|
-
...(endpoint.parameters.filter(
|
|
216
|
+
...(endpoint.parameters.filter(config => config.resolve).map(fn => fn.resolve!))
|
|
218
217
|
]
|
|
219
218
|
.map(fn => ({ filter: fn }));
|
|
220
219
|
|
|
@@ -231,8 +230,8 @@ export class EndpointUtil {
|
|
|
231
230
|
/**
|
|
232
231
|
* Get bound endpoints, honoring the conditional status
|
|
233
232
|
*/
|
|
234
|
-
static async getBoundEndpoints(
|
|
235
|
-
const config = ControllerRegistryIndex.getConfig(
|
|
233
|
+
static async getBoundEndpoints(cls: Class): Promise<EndpointConfig[]> {
|
|
234
|
+
const config = ControllerRegistryIndex.getConfig(cls);
|
|
236
235
|
|
|
237
236
|
// Skip registering conditional controllers
|
|
238
237
|
if (config.conditional && !await config.conditional()) {
|
|
@@ -247,15 +246,15 @@ export class EndpointUtil {
|
|
|
247
246
|
|
|
248
247
|
// Filter out conditional endpoints
|
|
249
248
|
const endpoints = (await Promise.all(
|
|
250
|
-
config.endpoints.map(
|
|
251
|
-
)).filter(
|
|
249
|
+
config.endpoints.map(endpoint => Promise.resolve(endpoint.conditional?.() ?? true).then(value => value ? endpoint : undefined))
|
|
250
|
+
)).filter(endpoint => !!endpoint);
|
|
252
251
|
|
|
253
252
|
if (!endpoints.length) {
|
|
254
253
|
return [];
|
|
255
254
|
}
|
|
256
255
|
|
|
257
|
-
for (const
|
|
258
|
-
|
|
256
|
+
for (const endpoint of endpoints) {
|
|
257
|
+
endpoint.instance = config.instance;
|
|
259
258
|
}
|
|
260
259
|
|
|
261
260
|
return endpoints;
|
|
@@ -266,12 +265,12 @@ export class EndpointUtil {
|
|
|
266
265
|
*/
|
|
267
266
|
static orderEndpoints(endpoints: EndpointConfig[]): EndpointConfig[] {
|
|
268
267
|
return endpoints
|
|
269
|
-
.map(
|
|
270
|
-
const parts =
|
|
271
|
-
return [
|
|
268
|
+
.map(endpoint => {
|
|
269
|
+
const parts = endpoint.path.replace(/^[/]|[/]$/g, '').split('/');
|
|
270
|
+
return [endpoint, parts.map(part => /[*]/.test(part) ? 1 : /:/.test(part) ? 2 : 3)] as const;
|
|
272
271
|
})
|
|
273
272
|
.toSorted((a, b) => this.#compareEndpoints(a[1], b[1]) || a[0].path.localeCompare(b[0].path))
|
|
274
|
-
.map(([
|
|
273
|
+
.map(([endpoint,]) => endpoint);
|
|
275
274
|
}
|
|
276
275
|
|
|
277
276
|
|
|
@@ -279,25 +278,25 @@ export class EndpointUtil {
|
|
|
279
278
|
* Order interceptors
|
|
280
279
|
*/
|
|
281
280
|
static orderInterceptors(instances: WebInterceptor[]): WebInterceptor[] {
|
|
282
|
-
const
|
|
283
|
-
key:
|
|
284
|
-
start: castTo<Class<WebInterceptor>>({ name: `${
|
|
285
|
-
end: castTo<Class<WebInterceptor>>({ name: `${
|
|
281
|
+
const categoryList = WEB_INTERCEPTOR_CATEGORIES.map(category => ({
|
|
282
|
+
key: category,
|
|
283
|
+
start: castTo<Class<WebInterceptor>>({ name: `${category}Start` }),
|
|
284
|
+
end: castTo<Class<WebInterceptor>>({ name: `${category}End` }),
|
|
286
285
|
}));
|
|
287
286
|
|
|
288
|
-
const categoryMapping = TypedObject.fromEntries(
|
|
287
|
+
const categoryMapping = TypedObject.fromEntries(categoryList.map(category => [category.key, category]));
|
|
289
288
|
|
|
290
|
-
const ordered = instances.map(
|
|
291
|
-
const group = categoryMapping[
|
|
292
|
-
const after = [...
|
|
293
|
-
const before = [...
|
|
294
|
-
return ({ key:
|
|
289
|
+
const ordered = instances.map(category => {
|
|
290
|
+
const group = categoryMapping[category.category];
|
|
291
|
+
const after = [...category.dependsOn ?? [], group.start];
|
|
292
|
+
const before = [...category.runsBefore ?? [], group.end];
|
|
293
|
+
return ({ key: category.constructor, before, after, target: category, placeholder: false });
|
|
295
294
|
});
|
|
296
295
|
|
|
297
296
|
// Add category sets into the ordering
|
|
298
297
|
let i = 0;
|
|
299
|
-
for (const cat of
|
|
300
|
-
const prevEnd =
|
|
298
|
+
for (const cat of categoryList) {
|
|
299
|
+
const prevEnd = categoryList[i - 1]?.end ? [categoryList[i - 1].end] : [];
|
|
301
300
|
ordered.push(
|
|
302
301
|
{ key: cat.start, before: [cat.end], after: prevEnd, placeholder: true, target: undefined! },
|
|
303
302
|
{ key: cat.end, before: [], after: [cat.start], placeholder: true, target: undefined! }
|
|
@@ -306,7 +305,7 @@ export class EndpointUtil {
|
|
|
306
305
|
}
|
|
307
306
|
|
|
308
307
|
return WebCommonUtil.ordered(ordered)
|
|
309
|
-
.filter(
|
|
310
|
-
.map(
|
|
308
|
+
.filter(category => !category.placeholder) // Drop out the placeholders
|
|
309
|
+
.map(category => category.target);
|
|
311
310
|
}
|
|
312
311
|
}
|