shelving 1.135.0 → 1.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/Resource.d.ts +2 -2
- package/api/Resource.js +4 -4
- package/db/ItemStore.js +9 -2
- package/db/Provider.js +29 -8
- package/db/QueryStore.js +16 -4
- package/db/ValidationProvider.js +26 -26
- package/error/AssertionError.d.ts +8 -0
- package/error/AssertionError.js +12 -0
- package/error/BaseError.d.ts +19 -0
- package/error/BaseError.js +18 -0
- package/error/NetworkError.d.ts +5 -0
- package/error/NetworkError.js +9 -0
- package/error/RequestError.d.ts +32 -0
- package/error/RequestError.js +50 -0
- package/error/RequiredError.d.ts +8 -0
- package/error/RequiredError.js +12 -0
- package/error/UnexpectedError.d.ts +5 -0
- package/error/UnexpectedError.js +9 -0
- package/error/UnimplementedError.d.ts +5 -0
- package/error/UnimplementedError.js +9 -0
- package/error/ValueError.d.ts +5 -0
- package/error/ValueError.js +9 -0
- package/error/index.d.ts +8 -8
- package/error/index.js +8 -8
- package/firestore/lite/FirestoreLiteProvider.js +3 -3
- package/iterate/InspectIterator.js +14 -4
- package/markup/rule/link.js +2 -2
- package/package.json +1 -1
- package/react/createCacheContext.js +12 -13
- package/react/createDataContext.js +2 -2
- package/schema/AllowSchema.js +2 -2
- package/schema/DateSchema.d.ts +1 -1
- package/schema/DateSchema.js +12 -12
- package/schema/FileSchema.js +2 -2
- package/schema/LinkSchema.js +2 -2
- package/schema/SlugSchema.js +2 -2
- package/schema/TimeSchema.js +4 -4
- package/sequence/InspectSequence.js +14 -4
- package/store/ArrayStore.js +6 -5
- package/store/PathStore.js +4 -4
- package/test/util.js +6 -3
- package/util/array.d.ts +39 -42
- package/util/array.js +48 -82
- package/util/async.d.ts +3 -3
- package/util/async.js +7 -7
- package/util/base64.d.ts +4 -4
- package/util/base64.js +6 -6
- package/util/boolean.js +6 -6
- package/util/buffer.d.ts +8 -0
- package/util/buffer.js +24 -0
- package/util/class.d.ts +4 -0
- package/util/class.js +14 -3
- package/util/color.d.ts +3 -3
- package/util/color.js +8 -8
- package/util/data.d.ts +3 -3
- package/util/data.js +3 -3
- package/util/date.d.ts +20 -15
- package/util/date.js +87 -37
- package/util/dictionary.d.ts +12 -12
- package/util/dictionary.js +20 -19
- package/util/entity.js +2 -2
- package/util/equal.d.ts +4 -0
- package/util/equal.js +12 -1
- package/util/error.d.ts +0 -2
- package/util/error.js +0 -20
- package/util/file.d.ts +3 -3
- package/util/file.js +6 -6
- package/util/function.js +2 -2
- package/util/hydrate.js +5 -3
- package/util/index.d.ts +1 -2
- package/util/index.js +1 -2
- package/util/iterate.d.ts +13 -0
- package/util/iterate.js +48 -0
- package/util/link.d.ts +4 -4
- package/util/link.js +8 -8
- package/util/map.d.ts +4 -4
- package/util/map.js +9 -9
- package/util/null.js +5 -5
- package/util/number.d.ts +6 -11
- package/util/number.js +23 -18
- package/util/object.js +4 -4
- package/util/optional.js +2 -2
- package/util/path.d.ts +3 -3
- package/util/path.js +6 -6
- package/util/query.d.ts +4 -4
- package/util/query.js +10 -9
- package/util/regexp.d.ts +12 -12
- package/util/regexp.js +13 -12
- package/util/sequence.d.ts +0 -2
- package/util/sequence.js +0 -7
- package/util/serialise.js +2 -2
- package/util/set.js +3 -3
- package/util/source.d.ts +4 -8
- package/util/source.js +8 -12
- package/util/string.d.ts +14 -22
- package/util/string.js +27 -29
- package/util/template.js +10 -11
- package/util/time.d.ts +2 -5
- package/util/time.js +12 -13
- package/util/undefined.js +2 -2
- package/util/units.d.ts +17 -12
- package/util/units.js +61 -40
- package/util/url.d.ts +3 -3
- package/util/url.js +11 -7
- package/util/validate.d.ts +4 -4
- package/util/validate.js +5 -9
- package/error/CodedError.d.ts +0 -13
- package/error/CodedError.js +0 -21
- package/error/ConflictError.d.ts +0 -6
- package/error/ConflictError.js +0 -11
- package/error/ConnectionError.d.ts +0 -5
- package/error/ConnectionError.js +0 -10
- package/error/ForbiddenError.d.ts +0 -6
- package/error/ForbiddenError.js +0 -11
- package/error/NotFoundError.d.ts +0 -6
- package/error/NotFoundError.js +0 -11
- package/error/NotImplementedError.d.ts +0 -6
- package/error/NotImplementedError.js +0 -11
- package/error/UnauthorizedError.d.ts +0 -6
- package/error/UnauthorizedError.js +0 -11
- package/error/ValidationError.d.ts +0 -6
- package/error/ValidationError.js +0 -11
- package/util/assert.d.ts +0 -4
- package/util/assert.js +0 -11
- package/util/duration.d.ts +0 -10
- package/util/duration.js +0 -50
package/util/source.d.ts
CHANGED
|
@@ -3,11 +3,7 @@ import type { Class } from "./class.js";
|
|
|
3
3
|
export interface Sourceable<T> {
|
|
4
4
|
readonly source: T;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export declare function
|
|
10
|
-
/**
|
|
11
|
-
* Recurse through `Sourceable` objects and return the first one that is an instance of `type`.
|
|
12
|
-
*/
|
|
13
|
-
export declare function getSource<T>(type: Class<T>, data: unknown): T;
|
|
6
|
+
/** Recurse through `Sourceable` objects and return the first one that is an instance of `type`, or `undefined` if no source object matches. */
|
|
7
|
+
export declare function getSource<T>(type: Class<T>, value: unknown): T | undefined;
|
|
8
|
+
/** Recurse through `Sourceable` objects and return the first one that is an instance of `type`, or throw `RequiredError` if no source object matches. */
|
|
9
|
+
export declare function requireSource<T>(type: Class<T>, data: unknown): T;
|
package/util/source.js
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { isObject } from "./object.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export function getOptionalSource(type, value) {
|
|
3
|
+
/** Recurse through `Sourceable` objects and return the first one that is an instance of `type`, or `undefined` if no source object matches. */
|
|
4
|
+
export function getSource(type, value) {
|
|
7
5
|
if (isObject(value)) {
|
|
8
6
|
if (value instanceof type)
|
|
9
7
|
return value;
|
|
10
8
|
if ("source" in value)
|
|
11
|
-
return
|
|
9
|
+
return getSource(type, value.source);
|
|
12
10
|
}
|
|
13
11
|
}
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function getSource(type, data) {
|
|
18
|
-
const source = getOptionalSource(type, data);
|
|
12
|
+
/** Recurse through `Sourceable` objects and return the first one that is an instance of `type`, or throw `RequiredError` if no source object matches. */
|
|
13
|
+
export function requireSource(type, data) {
|
|
14
|
+
const source = getSource(type, data);
|
|
19
15
|
if (!source)
|
|
20
|
-
throw new
|
|
16
|
+
throw new RequiredError(`Source "${type.name}" not found`, { received: data, expected: type, caller: requireSource });
|
|
21
17
|
return source;
|
|
22
18
|
}
|
package/util/string.d.ts
CHANGED
|
@@ -27,12 +27,12 @@ export declare function assertString(value: unknown): asserts value is string;
|
|
|
27
27
|
* - Everything else returns `"Unknown"`
|
|
28
28
|
*/
|
|
29
29
|
export declare function getString(value: unknown): string;
|
|
30
|
-
/** Does a string have the specified
|
|
30
|
+
/** Does a string have the specified length. */
|
|
31
31
|
export declare function isStringLength(str: string, min?: number, max?: number): boolean;
|
|
32
|
-
/** Assert that a value has a specific length
|
|
32
|
+
/** Assert that a value has a specific length, or throw `AssertionError` if the string is outside that length. */
|
|
33
33
|
export declare function assertStringLength(str: unknown, min?: number, max?: number): asserts str is string;
|
|
34
|
-
/** Get a string if it has the specified
|
|
35
|
-
export declare function
|
|
34
|
+
/** Get a string if it has the specified length, or throw `RequiredError` if the string was outside that length. */
|
|
35
|
+
export declare function requireStringLength(str: string, min?: number, max?: number): string;
|
|
36
36
|
/** Concatenate an iterable set of strings together. */
|
|
37
37
|
export declare function joinStrings(strs: Iterable<string> & NotString, joiner?: string): string;
|
|
38
38
|
/**
|
|
@@ -66,22 +66,14 @@ export declare function sanitizeMultilineText(str: string): string;
|
|
|
66
66
|
* @todo Convert confusables (e.g. `ℵ` alef symbol or `℮` estimate symbol) to their letterlike equivalent (e.g. `N` and `e`).
|
|
67
67
|
*/
|
|
68
68
|
export declare function simplifyString(str: string): string;
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
*/
|
|
72
|
-
export declare function
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
*/
|
|
76
|
-
export declare function
|
|
77
|
-
/**
|
|
78
|
-
* Convert a string to a unique ref e.g. `abc123`, or return `undefined` if conversion resulted in an empty string.
|
|
79
|
-
*/
|
|
80
|
-
export declare function getOptionalRef(str: string): string | undefined;
|
|
81
|
-
/**
|
|
82
|
-
* Convert a string to a unique ref e.g. `abc123`, or throw `ValueError` if conversion resulted in an empty string.
|
|
83
|
-
*/
|
|
84
|
-
export declare function getRef(str: string): string;
|
|
69
|
+
/** Convert a string to a `kebab-case` URL slug, or return `undefined` if conversion resulted in an empty ref. */
|
|
70
|
+
export declare function getSlug(str: string): string | undefined;
|
|
71
|
+
/** Convert a string to a `kebab-case` URL slug, or throw `RequiredError` if conversion resulted in an empty ref. */
|
|
72
|
+
export declare function requireSlug(str: string): string;
|
|
73
|
+
/** Convert a string to a unique ref e.g. `abc123`, or return `undefined` if conversion resulted in an empty string. */
|
|
74
|
+
export declare function getRef(str: string): string | undefined;
|
|
75
|
+
/** Convert a string to a unique ref e.g. `abc123`, or throw `RequiredError` if conversion resulted in an empty string. */
|
|
76
|
+
export declare function requireRef(str: string): string;
|
|
85
77
|
/**
|
|
86
78
|
* Return an array of the separate words and "quoted phrases" found in a string.
|
|
87
79
|
* - Phrases enclosed "in quotes" are a single word.
|
|
@@ -108,8 +100,8 @@ export declare function limitString(str: string, maxLength: number, append?: str
|
|
|
108
100
|
* - Excess segments in `String.prototype.split()` is counterintuitive because further parts are thrown away.
|
|
109
101
|
* - Excess segments in `splitString()` are concatenated onto the last segment (set `max` to `null` if you want infinite segments).
|
|
110
102
|
*
|
|
111
|
-
* @throws
|
|
112
|
-
* @throws
|
|
103
|
+
* @throws AssertionError if `min` isn't met.
|
|
104
|
+
* @throws AssertionError if any of the segments are empty.
|
|
113
105
|
*/
|
|
114
106
|
export declare function splitString(str: string, separator: string, min: 1, max: 1): readonly [string];
|
|
115
107
|
export declare function splitString(str: string, separator: string, min: 2, max: 2): readonly [string, string];
|
package/util/string.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AssertionError } from "../error/AssertionError.js";
|
|
2
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
3
|
+
import { ValueError } from "../error/ValueError.js";
|
|
2
4
|
import { getArray, isArray } from "./array.js";
|
|
3
5
|
import { formatDate, isDate } from "./date.js";
|
|
4
6
|
import { formatNumber, formatRange, isBetween } from "./number.js";
|
|
@@ -10,7 +12,7 @@ export function isString(value) {
|
|
|
10
12
|
/** Assert that a value is a string. */
|
|
11
13
|
export function assertString(value) {
|
|
12
14
|
if (typeof value !== "string")
|
|
13
|
-
throw new
|
|
15
|
+
throw new ValueError("Must be string", { received: value, caller: assertString });
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Convert an unknown value into a title string for user-facing use.
|
|
@@ -46,18 +48,19 @@ export function getString(value) {
|
|
|
46
48
|
return formatObject(value);
|
|
47
49
|
return "Unknown";
|
|
48
50
|
}
|
|
49
|
-
/** Does a string have the specified
|
|
51
|
+
/** Does a string have the specified length. */
|
|
50
52
|
export function isStringLength(str, min = 1, max = Number.POSITIVE_INFINITY) {
|
|
51
53
|
return str.length >= min && str.length <= max;
|
|
52
54
|
}
|
|
53
|
-
/** Assert that a value has a specific length
|
|
55
|
+
/** Assert that a value has a specific length, or throw `AssertionError` if the string is outside that length. */
|
|
54
56
|
export function assertStringLength(str, min = 1, max = Number.POSITIVE_INFINITY) {
|
|
55
57
|
if (!isString(str) || !isStringLength(str, min, max))
|
|
56
|
-
throw new
|
|
58
|
+
throw new AssertionError(`Must be string with length ${formatRange(min, max)}`, { received: str, caller: assertStringLength });
|
|
57
59
|
}
|
|
58
|
-
/** Get a string if it has the specified
|
|
59
|
-
export function
|
|
60
|
-
|
|
60
|
+
/** Get a string if it has the specified length, or throw `RequiredError` if the string was outside that length. */
|
|
61
|
+
export function requireStringLength(str, min = 1, max = Number.POSITIVE_INFINITY) {
|
|
62
|
+
if (!isStringLength(str, min, max))
|
|
63
|
+
throw new RequiredError(`Must be string with length ${formatRange(min, max)}`, { received: str, caller: requireStringLength });
|
|
61
64
|
return str;
|
|
62
65
|
}
|
|
63
66
|
/** Concatenate an iterable set of strings together. */
|
|
@@ -118,35 +121,27 @@ export function simplifyString(str) {
|
|
|
118
121
|
.trim()
|
|
119
122
|
.toLowerCase();
|
|
120
123
|
}
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
export function getOptionalSlug(str) {
|
|
124
|
+
/** Convert a string to a `kebab-case` URL slug, or return `undefined` if conversion resulted in an empty ref. */
|
|
125
|
+
export function getSlug(str) {
|
|
125
126
|
return simplifyString(str).replaceAll(" ", "-") || undefined;
|
|
126
127
|
}
|
|
127
|
-
/**
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
export function getSlug(str) {
|
|
131
|
-
const slug = getOptionalSlug(str);
|
|
128
|
+
/** Convert a string to a `kebab-case` URL slug, or throw `RequiredError` if conversion resulted in an empty ref. */
|
|
129
|
+
export function requireSlug(str) {
|
|
130
|
+
const slug = getSlug(str);
|
|
132
131
|
if (slug)
|
|
133
132
|
return slug;
|
|
134
|
-
throw new
|
|
133
|
+
throw new RequiredError("Invalid slug", { received: str, caller: requireSlug });
|
|
135
134
|
}
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
*/
|
|
139
|
-
export function getOptionalRef(str) {
|
|
135
|
+
/** Convert a string to a unique ref e.g. `abc123`, or return `undefined` if conversion resulted in an empty string. */
|
|
136
|
+
export function getRef(str) {
|
|
140
137
|
return simplifyString(str).replaceAll(" ", "") || undefined;
|
|
141
138
|
}
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
export function getRef(str) {
|
|
146
|
-
const ref = getOptionalRef(str);
|
|
139
|
+
/** Convert a string to a unique ref e.g. `abc123`, or throw `RequiredError` if conversion resulted in an empty string. */
|
|
140
|
+
export function requireRef(str) {
|
|
141
|
+
const ref = getRef(str);
|
|
147
142
|
if (ref)
|
|
148
143
|
return ref;
|
|
149
|
-
throw new
|
|
144
|
+
throw new RequiredError("Invalid string ref", { received: str, caller: requireRef });
|
|
150
145
|
}
|
|
151
146
|
/**
|
|
152
147
|
* Return an array of the separate words and "quoted phrases" found in a string.
|
|
@@ -195,6 +190,9 @@ export function splitString(str, separator, min = 1, max = Number.POSITIVE_INFIN
|
|
|
195
190
|
if (segments.length > max)
|
|
196
191
|
segments.splice(max - 1, segments.length, segments.slice(max - 1).join(separator));
|
|
197
192
|
if (segments.length < min || !segments.every(Boolean))
|
|
198
|
-
throw new
|
|
193
|
+
throw new ValueError(`Must be string with ${formatRange(min, max)} non-empty segments separated by "${separator}"`, {
|
|
194
|
+
received: str,
|
|
195
|
+
caller: splitString,
|
|
196
|
+
});
|
|
199
197
|
return segments;
|
|
200
198
|
}
|
package/util/template.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValueError } from "../error/ValueError.js";
|
|
2
2
|
import { EMPTY_DATA } from "./data.js";
|
|
3
3
|
import { setMapItem } from "./map.js";
|
|
4
4
|
import { isObject } from "./object.js";
|
|
@@ -6,8 +6,6 @@ import { isObject } from "./object.js";
|
|
|
6
6
|
const R_PLACEHOLDERS = /(\*|:[a-z][a-z0-9]*|\$\{[a-z][a-z0-9]*\}|\{\{[a-z][a-z0-9]*\}\}|\{[a-z][a-z0-9]*\})/i;
|
|
7
7
|
// Find actual name within template placeholder e.g. `${name}` → `name`
|
|
8
8
|
const R_NAME = /[a-z0-9]+/i;
|
|
9
|
-
// Cache of templates.
|
|
10
|
-
const TEMPLATE_CACHE = new Map();
|
|
11
9
|
/**
|
|
12
10
|
* Split up a template into an array of separator → placeholder → separator → placeholder → separator
|
|
13
11
|
* - Odd numbered chunks are separators.
|
|
@@ -16,10 +14,7 @@ const TEMPLATE_CACHE = new Map();
|
|
|
16
14
|
* @param template The template including template placeholders, e.g. `:name-${country}/{city}`
|
|
17
15
|
* @returns Array of strings alternating separator and placeholder.
|
|
18
16
|
*/
|
|
19
|
-
function _splitTemplate(template) {
|
|
20
|
-
return TEMPLATE_CACHE.get(template) || setMapItem(TEMPLATE_CACHE, template, _split(template));
|
|
21
|
-
}
|
|
22
|
-
function _split(template) {
|
|
17
|
+
function _splitTemplate(template, caller) {
|
|
23
18
|
const matches = template.split(R_PLACEHOLDERS);
|
|
24
19
|
let asterisks = 0;
|
|
25
20
|
const chunks = [];
|
|
@@ -28,12 +23,16 @@ function _split(template) {
|
|
|
28
23
|
const placeholder = matches[i];
|
|
29
24
|
const post = matches[i + 1];
|
|
30
25
|
if (i > 1 && !pre.length)
|
|
31
|
-
throw new
|
|
26
|
+
throw new ValueError("Placeholders must be separated by at least one character", { received: template, caller });
|
|
32
27
|
const name = placeholder === "*" ? String(asterisks++) : R_NAME.exec(placeholder)?.[0] || "";
|
|
33
28
|
chunks.push({ pre, placeholder, name, post });
|
|
34
29
|
}
|
|
35
30
|
return chunks;
|
|
36
31
|
}
|
|
32
|
+
const TEMPLATE_CACHE = new Map();
|
|
33
|
+
function _splitTemplateCached(template, caller) {
|
|
34
|
+
return TEMPLATE_CACHE.get(template) || setMapItem(TEMPLATE_CACHE, template, _splitTemplate(template, caller));
|
|
35
|
+
}
|
|
37
36
|
/**
|
|
38
37
|
* Get list of placeholders named in a template string.
|
|
39
38
|
*
|
|
@@ -41,7 +40,7 @@ function _split(template) {
|
|
|
41
40
|
* @returns Array of clean string names of found placeholders, e.g. `["name", "country", "city"]`
|
|
42
41
|
*/
|
|
43
42
|
export function getPlaceholders(template) {
|
|
44
|
-
return
|
|
43
|
+
return _splitTemplateCached(template, getPlaceholders).map(_getPlaceholder);
|
|
45
44
|
}
|
|
46
45
|
function _getPlaceholder({ name }) {
|
|
47
46
|
return name;
|
|
@@ -57,7 +56,7 @@ function _getPlaceholder({ name }) {
|
|
|
57
56
|
*/
|
|
58
57
|
export function matchTemplate(template, target) {
|
|
59
58
|
// Get separators and placeholders from template.
|
|
60
|
-
const chunks =
|
|
59
|
+
const chunks = _splitTemplateCached(template, matchTemplate);
|
|
61
60
|
const firstChunk = chunks[0];
|
|
62
61
|
// Return early if empty.
|
|
63
62
|
if (!firstChunk)
|
|
@@ -102,7 +101,7 @@ export function matchTemplates(templates, target) {
|
|
|
102
101
|
* @throws {ReferenceError} If a placeholder in the template string is not specified in values.
|
|
103
102
|
*/
|
|
104
103
|
export function renderTemplate(template, value) {
|
|
105
|
-
const chunks =
|
|
104
|
+
const chunks = _splitTemplateCached(template, renderTemplate);
|
|
106
105
|
if (!chunks.length)
|
|
107
106
|
return template;
|
|
108
107
|
let output = template;
|
package/util/time.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Optional } from "./optional.js";
|
|
2
1
|
/** Class representing a time in the day in 24 hour format in the user's current locale. */
|
|
3
2
|
export declare class Time {
|
|
4
3
|
/** Make a new `Time` instance from a time string. */
|
|
@@ -40,13 +39,11 @@ export declare function isTime(value: unknown): value is Time;
|
|
|
40
39
|
*
|
|
41
40
|
* @param possible Any value that we want to parse as a valid time (defaults to `undefined`).
|
|
42
41
|
*/
|
|
43
|
-
export declare function
|
|
42
|
+
export declare function getTime(possible: unknown): Time | undefined;
|
|
44
43
|
/**
|
|
45
44
|
* Convert a possible date to a `Time` instance, or throw `ValueError` if it couldn't be converted (defaults to `"now"`).
|
|
46
45
|
* @param possible Any value that we want to parse as a valid time (defaults to `"now"`).
|
|
47
46
|
*/
|
|
48
|
-
export declare function
|
|
47
|
+
export declare function requireTime(possible?: PossibleTime): Time;
|
|
49
48
|
/** Format a time as a string based on the browser locale settings. */
|
|
50
49
|
export declare function formatTime(time?: PossibleTime, precision?: 2 | 3 | 4 | 5 | 6): string;
|
|
51
|
-
/** Format an optional time as a string based on the browser locale settings. */
|
|
52
|
-
export declare function formatOptionalTime(time: Optional<PossibleTime>, precision?: 2 | 3 | 4 | 5 | 6): string | undefined;
|
package/util/time.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValueError } from "../error/ValueError.js";
|
|
2
2
|
import { DAY, HOUR, MINUTE, SECOND } from "./constants.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getDate } from "./date.js";
|
|
4
4
|
import { wrapNumber } from "./number.js";
|
|
5
5
|
/** Class representing a time in the day in 24 hour format in the user's current locale. */
|
|
6
6
|
export class Time {
|
|
@@ -20,7 +20,7 @@ export class Time {
|
|
|
20
20
|
(typeof ms === "string" ? Number.parseInt(ms, 10) : 0));
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
const date =
|
|
23
|
+
const date = getDate(possible);
|
|
24
24
|
if (date)
|
|
25
25
|
return new Time(date.getHours() * HOUR + date.getMinutes() * MINUTE + date.getSeconds() * SECOND + date.getMilliseconds());
|
|
26
26
|
}
|
|
@@ -72,7 +72,7 @@ export class Time {
|
|
|
72
72
|
hour: "2-digit",
|
|
73
73
|
minute: "2-digit",
|
|
74
74
|
second: precision >= 3 ? "2-digit" : undefined,
|
|
75
|
-
fractionalSecondDigits: precision >= 4 ? precision - 3 : undefined,
|
|
75
|
+
fractionalSecondDigits: precision >= 4 ? (precision - 3) : undefined,
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
// Implement `valueOf()`
|
|
@@ -100,24 +100,23 @@ export function isTime(value) {
|
|
|
100
100
|
*
|
|
101
101
|
* @param possible Any value that we want to parse as a valid time (defaults to `undefined`).
|
|
102
102
|
*/
|
|
103
|
-
export function
|
|
103
|
+
export function getTime(possible) {
|
|
104
104
|
return Time.from(possible);
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* Convert a possible date to a `Time` instance, or throw `ValueError` if it couldn't be converted (defaults to `"now"`).
|
|
108
108
|
* @param possible Any value that we want to parse as a valid time (defaults to `"now"`).
|
|
109
109
|
*/
|
|
110
|
-
export function
|
|
111
|
-
|
|
110
|
+
export function requireTime(possible) {
|
|
111
|
+
return _time(requireTime, possible);
|
|
112
|
+
}
|
|
113
|
+
function _time(caller, possible = "now") {
|
|
114
|
+
const time = Time.from(possible);
|
|
112
115
|
if (!time)
|
|
113
|
-
throw new
|
|
116
|
+
throw new ValueError("Invalid time", { received: possible, caller });
|
|
114
117
|
return time;
|
|
115
118
|
}
|
|
116
119
|
/** Format a time as a string based on the browser locale settings. */
|
|
117
120
|
export function formatTime(time, precision = 2) {
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
/** Format an optional time as a string based on the browser locale settings. */
|
|
121
|
-
export function formatOptionalTime(time, precision = 2) {
|
|
122
|
-
return getOptionalTime(time)?.format(precision);
|
|
121
|
+
return _time(formatTime, time).format(precision);
|
|
123
122
|
}
|
package/util/undefined.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AssertionError } from "../error/AssertionError.js";
|
|
2
2
|
/** Function that always returns undefined. */
|
|
3
3
|
export const getUndefined = () => undefined;
|
|
4
4
|
/** Is a value undefined? */
|
|
@@ -14,7 +14,7 @@ export const notUndefined = isDefined;
|
|
|
14
14
|
/** Assert that a value is not `undefined` */
|
|
15
15
|
export function assertDefined(value) {
|
|
16
16
|
if (value === undefined)
|
|
17
|
-
throw new
|
|
17
|
+
throw new AssertionError("Must be defined", { received: value, caller: assertDefined });
|
|
18
18
|
}
|
|
19
19
|
/** Get a defined value. */
|
|
20
20
|
export function getDefined(value) {
|
package/util/units.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { MapKey } from "./map.js";
|
|
2
2
|
import { ImmutableMap } from "./map.js";
|
|
3
|
-
import type { NumberOptions } from "./number.js";
|
|
4
3
|
import type { ImmutableObject } from "./object.js";
|
|
5
4
|
/** Conversion from one unit to another (either an amount to multiple by, or a function to convert). */
|
|
6
5
|
type Conversion = number | ((num: number) => number);
|
|
@@ -17,6 +16,8 @@ type UnitProps<T extends string> = {
|
|
|
17
16
|
readonly plural?: string;
|
|
18
17
|
/** Conversions to other units (typically needs at least the base conversion, unless it's already the base unit). */
|
|
19
18
|
readonly to?: Conversions<T>;
|
|
19
|
+
/** Possible options for formatting these units with `Intl.NumberFormat` (`.unit` can be specified if different from key, but is not required). */
|
|
20
|
+
readonly options?: Intl.NumberFormatOptions;
|
|
20
21
|
};
|
|
21
22
|
/** Represent a unit. */
|
|
22
23
|
export declare class Unit<K extends string> {
|
|
@@ -31,6 +32,8 @@ export declare class Unit<K extends string> {
|
|
|
31
32
|
readonly singular: string;
|
|
32
33
|
/** Plural name for this unit, e.g. `kilometers` (defaults to `singular` + "s"). */
|
|
33
34
|
readonly plural: string;
|
|
35
|
+
/** Possible options for formatting these units with `Intl.NumberFormat` (`.unit` can be specified if different from key, but is not required). */
|
|
36
|
+
readonly options?: Readonly<Intl.NumberFormatOptions>;
|
|
34
37
|
/** Title for this unit (uses format `abbr (plural)`, e.g. `fl oz (US fluid ounces)`) */
|
|
35
38
|
get title(): string;
|
|
36
39
|
constructor(
|
|
@@ -41,15 +44,17 @@ export declare class Unit<K extends string> {
|
|
|
41
44
|
/** Props to configure this unit. */
|
|
42
45
|
{ abbr, singular, plural, to }: UnitProps<K>);
|
|
43
46
|
/** Convert an amount from this unit to another unit. */
|
|
44
|
-
to(amount: number,
|
|
47
|
+
to(amount: number, targetKey?: K): number;
|
|
45
48
|
/** Convert an amount from another unit to this unit. */
|
|
46
|
-
from(amount: number,
|
|
49
|
+
from(amount: number, sourceKey?: K): number;
|
|
47
50
|
/** Convert an amount from this unit to another unit (must specify another `Unit` instance). */
|
|
48
|
-
private
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
private _convertTo;
|
|
52
|
+
/**
|
|
53
|
+
* Format an amount with a given unit of measure, e.g. `12 kg` or `29.5 l`
|
|
54
|
+
* - Uses `Intl.NumberFormat` if this is a supported unit (so e.g. `ounce` is translated to e.g. `Unze` in German).
|
|
55
|
+
* - Polyfills unsupported units to use long/short form based on `options.unitDisplay`.
|
|
56
|
+
*/
|
|
57
|
+
format(amount: number, options?: Intl.NumberFormatOptions): string;
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* Represent a list of units.
|
|
@@ -61,12 +66,12 @@ export declare class UnitList<K extends string> extends ImmutableMap<K, Unit<K>>
|
|
|
61
66
|
readonly base: Unit<K>;
|
|
62
67
|
constructor(units: ImmutableObject<K, UnitProps<K>>);
|
|
63
68
|
/** Convert an amount from a unit to another unit. */
|
|
64
|
-
convert(amount: number,
|
|
69
|
+
convert(amount: number, sourceKey: K, targetKey: K): number;
|
|
65
70
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @throws RequiredError if the unit is not found.
|
|
71
|
+
* Require a unit from this list.
|
|
72
|
+
* @throws RequiredError if the unit key is not found.
|
|
68
73
|
*/
|
|
69
|
-
|
|
74
|
+
require(key: K): Unit<K>;
|
|
70
75
|
}
|
|
71
76
|
/** Percentage units. */
|
|
72
77
|
export declare const PERCENT_UNITS: UnitList<"percent">;
|
package/util/units.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
+
import { ValueError } from "../error/ValueError.js";
|
|
3
3
|
import { DAY, HOUR, MILLION, MINUTE, MONTH, NNBSP, SECOND, WEEK, YEAR } from "./constants.js";
|
|
4
4
|
import { ImmutableMap } from "./map.js";
|
|
5
|
-
import { formatQuantity, pluralizeQuantity } from "./number.js";
|
|
5
|
+
import { formatNumber, formatQuantity, pluralizeQuantity } from "./number.js";
|
|
6
6
|
import { getProps } from "./object.js";
|
|
7
7
|
/** Convert an amount using a `Conversion. */
|
|
8
8
|
function _convert(amount, conversion) {
|
|
@@ -21,6 +21,8 @@ export class Unit {
|
|
|
21
21
|
singular;
|
|
22
22
|
/** Plural name for this unit, e.g. `kilometers` (defaults to `singular` + "s"). */
|
|
23
23
|
plural;
|
|
24
|
+
/** Possible options for formatting these units with `Intl.NumberFormat` (`.unit` can be specified if different from key, but is not required). */
|
|
25
|
+
options;
|
|
24
26
|
/** Title for this unit (uses format `abbr (plural)`, e.g. `fl oz (US fluid ounces)`) */
|
|
25
27
|
get title() {
|
|
26
28
|
return `${this.abbr} (${this.plural})`;
|
|
@@ -40,41 +42,53 @@ export class Unit {
|
|
|
40
42
|
this._to = to;
|
|
41
43
|
}
|
|
42
44
|
/** Convert an amount from this unit to another unit. */
|
|
43
|
-
to(amount,
|
|
44
|
-
|
|
45
|
+
to(amount, targetKey) {
|
|
46
|
+
const target = targetKey ? _requireUnit(this.to, this.list, targetKey) : this.list.base;
|
|
47
|
+
return this._convertTo(amount, target, this.to);
|
|
45
48
|
}
|
|
46
49
|
/** Convert an amount from another unit to this unit. */
|
|
47
|
-
from(amount,
|
|
48
|
-
|
|
50
|
+
from(amount, sourceKey) {
|
|
51
|
+
const source = sourceKey ? _requireUnit(this.from, this.list, sourceKey) : this.list.base;
|
|
52
|
+
return source._convertTo(amount, this, this.from);
|
|
49
53
|
}
|
|
50
54
|
/** Convert an amount from this unit to another unit (must specify another `Unit` instance). */
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
if (
|
|
55
|
+
_convertTo(amount, target, caller) {
|
|
56
|
+
// No conversion.
|
|
57
|
+
if (target === this)
|
|
54
58
|
return amount;
|
|
55
59
|
// Exact conversion.
|
|
56
|
-
|
|
60
|
+
// When this unit knows the multiplier or function to convert to the target unit.
|
|
61
|
+
const thisToUnit = this._to?.[target.key];
|
|
57
62
|
if (thisToUnit)
|
|
58
63
|
return _convert(amount, thisToUnit);
|
|
59
|
-
// Invert number conversion
|
|
60
|
-
|
|
64
|
+
// Invert number conversion.
|
|
65
|
+
// This is where the target type knows the multiplier to convert to this.
|
|
66
|
+
// Can't do this for function conversions.
|
|
67
|
+
const unitToThis = target._to?.[this.key];
|
|
61
68
|
if (typeof unitToThis === "number")
|
|
62
69
|
return amount / unitToThis;
|
|
63
|
-
//
|
|
70
|
+
// Via base conversion.
|
|
71
|
+
// Everything should know how to convert to its base units.
|
|
64
72
|
const base = this.list.base;
|
|
65
73
|
const thisToBase = this._to?.[base.key];
|
|
66
74
|
if (thisToBase)
|
|
67
|
-
return base.
|
|
68
|
-
//
|
|
69
|
-
throw new
|
|
75
|
+
return base._convertTo(_convert(amount, thisToBase), target, caller);
|
|
76
|
+
// Not convertable.
|
|
77
|
+
throw new ValueError(`Cannot convert "${base.key}" to "${this.key}"`, { list: this, caller });
|
|
70
78
|
}
|
|
71
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Format an amount with a given unit of measure, e.g. `12 kg` or `29.5 l`
|
|
81
|
+
* - Uses `Intl.NumberFormat` if this is a supported unit (so e.g. `ounce` is translated to e.g. `Unze` in German).
|
|
82
|
+
* - Polyfills unsupported units to use long/short form based on `options.unitDisplay`.
|
|
83
|
+
*/
|
|
72
84
|
format(amount, options) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
// If possible use `Intl` so that the user's locale is used.
|
|
86
|
+
if (Intl.supportedValuesOf("unit").includes(this.key))
|
|
87
|
+
return formatNumber(amount, { style: "unit", unitDisplay: "short", ...this.options, ...options, unit: this.key });
|
|
88
|
+
// Otherwise, use the default number format.
|
|
89
|
+
// If unitDisplay is "long" use the singular/plural form.
|
|
90
|
+
const o = { style: "decimal", unitDisplay: "short", ...this.options, ...options };
|
|
91
|
+
return o.unitDisplay === "long" ? pluralizeQuantity(amount, this.singular, this.plural, o) : formatQuantity(amount, this.abbr, o);
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
94
|
/**
|
|
@@ -95,19 +109,23 @@ export class UnitList extends ImmutableMap {
|
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
/** Convert an amount from a unit to another unit. */
|
|
98
|
-
convert(amount,
|
|
99
|
-
return this.
|
|
112
|
+
convert(amount, sourceKey, targetKey) {
|
|
113
|
+
return _requireUnit(this.convert, this, sourceKey).to(amount, targetKey);
|
|
100
114
|
}
|
|
101
115
|
/**
|
|
102
|
-
*
|
|
103
|
-
* @throws RequiredError if the unit is not found.
|
|
116
|
+
* Require a unit from this list.
|
|
117
|
+
* @throws RequiredError if the unit key is not found.
|
|
104
118
|
*/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return this.get(units);
|
|
108
|
-
throw new NotFoundError("Unknown unit", units);
|
|
119
|
+
require(key) {
|
|
120
|
+
return _requireUnit(this.require, this, key);
|
|
109
121
|
}
|
|
110
122
|
}
|
|
123
|
+
function _requireUnit(caller, list, key) {
|
|
124
|
+
const unit = list.get(key);
|
|
125
|
+
if (!unit)
|
|
126
|
+
throw new RequiredError(`Unknown unit "${key}"`, { key, list, caller });
|
|
127
|
+
return unit;
|
|
128
|
+
}
|
|
111
129
|
// Distance constants.
|
|
112
130
|
const IN_PER_FT = 12;
|
|
113
131
|
const IN_PER_YD = 36;
|
|
@@ -160,17 +178,20 @@ export const MASS_UNITS = new UnitList({
|
|
|
160
178
|
pound: { abbr: "lb", to: { milligram: MG_PER_LB, ounce: OZ_PER_LB } },
|
|
161
179
|
stone: { abbr: "st", plural: "stone", to: { milligram: MG_PER_LB * LB_PER_ST, pound: LB_PER_ST, ounce: OZ_PER_LB * LB_PER_ST } },
|
|
162
180
|
});
|
|
181
|
+
const TIME_OPTIONS = {
|
|
182
|
+
roundingMode: "trunc",
|
|
183
|
+
maximumFractionDigits: 0,
|
|
184
|
+
};
|
|
163
185
|
/** Time units. */
|
|
164
186
|
export const TIME_UNITS = new UnitList({
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
year: { to: { millisecond: YEAR } },
|
|
187
|
+
millisecond: { abbr: "ms", options: TIME_OPTIONS },
|
|
188
|
+
second: { to: { millisecond: SECOND }, options: TIME_OPTIONS },
|
|
189
|
+
minute: { to: { millisecond: MINUTE }, options: TIME_OPTIONS },
|
|
190
|
+
hour: { to: { millisecond: HOUR }, options: TIME_OPTIONS },
|
|
191
|
+
day: { to: { millisecond: DAY }, options: TIME_OPTIONS },
|
|
192
|
+
week: { to: { millisecond: WEEK }, options: TIME_OPTIONS },
|
|
193
|
+
month: { to: { millisecond: MONTH }, options: TIME_OPTIONS },
|
|
194
|
+
year: { to: { millisecond: YEAR }, options: TIME_OPTIONS },
|
|
174
195
|
});
|
|
175
196
|
/** Length units. */
|
|
176
197
|
export const LENGTH_UNITS = new UnitList({
|
package/util/url.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ export type PossibleURL = string | URL;
|
|
|
5
5
|
export declare function isURL(value: unknown): value is URL;
|
|
6
6
|
/** Assert that an unknown value is a URL object. */
|
|
7
7
|
export declare function assertURL(value: unknown): asserts value is URL;
|
|
8
|
-
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
8
|
+
/** Convert a possible URL to a URL, or return `undefined` if conversion fails. */
|
|
9
9
|
export declare function getOptionalURL(possible: Optional<PossibleURL>, base?: PossibleURL | undefined): URL | undefined;
|
|
10
|
-
/** Convert a possible URL to a URL
|
|
11
|
-
export declare function
|
|
10
|
+
/** Convert a possible URL to a URL, or throw `AssertionError` if conversion fails. */
|
|
11
|
+
export declare function requireURL(possible: PossibleURL, base?: PossibleURL): URL;
|
|
12
12
|
/** Just get the important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
13
13
|
export declare function formatURL(possible: PossibleURL, base?: PossibleURL): string;
|