@evolu/common 7.4.0 → 8.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +622 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +36 -26
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +415 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +465 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +1064 -630
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -823
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +209 -282
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -529
  246. package/src/Instances.ts +0 -90
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -960
package/src/Crypto.ts CHANGED
@@ -1,18 +1,29 @@
1
1
  /**
2
- * Cryptographic utilities
2
+ * Cryptographic utilities.
3
3
  *
4
- * Type-safe cryptographic operations including random number generation, SLIP21
4
+ * Type-safe cryptographic operations including random bytes generation, SLIP21
5
5
  * key derivation, XChaCha20-Poly1305 symmetric encryption, PADMÉ padding, and
6
6
  * timing-safe comparisons.
7
7
  *
8
8
  * @module
9
9
  */
10
+
10
11
  import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
11
12
  import { hmac } from "@noble/hashes/hmac.js";
12
13
  import { sha512 } from "@noble/hashes/sha2.js";
13
14
  import { randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
14
- import { Result, trySync } from "./Result.js";
15
- import { brand, length, NonNegativeInt, Uint8Array } from "./Type.js";
15
+ import { arrayFrom } from "./Array.js";
16
+ import type { RandomLibDep } from "./Random.js";
17
+ import type { Result } from "./Result.js";
18
+ import { trySync } from "./Result.js";
19
+ import {
20
+ brand,
21
+ length,
22
+ NonNegativeInt,
23
+ Uint8Array,
24
+ type Typed,
25
+ zeroNonNegativeInt,
26
+ } from "./Type.js";
16
27
 
17
28
  export interface RandomBytes {
18
29
  /**
@@ -23,11 +34,12 @@ export interface RandomBytes {
23
34
  * generator (crypto.getRandomValues) to generate high-quality entropy
24
35
  * suitable for cryptographic operations.
25
36
  *
26
- * ### Type Safety
37
+ * ## Type Safety
27
38
  *
28
39
  * Returns specific branded types for common sizes:
29
40
  *
30
41
  * - `Random16` for 16-byte values (128 bits)
42
+ * - `Random24` for 24-byte values (192 bits)
31
43
  * - `Random32` for 32-byte values (256 bits)
32
44
  * - `Random64` for 64-byte values (512 bits)
33
45
  * - `Random` for any other size
@@ -36,12 +48,14 @@ export interface RandomBytes {
36
48
  *
37
49
  * ```ts
38
50
  * const nonce = randomBytes.create(16); // Type: Random16
51
+ * const nonce24 = randomBytes.create(24); // Type: Random24
39
52
  * const key = randomBytes.create(32); // Type: Random32
40
53
  * const seed = randomBytes.create(64); // Type: Random64
41
54
  * const custom = randomBytes.create(48); // Type: Random
42
55
  * ```
43
56
  */
44
57
  create(bytesLength: 16): Entropy16;
58
+ create(bytesLength: 24): Entropy24;
45
59
  create(bytesLength: 32): Entropy32;
46
60
  create(bytesLength: 64): Entropy64;
47
61
  create(bytesLength: number): Entropy;
@@ -51,22 +65,34 @@ export interface RandomBytesDep {
51
65
  readonly randomBytes: RandomBytes;
52
66
  }
53
67
 
54
- const Entropy = brand("Entropy", Uint8Array);
68
+ const Entropy = /*#__PURE__*/ brand("Entropy", Uint8Array);
55
69
  type Entropy = typeof Entropy.Type;
56
70
 
57
- export const Entropy16 = length(16)(Entropy);
71
+ export const Entropy16 = /*#__PURE__*/ length(16)(Entropy);
58
72
  export type Entropy16 = typeof Entropy16.Type;
59
73
 
60
- export const Entropy32 = length(32)(Entropy);
74
+ export const Entropy24 = /*#__PURE__*/ length(24)(Entropy);
75
+ export type Entropy24 = typeof Entropy24.Type;
76
+
77
+ export const Entropy32 = /*#__PURE__*/ length(32)(Entropy);
61
78
  export type Entropy32 = typeof Entropy32.Type;
62
79
 
63
- export const Entropy64 = length(64)(Entropy);
80
+ export const Entropy64 = /*#__PURE__*/ length(64)(Entropy);
64
81
  export type Entropy64 = typeof Entropy64.Type;
65
82
 
66
83
  export const createRandomBytes = (): RandomBytes => ({
67
84
  create: randomBytes as RandomBytes["create"],
68
85
  });
69
86
 
87
+ /** Creates seeded random bytes for deterministic tests. */
88
+ export const testCreateRandomBytes = (deps: RandomLibDep): RandomBytes =>
89
+ ({
90
+ create: (bytesLength: number) => {
91
+ const array = arrayFrom(bytesLength, () => deps.randomLib.int(0, 255));
92
+ return new globalThis.Uint8Array(array);
93
+ },
94
+ }) as RandomBytes;
95
+
70
96
  /**
71
97
  * SLIP21.
72
98
  *
@@ -106,71 +132,94 @@ export const deriveSlip21Node = (
106
132
  return hmac(sha512, parentNode.slice(0, 32), message) as Entropy64;
107
133
  };
108
134
 
109
- /** The encryption key for {@link SymmetricCrypto}. */
110
- export const EncryptionKey = brand("EncryptionKey", Entropy32);
135
+ /** The encryption key for symmetric encryption. */
136
+ export const EncryptionKey = /*#__PURE__*/ brand("EncryptionKey", Entropy32);
111
137
  export type EncryptionKey = typeof EncryptionKey.Type;
112
138
 
113
- /** Symmetric cryptography. */
114
- export interface SymmetricCrypto {
115
- readonly nonceLength: NonNegativeInt;
139
+ /** The nonce length for XChaCha20-Poly1305 encryption. */
140
+ export const xChaCha20Poly1305NonceLength = 24;
116
141
 
117
- readonly encrypt: (
142
+ /**
143
+ * Branded Uint8Array for XChaCha20-Poly1305 encryption.
144
+ *
145
+ * @see {@link encryptWithXChaCha20Poly1305}
146
+ */
147
+ export const XChaCha20Poly1305Ciphertext = /*#__PURE__*/ brand(
148
+ "XChaCha20Poly1305Ciphertext",
149
+ Uint8Array,
150
+ );
151
+ export type XChaCha20Poly1305Ciphertext =
152
+ typeof XChaCha20Poly1305Ciphertext.Type;
153
+
154
+ /**
155
+ * Encrypts plaintext with XChaCha20-Poly1305.
156
+ *
157
+ * Generates a random nonce internally and returns both the ciphertext and
158
+ * nonce. The nonce must be stored alongside the ciphertext for decryption.
159
+ *
160
+ * ### Example
161
+ *
162
+ * ```ts
163
+ * const deps = { randomBytes: createRandomBytes() };
164
+ * const [ciphertext, nonce] = encryptWithXChaCha20Poly1305(deps)(
165
+ * utf8ToBytes("secret message"),
166
+ * encryptionKey,
167
+ * );
168
+ * ```
169
+ *
170
+ * @see https://github.com/paulmillr/noble-ciphers
171
+ */
172
+ export const encryptWithXChaCha20Poly1305 =
173
+ (deps: RandomBytesDep) =>
174
+ (
118
175
  plaintext: Uint8Array,
119
176
  encryptionKey: EncryptionKey,
120
- ) => {
121
- readonly nonce: Uint8Array;
122
- readonly ciphertext: Uint8Array;
177
+ ): [XChaCha20Poly1305Ciphertext, Entropy24] => {
178
+ const nonce = deps.randomBytes.create(xChaCha20Poly1305NonceLength);
179
+ const ciphertext = XChaCha20Poly1305Ciphertext.orThrow(
180
+ xchacha20poly1305(encryptionKey, nonce).encrypt(plaintext),
181
+ );
182
+ return [ciphertext, nonce];
123
183
  };
124
184
 
125
- readonly decrypt: (
126
- ciphertext: Uint8Array,
127
- encryptionKey: EncryptionKey,
128
- nonce: Uint8Array,
129
- ) => Result<Uint8Array, SymmetricCryptoDecryptError>;
130
- }
131
-
132
- export interface SymmetricCryptoDep {
133
- readonly symmetricCrypto: SymmetricCrypto;
134
- }
135
-
136
- export interface SymmetricCryptoDecryptError {
137
- readonly type: "SymmetricCryptoDecryptError";
185
+ export interface DecryptWithXChaCha20Poly1305Error extends Typed<"DecryptWithXChaCha20Poly1305Error"> {
138
186
  readonly error: unknown;
139
187
  }
140
188
 
141
189
  /**
142
- * XChaCha20-Poly1305 encryption
190
+ * Decrypts ciphertext with XChaCha20-Poly1305.
191
+ *
192
+ * Requires the same nonce that was used during encryption. Returns a
193
+ * {@link Result} that may contain a decryption error if the ciphertext was
194
+ * tampered with or the wrong key/nonce was used.
143
195
  *
144
- * https://github.com/paulmillr/noble-ciphers?tab=readme-ov-file#which-cipher-should-i-pick
196
+ * ### Example
197
+ *
198
+ * ```ts
199
+ * const result = decryptWithXChaCha20Poly1305(
200
+ * ciphertext,
201
+ * nonce,
202
+ * encryptionKey,
203
+ * );
204
+ * if (!result.ok) {
205
+ * // Handle decryption error
206
+ * return result;
207
+ * }
208
+ * const plaintext = result.value;
209
+ * ```
145
210
  */
146
- export const createSymmetricCrypto = (
147
- deps: RandomBytesDep,
148
- ): SymmetricCrypto => {
149
- const nonceLength = NonNegativeInt.orThrow(24);
150
-
151
- const symmetricCrypto: SymmetricCrypto = {
152
- nonceLength,
153
-
154
- encrypt: (plaintext, encryptionKey) => {
155
- const nonce = deps.randomBytes.create(nonceLength);
156
- const ciphertext = xchacha20poly1305(encryptionKey, nonce).encrypt(
157
- plaintext,
158
- );
159
- return { nonce, ciphertext };
160
- },
161
-
162
- decrypt: (ciphertext, encryptionKey, nonce) =>
163
- trySync(
164
- () => xchacha20poly1305(encryptionKey, nonce).decrypt(ciphertext),
165
- (error): SymmetricCryptoDecryptError => ({
166
- type: "SymmetricCryptoDecryptError",
167
- error,
168
- }),
169
- ),
170
- };
171
-
172
- return symmetricCrypto;
173
- };
211
+ export const decryptWithXChaCha20Poly1305 = (
212
+ ciphertext: XChaCha20Poly1305Ciphertext,
213
+ nonce: Entropy24,
214
+ encryptionKey: EncryptionKey,
215
+ ): Result<Uint8Array, DecryptWithXChaCha20Poly1305Error> =>
216
+ trySync(
217
+ () => xchacha20poly1305(encryptionKey, nonce).decrypt(ciphertext),
218
+ (error): DecryptWithXChaCha20Poly1305Error => ({
219
+ type: "DecryptWithXChaCha20Poly1305Error",
220
+ error,
221
+ }),
222
+ );
174
223
 
175
224
  /**
176
225
  * Returns the PADMÉ padded length for a given input length.
@@ -183,7 +232,7 @@ export const createSymmetricCrypto = (
183
232
  export const createPadmePaddedLength = (
184
233
  length: NonNegativeInt,
185
234
  ): NonNegativeInt => {
186
- if (length <= 0) return NonNegativeInt.orThrow(0);
235
+ if (length <= 0) return zeroNonNegativeInt;
187
236
  const e = 31 - Math.clz32(length >>> 0);
188
237
  const s = 32 - Math.clz32(e >>> 0);
189
238
  const z = Math.max(0, e - s);
package/src/Eq.ts CHANGED
@@ -1,5 +1,16 @@
1
- import { JsonArray, JsonObject, JsonValue, JsonValueInput } from "./Type.js";
2
- import { Order } from "./Order.js";
1
+ /**
2
+ * Equality comparison functions and utilities.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type { Order } from "./Order.js";
8
+ import type {
9
+ JsonArray,
10
+ JsonObject,
11
+ JsonValue,
12
+ JsonValueInput,
13
+ } from "./Type.js";
3
14
 
4
15
  /**
5
16
  * Compares two values of the same type `A` for equality.
@@ -51,6 +62,22 @@ export const createEqArrayLike =
51
62
  return true;
52
63
  };
53
64
 
65
+ /**
66
+ * Compares two array-like structures by strict reference equality (`===`).
67
+ *
68
+ * Useful for structural sharing checks where elements are compared by identity.
69
+ *
70
+ * ### Example
71
+ *
72
+ * ```ts
73
+ * const a = { x: 1 };
74
+ * const b = { x: 1 };
75
+ * eqArrayStrict([a, a], [a, a]); // true (same references)
76
+ * eqArrayStrict([a], [b]); // false (different references, even if equal values)
77
+ * ```
78
+ */
79
+ export const eqArrayStrict = /*#__PURE__*/ createEqArrayLike(eqStrict);
80
+
54
81
  /**
55
82
  * Compares two array-like structures of numbers for equality.
56
83
  *
@@ -62,7 +89,7 @@ export const createEqArrayLike =
62
89
  * eqArrayNumber([1, 2, 3], [1, 2, 4]); // false
63
90
  * ```
64
91
  */
65
- export const eqArrayNumber = createEqArrayLike(eqNumber);
92
+ export const eqArrayNumber = /*#__PURE__*/ createEqArrayLike(eqNumber);
66
93
 
67
94
  /**
68
95
  * Creates an equivalence function for objects based on an equivalence for their
package/src/Error.ts CHANGED
@@ -1,64 +1,80 @@
1
1
  /**
2
- * A serializable representation of an error for safe transfer between execution
3
- * contexts, such as Web Workers and the main thread.
2
+ * Error types and utilities for safe error handling.
4
3
  *
5
- * Use this type for unknown (unexpected) errors that need to be transferred
6
- * across boundaries where native Error objects cannot be sent directly. Not
7
- * intended for regular (expected) errors.
4
+ * @module
5
+ */
6
+
7
+ import type { Typed } from "./Type.js";
8
+
9
+ /**
10
+ * A wrapper for unknown errors caught at runtime.
11
+ *
12
+ * When catching errors from unsafe code (third-party libraries, worker
13
+ * boundaries, etc.), we wrap them in `UnknownError` so they can be used in
14
+ * union types and distinguished from other error types.
8
15
  *
9
- * The `error` property contains a plain object with error details, a string, or
10
- * a fallback value if serialization fails to preserve as much debugging
11
- * information as possible.
16
+ * The `error` property contains error details (including `message`, `stack`,
17
+ * and `cause` if available), a string, or a fallback value.
18
+ *
19
+ * Use {@link createUnknownError} to create instances.
12
20
  */
13
- export interface TransferableError {
14
- readonly type: "TransferableError";
21
+ export interface UnknownError extends Typed<"UnknownError"> {
15
22
  readonly error: unknown;
16
23
  }
17
24
 
18
- /** Creates a {@link TransferableError} from an unknown error. */
19
- export const createTransferableError = (error: unknown): TransferableError => {
25
+ /**
26
+ * Creates an {@link UnknownError} from an unknown error.
27
+ *
28
+ * Error objects cannot be directly structured-cloned (for worker messaging) or
29
+ * JSON-serialized because their properties (`message`, `stack`, `cause`) are
30
+ * non-enumerable. This function extracts those properties into a plain object.
31
+ */
32
+ export const createUnknownError = (error: unknown): UnknownError => {
20
33
  const convertError = (err: Error): Record<string, unknown> => {
21
- const transferableError: Record<string, unknown> =
22
- Object.getOwnPropertyNames(err).reduce<Record<string, unknown>>(
23
- (acc, key) => {
24
- const value = (err as never)[key] as unknown;
25
- if (key === "cause" && value instanceof Error) {
26
- // Recursively process the `cause` property
27
- acc[key] = convertError(value);
28
- } else if (typeof value !== "function") {
29
- acc[key] = value;
30
- }
31
- return acc;
32
- },
33
- {},
34
- );
35
- return transferableError;
34
+ const result: Record<string, unknown> = Object.getOwnPropertyNames(
35
+ err,
36
+ ).reduce<Record<string, unknown>>((acc, key) => {
37
+ const value = (err as never)[key] as unknown;
38
+ if (key === "cause" && value instanceof Error) {
39
+ // Recursively process the `cause` property
40
+ acc[key] = convertError(value);
41
+ } else if (typeof value !== "function") {
42
+ acc[key] = value;
43
+ }
44
+ return acc;
45
+ }, {});
46
+ // Firefox defines `stack` as a getter on Error.prototype, not as an own
47
+ // property, so getOwnPropertyNames misses it. Explicitly include it.
48
+ if (err.stack !== undefined && !("stack" in result)) {
49
+ result.stack = err.stack;
50
+ }
51
+ return result;
36
52
  };
37
53
 
38
54
  if (error instanceof Error) {
39
55
  return {
40
- type: "TransferableError",
56
+ type: "UnknownError",
41
57
  error: convertError(error),
42
58
  };
43
59
  }
44
60
 
45
61
  try {
46
- // Clone other values that are transferable
62
+ // Clone other values that are structured-clonable
47
63
  return {
48
- type: "TransferableError",
64
+ type: "UnknownError",
49
65
  error: structuredClone(error),
50
66
  };
51
67
  } catch {
52
- // Fallback for non-transferable or problematic values
68
+ // Fallback for non-clonable values
53
69
  try {
54
70
  return {
55
- type: "TransferableError",
56
- error: String(error), // Attempt to convert to a string
71
+ type: "UnknownError",
72
+ error: String(error),
57
73
  };
58
74
  } catch {
59
75
  // Final fallback if even `String(error)` fails
60
76
  return {
61
- type: "TransferableError",
77
+ type: "UnknownError",
62
78
  error: "[Unserializable Object]",
63
79
  };
64
80
  }
package/src/Function.ts CHANGED
@@ -1,19 +1,19 @@
1
- import { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.js";
2
- import { ReadonlyRecord } from "./Object.js";
1
+ /**
2
+ * Function utilities including exhaustive checks and composition.
3
+ *
4
+ * @module
5
+ */
3
6
 
4
7
  /**
5
8
  * Helper function to ensure exhaustive matching in a switch statement. Throws
6
9
  * an error if an unhandled case is encountered.
7
10
  *
8
- * Remember, it's useful only when we don't return anything from the switch
9
- * statement. Otherwise, a return type of a function is enough.
10
- *
11
11
  * ### Example
12
12
  *
13
13
  * ```ts
14
14
  * type Color = "red" | "green" | "blue";
15
15
  *
16
- * function handleColor(color: Color): void {
16
+ * const handleColor = (color: Color): void => {
17
17
  * switch (color) {
18
18
  * case "red":
19
19
  * console.log("Handling red");
@@ -27,7 +27,63 @@ import { ReadonlyRecord } from "./Object.js";
27
27
  * default:
28
28
  * exhaustiveCheck(color); // Ensures all cases are handled
29
29
  * }
30
- * }
30
+ * };
31
+ * ```
32
+ *
33
+ * Use this primarily in side-effect switches (`void` branches). For
34
+ * value-producing switches, TypeScript can enforce exhaustiveness without a
35
+ * `default` branch.
36
+ *
37
+ * ### Example
38
+ *
39
+ * Return from each case for value-producing switches.
40
+ *
41
+ * ```ts
42
+ * type Color = "red" | "green" | "blue";
43
+ *
44
+ * const colorToHex = (color: Color): string => {
45
+ * switch (color) {
46
+ * case "red":
47
+ * return "#ff0000";
48
+ * case "green":
49
+ * return "#00ff00";
50
+ * case "blue":
51
+ * return "#0000ff";
52
+ * }
53
+ * };
54
+ * ```
55
+ *
56
+ * ### Example
57
+ *
58
+ * Use assignment + no `default` to get exhaustiveness by definite assignment.
59
+ *
60
+ * ```ts
61
+ * type Input =
62
+ * | { readonly type: "Mutate" }
63
+ * | { readonly type: "Query" }
64
+ * | { readonly type: "Export" };
65
+ *
66
+ * const onInput = (input: Input): void => {
67
+ * let result: "A" | "B" | "C";
68
+ *
69
+ * switch (input.type) {
70
+ * case "Mutate":
71
+ * result = "A";
72
+ * break;
73
+ * case "Query":
74
+ * result = "B";
75
+ * break;
76
+ * case "Export":
77
+ * result = "C";
78
+ * break;
79
+ * }
80
+ *
81
+ * handleKind(result);
82
+ * };
83
+ *
84
+ * const handleKind = (kind: "A" | "B" | "C"): void => {
85
+ * console.log(kind);
86
+ * };
31
87
  * ```
32
88
  */
33
89
  export const exhaustiveCheck = (value: never): never => {
@@ -35,7 +91,7 @@ export const exhaustiveCheck = (value: never): never => {
35
91
  };
36
92
 
37
93
  /**
38
- * Returns the input value unchanged.
94
+ * Returns the value unchanged.
39
95
  *
40
96
  * Useful as a default transformation, placeholder callback, or when a function
41
97
  * is required but no transformation is needed.
@@ -53,80 +109,96 @@ export const exhaustiveCheck = (value: never): never => {
53
109
  export const identity = <A>(a: A): A => a;
54
110
 
55
111
  /**
56
- * Casts an array, set, record, or map to its readonly counterpart.
112
+ * A function that takes no arguments and returns a value of type T. Also known
113
+ * as a thunk.
57
114
  *
58
- * Zero runtime cost — returns the same value with a readonly type. Use this to
59
- * enforce immutability at the type level. Preserves {@link NonEmptyArray} as
60
- * {@link NonEmptyReadonlyArray}.
115
+ * Useful for:
116
+ *
117
+ * - Providing default callbacks (see {@link lazyVoid}, {@link lazyTrue}, etc.)
118
+ * - Delaying expensive operations until actually needed
119
+ * - Deferring side effects so the callee controls when they run
61
120
  *
62
121
  * ### Example
63
122
  *
64
123
  * ```ts
65
- * // Array literals become NonEmptyReadonlyArray
66
- * const items = readonly([1, 2, 3]);
67
- * // Type: NonEmptyReadonlyArray<number>
68
- *
69
- * // NonEmptyArray is preserved as NonEmptyReadonlyArray
70
- * const nonEmpty: NonEmptyArray<number> = [1, 2, 3];
71
- * const readonlyNonEmpty = readonly(nonEmpty);
72
- * // Type: NonEmptyReadonlyArray<number>
73
- *
74
- * // Regular arrays become ReadonlyArray
75
- * const arr: Array<number> = getNumbers();
76
- * const readonlyArr = readonly(arr);
77
- * // Type: ReadonlyArray<number>
78
- *
79
- * // Sets, Records, and Maps
80
- * const ids = readonly(new Set(["a", "b"]));
81
- * // Type: ReadonlySet<string>
82
- *
83
- * const users: Record<UserId, string> = { ... };
84
- * const readonlyUsers = readonly(users);
85
- * // Type: ReadonlyRecord<UserId, string>
86
- *
87
- * const lookup = readonly(new Map([["key", "value"]]));
88
- * // Type: ReadonlyMap<string, string>
124
+ * // Default callback
125
+ * const notify = (onDone: Lazy<void> = lazyVoid) => {
126
+ * onDone();
127
+ * };
128
+ *
129
+ * // Delay computation
130
+ * const getData: Lazy<Data> = () => compute();
131
+ * const data = getData();
132
+ *
133
+ * // Defer side effects
134
+ * const schedule = (job: Lazy<void>) => {
135
+ * queueMicrotask(job);
136
+ * };
137
+ * schedule(() => logMetric("loaded"));
89
138
  * ```
90
- *
91
- * @experimental
92
139
  */
93
- export function readonly<T>(array: NonEmptyArray<T>): NonEmptyReadonlyArray<T>;
94
- export function readonly<T>(array: Array<T>): ReadonlyArray<T>;
95
- export function readonly<T>(set: Set<T>): ReadonlySet<T>;
96
- export function readonly<K, V>(map: Map<K, V>): ReadonlyMap<K, V>;
97
- export function readonly<K extends keyof any, V>(
98
- record: Record<K, V>,
99
- ): ReadonlyRecord<K, V>;
100
- export function readonly<T, K extends keyof any, V>(
101
- value: Array<T> | Set<T> | Map<K, V> | Record<K, V>,
102
- ):
103
- | ReadonlyArray<T>
104
- | ReadonlySet<T>
105
- | ReadonlyMap<K, V>
106
- | ReadonlyRecord<K, V> {
107
- return value;
108
- }
140
+ export type Lazy<T> = () => T;
109
141
 
110
142
  /**
111
- * A function that delays computation and returns a value of type T.
143
+ * Creates a {@link Lazy} from a precomputed value.
112
144
  *
113
- * Useful for:
114
- *
115
- * - Lazy evaluation
116
- * - Returning constant values
117
- * - Providing default or placeholder behaviors
145
+ * Use when the value is expensive to compute and want to compute it once at
146
+ * definition time rather than on every call.
118
147
  *
119
148
  * ### Example
120
149
  *
121
150
  * ```ts
122
- * const getRandomNumber: LazyValue<number> = () => Math.random();
123
- * const randomValue = getRandomNumber();
151
+ * // Computed once at definition, returned on every call
152
+ * const getConfig = lazy(parseConfig(rawConfig));
153
+ *
154
+ * // vs. computed on every call
155
+ * const getConfig = () => parseConfig(rawConfig);
124
156
  * ```
125
157
  */
126
- export type LazyValue<T> = () => T;
158
+ export const lazy =
159
+ <T>(value: T): Lazy<T> =>
160
+ () =>
161
+ value;
162
+
163
+ /** A {@link Lazy} that returns `true`. */
164
+ export const lazyTrue: Lazy<true> = /*#__PURE__*/ lazy(true);
165
+
166
+ /** A {@link Lazy} that returns `false`. */
167
+ export const lazyFalse: Lazy<false> = /*#__PURE__*/ lazy(false);
168
+
169
+ /** A {@link Lazy} that returns `null`. */
170
+ export const lazyNull: Lazy<null> = /*#__PURE__*/ lazy(null);
171
+
172
+ /** A {@link Lazy} that returns `undefined`. */
173
+ export const lazyUndefined: Lazy<undefined> = /*#__PURE__*/ lazy(undefined);
127
174
 
128
- export const constVoid: LazyValue<void> = () => undefined;
129
- export const constUndefined: LazyValue<undefined> = () => undefined;
130
- export const constNull: LazyValue<null> = () => null;
131
- export const constTrue: LazyValue<true> = () => true;
132
- export const constFalse: LazyValue<false> = () => false;
175
+ /** A {@link Lazy} that returns `undefined` for void callbacks. */
176
+ export const lazyVoid: Lazy<void> = lazyUndefined;
177
+
178
+ /**
179
+ * Development placeholder that always throws.
180
+ *
181
+ * Use to sketch function bodies before implementing them. TypeScript infers the
182
+ * return type from context, so surrounding code still type-checks. Use an
183
+ * explicit generic when there is no return type annotation.
184
+ *
185
+ * ### Example
186
+ *
187
+ * ```ts
188
+ * // Type inferred from return type annotation
189
+ * const fetchUser = (id: UserId): Result<User, FetchError> => todo();
190
+ *
191
+ * expectTypeOf(fetchUser).returns.toEqualTypeOf<
192
+ * Result<User, FetchError>
193
+ * >();
194
+ *
195
+ * // Explicit generic when no return type
196
+ * const getConfig = () => todo<Config>();
197
+ *
198
+ * expectTypeOf(getConfig).returns.toEqualTypeOf<Config>();
199
+ * ```
200
+ */
201
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
202
+ export const todo = <T>(): T => {
203
+ throw new Error("not yet implemented");
204
+ };