@evolu/common 8.10.0 → 8.12.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 (152) hide show
  1. package/dist/src/Bytes.d.ts +39 -2
  2. package/dist/src/Bytes.d.ts.map +1 -1
  3. package/dist/src/Bytes.js +50 -2
  4. package/dist/src/Config.d.ts +22 -22
  5. package/dist/src/Config.d.ts.map +1 -1
  6. package/dist/src/Console.d.ts +62 -7
  7. package/dist/src/Console.d.ts.map +1 -1
  8. package/dist/src/Console.js +20 -4
  9. package/dist/src/Crypto.d.ts +76 -4
  10. package/dist/src/Crypto.d.ts.map +1 -1
  11. package/dist/src/Crypto.js +55 -4
  12. package/dist/src/Error.d.ts +45 -0
  13. package/dist/src/Error.d.ts.map +1 -1
  14. package/dist/src/Error.js +69 -0
  15. package/dist/src/Fs.d.ts +92 -18
  16. package/dist/src/Fs.d.ts.map +1 -1
  17. package/dist/src/Fs.js +2 -0
  18. package/dist/src/Identicon.d.ts +2 -2
  19. package/dist/src/Identicon.js +2 -2
  20. package/dist/src/LeakDetector.d.ts +22 -3
  21. package/dist/src/LeakDetector.d.ts.map +1 -1
  22. package/dist/src/LeakDetector.js +12 -2
  23. package/dist/src/LockManager.d.ts +8 -0
  24. package/dist/src/LockManager.d.ts.map +1 -1
  25. package/dist/src/LockManager.js +6 -0
  26. package/dist/src/Object.d.ts.map +1 -1
  27. package/dist/src/Object.js +5 -0
  28. package/dist/src/Platform.d.ts +47 -7
  29. package/dist/src/Platform.d.ts.map +1 -1
  30. package/dist/src/Platform.js +24 -5
  31. package/dist/src/Random.d.ts +25 -2
  32. package/dist/src/Random.d.ts.map +1 -1
  33. package/dist/src/Random.js +14 -2
  34. package/dist/src/Resource.d.ts +156 -1
  35. package/dist/src/Resource.d.ts.map +1 -1
  36. package/dist/src/Resource.js +201 -72
  37. package/dist/src/Schedule.d.ts +11 -10
  38. package/dist/src/Schedule.d.ts.map +1 -1
  39. package/dist/src/Schedule.js +1 -1
  40. package/dist/src/Sqlite.d.ts +132 -16
  41. package/dist/src/Sqlite.d.ts.map +1 -1
  42. package/dist/src/Sqlite.js +63 -9
  43. package/dist/src/Task.d.ts +15 -4
  44. package/dist/src/Task.d.ts.map +1 -1
  45. package/dist/src/Task.js +41 -15
  46. package/dist/src/Test.d.ts +9 -0
  47. package/dist/src/Test.d.ts.map +1 -1
  48. package/dist/src/Test.js +4 -0
  49. package/dist/src/Time.d.ts +106 -9
  50. package/dist/src/Time.d.ts.map +1 -1
  51. package/dist/src/Time.js +55 -4
  52. package/dist/src/Type.d.ts +1455 -1310
  53. package/dist/src/Type.d.ts.map +1 -1
  54. package/dist/src/Type.js +1274 -517
  55. package/dist/src/WebSocket.d.ts +164 -13
  56. package/dist/src/WebSocket.d.ts.map +1 -1
  57. package/dist/src/WebSocket.js +133 -24
  58. package/dist/src/Worker.d.ts +90 -8
  59. package/dist/src/Worker.d.ts.map +1 -1
  60. package/dist/src/Worker.js +28 -2
  61. package/dist/src/index.d.ts +6 -7
  62. package/dist/src/index.d.ts.map +1 -1
  63. package/dist/src/index.js +2 -3
  64. package/dist/src/local-first/Db.d.ts +52 -3
  65. package/dist/src/local-first/Db.d.ts.map +1 -1
  66. package/dist/src/local-first/Db.js +412 -137
  67. package/dist/src/local-first/Evolu.d.ts +412 -213
  68. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  69. package/dist/src/local-first/Evolu.js +181 -18
  70. package/dist/src/local-first/Owner.d.ts +13 -30
  71. package/dist/src/local-first/Owner.d.ts.map +1 -1
  72. package/dist/src/local-first/Owner.js +13 -30
  73. package/dist/src/local-first/Protocol.d.ts +106 -19
  74. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  75. package/dist/src/local-first/Protocol.js +162 -60
  76. package/dist/src/local-first/Query.d.ts +8 -15
  77. package/dist/src/local-first/Query.d.ts.map +1 -1
  78. package/dist/src/local-first/Relay.d.ts.map +1 -1
  79. package/dist/src/local-first/Relay.js +4 -2
  80. package/dist/src/local-first/Schema.d.ts +346 -23
  81. package/dist/src/local-first/Schema.d.ts.map +1 -1
  82. package/dist/src/local-first/Schema.js +214 -17
  83. package/dist/src/local-first/Shared.d.ts +537 -22
  84. package/dist/src/local-first/Shared.d.ts.map +1 -1
  85. package/dist/src/local-first/Shared.js +1437 -234
  86. package/dist/src/local-first/Storage.d.ts +195 -17
  87. package/dist/src/local-first/Storage.d.ts.map +1 -1
  88. package/dist/src/local-first/Storage.js +85 -22
  89. package/dist/src/local-first/Timestamp.d.ts +392 -41
  90. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  91. package/dist/src/local-first/Timestamp.js +403 -81
  92. package/dist/src/local-first/index.d.ts +0 -1
  93. package/dist/src/local-first/index.d.ts.map +1 -1
  94. package/dist/src/local-first/index.js +0 -1
  95. package/package.json +1 -1
  96. package/src/Assert.test.ts +2 -5
  97. package/src/Bytes.test.ts +27 -0
  98. package/src/Bytes.ts +58 -2
  99. package/src/Config.test.ts +2 -6
  100. package/src/Config.ts +133 -133
  101. package/src/Console.ts +62 -7
  102. package/src/Crypto.ts +76 -4
  103. package/src/Eq.test.ts +2 -3
  104. package/src/Error.test.ts +76 -3
  105. package/src/Error.ts +71 -0
  106. package/src/Fs.ts +92 -18
  107. package/src/Identicon.ts +2 -2
  108. package/src/LeakDetector.ts +22 -3
  109. package/src/LockManager.ts +8 -0
  110. package/src/Object.test.ts +27 -12
  111. package/src/Object.ts +5 -0
  112. package/src/Platform.ts +50 -8
  113. package/src/Random.ts +25 -2
  114. package/src/Resource.test.ts +837 -0
  115. package/src/Resource.ts +235 -15
  116. package/src/Schedule.test.ts +50 -12
  117. package/src/Schedule.ts +24 -14
  118. package/src/Sqlite.ts +137 -17
  119. package/src/Task.test.ts +189 -8
  120. package/src/Task.ts +56 -17
  121. package/src/Test.ts +9 -0
  122. package/src/Time.ts +106 -9
  123. package/src/Type.test.ts +946 -1028
  124. package/src/Type.ts +4195 -3136
  125. package/src/Types.test.ts +4 -14
  126. package/src/WebSocket.ts +313 -40
  127. package/src/Worker.ts +90 -8
  128. package/src/index.ts +20 -6
  129. package/src/local-first/Db.ts +644 -339
  130. package/src/local-first/Evolu.test.ts +994 -22
  131. package/src/local-first/Evolu.ts +625 -232
  132. package/src/local-first/Owner.ts +13 -30
  133. package/src/local-first/Protocol.test.ts +634 -10
  134. package/src/local-first/Protocol.ts +255 -109
  135. package/src/local-first/Query.ts +8 -15
  136. package/src/local-first/Relay.ts +4 -2
  137. package/src/local-first/Schema.test.ts +143 -0
  138. package/src/local-first/Schema.ts +376 -26
  139. package/src/local-first/Shared.test.ts +7731 -559
  140. package/src/local-first/Shared.ts +2036 -267
  141. package/src/local-first/Storage.ts +224 -36
  142. package/src/local-first/Timestamp.test.ts +344 -70
  143. package/src/local-first/Timestamp.ts +434 -118
  144. package/src/local-first/index.ts +0 -1
  145. package/dist/src/local-first/Error.d.ts +0 -12
  146. package/dist/src/local-first/Error.d.ts.map +0 -1
  147. package/dist/src/local-first/Error.js +0 -6
  148. package/dist/src/local-first/LocalAuth.d.ts +0 -150
  149. package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
  150. package/dist/src/local-first/LocalAuth.js +0 -179
  151. package/src/local-first/Error.ts +0 -17
  152. package/src/local-first/LocalAuth.ts +0 -457
