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 CHANGED
@@ -1,26 +1,33 @@
1
1
  import { type Schema } from "../schema/Schema.js";
2
- import type { AbsolutePath } from "../util/path.js";
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
- /** Types for an HTTP request or response that does something. */
5
- export type EndpointMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
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 resource, e.g. `GET`
10
- * @param path The path of the resource optionally including `{placeholder}` values, e.g. `/patient/{id}`
11
- * @param payload A `Schema` for the payload of the resource.
12
- * @param result A `Schema` for the result of the resource.
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 path, e.g. `/patient/{id}` */
18
- readonly path: AbsolutePath;
19
- /** Payload validator. */
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 validator. */
28
+ /** Result schema. */
22
29
  readonly result: Schema<R>;
23
- constructor(method: EndpointMethod, path: AbsolutePath, payload: Schema<P>, result: Schema<R>);
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
- /** Convert to string, e.g. `GET /user/{id}` */
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 path, with validated payload and return types.
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>(path: AbsolutePath, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
50
- export declare function GET<P>(path: AbsolutePath, payload: Schema<P>): Endpoint<P, undefined>;
51
- export declare function GET<R>(path: AbsolutePath, payload: undefined, result: Schema<R>): Endpoint<undefined, 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 path, with validated payload and return types.
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>(path: AbsolutePath, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
57
- export declare function POST<P>(path: AbsolutePath, payload: Schema<P>): Endpoint<P, undefined>;
58
- export declare function POST<R>(path: AbsolutePath, payload: undefined, result: Schema<R>): Endpoint<undefined, 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 path, with validated payload and return types.
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>(path: AbsolutePath, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
64
- export declare function PUT<P>(path: AbsolutePath, payload: Schema<P>): Endpoint<P, undefined>;
65
- export declare function PUT<R>(path: AbsolutePath, payload: undefined, result: Schema<R>): Endpoint<undefined, 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 path, with validated payload and return types.
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>(path: AbsolutePath, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
71
- export declare function PATCH<P>(path: AbsolutePath, payload: Schema<P>): Endpoint<P, undefined>;
72
- export declare function PATCH<R>(path: AbsolutePath, payload: undefined, result: Schema<R>): Endpoint<undefined, 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 path, with validated payload and return types.
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>(path: AbsolutePath, payload?: Schema<P>, result?: Schema<R>): Endpoint<P, R>;
78
- export declare function DELETE<P>(path: AbsolutePath, payload: Schema<P>): Endpoint<P, undefined>;
79
- export declare function DELETE<R>(path: AbsolutePath, payload: undefined, result: Schema<R>): Endpoint<undefined, 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 { getResponse } from "../util/http.js";
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 resource, e.g. `GET`
8
- * @param path The path of the resource optionally including `{placeholder}` values, e.g. `/patient/{id}`
9
- * @param payload A `Schema` for the payload of the resource.
10
- * @param result A `Schema` for the result of the resource.
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 path, e.g. `/patient/{id}` */
16
- path;
17
- /** Payload validator. */
20
+ /** Endpoint URL, possibly including placeholders e.g. `https://api.mysite.com/users/{id}` */
21
+ url;
22
+ /** Payload schema. */
18
23
  payload;
19
- /** Result validator. */
24
+ /** Result schema. */
20
25
  result;
21
- constructor(method, path, payload, result) {
26
+ constructor(method, url, payload, result) {
22
27
  this.method = method;
23
- this.path = path;
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
- /** Convert to string, e.g. `GET /user/{id}` */
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.path}`;
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
- export function GET(path, payload = UNDEFINED, result = UNDEFINED) {
58
- return new Endpoint("GET", path, payload, result);
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
- export function POST(path, payload = UNDEFINED, result = UNDEFINED) {
61
- return new Endpoint("POST", path, payload, result);
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
- export function PUT(path, payload = UNDEFINED, result = UNDEFINED) {
64
- return new Endpoint("PUT", path, payload, result);
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
- export function PATCH(path, payload = UNDEFINED, result = UNDEFINED) {
67
- return new Endpoint("PATCH", path, payload, result);
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
- export function DELETE(path, payload = UNDEFINED, result = UNDEFINED) {
70
- return new Endpoint("DELETE", path, payload, result);
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.path, pathname, handleEndpoints);
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
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.165.0",
14
+ "version": "1.167.0",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -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>(dict: ImmutableDictionary<T>): readonly DictionaryItem<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. */
@@ -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
- /** Turn a dictionary object into a set of props. */
18
- export function getDictionaryItems(dict) {
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) {
@@ -1,6 +1,6 @@
1
1
  import type { ImmutableArray } from "./array.js";
2
2
  import { type ImmutableDictionary } from "./dictionary.js";
3
- import type { AnyCaller } from "./function.js";
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<PossibleString> | ImmutableDictionary<PossibleString> | ((placeholder: string) => string);
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 (typeof values === "function")
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 key "${key}" must be defined`, { received: values, key, caller });
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 | Data | Iterable<DictionaryItem<unknown>>;
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 { isData } from "./data.js";
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
- return isURL(possible) ? possible : parseURL(possible, base) || undefined;
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 getProps(input)) {
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 (isData(input))
74
+ if (isDictionary(input))
79
75
  return getString(input[key]);
80
76
  return getURLParams(input)[key];
81
77
  }