@travetto/web 7.0.0-rc.0 → 7.0.0-rc.2
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 +8 -8
- package/package.json +10 -10
- package/src/decorator/param.ts +16 -18
- package/src/registry/registry-adapter.ts +5 -6
- package/src/registry/registry-index.ts +2 -1
- package/src/registry/types.ts +0 -4
- package/src/registry/visitor.ts +2 -2
- package/src/util/endpoint.ts +7 -12
- package/support/test/suite/schema.ts +1 -1
package/README.md
CHANGED
|
@@ -155,10 +155,10 @@ class SimpleController {
|
|
|
155
155
|
|
|
156
156
|
### Parameters
|
|
157
157
|
Endpoints can be configured to describe and enforce parameter behavior. Request parameters can be defined in five areas:
|
|
158
|
-
* [@PathParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
159
|
-
* [@QueryParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
160
|
-
* [@Body](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
161
|
-
* [@HeaderParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
158
|
+
* [@PathParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L36) - Path params
|
|
159
|
+
* [@QueryParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L43) - Query params - can be either a single value or bind to a whole object
|
|
160
|
+
* [@Body](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L57) - Request body
|
|
161
|
+
* [@HeaderParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L50) - Header values
|
|
162
162
|
|
|
163
163
|
Each [@Param](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L16) can be configured to indicate:
|
|
164
164
|
* `name` - Name of param, field name, defaults to handler parameter name if necessary
|
|
@@ -223,7 +223,7 @@ export class Simple {
|
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
### ContextParam
|
|
226
|
-
In addition to endpoint parameters (i.e. user-provided inputs), there may also be a desire to access indirect contextual information. Specifically you may need access to the entire [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11). These are able to be injected using the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
226
|
+
In addition to endpoint parameters (i.e. user-provided inputs), there may also be a desire to access indirect contextual information. Specifically you may need access to the entire [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11). These are able to be injected using the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L64) on a class-level field from the [WebAsyncContext](https://github.com/travetto/travetto/tree/main/module/web/src/context.ts#L11). These are not exposed as endpoint parameters as they cannot be provided when making RPC invocations.
|
|
227
227
|
|
|
228
228
|
**Code: Example ContextParam usage**
|
|
229
229
|
```typescript
|
|
@@ -251,12 +251,12 @@ class ContextController {
|
|
|
251
251
|
}
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
**Note**: When referencing the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
254
|
+
**Note**: When referencing the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L64) values, the contract for idempotency needs to be carefully inspected, if expected. You can see in the example above that the [@CacheControl](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/common.ts#L45) decorator is used to ensure that the response is not cached.
|
|
255
255
|
|
|
256
256
|
### Validating Inputs
|
|
257
257
|
The module provides high level access for [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") support, via decorators, for validating and typing request inputs.
|
|
258
258
|
|
|
259
|
-
By default, all endpoint parameters are validated for type, and any additional constraints added (required, vs optional, minlength, etc). Each parameter location ([@PathParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
259
|
+
By default, all endpoint parameters are validated for type, and any additional constraints added (required, vs optional, minlength, etc). Each parameter location ([@PathParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L36), [@Body](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L57), [@QueryParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L43), [@HeaderParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L50)) primarily provides a source to bind the endpoint arguments from. Once bound, the module will validate that the provided arguments are in fact valid. All validation will occur before the endpoint is ever executed, ensuring a strong contract.
|
|
260
260
|
|
|
261
261
|
**Code: Using Body for POST requests**
|
|
262
262
|
```typescript
|
|
@@ -735,7 +735,7 @@ export class SimpleAuthInterceptor implements WebInterceptor {
|
|
|
735
735
|
```
|
|
736
736
|
|
|
737
737
|
## Cookie Support
|
|
738
|
-
Cookies are a unique element, within the framework, as they sit on the request and response flows. Ideally we would separate these out, but given the support for key rotation, there is a scenario in which reading a cookie on the request, will result in a cookie needing to be written on the response. Because of this, cookies are treated as being outside the normal [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11) activity, and is exposed as the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#
|
|
738
|
+
Cookies are a unique element, within the framework, as they sit on the request and response flows. Ideally we would separate these out, but given the support for key rotation, there is a scenario in which reading a cookie on the request, will result in a cookie needing to be written on the response. Because of this, cookies are treated as being outside the normal [WebRequest](https://github.com/travetto/travetto/tree/main/module/web/src/types/request.ts#L11) activity, and is exposed as the [@ContextParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L64) [CookieJar](https://github.com/travetto/travetto/tree/main/module/web/src/util/cookie.ts#L12). The [CookieJar](https://github.com/travetto/travetto/tree/main/module/web/src/util/cookie.ts#L12) has a fairly basic contract:
|
|
739
739
|
|
|
740
740
|
**Code: CookieJar contract**
|
|
741
741
|
```typescript
|
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.2",
|
|
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.1",
|
|
29
|
+
"@travetto/context": "^7.0.0-rc.1",
|
|
30
|
+
"@travetto/di": "^7.0.0-rc.1",
|
|
31
|
+
"@travetto/registry": "^7.0.0-rc.1",
|
|
32
|
+
"@travetto/runtime": "^7.0.0-rc.1",
|
|
33
|
+
"@travetto/schema": "^7.0.0-rc.1",
|
|
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.1",
|
|
38
|
+
"@travetto/test": "^7.0.0-rc.1",
|
|
39
|
+
"@travetto/transformer": "^7.0.0-rc.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
42
42
|
"@travetto/transformer": {
|
package/src/decorator/param.ts
CHANGED
|
@@ -13,50 +13,48 @@ type ParamDecorator = (instance: ClassInstance, property: string | symbol, idx:
|
|
|
13
13
|
* @augments `@travetto/schema:Input`
|
|
14
14
|
* @kind decorator
|
|
15
15
|
*/
|
|
16
|
-
export function Param(location: EndpointParamLocation,
|
|
16
|
+
export function Param(location: EndpointParamLocation, aliasOrConfig: string | Partial<EndpointParameterConfig>): ParamDecorator {
|
|
17
17
|
return (instance: ClassInstance, property: string | symbol, idx: number): void => {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const config = typeof aliasOrConfig === 'string' ? {} : aliasOrConfig;
|
|
19
|
+
const cls = getClass(instance);
|
|
20
|
+
if (typeof aliasOrConfig === 'string') {
|
|
21
|
+
SchemaRegistryIndex.getForRegister(cls).registerParameter(property, idx, {
|
|
22
|
+
aliases: [aliasOrConfig] // Register extra input string as an alias
|
|
23
|
+
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
ControllerRegistryIndex.getForRegister(
|
|
27
|
-
index: idx, location, ...config
|
|
28
|
-
});
|
|
26
|
+
ControllerRegistryIndex.getForRegister(cls).registerEndpointParameter(property, idx, { location, ...config });
|
|
29
27
|
};
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
31
|
* Define a Path param
|
|
34
|
-
* @
|
|
32
|
+
* @input input The param configuration or alias
|
|
35
33
|
* @augments `@travetto/schema:Input`
|
|
36
34
|
* @kind decorator
|
|
37
35
|
*/
|
|
38
|
-
export function PathParam(
|
|
36
|
+
export function PathParam(input: string | Partial<EndpointParameterConfig> = {}): ParamDecorator { return Param('path', input); }
|
|
39
37
|
/**
|
|
40
38
|
* Define a Query param
|
|
41
|
-
* @
|
|
39
|
+
* @input input The param configuration or alias
|
|
42
40
|
* @augments `@travetto/schema:Input`
|
|
43
41
|
* @kind decorator
|
|
44
42
|
*/
|
|
45
|
-
export function QueryParam(
|
|
43
|
+
export function QueryParam(input: string | Partial<EndpointParameterConfig> = {}): ParamDecorator { return Param('query', input); }
|
|
46
44
|
/**
|
|
47
45
|
* Define a Header param
|
|
48
|
-
* @
|
|
46
|
+
* @input input The param configuration or alias
|
|
49
47
|
* @augments `@travetto/schema:Input`
|
|
50
48
|
* @kind decorator
|
|
51
49
|
*/
|
|
52
|
-
export function HeaderParam(
|
|
50
|
+
export function HeaderParam(input: string | Partial<EndpointParameterConfig> = {}): ParamDecorator { return Param('header', input); }
|
|
53
51
|
/**
|
|
54
52
|
* Define a body param as an input
|
|
55
|
-
* @
|
|
53
|
+
* @input input The param configuration
|
|
56
54
|
* @augments `@travetto/schema:Input`
|
|
57
55
|
* @kind decorator
|
|
58
56
|
*/
|
|
59
|
-
export function Body(
|
|
57
|
+
export function Body(input: Partial<EndpointParameterConfig> = {}): ParamDecorator { return Param('body', input); }
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* A contextual field as provided by the WebAsyncContext
|
|
@@ -108,7 +108,6 @@ export class ControllerRegistryAdapter implements RegistryAdapter<ControllerConf
|
|
|
108
108
|
allowsBody: false,
|
|
109
109
|
class: this.#cls,
|
|
110
110
|
filters: [],
|
|
111
|
-
endpoint: this.#cls.prototype[method],
|
|
112
111
|
methodName: method.toString(),
|
|
113
112
|
id: `${this.#cls.name}#${method.toString()}`,
|
|
114
113
|
parameters: [],
|
|
@@ -125,11 +124,11 @@ export class ControllerRegistryAdapter implements RegistryAdapter<ControllerConf
|
|
|
125
124
|
return this.#endpoints.get(method)!;
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
registerEndpointParameter(method: string | symbol,
|
|
127
|
+
registerEndpointParameter(method: string | symbol, idx: number, ...config: Partial<EndpointParameterConfig>[]): EndpointParameterConfig {
|
|
129
128
|
const ep = this.registerEndpoint(method);
|
|
130
|
-
ep.parameters[
|
|
131
|
-
safeAssign(ep.parameters[
|
|
132
|
-
return ep.parameters[
|
|
129
|
+
ep.parameters[idx] ??= { index: idx, location: 'query' };
|
|
130
|
+
safeAssign(ep.parameters[idx], ...config);
|
|
131
|
+
return ep.parameters[idx];
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
finalize(): void {
|
|
@@ -139,7 +138,7 @@ export class ControllerRegistryAdapter implements RegistryAdapter<ControllerConf
|
|
|
139
138
|
ep.fullPath = `/${this.#config.basePath}/${ep.path}`.replace(/[/]{1,4}/g, '/').replace(/(.)[/]$/, (_, a) => a);
|
|
140
139
|
ep.finalizedResponseHeaders = new WebHeaders({ ...this.#config.responseHeaders, ...ep.responseHeaders });
|
|
141
140
|
ep.responseContext = { ...this.#config.responseContext, ...ep.responseContext };
|
|
142
|
-
for (const schema of SchemaRegistryIndex.
|
|
141
|
+
for (const schema of SchemaRegistryIndex.get(this.#cls).getMethod(ep.methodName).parameters) {
|
|
143
142
|
ep.parameters[schema.index!] ??= { index: schema.index!, location: undefined! };
|
|
144
143
|
ep.parameters[schema.index!].location ??= computeParameterLocation(ep, schema);
|
|
145
144
|
}
|
|
@@ -62,8 +62,9 @@ export class ControllerRegistryIndex implements RegistryIndex {
|
|
|
62
62
|
const ctx = await DependencyRegistryIndex.getInstance(WebAsyncContext);
|
|
63
63
|
const cls = getClass(instance);
|
|
64
64
|
const map = this.getController(cls).contextParams;
|
|
65
|
+
const fieldMap = SchemaRegistryIndex.get(cls).getFields();
|
|
65
66
|
for (const field of Object.keys(map)) {
|
|
66
|
-
const { type } =
|
|
67
|
+
const { type } = fieldMap[field];
|
|
67
68
|
Object.defineProperty(instance, field, { get: ctx.getSource(type) });
|
|
68
69
|
}
|
|
69
70
|
}
|
package/src/registry/types.ts
CHANGED
|
@@ -118,10 +118,6 @@ export interface EndpointConfig extends CoreConfig {
|
|
|
118
118
|
* The path of the endpoint
|
|
119
119
|
*/
|
|
120
120
|
path: string;
|
|
121
|
-
/**
|
|
122
|
-
* The function the endpoint will call
|
|
123
|
-
*/
|
|
124
|
-
endpoint: EndpointFunction;
|
|
125
121
|
/**
|
|
126
122
|
* The compiled and finalized handler
|
|
127
123
|
*/
|
package/src/registry/visitor.ts
CHANGED
|
@@ -28,12 +28,12 @@ export class ControllerVisitUtil {
|
|
|
28
28
|
|
|
29
29
|
await visitor.onControllerStart?.(controller);
|
|
30
30
|
for (const endpoint of controller.endpoints) {
|
|
31
|
-
const endpointSchema = SchemaRegistryIndex.
|
|
31
|
+
const endpointSchema = SchemaRegistryIndex.get(cls).getMethod(endpoint.methodName);
|
|
32
32
|
if (endpointSchema.private === true && options.skipPrivate) {
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const { parameters: params, returnType } =
|
|
36
|
+
const { parameters: params, returnType } = endpointSchema;
|
|
37
37
|
await visitor.onEndpointStart?.(endpoint, controller, params);
|
|
38
38
|
if (returnType) {
|
|
39
39
|
await this.#onSchemaEvent(visitor, returnType.type);
|
package/src/util/endpoint.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asConstructable, castTo, Class, Runtime, TypedObject } from '@travetto/runtime';
|
|
1
|
+
import { asConstructable, castKey, castTo, Class, Runtime, TypedObject } from '@travetto/runtime';
|
|
2
2
|
import { BindUtil, SchemaParameterConfig, SchemaRegistryIndex, SchemaValidator, ValidationResultError } from '@travetto/schema';
|
|
3
3
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
4
4
|
import { RetargettingProxy } from '@travetto/registry';
|
|
@@ -8,7 +8,7 @@ import { WebResponse } from '../types/response.ts';
|
|
|
8
8
|
import { WebInterceptor } from '../types/interceptor.ts';
|
|
9
9
|
import { WebRequest } from '../types/request.ts';
|
|
10
10
|
import { WEB_INTERCEPTOR_CATEGORIES } from '../types/core.ts';
|
|
11
|
-
import { EndpointConfig, ControllerConfig, EndpointParameterConfig } from '../registry/types.ts';
|
|
11
|
+
import { EndpointConfig, ControllerConfig, EndpointParameterConfig, EndpointFunction } from '../registry/types.ts';
|
|
12
12
|
import { ControllerRegistryIndex } from '../registry/registry-index.ts';
|
|
13
13
|
import { WebCommonUtil } from './common.ts';
|
|
14
14
|
|
|
@@ -122,14 +122,9 @@ export class EndpointUtil {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
let res = this.extractParameterValue(request, param, input.name!.toString(), input.array);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
res = this.extractParameterValue(request, param, name, input.array);
|
|
129
|
-
if (res !== undefined) {
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
125
|
+
let res = this.extractParameterValue(request, param, input.name!.toString(), input.array) ?? undefined;
|
|
126
|
+
for (let i = 0; res === undefined && input.aliases && i < input.aliases.length; i += 1) {
|
|
127
|
+
res = this.extractParameterValue(request, param, input.aliases[i], input.array) ?? undefined;
|
|
133
128
|
}
|
|
134
129
|
return res;
|
|
135
130
|
}
|
|
@@ -143,7 +138,7 @@ export class EndpointUtil {
|
|
|
143
138
|
static async extractParameters(endpoint: EndpointConfig, request: WebRequest): Promise<unknown[]> {
|
|
144
139
|
const cls = endpoint.class;
|
|
145
140
|
const vals = WebCommonUtil.getRequestParams(request);
|
|
146
|
-
const { parameters } = SchemaRegistryIndex.
|
|
141
|
+
const { parameters } = SchemaRegistryIndex.get(cls).getMethod(endpoint.methodName);
|
|
147
142
|
const combined = parameters.map((cfg) =>
|
|
148
143
|
({ schema: cfg, param: endpoint.parameters[cfg.index], value: vals?.[cfg.index] }));
|
|
149
144
|
|
|
@@ -177,7 +172,7 @@ export class EndpointUtil {
|
|
|
177
172
|
static async invokeEndpoint(endpoint: EndpointConfig, { request }: WebChainedContext): Promise<WebResponse> {
|
|
178
173
|
try {
|
|
179
174
|
const params = await this.extractParameters(endpoint, request);
|
|
180
|
-
const body = await endpoint.endpoint.apply(endpoint.instance, params);
|
|
175
|
+
const body = await castTo<EndpointFunction>(endpoint.instance![castKey(endpoint.methodName)]).apply(endpoint.instance, params);
|
|
181
176
|
const headers = endpoint.finalizedResponseHeaders;
|
|
182
177
|
let response: WebResponse;
|
|
183
178
|
if (body instanceof WebResponse) {
|
|
@@ -110,7 +110,7 @@ function getEndpoint(path: string, method: HttpMethod) {
|
|
|
110
110
|
|
|
111
111
|
function getEndpointResponse(path: string, method: HttpMethod) {
|
|
112
112
|
const ep = getEndpoint(path, method);
|
|
113
|
-
const resp = SchemaRegistryIndex.
|
|
113
|
+
const resp = SchemaRegistryIndex.get(SchemaAPI).getMethod(ep.methodName);
|
|
114
114
|
return resp?.returnType?.type;
|
|
115
115
|
}
|
|
116
116
|
|