package/src/Crypto.ts CHANGED
@@ -24,6 +24,11 @@ import {
24
24
  zeroNonNegativeInt,
25
25
  } from "./Type.ts";
26
26
 
27
+ /**
28
+ * Cryptographically secure random bytes with length-branded results.
29
+ *
30
+ * @group Random bytes
31
+ */
27
32
  export interface RandomBytes {
28
33
  /**
29
34
  * Creates cryptographically secure random bytes with type-safe length
@@ -77,6 +82,11 @@ export interface RandomBytes {
77
82
  create(bytesLength: number): Entropy;
78
83
  }
79
84
 
85
+ /**
86
+ * Dependency wrapper for {@link RandomBytes}.
87
+ *
88
+ * @group Random bytes
89
+ */
80
90
  export interface RandomBytesDep {
81
91
  readonly randomBytes: RandomBytes;
82
92
  }
@@ -84,23 +94,53 @@ export interface RandomBytesDep {
84
94
  const Entropy = /*#__PURE__*/ brand("Entropy", Uint8Array);
85
95
  type Entropy = typeof Entropy.Output;
86
96
 
97
+ /**
98
+ * Cryptographic entropy of exactly 16 bytes.
99
+ *
100
+ * @group Random bytes
101
+ */
87
102
  export const Entropy16 = /*#__PURE__*/ length(16)(Entropy);
88
103
  export type Entropy16 = typeof Entropy16.Output;
89
104
 
105
+ /**
106
+ * Cryptographic entropy of exactly 24 bytes.
107
+ *
108
+ * @group Random bytes
109
+ */
90
110
  export const Entropy24 = /*#__PURE__*/ length(24)(Entropy);
91
111
  export type Entropy24 = typeof Entropy24.Output;
92
112
 
113
+ /**
114
+ * Cryptographic entropy of exactly 32 bytes.
115
+ *
116
+ * @group Random bytes
117
+ */
93
118
  export const Entropy32 = /*#__PURE__*/ length(32)(Entropy);
94
119
  export type Entropy32 = typeof Entropy32.Output;
95
120
 
121
+ /**
122
+ * Cryptographic entropy of exactly 64 bytes.
123
+ *
124
+ * @group Random bytes
125
+ */
96
126
  export const Entropy64 = /*#__PURE__*/ length(64)(Entropy);
97
127
  export type Entropy64 = typeof Entropy64.Output;
98
128
 
129
+ /**
130
+ * Creates {@link RandomBytes} backed by the platform's secure random number
131
+ * generator.
132
+ *
133
+ * @group Random bytes
134
+ */
99
135
  export const createRandomBytes = (): RandomBytes => ({
100
136
  create: randomBytes as RandomBytes["create"],
101
137
  });
102
138
 
103
- /** Creates seeded random bytes for deterministic tests. */
139
+ /**
140
+ * Creates seeded random bytes for deterministic tests.
141
+ *
142
+ * @group Testing
143
+ */
104
144
  export const testCreateRandomBytes = (deps: RandomLibDep): RandomBytes =>
105
145
  ({
106
146
  create: (bytesLength: number) =>
@@ -113,6 +153,8 @@ export const testCreateRandomBytes = (deps: RandomLibDep): RandomBytes =>
113
153
  * SLIP21.
114
154
  *
115
155
  * https://github.com/satoshilabs/slips/blob/master/slip-0021.md
156
+ *
157
+ * @group Key derivation
116
158
  */
117
159
  export const createSlip21 = (
118
160
  seed: Entropy16 | Entropy32 | Entropy64,
@@ -135,6 +177,7 @@ export const createSlip21 = (
135
177
  /**
136
178
  * Derives a single node in the SLIP-21 hierarchical key derivation.
137
179
  *
180
+ * @group Key derivation
138
181
  * @see {@link createSlip21}
139
182
  */
140
183
  export const deriveSlip21Node = (
@@ -148,16 +191,25 @@ export const deriveSlip21Node = (
148
191
  return hmac(sha512, parentNode.slice(0, 32), message) as Entropy64;
149
192
  };
150
193
 
151
- /** The encryption key for symmetric encryption. */
194
+ /**
195
+ * The encryption key for symmetric encryption.
196
+ *
197
+ * @group Encryption
198
+ */
152
199
  export const EncryptionKey = /*#__PURE__*/ brand("EncryptionKey", Entropy32);
153
200
  export type EncryptionKey = typeof EncryptionKey.Output;
154
201
 
155
- /** The nonce length for XChaCha20-Poly1305 encryption. */
202
+ /**
203
+ * The nonce length for XChaCha20-Poly1305 encryption.
204
+ *
205
+ * @group Encryption
206
+ */
156
207
  export const xChaCha20Poly1305NonceLength = 24;
157
208
 
158
209
  /**
159
210
  * Branded Uint8Array for XChaCha20-Poly1305 encryption.
160
211
  *
212
+ * @group Encryption
161
213
  * @see {@link encryptWithXChaCha20Poly1305}
162
214
  */
163
215
  export const XChaCha20Poly1305Ciphertext = /*#__PURE__*/ brand(
@@ -196,6 +248,7 @@ export type XChaCha20Poly1305Ciphertext =
196
248
  * assertEqual(nonce.length, 24);
197
249
  * ```
198
250
  *
251
+ * @group Encryption
199
252
  * @see https://github.com/paulmillr/noble-ciphers
200
253
  */
201
254
  export const encryptWithXChaCha20Poly1305 =
@@ -211,6 +264,11 @@ export const encryptWithXChaCha20Poly1305 =
211
264
  return [ciphertext, nonce];
212
265
  };
213
266
 
267
+ /**
268
+ * Error returned by {@link decryptWithXChaCha20Poly1305} when decryption fails.
269
+ *
270
+ * @group Encryption
271
+ */
214
272
  export interface DecryptWithXChaCha20Poly1305Error extends Typed<"DecryptWithXChaCha20Poly1305Error"> {
215
273
  readonly error: unknown;
216
274
  }
@@ -254,6 +312,8 @@ export interface DecryptWithXChaCha20Poly1305Error extends Typed<"DecryptWithXCh
254
312
  *
255
313
  * assertOk(decryptMessage(), "secret message");
256
314
  * ```
315
+ *
316
+ * @group Encryption
257
317
  */
258
318
  export const decryptWithXChaCha20Poly1305 = (
259
319
  ciphertext: XChaCha20Poly1305Ciphertext,
@@ -275,6 +335,8 @@ export const decryptWithXChaCha20Poly1305 = (
275
335
  * wide range of encrypted data sizes.
276
336
  *
277
337
  * See the PURBs paper for details: https://bford.info/pub/sec/purb.pdf
338
+ *
339
+ * @group Padding
278
340
  */
279
341
  export const createPadmePaddedLength = (
280
342
  length: NonNegativeInt,
@@ -287,7 +349,11 @@ export const createPadmePaddedLength = (
287
349
  return NonNegativeInt.orThrow((length + mask) & ~mask);
288
350
  };
289
351
 
290
- /** Creates a PADMÉ padding array of zeros for the given input length. */
352
+ /**
353
+ * Creates a PADMÉ padding array of zeros for the given input length.
354
+ *
355
+ * @group Padding
356
+ */
291
357
  export const createPadmePadding = (length: NonNegativeInt): Uint8Array => {
292
358
  const paddedLength = createPadmePaddedLength(length);
293
359
  const paddingLength = NonNegativeInt.orThrow(paddedLength - length);
@@ -299,10 +365,16 @@ export const createPadmePadding = (length: NonNegativeInt): Uint8Array => {
299
365
  * are equal, false otherwise. Takes constant time regardless of where the
300
366
  * arrays differ.
301
367
  *
368
+ * @group Comparison
302
369
  * @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
303
370
  */
304
371
  export type TimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean;
305
372
 
373
+ /**
374
+ * Dependency wrapper for {@link TimingSafeEqual}.
375
+ *
376
+ * @group Comparison
377
+ */
306
378
  export interface TimingSafeEqualDep {
307
379
  readonly timingSafeEqual: TimingSafeEqual;
308
380
  }
package/src/Eq.test.ts CHANGED
@@ -293,13 +293,12 @@ test("eqData", () => {
293
293
  }
294
294
  const service: Service = { run: () => undefined };
295
295
  const broadObject: NonNullable<unknown> = new WeakMap();
296
- const compileTimeAssertions = () => {
296
+ void (() => {
297
297
  // @ts-expect-error ⛔ eqData error: Actual and expected values must consist only of Data.
298
298
  eqData(service, service);
299
299
  // @ts-expect-error ⛔ eqData error: Actual and expected values must consist only of Data.
300
300
  eqData(broadObject, broadObject);
301
- };
302
- assertEqual(typeof compileTimeAssertions, "function");
301
+ });
303
302
  });
304
303
 
305
304
  test("eqData compares deeply nested Set and Map data", () => {
package/src/Error.test.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import { describe, it } from "node:test";
2
- import { assertEqual, assertFalse, assertSame, assertTrue } from "./Assert.ts";
3
-
4
- import { UnknownError, createUnknownError } from "./Error.ts";
2
+ import { runInNewContext } from "node:vm";
3
+ import {
4
+ assertEqual,
5
+ assertFalse,
6
+ assertInstanceOf,
7
+ assertSame,
8
+ assertTrue,
9
+ } from "./Assert.ts";
10
+
11
+ import { UnknownError, createUnknownError, defectToError } from "./Error.ts";
12
+ import { createRun } from "./Task.ts";
5
13
  import { assertType, Object, String } from "./Type.ts";
6
14
 
7
15
  describe("createUnknownError", () => {
@@ -116,3 +124,68 @@ describe("createUnknownError", () => {
116
124
  assertSame(actual.self, actual);
117
125
  });
118
126
  });
127
+
128
+ describe("defectToError", () => {
129
+ it("converts a panic to its Error defect", async () => {
130
+ const reported: Array<unknown> = [];
131
+ await using run = createRun({
132
+ reportDefect: (defect) => {
133
+ reported.push(defect);
134
+ },
135
+ });
136
+ const defect = new Error("boom");
137
+
138
+ run.panic(defect);
139
+
140
+ assertSame(defectToError(reported[0]), defect);
141
+ });
142
+
143
+ it("returns an Error from another realm as it is", () => {
144
+ const defect: unknown = runInNewContext(
145
+ 'new TypeError("other realm failed")',
146
+ );
147
+ assertFalse(defect instanceof Error);
148
+
149
+ assertSame(defectToError(defect), defect);
150
+ });
151
+
152
+ it("describes a DOMException with its name and message", () => {
153
+ const defect = new DOMException("dom failed", "NotFoundError");
154
+
155
+ const error = defectToError(defect);
156
+
157
+ assertEqual(error.message, "NotFoundError: dom failed");
158
+ assertSame(error.cause, defect);
159
+ });
160
+
161
+ it("describes a panic's defect that is not an Error", async () => {
162
+ const reported: Array<unknown> = [];
163
+ await using run = createRun({
164
+ reportDefect: (defect) => {
165
+ reported.push(defect);
166
+ },
167
+ });
168
+
169
+ const abortError = run.panic({ type: "UnexpectedState", count: 1 });
170
+
171
+ const error = defectToError(reported[0]);
172
+ assertInstanceOf(error, Error);
173
+ assertEqual(error.message, 'Defect: {"type":"UnexpectedState","count":1}');
174
+ assertSame(error.cause, abortError);
175
+ });
176
+
177
+ it("describes an AbortError that is not a panic", () => {
178
+ const abortError = {
179
+ type: "AbortError",
180
+ reason: { type: "OtherAbortReason" },
181
+ } as const;
182
+
183
+ const error = defectToError(abortError);
184
+
185
+ assertEqual(
186
+ error.message,
187
+ 'Defect: {"type":"AbortError","reason":{"type":"OtherAbortReason"}}',
188
+ );
189
+ assertSame(error.cause, abortError);
190
+ });
191
+ });
package/src/Error.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  * @module
5
5
  */
6
6
 
7
+ import { safelyStringifyUnknownValue } from "./String.ts";
8
+ import { AbortError, type createRun, type ReportDefect } from "./Task.ts";
7
9
  import { type InferType, typed, type TypedType, Unknown } from "./Type.ts";
8
10
 
9
11
  /**
@@ -84,3 +86,72 @@ export const createUnknownError = (error: unknown): UnknownError => {
84
86
  }
85
87
  }
86
88
  };
89
+
90
+ /**
91
+ * Converts a reported defect to an `Error` that a host error reporter shows
92
+ * readably.
93
+ *
94
+ * Hosts such as browsers and React Native show a reported value that is not an
95
+ * `Error` only as text such as "[object Object]", and a worker's error reaches
96
+ * its page, including an error tracker listening there, as that text alone. A
97
+ * panic reports a plain {@link AbortError}, so its defect is converted instead.
98
+ * An `Error` is returned as it is, including one from another realm, such as an
99
+ * iframe. A `DOMException` is described in an `Error` with its name and
100
+ * message, because Chromium reports one from a worker without them. Any other
101
+ * value is described in an `Error` whose cause is what was reported.
102
+ *
103
+ * Platform {@link createRun} adapters use it for their default reporting. A
104
+ * custom {@link ReportDefect} can use it too, such as before passing a defect to
105
+ * an error tracker.
106
+ *
107
+ * ### Example
108
+ *
109
+ * ```ts
110
+ * import {
111
+ * assertEqual,
112
+ * assertSame,
113
+ * createRun,
114
+ * defectToError,
115
+ * } from "@evolu/common";
116
+ *
117
+ * const errors: Array<Error> = [];
118
+ * await using run = createRun({
119
+ * reportDefect: (reported) => {
120
+ * errors.push(defectToError(reported));
121
+ * },
122
+ * });
123
+ * const defect = new Error("boom");
124
+ *
125
+ * run.panic(defect);
126
+ *
127
+ * assertSame(errors[0], defect);
128
+ * assertEqual(
129
+ * defectToError({ type: "UnexpectedState" }).message,
130
+ * 'Defect: {"type":"UnexpectedState"}',
131
+ * );
132
+ * ```
133
+ */
134
+ export const defectToError = (reported: unknown): Error => {
135
+ const defect =
136
+ AbortError.is(reported) && reported.reason.type === "PanicAbortReason"
137
+ ? reported.reason.defect
138
+ : reported;
139
+ // The internal tag survives crossing realms, such as from an iframe, where
140
+ // instanceof fails.
141
+ const tag = Object.prototype.toString.call(defect);
142
+ // Chromium reports a DOMException from a worker without its name or message,
143
+ // so it is described in an Error.
144
+ if (tag === "[object DOMException]") {
145
+ const { name, message } = defect as Error;
146
+ return new Error(`${name}: ${message}`, { cause: defect });
147
+ }
148
+ if (defect instanceof Error || tag === "[object Error]") {
149
+ return defect as Error;
150
+ }
151
+ // A value with a cycle or a bigint falls back to String, often
152
+ // "[object Object]", and a nested Error shows as "{}". That is enough:
153
+ // Evolu's own defects are Errors, and the cause still holds the value.
154
+ return new Error(`Defect: ${safelyStringifyUnknownValue(defect)}`, {
155
+ cause: reported,
156
+ });
157
+ };
package/src/Fs.ts CHANGED
@@ -60,7 +60,11 @@ import type { ByteLength } from "./Bytes.ts";
60
60
  import type { createRun, Task } from "./Task.ts";
61
61
  import type { Typed } from "./Type.ts";
62
62
 
63
- /** Asynchronous file system operations. */
63
+ /**
64
+ * Asynchronous file system operations.
65
+ *
66
+ * @group Core
67
+ */
64
68
  export interface Fs {
65
69
  /**
66
70
  * Reads a whole file, as bytes by default or as a string with an encoding. An
@@ -163,15 +167,27 @@ export interface Fs {
163
167
  ) => Task<FsTempDirectory, FsError>;
164
168
  }
165
169
 
166
- /** Dependency wrapper for {@link Fs}. */
170
+ /**
171
+ * Dependency wrapper for {@link Fs}.
172
+ *
173
+ * @group Core
174
+ */
167
175
  export interface FsDep {
168
176
  readonly fs: Fs;
169
177
  }
170
178
 
171
- /** A file system path, or a `file:` URL. */
179
+ /**
180
+ * A file system path, or a `file:` URL.
181
+ *
182
+ * @group Core
183
+ */
172
184
  export type FsPath = string | URL;
173
185
 
174
- /** Supported text encodings. */
186
+ /**
187
+ * Supported text encodings.
188
+ *
189
+ * @group Core
190
+ */
175
191
  export type FsEncoding =
176
192
  | "ascii"
177
193
  | "utf8"
@@ -186,7 +202,11 @@ export type FsEncoding =
186
202
  | "binary"
187
203
  | "hex";
188
204
 
189
- /** Supported file opening modes for {@link Fs.writeFile}. */
205
+ /**
206
+ * Supported file opening modes for {@link Fs.writeFile}.
207
+ *
208
+ * @group Core
209
+ */
190
210
  export type FsOpenFlag =
191
211
  | "a"
192
212
  | "ax"
@@ -202,7 +222,11 @@ export type FsOpenFlag =
202
222
  | "w+"
203
223
  | "wx+";
204
224
 
205
- /** Reads bytes by default, or text when an encoding is specified. */
225
+ /**
226
+ * Reads bytes by default, or text when an encoding is specified.
227
+ *
228
+ * @group Core
229
+ */
206
230
  export interface FsReadFile {
207
231
  (path: FsPath): Task<Uint8Array, FsError>;
208
232
  (
@@ -211,7 +235,11 @@ export interface FsReadFile {
211
235
  ): Task<string, FsError>;
212
236
  }
213
237
 
214
- /** Options for {@link Fs.writeFile}. */
238
+ /**
239
+ * Options for {@link Fs.writeFile}.
240
+ *
241
+ * @group Options
242
+ */
215
243
  export interface FsWriteFileOptions {
216
244
  /** Encoding of string data. Defaults to `utf8`. */
217
245
  readonly encoding?: FsEncoding;
@@ -221,13 +249,21 @@ export interface FsWriteFileOptions {
221
249
  readonly flag?: FsOpenFlag;
222
250
  }
223
251
 
224
- /** Options for {@link Fs.readDirectory}. */
252
+ /**
253
+ * Options for {@link Fs.readDirectory}.
254
+ *
255
+ * @group Options
256
+ */
225
257
  export interface FsReadDirectoryOptions {
226
258
  /** Includes entries from nested directories. Defaults to `false`. */
227
259
  readonly recursive?: boolean;
228
260
  }
229
261
 
230
- /** Options for {@link Fs.createDirectory}. */
262
+ /**
263
+ * Options for {@link Fs.createDirectory}.
264
+ *
265
+ * @group Options
266
+ */
231
267
  export interface FsCreateDirectoryOptions {
232
268
  /** Creates missing parents and accepts an existing directory. */
233
269
  readonly recursive?: boolean;
@@ -235,7 +271,11 @@ export interface FsCreateDirectoryOptions {
235
271
  readonly mode?: number;
236
272
  }
237
273
 
238
- /** Options for {@link Fs.copy}. */
274
+ /**
275
+ * Options for {@link Fs.copy}.
276
+ *
277
+ * @group Options
278
+ */
239
279
  export interface FsCopyOptions {
240
280
  /**
241
281
  * Node's `force` option. Replaces existing files; `false` skips them unless
@@ -253,13 +293,21 @@ export interface FsCopyOptions {
253
293
  readonly preserveTimestamps?: boolean;
254
294
  }
255
295
 
256
- /** Options for {@link Fs.copyFile}. */
296
+ /**
297
+ * Options for {@link Fs.copyFile}.
298
+ *
299
+ * @group Options
300
+ */
257
301
  export interface FsCopyFileOptions {
258
302
  /** Replaces an existing destination file. Defaults to `false`. */
259
303
  readonly overwrite?: boolean;
260
304
  }
261
305
 
262
- /** Options for {@link Fs.remove}. */
306
+ /**
307
+ * Options for {@link Fs.remove}.
308
+ *
309
+ * @group Options
310
+ */
263
311
  export interface FsRemoveOptions {
264
312
  /** Removes directories and their contents. */
265
313
  readonly recursive?: boolean;
@@ -277,7 +325,11 @@ export interface FsRemoveOptions {
277
325
  readonly retryDelay?: number;
278
326
  }
279
327
 
280
- /** File metadata as data, with Node's numeric and timestamp field names. */
328
+ /**
329
+ * File metadata as data, with Node's numeric and timestamp field names.
330
+ *
331
+ * @group Core
332
+ */
281
333
  export interface FsMetadata {
282
334
  readonly type: FsEntryType;
283
335
  readonly dev: number;
@@ -300,7 +352,11 @@ export interface FsMetadata {
300
352
  readonly birthtime: Date;
301
353
  }
302
354
 
303
- /** The kind of file system entry described by {@link FsMetadata}. */
355
+ /**
356
+ * The kind of file system entry described by {@link FsMetadata}.
357
+ *
358
+ * @group Core
359
+ */
304
360
  export type FsEntryType =
305
361
  | "File"
306
362
  | "Directory"
@@ -311,7 +367,11 @@ export type FsEntryType =
311
367
  | "Socket"
312
368
  | "Unknown";
313
369
 
314
- /** Options for {@link Fs.createTempDirectory}. */
370
+ /**
371
+ * Options for {@link Fs.createTempDirectory}.
372
+ *
373
+ * @group Options
374
+ */
315
375
  export interface FsCreateTempDirectoryOptions {
316
376
  /** Existing parent directory. Defaults to the system temporary directory. */
317
377
  readonly directory?: string;
@@ -319,12 +379,20 @@ export interface FsCreateTempDirectoryOptions {
319
379
  readonly prefix?: string;
320
380
  }
321
381
 
322
- /** A temporary directory removed, with its contents, on asynchronous disposal. */
382
+ /**
383
+ * A temporary directory removed, with its contents, on asynchronous disposal.
384
+ *
385
+ * @group Core
386
+ */
323
387
  export interface FsTempDirectory extends AsyncDisposable {
324
388
  readonly path: string;
325
389
  }
326
390
 
327
- /** A failed file system operation. */
391
+ /**
392
+ * A failed file system operation.
393
+ *
394
+ * @group Errors
395
+ */
328
396
  export interface FsError extends Typed<"FsError"> {
329
397
  readonly reason: FsErrorReason;
330
398
  /**
@@ -345,7 +413,11 @@ export interface FsError extends Typed<"FsError"> {
345
413
  readonly message: string;
346
414
  }
347
415
 
348
- /** Why a file system operation failed, mapped from the platform's error code. */
416
+ /**
417
+ * Why a file system operation failed, mapped from the platform's error code.
418
+ *
419
+ * @group Errors
420
+ */
349
421
  export type FsErrorReason =
350
422
  | "NotFound"
351
423
  | "AlreadyExists"
@@ -392,6 +464,8 @@ export type FsErrorReason =
392
464
  *
393
465
  * assertOk(await run(saveMessage));
394
466
  * ```
467
+ *
468
+ * @group Testing
395
469
  */
396
470
  export const testCreateFs = (overrides: Partial<Fs> = {}): Fs => ({
397
471
  readFile: createUnexpectedFsOperation("readFile"),
package/src/Identicon.ts CHANGED
@@ -37,6 +37,7 @@ export type IdenticonStyle = "github" | "quadrant" | "gradient" | "sutnar";
37
37
  * assertTrue,
38
38
  * createIdFromString,
39
39
  * createIdenticon,
40
+ * testTodoId,
40
41
  * } from "@evolu/common";
41
42
  *
42
43
  * const id = createIdFromString("identicon-example");
@@ -47,8 +48,7 @@ export type IdenticonStyle = "github" | "quadrant" | "gradient" | "sutnar";
47
48
  * );
48
49
  *
49
50
  * // Branded IDs work too.
50
- * const todoId = createIdFromString<"Todo">("todo-1");
51
- * const todoSvg = createIdenticon(todoId);
51
+ * const todoSvg = createIdenticon(testTodoId);
52
52
  *
53
53
  * assertTrue(svg.startsWith("<svg"));
54
54
  * assertEqual(new Set([svg, ...alternativeSvgs]).size, 4);
@@ -22,6 +22,8 @@ import { constVoid } from "./Function.ts";
22
22
  * a warning may come late or, in short-lived processes, never. It is a
23
23
  * development canary, not a guarantee. Production uses
24
24
  * {@link noopLeakDetector}.
25
+ *
26
+ * @group Core
25
27
  */
26
28
  export interface LeakDetector {
27
29
  /**
@@ -38,7 +40,11 @@ export interface LeakDetector {
38
40
  readonly untrack: (unregisterToken: object) => void;
39
41
  }
40
42
 
41
- /** Describes a tracked handle for {@link LeakDetector.track}. */
43
+ /**
44
+ * Describes a tracked handle for {@link LeakDetector.track}.
45
+ *
46
+ * @group Core
47
+ */
42
48
  export interface Leak {
43
49
  /** Handle name used in the warning, for example `"Lease"`. */
44
50
  readonly name: string;
@@ -50,6 +56,7 @@ export interface Leak {
50
56
  /**
51
57
  * Dependency wrapper for {@link LeakDetector}.
52
58
  *
59
+ * @group Core
53
60
  * @see {@link LeakDetector}
54
61
  */
55
62
  export interface LeakDetectorDep {
@@ -61,6 +68,8 @@ export interface LeakDetectorDep {
61
68
  *
62
69
  * Capturing a stack per track call is too expensive for production; use
63
70
  * {@link noopLeakDetector} there.
71
+ *
72
+ * @group Core
64
73
  */
65
74
  export const createLeakDetector = (deps: ConsoleDep): LeakDetector => {
66
75
  if (typeof globalThis.FinalizationRegistry !== "function")
@@ -86,7 +95,11 @@ export const createLeakDetector = (deps: ConsoleDep): LeakDetector => {
86
95
  };
87
96
  };
88
97
 
89
- /** No-op {@link LeakDetector} for production. */
98
+ /**
99
+ * No-op {@link LeakDetector} for production.
100
+ *
101
+ * @group Core
102
+ */
90
103
  export const noopLeakDetector: LeakDetector = {
91
104
  track: constVoid,
92
105
  untrack: constVoid,
@@ -118,6 +131,7 @@ const reportLeak =
118
131
  /**
119
132
  * Test {@link LeakDetector} with deterministic collection.
120
133
  *
134
+ * @group Testing
121
135
  * @see {@link testCreateLeakDetector}
122
136
  */
123
137
  export interface TestLeakDetector extends LeakDetector {
@@ -136,13 +150,18 @@ export interface TestLeakDetector extends LeakDetector {
136
150
  /**
137
151
  * Dependency wrapper for {@link TestLeakDetector}.
138
152
  *
153
+ * @group Testing
139
154
  * @see {@link TestLeakDetector}
140
155
  */
141
156
  export interface TestLeakDetectorDep extends LeakDetectorDep {
142
157
  readonly leakDetector: TestLeakDetector;
143
158
  }
144
159
 
145
- /** Creates {@link TestLeakDetector}. */
160
+ /**
161
+ * Creates {@link TestLeakDetector}.
162
+ *
163
+ * @group Testing
164
+ */
146
165
  export const testCreateLeakDetector = (deps: ConsoleDep): TestLeakDetector => {
147
166
  const trackedLeaksByToken = new Map<object, ReadonlyArray<TrackedLeak>>();
148
167
  const report = reportLeak(deps);