shelving 1.138.0 → 1.139.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/db/Provider.js +3 -3
- package/error/BaseError.d.ts +3 -1
- package/error/index.d.ts +0 -1
- package/error/index.js +0 -1
- package/markup/rule/link.js +3 -2
- package/package.json +1 -1
- package/schema/AllowSchema.js +4 -4
- package/schema/DateSchema.js +2 -1
- package/schema/EntitySchema.js +4 -4
- package/schema/NumberSchema.js +3 -2
- package/store/ArrayStore.js +5 -5
- package/util/array.d.ts +67 -66
- package/util/array.js +90 -30
- package/util/async.js +4 -4
- package/util/base64.d.ts +7 -7
- package/util/base64.js +40 -31
- package/util/boolean.d.ts +6 -5
- package/util/boolean.js +11 -11
- package/util/bytes.d.ts +4 -1
- package/util/bytes.js +7 -3
- package/util/class.js +2 -2
- package/util/color.d.ts +4 -3
- package/util/color.js +5 -6
- package/util/crypto.d.ts +21 -0
- package/util/crypto.js +80 -0
- package/util/data.d.ts +6 -13
- package/util/data.js +8 -12
- package/util/date.d.ts +35 -27
- package/util/date.js +90 -82
- package/util/dictionary.d.ts +13 -7
- package/util/dictionary.js +12 -12
- package/util/entity.d.ts +8 -7
- package/util/entity.js +8 -8
- package/util/equal.d.ts +3 -2
- package/util/equal.js +5 -5
- package/util/file.d.ts +2 -1
- package/util/file.js +3 -3
- package/util/format.d.ts +51 -0
- package/util/format.js +113 -0
- package/util/function.js +2 -2
- package/util/index.d.ts +2 -0
- package/util/index.js +2 -0
- package/util/iterate.d.ts +0 -15
- package/util/iterate.js +0 -55
- package/util/jwt.d.ts +4 -2
- package/util/jwt.js +50 -41
- package/util/link.d.ts +26 -10
- package/util/link.js +34 -21
- package/util/map.d.ts +8 -7
- package/util/map.js +17 -18
- package/util/null.d.ts +7 -6
- package/util/null.js +13 -13
- package/util/number.d.ts +36 -42
- package/util/number.js +60 -82
- package/util/object.d.ts +0 -7
- package/util/object.js +4 -24
- package/util/optional.d.ts +2 -1
- package/util/optional.js +2 -2
- package/util/path.d.ts +7 -6
- package/util/path.js +10 -10
- package/util/query.js +3 -3
- package/util/random.js +2 -2
- package/util/regexp.js +2 -2
- package/util/set.d.ts +7 -6
- package/util/set.js +13 -13
- package/util/string.d.ts +26 -37
- package/util/string.js +40 -69
- package/util/template.d.ts +5 -4
- package/util/template.js +14 -13
- package/util/time.d.ts +7 -8
- package/util/time.js +14 -21
- package/util/transform.d.ts +3 -2
- package/util/transform.js +0 -1
- package/util/undefined.d.ts +4 -3
- package/util/undefined.js +8 -6
- package/util/units.d.ts +1 -2
- package/util/units.js +1 -1
- package/util/update.js +3 -3
- package/util/url.d.ts +6 -7
- package/util/url.js +7 -17
- package/util/validate.d.ts +2 -2
- package/error/AssertionError.d.ts +0 -8
- package/error/AssertionError.js +0 -12
package/util/base64.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { ValueError } from "../error/ValueError.js";
|
|
1
2
|
import { requireBytes } from "./bytes.js";
|
|
2
3
|
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
4
|
const BASE64URL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
5
|
+
/** Create a reverse lookup table for decoding in { charCode: asciiValue } format. Works for both alphabets. */
|
|
6
|
+
const REVERSE_CHARS = new Uint8Array(128).fill(255);
|
|
7
|
+
for (let i = 0; i < BASE64_CHARS.length; i++) {
|
|
8
|
+
REVERSE_CHARS[BASE64_CHARS.charCodeAt(i)] = i;
|
|
9
|
+
REVERSE_CHARS[BASE64URL_CHARS.charCodeAt(i)] = i;
|
|
10
|
+
}
|
|
4
11
|
/**
|
|
5
12
|
* @todo DH: When it's well supported, use `Uint8Array.toBase64()`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
|
|
6
13
|
*/
|
|
@@ -20,58 +27,60 @@ function _encode(bytes, alphabet, pad) {
|
|
|
20
27
|
}
|
|
21
28
|
return output;
|
|
22
29
|
}
|
|
23
|
-
function _decode(base64,
|
|
24
|
-
// Create a reverse lookup table: char -> 6-bit value
|
|
25
|
-
const values = new Uint8Array(128);
|
|
26
|
-
for (let i = 0; i < alphabet.length; i++)
|
|
27
|
-
values[alphabet.charCodeAt(i)] = i;
|
|
30
|
+
function _decode(base64, caller) {
|
|
28
31
|
// Remove padding.
|
|
29
32
|
const cleaned = base64.replace(/=+$/, "");
|
|
30
33
|
const length = cleaned.length;
|
|
31
34
|
// Calculate output byte length
|
|
32
35
|
// Every 4 base64 chars = 3 bytes; adjust for padding
|
|
33
|
-
const
|
|
34
|
-
const
|
|
36
|
+
const byteLength = Math.floor((length * 6) / 8);
|
|
37
|
+
const bytes = new Uint8Array(byteLength);
|
|
35
38
|
let j = 0;
|
|
36
39
|
for (let i = 0; i < length; i += 4) {
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const c4 = i + 3 < length ? values[cleaned.charCodeAt(i + 3)] : 0;
|
|
40
|
+
const l1 = _lookup(cleaned, i, caller);
|
|
41
|
+
const l2 = _lookup(cleaned, i + 1, caller);
|
|
42
|
+
const l3 = i + 2 < length ? _lookup(cleaned, i + 2, caller) : 0;
|
|
43
|
+
const l4 = i + 3 < length ? _lookup(cleaned, i + 3, caller) : 0;
|
|
42
44
|
// Combine into 24 bits
|
|
43
|
-
const combined = (
|
|
45
|
+
const combined = (l1 << 18) | (l2 << 12) | (l3 << 6) | l4;
|
|
44
46
|
// Extract bytes and add to output if within range
|
|
45
|
-
if (j <
|
|
46
|
-
|
|
47
|
-
if (j <
|
|
48
|
-
|
|
49
|
-
if (j <
|
|
50
|
-
|
|
47
|
+
if (j < byteLength)
|
|
48
|
+
bytes[j++] = (combined >> 16) & 0xff;
|
|
49
|
+
if (j < byteLength)
|
|
50
|
+
bytes[j++] = (combined >> 8) & 0xff;
|
|
51
|
+
if (j < byteLength)
|
|
52
|
+
bytes[j++] = combined & 0xff;
|
|
51
53
|
}
|
|
52
|
-
return
|
|
54
|
+
return bytes;
|
|
55
|
+
}
|
|
56
|
+
function _lookup(base64, index, caller) {
|
|
57
|
+
const code = base64.charCodeAt(index);
|
|
58
|
+
const value = REVERSE_CHARS[code];
|
|
59
|
+
if (value === undefined || value === 255)
|
|
60
|
+
throw new ValueError(`Invalid character "${code}" in Base64 string`, { received: base64, index: index, caller });
|
|
61
|
+
return value;
|
|
53
62
|
}
|
|
54
63
|
/** Encode a string or binary data to Base64 string. */
|
|
55
64
|
export function encodeBase64(input, pad = true) {
|
|
56
65
|
return _encode(requireBytes(input), BASE64_CHARS, pad ? "=" : "");
|
|
57
66
|
}
|
|
58
|
-
/** Decode Base64 string to string. */
|
|
67
|
+
/** Decode Base64 string to string (decodes Base64URL too). */
|
|
59
68
|
export function decodeBase64String(base64) {
|
|
60
|
-
return new TextDecoder("utf-8").decode(_decode(base64,
|
|
69
|
+
return new TextDecoder("utf-8").decode(_decode(base64, decodeBase64String));
|
|
61
70
|
}
|
|
62
|
-
/** Decode URL-safe Base64 string to
|
|
71
|
+
/** Decode URL-safe Base64 string to byte sequence (decodes Base64URL too). */
|
|
63
72
|
export function decodeBase64Bytes(base64) {
|
|
64
|
-
return _decode(base64,
|
|
73
|
+
return _decode(base64, decodeBase64Bytes);
|
|
65
74
|
}
|
|
66
75
|
/** Encode a string or binary data to URL-safe Base64 */
|
|
67
|
-
export function
|
|
76
|
+
export function encodeBase64URL(input, pad = false) {
|
|
68
77
|
return _encode(requireBytes(input), BASE64URL_CHARS, pad ? "=" : "");
|
|
69
78
|
}
|
|
70
|
-
/** Decode a string from URL-safe Base64. */
|
|
71
|
-
export function
|
|
72
|
-
return new TextDecoder("utf-8").decode(_decode(base64,
|
|
79
|
+
/** Decode a string from URL-safe Base64 (decodes Base64 too). */
|
|
80
|
+
export function decodeBase64URLString(base64) {
|
|
81
|
+
return new TextDecoder("utf-8").decode(_decode(base64, decodeBase64URLString));
|
|
73
82
|
}
|
|
74
|
-
/** Decode URL-safe Base64 string to
|
|
75
|
-
export function
|
|
76
|
-
return _decode(base64,
|
|
83
|
+
/** Decode URL-safe Base64 string to byte sequence (decodes Base64 too). */
|
|
84
|
+
export function decodeBase64URLBytes(base64) {
|
|
85
|
+
return _decode(base64, decodeBase64URLBytes);
|
|
77
86
|
}
|
package/util/boolean.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
/** Is a value a boolean? */
|
|
2
3
|
export declare function isBoolean(value: unknown): value is boolean;
|
|
3
4
|
/** Is a value true? */
|
|
@@ -9,12 +10,12 @@ export declare function isTruthy(value: unknown): boolean;
|
|
|
9
10
|
/** Is a value falsey? */
|
|
10
11
|
export declare function isFalsey(value: unknown): boolean;
|
|
11
12
|
/** Assert that a value is a boolean. */
|
|
12
|
-
export declare function assertBoolean(value: unknown): asserts value is boolean;
|
|
13
|
+
export declare function assertBoolean(value: unknown, caller?: AnyCaller): asserts value is boolean;
|
|
13
14
|
/** Assert that a value is true. */
|
|
14
|
-
export declare function assertTrue(value: unknown): asserts value is true;
|
|
15
|
+
export declare function assertTrue(value: unknown, caller?: AnyCaller): asserts value is true;
|
|
15
16
|
/** Assert that a value is false. */
|
|
16
|
-
export declare function assertFalse(value: unknown): asserts value is false;
|
|
17
|
+
export declare function assertFalse(value: unknown, caller?: AnyCaller): asserts value is false;
|
|
17
18
|
/** Assert that a value is truthy. */
|
|
18
|
-
export declare function assertTruthy(value: unknown): asserts value is true;
|
|
19
|
+
export declare function assertTruthy(value: unknown, caller?: AnyCaller): asserts value is true;
|
|
19
20
|
/** Assert that a value is falsy. */
|
|
20
|
-
export declare function assertFalsy(value: unknown): asserts value is false;
|
|
21
|
+
export declare function assertFalsy(value: unknown, caller?: AnyCaller): asserts value is false;
|
package/util/boolean.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
/** Is a value a boolean? */
|
|
3
3
|
export function isBoolean(value) {
|
|
4
4
|
return typeof value === "boolean";
|
|
@@ -20,27 +20,27 @@ export function isFalsey(value) {
|
|
|
20
20
|
return !value;
|
|
21
21
|
}
|
|
22
22
|
/** Assert that a value is a boolean. */
|
|
23
|
-
export function assertBoolean(value) {
|
|
23
|
+
export function assertBoolean(value, caller = assertBoolean) {
|
|
24
24
|
if (typeof value !== "boolean")
|
|
25
|
-
throw new
|
|
25
|
+
throw new RequiredError("Must be boolean", { received: value, caller });
|
|
26
26
|
}
|
|
27
27
|
/** Assert that a value is true. */
|
|
28
|
-
export function assertTrue(value) {
|
|
28
|
+
export function assertTrue(value, caller = assertTrue) {
|
|
29
29
|
if (value !== true)
|
|
30
|
-
throw new
|
|
30
|
+
throw new RequiredError("Must be true", { received: value, caller });
|
|
31
31
|
}
|
|
32
32
|
/** Assert that a value is false. */
|
|
33
|
-
export function assertFalse(value) {
|
|
33
|
+
export function assertFalse(value, caller = assertFalse) {
|
|
34
34
|
if (value !== false)
|
|
35
|
-
throw new
|
|
35
|
+
throw new RequiredError("Must be false", { received: value, caller });
|
|
36
36
|
}
|
|
37
37
|
/** Assert that a value is truthy. */
|
|
38
|
-
export function assertTruthy(value) {
|
|
38
|
+
export function assertTruthy(value, caller = assertTruthy) {
|
|
39
39
|
if (!value)
|
|
40
|
-
throw new
|
|
40
|
+
throw new RequiredError("Must be truthy", { received: value, caller });
|
|
41
41
|
}
|
|
42
42
|
/** Assert that a value is falsy. */
|
|
43
|
-
export function assertFalsy(value) {
|
|
43
|
+
export function assertFalsy(value, caller = assertFalsy) {
|
|
44
44
|
if (value)
|
|
45
|
-
throw new
|
|
45
|
+
throw new RequiredError("Must be falsy", { received: value, caller });
|
|
46
46
|
}
|
package/util/bytes.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
/** Types that can be converted to a `Uint8Array` byte sequence. */
|
|
2
3
|
export type PossibleBytes = Uint8Array | ArrayBuffer | string;
|
|
4
|
+
/** Assert that an unknown value is a `Uint8Array` byte sequence. */
|
|
5
|
+
export declare function assertBytes(value: unknown, min?: number, max?: number, caller?: AnyCaller): asserts value is Uint8Array;
|
|
3
6
|
/**
|
|
4
7
|
* Convert an unknown value to a `Uint8Array` byte sequence, or `undefined` if the value cannot be converted.
|
|
5
8
|
*
|
|
@@ -9,4 +12,4 @@ export type PossibleBytes = Uint8Array | ArrayBuffer | string;
|
|
|
9
12
|
*/
|
|
10
13
|
export declare function getBytes(value: unknown): Uint8Array | undefined;
|
|
11
14
|
/** Convert an unknown value to a `Uint8Array` byte sequence, or throw `RequiredError` if the value cannot be converted. */
|
|
12
|
-
export declare function requireBytes(value: PossibleBytes): Uint8Array;
|
|
15
|
+
export declare function requireBytes(value: PossibleBytes, min?: number, max?: number, caller?: AnyCaller): Uint8Array;
|
package/util/bytes.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
+
/** Assert that an unknown value is a `Uint8Array` byte sequence. */
|
|
3
|
+
export function assertBytes(value, min = 0, max = Number.POSITIVE_INFINITY, caller = assertBytes) {
|
|
4
|
+
if (!(value instanceof Uint8Array) || value.length < min || value.length > max)
|
|
5
|
+
throw new RequiredError(`Value must be byte sequence${min > 0 || max < Number.POSITIVE_INFINITY ? ` with length between ${min} and ${max}` : ""}`, { received: value, caller });
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* Convert an unknown value to a `Uint8Array` byte sequence, or `undefined` if the value cannot be converted.
|
|
4
9
|
*
|
|
@@ -16,9 +21,8 @@ export function getBytes(value) {
|
|
|
16
21
|
return undefined;
|
|
17
22
|
}
|
|
18
23
|
/** Convert an unknown value to a `Uint8Array` byte sequence, or throw `RequiredError` if the value cannot be converted. */
|
|
19
|
-
export function requireBytes(value) {
|
|
24
|
+
export function requireBytes(value, min = 0, max = Number.POSITIVE_INFINITY, caller = requireBytes) {
|
|
20
25
|
const bytes = getBytes(value);
|
|
21
|
-
|
|
22
|
-
throw new RequiredError("Value cannot be converted to byte array", { received: value, caller: requireBytes });
|
|
26
|
+
assertBytes(bytes, min, max, caller);
|
|
23
27
|
return bytes;
|
|
24
28
|
}
|
package/util/class.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
/** Is a given value a class constructor? */
|
|
3
3
|
export function isConstructor(value) {
|
|
4
4
|
return typeof value === "function" && value.toString().startsWith("class");
|
|
@@ -10,7 +10,7 @@ export function isInstance(value, type) {
|
|
|
10
10
|
/** Assert that a value is an instance of something. */
|
|
11
11
|
export function assertInstance(value, type) {
|
|
12
12
|
if (!(value instanceof type))
|
|
13
|
-
throw new
|
|
13
|
+
throw new RequiredError(`Must be instance of class "${type.name}"`, { received: value, expected: type, caller: assertInstance });
|
|
14
14
|
}
|
|
15
15
|
/** Get the 'getter' function for a given property, or `undefined` if it doesn't exist. */
|
|
16
16
|
// biome-ignore lint/complexity/noBannedTypes: This is correct here.
|
package/util/color.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
export declare const HEX3_REGEXP: RegExp;
|
|
2
3
|
export declare const HEX6_REGEXP: RegExp;
|
|
3
4
|
/** Things that can be converted to a `Color` instance. */
|
|
@@ -28,8 +29,8 @@ export declare class Color {
|
|
|
28
29
|
/** Is an unknown value a `Color` instance. */
|
|
29
30
|
export declare function isColor(value: unknown): value is Color;
|
|
30
31
|
/** Assert that an unknown value is a `Color` instance. */
|
|
31
|
-
export declare function assertColor(value: unknown): asserts value is Color;
|
|
32
|
-
/** Convert
|
|
32
|
+
export declare function assertColor(value: unknown, caller?: AnyCaller): asserts value is Color;
|
|
33
|
+
/** Convert an unknown value to a `Color` instance, or return `undefined` if conversion fails. */
|
|
33
34
|
export declare function getColor(value: unknown): Color | undefined;
|
|
34
35
|
/** Convert a possible color to a `Color` instance, or throw `RequiredError` if it can't be converted. */
|
|
35
|
-
export declare function requireColor(value: PossibleColor): Color;
|
|
36
|
+
export declare function requireColor(value: PossibleColor, caller?: AnyCaller): Color;
|
package/util/color.js
CHANGED
|
@@ -69,18 +69,17 @@ export function isColor(value) {
|
|
|
69
69
|
return value instanceof Color;
|
|
70
70
|
}
|
|
71
71
|
/** Assert that an unknown value is a `Color` instance. */
|
|
72
|
-
export function assertColor(value) {
|
|
72
|
+
export function assertColor(value, caller = assertColor) {
|
|
73
73
|
if (!isColor(value))
|
|
74
|
-
throw new RequiredError("Must be color", { received: value, caller
|
|
74
|
+
throw new RequiredError("Must be color", { received: value, caller });
|
|
75
75
|
}
|
|
76
|
-
/** Convert
|
|
76
|
+
/** Convert an unknown value to a `Color` instance, or return `undefined` if conversion fails. */
|
|
77
77
|
export function getColor(value) {
|
|
78
78
|
return Color.from(value);
|
|
79
79
|
}
|
|
80
80
|
/** Convert a possible color to a `Color` instance, or throw `RequiredError` if it can't be converted. */
|
|
81
|
-
export function requireColor(value) {
|
|
81
|
+
export function requireColor(value, caller = requireColor) {
|
|
82
82
|
const color = getColor(value);
|
|
83
|
-
|
|
84
|
-
throw new RequiredError("Invalid color", { received: value, caller: requireColor });
|
|
83
|
+
assertColor(color, requireColor);
|
|
85
84
|
return color;
|
|
86
85
|
}
|
package/util/crypto.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Get random bytes from the crypto API. */
|
|
2
|
+
export declare function getRandomBytes(length: number): Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* Hash a password using PBKDF2, generating a new salt, and return the combined salt$iterations$hash string.
|
|
5
|
+
*
|
|
6
|
+
* @param password The password to hash.
|
|
7
|
+
* @param iterations The number of iterations.
|
|
8
|
+
*
|
|
9
|
+
* @returns Hash in the format `salt$iterations$hash`, where `salt` and `hash` are base64-encoded.
|
|
10
|
+
* - Returned hash tring will be about 128 characters long (16 byte salt + iteration count + 64 byte hash + 2 separators = 116 characters once base64 encoded).
|
|
11
|
+
*/
|
|
12
|
+
export declare function hashPassword(password: string, iterations?: number): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Verify a password against a stored salt$iterations$hash string using PBKDF2.
|
|
15
|
+
*
|
|
16
|
+
* @param password The password to verify.
|
|
17
|
+
* @param hash String in the format `salt$iterations$hash`, where `salt` and `hash` are base64-encoded.
|
|
18
|
+
*
|
|
19
|
+
* @returns True if the password matches the hash, false otherwise.
|
|
20
|
+
*/
|
|
21
|
+
export declare function verifyPassword(password: string, hash: string): Promise<boolean>;
|
package/util/crypto.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ValueError } from "../error/ValueError.js";
|
|
2
|
+
import { decodeBase64URLBytes, encodeBase64URL } from "./base64.js";
|
|
3
|
+
import { requireBytes } from "./bytes.js";
|
|
4
|
+
// Constants.
|
|
5
|
+
const ALGORITHM = { name: "PBKDF2", hash: "SHA-512" };
|
|
6
|
+
const ITERATIONS = 500000;
|
|
7
|
+
const SALT_LENGTH = 16; // 16 bytes = 128 bits
|
|
8
|
+
const HASH_LENGTH = 64; // 64 bytes = 512 bits
|
|
9
|
+
const PASSWORD_LENGTH = 6;
|
|
10
|
+
/** Import a key for PBKDF2 operations (deriveBits) using SHA-512. */
|
|
11
|
+
function _getKey(password) {
|
|
12
|
+
return crypto.subtle.importKey("raw", requireBytes(password), ALGORITHM, false, ["deriveBits"]);
|
|
13
|
+
}
|
|
14
|
+
/** Get random bytes from the crypto API. */
|
|
15
|
+
export function getRandomBytes(length) {
|
|
16
|
+
const bytes = new Uint8Array(length);
|
|
17
|
+
crypto.getRandomValues(bytes);
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hash a password using PBKDF2, generating a new salt, and return the combined salt$iterations$hash string.
|
|
22
|
+
*
|
|
23
|
+
* @param password The password to hash.
|
|
24
|
+
* @param iterations The number of iterations.
|
|
25
|
+
*
|
|
26
|
+
* @returns Hash in the format `salt$iterations$hash`, where `salt` and `hash` are base64-encoded.
|
|
27
|
+
* - Returned hash tring will be about 128 characters long (16 byte salt + iteration count + 64 byte hash + 2 separators = 116 characters once base64 encoded).
|
|
28
|
+
*/
|
|
29
|
+
export async function hashPassword(password, iterations = ITERATIONS) {
|
|
30
|
+
// Checks.
|
|
31
|
+
if (password.length < PASSWORD_LENGTH)
|
|
32
|
+
throw new ValueError(`Password must be at least ${PASSWORD_LENGTH} characters long`, {
|
|
33
|
+
received: password.length,
|
|
34
|
+
caller: hashPassword,
|
|
35
|
+
});
|
|
36
|
+
if (iterations < 1)
|
|
37
|
+
throw new ValueError("Iterations must be number greater than 0", { received: iterations, caller: hashPassword });
|
|
38
|
+
// Hash the password.
|
|
39
|
+
const key = await _getKey(password);
|
|
40
|
+
const salt = getRandomBytes(SALT_LENGTH);
|
|
41
|
+
const bits = HASH_LENGTH * 8;
|
|
42
|
+
const hash = await crypto.subtle.deriveBits({ ...ALGORITHM, salt, iterations }, key, bits);
|
|
43
|
+
// Return the combined string
|
|
44
|
+
return `${encodeBase64URL(salt)}$${iterations}$${encodeBase64URL(hash)}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Verify a password against a stored salt$iterations$hash string using PBKDF2.
|
|
48
|
+
*
|
|
49
|
+
* @param password The password to verify.
|
|
50
|
+
* @param hash String in the format `salt$iterations$hash`, where `salt` and `hash` are base64-encoded.
|
|
51
|
+
*
|
|
52
|
+
* @returns True if the password matches the hash, false otherwise.
|
|
53
|
+
*/
|
|
54
|
+
export async function verifyPassword(password, hash) {
|
|
55
|
+
// Check salthash.
|
|
56
|
+
const [s, i, h] = hash.split("$");
|
|
57
|
+
if (!s || !i || !h)
|
|
58
|
+
return false;
|
|
59
|
+
const hashBytes = decodeBase64URLBytes(h);
|
|
60
|
+
// Check iterations.
|
|
61
|
+
const iterations = Number.parseInt(i, 10);
|
|
62
|
+
if (!Number.isFinite(iterations) || iterations < 1)
|
|
63
|
+
return false;
|
|
64
|
+
// Derive the hash.
|
|
65
|
+
const key = await _getKey(password);
|
|
66
|
+
const salt = decodeBase64URLBytes(s);
|
|
67
|
+
const bits = hashBytes.length * 8;
|
|
68
|
+
const derivedBytes = new Uint8Array(await crypto.subtle.deriveBits({ ...ALGORITHM, salt, iterations }, key, bits));
|
|
69
|
+
// Compare the derived hash with the stored hash.
|
|
70
|
+
return _compareBytes(derivedBytes, hashBytes);
|
|
71
|
+
}
|
|
72
|
+
/** Compare two sets of bytes using constant time comparison. */
|
|
73
|
+
function _compareBytes(left, right) {
|
|
74
|
+
if (left.length !== right.length)
|
|
75
|
+
return false;
|
|
76
|
+
let result = 0;
|
|
77
|
+
for (let i = 0; i < left.length; ++i)
|
|
78
|
+
result |= left[i] ^ right[i];
|
|
79
|
+
return result === 0;
|
|
80
|
+
}
|
package/util/data.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
import type { ImmutableArray } from "./array.js";
|
|
2
3
|
import type { EntryObject } from "./entry.js";
|
|
3
4
|
import type { DeepPartial } from "./object.js";
|
|
@@ -46,14 +47,14 @@ export type LeafProp<T extends Data> = {
|
|
|
46
47
|
/** Is an unknown value a data object? */
|
|
47
48
|
export declare function isData(value: unknown): value is Data;
|
|
48
49
|
/** Assert that an unknown value is a data object. */
|
|
49
|
-
export declare function assertData(value: unknown): asserts value is Data;
|
|
50
|
-
/** Is an unknown value the key for an own prop of a data object. */
|
|
51
|
-
export declare const isDataProp: <T extends Data>(data: T, key: unknown) => key is DataKey<T>;
|
|
52
|
-
/** Assert that an unknown value is the key for an own prop of a data object. */
|
|
53
|
-
export declare function assertDataProp<T extends Data>(data: T, key: unknown): asserts key is DataKey<T>;
|
|
50
|
+
export declare function assertData(value: unknown, caller?: AnyCaller): asserts value is Data;
|
|
54
51
|
/** Convert a data object or set of `DataProp` props for that object back into the full object. */
|
|
55
52
|
export declare function getData<T extends Data>(input: T): T;
|
|
56
53
|
export declare function getData<T extends Data>(input: T | Iterable<DataProp<T>>): Partial<T>;
|
|
54
|
+
/** Is an unknown value the key for an own prop of a data object. */
|
|
55
|
+
export declare const isDataProp: <T extends Data>(data: T, key: unknown) => key is DataKey<T>;
|
|
56
|
+
/** Assert that an unknown value is the key for an own prop of a data object. */
|
|
57
|
+
export declare function assertDataProp<T extends Data>(data: T, key: unknown, caller?: AnyCaller): asserts key is DataKey<T>;
|
|
57
58
|
/** Get the props of a data object as a set of entries. */
|
|
58
59
|
export declare function getDataProps<T extends Data>(data: T): ImmutableArray<DataProp<T>>;
|
|
59
60
|
export declare function getDataProps<T extends Data>(data: T | Partial<T>): ImmutableArray<DataProp<T>>;
|
|
@@ -64,11 +65,3 @@ export declare function getDataKeys<T extends Data>(data: T | Partial<T>): Immut
|
|
|
64
65
|
export declare function getDataProp<T extends Data, K extends BranchKey<T> = BranchKey<T>>(data: T, key: K): BranchData<T>[K];
|
|
65
66
|
export declare function getDataProp<T extends Data, K extends BranchKey<T> = BranchKey<T>>(data: DeepPartial<T>, key: K): BranchData<T>[K] | undefined;
|
|
66
67
|
export declare function getDataProp(data: Data, key: string): unknown;
|
|
67
|
-
/** Type that represents an empty data object. */
|
|
68
|
-
export type EmptyData = {
|
|
69
|
-
readonly [K in never]: never;
|
|
70
|
-
};
|
|
71
|
-
/** An empty object. */
|
|
72
|
-
export declare const EMPTY_DATA: EmptyData;
|
|
73
|
-
/** Function that returns an an empty object. */
|
|
74
|
-
export declare const getEmptyData: () => EmptyData;
|
package/util/data.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { isIterable } from "./iterate.js";
|
|
3
3
|
import { isObject, isPlainObject } from "./object.js";
|
|
4
4
|
/** Is an unknown value a data object? */
|
|
@@ -6,19 +6,19 @@ export function isData(value) {
|
|
|
6
6
|
return isPlainObject(value);
|
|
7
7
|
}
|
|
8
8
|
/** Assert that an unknown value is a data object. */
|
|
9
|
-
export function assertData(value) {
|
|
9
|
+
export function assertData(value, caller = assertData) {
|
|
10
10
|
if (!isPlainObject(value))
|
|
11
|
-
throw new
|
|
11
|
+
throw new RequiredError("Must be data object", { received: value, caller });
|
|
12
|
+
}
|
|
13
|
+
export function getData(input) {
|
|
14
|
+
return isIterable(input) ? Object.fromEntries(input) : input;
|
|
12
15
|
}
|
|
13
16
|
/** Is an unknown value the key for an own prop of a data object. */
|
|
14
17
|
export const isDataProp = (data, key) => typeof key === "string" && Object.hasOwn(data, key);
|
|
15
18
|
/** Assert that an unknown value is the key for an own prop of a data object. */
|
|
16
|
-
export function assertDataProp(data, key) {
|
|
19
|
+
export function assertDataProp(data, key, caller = assertDataProp) {
|
|
17
20
|
if (!isDataProp(data, key))
|
|
18
|
-
throw new
|
|
19
|
-
}
|
|
20
|
-
export function getData(input) {
|
|
21
|
-
return isIterable(input) ? Object.fromEntries(input) : input;
|
|
21
|
+
throw new RequiredError("Key must exist in data object", { key, data, caller });
|
|
22
22
|
}
|
|
23
23
|
export function getDataProps(data) {
|
|
24
24
|
return Object.entries(data);
|
|
@@ -35,7 +35,3 @@ export function getDataProp(data, key) {
|
|
|
35
35
|
}
|
|
36
36
|
return current;
|
|
37
37
|
}
|
|
38
|
-
/** An empty object. */
|
|
39
|
-
export const EMPTY_DATA = { __proto__: null };
|
|
40
|
-
/** Function that returns an an empty object. */
|
|
41
|
-
export const getEmptyData = () => EMPTY_DATA;
|
package/util/date.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
2
|
+
/** Values that can be converted to dates. */
|
|
3
3
|
export type PossibleDate = "now" | "today" | "tomorrow" | "yesterday" | Date | number | string;
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Is a value a valid date?
|
|
6
|
+
* - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `false`
|
|
7
|
+
*/
|
|
5
8
|
export declare function isDate(value: unknown): value is Date;
|
|
6
9
|
/** Assert that a value is a `Date` instance. */
|
|
7
|
-
export declare function assertDate(value: unknown): asserts value is Date;
|
|
10
|
+
export declare function assertDate(value: unknown, caller?: AnyCaller): asserts value is Date;
|
|
8
11
|
/**
|
|
9
12
|
* Convert an unknown value to a valid `Date` instance, or return `undefined` if it couldn't be converted.
|
|
10
13
|
* - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `null`
|
|
@@ -20,24 +23,31 @@ export declare function assertDate(value: unknown): asserts value is Date;
|
|
|
20
23
|
* - Numbers are return the corresponding date (using `new Date(number)`, i.e. milliseconds since 01/01/1970).
|
|
21
24
|
* - Anything else returns `undefined`
|
|
22
25
|
*
|
|
23
|
-
* @param
|
|
26
|
+
* @param value Any value that we want to parse as a valid date (defaults to `undefined`).
|
|
24
27
|
* @returns `Date` instance if the value could be converted to a valid date, and `null` if not.
|
|
25
28
|
*/
|
|
26
|
-
export declare function getDate(
|
|
29
|
+
export declare function getDate(value: unknown): Date | undefined;
|
|
30
|
+
/** Get a date representing this exact moment. */
|
|
31
|
+
export declare function getNow(): Date;
|
|
32
|
+
/** Get a date representing midnight of the previous day. */
|
|
33
|
+
export declare function getYesterday(): Date;
|
|
34
|
+
/** Get a date representing midnight of the current day. */
|
|
35
|
+
export declare function getToday(): Date;
|
|
36
|
+
/** Get a date representing midnight of the next day. */
|
|
37
|
+
export declare function getTomorrow(): Date;
|
|
27
38
|
/**
|
|
28
39
|
* Convert a possible date to a `Date` instance, or throw `RequiredError` if it couldn't be converted.
|
|
29
|
-
* @param
|
|
40
|
+
* @param value Any value that we want to parse as a valid date (defaults to `"now"`).
|
|
30
41
|
*/
|
|
31
|
-
export declare function requireDate(
|
|
32
|
-
export declare function _date(caller: AnyFunction, possible?: PossibleDate): Date;
|
|
42
|
+
export declare function requireDate(value?: PossibleDate, caller?: AnyCaller): Date;
|
|
33
43
|
/** Convert an unknown value to a timestamp (milliseconds past Unix epoch), or `undefined` if it couldn't be converted. */
|
|
34
|
-
export declare function getTimestamp(
|
|
44
|
+
export declare function getTimestamp(value?: unknown): number | undefined;
|
|
35
45
|
/** Convert a possible date to a timestamp (milliseconds past Unix epoch), or throw `RequiredError` if it couldn't be converted. */
|
|
36
|
-
export declare function requireTimestamp(
|
|
46
|
+
export declare function requireTimestamp(value?: PossibleDate): number;
|
|
37
47
|
/** Convert an unknown value to a YMD date string like "2015-09-12", or `undefined` if it couldn't be converted. */
|
|
38
|
-
export declare function getYMD(
|
|
48
|
+
export declare function getYMD(value?: unknown): string | undefined;
|
|
39
49
|
/** Convert a `Date` instance to a YMD string like "2015-09-12", or throw `RequiredError` if it couldn't be converted. */
|
|
40
|
-
export declare function requireYMD(
|
|
50
|
+
export declare function requireYMD(value?: PossibleDate, caller?: AnyCaller): string;
|
|
41
51
|
/** List of day-of-week strings. */
|
|
42
52
|
export declare const DAYS: readonly ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
43
53
|
/** Type listing day-of-week strings. */
|
|
@@ -45,9 +55,9 @@ export type Day = (typeof DAYS)[number];
|
|
|
45
55
|
/** Convert a `Date` instance to a day-of-week string like "monday" */
|
|
46
56
|
export declare function getDay(target?: PossibleDate): Day;
|
|
47
57
|
/** Get a Date representing exactly midnight of the specified date. */
|
|
48
|
-
export declare function getMidnight(target?: PossibleDate): Date;
|
|
58
|
+
export declare function getMidnight(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
49
59
|
/** Get a Date representing midnight on Monday of the specified week. */
|
|
50
|
-
export declare function getMonday(target?: PossibleDate): Date;
|
|
60
|
+
export declare function getMonday(target?: PossibleDate, caller?: AnyCaller): Date;
|
|
51
61
|
/** Return a new date that increase or decreases the number of days based on an input date. */
|
|
52
62
|
export declare function addDays(change: number, target?: PossibleDate): Date;
|
|
53
63
|
/** Return a new date that increase or decreases the number of hours based on an input date. */
|
|
@@ -58,27 +68,25 @@ export declare function addHours(change: number, target?: PossibleDate): Date;
|
|
|
58
68
|
* @param target The date when the thing will happen or did happen.
|
|
59
69
|
* @param current Today's date (or a different date to measure from).
|
|
60
70
|
*/
|
|
61
|
-
export declare function getMillisecondsUntil(target: PossibleDate, current?: PossibleDate): number;
|
|
71
|
+
export declare function getMillisecondsUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
62
72
|
/** Count the number of seconds until a date. */
|
|
63
|
-
export declare function getSecondsUntil(target: PossibleDate, current?: PossibleDate): number;
|
|
73
|
+
export declare function getSecondsUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
64
74
|
/** Count the number of days ago a date was. */
|
|
65
|
-
export declare function getSecondsAgo(target: PossibleDate, current?: PossibleDate): number;
|
|
75
|
+
export declare function getSecondsAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
66
76
|
/** Count the number of days until a date. */
|
|
67
|
-
export declare function getDaysUntil(target: PossibleDate, current?: PossibleDate): number;
|
|
77
|
+
export declare function getDaysUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
68
78
|
/** Count the number of days ago a date was. */
|
|
69
|
-
export declare function getDaysAgo(target: PossibleDate, current?: PossibleDate): number;
|
|
79
|
+
export declare function getDaysAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
70
80
|
/** Count the number of weeks until a date. */
|
|
71
|
-
export declare function getWeeksUntil(target: PossibleDate, current?: PossibleDate): number;
|
|
81
|
+
export declare function getWeeksUntil(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
72
82
|
/** Count the number of weeks ago a date was. */
|
|
73
|
-
export declare function getWeeksAgo(target: PossibleDate, current?: PossibleDate): number;
|
|
83
|
+
export declare function getWeeksAgo(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): number;
|
|
74
84
|
/** Is a date in the past? */
|
|
75
|
-
export declare function isPast(target: PossibleDate, current?: PossibleDate): boolean;
|
|
85
|
+
export declare function isPast(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
76
86
|
/** Is a date in the future? */
|
|
77
|
-
export declare function isFuture(target: PossibleDate, current?: PossibleDate): boolean;
|
|
87
|
+
export declare function isFuture(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
78
88
|
/** Is a date today (taking into account midnight). */
|
|
79
|
-
export declare function isToday(target: PossibleDate, current?: PossibleDate): boolean;
|
|
80
|
-
/** Format a date in the browser locale. */
|
|
81
|
-
export declare function formatDate(date?: PossibleDate): string;
|
|
89
|
+
export declare function isToday(target: PossibleDate, current?: PossibleDate, caller?: AnyCaller): boolean;
|
|
82
90
|
/** Compact when a date happens/happened, e.g. `in 10d` or `2h ago` or `in 1w` or `just now` */
|
|
83
91
|
export declare function formatWhen(target: PossibleDate, current?: PossibleDate, options?: Intl.NumberFormatOptions): string;
|
|
84
92
|
/** Compact when a date happens, e.g. `10d` or `2h` or `-1w` */
|