@rexezuge/shared 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/dist/Base64.d.ts +52 -0
- package/dist/Base64.d.ts.map +1 -0
- package/dist/Base64.js +81 -0
- package/dist/Base64.js.map +1 -0
- package/dist/Clock.d.ts +56 -0
- package/dist/Clock.d.ts.map +1 -0
- package/dist/Clock.js +59 -0
- package/dist/Clock.js.map +1 -0
- package/dist/CryptoUtil.d.ts +79 -0
- package/dist/CryptoUtil.d.ts.map +1 -0
- package/dist/CryptoUtil.js +97 -0
- package/dist/CryptoUtil.js.map +1 -0
- package/dist/CursorCodec.d.ts +61 -0
- package/dist/CursorCodec.d.ts.map +1 -0
- package/dist/CursorCodec.js +60 -0
- package/dist/CursorCodec.js.map +1 -0
- package/dist/EmailUtil.d.ts +29 -0
- package/dist/EmailUtil.d.ts.map +1 -0
- package/dist/EmailUtil.js +31 -0
- package/dist/EmailUtil.js.map +1 -0
- package/dist/ErrorSanitizationUtil.d.ts +69 -0
- package/dist/ErrorSanitizationUtil.d.ts.map +1 -0
- package/dist/ErrorSanitizationUtil.js +98 -0
- package/dist/ErrorSanitizationUtil.js.map +1 -0
- package/dist/IdGenerator.d.ts +27 -0
- package/dist/IdGenerator.d.ts.map +1 -0
- package/dist/IdGenerator.js +28 -0
- package/dist/IdGenerator.js.map +1 -0
- package/dist/Identity.d.ts +36 -0
- package/dist/Identity.d.ts.map +1 -0
- package/dist/Identity.js +39 -0
- package/dist/Identity.js.map +1 -0
- package/dist/LanguageTag.d.ts +25 -0
- package/dist/LanguageTag.d.ts.map +1 -0
- package/dist/LanguageTag.js +29 -0
- package/dist/LanguageTag.js.map +1 -0
- package/dist/LocaleUtil.d.ts +35 -0
- package/dist/LocaleUtil.d.ts.map +1 -0
- package/dist/LocaleUtil.js +97 -0
- package/dist/LocaleUtil.js.map +1 -0
- package/dist/PasswordFingerprint.d.ts +30 -0
- package/dist/PasswordFingerprint.d.ts.map +1 -0
- package/dist/PasswordFingerprint.js +33 -0
- package/dist/PasswordFingerprint.js.map +1 -0
- package/dist/RemoteUrlPolicy.d.ts +121 -0
- package/dist/RemoteUrlPolicy.d.ts.map +1 -0
- package/dist/RemoteUrlPolicy.js +339 -0
- package/dist/RemoteUrlPolicy.js.map +1 -0
- package/dist/Result.d.ts +32 -0
- package/dist/Result.d.ts.map +1 -0
- package/dist/Result.js +19 -0
- package/dist/Result.js.map +1 -0
- package/dist/SubrequestMeter.d.ts +204 -0
- package/dist/SubrequestMeter.d.ts.map +1 -0
- package/dist/SubrequestMeter.js +169 -0
- package/dist/SubrequestMeter.js.map +1 -0
- package/dist/TimeZoneUtil.d.ts +47 -0
- package/dist/TimeZoneUtil.d.ts.map +1 -0
- package/dist/TimeZoneUtil.js +101 -0
- package/dist/TimeZoneUtil.js.map +1 -0
- package/dist/TimestampUtil.d.ts +53 -0
- package/dist/TimestampUtil.d.ts.map +1 -0
- package/dist/TimestampUtil.js +69 -0
- package/dist/TimestampUtil.js.map +1 -0
- package/dist/TokenHashUtil.d.ts +34 -0
- package/dist/TokenHashUtil.d.ts.map +1 -0
- package/dist/TokenHashUtil.js +37 -0
- package/dist/TokenHashUtil.js.map +1 -0
- package/dist/UUIDUtil.d.ts +70 -0
- package/dist/UUIDUtil.d.ts.map +1 -0
- package/dist/UUIDUtil.js +79 -0
- package/dist/UUIDUtil.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -3
- package/README.md +0 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SYSTEM_CLOCK } from './Clock';
|
|
2
|
+
/**
|
|
3
|
+
* Unix timestamps, in seconds and milliseconds.
|
|
4
|
+
*
|
|
5
|
+
* ## Provenance
|
|
6
|
+
*
|
|
7
|
+
* Converged from all nine `TimestampUtil.ts` copies. They fall into three
|
|
8
|
+
* shapes: Durable-DAV's minimal two-method set (`getCurrentUnixTimestampInSeconds`
|
|
9
|
+
* + `addDays`) with a deliberate "seconds everywhere" argument; AWS's compact set
|
|
10
|
+
* plus an **injectable `Clock`** on the current-time readers; and the full
|
|
11
|
+
* ChordDHT/Mail-Otter set (`…InMilliseconds`, `addMinutes`, `addHours`,
|
|
12
|
+
* `subtractMinutes`, `subtractDays`, `convertIso…`) reading `Date.now()` directly.
|
|
13
|
+
*
|
|
14
|
+
* ## Design decisions
|
|
15
|
+
*
|
|
16
|
+
* - The **full method set** is kept: the union of the three shapes loses nothing
|
|
17
|
+
* and each method exists because at least two repos independently reached for
|
|
18
|
+
* it.
|
|
19
|
+
* - The current-time reads go through an **injectable `Clock`** defaulting to
|
|
20
|
+
* `SYSTEM_CLOCK` (AWS's shape). Reading the clock through one seam lets a test
|
|
21
|
+
* pin "now" without racing `Date.now()`, and a caller cannot accidentally store
|
|
22
|
+
* milliseconds in a column compared against seconds.
|
|
23
|
+
* - Seconds is the storage unit (Durable-DAV's argument): where millisecond
|
|
24
|
+
* precision is genuinely wanted — ordering within a single pass — reach for
|
|
25
|
+
* `…InMilliseconds` (or `clock.now()`) explicitly, so the unit is visible at the
|
|
26
|
+
* call site. `add*`/`subtract*` operate on the **second** unit and are pure
|
|
27
|
+
* calendar arithmetic, never `Date` arithmetic, so they cannot pick up a
|
|
28
|
+
* timezone or a DST transition.
|
|
29
|
+
*/
|
|
30
|
+
class TimestampUtil {
|
|
31
|
+
/**
|
|
32
|
+
* Now, in whole milliseconds.
|
|
33
|
+
*
|
|
34
|
+
* @param clock Inject a `FixedClock` to pin "now" in a test; production leaves
|
|
35
|
+
* it defaulted.
|
|
36
|
+
*/
|
|
37
|
+
static getCurrentUnixTimestampInMilliseconds(clock = SYSTEM_CLOCK) {
|
|
38
|
+
return clock.now();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Now, in whole seconds. The default storage unit.
|
|
42
|
+
*
|
|
43
|
+
* @param clock Inject a `FixedClock` to pin "now" in a test; production leaves
|
|
44
|
+
* it defaulted.
|
|
45
|
+
*/
|
|
46
|
+
static getCurrentUnixTimestampInSeconds(clock = SYSTEM_CLOCK) {
|
|
47
|
+
return Math.floor(clock.now() / 1000);
|
|
48
|
+
}
|
|
49
|
+
static addMinutes(timestamp, minutes) {
|
|
50
|
+
return timestamp + minutes * 60;
|
|
51
|
+
}
|
|
52
|
+
static addHours(timestamp, hours) {
|
|
53
|
+
return timestamp + hours * 60 * 60;
|
|
54
|
+
}
|
|
55
|
+
static addDays(timestamp, days) {
|
|
56
|
+
return timestamp + days * 60 * 60 * 24;
|
|
57
|
+
}
|
|
58
|
+
static subtractMinutes(timestamp, minutes) {
|
|
59
|
+
return timestamp - minutes * 60;
|
|
60
|
+
}
|
|
61
|
+
static subtractDays(timestamp, days) {
|
|
62
|
+
return timestamp - days * 60 * 60 * 24;
|
|
63
|
+
}
|
|
64
|
+
static convertIsoToUnixTimestampInSeconds(isoString) {
|
|
65
|
+
return Math.floor(new Date(isoString).getTime() / 1000);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export { TimestampUtil };
|
|
69
|
+
//# sourceMappingURL=TimestampUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimestampUtil.js","sourceRoot":"","sources":["../src/TimestampUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAc,MAAM,SAAS,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,aAAa;IACjB;;;;;OAKG;IACI,MAAM,CAAC,qCAAqC,CAAC,QAAe,YAAY;QAC7E,OAAO,KAAK,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,gCAAgC,CAAC,QAAe,YAAY;QACxE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACxC,CAAC;IAEM,MAAM,CAAC,UAAU,CAAC,SAAiB,EAAE,OAAe;QACzD,OAAO,SAAS,GAAG,OAAO,GAAG,EAAE,CAAC;IAClC,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAC,SAAiB,EAAE,KAAa;QACrD,OAAO,SAAS,GAAG,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;IACrC,CAAC;IAEM,MAAM,CAAC,OAAO,CAAC,SAAiB,EAAE,IAAY;QACnD,OAAO,SAAS,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACzC,CAAC;IAEM,MAAM,CAAC,eAAe,CAAC,SAAiB,EAAE,OAAe;QAC9D,OAAO,SAAS,GAAG,OAAO,GAAG,EAAE,CAAC;IAClC,CAAC;IAEM,MAAM,CAAC,YAAY,CAAC,SAAiB,EAAE,IAAY;QACxD,OAAO,SAAS,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACzC,CAAC;IAEM,MAAM,CAAC,kCAAkC,CAAC,SAAiB;QAChE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1D,CAAC;CACF;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-place hash for API / personal access tokens.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Taken from AWS-AccessBridge's `TokenHashUtil.ts` — the only repo that factored
|
|
7
|
+
* token hashing behind a named helper (as opposed to calling `CryptoUtil.sha256Hex`
|
|
8
|
+
* inline, as Edge-Git does).
|
|
9
|
+
*
|
|
10
|
+
* A PAT is ~244 random bits from two UUIDs, so a plain SHA-256 hex digest is the
|
|
11
|
+
* right storage form: the input is not dictionary-searchable, so no salt or
|
|
12
|
+
* password KDF is needed, and the digest is indexable. Binding the digest for
|
|
13
|
+
* storage means a D1 read or backup no longer carries a live credential.
|
|
14
|
+
*
|
|
15
|
+
* ## Design decisions
|
|
16
|
+
*
|
|
17
|
+
* - `sha256Hex` **delegates to `CryptoUtil.sha256Hex`** rather than reimplementing
|
|
18
|
+
* the digest loop. AWS carried its own copy only because its `CryptoUtil` did
|
|
19
|
+
* not yet exist to delegate to; the kit's does, and a second SHA-256
|
|
20
|
+
* implementation is exactly the drift this package removes.
|
|
21
|
+
* - **No timing-safe compare is shipped.** The brief says to keep an
|
|
22
|
+
* HMAC/TimingSafe helper "if present"; none of the source repos carries one —
|
|
23
|
+
* token verification compares two SHA-256 *hex strings*, and nothing in the
|
|
24
|
+
* repos does it in constant time. Rather than invent a security primitive here
|
|
25
|
+
* that has no provenance, the helper is left out; a verifier that must compare a
|
|
26
|
+
* presented token's digest against a stored one does the byte-wise, length-
|
|
27
|
+
* independent compare itself (a `crypto.subtle`-backed constant-time compare
|
|
28
|
+
* belongs with the caller's chosen error mapping).
|
|
29
|
+
*/
|
|
30
|
+
declare class TokenHashUtil {
|
|
31
|
+
static sha256Hex(token: string): Promise<string>;
|
|
32
|
+
}
|
|
33
|
+
export { TokenHashUtil };
|
|
34
|
+
//# sourceMappingURL=TokenHashUtil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenHashUtil.d.ts","sourceRoot":"","sources":["../src/TokenHashUtil.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,cAAM,aAAa;WACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAG9D;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CryptoUtil } from './CryptoUtil';
|
|
2
|
+
/**
|
|
3
|
+
* The one-place hash for API / personal access tokens.
|
|
4
|
+
*
|
|
5
|
+
* ## Provenance
|
|
6
|
+
*
|
|
7
|
+
* Taken from AWS-AccessBridge's `TokenHashUtil.ts` — the only repo that factored
|
|
8
|
+
* token hashing behind a named helper (as opposed to calling `CryptoUtil.sha256Hex`
|
|
9
|
+
* inline, as Edge-Git does).
|
|
10
|
+
*
|
|
11
|
+
* A PAT is ~244 random bits from two UUIDs, so a plain SHA-256 hex digest is the
|
|
12
|
+
* right storage form: the input is not dictionary-searchable, so no salt or
|
|
13
|
+
* password KDF is needed, and the digest is indexable. Binding the digest for
|
|
14
|
+
* storage means a D1 read or backup no longer carries a live credential.
|
|
15
|
+
*
|
|
16
|
+
* ## Design decisions
|
|
17
|
+
*
|
|
18
|
+
* - `sha256Hex` **delegates to `CryptoUtil.sha256Hex`** rather than reimplementing
|
|
19
|
+
* the digest loop. AWS carried its own copy only because its `CryptoUtil` did
|
|
20
|
+
* not yet exist to delegate to; the kit's does, and a second SHA-256
|
|
21
|
+
* implementation is exactly the drift this package removes.
|
|
22
|
+
* - **No timing-safe compare is shipped.** The brief says to keep an
|
|
23
|
+
* HMAC/TimingSafe helper "if present"; none of the source repos carries one —
|
|
24
|
+
* token verification compares two SHA-256 *hex strings*, and nothing in the
|
|
25
|
+
* repos does it in constant time. Rather than invent a security primitive here
|
|
26
|
+
* that has no provenance, the helper is left out; a verifier that must compare a
|
|
27
|
+
* presented token's digest against a stored one does the byte-wise, length-
|
|
28
|
+
* independent compare itself (a `crypto.subtle`-backed constant-time compare
|
|
29
|
+
* belongs with the caller's chosen error mapping).
|
|
30
|
+
*/
|
|
31
|
+
class TokenHashUtil {
|
|
32
|
+
static async sha256Hex(token) {
|
|
33
|
+
return CryptoUtil.sha256Hex(token);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export { TokenHashUtil };
|
|
37
|
+
//# sourceMappingURL=TokenHashUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenHashUtil.js","sourceRoot":"","sources":["../src/TokenHashUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,aAAa;IACV,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,KAAa;QACzC,OAAO,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;CACF;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primary keys.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from `UUIDUtil.ts` in Durable-DAV, AWS-AccessBridge, Edge-Git,
|
|
7
|
+
* Mail-Otter (a re-export of the DAV form under the name `UIDUtility`), the
|
|
8
|
+
* Router, CalDAV-Bridge, Mail-Meow and Edge-Sonic. Four carried the bare
|
|
9
|
+
* `getRandomUUID`; Edge-Git and AWS added `getRandomUUIDNoDash`; Edge-Sonic added
|
|
10
|
+
* the derived `deterministicId`. ChordDHT-Tracker has no UUIDUtil (its ids go
|
|
11
|
+
* through `IdGenerator`).
|
|
12
|
+
*
|
|
13
|
+
* ## Design decision
|
|
14
|
+
*
|
|
15
|
+
* The whole requirement for a row whose id never appears on the wire is a v4 UUID
|
|
16
|
+
* in canonical dashed form, so `getRandomUUID` is the base. `getRandomUUIDNoDash`
|
|
17
|
+
* is kept from the AWS/Edge-Git form (two repos independently needed the compact
|
|
18
|
+
* spelling). `deterministicId` is Edge-Sonic's and is the single most valuable
|
|
19
|
+
* member — see its doc comment for why length-prefixing the parts is load-bearing.
|
|
20
|
+
*/
|
|
21
|
+
declare class UUIDUtil {
|
|
22
|
+
static getRandomUUID(): string;
|
|
23
|
+
/**
|
|
24
|
+
* The canonical form with the dashes removed, for a context that wants a
|
|
25
|
+
* compact, dash-free hex-ish token.
|
|
26
|
+
*/
|
|
27
|
+
static getRandomUUIDNoDash(): string;
|
|
28
|
+
/**
|
|
29
|
+
* A UUID-shaped id derived from a namespace and its parts, stable across runs.
|
|
30
|
+
*
|
|
31
|
+
* ### Why this exists
|
|
32
|
+
*
|
|
33
|
+
* **Because "create this" is not idempotent.** A Cloudflare Workflow step is
|
|
34
|
+
* cached by name and may be retried, so an import that mints a fresh id per
|
|
35
|
+
* attempt writes the *same row twice* on every retry — and a user who pressed
|
|
36
|
+
* the button three times finds four copies. `getRandomUUID` is right for the
|
|
37
|
+
* path where each create really is a new row, and wrong for an import, where a
|
|
38
|
+
* retried step is the *same* row.
|
|
39
|
+
*
|
|
40
|
+
* So an imported row's id is a function of `(namespace, ...parts)`: a retry
|
|
41
|
+
* resolves the same id, the write rewrites it, and the outcome does not depend
|
|
42
|
+
* on how many times the step ran.
|
|
43
|
+
*
|
|
44
|
+
* ### SHA-256, and why not MD5
|
|
45
|
+
*
|
|
46
|
+
* MD5 exists in one repo for a token scheme that specifies it, but this is a
|
|
47
|
+
* collision question with a consequence: two different inputs sharing an id
|
|
48
|
+
* means one overwrites the other, and MD5's collision resistance is not
|
|
49
|
+
* something to lean on when `crypto.subtle` is available. 128 bits of SHA-256
|
|
50
|
+
* is used, in canonical dashed form so the value is a UUID by inspection rather
|
|
51
|
+
* than 32 hex characters used as one.
|
|
52
|
+
*
|
|
53
|
+
* ### Every part is **length-prefixed**, because a separator proves nothing
|
|
54
|
+
*
|
|
55
|
+
* The obvious implementation joins the parts with a delimiter, and the obvious
|
|
56
|
+
* claim is that the delimiter cannot appear inside a part. That claim is
|
|
57
|
+
* **false**: joining `[ns, a, b]` and joining `[ns, a\u0000b]` both produce the
|
|
58
|
+
* same string, so a delimiter — *any* delimiter — is forgeable by choosing a
|
|
59
|
+
* part containing it. Two different inputs then hash to one id, and one
|
|
60
|
+
* overwrites the other.
|
|
61
|
+
*
|
|
62
|
+
* So each part contributes its **length** before its bytes: `3:ns1:a1:b`. That
|
|
63
|
+
* encoding is injective, because the length is recoverable from the bytes
|
|
64
|
+
* themselves — there is exactly one way to parse it — so no choice of parts
|
|
65
|
+
* produces the same string.
|
|
66
|
+
*/
|
|
67
|
+
static deterministicId(namespace: string, ...parts: readonly string[]): Promise<string>;
|
|
68
|
+
}
|
|
69
|
+
export { UUIDUtil };
|
|
70
|
+
//# sourceMappingURL=UUIDUtil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UUIDUtil.d.ts","sourceRoot":"","sources":["../src/UUIDUtil.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,cAAM,QAAQ;WACE,aAAa,IAAI,MAAM;IAIrC;;;OAGG;WACW,mBAAmB,IAAI,MAAM;IAI3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;WACiB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAMrG;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/UUIDUtil.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primary keys.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from `UUIDUtil.ts` in Durable-DAV, AWS-AccessBridge, Edge-Git,
|
|
7
|
+
* Mail-Otter (a re-export of the DAV form under the name `UIDUtility`), the
|
|
8
|
+
* Router, CalDAV-Bridge, Mail-Meow and Edge-Sonic. Four carried the bare
|
|
9
|
+
* `getRandomUUID`; Edge-Git and AWS added `getRandomUUIDNoDash`; Edge-Sonic added
|
|
10
|
+
* the derived `deterministicId`. ChordDHT-Tracker has no UUIDUtil (its ids go
|
|
11
|
+
* through `IdGenerator`).
|
|
12
|
+
*
|
|
13
|
+
* ## Design decision
|
|
14
|
+
*
|
|
15
|
+
* The whole requirement for a row whose id never appears on the wire is a v4 UUID
|
|
16
|
+
* in canonical dashed form, so `getRandomUUID` is the base. `getRandomUUIDNoDash`
|
|
17
|
+
* is kept from the AWS/Edge-Git form (two repos independently needed the compact
|
|
18
|
+
* spelling). `deterministicId` is Edge-Sonic's and is the single most valuable
|
|
19
|
+
* member — see its doc comment for why length-prefixing the parts is load-bearing.
|
|
20
|
+
*/
|
|
21
|
+
class UUIDUtil {
|
|
22
|
+
static getRandomUUID() {
|
|
23
|
+
return crypto.randomUUID();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The canonical form with the dashes removed, for a context that wants a
|
|
27
|
+
* compact, dash-free hex-ish token.
|
|
28
|
+
*/
|
|
29
|
+
static getRandomUUIDNoDash() {
|
|
30
|
+
return crypto.randomUUID().replaceAll('-', '');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A UUID-shaped id derived from a namespace and its parts, stable across runs.
|
|
34
|
+
*
|
|
35
|
+
* ### Why this exists
|
|
36
|
+
*
|
|
37
|
+
* **Because "create this" is not idempotent.** A Cloudflare Workflow step is
|
|
38
|
+
* cached by name and may be retried, so an import that mints a fresh id per
|
|
39
|
+
* attempt writes the *same row twice* on every retry — and a user who pressed
|
|
40
|
+
* the button three times finds four copies. `getRandomUUID` is right for the
|
|
41
|
+
* path where each create really is a new row, and wrong for an import, where a
|
|
42
|
+
* retried step is the *same* row.
|
|
43
|
+
*
|
|
44
|
+
* So an imported row's id is a function of `(namespace, ...parts)`: a retry
|
|
45
|
+
* resolves the same id, the write rewrites it, and the outcome does not depend
|
|
46
|
+
* on how many times the step ran.
|
|
47
|
+
*
|
|
48
|
+
* ### SHA-256, and why not MD5
|
|
49
|
+
*
|
|
50
|
+
* MD5 exists in one repo for a token scheme that specifies it, but this is a
|
|
51
|
+
* collision question with a consequence: two different inputs sharing an id
|
|
52
|
+
* means one overwrites the other, and MD5's collision resistance is not
|
|
53
|
+
* something to lean on when `crypto.subtle` is available. 128 bits of SHA-256
|
|
54
|
+
* is used, in canonical dashed form so the value is a UUID by inspection rather
|
|
55
|
+
* than 32 hex characters used as one.
|
|
56
|
+
*
|
|
57
|
+
* ### Every part is **length-prefixed**, because a separator proves nothing
|
|
58
|
+
*
|
|
59
|
+
* The obvious implementation joins the parts with a delimiter, and the obvious
|
|
60
|
+
* claim is that the delimiter cannot appear inside a part. That claim is
|
|
61
|
+
* **false**: joining `[ns, a, b]` and joining `[ns, a\u0000b]` both produce the
|
|
62
|
+
* same string, so a delimiter — *any* delimiter — is forgeable by choosing a
|
|
63
|
+
* part containing it. Two different inputs then hash to one id, and one
|
|
64
|
+
* overwrites the other.
|
|
65
|
+
*
|
|
66
|
+
* So each part contributes its **length** before its bytes: `3:ns1:a1:b`. That
|
|
67
|
+
* encoding is injective, because the length is recoverable from the bytes
|
|
68
|
+
* themselves — there is exactly one way to parse it — so no choice of parts
|
|
69
|
+
* produces the same string.
|
|
70
|
+
*/
|
|
71
|
+
static async deterministicId(namespace, ...parts) {
|
|
72
|
+
const payload = [namespace, ...parts].map((part) => `${part.length}:${part}`).join('');
|
|
73
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(payload));
|
|
74
|
+
const hex = Array.from(new Uint8Array(digest).slice(0, 16), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
75
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export { UUIDUtil };
|
|
79
|
+
//# sourceMappingURL=UUIDUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UUIDUtil.js","sourceRoot":"","sources":["../src/UUIDUtil.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,QAAQ;IACL,MAAM,CAAC,aAAa;QACzB,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,mBAAmB;QAC/B,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACI,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,SAAiB,EAAE,GAAG,KAAwB;QAChF,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QACxF,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAU,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3H,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IACjH,CAAC;CACF;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@rexezuge/shared` — the converged, platform-generic utilities distilled from
|
|
3
|
+
* the nine Rexezuge Cloudflare Worker monorepos. One concept per file; this barrel
|
|
4
|
+
* only re-exports. Business-specific helpers (money, git oids, calendar sync,
|
|
5
|
+
* credential sharding, …) are deliberately left in their consumer repos.
|
|
6
|
+
*
|
|
7
|
+
* `isValidEmailFormat` / `MAX_EMAIL_LENGTH` are defined in `Identity.ts` and
|
|
8
|
+
* re-exported by `EmailUtil.ts`; the barrel sources each name **once** (from
|
|
9
|
+
* `EmailUtil`, the one-stop email surface) so an `export *` merge cannot silently
|
|
10
|
+
* drop them to ambiguity.
|
|
11
|
+
*/
|
|
12
|
+
export { bytesToBase64, base64ToBytes } from './Base64';
|
|
13
|
+
export { SystemClock, FixedClock, SYSTEM_CLOCK } from './Clock';
|
|
14
|
+
export type { Clock } from './Clock';
|
|
15
|
+
export { TimestampUtil } from './TimestampUtil';
|
|
16
|
+
export { TimeZoneUtil, DEFAULT_TIME_ZONE } from './TimeZoneUtil';
|
|
17
|
+
export { LocaleUtil, DEFAULT_LOCALE, SUPPORTED_LOCALES, LOCALE_DISPLAY_NAMES } from './LocaleUtil';
|
|
18
|
+
export type { SupportedLocale } from './LocaleUtil';
|
|
19
|
+
export { canonicalizeLanguageTag } from './LanguageTag';
|
|
20
|
+
export type { LanguageTag } from './LanguageTag';
|
|
21
|
+
export { normalizeEmail, isValidEmailFormat, MAX_EMAIL_LENGTH } from './EmailUtil';
|
|
22
|
+
export { EMAIL_FORMAT_RE } from './Identity';
|
|
23
|
+
export { UUIDUtil } from './UUIDUtil';
|
|
24
|
+
export { CryptoIdGenerator, FixedIdGenerator } from './IdGenerator';
|
|
25
|
+
export type { IdGenerator } from './IdGenerator';
|
|
26
|
+
export { CryptoUtil } from './CryptoUtil';
|
|
27
|
+
export { ErrorSanitizationUtil } from './ErrorSanitizationUtil';
|
|
28
|
+
export { err, getOrThrow, isOk, mapResult, ok } from './Result';
|
|
29
|
+
export type { Err, Ok, Result } from './Result';
|
|
30
|
+
export { normalizeRemoteUrl, resolveRedirectUrl, parseAllowedHosts, isPrivateOrInternalHost, stripBrackets, stripTrailingDot, RemoteUrlRejectedError, MAX_REMOTE_URL_LENGTH, MAX_REDIRECTS, BLOCKED_HOST_SUFFIXES, BLOCKED_HOSTNAMES, } from './RemoteUrlPolicy';
|
|
31
|
+
export type { UrlPolicyOptions } from './RemoteUrlPolicy';
|
|
32
|
+
export { CursorCodec } from './CursorCodec';
|
|
33
|
+
export type { Page } from './CursorCodec';
|
|
34
|
+
export { SubrequestCounter, UNMETERED_SUBREQUESTS, SUBREQUEST_KINDS, subrequestSpend, NO_SUBREQUESTS_SPENT } from './SubrequestMeter';
|
|
35
|
+
export type { SubrequestMeter, SubrequestKind, SubrequestSpend } from './SubrequestMeter';
|
|
36
|
+
export { passwordFingerprint } from './PasswordFingerprint';
|
|
37
|
+
export { TokenHashUtil } from './TokenHashUtil';
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,YAAY,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAChE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGhD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACtI,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAG1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@rexezuge/shared` — the converged, platform-generic utilities distilled from
|
|
3
|
+
* the nine Rexezuge Cloudflare Worker monorepos. One concept per file; this barrel
|
|
4
|
+
* only re-exports. Business-specific helpers (money, git oids, calendar sync,
|
|
5
|
+
* credential sharding, …) are deliberately left in their consumer repos.
|
|
6
|
+
*
|
|
7
|
+
* `isValidEmailFormat` / `MAX_EMAIL_LENGTH` are defined in `Identity.ts` and
|
|
8
|
+
* re-exported by `EmailUtil.ts`; the barrel sources each name **once** (from
|
|
9
|
+
* `EmailUtil`, the one-stop email surface) so an `export *` merge cannot silently
|
|
10
|
+
* drop them to ambiguity.
|
|
11
|
+
*/
|
|
12
|
+
// Byte codecs.
|
|
13
|
+
export { bytesToBase64, base64ToBytes } from './Base64';
|
|
14
|
+
// Time.
|
|
15
|
+
export { SystemClock, FixedClock, SYSTEM_CLOCK } from './Clock';
|
|
16
|
+
export { TimestampUtil } from './TimestampUtil';
|
|
17
|
+
export { TimeZoneUtil, DEFAULT_TIME_ZONE } from './TimeZoneUtil';
|
|
18
|
+
// Locale.
|
|
19
|
+
export { LocaleUtil, DEFAULT_LOCALE, SUPPORTED_LOCALES, LOCALE_DISPLAY_NAMES } from './LocaleUtil';
|
|
20
|
+
export { canonicalizeLanguageTag } from './LanguageTag';
|
|
21
|
+
// Identity + email.
|
|
22
|
+
export { normalizeEmail, isValidEmailFormat, MAX_EMAIL_LENGTH } from './EmailUtil';
|
|
23
|
+
export { EMAIL_FORMAT_RE } from './Identity';
|
|
24
|
+
// Identifiers.
|
|
25
|
+
export { UUIDUtil } from './UUIDUtil';
|
|
26
|
+
export { CryptoIdGenerator, FixedIdGenerator } from './IdGenerator';
|
|
27
|
+
// Crypto + secrets.
|
|
28
|
+
export { CryptoUtil } from './CryptoUtil';
|
|
29
|
+
// Error handling.
|
|
30
|
+
export { ErrorSanitizationUtil } from './ErrorSanitizationUtil';
|
|
31
|
+
export { err, getOrThrow, isOk, mapResult, ok } from './Result';
|
|
32
|
+
// Network policy.
|
|
33
|
+
export { normalizeRemoteUrl, resolveRedirectUrl, parseAllowedHosts, isPrivateOrInternalHost, stripBrackets, stripTrailingDot, RemoteUrlRejectedError, MAX_REMOTE_URL_LENGTH, MAX_REDIRECTS, BLOCKED_HOST_SUFFIXES, BLOCKED_HOSTNAMES, } from './RemoteUrlPolicy';
|
|
34
|
+
// Pagination.
|
|
35
|
+
export { CursorCodec } from './CursorCodec';
|
|
36
|
+
// Cloudflare subrequest accounting.
|
|
37
|
+
export { SubrequestCounter, UNMETERED_SUBREQUESTS, SUBREQUEST_KINDS, subrequestSpend, NO_SUBREQUESTS_SPENT } from './SubrequestMeter';
|
|
38
|
+
// Auth-side memo helper.
|
|
39
|
+
export { passwordFingerprint } from './PasswordFingerprint';
|
|
40
|
+
// Token hashing.
|
|
41
|
+
export { TokenHashUtil } from './TokenHashUtil';
|
|
42
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAe;AACf,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAExD,QAAQ;AACR,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjE,UAAU;AACV,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEnG,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGxD,oBAAoB;AACpB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAe;AACf,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGpE,oBAAoB;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,kBAAkB;AAClB,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAGhE,kBAAkB;AAClB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAG3B,cAAc;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,oCAAoC;AACpC,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGtI,yBAAyB;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,iBAAiB;AACjB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rexezuge/shared",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Rexezuge-CloudflareWorkers/SDK.git",
|
|
9
|
+
"directory": "packages/shared"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./*": {
|
|
20
|
+
"types": "./dist/*.d.ts",
|
|
21
|
+
"default": "./dist/*.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "26.6.4",
|
|
29
|
+
"typescript": "6.0.3",
|
|
30
|
+
"vitest": "4.1.11"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
6
37
|
}
|
package/README.md
DELETED