shelving 1.167.2 → 1.168.1
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}/Endpoint.d.ts +22 -29
- package/{api → endpoint}/Endpoint.js +2 -69
- package/{api → endpoint}/util.js +6 -7
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/markup/rule/link.js +6 -6
- package/markup/util/options.d.ts +6 -10
- package/package.json +5 -5
- package/schema/URISchema.d.ts +21 -0
- package/schema/URISchema.js +38 -0
- package/schema/URLSchema.d.ts +24 -0
- package/schema/{LinkSchema.js → URLSchema.js} +14 -14
- package/schema/index.d.ts +2 -1
- package/schema/index.js +2 -3
- package/store/EndpointStore.d.ts +24 -0
- package/store/EndpointStore.js +74 -0
- package/store/URLStore.d.ts +4 -3
- package/store/URLStore.js +13 -12
- package/store/index.d.ts +1 -0
- package/store/index.js +1 -0
- package/util/format.d.ts +2 -2
- package/util/format.js +6 -4
- package/util/http.d.ts +22 -0
- package/util/http.js +59 -0
- package/util/index.d.ts +1 -1
- package/util/index.js +1 -1
- package/util/uri.d.ts +94 -0
- package/util/uri.js +119 -0
- package/util/url.d.ts +47 -37
- package/util/url.js +21 -95
- package/schema/LinkSchema.d.ts +0 -27
- package/util/link.d.ts +0 -71
- package/util/link.js +0 -44
- /package/{api → endpoint}/index.d.ts +0 -0
- /package/{api → endpoint}/index.js +0 -0
- /package/{api → endpoint}/util.d.ts +0 -0
package/store/URLStore.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getGetter, getSetter } from "../util/class.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getURIParam, getURIParams, omitURIParams, requireURIParam, withURIParam, withURIParams, } from "../util/uri.js";
|
|
3
|
+
import { getURL, requireURL } from "../util/url.js";
|
|
3
4
|
import { Store } from "./Store.js";
|
|
4
5
|
/** Store a URL, e.g. `https://top.com/a/b/c` */
|
|
5
6
|
export class URLStore extends Store {
|
|
@@ -51,47 +52,47 @@ export class URLStore extends Store {
|
|
|
51
52
|
}
|
|
52
53
|
/** Get the URL params as a dictionary. */
|
|
53
54
|
get params() {
|
|
54
|
-
return
|
|
55
|
+
return getURIParams(this.value.searchParams, getGetter(this, "params"));
|
|
55
56
|
}
|
|
56
57
|
/** Return a single param in this URL, or `undefined` if it could not be found. */
|
|
57
58
|
getParam(key) {
|
|
58
|
-
return
|
|
59
|
+
return getURIParam(this.value.searchParams, key);
|
|
59
60
|
}
|
|
60
61
|
/** Require a single param in this URL, or throw `RequiredError` if it could not be found. */
|
|
61
62
|
requireParam(key) {
|
|
62
|
-
return
|
|
63
|
+
return requireURIParam(this.value.searchParams, key, getSetter(this, "requireParam"));
|
|
63
64
|
}
|
|
64
65
|
/** Update a single param in this URL. */
|
|
65
66
|
setParam(key, value) {
|
|
66
|
-
this.value =
|
|
67
|
+
this.value = withURIParam(this.value, key, value, this.setParams);
|
|
67
68
|
}
|
|
68
69
|
/** Update several params in this URL. */
|
|
69
70
|
setParams(params) {
|
|
70
|
-
this.value =
|
|
71
|
+
this.value = withURIParams(this.value, params, this.setParams);
|
|
71
72
|
}
|
|
72
73
|
/** Delete one or more params in this URL. */
|
|
73
74
|
deleteParam(key, ...keys) {
|
|
74
|
-
this.value =
|
|
75
|
+
this.value = omitURIParams(this.value, key, ...keys);
|
|
75
76
|
}
|
|
76
77
|
/** Delete one or more params in this URL. */
|
|
77
78
|
deleteParams(key, ...keys) {
|
|
78
|
-
this.value =
|
|
79
|
+
this.value = omitURIParams(this.value, key, ...keys);
|
|
79
80
|
}
|
|
80
81
|
/** Return the current URL with an additional param. */
|
|
81
82
|
withParam(key, value) {
|
|
82
|
-
return
|
|
83
|
+
return withURIParam(this.value, key, value, this.withParam);
|
|
83
84
|
}
|
|
84
85
|
/** Return the current URL with an additional param. */
|
|
85
86
|
withParams(params) {
|
|
86
|
-
return
|
|
87
|
+
return withURIParams(this.value, params, this.withParams);
|
|
87
88
|
}
|
|
88
89
|
/** Return the current URL with an additional param. */
|
|
89
90
|
omitParams(...keys) {
|
|
90
|
-
return
|
|
91
|
+
return omitURIParams(this.value, ...keys);
|
|
91
92
|
}
|
|
92
93
|
/** Return the current URL with an additional param. */
|
|
93
94
|
omitParam(key) {
|
|
94
|
-
return
|
|
95
|
+
return omitURIParams(this.value, key);
|
|
95
96
|
}
|
|
96
97
|
toString() {
|
|
97
98
|
return this.href;
|
package/store/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./ArrayStore.js";
|
|
|
2
2
|
export * from "./BooleanStore.js";
|
|
3
3
|
export * from "./DataStore.js";
|
|
4
4
|
export * from "./DictionaryStore.js";
|
|
5
|
+
export * from "./EndpointStore.js";
|
|
5
6
|
export * from "./PathStore.js";
|
|
6
7
|
export * from "./Store.js";
|
|
7
8
|
export * from "./URLStore.js";
|
package/store/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./ArrayStore.js";
|
|
|
2
2
|
export * from "./BooleanStore.js";
|
|
3
3
|
export * from "./DataStore.js";
|
|
4
4
|
export * from "./DictionaryStore.js";
|
|
5
|
+
export * from "./EndpointStore.js";
|
|
5
6
|
export * from "./PathStore.js";
|
|
6
7
|
export * from "./Store.js";
|
|
7
8
|
export * from "./URLStore.js";
|
package/util/format.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ImmutableArray } from "./array.js";
|
|
|
2
2
|
import { type PossibleDate } from "./date.js";
|
|
3
3
|
import { type Duration } from "./duration.js";
|
|
4
4
|
import { type ImmutableObject } from "./object.js";
|
|
5
|
-
import { type
|
|
5
|
+
import { type PossibleURI } from "./uri.js";
|
|
6
6
|
/** Options we use for number formatting. */
|
|
7
7
|
export type NumberOptions = Omit<Intl.NumberFormatOptions, "style" | "unit" | "unitDisplay" | "currency" | "currencyDisplay" | "currencySign">;
|
|
8
8
|
/** Format a number (based on the user's browser language settings). */
|
|
@@ -73,7 +73,7 @@ export declare function formatTime(time?: PossibleDate, options?: Intl.DateTimeF
|
|
|
73
73
|
/** Format a datetime in the browser locale (no seconds by default). */
|
|
74
74
|
export declare function formatDateTime(date: PossibleDate, options?: Intl.DateTimeFormatOptions): string;
|
|
75
75
|
/** Format a URL as a user-friendly string, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
76
|
-
export declare function
|
|
76
|
+
export declare function formatURI(possible: PossibleURI): string;
|
|
77
77
|
/**
|
|
78
78
|
* Convert any unknown value into a friendly string for user-facing use.
|
|
79
79
|
* - Strings return the string.
|
package/util/format.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getBestTimeUnit, getMilliseconds } from "./duration.js";
|
|
|
5
5
|
import { getPercent } from "./number.js";
|
|
6
6
|
import { isObject } from "./object.js";
|
|
7
7
|
import { TIME_UNITS } from "./units.js";
|
|
8
|
-
import {
|
|
8
|
+
import { isURI, requireURI } from "./uri.js";
|
|
9
9
|
/** Format a number (based on the user's browser language settings). */
|
|
10
10
|
export function formatNumber(num, options) {
|
|
11
11
|
return Intl.NumberFormat(undefined, options).format(num);
|
|
@@ -110,9 +110,9 @@ export function formatDateTime(date, options) {
|
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
/** Format a URL as a user-friendly string, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
113
|
-
export function
|
|
114
|
-
const { host, pathname } =
|
|
115
|
-
return `${host}${pathname
|
|
113
|
+
export function formatURI(possible) {
|
|
114
|
+
const { host, pathname } = requireURI(possible, formatURI);
|
|
115
|
+
return `${host}${pathname !== "/" ? pathname : ""}`;
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
118
|
* Convert any unknown value into a friendly string for user-facing use.
|
|
@@ -146,6 +146,8 @@ export function formatValue(value) {
|
|
|
146
146
|
return formatArray(value);
|
|
147
147
|
if (isObject(value))
|
|
148
148
|
return formatObject(value);
|
|
149
|
+
if (isURI(value))
|
|
150
|
+
return formatURI(value);
|
|
149
151
|
return "Unknown";
|
|
150
152
|
}
|
|
151
153
|
/**
|
package/util/http.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { RequestError } from "../error/RequestError.js";
|
|
2
2
|
import { ResponseError } from "../error/ResponseError.js";
|
|
3
|
+
import { type ImmutableDictionary } from "./dictionary.js";
|
|
3
4
|
import type { AnyCaller } from "./function.js";
|
|
5
|
+
import type { URLString } from "./url.js";
|
|
4
6
|
/** A handler function takes a `Request` and returns a `Response` (possibly asynchronously). */
|
|
5
7
|
export type RequestHandler = (request: Request) => Response | Promise<Response>;
|
|
6
8
|
export declare function _getMessageJSON(message: Request | Response, MessageError: typeof RequestError | typeof ResponseError, caller: AnyCaller): Promise<unknown>;
|
|
@@ -49,3 +51,23 @@ export declare function getResponse(value: unknown): Response;
|
|
|
49
51
|
* @param debug If `true` include the error message in the response (for debugging), or `false` to return generic error codes (for security).
|
|
50
52
|
*/
|
|
51
53
|
export declare function getErrorResponse(reason: unknown, debug?: boolean): Response;
|
|
54
|
+
/** HTTP request methods. */
|
|
55
|
+
export type RequestMethod = RequestBodyMethod | RequestHeadMethod;
|
|
56
|
+
/** HTTP request methods that have no body. */
|
|
57
|
+
export type RequestHeadMethod = "HEAD" | "GET";
|
|
58
|
+
/** HTTP request methods that have a body. */
|
|
59
|
+
export type RequestBodyMethod = "POST" | "PUT" | "PATCH" | "DELETE";
|
|
60
|
+
/** Configurable options for endpoint. */
|
|
61
|
+
export type RequestOptions = Omit<RequestInit, "method" | "body">;
|
|
62
|
+
/**
|
|
63
|
+
* Create a `Request` instance for a method/url and payload.
|
|
64
|
+
*
|
|
65
|
+
* - If `{placeholders}` are set in the URL, they are replaced by values from payload (will throw if `payload` is not a dictionary object).
|
|
66
|
+
* - If the method is `HEAD` or `GET`, the payload is sent as `?query` parameters in the URL.
|
|
67
|
+
* - If the method is anything else, the payload is sent in the body (plain text string, `FormData` object, or JSON for any other).
|
|
68
|
+
*
|
|
69
|
+
* @throws ValueError if this is a `HEAD` or `GET` request but `body` is not a dictionary object.
|
|
70
|
+
* @throws ValueError if `{placeholders}` are set in the URL but `body` is not a dictionary object.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getRequest(method: RequestHeadMethod, url: URLString, payload: ImmutableDictionary<unknown>, options?: RequestOptions, caller?: AnyCaller): Request;
|
|
73
|
+
export declare function getRequest(method: RequestMethod, url: URLString, payload: unknown, options?: RequestOptions, caller?: AnyCaller): Request;
|
package/util/http.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { RequestError } from "../error/RequestError.js";
|
|
2
2
|
import { ResponseError } from "../error/ResponseError.js";
|
|
3
3
|
import { Feedback } from "../feedback/Feedback.js";
|
|
4
|
+
import { assertDictionary } from "./dictionary.js";
|
|
4
5
|
import { isError } from "./error.js";
|
|
6
|
+
import { getPlaceholders, renderTemplate } from "./template.js";
|
|
7
|
+
import { omitURIParams, withURIParams } from "./uri.js";
|
|
5
8
|
export async function _getMessageJSON(message, MessageError, caller) {
|
|
6
9
|
const trimmed = (await message.text()).trim();
|
|
7
10
|
if (!trimmed.length)
|
|
@@ -112,3 +115,59 @@ export function getErrorResponse(reason, debug = false) {
|
|
|
112
115
|
// Otherwise return a generic error message with no details.
|
|
113
116
|
return new Response(undefined, { status });
|
|
114
117
|
}
|
|
118
|
+
export function getRequest(method, url, payload, options = {}, caller = getRequest) {
|
|
119
|
+
// This is a head request, so ensure the payload is a dictionary object.
|
|
120
|
+
if (method === "GET" || method === "HEAD") {
|
|
121
|
+
assertDictionary(payload, caller);
|
|
122
|
+
return getHeadRequest(method, url, payload, options, caller);
|
|
123
|
+
}
|
|
124
|
+
// This is a normal body request.
|
|
125
|
+
return getBodyRequest(method, url, payload, options, caller);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create a body-less request to a URL.
|
|
129
|
+
* - Any `{placeholders}` in the URL will be rendered with values from `params`, and won't be set in `?query` parameters in the URL.
|
|
130
|
+
*/
|
|
131
|
+
function getHeadRequest(method, url, params, options = {}, caller = getHeadRequest) {
|
|
132
|
+
const placeholders = getPlaceholders(url);
|
|
133
|
+
// URL has `{placeholders}` to render, so rendere those to the URL and add all other params as `?query` params.
|
|
134
|
+
if (placeholders.length) {
|
|
135
|
+
const rendered = omitURIParams(withURIParams(renderTemplate(url, params, caller), params, caller), ...placeholders);
|
|
136
|
+
return new Request(rendered, { ...options, method });
|
|
137
|
+
}
|
|
138
|
+
// URL has no `{placeholders}`, so add all payload params to the URL.
|
|
139
|
+
return new Request(withURIParams(url, params, caller), { ...options, method });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create a body request to a URL.
|
|
143
|
+
* - Any `{placeholders}` in the URL will be rendered with values from `data`, and won't be set in the request body.
|
|
144
|
+
* - The payload is sent in the body (either as JSON, string, or `FormData`).
|
|
145
|
+
*
|
|
146
|
+
* @throws ValueError if `{placeholders}` are set in the URL but `body` is not a dictionary object.
|
|
147
|
+
*/
|
|
148
|
+
function getBodyRequest(method, url, body, options = {}, caller = getBodyRequest) {
|
|
149
|
+
const placeholders = getPlaceholders(url);
|
|
150
|
+
// If `{placeholders}` are set in the URL then body must be a dictionary object and is sent as JSON.
|
|
151
|
+
if (placeholders.length) {
|
|
152
|
+
assertDictionary(body, caller);
|
|
153
|
+
return getJSONRequest(method, renderTemplate(url, body, caller), body, options);
|
|
154
|
+
}
|
|
155
|
+
// `FormData` instances pass through unaltered and will set their own `Content-Type` with complex boundary information.
|
|
156
|
+
if (body instanceof FormData)
|
|
157
|
+
return getFormDataRequest(method, url, body, options);
|
|
158
|
+
if (typeof body === "string")
|
|
159
|
+
return getTextRequest(method, url, body, options);
|
|
160
|
+
return getJSONRequest(method, url, body, options); // JSON is the default.
|
|
161
|
+
}
|
|
162
|
+
/** Create a `FormData` request to a URL. */
|
|
163
|
+
function getFormDataRequest(method, url, body, options = {}) {
|
|
164
|
+
return new Request(url, { ...options, method, body });
|
|
165
|
+
}
|
|
166
|
+
/** Create a plain text request to a URL. */
|
|
167
|
+
function getTextRequest(method, url, body, { headers, ...options } = {}) {
|
|
168
|
+
return new Request(url, { ...options, headers: { ...headers, "Content-Type": "text/plain" }, method, body });
|
|
169
|
+
}
|
|
170
|
+
/** Create a JSON request to a URL. */
|
|
171
|
+
function getJSONRequest(method, url, body, { headers, ...options } = {}) {
|
|
172
|
+
return new Request(url, { ...options, headers: { ...headers, "Content-Type": "application/json" }, method, body: JSON.stringify(body) });
|
|
173
|
+
}
|
package/util/index.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ export * from "./iterate.js";
|
|
|
34
34
|
export * from "./jsx.js";
|
|
35
35
|
export * from "./jwt.js";
|
|
36
36
|
export * from "./lazy.js";
|
|
37
|
-
export * from "./link.js";
|
|
38
37
|
export * from "./map.js";
|
|
39
38
|
export * from "./merge.js";
|
|
40
39
|
export * from "./null.js";
|
|
@@ -56,6 +55,7 @@ export * from "./transform.js";
|
|
|
56
55
|
export * from "./undefined.js";
|
|
57
56
|
export * from "./units.js";
|
|
58
57
|
export * from "./update.js";
|
|
58
|
+
export * from "./uri.js";
|
|
59
59
|
export * from "./url.js";
|
|
60
60
|
export * from "./uuid.js";
|
|
61
61
|
export * from "./validate.js";
|
package/util/index.js
CHANGED
|
@@ -34,7 +34,6 @@ export * from "./iterate.js";
|
|
|
34
34
|
export * from "./jsx.js";
|
|
35
35
|
export * from "./jwt.js";
|
|
36
36
|
export * from "./lazy.js";
|
|
37
|
-
export * from "./link.js";
|
|
38
37
|
export * from "./map.js";
|
|
39
38
|
export * from "./merge.js";
|
|
40
39
|
export * from "./null.js";
|
|
@@ -56,6 +55,7 @@ export * from "./transform.js";
|
|
|
56
55
|
export * from "./undefined.js";
|
|
57
56
|
export * from "./units.js";
|
|
58
57
|
export * from "./update.js";
|
|
58
|
+
export * from "./uri.js";
|
|
59
59
|
export * from "./url.js";
|
|
60
60
|
export * from "./uuid.js";
|
|
61
61
|
export * from "./validate.js";
|
package/util/uri.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ImmutableArray } from "./array.js";
|
|
2
|
+
import { type ImmutableDictionary } from "./dictionary.js";
|
|
3
|
+
import type { AnyCaller } from "./function.js";
|
|
4
|
+
import { type Nullish } from "./null.js";
|
|
5
|
+
import type { URL, URLString } from "./url.js";
|
|
6
|
+
/**
|
|
7
|
+
* Valid URI string is anything following `protocol:resource` format, e.g. `urn:isbn:0451450523` or `http://example.com/path/to/resource`
|
|
8
|
+
*
|
|
9
|
+
* URI and URL differences:
|
|
10
|
+
* - According to RFC 3986, URLs are a subset of URIs that have a hierarchical path component, e.g. `http://example.com/path`.
|
|
11
|
+
* - The `//` at the start of a URL indicates that it has a hierarchical path component, so this makes it a URL.
|
|
12
|
+
* - The absence of `//` indicates a non-hierarchical URI.
|
|
13
|
+
* - URLs can be considered as "hierarchical URIs".
|
|
14
|
+
* - All URLs are also URIs, but not all URIs are URLs.
|
|
15
|
+
*/
|
|
16
|
+
export type URIString = `${string}:${string}`;
|
|
17
|
+
/**
|
|
18
|
+
* Object that describes a valid URI, e.g. `urn:isbn:0451450523` or `http://example.com/path/to/resource`
|
|
19
|
+
* - Improves the builtin Javascript `URL` class to more accurately type its properties.
|
|
20
|
+
*
|
|
21
|
+
* URI and URL differences:
|
|
22
|
+
* - According to RFC 3986, URLs are a subset of URIs that have a hierarchical path component, e.g. `http://example.com/path`.
|
|
23
|
+
* - The `//` at the start of a URL indicates that it has a hierarchical path component, so this makes it a URL.
|
|
24
|
+
* - The absence of `//` indicates a non-hierarchical URI.
|
|
25
|
+
* - URLs can be considered as "hierarchical URIs".
|
|
26
|
+
* - All URLs are also URIs, but not all URIs are URLs.
|
|
27
|
+
*
|
|
28
|
+
* Javascript URL problems:
|
|
29
|
+
* - Javascript `URL` instance can actually represent any kind of URI (not just URLs).
|
|
30
|
+
* - It's more "correct" terminology to use `URI` to refer to what the Javascript `URL` class represents.
|
|
31
|
+
* - You can tell the difference because a URL will have a non-empty `host` property, whereas URIs will never have a `host` (it will be `""` empty string).
|
|
32
|
+
*/
|
|
33
|
+
export interface URI extends globalThis.URL {
|
|
34
|
+
protocol: URIScheme;
|
|
35
|
+
href: URIString;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Construct a correctly-typed `URI` object.
|
|
39
|
+
* - This is a more correctly typed version of the builtin Javascript `URI` constructor.
|
|
40
|
+
* - Requires a URI string, URI object, or path as input, and optionally a base URI.
|
|
41
|
+
* - If a path is provided as input, a base URI _must_ also be provided.
|
|
42
|
+
* - The returned type is
|
|
43
|
+
*/
|
|
44
|
+
export interface URIConstructor {
|
|
45
|
+
new (input: URIString | URI): URI;
|
|
46
|
+
}
|
|
47
|
+
export declare const URI: URIConstructor;
|
|
48
|
+
/** Values that can be converted to a URI instance. */
|
|
49
|
+
export type PossibleURI = string | globalThis.URL;
|
|
50
|
+
/** Is an unknown value a URI object? */
|
|
51
|
+
export declare function isURI(value: unknown): value is URI;
|
|
52
|
+
/** Assert that an unknown value is a URI object. */
|
|
53
|
+
export declare function assertURI(value: unknown, caller?: AnyCaller): asserts value is URI;
|
|
54
|
+
/** Convert a possible URI to a URI, or return `undefined` if conversion fails. */
|
|
55
|
+
export declare function getURI(possible: Nullish<PossibleURI>): URI | undefined;
|
|
56
|
+
/** Convert a possible URI to a URI, or throw `RequiredError` if conversion fails. */
|
|
57
|
+
export declare function requireURI(possible: PossibleURI, caller?: AnyCaller): URI;
|
|
58
|
+
/** Convert a possible URI to a URI string, or return `undefined` if conversion fails. */
|
|
59
|
+
export declare function getURIString(possible: Nullish<PossibleURI>): URIString | undefined;
|
|
60
|
+
/** Convert a possible URI to a URI string, or throw `RequiredError` if conversion fails. */
|
|
61
|
+
export declare function requireURIString(possible: PossibleURI, caller?: AnyCaller): URIString | undefined;
|
|
62
|
+
/** Type for a set of named URL parameters. */
|
|
63
|
+
export type URIParams = ImmutableDictionary<string>;
|
|
64
|
+
/** Type for things that can be converted to named URI parameters. */
|
|
65
|
+
export type PossibleURIParams = PossibleURI | URLSearchParams | ImmutableDictionary<unknown>;
|
|
66
|
+
/** Get a set of params for a URI as a dictionary. */
|
|
67
|
+
export declare function getURIParams(input: PossibleURIParams, caller?: AnyCaller): URIParams;
|
|
68
|
+
/** Get a single named param from a URI. */
|
|
69
|
+
export declare function getURIParam(input: PossibleURIParams, key: string): string | undefined;
|
|
70
|
+
/** Get a single named param from a URI. */
|
|
71
|
+
export declare function requireURIParam(input: PossibleURIParams, key: string, caller?: AnyCaller): string;
|
|
72
|
+
/**
|
|
73
|
+
* Return a URI with a new param set (or same URI if no changes were made).
|
|
74
|
+
* - Throws `ValueError` if the value could not be converted to a string.
|
|
75
|
+
*/
|
|
76
|
+
export declare function withURIParam(url: URL | URLString, key: string, value: unknown, caller?: AnyCaller): URL;
|
|
77
|
+
export declare function withURIParam(url: PossibleURI, key: string, value: unknown, caller?: AnyCaller): URI;
|
|
78
|
+
/**
|
|
79
|
+
* Return a URI with several new params set (or same URI if no changes were made).
|
|
80
|
+
* - Throws `ValueError` if any of the values could not be converted to strings.
|
|
81
|
+
*/
|
|
82
|
+
export declare function withURIParams(url: URL | URLString, params: PossibleURIParams, caller?: AnyCaller): URL;
|
|
83
|
+
export declare function withURIParams(url: PossibleURI, params: PossibleURIParams, caller?: AnyCaller): URI;
|
|
84
|
+
/** Return a URI without one or more params (or same URI if no changes were made). */
|
|
85
|
+
export declare function omitURIParams(url: URL | URLString, ...keys: string[]): URL;
|
|
86
|
+
export declare function omitURIParams(url: PossibleURI, ...keys: string[]): URI;
|
|
87
|
+
/** Return a URI without a param (or same URI if no changes were made). */
|
|
88
|
+
export declare const omitURIParam: (url: PossibleURI, key: string) => URI;
|
|
89
|
+
/** A single schema for a URL. */
|
|
90
|
+
export type URIScheme = `${string}:`;
|
|
91
|
+
/** List of allowed URI schemes. */
|
|
92
|
+
export type URISchemes = ImmutableArray<URIScheme>;
|
|
93
|
+
/** Valid HTTP schemes for a URI. */
|
|
94
|
+
export declare const HTTP_SCHEMES: URISchemes;
|
package/util/uri.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
+
import { ValueError } from "../error/ValueError.js";
|
|
3
|
+
import { getDictionaryItems, isDictionary } from "./dictionary.js";
|
|
4
|
+
import { notNullish } from "./null.js";
|
|
5
|
+
import { getString, isString } from "./string.js";
|
|
6
|
+
export const URI = globalThis.URL;
|
|
7
|
+
/** Is an unknown value a URI object? */
|
|
8
|
+
export function isURI(value) {
|
|
9
|
+
return value instanceof URI;
|
|
10
|
+
}
|
|
11
|
+
/** Assert that an unknown value is a URI object. */
|
|
12
|
+
export function assertURI(value, caller = assertURI) {
|
|
13
|
+
if (!isURI(value))
|
|
14
|
+
throw new RequiredError("Invalid URI", { received: value, caller });
|
|
15
|
+
}
|
|
16
|
+
/** Convert a possible URI to a URI, or return `undefined` if conversion fails. */
|
|
17
|
+
export function getURI(possible) {
|
|
18
|
+
if (notNullish(possible)) {
|
|
19
|
+
if (isURI(possible))
|
|
20
|
+
return possible;
|
|
21
|
+
try {
|
|
22
|
+
return new globalThis.URL(possible);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Convert a possible URI to a URI, or throw `RequiredError` if conversion fails. */
|
|
30
|
+
export function requireURI(possible, caller = requireURI) {
|
|
31
|
+
const url = getURI(possible);
|
|
32
|
+
assertURI(url, caller);
|
|
33
|
+
return url;
|
|
34
|
+
}
|
|
35
|
+
/** Convert a possible URI to a URI string, or return `undefined` if conversion fails. */
|
|
36
|
+
export function getURIString(possible) {
|
|
37
|
+
return getURI(possible)?.href;
|
|
38
|
+
}
|
|
39
|
+
/** Convert a possible URI to a URI string, or throw `RequiredError` if conversion fails. */
|
|
40
|
+
export function requireURIString(possible, caller = requireURIString) {
|
|
41
|
+
return requireURI(possible, caller).href;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get a set of entries for a set of possible URI params.
|
|
45
|
+
*
|
|
46
|
+
* Note: Not as simple as just converting with `Object.fromEntries()`:
|
|
47
|
+
* 1. When `URLSearchParams` contains multiple values for the same key, calling `params.get()` will return the _first_ value.
|
|
48
|
+
* 2. So when converting this to a simple data object, only one value per key can be represented, but it needs to be the _first_ one.
|
|
49
|
+
* 3. Since we're looping through anyway, we also take the time to convert values to strings, so we can accept a wider range of input types.
|
|
50
|
+
*/
|
|
51
|
+
function* getURIEntries(input, caller = getURIParams) {
|
|
52
|
+
if (input instanceof URLSearchParams) {
|
|
53
|
+
yield* input;
|
|
54
|
+
}
|
|
55
|
+
else if (isString(input) || input instanceof globalThis.URL) {
|
|
56
|
+
yield* requireURI(input, caller).searchParams;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const done = [];
|
|
60
|
+
for (const [key, value] of getDictionaryItems(input)) {
|
|
61
|
+
if (done.includes(key))
|
|
62
|
+
continue;
|
|
63
|
+
done.push(key);
|
|
64
|
+
const str = getString(value);
|
|
65
|
+
if (str === undefined)
|
|
66
|
+
throw new ValueError(`URI param "${key}" must be string`, { received: value, caller });
|
|
67
|
+
yield [key, str];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Get a set of params for a URI as a dictionary. */
|
|
72
|
+
export function getURIParams(input, caller = getURIParams) {
|
|
73
|
+
const output = {};
|
|
74
|
+
for (const [key, str] of getURIEntries(input, caller))
|
|
75
|
+
output[key] = str;
|
|
76
|
+
return output;
|
|
77
|
+
}
|
|
78
|
+
/** Get a single named param from a URI. */
|
|
79
|
+
export function getURIParam(input, key) {
|
|
80
|
+
if (input instanceof URLSearchParams)
|
|
81
|
+
return input.get(key) || undefined;
|
|
82
|
+
if (isDictionary(input))
|
|
83
|
+
return getString(input[key]);
|
|
84
|
+
return getURIParams(input)[key];
|
|
85
|
+
}
|
|
86
|
+
/** Get a single named param from a URI. */
|
|
87
|
+
export function requireURIParam(input, key, caller = requireURIParam) {
|
|
88
|
+
const value = getURIParam(input, key);
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
throw new RequiredError(`URI param "${key}" is required`, { received: input, caller });
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
export function withURIParam(url, key, value, caller = withURIParam) {
|
|
94
|
+
const input = requireURI(url, caller);
|
|
95
|
+
const output = new URI(input);
|
|
96
|
+
const str = getString(value);
|
|
97
|
+
if (str === undefined)
|
|
98
|
+
throw new ValueError(`URI param "${key}" must be string`, { received: value, caller });
|
|
99
|
+
output.searchParams.set(key, str);
|
|
100
|
+
return input.href === output.href ? input : output;
|
|
101
|
+
}
|
|
102
|
+
export function withURIParams(url, params, caller = withURIParams) {
|
|
103
|
+
const input = requireURI(url, caller);
|
|
104
|
+
const output = new URI(input);
|
|
105
|
+
for (const [key, str] of getURIEntries(params, caller))
|
|
106
|
+
output.searchParams.set(key, str);
|
|
107
|
+
return input.href === output.href ? input : output;
|
|
108
|
+
}
|
|
109
|
+
export function omitURIParams(url, ...keys) {
|
|
110
|
+
const input = requireURI(url, omitURIParams);
|
|
111
|
+
const output = new URI(input);
|
|
112
|
+
for (const key of keys)
|
|
113
|
+
output.searchParams.delete(key);
|
|
114
|
+
return input.href === output.href ? input : output;
|
|
115
|
+
}
|
|
116
|
+
/** Return a URI without a param (or same URI if no changes were made). */
|
|
117
|
+
export const omitURIParam = omitURIParams;
|
|
118
|
+
/** Valid HTTP schemes for a URI. */
|
|
119
|
+
export const HTTP_SCHEMES = ["http:", "https:"];
|
package/util/url.d.ts
CHANGED
|
@@ -1,46 +1,56 @@
|
|
|
1
|
-
import type { DictionaryItem, ImmutableDictionary } from "./dictionary.js";
|
|
2
1
|
import type { AnyCaller } from "./function.js";
|
|
3
2
|
import { type Nullish } from "./null.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
/** Convert a possible URL to a URL, or throw `RequiredError` if conversion fails. */
|
|
13
|
-
export declare function requireURL(possible: PossibleURL, base?: PossibleURL, caller?: AnyCaller): URL;
|
|
14
|
-
/** Type for a set of named URL parameters. */
|
|
15
|
-
export type URLParams = ImmutableDictionary<string>;
|
|
16
|
-
/** Type for things that can be converted to named URL parameters. */
|
|
17
|
-
export type PossibleURLParams = PossibleURL | URLSearchParams | ImmutableDictionary<unknown>;
|
|
3
|
+
import type { AbsolutePath, Path } from "./path.js";
|
|
4
|
+
import type { URI } from "./uri.js";
|
|
5
|
+
/**
|
|
6
|
+
* A URL string has a protocol and a `//`.
|
|
7
|
+
* - The `//` at the start of a URL indicates that it has a hierarchical path component, so this makes it a URL.
|
|
8
|
+
* - URLs have a concept of "absolute" or "relative" URLs, since they have a path.
|
|
9
|
+
*/
|
|
10
|
+
export type URLString = `${string}://${string}`;
|
|
18
11
|
/**
|
|
19
|
-
*
|
|
12
|
+
* Object that describes a valid URL, e.g. `http://example.com/path/to/resource`
|
|
13
|
+
* - Improves the builtin Javascript `URL` class to more accurately type its properties.
|
|
20
14
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* URI and URL differences:
|
|
16
|
+
* - According to RFC 3986, URLs are a subset of URIs that have a hierarchical path component, e.g. `http://example.com/path`.
|
|
17
|
+
* - The `//` at the start of a URL indicates that it has a hierarchical path component, so this makes it a URL.
|
|
18
|
+
* - The absence of `//` indicates a non-hierarchical URI.
|
|
19
|
+
* - URLs can be considered as "hierarchical URIs".
|
|
20
|
+
* - All URLs are also URIs, but not all URIs are URLs.
|
|
21
|
+
*
|
|
22
|
+
* Javascript URL problems:
|
|
23
|
+
* - Javascript `URL` instance can actually represent any kind of URI (not just URLs).
|
|
24
|
+
* - It's more "correct" terminology to use `URI` to refer to what the Javascript `URL` class represents.
|
|
25
|
+
* - You can tell the difference because a URL will have a non-empty `host` property, whereas URIs will never have a `host` (it will be `""` empty string).
|
|
25
26
|
*/
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/** Get a single named param from a URL. */
|
|
32
|
-
export declare function requireURLParam(input: PossibleURLParams, key: string, caller?: AnyCaller): string;
|
|
27
|
+
export interface URL extends URI {
|
|
28
|
+
href: URLString;
|
|
29
|
+
origin: URLString;
|
|
30
|
+
pathname: AbsolutePath;
|
|
31
|
+
}
|
|
33
32
|
/**
|
|
34
|
-
*
|
|
35
|
-
* -
|
|
33
|
+
* Construct a correctly-typed `URL` object.
|
|
34
|
+
* - This is a more correctly typed version of the builtin Javascript `URL` constructor.
|
|
35
|
+
* - Requires a URL string, URL object, or path as input, and optionally a base URL.
|
|
36
|
+
* - If a path is provided as input, a base URL _must_ also be provided.
|
|
37
|
+
* - The returned type is
|
|
36
38
|
*/
|
|
37
|
-
export
|
|
39
|
+
export interface URLConstructor {
|
|
40
|
+
new (input: URLString | URL, base?: URLString | URL): URL;
|
|
41
|
+
new (input: URLString | URL | Path, base: URLString | URL): URL;
|
|
42
|
+
}
|
|
43
|
+
export declare const URL: URLConstructor;
|
|
44
|
+
/** Values that can be converted to a URL instance. */
|
|
45
|
+
export type PossibleURL = string | globalThis.URL;
|
|
38
46
|
/**
|
|
39
|
-
*
|
|
40
|
-
* -
|
|
47
|
+
* Is an unknown value a URL object?
|
|
48
|
+
* - Must be a `URL` instance and its origin must start with `scheme://`
|
|
41
49
|
*/
|
|
42
|
-
export declare function
|
|
43
|
-
/**
|
|
44
|
-
export declare function
|
|
45
|
-
/**
|
|
46
|
-
export declare
|
|
50
|
+
export declare function isURL(value: unknown): value is URL;
|
|
51
|
+
/** Assert that an unknown value is a URL object. */
|
|
52
|
+
export declare function assertURL(value: unknown, caller?: AnyCaller): asserts value is URL;
|
|
53
|
+
/** Convert a possible URL to a URL, or return `undefined` if conversion fails. */
|
|
54
|
+
export declare function getURL(possible: Nullish<PossibleURL>, base?: PossibleURL | undefined): URL | undefined;
|
|
55
|
+
/** Convert a possible URL to a URL, or throw `RequiredError` if conversion fails. */
|
|
56
|
+
export declare function requireURL(possible: PossibleURL, base?: PossibleURL | undefined, caller?: AnyCaller): URL;
|