@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rexezuge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/Base64.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64 codec for byte payloads.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from `packages/shared/src/utils/Base64.ts` (Durable-DAV), the
|
|
7
|
+
* documented best form. Four implementations once existed — Durable-DAV's own,
|
|
8
|
+
* `DavReadCache`, and `aes-gcm` running the `String.fromCodePoint(...subarray)`
|
|
9
|
+
* + `btoa` form this file was written to replace. ChordDHT-Tracker and
|
|
10
|
+
* Mail-Otter carry no byte codec of their own; their `CryptoUtil`s use the
|
|
11
|
+
* `btoa`+transform form. This file is the canonical one for **standard**
|
|
12
|
+
* base64. The **base64url** alphabet is a different job and lives in
|
|
13
|
+
* `CryptoUtil` — that duplication is deliberate (different alphabet).
|
|
14
|
+
*
|
|
15
|
+
* ## Why not `String.fromCodePoint` + `btoa`
|
|
16
|
+
*
|
|
17
|
+
* The obvious encoder builds a binary string and `btoa`s it:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* let binary = '';
|
|
21
|
+
* for (let i = 0; i < bytes.length; i += 8192) {
|
|
22
|
+
* binary += String.fromCodePoint(...bytes.subarray(i, i + 8192));
|
|
23
|
+
* }
|
|
24
|
+
* return btoa(binary);
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* That allocates roughly 2× the input as UTF-16 — up to 100 MB of garbage for a
|
|
28
|
+
* 50 MB payload, on top of the buffer and the result — and the variadic spread
|
|
29
|
+
* approaches the engine's argument limit. A direct 3-byte-to-4-char table is
|
|
30
|
+
* allocation-light and has no argument limit. `base64ToBytes` keeps the `atob`
|
|
31
|
+
* form: decoding allocates one binary string and one buffer, which is the
|
|
32
|
+
* output's own size rather than double it, and `atob` does the validation for
|
|
33
|
+
* free.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Encode `bytes` as standard base64 (with `=` padding).
|
|
37
|
+
*
|
|
38
|
+
* Not base64url: pre-binary KV file entries stored these as opaque values and
|
|
39
|
+
* `aes-gcm` stores them where `atob` reads them back. `CryptoUtil` has the
|
|
40
|
+
* URL-safe variant for the cases that need one.
|
|
41
|
+
*/
|
|
42
|
+
declare function bytesToBase64(bytes: Uint8Array): string;
|
|
43
|
+
/**
|
|
44
|
+
* Decode standard base64.
|
|
45
|
+
*
|
|
46
|
+
* `atob` throws on malformed input rather than producing partial bytes, which is
|
|
47
|
+
* the right behaviour for every caller here: a corrupt cache entry or a mistyped
|
|
48
|
+
* key should fail loudly, not decrypt to noise.
|
|
49
|
+
*/
|
|
50
|
+
declare function base64ToBytes(b64: string): Uint8Array;
|
|
51
|
+
export { bytesToBase64, base64ToBytes };
|
|
52
|
+
//# sourceMappingURL=Base64.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Base64.d.ts","sourceRoot":"","sources":["../src/Base64.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH;;;;;;GAMG;AACH,iBAAS,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAqBhD;AAED;;;;;;GAMG;AACH,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAK9C;AAED,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/Base64.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64 codec for byte payloads.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from `packages/shared/src/utils/Base64.ts` (Durable-DAV), the
|
|
7
|
+
* documented best form. Four implementations once existed — Durable-DAV's own,
|
|
8
|
+
* `DavReadCache`, and `aes-gcm` running the `String.fromCodePoint(...subarray)`
|
|
9
|
+
* + `btoa` form this file was written to replace. ChordDHT-Tracker and
|
|
10
|
+
* Mail-Otter carry no byte codec of their own; their `CryptoUtil`s use the
|
|
11
|
+
* `btoa`+transform form. This file is the canonical one for **standard**
|
|
12
|
+
* base64. The **base64url** alphabet is a different job and lives in
|
|
13
|
+
* `CryptoUtil` — that duplication is deliberate (different alphabet).
|
|
14
|
+
*
|
|
15
|
+
* ## Why not `String.fromCodePoint` + `btoa`
|
|
16
|
+
*
|
|
17
|
+
* The obvious encoder builds a binary string and `btoa`s it:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* let binary = '';
|
|
21
|
+
* for (let i = 0; i < bytes.length; i += 8192) {
|
|
22
|
+
* binary += String.fromCodePoint(...bytes.subarray(i, i + 8192));
|
|
23
|
+
* }
|
|
24
|
+
* return btoa(binary);
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* That allocates roughly 2× the input as UTF-16 — up to 100 MB of garbage for a
|
|
28
|
+
* 50 MB payload, on top of the buffer and the result — and the variadic spread
|
|
29
|
+
* approaches the engine's argument limit. A direct 3-byte-to-4-char table is
|
|
30
|
+
* allocation-light and has no argument limit. `base64ToBytes` keeps the `atob`
|
|
31
|
+
* form: decoding allocates one binary string and one buffer, which is the
|
|
32
|
+
* output's own size rather than double it, and `atob` does the validation for
|
|
33
|
+
* free.
|
|
34
|
+
*/
|
|
35
|
+
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
36
|
+
/**
|
|
37
|
+
* Encode `bytes` as standard base64 (with `=` padding).
|
|
38
|
+
*
|
|
39
|
+
* Not base64url: pre-binary KV file entries stored these as opaque values and
|
|
40
|
+
* `aes-gcm` stores them where `atob` reads them back. `CryptoUtil` has the
|
|
41
|
+
* URL-safe variant for the cases that need one.
|
|
42
|
+
*/
|
|
43
|
+
function bytesToBase64(bytes) {
|
|
44
|
+
let out = '';
|
|
45
|
+
const len = bytes.length;
|
|
46
|
+
const remainder = len % 3;
|
|
47
|
+
const limit = len - remainder;
|
|
48
|
+
for (let i = 0; i < limit; i += 3) {
|
|
49
|
+
const triple = bytes[i] * 65_536 + bytes[i + 1] * 256 + bytes[i + 2];
|
|
50
|
+
out +=
|
|
51
|
+
BASE64_ALPHABET[(triple >> 18) & 63] +
|
|
52
|
+
BASE64_ALPHABET[(triple >> 12) & 63] +
|
|
53
|
+
BASE64_ALPHABET[(triple >> 6) & 63] +
|
|
54
|
+
BASE64_ALPHABET[triple & 63];
|
|
55
|
+
}
|
|
56
|
+
if (remainder === 1) {
|
|
57
|
+
const value = bytes[limit];
|
|
58
|
+
out += `${BASE64_ALPHABET[value >> 2]}${BASE64_ALPHABET[(value << 4) & 63]}==`;
|
|
59
|
+
}
|
|
60
|
+
else if (remainder === 2) {
|
|
61
|
+
const pair = (bytes[limit] << 8) | bytes[limit + 1];
|
|
62
|
+
out += `${BASE64_ALPHABET[pair >> 10]}${BASE64_ALPHABET[(pair >> 4) & 63]}${BASE64_ALPHABET[(pair << 2) & 63]}=`;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Decode standard base64.
|
|
68
|
+
*
|
|
69
|
+
* `atob` throws on malformed input rather than producing partial bytes, which is
|
|
70
|
+
* the right behaviour for every caller here: a corrupt cache entry or a mistyped
|
|
71
|
+
* key should fail loudly, not decrypt to noise.
|
|
72
|
+
*/
|
|
73
|
+
function base64ToBytes(b64) {
|
|
74
|
+
const binary = atob(b64);
|
|
75
|
+
const out = new Uint8Array(binary.length);
|
|
76
|
+
for (let i = 0; i < binary.length; i += 1)
|
|
77
|
+
out[i] = (binary.codePointAt(i) ?? 0) & 0xff;
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
export { bytesToBase64, base64ToBytes };
|
|
81
|
+
//# sourceMappingURL=Base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Base64.js","sourceRoot":"","sources":["../src/Base64.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,eAAe,GAAG,kEAAkE,CAAC;AAE3F;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;IACzB,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,GAAG,GAAG,SAAS,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrE,GAAG;YACD,eAAe,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;gBACpC,eAAe,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;gBACpC,eAAe,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;gBACnC,eAAe,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3B,GAAG,IAAI,GAAG,eAAe,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;IACjF,CAAC;SAAM,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACpD,GAAG,IAAI,GAAG,eAAe,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC;IACnH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IACxF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/Clock.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inject-able time source.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from AWS-AccessBridge's `Clock.ts`, Mail-Otter's `Clock.ts` and
|
|
7
|
+
* ChordDHT-Tracker's `Clock.ts`. The three differ in surface, not concept: AWS
|
|
8
|
+
* names the seam `Clock` with `now(): number` and a shared `SYSTEM_CLOCK`;
|
|
9
|
+
* Mail-Otter/ChordDHT name it `IClock` with `nowMs()` and a mutable
|
|
10
|
+
* `FixedClock` carrying `setNowMs`/`advanceByMs`.
|
|
11
|
+
*
|
|
12
|
+
* ## Design decision
|
|
13
|
+
*
|
|
14
|
+
* The kit drops the `I` prefix on the interface (no `any`, no Hungarian drift;
|
|
15
|
+
* the consumer repos' own "no `I` on interfaces" cleanups point the same way),
|
|
16
|
+
* keeps AWS's single-method `now()` as the primary read, and keeps Mail-Otter's
|
|
17
|
+
* mutable `FixedClock` helpers — they cost nothing and are exactly what a test
|
|
18
|
+
* wants. `TimestampUtil`'s current-time reads go through `SYSTEM_CLOCK` by
|
|
19
|
+
* default; pass a `FixedClock` to pin them.
|
|
20
|
+
*/
|
|
21
|
+
interface Clock {
|
|
22
|
+
/**
|
|
23
|
+
* Milliseconds since the Unix epoch.
|
|
24
|
+
*/
|
|
25
|
+
now(): number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The production clock. A single shared instance — it holds no state, so there
|
|
29
|
+
* is nothing to gain from more than one, and a default parameter needs a value.
|
|
30
|
+
*/
|
|
31
|
+
declare class SystemClock implements Clock {
|
|
32
|
+
now(): number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A clock seeded at `initialNowMs` and moved forward with `setNowMs`/`advanceByMs`.
|
|
36
|
+
*
|
|
37
|
+
* Two shapes were merged: AWS's `FixedClock` was immutable (constructed with the
|
|
38
|
+
* instant under test), Mail-Otter's was mutable. The mutable form is a superset
|
|
39
|
+
* and is what the time-dependent tests in every repo actually reach for, so a
|
|
40
|
+
* test either pins the instant up front or advances it as work happens.
|
|
41
|
+
*/
|
|
42
|
+
declare class FixedClock implements Clock {
|
|
43
|
+
private currentMs;
|
|
44
|
+
constructor(initialNowMs?: number);
|
|
45
|
+
now(): number;
|
|
46
|
+
setNowMs(valueMs: number): void;
|
|
47
|
+
advanceByMs(deltaMs: number): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The shared production instance, for the common case where a caller wants "now"
|
|
51
|
+
* without choosing a clock. Stateless, so one instance is enough.
|
|
52
|
+
*/
|
|
53
|
+
declare const SYSTEM_CLOCK: Clock;
|
|
54
|
+
export type { Clock };
|
|
55
|
+
export { SystemClock, FixedClock, SYSTEM_CLOCK };
|
|
56
|
+
//# sourceMappingURL=Clock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Clock.d.ts","sourceRoot":"","sources":["../src/Clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,UAAU,KAAK;IACb;;OAEG;IACH,GAAG,IAAI,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,cAAM,WAAY,YAAW,KAAK;IAChC,GAAG,IAAI,MAAM;CAGd;AAED;;;;;;;GAOG;AACH,cAAM,UAAW,YAAW,KAAK;IAC/B,OAAO,CAAC,SAAS,CAAS;gBAEd,YAAY,SAAI;IAI5B,GAAG,IAAI,MAAM;IAIb,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI/B,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAGnC;AAED;;;GAGG;AACH,QAAA,MAAM,YAAY,EAAE,KAAyB,CAAC;AAE9C,YAAY,EAAE,KAAK,EAAE,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/Clock.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inject-able time source.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from AWS-AccessBridge's `Clock.ts`, Mail-Otter's `Clock.ts` and
|
|
7
|
+
* ChordDHT-Tracker's `Clock.ts`. The three differ in surface, not concept: AWS
|
|
8
|
+
* names the seam `Clock` with `now(): number` and a shared `SYSTEM_CLOCK`;
|
|
9
|
+
* Mail-Otter/ChordDHT name it `IClock` with `nowMs()` and a mutable
|
|
10
|
+
* `FixedClock` carrying `setNowMs`/`advanceByMs`.
|
|
11
|
+
*
|
|
12
|
+
* ## Design decision
|
|
13
|
+
*
|
|
14
|
+
* The kit drops the `I` prefix on the interface (no `any`, no Hungarian drift;
|
|
15
|
+
* the consumer repos' own "no `I` on interfaces" cleanups point the same way),
|
|
16
|
+
* keeps AWS's single-method `now()` as the primary read, and keeps Mail-Otter's
|
|
17
|
+
* mutable `FixedClock` helpers — they cost nothing and are exactly what a test
|
|
18
|
+
* wants. `TimestampUtil`'s current-time reads go through `SYSTEM_CLOCK` by
|
|
19
|
+
* default; pass a `FixedClock` to pin them.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The production clock. A single shared instance — it holds no state, so there
|
|
23
|
+
* is nothing to gain from more than one, and a default parameter needs a value.
|
|
24
|
+
*/
|
|
25
|
+
class SystemClock {
|
|
26
|
+
now() {
|
|
27
|
+
return Date.now();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A clock seeded at `initialNowMs` and moved forward with `setNowMs`/`advanceByMs`.
|
|
32
|
+
*
|
|
33
|
+
* Two shapes were merged: AWS's `FixedClock` was immutable (constructed with the
|
|
34
|
+
* instant under test), Mail-Otter's was mutable. The mutable form is a superset
|
|
35
|
+
* and is what the time-dependent tests in every repo actually reach for, so a
|
|
36
|
+
* test either pins the instant up front or advances it as work happens.
|
|
37
|
+
*/
|
|
38
|
+
class FixedClock {
|
|
39
|
+
currentMs;
|
|
40
|
+
constructor(initialNowMs = 0) {
|
|
41
|
+
this.currentMs = initialNowMs;
|
|
42
|
+
}
|
|
43
|
+
now() {
|
|
44
|
+
return this.currentMs;
|
|
45
|
+
}
|
|
46
|
+
setNowMs(valueMs) {
|
|
47
|
+
this.currentMs = valueMs;
|
|
48
|
+
}
|
|
49
|
+
advanceByMs(deltaMs) {
|
|
50
|
+
this.currentMs += deltaMs;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The shared production instance, for the common case where a caller wants "now"
|
|
55
|
+
* without choosing a clock. Stateless, so one instance is enough.
|
|
56
|
+
*/
|
|
57
|
+
const SYSTEM_CLOCK = new SystemClock();
|
|
58
|
+
export { SystemClock, FixedClock, SYSTEM_CLOCK };
|
|
59
|
+
//# sourceMappingURL=Clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Clock.js","sourceRoot":"","sources":["../src/Clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH;;;GAGG;AACH,MAAM,WAAW;IACf,GAAG;QACD,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU;IACN,SAAS,CAAS;IAE1B,YAAY,YAAY,GAAG,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,YAAY,CAAC;IAChC,CAAC;IAED,GAAG;QACD,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,QAAQ,CAAC,OAAe;QACtB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC;IAC3B,CAAC;IAED,WAAW,CAAC,OAAe;QACzB,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC;IAC5B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,YAAY,GAAU,IAAI,WAAW,EAAE,CAAC;AAG9C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashing and key generation.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from `CryptoUtil.ts` in Durable-DAV, Edge-Git, Mail-Meow,
|
|
7
|
+
* Mail-Otter, ChordDHT-Tracker and CalDAV-Bridge. Every copy carries
|
|
8
|
+
* `sha256Hex` and `randomBase64Url`; Edge-Git/Mail-Meow/Mail-Otter/ChordDHT add
|
|
9
|
+
* `hmacSha256Hex`; Mail-Meow adds the string overload `base64UrlEncode`;
|
|
10
|
+
* Durable-DAV uniquely factors the digest over raw bytes (`sha256HexOfBytes`) and
|
|
11
|
+
* rewrites base64url on the shared codec.
|
|
12
|
+
*
|
|
13
|
+
* ## Design decisions
|
|
14
|
+
*
|
|
15
|
+
* - The base64url alphabet is a **different alphabet for a different job** from
|
|
16
|
+
* `Base64.ts`, so the URL-safe transform stays here — but it delegates to the
|
|
17
|
+
* documented best encoder rather than re-spreading bytes (`String.fromCodePoint`
|
|
18
|
+
* + `btoa`), which was four copies of the ~2×-UTF-16 form `Base64.ts` replaced.
|
|
19
|
+
* - `sha256Hex` is defined over `sha256HexOfBytes` (Durable-DAV's shape): the
|
|
20
|
+
* digest then covers exactly the bytes presented, which matters when two sides
|
|
21
|
+
* compare a digest — an intermediate encoding is a place for them to disagree.
|
|
22
|
+
* - `hmacSha256Hex` is kept from the four repos that carry it.
|
|
23
|
+
*
|
|
24
|
+
* ## Left local
|
|
25
|
+
*
|
|
26
|
+
* ChordDHT-Tracker's `sha1Hex` is deliberately **not** here. SHA-1 is the Chord
|
|
27
|
+
* ring identifier hash (protocol compatibility), not a general security digest;
|
|
28
|
+
* surfacing it in the shared surface would invite exactly the misuse the
|
|
29
|
+
* `sonarjs/hashing` lint it carries a disable for is there to catch. A repo that
|
|
30
|
+
* needs a protocol-mandated SHA-1 computes it on a `BufferSource` itself.
|
|
31
|
+
*/
|
|
32
|
+
declare class CryptoUtil {
|
|
33
|
+
/**
|
|
34
|
+
* SHA-256 of a string, hex-encoded.
|
|
35
|
+
*
|
|
36
|
+
* Encoded as UTF-8 first. For bytes you already hold, prefer `sha256HexOfBytes`
|
|
37
|
+
* — this overload is the reason a byte payload used to be laundered through
|
|
38
|
+
* `String.fromCodePoint` into a binary string and back, allocating roughly 2×
|
|
39
|
+
* the input as UTF-16 on the way.
|
|
40
|
+
*/
|
|
41
|
+
static sha256Hex(value: string): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* SHA-256 of raw bytes, hex-encoded.
|
|
44
|
+
*
|
|
45
|
+
* The hash covers exactly these bytes. This matters where the digest is
|
|
46
|
+
* compared between two sides: any intermediate encoding is a place for the two
|
|
47
|
+
* to disagree for a reason that has nothing to do with the content.
|
|
48
|
+
*/
|
|
49
|
+
static sha256HexOfBytes(bytes: Uint8Array): Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* HMAC-SHA256 of `value` under `secret`, hex-encoded.
|
|
52
|
+
*
|
|
53
|
+
* A keyed MAC for a message a caller will recompute on the other side. The key
|
|
54
|
+
* is imported as non-extractable; a comparison against a re-derived MAC must be
|
|
55
|
+
* timing-safe on the caller's side — this helper returns the hex string, it does
|
|
56
|
+
* not compare.
|
|
57
|
+
*/
|
|
58
|
+
static hmacSha256Hex(value: string, secret: string): Promise<string>;
|
|
59
|
+
private static toHex;
|
|
60
|
+
/**
|
|
61
|
+
* URL-safe base64 of `bytes` (no `+`, `/` or `=`).
|
|
62
|
+
*
|
|
63
|
+
* Delegates to the shared codec rather than rebuilding the binary string here:
|
|
64
|
+
* the URL-safe transform is applied to the standard alphabet because
|
|
65
|
+
* `Uint8Array#toBase64` is not guaranteed across Workers runtimes.
|
|
66
|
+
*/
|
|
67
|
+
static toBase64Url(bytes: Uint8Array): string;
|
|
68
|
+
/**
|
|
69
|
+
* URL-safe base64 of a UTF-8 string.
|
|
70
|
+
*
|
|
71
|
+
* The same transform as {@link toBase64Url}, for callers holding text rather
|
|
72
|
+
* than bytes. This and `toBase64Url` used to be separate implementations in
|
|
73
|
+
* different packages; they are the same encoding and must not drift.
|
|
74
|
+
*/
|
|
75
|
+
static base64UrlEncode(value: string): string;
|
|
76
|
+
static randomBase64Url(byteLength: number): string;
|
|
77
|
+
}
|
|
78
|
+
export { CryptoUtil };
|
|
79
|
+
//# sourceMappingURL=CryptoUtil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CryptoUtil.d.ts","sourceRoot":"","sources":["../src/CryptoUtil.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,cAAM,UAAU;IACd;;;;;;;OAOG;WACiB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7D;;;;;;OAMG;WACiB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAKxE;;;;;;;OAOG;WACiB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYjF,OAAO,CAAC,MAAM,CAAC,KAAK;IAIpB;;;;;;OAMG;WACW,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;IAIpD;;;;;;OAMG;WACW,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;WAItC,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAG1D;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { bytesToBase64 } from './Base64';
|
|
2
|
+
/**
|
|
3
|
+
* Hashing and key generation.
|
|
4
|
+
*
|
|
5
|
+
* ## Provenance
|
|
6
|
+
*
|
|
7
|
+
* Converged from `CryptoUtil.ts` in Durable-DAV, Edge-Git, Mail-Meow,
|
|
8
|
+
* Mail-Otter, ChordDHT-Tracker and CalDAV-Bridge. Every copy carries
|
|
9
|
+
* `sha256Hex` and `randomBase64Url`; Edge-Git/Mail-Meow/Mail-Otter/ChordDHT add
|
|
10
|
+
* `hmacSha256Hex`; Mail-Meow adds the string overload `base64UrlEncode`;
|
|
11
|
+
* Durable-DAV uniquely factors the digest over raw bytes (`sha256HexOfBytes`) and
|
|
12
|
+
* rewrites base64url on the shared codec.
|
|
13
|
+
*
|
|
14
|
+
* ## Design decisions
|
|
15
|
+
*
|
|
16
|
+
* - The base64url alphabet is a **different alphabet for a different job** from
|
|
17
|
+
* `Base64.ts`, so the URL-safe transform stays here — but it delegates to the
|
|
18
|
+
* documented best encoder rather than re-spreading bytes (`String.fromCodePoint`
|
|
19
|
+
* + `btoa`), which was four copies of the ~2×-UTF-16 form `Base64.ts` replaced.
|
|
20
|
+
* - `sha256Hex` is defined over `sha256HexOfBytes` (Durable-DAV's shape): the
|
|
21
|
+
* digest then covers exactly the bytes presented, which matters when two sides
|
|
22
|
+
* compare a digest — an intermediate encoding is a place for them to disagree.
|
|
23
|
+
* - `hmacSha256Hex` is kept from the four repos that carry it.
|
|
24
|
+
*
|
|
25
|
+
* ## Left local
|
|
26
|
+
*
|
|
27
|
+
* ChordDHT-Tracker's `sha1Hex` is deliberately **not** here. SHA-1 is the Chord
|
|
28
|
+
* ring identifier hash (protocol compatibility), not a general security digest;
|
|
29
|
+
* surfacing it in the shared surface would invite exactly the misuse the
|
|
30
|
+
* `sonarjs/hashing` lint it carries a disable for is there to catch. A repo that
|
|
31
|
+
* needs a protocol-mandated SHA-1 computes it on a `BufferSource` itself.
|
|
32
|
+
*/
|
|
33
|
+
class CryptoUtil {
|
|
34
|
+
/**
|
|
35
|
+
* SHA-256 of a string, hex-encoded.
|
|
36
|
+
*
|
|
37
|
+
* Encoded as UTF-8 first. For bytes you already hold, prefer `sha256HexOfBytes`
|
|
38
|
+
* — this overload is the reason a byte payload used to be laundered through
|
|
39
|
+
* `String.fromCodePoint` into a binary string and back, allocating roughly 2×
|
|
40
|
+
* the input as UTF-16 on the way.
|
|
41
|
+
*/
|
|
42
|
+
static async sha256Hex(value) {
|
|
43
|
+
return this.sha256HexOfBytes(new TextEncoder().encode(value));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* SHA-256 of raw bytes, hex-encoded.
|
|
47
|
+
*
|
|
48
|
+
* The hash covers exactly these bytes. This matters where the digest is
|
|
49
|
+
* compared between two sides: any intermediate encoding is a place for the two
|
|
50
|
+
* to disagree for a reason that has nothing to do with the content.
|
|
51
|
+
*/
|
|
52
|
+
static async sha256HexOfBytes(bytes) {
|
|
53
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
|
54
|
+
return this.toHex(new Uint8Array(digest));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* HMAC-SHA256 of `value` under `secret`, hex-encoded.
|
|
58
|
+
*
|
|
59
|
+
* A keyed MAC for a message a caller will recompute on the other side. The key
|
|
60
|
+
* is imported as non-extractable; a comparison against a re-derived MAC must be
|
|
61
|
+
* timing-safe on the caller's side — this helper returns the hex string, it does
|
|
62
|
+
* not compare.
|
|
63
|
+
*/
|
|
64
|
+
static async hmacSha256Hex(value, secret) {
|
|
65
|
+
const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
66
|
+
const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(value));
|
|
67
|
+
return this.toHex(new Uint8Array(signature));
|
|
68
|
+
}
|
|
69
|
+
static toHex(bytes) {
|
|
70
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* URL-safe base64 of `bytes` (no `+`, `/` or `=`).
|
|
74
|
+
*
|
|
75
|
+
* Delegates to the shared codec rather than rebuilding the binary string here:
|
|
76
|
+
* the URL-safe transform is applied to the standard alphabet because
|
|
77
|
+
* `Uint8Array#toBase64` is not guaranteed across Workers runtimes.
|
|
78
|
+
*/
|
|
79
|
+
static toBase64Url(bytes) {
|
|
80
|
+
return bytesToBase64(bytes).replaceAll('+', '-').replaceAll('/', '_').replace(/={0,2}$/, '');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* URL-safe base64 of a UTF-8 string.
|
|
84
|
+
*
|
|
85
|
+
* The same transform as {@link toBase64Url}, for callers holding text rather
|
|
86
|
+
* than bytes. This and `toBase64Url` used to be separate implementations in
|
|
87
|
+
* different packages; they are the same encoding and must not drift.
|
|
88
|
+
*/
|
|
89
|
+
static base64UrlEncode(value) {
|
|
90
|
+
return this.toBase64Url(new TextEncoder().encode(value));
|
|
91
|
+
}
|
|
92
|
+
static randomBase64Url(byteLength) {
|
|
93
|
+
return this.toBase64Url(crypto.getRandomValues(new Uint8Array(byteLength)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export { CryptoUtil };
|
|
97
|
+
//# sourceMappingURL=CryptoUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CryptoUtil.js","sourceRoot":"","sources":["../src/CryptoUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU;IACd;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,KAAa;QACzC,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAiB;QACpD,MAAM,MAAM,GAAgB,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAqB,CAAC,CAAC;QACzF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,KAAa,EAAE,MAAc;QAC7D,MAAM,GAAG,GAAc,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAClD,KAAK,EACL,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAChC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;QACF,MAAM,SAAS,GAAgB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtG,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/C,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,KAAiB;QACpC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,WAAW,CAAC,KAAiB;QACzC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,eAAe,CAAC,KAAa;QACzC,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,CAAC;IAEM,MAAM,CAAC,eAAe,CAAC,UAAkB;QAC9C,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;CACF;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical opaque-cursor codec, plus the `Page<T>` shape it produces.
|
|
3
|
+
*
|
|
4
|
+
* ## Provenance
|
|
5
|
+
*
|
|
6
|
+
* Converged from ChordDHT-Tracker's `utils/Cursor.ts` (a `Cursor` class + a
|
|
7
|
+
* `Page<T>` type), Edge-Git's `backend-data/.../CursorUtil.ts` (a `CursorUtil`
|
|
8
|
+
* class that adds `decodeOrThrow` + `isValidCursor`) and Mail-Otter's
|
|
9
|
+
* `CursorUtil.ts` (the minimal `encode`/`decode` pair). All three hand-rolled
|
|
10
|
+
* DAO pagination cursors and all three converged on `btoa(JSON.stringify(...))`
|
|
11
|
+
* / `JSON.parse(atob(...))`. Canonical name is **`CursorCodec`**; the cursor is an
|
|
12
|
+
* opaque token, so callers pass it through and never inspect it.
|
|
13
|
+
*
|
|
14
|
+
* ## Design decisions
|
|
15
|
+
*
|
|
16
|
+
* - The cursor is `btoa`/`atob`, which works in workerd *and* Node 24 (both
|
|
17
|
+
* expose browser globals now). No `Buffer` is used, so the same bundle runs in
|
|
18
|
+
* both.
|
|
19
|
+
* - `decode` never throws: a malformed or tampered cursor is `undefined`, which a
|
|
20
|
+
* DAO reads as "no cursor, start from the beginning" rather than an error an
|
|
21
|
+
* attacker can provoke. `decodeOrThrow` and `isValidCursor` are kept from
|
|
22
|
+
* Edge-Git for the call sites that *want* the strict behaviour.
|
|
23
|
+
* - **Layer 0 stays dependency-free.** Edge-Git's `decodeOrThrow` threw its
|
|
24
|
+
* `@edge-git/backend-errors` `BadRequestError`; the shared package imports
|
|
25
|
+
* nothing, so it throws a plain `Error` and leaves the HTTP mapping to the
|
|
26
|
+
* layer that owns the error taxonomy (the same "throw plain `Error`, map above"
|
|
27
|
+
* rule `Identity`/`EmailUtil` follow).
|
|
28
|
+
*/
|
|
29
|
+
interface Page<T> {
|
|
30
|
+
readonly items: readonly T[];
|
|
31
|
+
readonly nextCursor?: string;
|
|
32
|
+
}
|
|
33
|
+
declare class CursorCodec {
|
|
34
|
+
static encode(value: unknown): string;
|
|
35
|
+
/**
|
|
36
|
+
* Decode a cursor, or `undefined` for a missing/malformed one.
|
|
37
|
+
*
|
|
38
|
+
* `atob`/`JSON.parse` throw on bad input; the `try` turns that into `undefined`
|
|
39
|
+
* so a corrupt cursor degrades to a first page instead of an uncaught throw.
|
|
40
|
+
*/
|
|
41
|
+
static decode<T>(cursor: string | undefined | null): T | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Decode a cursor, throwing a plain `Error` on malformed input.
|
|
44
|
+
*
|
|
45
|
+
* For a call site that treats a bad cursor as a client error; the HTTP mapping
|
|
46
|
+
* to `BadRequest` is the caller's layer's job (see the file header).
|
|
47
|
+
*/
|
|
48
|
+
static decodeOrThrow<T>(cursor: string | undefined | null): T | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a cursor is well-formed. A missing cursor is valid (first page).
|
|
51
|
+
*/
|
|
52
|
+
static isValidCursor(cursor: string | undefined | null): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Build a `Page`, omitting `nextCursor` entirely on the last page rather than
|
|
55
|
+
* carrying an explicit `undefined`.
|
|
56
|
+
*/
|
|
57
|
+
static page<T>(items: readonly T[], nextCursor?: string): Page<T>;
|
|
58
|
+
}
|
|
59
|
+
export { CursorCodec };
|
|
60
|
+
export type { Page };
|
|
61
|
+
//# sourceMappingURL=CursorCodec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CursorCodec.d.ts","sourceRoot":"","sources":["../src/CursorCodec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,UAAU,IAAI,CAAC,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,cAAM,WAAW;WACD,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAI5C;;;;;OAKG;WACW,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,SAAS;IASzE;;;;;OAKG;WACW,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,SAAS;IAShF;;OAEG;WACW,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO;IAUvE;;;OAGG;WACW,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;CAGzE;AAED,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,YAAY,EAAE,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
class CursorCodec {
|
|
2
|
+
static encode(value) {
|
|
3
|
+
return btoa(JSON.stringify(value));
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Decode a cursor, or `undefined` for a missing/malformed one.
|
|
7
|
+
*
|
|
8
|
+
* `atob`/`JSON.parse` throw on bad input; the `try` turns that into `undefined`
|
|
9
|
+
* so a corrupt cursor degrades to a first page instead of an uncaught throw.
|
|
10
|
+
*/
|
|
11
|
+
static decode(cursor) {
|
|
12
|
+
if (!cursor)
|
|
13
|
+
return undefined;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(atob(cursor));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Decode a cursor, throwing a plain `Error` on malformed input.
|
|
23
|
+
*
|
|
24
|
+
* For a call site that treats a bad cursor as a client error; the HTTP mapping
|
|
25
|
+
* to `BadRequest` is the caller's layer's job (see the file header).
|
|
26
|
+
*/
|
|
27
|
+
static decodeOrThrow(cursor) {
|
|
28
|
+
if (!cursor)
|
|
29
|
+
return undefined;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(atob(cursor));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error('Invalid cursor');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether a cursor is well-formed. A missing cursor is valid (first page).
|
|
39
|
+
*/
|
|
40
|
+
static isValidCursor(cursor) {
|
|
41
|
+
if (!cursor)
|
|
42
|
+
return true;
|
|
43
|
+
try {
|
|
44
|
+
JSON.parse(atob(cursor));
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build a `Page`, omitting `nextCursor` entirely on the last page rather than
|
|
53
|
+
* carrying an explicit `undefined`.
|
|
54
|
+
*/
|
|
55
|
+
static page(items, nextCursor) {
|
|
56
|
+
return nextCursor ? { items, nextCursor } : { items };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export { CursorCodec };
|
|
60
|
+
//# sourceMappingURL=CursorCodec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CursorCodec.js","sourceRoot":"","sources":["../src/CursorCodec.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW;IACR,MAAM,CAAC,MAAM,CAAC,KAAc;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,MAAM,CAAI,MAAiC;QACvD,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAM,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,aAAa,CAAI,MAAiC;QAC9D,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAM,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,aAAa,CAAC,MAAiC;QAC3D,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,IAAI,CAAI,KAAmB,EAAE,UAAmB;QAC5D,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;IACxD,CAAC;CACF;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isValidEmailFormat, MAX_EMAIL_LENGTH } from './Identity';
|
|
2
|
+
/**
|
|
3
|
+
* Email address value helpers.
|
|
4
|
+
*
|
|
5
|
+
* ## Provenance
|
|
6
|
+
*
|
|
7
|
+
* Converged from Durable-DAV's `EmailUtil.ts` (a shape check) and Mail-Meow's
|
|
8
|
+
* `EmailUtil.ts` (address normalization). Durable-DAV's predicate `isValidEmailFormat`
|
|
9
|
+
* canonically lives in `Identity.ts` — the wider group's home, and the stricter
|
|
10
|
+
* of the two regexes found (see the merge note there) — and is re-exported here
|
|
11
|
+
* so `EmailUtil` is the one-stop surface for validation + normalization.
|
|
12
|
+
*
|
|
13
|
+
* `normalizeEmail` comes from Mail-Meow, whose header records why a single
|
|
14
|
+
* normalization rule is a correctness concern, not a cosmetic one: "This function
|
|
15
|
+
* used to exist four times, and the copies disagreed." One copy trimmed and
|
|
16
|
+
* lowercased, the others lowercased only, so `" Alice@Example.com "` and
|
|
17
|
+
* `"alice@example.com"` were the same account to one layer and different accounts
|
|
18
|
+
* to another — a different account key on each side of a boundary, and a stored
|
|
19
|
+
* registry row that its own service could not resolve.
|
|
20
|
+
*
|
|
21
|
+
* ## Design decision
|
|
22
|
+
*
|
|
23
|
+
* The kit keeps `normalizeEmail` and re-exports the validator; it does not
|
|
24
|
+
* re-introduce Edge-Git's `EmailAddress` value class, which is sugar over the
|
|
25
|
+
* same trim+lowercase and would be a second spelling of the same rule.
|
|
26
|
+
*/
|
|
27
|
+
declare function normalizeEmail(email: string): string;
|
|
28
|
+
export { normalizeEmail, isValidEmailFormat, MAX_EMAIL_LENGTH };
|
|
29
|
+
//# sourceMappingURL=EmailUtil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailUtil.d.ts","sourceRoot":"","sources":["../src/EmailUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,CAAC"}
|