shelving 1.165.0 → 1.167.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 +69 -33
- package/api/Endpoint.js +147 -23
- package/api/util.js +2 -2
- package/package.json +1 -1
- package/util/dictionary.d.ts +2 -1
- package/util/dictionary.js +3 -3
- package/util/template.d.ts +2 -2
- package/util/template.js +3 -2
- package/util/url.d.ts +1 -2
- package/util/url.js +13 -17
package/api/Endpoint.d.ts
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import { type Schema } from "../schema/Schema.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AnyCaller } from "../util/function.js";
|
|
3
|
+
import type { AbsoluteLink } from "../util/link.js";
|
|
3
4
|
import type { EndpointCallback, EndpointHandler } from "./util.js";
|
|
4
|
-
/**
|
|
5
|
-
export type EndpointMethod =
|
|
5
|
+
/** HTTP request methods. */
|
|
6
|
+
export type EndpointMethod = EndpointBodyMethod | EndpointHeadMethod;
|
|
7
|
+
/** HTTP request methods that have no body. */
|
|
8
|
+
export type EndpointHeadMethod = "HEAD" | "GET";
|
|
9
|
+
/** HTTP request methods that have a body. */
|
|
10
|
+
export type EndpointBodyMethod = "POST" | "PUT" | "PATCH" | "DELETE";
|
|
11
|
+
/** Configurable options for endpoint. */
|
|
12
|
+
export type EndpointOptions = Omit<RequestInit, "method" | "body">;
|
|
6
13
|
/**
|
|
7
14
|
* An abstract API resource definition, used to specify types for e.g. serverless functions.
|
|
8
15
|
*
|
|
9
|
-
* @param method The method of the
|
|
10
|
-
* @param
|
|
11
|
-
* @param payload A `Schema` for the payload of the
|
|
12
|
-
* @param result A `Schema` for the result of the
|
|
16
|
+
* @param method The method of the endpoint, e.g. `GET`
|
|
17
|
+
* @param url Endpoint URL, possibly including placeholders e.g. `https://api.mysite.com/users/{id}`
|
|
18
|
+
* @param payload A `Schema` for the payload of the endpoint.
|
|
19
|
+
* @param result A `Schema` for the result of the endpoint.
|
|
13
20
|
*/
|
|
14
21
|
export declare class Endpoint<P, R> {
|
|
15
22
|
/** Endpoint method. */
|
|
16
23
|
readonly method: EndpointMethod;
|
|
17
|
-
/** Endpoint
|
|
18
|
-
readonly
|
|
19
|
-
/** Payload
|
|
24
|
+
/** Endpoint URL, possibly including placeholders e.g. `https://api.mysite.com/users/{id}` */
|
|
25
|
+
readonly url: AbsoluteLink;
|
|
26
|
+
/** Payload schema. */
|
|
20
27
|
readonly payload: Schema<P>;
|
|
21
|
-
/** Result
|
|
28
|
+
/** Result schema. */
|
|
22
29
|
readonly result: Schema<R>;
|
|
23
|
-
constructor(method: EndpointMethod,
|
|
30
|
+
constructor(method: EndpointMethod, url: AbsoluteLink, payload: Schema<P>, result: Schema<R>);
|
|
24
31
|
/**
|
|
25
32
|
* Return an `EndpointHandler` for this endpoint.
|
|
26
33
|
*
|
|
@@ -35,7 +42,36 @@ export declare class Endpoint<P, R> {
|
|
|
35
42
|
* @param request The entire HTTP request that is being handled (payload was possibly extracted from this somehow).
|
|
36
43
|
*/
|
|
37
44
|
handle(callback: EndpointCallback<P, R>, unsafePayload: unknown, request: Request): Promise<Response>;
|
|
38
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Render the URL for this endpoint with the given payload.
|
|
47
|
+
* - URL mioght contain `{placeholder}` values that are replaced with values from the payload.
|
|
48
|
+
*/
|
|
49
|
+
renderURL(payload: P, caller?: AnyCaller): string;
|
|
50
|
+
/**
|
|
51
|
+
* Get an HTTP `Request` object for this endpoint.
|
|
52
|
+
* - Validates a payload against this endpoints payload schema
|
|
53
|
+
* - Return an HTTP `Request` that will send it the valid payload to this endpoint.
|
|
54
|
+
*
|
|
55
|
+
* @throws Feedback if the payload is invalid.
|
|
56
|
+
*/
|
|
57
|
+
request(payload: P, options?: EndpointOptions, caller?: AnyCaller): Request;
|
|
58
|
+
/**
|
|
59
|
+
* Validate an HTTP `Response` against this endpoint.
|
|
60
|
+
* @throws ResponseError if the response status is not ok (200-299)
|
|
61
|
+
* @throws ResponseError if the response content is invalid.
|
|
62
|
+
*/
|
|
63
|
+
response(response: Response, caller?: AnyCaller): Promise<R>;
|
|
64
|
+
/**
|
|
65
|
+
* Perform a fetch to this endpoint.
|
|
66
|
+
* - Validate the `payload` against this endpoint's payload schema.
|
|
67
|
+
* - Validate the returned response against this endpoint's result schema.
|
|
68
|
+
*
|
|
69
|
+
* @throws Feedback if the payload is invalid.
|
|
70
|
+
* @throws ResponseError if the response status is not ok (200-299)
|
|
71
|
+
* @throws ResponseError if the response content is invalid.
|
|
72
|
+
*/
|
|
73
|
+
fetch(payload: P, options?: EndpointOptions, caller?: AnyCaller): Promise<R>;
|
|
74
|
+
/** Convert to string, e.g. `GET https://a.com/user/{id}` */
|
|
39
75
|
toString(): string;
|
|
40
76
|
}
|
|
41
77
|
/** Extract the payload type from a `Endpoint`. */
|
|
@@ -43,37 +79,37 @@ export type PayloadType<X extends Endpoint<unknown, unknown>> = X extends Endpoi
|
|
|
43
79
|
/** Extract the result type from a `Endpoint`. */
|
|
44
80
|
export type EndpointType<X extends Endpoint<unknown, unknown>> = X extends Endpoint<unknown, infer Y> ? Y : never;
|
|
45
81
|
/**
|
|
46
|
-
* Represent a GET request to a specified
|
|
82
|
+
* Represent a GET request to a specified URL, with validated payload and return types.
|
|
47
83
|
* "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
84
|
*/
|
|
49
|
-
export declare function GET<P, R>(
|
|
50
|
-
export declare function GET<P>(
|
|
51
|
-
export declare function GET<R>(
|
|
85
|
+
export declare function GET<P, R>(url: AbsoluteLink, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
|
|
86
|
+
export declare function GET<P>(url: AbsoluteLink, payload: Schema<P>): Endpoint<P, undefined>;
|
|
87
|
+
export declare function GET<R>(url: AbsoluteLink, payload: undefined, result: Schema<R>): Endpoint<undefined, R>;
|
|
52
88
|
/**
|
|
53
|
-
* Represent a POST request to a specified
|
|
89
|
+
* Represent a POST request to a specified URL, with validated payload and return types.
|
|
54
90
|
* "The POST method submits an entity to the specified resource, often causing a change in state or side effects on the server.
|
|
55
91
|
*/
|
|
56
|
-
export declare function POST<P, R>(
|
|
57
|
-
export declare function POST<P>(
|
|
58
|
-
export declare function POST<R>(
|
|
92
|
+
export declare function POST<P, R>(url: AbsoluteLink, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
|
|
93
|
+
export declare function POST<P>(url: AbsoluteLink, payload: Schema<P>): Endpoint<P, undefined>;
|
|
94
|
+
export declare function POST<R>(url: AbsoluteLink, payload: undefined, result: Schema<R>): Endpoint<undefined, R>;
|
|
59
95
|
/**
|
|
60
|
-
* Represent a PUT request to a specified
|
|
96
|
+
* Represent a PUT request to a specified URL, with validated payload and return types.
|
|
61
97
|
* "The PUT method replaces all current representations of the target resource with the request content."
|
|
62
98
|
*/
|
|
63
|
-
export declare function PUT<P, R>(
|
|
64
|
-
export declare function PUT<P>(
|
|
65
|
-
export declare function PUT<R>(
|
|
99
|
+
export declare function PUT<P, R>(url: AbsoluteLink, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
|
|
100
|
+
export declare function PUT<P>(url: AbsoluteLink, payload: Schema<P>): Endpoint<P, undefined>;
|
|
101
|
+
export declare function PUT<R>(url: AbsoluteLink, payload: undefined, result: Schema<R>): Endpoint<undefined, R>;
|
|
66
102
|
/**
|
|
67
|
-
* Represent a PATCH request to a specified
|
|
103
|
+
* Represent a PATCH request to a specified URL, with validated payload and return types.
|
|
68
104
|
* "The PATCH method applies partial modifications to a resource."
|
|
69
105
|
*/
|
|
70
|
-
export declare function PATCH<P, R>(
|
|
71
|
-
export declare function PATCH<P>(
|
|
72
|
-
export declare function PATCH<R>(
|
|
106
|
+
export declare function PATCH<P, R>(url: AbsoluteLink, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
|
|
107
|
+
export declare function PATCH<P>(url: AbsoluteLink, payload: Schema<P>): Endpoint<P, undefined>;
|
|
108
|
+
export declare function PATCH<R>(url: AbsoluteLink, payload: undefined, result: Schema<R>): Endpoint<undefined, R>;
|
|
73
109
|
/**
|
|
74
|
-
* Represent a DELETE request to a specified
|
|
110
|
+
* Represent a DELETE request to a specified URL, with validated payload and return types.
|
|
75
111
|
* "The DELETE method deletes the specified resource."
|
|
76
112
|
*/
|
|
77
|
-
export declare function DELETE<P, R>(
|
|
78
|
-
export declare function DELETE<P>(
|
|
79
|
-
export declare function DELETE<R>(
|
|
113
|
+
export declare function DELETE<P, R>(url: AbsoluteLink, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
|
|
114
|
+
export declare function DELETE<P>(url: AbsoluteLink, payload: Schema<P>): Endpoint<P, undefined>;
|
|
115
|
+
export declare function DELETE<R>(url: AbsoluteLink, payload: undefined, result: Schema<R>): Endpoint<undefined, R>;
|
package/api/Endpoint.js
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
+
import { ResponseError } from "../error/ResponseError.js";
|
|
1
2
|
import { UNDEFINED } from "../schema/Schema.js";
|
|
2
|
-
import {
|
|
3
|
+
import { assertDictionary } from "../util/dictionary.js";
|
|
4
|
+
import { getMessage } from "../util/error.js";
|
|
5
|
+
import { getResponse, getResponseContent } from "../util/http.js";
|
|
6
|
+
import { getPlaceholders, renderTemplate } from "../util/template.js";
|
|
7
|
+
import { omitURLParams, withURLParams } from "../util/url.js";
|
|
3
8
|
import { getValid } from "../util/validate.js";
|
|
4
9
|
/**
|
|
5
10
|
* An abstract API resource definition, used to specify types for e.g. serverless functions.
|
|
6
11
|
*
|
|
7
|
-
* @param method The method of the
|
|
8
|
-
* @param
|
|
9
|
-
* @param payload A `Schema` for the payload of the
|
|
10
|
-
* @param result A `Schema` for the result of the
|
|
12
|
+
* @param method The method of the endpoint, e.g. `GET`
|
|
13
|
+
* @param url Endpoint URL, possibly including placeholders e.g. `https://api.mysite.com/users/{id}`
|
|
14
|
+
* @param payload A `Schema` for the payload of the endpoint.
|
|
15
|
+
* @param result A `Schema` for the result of the endpoint.
|
|
11
16
|
*/
|
|
12
17
|
export class Endpoint {
|
|
13
18
|
/** Endpoint method. */
|
|
14
19
|
method;
|
|
15
|
-
/** Endpoint
|
|
16
|
-
|
|
17
|
-
/** Payload
|
|
20
|
+
/** Endpoint URL, possibly including placeholders e.g. `https://api.mysite.com/users/{id}` */
|
|
21
|
+
url;
|
|
22
|
+
/** Payload schema. */
|
|
18
23
|
payload;
|
|
19
|
-
/** Result
|
|
24
|
+
/** Result schema. */
|
|
20
25
|
result;
|
|
21
|
-
constructor(method,
|
|
26
|
+
constructor(method, url, payload, result) {
|
|
22
27
|
this.method = method;
|
|
23
|
-
this.
|
|
28
|
+
this.url = url;
|
|
24
29
|
this.payload = payload;
|
|
25
30
|
this.result = result;
|
|
26
31
|
}
|
|
@@ -49,23 +54,142 @@ export class Endpoint {
|
|
|
49
54
|
// Convert the result to a `Response` object.
|
|
50
55
|
return getResponse(result);
|
|
51
56
|
}
|
|
52
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Render the URL for this endpoint with the given payload.
|
|
59
|
+
* - URL mioght contain `{placeholder}` values that are replaced with values from the payload.
|
|
60
|
+
*/
|
|
61
|
+
renderURL(payload, caller = this.renderURL) {
|
|
62
|
+
const { url } = this;
|
|
63
|
+
// URL has `{placeholders}` to render.
|
|
64
|
+
const placeholders = getPlaceholders(url);
|
|
65
|
+
if (placeholders.length) {
|
|
66
|
+
assertDictionary(payload, caller);
|
|
67
|
+
return renderTemplate(url, payload, caller);
|
|
68
|
+
}
|
|
69
|
+
// URL has no `{placeholders}`
|
|
70
|
+
return url;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get an HTTP `Request` object for this endpoint.
|
|
74
|
+
* - Validates a payload against this endpoints payload schema
|
|
75
|
+
* - Return an HTTP `Request` that will send it the valid payload to this endpoint.
|
|
76
|
+
*
|
|
77
|
+
* @throws Feedback if the payload is invalid.
|
|
78
|
+
*/
|
|
79
|
+
request(payload, options = {}, caller = this.request) {
|
|
80
|
+
return createRequest(this.method, this.url, this.payload.validate(payload), options, caller);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate an HTTP `Response` against this endpoint.
|
|
84
|
+
* @throws ResponseError if the response status is not ok (200-299)
|
|
85
|
+
* @throws ResponseError if the response content is invalid.
|
|
86
|
+
*/
|
|
87
|
+
async response(response, caller = this.response) {
|
|
88
|
+
// Get the response.
|
|
89
|
+
const { ok, status } = response;
|
|
90
|
+
const content = await getResponseContent(response, caller);
|
|
91
|
+
// Throw `ResponseError` if the API returns status outside the 200-299 range.
|
|
92
|
+
if (!ok)
|
|
93
|
+
throw new ResponseError(getMessage(content) ?? `Error ${status}`);
|
|
94
|
+
// Validate the success response.
|
|
95
|
+
return getValid(content, this.result, ResponseError, caller);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Perform a fetch to this endpoint.
|
|
99
|
+
* - Validate the `payload` against this endpoint's payload schema.
|
|
100
|
+
* - Validate the returned response against this endpoint's result schema.
|
|
101
|
+
*
|
|
102
|
+
* @throws Feedback if the payload is invalid.
|
|
103
|
+
* @throws ResponseError if the response status is not ok (200-299)
|
|
104
|
+
* @throws ResponseError if the response content is invalid.
|
|
105
|
+
*/
|
|
106
|
+
async fetch(payload, options = {}, caller = this.fetch) {
|
|
107
|
+
const response = await fetch(this.request(payload, options, caller));
|
|
108
|
+
return this.response(response, caller);
|
|
109
|
+
}
|
|
110
|
+
/** Convert to string, e.g. `GET https://a.com/user/{id}` */
|
|
53
111
|
toString() {
|
|
54
|
-
return `${this.method} ${this.
|
|
112
|
+
return `${this.method} ${this.url}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function GET(url, payload = UNDEFINED, result = UNDEFINED) {
|
|
116
|
+
return new Endpoint("GET", url, payload, result);
|
|
117
|
+
}
|
|
118
|
+
export function POST(url, payload = UNDEFINED, result = UNDEFINED) {
|
|
119
|
+
return new Endpoint("POST", url, payload, result);
|
|
120
|
+
}
|
|
121
|
+
export function PUT(url, payload = UNDEFINED, result = UNDEFINED) {
|
|
122
|
+
return new Endpoint("PUT", url, payload, result);
|
|
123
|
+
}
|
|
124
|
+
export function PATCH(url, payload = UNDEFINED, result = UNDEFINED) {
|
|
125
|
+
return new Endpoint("PATCH", url, payload, result);
|
|
126
|
+
}
|
|
127
|
+
export function DELETE(url, payload = UNDEFINED, result = UNDEFINED) {
|
|
128
|
+
return new Endpoint("DELETE", url, payload, result);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create a `Request` instance for a method/url and payload.
|
|
132
|
+
*
|
|
133
|
+
* - If `{placeholders}` are set in the URL, they are replaced by values from payload (will throw if `payload` is not a dictionary object).
|
|
134
|
+
* - If the method is `HEAD` or `GET`, the payload is sent as `?query` parameters in the URL.
|
|
135
|
+
* - If the method is anything else, the payload is sent in the body (either as JSON, string, or `FormData`).
|
|
136
|
+
*
|
|
137
|
+
* @throws ValueError if this is a `HEAD` or `GET` request but `body` is not a dictionary object.
|
|
138
|
+
* @throws ValueError if `{placeholders}` are set in the URL but `body` is not a dictionary object.
|
|
139
|
+
*/
|
|
140
|
+
function createRequest(method, url, payload, options = {}, caller = createRequest) {
|
|
141
|
+
// This is a head request, so ensure the payload is a dictionary object.
|
|
142
|
+
if (method === "GET" || method === "HEAD") {
|
|
143
|
+
assertDictionary(payload, caller);
|
|
144
|
+
return createHeadRequest(method, url, payload, options, caller);
|
|
55
145
|
}
|
|
146
|
+
// This is a normal body request.
|
|
147
|
+
return createBodyRequest(method, url, payload, options, caller);
|
|
56
148
|
}
|
|
57
|
-
|
|
58
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Create a body-less request to a URL.
|
|
151
|
+
* - Any `{placeholders}` in the URL will be rendered with values from `params`, and won't be set in `?query` parameters in the URL.
|
|
152
|
+
*/
|
|
153
|
+
function createHeadRequest(method, url, params, options = {}, caller = createHeadRequest) {
|
|
154
|
+
const placeholders = getPlaceholders(url);
|
|
155
|
+
// URL has `{placeholders}` to render, so rendere those to the URL and add all other params as `?query` params.
|
|
156
|
+
if (placeholders.length) {
|
|
157
|
+
const rendered = omitURLParams(withURLParams(renderTemplate(url, params, caller), params, caller), ...placeholders);
|
|
158
|
+
return new Request(rendered, { ...options, method });
|
|
159
|
+
}
|
|
160
|
+
// URL has no `{placeholders}`, so add all payload params to the URL.
|
|
161
|
+
return new Request(withURLParams(url, params, caller), { ...options, method });
|
|
59
162
|
}
|
|
60
|
-
|
|
61
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Create a body request to a URL.
|
|
165
|
+
* - Any `{placeholders}` in the URL will be rendered with values from `data`, and won't be set in the request body.
|
|
166
|
+
* - The payload is sent in the body (either as JSON, string, or `FormData`).
|
|
167
|
+
*
|
|
168
|
+
* @throws ValueError if `{placeholders}` are set in the URL but `body` is not a dictionary object.
|
|
169
|
+
*/
|
|
170
|
+
function createBodyRequest(method, url, body, options = {}, caller = createBodyRequest) {
|
|
171
|
+
const placeholders = getPlaceholders(url);
|
|
172
|
+
// If `{placeholders}` are set in the URL then body must be a dictionary object and is sent as JSON.
|
|
173
|
+
if (placeholders.length) {
|
|
174
|
+
assertDictionary(body, caller);
|
|
175
|
+
return createJSONRequest(method, url, body, options);
|
|
176
|
+
}
|
|
177
|
+
// `FormData` instances pass through unaltered and will set their own `Content-Type` with complex boundary information.
|
|
178
|
+
if (body instanceof FormData)
|
|
179
|
+
return createFormDataRequest(method, url, body, options);
|
|
180
|
+
if (typeof body === "string")
|
|
181
|
+
return createTextRequest(method, url, body, options);
|
|
182
|
+
return createJSONRequest(method, url, body, options); // JSON is the default.
|
|
62
183
|
}
|
|
63
|
-
|
|
64
|
-
|
|
184
|
+
/** Create a `FormData` request to a URL. */
|
|
185
|
+
function createFormDataRequest(method, url, body, options = {}) {
|
|
186
|
+
return new Request(url, { ...options, method, body });
|
|
65
187
|
}
|
|
66
|
-
|
|
67
|
-
|
|
188
|
+
/** Create a plain text request to a URL. */
|
|
189
|
+
function createTextRequest(method, url, body, { headers, ...options } = {}) {
|
|
190
|
+
return new Request(url, { ...options, headers: { ...headers, "Content-Type": "text/plain" }, method, body });
|
|
68
191
|
}
|
|
69
|
-
|
|
70
|
-
|
|
192
|
+
/** Create a JSON request to a URL. */
|
|
193
|
+
function createJSONRequest(method, url, body, { headers, ...options } = {}) {
|
|
194
|
+
return new Request(url, { ...options, headers: { ...headers, "Content-Type": "application/json" }, method, body: JSON.stringify(body) });
|
|
71
195
|
}
|
package/api/util.js
CHANGED
|
@@ -20,7 +20,7 @@ export function handleEndpoints(request, endpoints) {
|
|
|
20
20
|
const url = getURL(requestUrl);
|
|
21
21
|
if (!url)
|
|
22
22
|
throw new RequestError("Invalid request URL", { received: requestUrl, caller: handleEndpoints });
|
|
23
|
-
const { pathname, searchParams } = url;
|
|
23
|
+
const { origin, pathname, searchParams } = url;
|
|
24
24
|
// Iterate over the handlers and return the first one that matches the request.
|
|
25
25
|
for (const { endpoint, callback } of endpoints) {
|
|
26
26
|
// Ensure the request method e.g. `GET`, does not match the endpoint method e.g. `POST`
|
|
@@ -28,7 +28,7 @@ export function handleEndpoints(request, endpoints) {
|
|
|
28
28
|
continue;
|
|
29
29
|
// Ensure the request URL e.g. `/user/123` matches the endpoint path e.g. `/user/{id}`
|
|
30
30
|
// Any `{placeholders}` in the endpoint path are matched against the request URL to extract parameters.
|
|
31
|
-
const pathParams = matchTemplate(endpoint.
|
|
31
|
+
const pathParams = matchTemplate(endpoint.url, `${origin}${pathname}`, handleEndpoints);
|
|
32
32
|
if (!pathParams)
|
|
33
33
|
continue;
|
|
34
34
|
// Make a simple dictionary object from the `{placeholder}` path params and the `?a=123` query params from the URL.
|
package/package.json
CHANGED
package/util/dictionary.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ export declare function assertDictionary(value: unknown, caller?: AnyCaller): as
|
|
|
20
20
|
/** Convert a possible dictionary into a dictionary. */
|
|
21
21
|
export declare function getDictionary<T>(dict: PossibleDictionary<T>): ImmutableDictionary<T>;
|
|
22
22
|
/** Turn a dictionary object into a set of props. */
|
|
23
|
-
export declare function getDictionaryItems<T>(
|
|
23
|
+
export declare function getDictionaryItems<T>(input: ImmutableDictionary<T>): readonly DictionaryItem<T>[];
|
|
24
|
+
export declare function getDictionaryItems<T>(input: PossibleDictionary<T>): Iterable<DictionaryItem<T>>;
|
|
24
25
|
/** Is an unknown value the key for an own prop of a dictionary. */
|
|
25
26
|
export declare function isDictionaryItem<T>(dict: ImmutableDictionary<T>, key: unknown): key is string;
|
|
26
27
|
/** Assert that an unknown value is the key for an own prop of a dictionary. */
|
package/util/dictionary.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
|
+
import { isIterable } from "./iterate.js";
|
|
3
4
|
import { deleteProps, isPlainObject, omitProps, pickProps, setProp, setProps, withProp, withProps } from "./object.js";
|
|
4
5
|
/** Is an unknown value a dictionary object? */
|
|
5
6
|
export function isDictionary(value) {
|
|
@@ -14,9 +15,8 @@ export function assertDictionary(value, caller = assertDictionary) {
|
|
|
14
15
|
export function getDictionary(dict) {
|
|
15
16
|
return isDictionary(dict) ? dict : Object.fromEntries(dict);
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return Object.entries(dict);
|
|
18
|
+
export function getDictionaryItems(input) {
|
|
19
|
+
return isIterable(input) ? input : Object.entries(input);
|
|
20
20
|
}
|
|
21
21
|
/** Is an unknown value the key for an own prop of a dictionary. */
|
|
22
22
|
export function isDictionaryItem(dict, key) {
|
package/util/template.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ImmutableArray } from "./array.js";
|
|
2
2
|
import { type ImmutableDictionary } from "./dictionary.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type AnyCaller } from "./function.js";
|
|
4
4
|
import { type NotString, type PossibleString } from "./string.js";
|
|
5
5
|
/**
|
|
6
6
|
* Things that can be converted to the value for a named placeholder.
|
|
@@ -10,7 +10,7 @@ import { type NotString, type PossibleString } from "./string.js";
|
|
|
10
10
|
* `ImmutableDictionary<PossibleString>` — Object containing named strings used for named placeholders, e.g. `{ val1: "Ellie", val2: 123 }`
|
|
11
11
|
* `(placeholder: string) => string` — Function that returns the right string for a named `{placeholder}`.v
|
|
12
12
|
*/
|
|
13
|
-
export type TemplateValues = PossibleString | ImmutableArray<
|
|
13
|
+
export type TemplateValues = PossibleString | ImmutableArray<unknown> | ImmutableDictionary<unknown> | ((placeholder: string) => string);
|
|
14
14
|
/** The output of matching a template is a dictionary in `{ myPlaceholder: "value" }` format. */
|
|
15
15
|
export type TemplateMatches = ImmutableDictionary<string>;
|
|
16
16
|
/**
|
package/util/template.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
3
|
import { EMPTY_DICTIONARY } from "./dictionary.js";
|
|
4
|
+
import { isFunction } from "./function.js";
|
|
4
5
|
import { setMapItem } from "./map.js";
|
|
5
6
|
import { isObject } from "./object.js";
|
|
6
7
|
import { getString } from "./string.js";
|
|
@@ -118,7 +119,7 @@ export function renderTemplate(template, values, caller = renderTemplate) {
|
|
|
118
119
|
return output;
|
|
119
120
|
}
|
|
120
121
|
function _replaceTemplateKey(key, values, caller) {
|
|
121
|
-
if (
|
|
122
|
+
if (isFunction(values))
|
|
122
123
|
return values(key);
|
|
123
124
|
if (isObject(values)) {
|
|
124
125
|
// Dictionary or array of values.
|
|
@@ -132,5 +133,5 @@ function _replaceTemplateKey(key, values, caller) {
|
|
|
132
133
|
if (v !== undefined)
|
|
133
134
|
return v;
|
|
134
135
|
}
|
|
135
|
-
throw new RequiredError(`Template
|
|
136
|
+
throw new RequiredError(`Template value for "${key}" must be string`, { received: values, key, caller });
|
|
136
137
|
}
|
package/util/url.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type Data } from "./data.js";
|
|
2
1
|
import type { DictionaryItem, ImmutableDictionary } from "./dictionary.js";
|
|
3
2
|
import type { AnyCaller } from "./function.js";
|
|
4
3
|
import { type Nullish } from "./null.js";
|
|
@@ -15,7 +14,7 @@ export declare function requireURL(possible: PossibleURL, base?: PossibleURL, ca
|
|
|
15
14
|
/** Type for a set of named URL parameters. */
|
|
16
15
|
export type URLParams = ImmutableDictionary<string>;
|
|
17
16
|
/** Type for things that can be converted to named URL parameters. */
|
|
18
|
-
export type PossibleURLParams = PossibleURL | URLSearchParams |
|
|
17
|
+
export type PossibleURLParams = PossibleURL | URLSearchParams | ImmutableDictionary<unknown>;
|
|
19
18
|
/**
|
|
20
19
|
* Get a set of entries for a set of possible URL params.
|
|
21
20
|
*
|
package/util/url.js
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getDictionaryItems, isDictionary } from "./dictionary.js";
|
|
4
4
|
import { notNullish } from "./null.js";
|
|
5
|
-
import { getProps } from "./object.js";
|
|
6
5
|
import { getString, isString } from "./string.js";
|
|
7
|
-
function parseURL(value, base) {
|
|
8
|
-
const ctor = URL;
|
|
9
|
-
if (typeof ctor.parse === "function")
|
|
10
|
-
return ctor.parse(value, base);
|
|
11
|
-
try {
|
|
12
|
-
return new URL(value, base);
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
6
|
/** Is an unknown value a URL object? */
|
|
19
7
|
export function isURL(value) {
|
|
20
8
|
return value instanceof URL;
|
|
@@ -26,8 +14,16 @@ export function assertURL(value, caller = assertURL) {
|
|
|
26
14
|
}
|
|
27
15
|
/** Convert a possible URL to a URL, or return `undefined` if conversion fails. */
|
|
28
16
|
export function getURL(possible, base = _BASE) {
|
|
29
|
-
if (notNullish(possible))
|
|
30
|
-
|
|
17
|
+
if (notNullish(possible)) {
|
|
18
|
+
if (isURL(possible))
|
|
19
|
+
return possible;
|
|
20
|
+
try {
|
|
21
|
+
return new URL(possible, base);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
31
27
|
}
|
|
32
28
|
const _BASE = typeof document === "object" ? document.baseURI : undefined;
|
|
33
29
|
/** Convert a possible URL to a URL, or throw `RequiredError` if conversion fails. */
|
|
@@ -53,7 +49,7 @@ export function* getURLEntries(input, caller = getURLParams) {
|
|
|
53
49
|
}
|
|
54
50
|
else {
|
|
55
51
|
const done = [];
|
|
56
|
-
for (const [key, value] of
|
|
52
|
+
for (const [key, value] of getDictionaryItems(input)) {
|
|
57
53
|
if (done.includes(key))
|
|
58
54
|
continue;
|
|
59
55
|
done.push(key);
|
|
@@ -75,7 +71,7 @@ export function getURLParams(input, caller = getURLParams) {
|
|
|
75
71
|
export function getURLParam(input, key) {
|
|
76
72
|
if (input instanceof URLSearchParams)
|
|
77
73
|
return input.get(key) || undefined;
|
|
78
|
-
if (
|
|
74
|
+
if (isDictionary(input))
|
|
79
75
|
return getString(input[key]);
|
|
80
76
|
return getURLParams(input)[key];
|
|
81
77
|
}
|