shelving 1.140.0 → 1.142.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/api/{Resource.d.ts → Endpoint.d.ts} +32 -30
- package/api/Endpoint.js +64 -0
- package/api/index.d.ts +1 -1
- package/api/index.js +1 -1
- package/api/util.d.ts +31 -24
- package/api/util.js +47 -51
- package/package.json +1 -1
- package/util/http.d.ts +10 -71
- package/util/http.js +25 -97
- package/util/template.d.ts +4 -2
- package/util/template.js +9 -8
- package/api/Resource.js +0 -67
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { AnyCaller } from "../error/BaseError.js";
|
|
2
1
|
import type { Path } from "../util/path.js";
|
|
3
2
|
import { type Validator } from "../util/validate.js";
|
|
3
|
+
import type { EndpointCallback, EndpointHandler } from "./util.js";
|
|
4
4
|
/** Types for an HTTP request or response that does something. */
|
|
5
|
-
export type
|
|
6
|
-
/** A function that handles a resource request, with a payload and returns a result. */
|
|
7
|
-
export type ResourceCallback<P, R> = (payload: P, request: Request) => R | Promise<R>;
|
|
5
|
+
export type EndpointMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
8
6
|
/**
|
|
9
7
|
* An abstract API resource definition, used to specify types for e.g. serverless functions.
|
|
10
8
|
*
|
|
@@ -13,16 +11,16 @@ export type ResourceCallback<P, R> = (payload: P, request: Request) => R | Promi
|
|
|
13
11
|
* @param payload A `Validator` for the payload of the resource.
|
|
14
12
|
* @param result A `Validator` for the result of the resource.
|
|
15
13
|
*/
|
|
16
|
-
export declare class
|
|
17
|
-
/**
|
|
18
|
-
readonly method:
|
|
19
|
-
/**
|
|
14
|
+
export declare class Endpoint<P, R> implements Validator<R> {
|
|
15
|
+
/** Endpoint method. */
|
|
16
|
+
readonly method: EndpointMethod;
|
|
17
|
+
/** Endpoint path, e.g. `/patient/{id}` */
|
|
20
18
|
readonly path: Path;
|
|
21
19
|
/** Payload validator. */
|
|
22
20
|
readonly payload: Validator<P>;
|
|
23
21
|
/** Result validator. */
|
|
24
22
|
readonly result: Validator<R>;
|
|
25
|
-
constructor(method:
|
|
23
|
+
constructor(method: EndpointMethod, path: Path, payload: Validator<P>, result: Validator<R>);
|
|
26
24
|
/**
|
|
27
25
|
* Validate a payload for this resource.
|
|
28
26
|
*
|
|
@@ -34,46 +32,50 @@ export declare class Resource<P, R> implements Validator<R> {
|
|
|
34
32
|
* Validate a result for this resource.
|
|
35
33
|
*
|
|
36
34
|
* @returns The validated result for this resource.
|
|
37
|
-
* @throws
|
|
35
|
+
* @throws `Feedback` if the value is invalid. `Feedback` instances can be reported safely back to the end client so they know how to fix their request.
|
|
38
36
|
*/
|
|
39
|
-
validate(unsafeResult: unknown
|
|
37
|
+
validate(unsafeResult: unknown): R;
|
|
38
|
+
/**
|
|
39
|
+
* Return an `EndpointHandler` for this endpoint
|
|
40
|
+
*/
|
|
41
|
+
handler(callback: EndpointCallback<P, R>): EndpointHandler<P, R>;
|
|
40
42
|
}
|
|
41
|
-
/** Extract the payload type from a `
|
|
42
|
-
export type PayloadType<X extends
|
|
43
|
-
/** Extract the result type from a `
|
|
44
|
-
export type
|
|
43
|
+
/** Extract the payload type from a `Endpoint`. */
|
|
44
|
+
export type PayloadType<X extends Endpoint<unknown, unknown>> = X extends Endpoint<infer Y, unknown> ? Y : never;
|
|
45
|
+
/** Extract the result type from a `Endpoint`. */
|
|
46
|
+
export type EndpointType<X extends Endpoint<unknown, unknown>> = X extends Endpoint<unknown, infer Y> ? Y : never;
|
|
45
47
|
/**
|
|
46
48
|
* Represent a GET request to a specified path, with validated payload and return types.
|
|
47
49
|
* "The GET method requests a representation of the specified resource. Requests using GET should only retrieve data and should not contain a request content."
|
|
48
50
|
*/
|
|
49
|
-
export declare function GET<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>):
|
|
50
|
-
export declare function GET<P>(path: Path, payload: Validator<P>):
|
|
51
|
-
export declare function GET<R>(path: Path, payload: undefined, result: Validator<R>):
|
|
51
|
+
export declare function GET<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>): Endpoint<P, R>;
|
|
52
|
+
export declare function GET<P>(path: Path, payload: Validator<P>): Endpoint<P, undefined>;
|
|
53
|
+
export declare function GET<R>(path: Path, payload: undefined, result: Validator<R>): Endpoint<undefined, R>;
|
|
52
54
|
/**
|
|
53
55
|
* Represent a POST request to a specified path, with validated payload and return types.
|
|
54
56
|
* "The POST method submits an entity to the specified resource, often causing a change in state or side effects on the server.
|
|
55
57
|
*/
|
|
56
|
-
export declare function POST<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>):
|
|
57
|
-
export declare function POST<P>(path: Path, payload: Validator<P>):
|
|
58
|
-
export declare function POST<R>(path: Path, payload: undefined, result: Validator<R>):
|
|
58
|
+
export declare function POST<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>): Endpoint<P, R>;
|
|
59
|
+
export declare function POST<P>(path: Path, payload: Validator<P>): Endpoint<P, undefined>;
|
|
60
|
+
export declare function POST<R>(path: Path, payload: undefined, result: Validator<R>): Endpoint<undefined, R>;
|
|
59
61
|
/**
|
|
60
62
|
* Represent a PUT request to a specified path, with validated payload and return types.
|
|
61
63
|
* "The PUT method replaces all current representations of the target resource with the request content."
|
|
62
64
|
*/
|
|
63
|
-
export declare function PUT<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>):
|
|
64
|
-
export declare function PUT<P>(path: Path, payload: Validator<P>):
|
|
65
|
-
export declare function PUT<R>(path: Path, payload: undefined, result: Validator<R>):
|
|
65
|
+
export declare function PUT<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>): Endpoint<P, R>;
|
|
66
|
+
export declare function PUT<P>(path: Path, payload: Validator<P>): Endpoint<P, undefined>;
|
|
67
|
+
export declare function PUT<R>(path: Path, payload: undefined, result: Validator<R>): Endpoint<undefined, R>;
|
|
66
68
|
/**
|
|
67
69
|
* Represent a PATCH request to a specified path, with validated payload and return types.
|
|
68
70
|
* "The PATCH method applies partial modifications to a resource."
|
|
69
71
|
*/
|
|
70
|
-
export declare function PATCH<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>):
|
|
71
|
-
export declare function PATCH<P>(path: Path, payload: Validator<P>):
|
|
72
|
-
export declare function PATCH<R>(path: Path, payload: undefined, result: Validator<R>):
|
|
72
|
+
export declare function PATCH<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>): Endpoint<P, R>;
|
|
73
|
+
export declare function PATCH<P>(path: Path, payload: Validator<P>): Endpoint<P, undefined>;
|
|
74
|
+
export declare function PATCH<R>(path: Path, payload: undefined, result: Validator<R>): Endpoint<undefined, R>;
|
|
73
75
|
/**
|
|
74
76
|
* Represent a DELETE request to a specified path, with validated payload and return types.
|
|
75
77
|
* "The DELETE method deletes the specified resource."
|
|
76
78
|
*/
|
|
77
|
-
export declare function DELETE<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>):
|
|
78
|
-
export declare function DELETE<P>(path: Path, payload: Validator<P>):
|
|
79
|
-
export declare function DELETE<R>(path: Path, payload: undefined, result: Validator<R>):
|
|
79
|
+
export declare function DELETE<P, R>(path: Path, payload?: Validator<P>, result?: Validator<R>): Endpoint<P, R>;
|
|
80
|
+
export declare function DELETE<P>(path: Path, payload: Validator<P>): Endpoint<P, undefined>;
|
|
81
|
+
export declare function DELETE<R>(path: Path, payload: undefined, result: Validator<R>): Endpoint<undefined, R>;
|
package/api/Endpoint.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UNDEFINED } from "../util/validate.js";
|
|
2
|
+
/**
|
|
3
|
+
* An abstract API resource definition, used to specify types for e.g. serverless functions.
|
|
4
|
+
*
|
|
5
|
+
* @param method The method of the resource, e.g. `GET`
|
|
6
|
+
* @param path The path of the resource optionally including `{placeholder}` values, e.g. `/patient/{id}`
|
|
7
|
+
* @param payload A `Validator` for the payload of the resource.
|
|
8
|
+
* @param result A `Validator` for the result of the resource.
|
|
9
|
+
*/
|
|
10
|
+
export class Endpoint {
|
|
11
|
+
/** Endpoint method. */
|
|
12
|
+
method;
|
|
13
|
+
/** Endpoint path, e.g. `/patient/{id}` */
|
|
14
|
+
path;
|
|
15
|
+
/** Payload validator. */
|
|
16
|
+
payload;
|
|
17
|
+
/** Result validator. */
|
|
18
|
+
result;
|
|
19
|
+
constructor(method, path, payload, result) {
|
|
20
|
+
this.method = method;
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.payload = payload;
|
|
23
|
+
this.result = result;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate a payload for this resource.
|
|
27
|
+
*
|
|
28
|
+
* @returns The validated payload for this resource.
|
|
29
|
+
* @throws `Feedback` if the payload is invalid. `Feedback` instances can be reported safely back to the end client so they know how to fix their request.
|
|
30
|
+
*/
|
|
31
|
+
prepare(unsafePayload) {
|
|
32
|
+
return this.payload.validate(unsafePayload);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate a result for this resource.
|
|
36
|
+
*
|
|
37
|
+
* @returns The validated result for this resource.
|
|
38
|
+
* @throws `Feedback` if the value is invalid. `Feedback` instances can be reported safely back to the end client so they know how to fix their request.
|
|
39
|
+
*/
|
|
40
|
+
validate(unsafeResult) {
|
|
41
|
+
return this.result.validate(unsafeResult);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Return an `EndpointHandler` for this endpoint
|
|
45
|
+
*/
|
|
46
|
+
handler(callback) {
|
|
47
|
+
return { endpoint: this, callback };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function GET(path, payload, result) {
|
|
51
|
+
return new Endpoint("GET", path, payload || UNDEFINED, result || UNDEFINED);
|
|
52
|
+
}
|
|
53
|
+
export function POST(path, payload, result) {
|
|
54
|
+
return new Endpoint("POST", path, payload || UNDEFINED, result || UNDEFINED);
|
|
55
|
+
}
|
|
56
|
+
export function PUT(path, payload, result) {
|
|
57
|
+
return new Endpoint("PUT", path, payload || UNDEFINED, result || UNDEFINED);
|
|
58
|
+
}
|
|
59
|
+
export function PATCH(path, payload, result) {
|
|
60
|
+
return new Endpoint("PATCH", path, payload || UNDEFINED, result || UNDEFINED);
|
|
61
|
+
}
|
|
62
|
+
export function DELETE(path, payload, result) {
|
|
63
|
+
return new Endpoint("DELETE", path, payload || UNDEFINED, result || UNDEFINED);
|
|
64
|
+
}
|
package/api/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./Endpoint.js";
|
|
2
2
|
export * from "./util.js";
|
package/api/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./Endpoint.js";
|
|
2
2
|
export * from "./util.js";
|
package/api/util.d.ts
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Resource, ResourceCallback } from "./Resource.js";
|
|
1
|
+
import type { Endpoint } from "./Endpoint.js";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
3
|
+
* A function that handles a endpoint request, with a payload and returns a result.
|
|
5
4
|
*
|
|
6
|
-
* @param
|
|
5
|
+
* @param payload The payload of the request is the result of merging the `{placeholder}` path parameters and `?a=123` query parameters from the URL, with the body of the request.
|
|
6
|
+
* - Payload is validated by the payload validator for the `Endpoint`.
|
|
7
|
+
* - If the body of the `Request` is a data object (i.e. a plain object), then body data is merged with the path and query parameters to form a single flat object.
|
|
8
|
+
* - If payload is _not_ a data object (i.e. it's another JSON type like `string` or `number`) then the payload include the path and query parameters, and a key called `content` that contains the body of the request.
|
|
7
9
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* > @param payload is the parse body content of the request.
|
|
10
|
-
* > - If the body is parsed as a `Data` object, also adds in path `{placeholders}` and `?query=` parameters in the URL.
|
|
10
|
+
* @param request The raw `Request` object in case it needs any additional processing.
|
|
11
11
|
*
|
|
12
|
-
* @returns
|
|
13
|
-
* > @returns `undefined` if the request does not match the resource's method or path.
|
|
14
|
-
* > @returns `Response` (possibly async) if the callback returns a valid response.
|
|
15
|
-
* > @throws `RequestError` if the request matches but the payload from the request is invalid (this is an end user error in the request that needs to be reported back).
|
|
16
|
-
* > @throws `ValueError` if the request matches but the value returned by the callback is invalid (this is more likely internal developer error that is reporting something wrong in the callback implementation).
|
|
12
|
+
* @returns The correct `Result` type for the `Endpoint`, or a raw `Response` object if you wish to return a custom response.
|
|
17
13
|
*/
|
|
18
|
-
export
|
|
14
|
+
export type EndpointCallback<P, R> = (payload: P, request: Request) => R | Response | Promise<R | Response>;
|
|
19
15
|
/**
|
|
20
|
-
*
|
|
16
|
+
* Object combining an abstract `Endpoint` and an `EndpointCallback` implementation.
|
|
17
|
+
*/
|
|
18
|
+
export interface EndpointHandler<P, R> {
|
|
19
|
+
readonly endpoint: Endpoint<P, R>;
|
|
20
|
+
readonly callback: EndpointCallback<P, R>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Any handler (purposefully as wide as possible for use with `extends X` or `is X` statements).
|
|
24
|
+
*/
|
|
25
|
+
export type AnyEndpointHandler = EndpointHandler<any, any>;
|
|
26
|
+
/**
|
|
27
|
+
* List of `EndpointHandler` objects objects that can handle requests to an `Endpoint`.
|
|
28
|
+
*/
|
|
29
|
+
export type EndpointHandlers = ReadonlyArray<AnyEndpointHandler>;
|
|
30
|
+
/**
|
|
31
|
+
* Handler a `Request` with the first matching `EndpointHandlers`.
|
|
21
32
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* > @param payload is the parse body content of the request.
|
|
26
|
-
* > - If the body is parsed as a `Data` object, also adds in path `{placeholders}` and `?query=` parameters in the URL.
|
|
33
|
+
* 1. Define your `Endpoint` objects with a method, path, payload and result validators, e.g. `GET("/test/{id}", PAYLOAD, STRING)`
|
|
34
|
+
* 2. Make an array of `EndpointHandler` objects combining an `Endpoint` with a `callback` function
|
|
35
|
+
* -
|
|
27
36
|
*
|
|
28
|
-
* @returns `
|
|
29
|
-
* @
|
|
30
|
-
* @throws `Feedback` if the payload the client user provided is invalid. `Feedback` instances can be reported safely back to the end client so they know how to fix their request.
|
|
31
|
-
* @throws `ValueError` if the request matches but the value returned by the callback is invalid (this is an internal error that is reporting something wrong in the callback implementation).
|
|
37
|
+
* @returns The resulting `Response` from the first handler that matches the `Request`.
|
|
38
|
+
* @throws `NotFoundError` if no handler matches the `Request`.
|
|
32
39
|
*/
|
|
33
|
-
export declare function
|
|
40
|
+
export declare function handleEndpoints(request: Request, endpoints: EndpointHandlers): Promise<Response>;
|
package/api/util.js
CHANGED
|
@@ -1,65 +1,61 @@
|
|
|
1
|
-
import { RequestError } from "../error/RequestError.js";
|
|
2
|
-
import {
|
|
1
|
+
import { NotFoundError, RequestError } from "../error/RequestError.js";
|
|
2
|
+
import { ValueError } from "../error/ValueError.js";
|
|
3
|
+
import { isData } from "../util/data.js";
|
|
4
|
+
import { getDictionary } from "../util/dictionary.js";
|
|
5
|
+
import { getRequestContent } from "../util/http.js";
|
|
3
6
|
import { matchTemplate } from "../util/template.js";
|
|
4
7
|
import { getURL } from "../util/url.js";
|
|
8
|
+
import { getValid } from "../util/validate.js";
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
10
|
+
* Handler a `Request` with the first matching `EndpointHandlers`.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
12
|
+
* 1. Define your `Endpoint` objects with a method, path, payload and result validators, e.g. `GET("/test/{id}", PAYLOAD, STRING)`
|
|
13
|
+
* 2. Make an array of `EndpointHandler` objects combining an `Endpoint` with a `callback` function
|
|
14
|
+
* -
|
|
9
15
|
*
|
|
10
|
-
* @
|
|
11
|
-
*
|
|
12
|
-
* > - If the body is parsed as a `Data` object, also adds in path `{placeholders}` and `?query=` parameters in the URL.
|
|
13
|
-
*
|
|
14
|
-
* @returns An `OptionalHandler` that might handle a given `Request` if it matches the resource's method and path.
|
|
15
|
-
* > @returns `undefined` if the request does not match the resource's method or path.
|
|
16
|
-
* > @returns `Response` (possibly async) if the callback returns a valid response.
|
|
17
|
-
* > @throws `RequestError` if the request matches but the payload from the request is invalid (this is an end user error in the request that needs to be reported back).
|
|
18
|
-
* > @throws `ValueError` if the request matches but the value returned by the callback is invalid (this is more likely internal developer error that is reporting something wrong in the callback implementation).
|
|
19
|
-
*/
|
|
20
|
-
export function createResourceHandler(resource, callback) {
|
|
21
|
-
return request => handleResourceRequest(request, resource, callback);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Handle a `Request` to a `Resource` using a `ResourceCallback` that implements the logic for the resource.
|
|
25
|
-
*
|
|
26
|
-
* @param request The `Request` to handle, which may have a method and URL that match the resource's method and path.
|
|
27
|
-
* @param resource A `Resource` instance that defines the method, path, and allowed types for the request.
|
|
28
|
-
* @param callback A callback function that will be called with the validated payload and should return the correct response type for the resource.
|
|
29
|
-
* > @param payload is the parse body content of the request.
|
|
30
|
-
* > - If the body is parsed as a `Data` object, also adds in path `{placeholders}` and `?query=` parameters in the URL.
|
|
31
|
-
*
|
|
32
|
-
* @returns `undefined` if the request does not match the resource's method or path.
|
|
33
|
-
* @returns `Response` (possibly async) if the callback returns a valid response.
|
|
34
|
-
* @throws `Feedback` if the payload the client user provided is invalid. `Feedback` instances can be reported safely back to the end client so they know how to fix their request.
|
|
35
|
-
* @throws `ValueError` if the request matches but the value returned by the callback is invalid (this is an internal error that is reporting something wrong in the callback implementation).
|
|
16
|
+
* @returns The resulting `Response` from the first handler that matches the `Request`.
|
|
17
|
+
* @throws `NotFoundError` if no handler matches the `Request`.
|
|
36
18
|
*/
|
|
37
|
-
export function
|
|
38
|
-
// Ensure the request method e.g. `GET`, does not match the resource method e.g. `POST`
|
|
39
|
-
if (request.method !== resource.method)
|
|
40
|
-
return undefined;
|
|
19
|
+
export function handleEndpoints(request, endpoints) {
|
|
41
20
|
// Parse the URL of the request.
|
|
42
21
|
const url = getURL(request.url);
|
|
43
22
|
if (!url)
|
|
44
|
-
throw new RequestError("Invalid request URL", { received: request.url, caller:
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
23
|
+
throw new RequestError("Invalid request URL", { received: request.url, caller: handleEndpoints });
|
|
24
|
+
const { pathname, searchParams } = url;
|
|
25
|
+
// Iterate over the handlers and return the first one that matches the request.
|
|
26
|
+
for (const { endpoint, callback } of endpoints) {
|
|
27
|
+
// Ensure the request method e.g. `GET`, does not match the endpoint method e.g. `POST`
|
|
28
|
+
if (request.method !== endpoint.method)
|
|
29
|
+
continue;
|
|
30
|
+
// Ensure the request URL e.g. `/user/123` matches the endpoint path e.g. `/user/{id}`
|
|
31
|
+
// Any `{placeholders}` in the endpoint path are matched against the request URL to extract parameters.
|
|
32
|
+
const pathParams = matchTemplate(endpoint.path, pathname, handleEndpoints);
|
|
33
|
+
if (!pathParams)
|
|
34
|
+
continue;
|
|
35
|
+
// Make a simple dictionary object from the `{placeholder}` path params and the `?a=123` query params from the URL.
|
|
36
|
+
const params = searchParams.size ? { ...getDictionary(searchParams), ...pathParams } : pathParams;
|
|
37
|
+
// Get the response by calling the callback.
|
|
38
|
+
return getEndpointResponse(endpoint, callback, params, request);
|
|
39
|
+
}
|
|
40
|
+
// No handler matched the request.
|
|
41
|
+
throw new NotFoundError("Not found", { request, caller: handleEndpoints });
|
|
55
42
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
43
|
+
async function getEndpointResponse(endpoint, callback, params, request) {
|
|
44
|
+
// Extract a data object from the request body and validate it against the endpoint's payload type.
|
|
45
|
+
const content = await getRequestContent(request, handleEndpoints);
|
|
46
|
+
// If content is undefined, it means the request has no body, so params are the only payload.
|
|
47
|
+
// If the content is a data object merge if with the params.
|
|
48
|
+
// If the content is not a data object (e.g. string, number, array), set a single `content` property and merge it with the params.
|
|
49
|
+
const unsafePayload = content === undefined ? params : isData(content) ? { ...content, ...params } : { content, ...params };
|
|
50
|
+
const payload = endpoint.prepare(unsafePayload);
|
|
51
|
+
// Call the callback with the validated payload to get the result.
|
|
52
|
+
const returned = await callback(payload, request);
|
|
53
|
+
// If the callback returned a `Response`, return it directly.
|
|
54
|
+
if (returned instanceof Response)
|
|
55
|
+
return returned;
|
|
56
|
+
// Otherwise validate the result against the endpoint's result type.
|
|
61
57
|
// Throw a `ValueError` if the result is not valid, which indicates an internal error in the callback implementation.
|
|
62
|
-
const result =
|
|
58
|
+
const result = getValid(returned, endpoint, ValueError, handleEndpoints);
|
|
63
59
|
// Return a new `Response` with a 200 status and the validated result data.
|
|
64
60
|
return Response.json(result);
|
|
65
61
|
}
|
package/package.json
CHANGED
package/util/http.d.ts
CHANGED
|
@@ -1,89 +1,28 @@
|
|
|
1
1
|
import type { AnyCaller } from "../error/BaseError.js";
|
|
2
2
|
import { RequestError } from "../error/RequestError.js";
|
|
3
3
|
import { ResponseError } from "../error/ResponseError.js";
|
|
4
|
-
import type { ImmutableArray } from "./array.js";
|
|
5
|
-
import { type Data } from "./data.js";
|
|
6
|
-
import type { Optional } from "./optional.js";
|
|
7
4
|
/** A handler function takes a `Request` and returns a `Response` (possibly asynchronously). */
|
|
8
|
-
export type
|
|
9
|
-
/** An optional handler function _may_ match a `Request` and return a `Response`, or may return `undefined` if it doesn't match. */
|
|
10
|
-
export type OptionalHandler = (request: Request) => Optional<Response | Promise<Response>>;
|
|
11
|
-
/** A list of `OptionalHandler` functions that may match a `Request` */
|
|
12
|
-
export type Handlers = ImmutableArray<OptionalHandler>;
|
|
5
|
+
export type RequestHandler = (request: Request) => Response | Promise<Response>;
|
|
13
6
|
export declare function _getMessageJSON(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
14
|
-
export declare function
|
|
15
|
-
export declare function _requireMessageData(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<Data>;
|
|
7
|
+
export declare function _getMessageFormData(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
16
8
|
export declare function _getMessageContent(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
17
9
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @returns string (if the content type is `text/plain`)
|
|
21
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
22
|
-
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
23
|
-
*/
|
|
24
|
-
export declare function getRequestJSON(message: Request): Promise<unknown>;
|
|
25
|
-
/**
|
|
26
|
-
* Parse the content of an HTTP `Response` based as JSON, or throw `ResponseError` if the content could not be getd.
|
|
27
|
-
*
|
|
28
|
-
* @returns string (if the content type is `text/plain`)
|
|
29
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
30
|
-
* @throws ResponseError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
31
|
-
*/
|
|
32
|
-
export declare function getResponseJSON(message: Response): Promise<unknown>;
|
|
33
|
-
/**
|
|
34
|
-
* Require the content of an HTTP `Request` as a data object in JSON format, or throw `RequestError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
35
|
-
*
|
|
36
|
-
* @returns string (if the content type is `text/plain`)
|
|
37
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
38
|
-
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
39
|
-
*/
|
|
40
|
-
export declare function getRequestData(message: Request): Promise<Data | undefined>;
|
|
41
|
-
/**
|
|
42
|
-
* Require the content of an HTTP `Response` as a data object in JSON format, or throw `ResponseError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
43
|
-
*
|
|
44
|
-
* @returns string (if the content type is `text/plain`)
|
|
45
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
46
|
-
* @throws ResponseError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
47
|
-
*/
|
|
48
|
-
export declare function getResponseData(message: Response): Promise<Data | undefined>;
|
|
49
|
-
/**
|
|
50
|
-
* Require the content of an HTTP `Request` as a data object in JSON format, or throw `RequestError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
51
|
-
*
|
|
52
|
-
* @returns string (if the content type is `text/plain`)
|
|
53
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
54
|
-
* @throws RequestError if the content is not `application/json` with valid JSON that parses as a Data object.
|
|
55
|
-
*/
|
|
56
|
-
export declare function requireRequestData(message: Request): Promise<Data>;
|
|
57
|
-
/**
|
|
58
|
-
* Require the content of an HTTP `Response` as a data object in JSON format, or throw `ResponseError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
59
|
-
*
|
|
60
|
-
* @returns string (if the content type is `text/plain`)
|
|
61
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
62
|
-
* @throws ResponseError if the content is not `application/json` with valid JSON that parses as a Data object.
|
|
63
|
-
*/
|
|
64
|
-
export declare function requireResponseData(message: Response): Promise<Data>;
|
|
65
|
-
/**
|
|
66
|
-
* Get the content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
|
|
10
|
+
* Get the body content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
|
|
67
11
|
*
|
|
68
12
|
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
69
|
-
* @returns unknown If content type is `application/
|
|
13
|
+
* @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
|
|
14
|
+
* @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
|
|
70
15
|
*
|
|
71
16
|
* @throws RequestError if the content is not `text/plain`, or `application/json` with valid JSON.
|
|
72
17
|
*/
|
|
73
|
-
export declare function getRequestContent(message: Request): Promise<unknown>;
|
|
18
|
+
export declare function getRequestContent(message: Request, caller?: AnyCaller): Promise<unknown>;
|
|
74
19
|
/**
|
|
75
|
-
* Get the content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
|
|
20
|
+
* Get the body content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
|
|
76
21
|
*
|
|
77
22
|
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
78
|
-
* @returns unknown If content type is `application/
|
|
23
|
+
* @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
|
|
24
|
+
* @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
|
|
79
25
|
*
|
|
80
26
|
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
81
27
|
*/
|
|
82
|
-
export declare function getResponseContent(message: Response): Promise<unknown>;
|
|
83
|
-
/**
|
|
84
|
-
* Match a `Request` against a `Handlers` array.
|
|
85
|
-
*
|
|
86
|
-
* @returns The resulting `Response` from the first handler that matches the `Request`.
|
|
87
|
-
* @throws `NotFoundError` if no handler matches the `Request`.
|
|
88
|
-
*/
|
|
89
|
-
export declare function handleRequest(request: Request, handlers: ImmutableArray<OptionalHandler>): Response | Promise<Response>;
|
|
28
|
+
export declare function getResponseContent(message: Response, caller?: AnyCaller): Promise<unknown>;
|
package/util/http.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequestError } from "../error/RequestError.js";
|
|
2
2
|
import { ResponseError } from "../error/ResponseError.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getDictionary } from "./dictionary.js";
|
|
4
4
|
export async function _getMessageJSON(message, MessageError, caller) {
|
|
5
5
|
const trimmed = (await message.text()).trim();
|
|
6
6
|
if (!trimmed.length)
|
|
@@ -12,119 +12,47 @@ export async function _getMessageJSON(message, MessageError, caller) {
|
|
|
12
12
|
throw new MessageError("Body must be valid JSON", { received: trimmed, caller });
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
export async function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const data = await _getMessageJSON(message, MessageError, caller);
|
|
23
|
-
if (isData(data))
|
|
24
|
-
return data;
|
|
25
|
-
throw new MessageError("Body must be data object", { received: data, caller });
|
|
15
|
+
export async function _getMessageFormData(message, MessageError, caller) {
|
|
16
|
+
try {
|
|
17
|
+
return getDictionary(await message.formData());
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new MessageError("Body must be valid valid form multipart data", { caller });
|
|
21
|
+
}
|
|
26
22
|
}
|
|
27
23
|
export function _getMessageContent(message, MessageError, caller) {
|
|
28
24
|
const type = message.headers.get("Content-Type");
|
|
29
|
-
if (type?.startsWith("application/json"))
|
|
30
|
-
return _getMessageJSON(message, MessageError, caller);
|
|
31
25
|
if (type?.startsWith("text/plain"))
|
|
32
26
|
return message.text();
|
|
27
|
+
if (type?.startsWith("application/json"))
|
|
28
|
+
return _getMessageJSON(message, MessageError, caller);
|
|
29
|
+
if (type?.startsWith("multipart/form-data"))
|
|
30
|
+
return _getMessageFormData(message, MessageError, caller);
|
|
33
31
|
throw new MessageError("Unexpected content type", { received: type, caller });
|
|
34
32
|
}
|
|
35
33
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* @returns string (if the content type is `text/plain`)
|
|
39
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
40
|
-
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
41
|
-
*/
|
|
42
|
-
export function getRequestJSON(message) {
|
|
43
|
-
return _getMessageJSON(message, RequestError, getRequestJSON);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Parse the content of an HTTP `Response` based as JSON, or throw `ResponseError` if the content could not be getd.
|
|
47
|
-
*
|
|
48
|
-
* @returns string (if the content type is `text/plain`)
|
|
49
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
50
|
-
* @throws ResponseError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
51
|
-
*/
|
|
52
|
-
export function getResponseJSON(message) {
|
|
53
|
-
return _getMessageJSON(message, ResponseError, getResponseJSON);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Require the content of an HTTP `Request` as a data object in JSON format, or throw `RequestError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
57
|
-
*
|
|
58
|
-
* @returns string (if the content type is `text/plain`)
|
|
59
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
60
|
-
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
61
|
-
*/
|
|
62
|
-
export function getRequestData(message) {
|
|
63
|
-
return _getMessageData(message, RequestError, getRequestData);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Require the content of an HTTP `Response` as a data object in JSON format, or throw `ResponseError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
67
|
-
*
|
|
68
|
-
* @returns string (if the content type is `text/plain`)
|
|
69
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
70
|
-
* @throws ResponseError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
71
|
-
*/
|
|
72
|
-
export function getResponseData(message) {
|
|
73
|
-
return _getMessageData(message, ResponseError, getResponseData);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Require the content of an HTTP `Request` as a data object in JSON format, or throw `RequestError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
77
|
-
*
|
|
78
|
-
* @returns string (if the content type is `text/plain`)
|
|
79
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
80
|
-
* @throws RequestError if the content is not `application/json` with valid JSON that parses as a Data object.
|
|
81
|
-
*/
|
|
82
|
-
export function requireRequestData(message) {
|
|
83
|
-
return _requireMessageData(message, RequestError, requireRequestData);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Require the content of an HTTP `Response` as a data object in JSON format, or throw `ResponseError` if the content cannot not be parsed or is not a data object in JSON format.
|
|
87
|
-
*
|
|
88
|
-
* @returns string (if the content type is `text/plain`)
|
|
89
|
-
* @returns Data If the content can be parsed as a JSON object, or `undefined` if the content was empty.
|
|
90
|
-
* @throws ResponseError if the content is not `application/json` with valid JSON that parses as a Data object.
|
|
91
|
-
*/
|
|
92
|
-
export function requireResponseData(message) {
|
|
93
|
-
return _requireMessageData(message, ResponseError, requireResponseData);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Get the content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
|
|
34
|
+
* Get the body content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
|
|
97
35
|
*
|
|
98
36
|
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
99
|
-
* @returns unknown If content type is `application/
|
|
37
|
+
* @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
|
|
38
|
+
* @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
|
|
100
39
|
*
|
|
101
40
|
* @throws RequestError if the content is not `text/plain`, or `application/json` with valid JSON.
|
|
102
41
|
*/
|
|
103
|
-
export function getRequestContent(message) {
|
|
104
|
-
|
|
42
|
+
export function getRequestContent(message, caller = getRequestContent) {
|
|
43
|
+
if (message.method === "GET" || message.method === "HEAD")
|
|
44
|
+
return Promise.resolve(undefined);
|
|
45
|
+
return _getMessageContent(message, RequestError, caller);
|
|
105
46
|
}
|
|
106
47
|
/**
|
|
107
|
-
* Get the content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
|
|
48
|
+
* Get the body content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
|
|
108
49
|
*
|
|
109
50
|
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
110
|
-
* @returns unknown If content type is `application/
|
|
51
|
+
* @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
|
|
52
|
+
* @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
|
|
111
53
|
*
|
|
112
54
|
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
113
55
|
*/
|
|
114
|
-
export function getResponseContent(message) {
|
|
115
|
-
return _getMessageContent(message, ResponseError,
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Match a `Request` against a `Handlers` array.
|
|
119
|
-
*
|
|
120
|
-
* @returns The resulting `Response` from the first handler that matches the `Request`.
|
|
121
|
-
* @throws `NotFoundError` if no handler matches the `Request`.
|
|
122
|
-
*/
|
|
123
|
-
export function handleRequest(request, handlers) {
|
|
124
|
-
for (const handler of handlers) {
|
|
125
|
-
const response = handler(request);
|
|
126
|
-
if (response)
|
|
127
|
-
return response;
|
|
128
|
-
}
|
|
129
|
-
throw new NotFoundError("Not found", { request, caller: handleRequest });
|
|
56
|
+
export function getResponseContent(message, caller = getResponseContent) {
|
|
57
|
+
return _getMessageContent(message, ResponseError, caller);
|
|
130
58
|
}
|
package/util/template.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
import type { ImmutableArray } from "./array.js";
|
|
2
3
|
import { type ImmutableDictionary } from "./dictionary.js";
|
|
3
4
|
import type { NotString } from "./string.js";
|
|
@@ -28,9 +29,10 @@ export declare function getPlaceholders(template: string): readonly string[];
|
|
|
28
29
|
* @param templates Either a single template string, or an iterator that returns multiple template template strings.
|
|
29
30
|
* - Template strings can include placeholders (e.g. `:name-${country}/{city}`).
|
|
30
31
|
* @param target The string containing values, e.g. `Dave-UK/Manchester`
|
|
32
|
+
*
|
|
31
33
|
* @return An object containing values, e.g. `{ name: "Dave", country: "UK", city: "Manchester" }`, or undefined if target didn't match the template.
|
|
32
34
|
*/
|
|
33
|
-
export declare function matchTemplate(template: string, target: string): TemplateDictionary | undefined;
|
|
35
|
+
export declare function matchTemplate(template: string, target: string, caller?: AnyCaller): TemplateDictionary | undefined;
|
|
34
36
|
/**
|
|
35
37
|
* Match multiple templates against a target string and return the first match.
|
|
36
38
|
*/
|
|
@@ -44,4 +46,4 @@ export declare function matchTemplates(templates: Iterable<string> & NotString,
|
|
|
44
46
|
*
|
|
45
47
|
* @throws {RequiredError} If a placeholder in the template string is not specified in values.
|
|
46
48
|
*/
|
|
47
|
-
export declare function renderTemplate(template: string, values: TemplateValues): string;
|
|
49
|
+
export declare function renderTemplate(template: string, values: TemplateValues, caller?: AnyCaller): string;
|
package/util/template.js
CHANGED
|
@@ -24,7 +24,7 @@ function _splitTemplate(template, caller) {
|
|
|
24
24
|
const placeholder = matches[i];
|
|
25
25
|
const post = matches[i + 1];
|
|
26
26
|
if (i > 1 && !pre.length)
|
|
27
|
-
throw new ValueError("
|
|
27
|
+
throw new ValueError("Template placeholders must be separated by at least one character", { received: template, caller });
|
|
28
28
|
const name = placeholder === "*" ? String(asterisks++) : R_NAME.exec(placeholder)?.[0] || "";
|
|
29
29
|
chunks.push({ pre, placeholder, name, post });
|
|
30
30
|
}
|
|
@@ -53,11 +53,12 @@ function _getPlaceholder({ name }) {
|
|
|
53
53
|
* @param templates Either a single template string, or an iterator that returns multiple template template strings.
|
|
54
54
|
* - Template strings can include placeholders (e.g. `:name-${country}/{city}`).
|
|
55
55
|
* @param target The string containing values, e.g. `Dave-UK/Manchester`
|
|
56
|
+
*
|
|
56
57
|
* @return An object containing values, e.g. `{ name: "Dave", country: "UK", city: "Manchester" }`, or undefined if target didn't match the template.
|
|
57
58
|
*/
|
|
58
|
-
export function matchTemplate(template, target) {
|
|
59
|
+
export function matchTemplate(template, target, caller = matchTemplate) {
|
|
59
60
|
// Get separators and placeholders from template.
|
|
60
|
-
const chunks = _splitTemplateCached(template,
|
|
61
|
+
const chunks = _splitTemplateCached(template, caller);
|
|
61
62
|
const firstChunk = chunks[0];
|
|
62
63
|
// Return early if empty.
|
|
63
64
|
if (!firstChunk)
|
|
@@ -101,16 +102,16 @@ export function matchTemplates(templates, target) {
|
|
|
101
102
|
*
|
|
102
103
|
* @throws {RequiredError} If a placeholder in the template string is not specified in values.
|
|
103
104
|
*/
|
|
104
|
-
export function renderTemplate(template, values) {
|
|
105
|
-
const chunks = _splitTemplateCached(template,
|
|
105
|
+
export function renderTemplate(template, values, caller = renderTemplate) {
|
|
106
|
+
const chunks = _splitTemplateCached(template, caller);
|
|
106
107
|
if (!chunks.length)
|
|
107
108
|
return template;
|
|
108
109
|
let output = template;
|
|
109
110
|
for (const { name, placeholder } of chunks)
|
|
110
|
-
output = output.replace(placeholder, _replaceTemplateKey(name, values));
|
|
111
|
+
output = output.replace(placeholder, _replaceTemplateKey(name, values, caller));
|
|
111
112
|
return output;
|
|
112
113
|
}
|
|
113
|
-
function _replaceTemplateKey(key, values) {
|
|
114
|
+
function _replaceTemplateKey(key, values, caller) {
|
|
114
115
|
if (typeof values === "string")
|
|
115
116
|
return values;
|
|
116
117
|
if (typeof values === "function")
|
|
@@ -120,5 +121,5 @@ function _replaceTemplateKey(key, values) {
|
|
|
120
121
|
if (typeof v === "string")
|
|
121
122
|
return v;
|
|
122
123
|
}
|
|
123
|
-
throw new RequiredError(`Template key "${key}" must be defined`, { received: values, key, caller
|
|
124
|
+
throw new RequiredError(`Template key "${key}" must be defined`, { received: values, key, caller });
|
|
124
125
|
}
|
package/api/Resource.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { ValueError } from "../error/ValueError.js";
|
|
2
|
-
import { Feedback } from "../feedback/Feedback.js";
|
|
3
|
-
import { UNDEFINED } from "../util/validate.js";
|
|
4
|
-
/**
|
|
5
|
-
* An abstract API resource definition, used to specify types for e.g. serverless functions.
|
|
6
|
-
*
|
|
7
|
-
* @param method The method of the resource, e.g. `GET`
|
|
8
|
-
* @param path The path of the resource optionally including `{placeholder}` values, e.g. `/patient/{id}`
|
|
9
|
-
* @param payload A `Validator` for the payload of the resource.
|
|
10
|
-
* @param result A `Validator` for the result of the resource.
|
|
11
|
-
*/
|
|
12
|
-
export class Resource {
|
|
13
|
-
/** Resource method. */
|
|
14
|
-
method;
|
|
15
|
-
/** Resource path, e.g. `/patient/{id}` */
|
|
16
|
-
path;
|
|
17
|
-
/** Payload validator. */
|
|
18
|
-
payload;
|
|
19
|
-
/** Result validator. */
|
|
20
|
-
result;
|
|
21
|
-
constructor(method, path, payload, result) {
|
|
22
|
-
this.method = method;
|
|
23
|
-
this.path = path;
|
|
24
|
-
this.payload = payload;
|
|
25
|
-
this.result = result;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Validate a payload for this resource.
|
|
29
|
-
*
|
|
30
|
-
* @returns The validated payload for this resource.
|
|
31
|
-
* @throws `Feedback` if the payload is invalid. `Feedback` instances can be reported safely back to the end client so they know how to fix their request.
|
|
32
|
-
*/
|
|
33
|
-
prepare(unsafePayload) {
|
|
34
|
-
return this.payload.validate(unsafePayload);
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Validate a result for this resource.
|
|
38
|
-
*
|
|
39
|
-
* @returns The validated result for this resource.
|
|
40
|
-
* @throws ValueError if the result could not be validated.
|
|
41
|
-
*/
|
|
42
|
-
validate(unsafeResult, caller = this.validate) {
|
|
43
|
-
try {
|
|
44
|
-
return this.result.validate(unsafeResult);
|
|
45
|
-
}
|
|
46
|
-
catch (thrown) {
|
|
47
|
-
if (thrown instanceof Feedback)
|
|
48
|
-
throw new ValueError(`Invalid result for resource "${this.path}"`, { received: unsafeResult, caller });
|
|
49
|
-
throw thrown;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
export function GET(path, payload = UNDEFINED, result = UNDEFINED) {
|
|
54
|
-
return new Resource("GET", path, payload, result);
|
|
55
|
-
}
|
|
56
|
-
export function POST(path, payload = UNDEFINED, result = UNDEFINED) {
|
|
57
|
-
return new Resource("POST", path, payload, result);
|
|
58
|
-
}
|
|
59
|
-
export function PUT(path, payload = UNDEFINED, result = UNDEFINED) {
|
|
60
|
-
return new Resource("PUT", path, payload, result);
|
|
61
|
-
}
|
|
62
|
-
export function PATCH(path, payload = UNDEFINED, result = UNDEFINED) {
|
|
63
|
-
return new Resource("PATCH", path, payload, result);
|
|
64
|
-
}
|
|
65
|
-
export function DELETE(path, payload = UNDEFINED, result = UNDEFINED) {
|
|
66
|
-
return new Resource("DELETE", path, payload, result);
|
|
67
|
-
}
|