@travetto/web 7.0.0-rc.3 → 7.0.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/package.json +10 -10
- package/src/registry/registry-index.ts +8 -22
- package/src/registry/types.ts +3 -9
- package/src/registry/visitor.ts +5 -6
- package/src/router/base.ts +2 -19
- package/src/router/standard.ts +1 -7
- package/src/types/dispatch.ts +1 -1
- package/src/types/response.ts +4 -4
- package/src/util/body.ts +2 -2
- package/src/util/endpoint.ts +1 -6
- package/support/test/dispatch-util.ts +2 -2
- package/support/test/suite/standard.ts +2 -8
package/README.md
CHANGED
|
@@ -78,10 +78,10 @@ export interface WebResponseContext {
|
|
|
78
78
|
export class WebResponse<B = unknown> extends BaseWebMessage<B, WebResponseContext> {
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
* Build the redirect
|
|
82
|
+
* @param location Location to redirect to
|
|
83
|
+
* @param statusCode Status code
|
|
84
|
+
*/
|
|
85
85
|
static redirect(location: string, statusCode = 302): WebResponse<undefined> {
|
|
86
86
|
return new WebResponse({ context: { httpStatusCode: statusCode }, headers: { Location: location } });
|
|
87
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/web",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.5",
|
|
4
4
|
"description": "Declarative support for creating Web Applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"directory": "module/web"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@travetto/config": "^7.0.0-rc.
|
|
29
|
-
"@travetto/context": "^7.0.0-rc.
|
|
30
|
-
"@travetto/di": "^7.0.0-rc.
|
|
31
|
-
"@travetto/registry": "^7.0.0-rc.
|
|
32
|
-
"@travetto/runtime": "^7.0.0-rc.
|
|
33
|
-
"@travetto/schema": "^7.0.0-rc.
|
|
28
|
+
"@travetto/config": "^7.0.0-rc.4",
|
|
29
|
+
"@travetto/context": "^7.0.0-rc.4",
|
|
30
|
+
"@travetto/di": "^7.0.0-rc.4",
|
|
31
|
+
"@travetto/registry": "^7.0.0-rc.4",
|
|
32
|
+
"@travetto/runtime": "^7.0.0-rc.4",
|
|
33
|
+
"@travetto/schema": "^7.0.0-rc.4",
|
|
34
34
|
"find-my-way": "^9.3.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/cli": "^7.0.0-rc.
|
|
38
|
-
"@travetto/test": "^7.0.0-rc.
|
|
39
|
-
"@travetto/transformer": "^7.0.0-rc.
|
|
37
|
+
"@travetto/cli": "^7.0.0-rc.4",
|
|
38
|
+
"@travetto/test": "^7.0.0-rc.4",
|
|
39
|
+
"@travetto/transformer": "^7.0.0-rc.3"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
42
42
|
"@travetto/transformer": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Class, ClassInstance, getClass, RetainPrimitiveFields } from '@travetto/runtime';
|
|
1
|
+
import { RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
2
|
+
import { Class, ClassInstance, getClass, isClass, RetainPrimitiveFields } from '@travetto/runtime';
|
|
3
3
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
4
4
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
5
5
|
|
|
@@ -8,8 +8,6 @@ import { ControllerConfig, EndpointConfig, EndpointDecorator } from './types';
|
|
|
8
8
|
import { WebAsyncContext } from '../context';
|
|
9
9
|
import type { WebInterceptor } from '../types/interceptor.ts';
|
|
10
10
|
|
|
11
|
-
const isClass = (property: unknown, target: unknown): target is Class => !property;
|
|
12
|
-
|
|
13
11
|
export class ControllerRegistryIndex implements RegistryIndex {
|
|
14
12
|
|
|
15
13
|
static #instance = Registry.registerIndex(this);
|
|
@@ -46,7 +44,7 @@ export class ControllerRegistryIndex implements RegistryIndex {
|
|
|
46
44
|
): EndpointDecorator {
|
|
47
45
|
return (instanceOrCls: Class | ClassInstance, property?: string): void => {
|
|
48
46
|
const adapter = ControllerRegistryIndex.getForRegister(getClass(instanceOrCls));
|
|
49
|
-
if (isClass(
|
|
47
|
+
if (isClass(instanceOrCls)) {
|
|
50
48
|
adapter.registerInterceptorConfig(cls, config, extra);
|
|
51
49
|
} else {
|
|
52
50
|
adapter.registerEndpointInterceptorConfig(property!, cls, config, extra);
|
|
@@ -58,6 +56,8 @@ export class ControllerRegistryIndex implements RegistryIndex {
|
|
|
58
56
|
|
|
59
57
|
store = new RegistryIndexStore(ControllerRegistryAdapter);
|
|
60
58
|
|
|
59
|
+
/** @private */ constructor(source: unknown) { Registry.validateConstructor(source); }
|
|
60
|
+
|
|
61
61
|
async #bindContextParams<T>(instance: ClassInstance<T>): Promise<void> {
|
|
62
62
|
const ctx = await DependencyRegistryIndex.getInstance(WebAsyncContext);
|
|
63
63
|
const cls = getClass(instance);
|
|
@@ -85,27 +85,13 @@ export class ControllerRegistryIndex implements RegistryIndex {
|
|
|
85
85
|
return this.store.get(cls).get();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
getEndpoint(cls: Class, method: string): EndpointConfig {
|
|
89
|
-
return this.getController(cls).endpoints.find(endpoint => endpoint.methodName === method)!;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
88
|
getEndpointById(id: string): EndpointConfig | undefined {
|
|
93
89
|
return this.#endpointsById.get(id.replace(':', '#'));
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
for (const
|
|
98
|
-
|
|
99
|
-
for (const endpoint of this.getController(event.current).endpoints) {
|
|
100
|
-
this.#endpointsById.set(`${event.current.name}#${endpoint.methodName}`, endpoint);
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
// Match by name
|
|
104
|
-
const toDelete = [...this.#endpointsById.values()].filter(endpoint => endpoint.class.name === event.previous.name);
|
|
105
|
-
for (const endpoint of toDelete) {
|
|
106
|
-
this.#endpointsById.delete(endpoint.id);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
92
|
+
onCreate(cls: Class): void {
|
|
93
|
+
for (const endpoint of this.getController(cls).endpoints) {
|
|
94
|
+
this.#endpointsById.set(endpoint.id, endpoint);
|
|
109
95
|
}
|
|
110
96
|
}
|
|
111
97
|
}
|
package/src/registry/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Any, Class, TypedFunction } from '@travetto/runtime';
|
|
2
|
-
import type { SchemaClassConfig
|
|
2
|
+
import type { SchemaClassConfig } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import type { WebInterceptor } from '../types/interceptor.ts';
|
|
5
5
|
import type { WebChainedFilter, WebFilter } from '../types/filter.ts';
|
|
@@ -177,16 +177,10 @@ export interface ControllerVisitor<T = unknown> {
|
|
|
177
177
|
onControllerStart?(controller: ControllerConfig): Promise<unknown> | unknown;
|
|
178
178
|
onControllerEnd?(controller: ControllerConfig): Promise<unknown> | unknown;
|
|
179
179
|
|
|
180
|
-
onEndpointStart?(endpoint: EndpointConfig, controller: ControllerConfig
|
|
181
|
-
onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig
|
|
180
|
+
onEndpointStart?(endpoint: EndpointConfig, controller: ControllerConfig): Promise<unknown> | unknown;
|
|
181
|
+
onEndpointEnd?(endpoint: EndpointConfig, controller: ControllerConfig): Promise<unknown> | unknown;
|
|
182
182
|
|
|
183
183
|
onSchema?(schema: SchemaClassConfig): Promise<unknown> | unknown;
|
|
184
184
|
|
|
185
|
-
onControllerAdd?(cls: Class): Promise<unknown> | unknown;
|
|
186
|
-
onControllerRemove?(cls: Class): Promise<unknown> | unknown;
|
|
187
|
-
|
|
188
|
-
onSchemaAdd?(cls: Class): Promise<boolean> | boolean;
|
|
189
|
-
onSchemaRemove?(cls: Class): Promise<boolean> | boolean;
|
|
190
|
-
|
|
191
185
|
onComplete?(): T | Promise<T>;
|
|
192
186
|
}
|
package/src/registry/visitor.ts
CHANGED
|
@@ -33,15 +33,14 @@ export class ControllerVisitUtil {
|
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
await this.#onSchemaEvent(visitor, returnType.type);
|
|
36
|
+
await visitor.onEndpointStart?.(endpoint, controller);
|
|
37
|
+
if (endpointSchema.returnType) {
|
|
38
|
+
await this.#onSchemaEvent(visitor, endpointSchema.returnType.type);
|
|
40
39
|
}
|
|
41
|
-
for (const param of
|
|
40
|
+
for (const param of endpointSchema.parameters) {
|
|
42
41
|
await this.#onSchemaEvent(visitor, param.type);
|
|
43
42
|
}
|
|
44
|
-
await visitor.onEndpointEnd?.(endpoint, controller
|
|
43
|
+
await visitor.onEndpointEnd?.(endpoint, controller);
|
|
45
44
|
}
|
|
46
45
|
await visitor.onControllerEnd?.(controller);
|
|
47
46
|
}
|
package/src/router/base.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Class, toConcrete } from '@travetto/runtime';
|
|
2
2
|
import { DependencyRegistryIndex, Injectable } from '@travetto/di';
|
|
3
3
|
import { ControllerRegistryIndex } from '@travetto/web';
|
|
4
|
-
import { Registry } from '@travetto/registry';
|
|
5
4
|
|
|
6
5
|
import { ControllerConfig, EndpointConfig } from '../registry/types.ts';
|
|
7
6
|
import type { WebRouter } from '../types/dispatch.ts';
|
|
@@ -17,7 +16,6 @@ import { EndpointUtil } from '../util/endpoint.ts';
|
|
|
17
16
|
@Injectable()
|
|
18
17
|
export abstract class BaseWebRouter implements WebRouter {
|
|
19
18
|
|
|
20
|
-
#cleanup = new Map<string, Function>();
|
|
21
19
|
#interceptors: WebInterceptor[];
|
|
22
20
|
|
|
23
21
|
async #register(cls: Class): Promise<void> {
|
|
@@ -30,8 +28,7 @@ export abstract class BaseWebRouter implements WebRouter {
|
|
|
30
28
|
endpoint.filter = EndpointUtil.createEndpointHandler(this.#interceptors, endpoint, config);
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
this.#cleanup.set(cls.Ⲑid, fn);
|
|
31
|
+
await this.register(endpoints, config);
|
|
35
32
|
};
|
|
36
33
|
|
|
37
34
|
/**
|
|
@@ -48,22 +45,8 @@ export abstract class BaseWebRouter implements WebRouter {
|
|
|
48
45
|
for (const cls of ControllerRegistryIndex.getClasses()) {
|
|
49
46
|
await this.#register(cls);
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
// Listen for updates
|
|
53
|
-
Registry.onClassChange(async event => {
|
|
54
|
-
const targetCls = 'current' in event ? event.current : event.previous;
|
|
55
|
-
console.debug('Registry event', { type: event.type, target: targetCls.Ⲑid });
|
|
56
|
-
|
|
57
|
-
if ('previous' in event) {
|
|
58
|
-
this.#cleanup.get(event.previous.Ⲑid)?.();
|
|
59
|
-
this.#cleanup.delete(event.previous.Ⲑid);
|
|
60
|
-
}
|
|
61
|
-
if ('current' in event) {
|
|
62
|
-
await this.#register(event.current);
|
|
63
|
-
}
|
|
64
|
-
}, ControllerRegistryIndex);
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
abstract register(endpoints: EndpointConfig[], controller: ControllerConfig): Promise<
|
|
50
|
+
abstract register(endpoints: EndpointConfig[], controller: ControllerConfig): Promise<void>;
|
|
68
51
|
abstract dispatch(ctx: WebFilterContext): Promise<WebResponse>;
|
|
69
52
|
}
|
package/src/router/standard.ts
CHANGED
|
@@ -26,19 +26,13 @@ export class StandardWebRouter extends BaseWebRouter {
|
|
|
26
26
|
#cache = new Map<Function, EndpointConfig>();
|
|
27
27
|
raw = router();
|
|
28
28
|
|
|
29
|
-
async register(endpoints: EndpointConfig[]): Promise<
|
|
29
|
+
async register(endpoints: EndpointConfig[]): Promise<void> {
|
|
30
30
|
for (const endpoint of endpoints) {
|
|
31
31
|
const fullPath = endpoint.fullPath.replace(/[*][^*]+/g, '*'); // Flatten wildcards
|
|
32
32
|
const handler = (): void => { };
|
|
33
33
|
this.#cache.set(handler, endpoint);
|
|
34
34
|
this.raw[HTTP_METHODS[endpoint.httpMethod ?? DEFAULT_HTTP_METHOD].lower](fullPath, handler);
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
return (): void => {
|
|
38
|
-
for (const endpoint of endpoints ?? []) {
|
|
39
|
-
this.raw.off(endpoint.httpMethod ?? DEFAULT_HTTP_METHOD, endpoint.fullPath);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
/**
|
package/src/types/dispatch.ts
CHANGED
|
@@ -19,5 +19,5 @@ export interface WebRouter extends WebDispatcher {
|
|
|
19
19
|
/**
|
|
20
20
|
* Register a controller with the prepared endpoints
|
|
21
21
|
*/
|
|
22
|
-
register(endpoints: EndpointConfig[], controller: ControllerConfig): Promise<
|
|
22
|
+
register(endpoints: EndpointConfig[], controller: ControllerConfig): Promise<void>;
|
|
23
23
|
}
|
package/src/types/response.ts
CHANGED
|
@@ -12,10 +12,10 @@ export interface WebResponseContext {
|
|
|
12
12
|
export class WebResponse<B = unknown> extends BaseWebMessage<B, WebResponseContext> {
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
* Build the redirect
|
|
16
|
+
* @param location Location to redirect to
|
|
17
|
+
* @param statusCode Status code
|
|
18
|
+
*/
|
|
19
19
|
static redirect(location: string, statusCode = 302): WebResponse<undefined> {
|
|
20
20
|
return new WebResponse({ context: { httpStatusCode: statusCode }, headers: { Location: location } });
|
|
21
21
|
}
|
package/src/util/body.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TextDecoder } from 'node:util';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
3
|
|
|
4
|
-
import { Any, BinaryUtil, castTo, hasToJSON, Util } from '@travetto/runtime';
|
|
4
|
+
import { Any, BinaryUtil, castTo, hasToJSON, JSONUtil, Util } from '@travetto/runtime';
|
|
5
5
|
|
|
6
6
|
import { WebBinaryBody, WebMessage } from '../types/message.ts';
|
|
7
7
|
import { WebHeaders } from '../types/headers.ts';
|
|
@@ -160,7 +160,7 @@ export class WebBodyUtil {
|
|
|
160
160
|
static parseBody(type: string, body: string): unknown {
|
|
161
161
|
switch (type) {
|
|
162
162
|
case 'text': return body;
|
|
163
|
-
case 'json': return
|
|
163
|
+
case 'json': return JSONUtil.parseSafe(body);
|
|
164
164
|
case 'form': return Object.fromEntries(new URLSearchParams(body));
|
|
165
165
|
}
|
|
166
166
|
}
|
package/src/util/endpoint.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { asConstructable, castKey, castTo, Class,
|
|
1
|
+
import { asConstructable, castKey, castTo, Class, TypedObject } from '@travetto/runtime';
|
|
2
2
|
import { BindUtil, SchemaParameterConfig, SchemaRegistryIndex, SchemaValidator, ValidationResultError } from '@travetto/schema';
|
|
3
3
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
4
|
-
import { RetargettingProxy } from '@travetto/registry';
|
|
5
4
|
|
|
6
5
|
import { WebChainedFilter, WebChainedContext, WebFilter } from '../types/filter.ts';
|
|
7
6
|
import { WebResponse } from '../types/response.ts';
|
|
@@ -240,10 +239,6 @@ export class EndpointUtil {
|
|
|
240
239
|
|
|
241
240
|
config.instance = await DependencyRegistryIndex.getInstance(config.class);
|
|
242
241
|
|
|
243
|
-
if (Runtime.dynamic) {
|
|
244
|
-
config.instance = RetargettingProxy.unwrap(config.instance);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
242
|
// Filter out conditional endpoints
|
|
248
243
|
const endpoints = (await Promise.all(
|
|
249
244
|
config.endpoints.map(endpoint => Promise.resolve(endpoint.conditional?.() ?? true).then(value => value ? endpoint : undefined))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buffer } from 'node:stream/consumers';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
3
|
|
|
4
|
-
import { AppError, BinaryUtil, castTo } from '@travetto/runtime';
|
|
4
|
+
import { AppError, BinaryUtil, castTo, JSONUtil } from '@travetto/runtime';
|
|
5
5
|
import { BindUtil } from '@travetto/schema';
|
|
6
6
|
|
|
7
7
|
import { WebResponse } from '../../src/types/response.ts';
|
|
@@ -51,7 +51,7 @@ export class WebTestDispatchUtil {
|
|
|
51
51
|
|
|
52
52
|
if (text) {
|
|
53
53
|
switch (response.headers.get('Content-Type')) {
|
|
54
|
-
case 'application/json': result =
|
|
54
|
+
case 'application/json': result = JSONUtil.parseSafe(castTo(text)); break;
|
|
55
55
|
case 'text/plain': result = text; break;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import { Test, Suite
|
|
4
|
-
import { Registry } from '@travetto/registry';
|
|
3
|
+
import { Test, Suite } from '@travetto/test';
|
|
5
4
|
|
|
6
5
|
import { BaseWebSuite } from './base.ts';
|
|
7
|
-
import
|
|
6
|
+
import './controller.ts'; // Ensure imported
|
|
8
7
|
|
|
9
8
|
@Suite()
|
|
10
9
|
export abstract class StandardWebServerSuite extends BaseWebSuite {
|
|
11
10
|
|
|
12
|
-
@BeforeAll()
|
|
13
|
-
async init() {
|
|
14
|
-
Registry.process([{ type: 'added', curr: TestController }]);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
@Test()
|
|
18
12
|
async getJSON() {
|
|
19
13
|
const response = await this.request({ context: { httpMethod: 'GET', path: '/test/json' } });
|