shelving 1.139.1 → 1.141.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/Endpoint.d.ts +81 -0
- package/api/Endpoint.js +64 -0
- package/api/index.d.ts +2 -1
- package/api/index.js +2 -1
- package/api/util.d.ts +32 -0
- package/api/util.js +53 -0
- package/error/ResponseError.d.ts +5 -0
- package/error/ResponseError.js +9 -0
- package/error/index.d.ts +1 -0
- package/error/index.js +1 -0
- package/package.json +1 -1
- package/util/array.js +1 -1
- package/util/color.js +1 -1
- package/util/entity.js +1 -1
- package/util/http.d.ts +28 -0
- package/util/http.js +58 -0
- package/util/index.d.ts +1 -0
- package/util/index.js +1 -0
- package/util/jwt.d.ts +39 -5
- package/util/jwt.js +65 -20
- package/util/string.js +1 -1
- package/util/template.d.ts +19 -9
- package/util/template.js +10 -9
- package/util/validate.d.ts +2 -2
- package/util/validate.js +2 -2
- package/api/Resource.d.ts +0 -37
- package/api/Resource.js +0 -48
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Path } from "../util/path.js";
|
|
2
|
+
import { type Validator } from "../util/validate.js";
|
|
3
|
+
import type { EndpointCallback, EndpointHandler } from "./util.js";
|
|
4
|
+
/** Types for an HTTP request or response that does something. */
|
|
5
|
+
export type EndpointMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
6
|
+
/**
|
|
7
|
+
* An abstract API resource definition, used to specify types for e.g. serverless functions.
|
|
8
|
+
*
|
|
9
|
+
* @param method The method of the resource, e.g. `GET`
|
|
10
|
+
* @param path The path of the resource optionally including `{placeholder}` values, e.g. `/patient/{id}`
|
|
11
|
+
* @param payload A `Validator` for the payload of the resource.
|
|
12
|
+
* @param result A `Validator` for the result of the resource.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Endpoint<P, R> implements Validator<R> {
|
|
15
|
+
/** Endpoint method. */
|
|
16
|
+
readonly method: EndpointMethod;
|
|
17
|
+
/** Endpoint path, e.g. `/patient/{id}` */
|
|
18
|
+
readonly path: Path;
|
|
19
|
+
/** Payload validator. */
|
|
20
|
+
readonly payload: Validator<P>;
|
|
21
|
+
/** Result validator. */
|
|
22
|
+
readonly result: Validator<R>;
|
|
23
|
+
constructor(method: EndpointMethod, path: Path, payload: Validator<P>, result: Validator<R>);
|
|
24
|
+
/**
|
|
25
|
+
* Validate a payload for this resource.
|
|
26
|
+
*
|
|
27
|
+
* @returns The validated payload for this resource.
|
|
28
|
+
* @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.
|
|
29
|
+
*/
|
|
30
|
+
prepare(unsafePayload: unknown): P;
|
|
31
|
+
/**
|
|
32
|
+
* Validate a result for this resource.
|
|
33
|
+
*
|
|
34
|
+
* @returns The validated result for this resource.
|
|
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.
|
|
36
|
+
*/
|
|
37
|
+
validate(unsafeResult: unknown): R;
|
|
38
|
+
/**
|
|
39
|
+
* Return an `EndpointHandler` for this endpoint
|
|
40
|
+
*/
|
|
41
|
+
handler(callback: EndpointCallback<P, R>): EndpointHandler<P, R>;
|
|
42
|
+
}
|
|
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;
|
|
47
|
+
/**
|
|
48
|
+
* Represent a GET request to a specified path, with validated payload and return types.
|
|
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."
|
|
50
|
+
*/
|
|
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>;
|
|
54
|
+
/**
|
|
55
|
+
* Represent a POST request to a specified path, with validated payload and return types.
|
|
56
|
+
* "The POST method submits an entity to the specified resource, often causing a change in state or side effects on the server.
|
|
57
|
+
*/
|
|
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>;
|
|
61
|
+
/**
|
|
62
|
+
* Represent a PUT request to a specified path, with validated payload and return types.
|
|
63
|
+
* "The PUT method replaces all current representations of the target resource with the request content."
|
|
64
|
+
*/
|
|
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>;
|
|
68
|
+
/**
|
|
69
|
+
* Represent a PATCH request to a specified path, with validated payload and return types.
|
|
70
|
+
* "The PATCH method applies partial modifications to a resource."
|
|
71
|
+
*/
|
|
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>;
|
|
75
|
+
/**
|
|
76
|
+
* Represent a DELETE request to a specified path, with validated payload and return types.
|
|
77
|
+
* "The DELETE method deletes the specified resource."
|
|
78
|
+
*/
|
|
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 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./Endpoint.js";
|
|
2
|
+
export * from "./util.js";
|
package/api/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./Endpoint.js";
|
|
2
|
+
export * from "./util.js";
|
package/api/util.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Endpoint } from "./Endpoint.js";
|
|
2
|
+
/**
|
|
3
|
+
* A function that handles a endpoint request, with a payload and returns a result.
|
|
4
|
+
*
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
export type EndpointCallback<P, R> = (payload: P, request: Request) => R | Promise<R>;
|
|
11
|
+
/**
|
|
12
|
+
* Object combining an abstract `Endpoint` and an `EndpointCallback` implementation.
|
|
13
|
+
*/
|
|
14
|
+
export interface EndpointHandler<P, R> {
|
|
15
|
+
readonly endpoint: Endpoint<P, R>;
|
|
16
|
+
readonly callback: EndpointCallback<P, R>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Any handler (purposefully as wide as possible for use with `extends X` or `is X` statements).
|
|
20
|
+
*/
|
|
21
|
+
export type AnyEndpointHandler = EndpointHandler<any, any>;
|
|
22
|
+
/**
|
|
23
|
+
* List of `EndpointHandler` objects objects that can handle requests to an `Endpoint`.
|
|
24
|
+
*/
|
|
25
|
+
export type EndpointHandlers = ReadonlyArray<AnyEndpointHandler>;
|
|
26
|
+
/**
|
|
27
|
+
* Handler a `Request` with the first matching `OptionalHandler` in a `Handlers` array.
|
|
28
|
+
*
|
|
29
|
+
* @returns The resulting `Response` from the first handler that matches the `Request`.
|
|
30
|
+
* @throws `NotFoundError` if no handler matches the `Request`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function handleEndpoints(request: Request, endpoints: EndpointHandlers): Promise<Response>;
|
package/api/util.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
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";
|
|
6
|
+
import { matchTemplate } from "../util/template.js";
|
|
7
|
+
import { getURL } from "../util/url.js";
|
|
8
|
+
import { getValid } from "../util/validate.js";
|
|
9
|
+
/**
|
|
10
|
+
* Handler a `Request` with the first matching `OptionalHandler` in a `Handlers` array.
|
|
11
|
+
*
|
|
12
|
+
* @returns The resulting `Response` from the first handler that matches the `Request`.
|
|
13
|
+
* @throws `NotFoundError` if no handler matches the `Request`.
|
|
14
|
+
*/
|
|
15
|
+
export function handleEndpoints(request, endpoints) {
|
|
16
|
+
// Parse the URL of the request.
|
|
17
|
+
const url = getURL(request.url);
|
|
18
|
+
if (!url)
|
|
19
|
+
throw new RequestError("Invalid request URL", { received: request.url, caller: handleEndpoints });
|
|
20
|
+
const { pathname, searchParams } = url;
|
|
21
|
+
// Iterate over the handlers and return the first one that matches the request.
|
|
22
|
+
for (const { endpoint, callback } of endpoints) {
|
|
23
|
+
// Ensure the request method e.g. `GET`, does not match the endpoint method e.g. `POST`
|
|
24
|
+
if (request.method !== endpoint.method)
|
|
25
|
+
continue;
|
|
26
|
+
// Ensure the request URL e.g. `/user/123` matches the endpoint path e.g. `/user/{id}`
|
|
27
|
+
// Any `{placeholders}` in the endpoint path are matched against the request URL to extract parameters.
|
|
28
|
+
const pathParams = matchTemplate(endpoint.path, pathname, handleEndpoints);
|
|
29
|
+
if (!pathParams)
|
|
30
|
+
continue;
|
|
31
|
+
// Merge the search params and path params.
|
|
32
|
+
const params = searchParams.size ? { ...getDictionary(searchParams), ...pathParams } : pathParams;
|
|
33
|
+
// Get the response by calling the callback.
|
|
34
|
+
return _getResponse(endpoint, callback, params, request);
|
|
35
|
+
}
|
|
36
|
+
throw new NotFoundError("Not found", { request, caller: handleEndpoints });
|
|
37
|
+
}
|
|
38
|
+
async function _getResponse(endpoint, callback, params, request) {
|
|
39
|
+
// Extract a data object from the request body and validate it against the endpoint's payload type.
|
|
40
|
+
const content = await getRequestContent(request, handleEndpoints);
|
|
41
|
+
// If content is undefined, it means the request has no body, so params are the only payload.
|
|
42
|
+
// If the content is a data object merge if with the params.
|
|
43
|
+
// If the content is not a data object (e.g. string, number, array), set a single `content` property and merge it with the params.
|
|
44
|
+
const unsafePayload = content === undefined ? params : isData(content) ? { ...content, ...params } : { content, ...params };
|
|
45
|
+
const payload = endpoint.prepare(unsafePayload);
|
|
46
|
+
// Call the handler with the validated payload to get the result.
|
|
47
|
+
const unsafeResult = await callback(payload, request);
|
|
48
|
+
// Validate the result against the endpoint's result type.
|
|
49
|
+
// Throw a `ValueError` if the result is not valid, which indicates an internal error in the callback implementation.
|
|
50
|
+
const result = getValid(unsafeResult, endpoint, ValueError, handleEndpoints);
|
|
51
|
+
// Return a new `Response` with a 200 status and the validated result data.
|
|
52
|
+
return Response.json(result);
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseError } from "./BaseError.js";
|
|
2
|
+
/** Error thrown when an HTTP response isn't well-formed. */
|
|
3
|
+
export class ResponseError extends BaseError {
|
|
4
|
+
constructor(message = ResponseError.prototype.message, options) {
|
|
5
|
+
super(message, { caller: ResponseError, ...options });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
ResponseError.prototype.name = "ResponseError";
|
|
9
|
+
ResponseError.prototype.message = "Invalid response";
|
package/error/index.d.ts
CHANGED
package/error/index.js
CHANGED
package/package.json
CHANGED
package/util/array.js
CHANGED
|
@@ -137,7 +137,7 @@ export function getLast(items) {
|
|
|
137
137
|
return last;
|
|
138
138
|
}
|
|
139
139
|
/** Get the last item from an array or iterable. */
|
|
140
|
-
export function requireLast(items, caller =
|
|
140
|
+
export function requireLast(items, caller = requireLast) {
|
|
141
141
|
const item = getLast(items);
|
|
142
142
|
if (item === undefined)
|
|
143
143
|
throw new RequiredError("Last item is required", { items, caller });
|
package/util/color.js
CHANGED
|
@@ -80,6 +80,6 @@ export function getColor(value) {
|
|
|
80
80
|
/** Convert a possible color to a `Color` instance, or throw `RequiredError` if it can't be converted. */
|
|
81
81
|
export function requireColor(value, caller = requireColor) {
|
|
82
82
|
const color = getColor(value);
|
|
83
|
-
assertColor(color,
|
|
83
|
+
assertColor(color, caller);
|
|
84
84
|
return color;
|
|
85
85
|
}
|
package/util/entity.js
CHANGED
|
@@ -13,5 +13,5 @@ export function requireEntity(entity, caller = requireEntity) {
|
|
|
13
13
|
const bits = entity.split(":", 2);
|
|
14
14
|
if (bits[0] && bits[1])
|
|
15
15
|
return bits;
|
|
16
|
-
throw new RequiredError("Invalid entity", { received: entity, caller
|
|
16
|
+
throw new RequiredError("Invalid entity", { received: entity, caller });
|
|
17
17
|
}
|
package/util/http.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
2
|
+
import { RequestError } from "../error/RequestError.js";
|
|
3
|
+
import { ResponseError } from "../error/ResponseError.js";
|
|
4
|
+
/** A handler function takes a `Request` and returns a `Response` (possibly asynchronously). */
|
|
5
|
+
export type Handler = (request: Request) => Response | Promise<Response>;
|
|
6
|
+
export declare function _getMessageJSON(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
7
|
+
export declare function _getMessageFormData(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
8
|
+
export declare function _getMessageContent(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Get the body content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
|
|
11
|
+
*
|
|
12
|
+
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
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.
|
|
15
|
+
*
|
|
16
|
+
* @throws RequestError if the content is not `text/plain`, or `application/json` with valid JSON.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getRequestContent(message: Request, caller?: AnyCaller): Promise<unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Get the body content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
|
|
21
|
+
*
|
|
22
|
+
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
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.
|
|
25
|
+
*
|
|
26
|
+
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getResponseContent(message: Response, caller?: AnyCaller): Promise<unknown>;
|
package/util/http.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RequestError } from "../error/RequestError.js";
|
|
2
|
+
import { ResponseError } from "../error/ResponseError.js";
|
|
3
|
+
import { getDictionary } from "./dictionary.js";
|
|
4
|
+
export async function _getMessageJSON(message, MessageError, caller) {
|
|
5
|
+
const trimmed = (await message.text()).trim();
|
|
6
|
+
if (!trimmed.length)
|
|
7
|
+
return undefined;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(trimmed);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new MessageError("Body must be valid JSON", { received: trimmed, caller });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
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
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function _getMessageContent(message, MessageError, caller) {
|
|
24
|
+
const type = message.headers.get("Content-Type");
|
|
25
|
+
if (type?.startsWith("text/plain"))
|
|
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);
|
|
31
|
+
throw new MessageError("Unexpected content type", { received: type, caller });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the body content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
|
|
35
|
+
*
|
|
36
|
+
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
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.
|
|
39
|
+
*
|
|
40
|
+
* @throws RequestError if the content is not `text/plain`, or `application/json` with valid JSON.
|
|
41
|
+
*/
|
|
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);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the body content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
|
|
49
|
+
*
|
|
50
|
+
* @returns string If content type is `text/plain` (including empty string if it's empty).
|
|
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.
|
|
53
|
+
*
|
|
54
|
+
* @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
|
|
55
|
+
*/
|
|
56
|
+
export function getResponseContent(message, caller = getResponseContent) {
|
|
57
|
+
return _getMessageContent(message, ResponseError, caller);
|
|
58
|
+
}
|
package/util/index.d.ts
CHANGED
package/util/index.js
CHANGED
package/util/jwt.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
import { type PossibleBytes } from "./bytes.js";
|
|
2
3
|
import type { Data } from "./data.js";
|
|
3
|
-
import type { AnyFunction } from "./function.js";
|
|
4
4
|
/**
|
|
5
5
|
* Encode a JWT and return the string token.
|
|
6
6
|
* - Currently only supports HMAC SHA-512 signing.
|
|
@@ -20,14 +20,48 @@ export type TokenData = {
|
|
|
20
20
|
/**
|
|
21
21
|
* Split a JSON Web Token into its header, payload, and signature, and decode and parse the JSON.
|
|
22
22
|
*/
|
|
23
|
-
export declare function splitToken(token:
|
|
24
|
-
export declare function _splitToken(caller: AnyFunction, token: unknown): TokenData;
|
|
23
|
+
export declare function splitToken(token: string, caller?: AnyCaller): TokenData;
|
|
25
24
|
/**
|
|
26
25
|
* Decode a JWT, verify it, and return the full payload data.
|
|
27
26
|
* - Currently only supports HMAC SHA-512 signing.
|
|
28
27
|
*
|
|
29
28
|
* @throws ValueError If the input parameters, e.g. `secret` or `issuer`, are invalid.
|
|
30
|
-
* @throws
|
|
29
|
+
* @throws UnauthorizedError If the token is invalid or malformed.
|
|
31
30
|
* @throws UnauthorizedError If the token signature is incorrect, token is expired or not issued yet.
|
|
32
31
|
*/
|
|
33
|
-
export declare function verifyToken(token:
|
|
32
|
+
export declare function verifyToken(token: string, secret: PossibleBytes, caller?: AnyCaller): Promise<Data>;
|
|
33
|
+
/**
|
|
34
|
+
* Set the `Authorization: Bearer {token}` on a `Request` object (by reference).
|
|
35
|
+
*
|
|
36
|
+
* @param request The `Request` object to set the token on.
|
|
37
|
+
* @returns The same `Request` object that was passed in.
|
|
38
|
+
*/
|
|
39
|
+
export declare function setRequestToken(request: Request, token: string): Request;
|
|
40
|
+
/**
|
|
41
|
+
* Extract the `Authorization: Bearer {token}` from a `Request` object, or return `undefined` if not set.
|
|
42
|
+
*
|
|
43
|
+
* @param request The `Request` object possibly containing an `Authorization: Bearer {token}` header to extract the token from.
|
|
44
|
+
* @returns The string token extracted from the `Authorization` header, or `undefined` if not set.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getRequestToken(request: Request): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Extract the `Authorization: Bearer {token}` from a `Request` object, or throw `UnauthorizedError` if not set or malformed.
|
|
49
|
+
*
|
|
50
|
+
* @param request The `Request` object containing an `Authorization: Bearer {token}` header to extract the token from.
|
|
51
|
+
* @returns The string token extracted from the `Authorization` header.
|
|
52
|
+
* @throws UnauthorizedError If the `Authorization` header is not set, or the JWT it contains is not well-formed.
|
|
53
|
+
*/
|
|
54
|
+
export declare function requireRequestToken(request: Request, caller?: AnyCaller): string;
|
|
55
|
+
/**
|
|
56
|
+
* Extract the `Authorization: Bearer {token}` from a `Request` object and verify it using a signature, or throw `UnauthorizedError` if not set, malformed, or invalid.
|
|
57
|
+
* - Same as doing `requireRequestToken(request)` and then `verifyToken(token, secret)`.
|
|
58
|
+
*
|
|
59
|
+
* @param request The `Request` object containing an `Authorization: Bearer {token}` header to extract the token from.
|
|
60
|
+
* @param secret The secret key to verify the JWT signature with.
|
|
61
|
+
*
|
|
62
|
+
* @returns The decoded payload data from the JWT.
|
|
63
|
+
* @throws UnauthorizedError If the `Authorization` header is not set, the JWT it contains is not well-formed, or the JWT signature is invalid.
|
|
64
|
+
*
|
|
65
|
+
* @example `const { sub, iss, customClaim } = await verifyRequestToken(request, secret);`
|
|
66
|
+
*/
|
|
67
|
+
export declare function verifyRequestToken(request: Request, secret: PossibleBytes, caller?: AnyCaller): Promise<Data>;
|
package/util/jwt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UnauthorizedError } from "../error/RequestError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
3
|
import { decodeBase64URLBytes, decodeBase64URLString, encodeBase64URL } from "./base64.js";
|
|
4
4
|
import { getBytes, requireBytes } from "./bytes.js";
|
|
@@ -41,23 +41,18 @@ export async function encodeToken(claims, secret) {
|
|
|
41
41
|
/**
|
|
42
42
|
* Split a JSON Web Token into its header, payload, and signature, and decode and parse the JSON.
|
|
43
43
|
*/
|
|
44
|
-
export function splitToken(token) {
|
|
45
|
-
return _splitToken(splitToken, token);
|
|
46
|
-
}
|
|
47
|
-
export function _splitToken(caller, token) {
|
|
48
|
-
if (typeof token !== "string")
|
|
49
|
-
throw new RequestError("JWT token must be string", { received: token, caller });
|
|
44
|
+
export function splitToken(token, caller = splitToken) {
|
|
50
45
|
// Split token.
|
|
51
46
|
const [header, payload, signature] = token.split(".");
|
|
52
47
|
if (!header || !payload || !signature)
|
|
53
|
-
throw new
|
|
48
|
+
throw new UnauthorizedError("JWT token must have header, payload, and signature", { received: token, caller });
|
|
54
49
|
// Decode signature.
|
|
55
50
|
let signatureBytes;
|
|
56
51
|
try {
|
|
57
52
|
signatureBytes = decodeBase64URLBytes(signature);
|
|
58
53
|
}
|
|
59
54
|
catch (cause) {
|
|
60
|
-
throw new
|
|
55
|
+
throw new UnauthorizedError("JWT token signature must be Base64URL encoded", { received: signature, cause, caller });
|
|
61
56
|
}
|
|
62
57
|
// Decode header.
|
|
63
58
|
let headerData;
|
|
@@ -65,7 +60,7 @@ export function _splitToken(caller, token) {
|
|
|
65
60
|
headerData = JSON.parse(decodeBase64URLString(header));
|
|
66
61
|
}
|
|
67
62
|
catch (cause) {
|
|
68
|
-
throw new
|
|
63
|
+
throw new UnauthorizedError("JWT token header must be Base64URL encoded JSON", { received: header, cause, caller });
|
|
69
64
|
}
|
|
70
65
|
// Decode payload.
|
|
71
66
|
let payloadData;
|
|
@@ -73,7 +68,7 @@ export function _splitToken(caller, token) {
|
|
|
73
68
|
payloadData = JSON.parse(decodeBase64URLString(payload));
|
|
74
69
|
}
|
|
75
70
|
catch (cause) {
|
|
76
|
-
throw new
|
|
71
|
+
throw new UnauthorizedError("JWT token payload must be Base64URL encoded JSON", { received: payload, cause, caller });
|
|
77
72
|
}
|
|
78
73
|
return { header, payload, signature, headerData, payloadData, signatureBytes };
|
|
79
74
|
}
|
|
@@ -82,29 +77,79 @@ export function _splitToken(caller, token) {
|
|
|
82
77
|
* - Currently only supports HMAC SHA-512 signing.
|
|
83
78
|
*
|
|
84
79
|
* @throws ValueError If the input parameters, e.g. `secret` or `issuer`, are invalid.
|
|
85
|
-
* @throws
|
|
80
|
+
* @throws UnauthorizedError If the token is invalid or malformed.
|
|
86
81
|
* @throws UnauthorizedError If the token signature is incorrect, token is expired or not issued yet.
|
|
87
82
|
*/
|
|
88
|
-
export async function verifyToken(token, secret) {
|
|
89
|
-
const { header, payload, signature, headerData, payloadData } =
|
|
83
|
+
export async function verifyToken(token, secret, caller = verifyToken) {
|
|
84
|
+
const { header, payload, signature, headerData, payloadData } = splitToken(token, caller);
|
|
90
85
|
// Validate header.
|
|
91
86
|
if (headerData.typ !== HEADER.typ)
|
|
92
|
-
throw new
|
|
87
|
+
throw new UnauthorizedError(`JWT header type must be \"${HEADER.typ}\"`, { received: headerData.typ, caller });
|
|
93
88
|
if (headerData.alg !== HEADER.alg)
|
|
94
|
-
throw new
|
|
89
|
+
throw new UnauthorizedError(`JWT header algorithm must be \"${HEADER.alg}\"`, { received: headerData.alg, caller });
|
|
95
90
|
// Validate signature.
|
|
96
91
|
const key = await _getKey(verifyToken, secret, "verify");
|
|
97
92
|
const isValid = await crypto.subtle.verify("HMAC", key, decodeBase64URLBytes(signature), requireBytes(`${header}.${payload}`));
|
|
98
93
|
if (!isValid)
|
|
99
|
-
throw new UnauthorizedError("JWT signature does not match", { received: token, caller
|
|
94
|
+
throw new UnauthorizedError("JWT signature does not match", { received: token, caller });
|
|
100
95
|
// Validate payload.
|
|
101
96
|
const { nbf, iat, exp } = payloadData;
|
|
102
97
|
const now = Math.floor(Date.now() / 1000);
|
|
103
98
|
if (typeof nbf === "number" && nbf < now - SKEW_MS)
|
|
104
|
-
throw new UnauthorizedError("JWT cannot be used yet", { received: payloadData, expected: now, caller
|
|
99
|
+
throw new UnauthorizedError("JWT cannot be used yet", { received: payloadData, expected: now, caller });
|
|
105
100
|
if (typeof iat === "number" && iat > now + SKEW_MS)
|
|
106
|
-
throw new UnauthorizedError("JWT not issued yet", { received: payloadData, expected: now, caller
|
|
101
|
+
throw new UnauthorizedError("JWT not issued yet", { received: payloadData, expected: now, caller });
|
|
107
102
|
if (typeof exp === "number" && exp < now - SKEW_MS)
|
|
108
|
-
throw new UnauthorizedError("JWT has expired", { received: payloadData, expected: now, caller
|
|
103
|
+
throw new UnauthorizedError("JWT has expired", { received: payloadData, expected: now, caller });
|
|
109
104
|
return payloadData;
|
|
110
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Set the `Authorization: Bearer {token}` on a `Request` object (by reference).
|
|
108
|
+
*
|
|
109
|
+
* @param request The `Request` object to set the token on.
|
|
110
|
+
* @returns The same `Request` object that was passed in.
|
|
111
|
+
*/
|
|
112
|
+
export function setRequestToken(request, token) {
|
|
113
|
+
request.headers.set("Authorization", `Bearer ${token}`);
|
|
114
|
+
return request;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Extract the `Authorization: Bearer {token}` from a `Request` object, or return `undefined` if not set.
|
|
118
|
+
*
|
|
119
|
+
* @param request The `Request` object possibly containing an `Authorization: Bearer {token}` header to extract the token from.
|
|
120
|
+
* @returns The string token extracted from the `Authorization` header, or `undefined` if not set.
|
|
121
|
+
*/
|
|
122
|
+
export function getRequestToken(request) {
|
|
123
|
+
const auth = request.headers.get("Authorization");
|
|
124
|
+
if (auth?.startsWith("Bearer "))
|
|
125
|
+
return auth.substring(7).trim() || undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extract the `Authorization: Bearer {token}` from a `Request` object, or throw `UnauthorizedError` if not set or malformed.
|
|
129
|
+
*
|
|
130
|
+
* @param request The `Request` object containing an `Authorization: Bearer {token}` header to extract the token from.
|
|
131
|
+
* @returns The string token extracted from the `Authorization` header.
|
|
132
|
+
* @throws UnauthorizedError If the `Authorization` header is not set, or the JWT it contains is not well-formed.
|
|
133
|
+
*/
|
|
134
|
+
export function requireRequestToken(request, caller = requireRequestToken) {
|
|
135
|
+
const token = getRequestToken(request);
|
|
136
|
+
if (!token)
|
|
137
|
+
throw new UnauthorizedError("JWT is required", { received: request.headers.get("Authorization"), caller });
|
|
138
|
+
return token;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract the `Authorization: Bearer {token}` from a `Request` object and verify it using a signature, or throw `UnauthorizedError` if not set, malformed, or invalid.
|
|
142
|
+
* - Same as doing `requireRequestToken(request)` and then `verifyToken(token, secret)`.
|
|
143
|
+
*
|
|
144
|
+
* @param request The `Request` object containing an `Authorization: Bearer {token}` header to extract the token from.
|
|
145
|
+
* @param secret The secret key to verify the JWT signature with.
|
|
146
|
+
*
|
|
147
|
+
* @returns The decoded payload data from the JWT.
|
|
148
|
+
* @throws UnauthorizedError If the `Authorization` header is not set, the JWT it contains is not well-formed, or the JWT signature is invalid.
|
|
149
|
+
*
|
|
150
|
+
* @example `const { sub, iss, customClaim } = await verifyRequestToken(request, secret);`
|
|
151
|
+
*/
|
|
152
|
+
export function verifyRequestToken(request, secret, caller = verifyRequestToken) {
|
|
153
|
+
const token = requireRequestToken(request, caller);
|
|
154
|
+
return verifyToken(token, secret, caller);
|
|
155
|
+
}
|
package/util/string.js
CHANGED
|
@@ -7,7 +7,7 @@ export function isString(value, min = 0, max = Number.POSITIVE_INFINITY) {
|
|
|
7
7
|
return typeof value === "string" && value.length >= min && value.length <= max;
|
|
8
8
|
}
|
|
9
9
|
/** Assert that a value is a string (optionally with specified min/max length). */
|
|
10
|
-
export function assertString(value, min, max, caller =
|
|
10
|
+
export function assertString(value, min, max, caller = assertString) {
|
|
11
11
|
if (!isString(value, min, max))
|
|
12
12
|
throw new RequiredError(`Must be string${min !== undefined || max !== undefined ? ` with ${min ?? 0} to ${max ?? "∞"} characters` : ""}`, {
|
|
13
13
|
received: value,
|
package/util/template.d.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
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";
|
|
4
|
-
/**
|
|
5
|
-
type
|
|
6
|
-
/**
|
|
7
|
-
type
|
|
5
|
+
/** Dictionary of named template values in `{ myPlaceholder: "value" }` format. */
|
|
6
|
+
export type TemplateDictionary = ImmutableDictionary<string>;
|
|
7
|
+
/** Callback that returns the right replacement string for a given placeholder. */
|
|
8
|
+
export type TemplateCallback = (placeholder: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Things that can be converted to the value for a named placeholder.
|
|
11
|
+
*
|
|
12
|
+
* `string` — Single string used for every `{placeholder}`
|
|
13
|
+
* `ImmutableArray<string>` — Array of strings (or functions that return strings) used for `*` numbered placeholders e.g. `["John"]`
|
|
14
|
+
* `TemplateValues` — Object containing named strings used for named placeholders, e.g. `{ myPlaceholder: "Ellie" }`
|
|
15
|
+
* `TemplateCallback` — Function that returns the right string for a named `{placeholder}`.v
|
|
16
|
+
*/
|
|
17
|
+
export type TemplateValues = string | ImmutableArray<string> | TemplateDictionary | TemplateCallback;
|
|
8
18
|
/**
|
|
9
19
|
* Get list of placeholders named in a template string.
|
|
10
20
|
*
|
|
@@ -19,13 +29,14 @@ export declare function getPlaceholders(template: string): readonly string[];
|
|
|
19
29
|
* @param templates Either a single template string, or an iterator that returns multiple template template strings.
|
|
20
30
|
* - Template strings can include placeholders (e.g. `:name-${country}/{city}`).
|
|
21
31
|
* @param target The string containing values, e.g. `Dave-UK/Manchester`
|
|
32
|
+
*
|
|
22
33
|
* @return An object containing values, e.g. `{ name: "Dave", country: "UK", city: "Manchester" }`, or undefined if target didn't match the template.
|
|
23
34
|
*/
|
|
24
|
-
export declare function matchTemplate(template: string, target: string):
|
|
35
|
+
export declare function matchTemplate(template: string, target: string, caller?: AnyCaller): TemplateDictionary | undefined;
|
|
25
36
|
/**
|
|
26
37
|
* Match multiple templates against a target string and return the first match.
|
|
27
38
|
*/
|
|
28
|
-
export declare function matchTemplates(templates: Iterable<string> & NotString, target: string):
|
|
39
|
+
export declare function matchTemplates(templates: Iterable<string> & NotString, target: string): TemplateDictionary | undefined;
|
|
29
40
|
/**
|
|
30
41
|
* Turn ":year-:month" and `{ year: "2016"... }` etc into "2016-06..." etc.
|
|
31
42
|
*
|
|
@@ -33,7 +44,6 @@ export declare function matchTemplates(templates: Iterable<string> & NotString,
|
|
|
33
44
|
* @param values An object containing values, e.g. `{ name: "Dave", country: "UK", city: "Manchester" }` (functions are called, everything else converted to string), or a function or string to use for all placeholders.
|
|
34
45
|
* @return The rendered string, e.g. `Dave-UK/Manchester`
|
|
35
46
|
*
|
|
36
|
-
* @throws {
|
|
47
|
+
* @throws {RequiredError} If a placeholder in the template string is not specified in values.
|
|
37
48
|
*/
|
|
38
|
-
export declare function renderTemplate(template: string, values:
|
|
39
|
-
export {};
|
|
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)
|
|
@@ -99,18 +100,18 @@ export function matchTemplates(templates, target) {
|
|
|
99
100
|
* @param values An object containing values, e.g. `{ name: "Dave", country: "UK", city: "Manchester" }` (functions are called, everything else converted to string), or a function or string to use for all placeholders.
|
|
100
101
|
* @return The rendered string, e.g. `Dave-UK/Manchester`
|
|
101
102
|
*
|
|
102
|
-
* @throws {
|
|
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/util/validate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseError, BaseErrorOptions } from "../error/BaseError.js";
|
|
1
|
+
import type { AnyCaller, BaseError, BaseErrorOptions } from "../error/BaseError.js";
|
|
2
2
|
import type { ImmutableArray, PossibleArray } from "./array.js";
|
|
3
3
|
import type { Constructor } from "./class.js";
|
|
4
4
|
import type { Data } from "./data.js";
|
|
@@ -29,7 +29,7 @@ export type ValidatorsType<T> = {
|
|
|
29
29
|
readonly [K in keyof T]: ValidatorType<T[K]>;
|
|
30
30
|
};
|
|
31
31
|
/** Get value that validates against a given `Validator`, or throw `ValueError` */
|
|
32
|
-
export declare function getValid<T>(value: unknown, validator: Validator<T>, ErrorConstructor?: Constructor<BaseError, [string, BaseErrorOptions]
|
|
32
|
+
export declare function getValid<T>(value: unknown, validator: Validator<T>, ErrorConstructor?: Constructor<BaseError, [string, BaseErrorOptions]>, caller?: AnyCaller): T;
|
|
33
33
|
/**
|
|
34
34
|
* Validate an iterable set of items with a validator.
|
|
35
35
|
*
|
package/util/validate.js
CHANGED
|
@@ -9,13 +9,13 @@ import { isIterable } from "./iterate.js";
|
|
|
9
9
|
import { getNull } from "./null.js";
|
|
10
10
|
import { getUndefined } from "./undefined.js";
|
|
11
11
|
/** Get value that validates against a given `Validator`, or throw `ValueError` */
|
|
12
|
-
export function getValid(value, validator, ErrorConstructor = ValueError) {
|
|
12
|
+
export function getValid(value, validator, ErrorConstructor = ValueError, caller = getValid) {
|
|
13
13
|
try {
|
|
14
14
|
return validator.validate(value);
|
|
15
15
|
}
|
|
16
16
|
catch (thrown) {
|
|
17
17
|
if (thrown instanceof Feedback)
|
|
18
|
-
throw new ErrorConstructor(thrown.message, { received: value, cause: thrown, caller
|
|
18
|
+
throw new ErrorConstructor(thrown.message, { received: value, cause: thrown, caller });
|
|
19
19
|
throw thrown;
|
|
20
20
|
}
|
|
21
21
|
}
|
package/api/Resource.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { Validator } from "../util/validate.js";
|
|
2
|
-
/**
|
|
3
|
-
* An abstract API resource definition, used to specify types for e.g. serverless functions..
|
|
4
|
-
*
|
|
5
|
-
* @param name The name of the resource, e.g. `getUser`.
|
|
6
|
-
* @param payload The `Validator` the resource's payload must conform to (defaults to `undefined` if not specified).
|
|
7
|
-
* @param returns The `Validator` the resource's returned value must conform to (defaults to `undefined` if not specified).
|
|
8
|
-
*/
|
|
9
|
-
export declare class Resource<P = undefined, R = void> implements Validator<R> {
|
|
10
|
-
/** Resource name.. */
|
|
11
|
-
readonly name: string;
|
|
12
|
-
/** Payload validator. */
|
|
13
|
-
readonly payload: Validator<P>;
|
|
14
|
-
/** Result validator. */
|
|
15
|
-
readonly result: Validator<R>;
|
|
16
|
-
constructor(name: string, payload: Validator<P>, result: Validator<R>);
|
|
17
|
-
constructor(name: string, payload: Validator<P>);
|
|
18
|
-
constructor(name: string);
|
|
19
|
-
/**
|
|
20
|
-
* Validate a payload for this resource.
|
|
21
|
-
*
|
|
22
|
-
* @returns The validated payload for this resource.
|
|
23
|
-
* @throws Feedback if the payload could not be validated.
|
|
24
|
-
*/
|
|
25
|
-
prepare(unsafePayload: unknown): P;
|
|
26
|
-
/**
|
|
27
|
-
* Validate a result for this resource.
|
|
28
|
-
*
|
|
29
|
-
* @returns The validated result for this resource.
|
|
30
|
-
* @throws ValueError if the result could not be validated.
|
|
31
|
-
*/
|
|
32
|
-
validate(unsafeResult: unknown): R;
|
|
33
|
-
}
|
|
34
|
-
/** Extract the payload type from a `Resource`. */
|
|
35
|
-
export type PayloadType<X extends Resource> = X extends Resource<infer Y, unknown> ? Y : never;
|
|
36
|
-
/** Extract the result type from a `Resource`. */
|
|
37
|
-
export type ResourceType<X extends Resource> = X extends Resource<unknown, infer Y> ? Y : never;
|
package/api/Resource.js
DELETED
|
@@ -1,48 +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 name The name of the resource, e.g. `getUser`.
|
|
8
|
-
* @param payload The `Validator` the resource's payload must conform to (defaults to `undefined` if not specified).
|
|
9
|
-
* @param returns The `Validator` the resource's returned value must conform to (defaults to `undefined` if not specified).
|
|
10
|
-
*/
|
|
11
|
-
export class Resource {
|
|
12
|
-
/** Resource name.. */
|
|
13
|
-
name;
|
|
14
|
-
/** Payload validator. */
|
|
15
|
-
payload;
|
|
16
|
-
/** Result validator. */
|
|
17
|
-
result;
|
|
18
|
-
constructor(name, payload = UNDEFINED, result = UNDEFINED) {
|
|
19
|
-
this.name = name;
|
|
20
|
-
this.payload = payload;
|
|
21
|
-
this.result = result;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Validate a payload for this resource.
|
|
25
|
-
*
|
|
26
|
-
* @returns The validated payload for this resource.
|
|
27
|
-
* @throws Feedback if the payload could not be validated.
|
|
28
|
-
*/
|
|
29
|
-
prepare(unsafePayload) {
|
|
30
|
-
return this.payload?.validate(unsafePayload);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Validate a result for this resource.
|
|
34
|
-
*
|
|
35
|
-
* @returns The validated result for this resource.
|
|
36
|
-
* @throws ValueError if the result could not be validated.
|
|
37
|
-
*/
|
|
38
|
-
validate(unsafeResult) {
|
|
39
|
-
try {
|
|
40
|
-
return this.result.validate(unsafeResult);
|
|
41
|
-
}
|
|
42
|
-
catch (thrown) {
|
|
43
|
-
if (thrown instanceof Feedback)
|
|
44
|
-
throw new ValueError(`Invalid result for resource "${this.name}"`, { received: unsafeResult, caller: this.validate });
|
|
45
|
-
throw thrown;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|