@evolu/common 6.0.1-preview.17 → 6.0.1-preview.19

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 (42) hide show
  1. package/dist/src/Brand.d.ts +75 -0
  2. package/dist/src/Brand.d.ts.map +1 -0
  3. package/dist/src/Brand.js +1 -0
  4. package/dist/src/Callbacks.d.ts +1 -1
  5. package/dist/src/Crypto.d.ts +8 -2
  6. package/dist/src/Crypto.d.ts.map +1 -1
  7. package/dist/src/Crypto.js +15 -6
  8. package/dist/src/Evolu/Db.d.ts +4 -4
  9. package/dist/src/Evolu/Owner.d.ts +1 -1
  10. package/dist/src/Evolu/Protocol.d.ts +1 -1
  11. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  12. package/dist/src/Evolu/Query.d.ts +2 -1
  13. package/dist/src/Evolu/Query.d.ts.map +1 -1
  14. package/dist/src/Evolu/Timestamp.d.ts +1 -1
  15. package/dist/src/Number.d.ts +2 -1
  16. package/dist/src/Number.d.ts.map +1 -1
  17. package/dist/src/Result.d.ts +11 -37
  18. package/dist/src/Result.d.ts.map +1 -1
  19. package/dist/src/Result.js +3 -241
  20. package/dist/src/Sqlite.d.ts +2 -1
  21. package/dist/src/Sqlite.d.ts.map +1 -1
  22. package/dist/src/Type.d.ts +2 -1
  23. package/dist/src/Type.d.ts.map +1 -1
  24. package/dist/src/Types.d.ts +0 -74
  25. package/dist/src/Types.d.ts.map +1 -1
  26. package/dist/src/index.d.ts +1 -0
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/index.js +1 -0
  29. package/package.json +5 -5
  30. package/src/Brand.ts +75 -0
  31. package/src/Callbacks.ts +1 -1
  32. package/src/Crypto.ts +21 -8
  33. package/src/Evolu/Protocol.ts +2 -1
  34. package/src/Evolu/Query.ts +2 -1
  35. package/src/Evolu/Storage.ts +1 -1
  36. package/src/Evolu/Timestamp.ts +1 -1
  37. package/src/Number.ts +2 -6
  38. package/src/Result.ts +12 -40
  39. package/src/Sqlite.ts +2 -1
  40. package/src/Type.ts +2 -1
  41. package/src/Types.ts +0 -76
  42. package/src/index.ts +1 -0
package/src/Brand.ts ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A utility interface for creating branded types.
3
+ *
4
+ * Branded types enhance type safety by differentiating otherwise identical base
5
+ * types, such as `number` or `string`, to enforce stricter type checks.
6
+ *
7
+ * Supports multiple brands, allowing types to act like flags.
8
+ *
9
+ * ### Example 1: Single Brand
10
+ *
11
+ * ```ts
12
+ * // A branded type definition
13
+ * type UserId = number & Brand<"UserId">;
14
+ *
15
+ * // A function that creates `UserId` values.
16
+ * // Casting with `as UserId` is unsafe, so `createUserId` must be unit-tested.
17
+ * const createUserId = (): UserId => {
18
+ * return 123 as UserId; // Unsafe casting
19
+ * };
20
+ *
21
+ * const userId = createUserId();
22
+ *
23
+ * // A function that accepts only `UserId`.
24
+ * const getUser = (id: UserId) => {
25
+ * // Implementation
26
+ * };
27
+ *
28
+ * getUser(userId); // ✅ Valid
29
+ * getUser(123); // ❌ TypeScript error
30
+ * getUser("123"); // ❌ TypeScript error
31
+ * ```
32
+ *
33
+ * ### Example 2: Multiple Brands
34
+ *
35
+ * ```ts
36
+ * // Define branded types
37
+ * type Min1 = string & Brand<"Min1">;
38
+ * type Max100 = string & Brand<"Max100">;
39
+ * type Min1Max100 = string & Brand<"Min1" | "Max100">;
40
+ *
41
+ * // Functions requiring specific brands
42
+ * const requiresMin1 = (value: Min1): void => {};
43
+ * const requiresMax100 = (value: Max100): void => {};
44
+ *
45
+ * // Values with single brands
46
+ * const min1Value: Min1 = "hello" as Min1;
47
+ * const max100Value: Max100 = "world" as Max100;
48
+ *
49
+ * // Value with multiple brands
50
+ * const min1Max100Value: Min1Max100 = "typescript" as Min1Max100;
51
+ *
52
+ * // Valid cases
53
+ * requiresMin1(min1Value); // ✅ Valid
54
+ * requiresMax100(max100Value); // ✅ Valid
55
+ * requiresMin1(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Min1
56
+ * requiresMax100(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Max100
57
+ * ```
58
+ */
59
+ export interface Brand<B extends string> {
60
+ readonly [__brand]: Readonly<Record<B, true>>;
61
+ }
62
+
63
+ declare const __brand: unique symbol;
64
+
65
+ /**
66
+ * Determines whether a type `T` is a branded type.
67
+ *
68
+ * Works with any base type intersected with a `Brand`.
69
+ *
70
+ * ### Examples
71
+ *
72
+ * - `IsBranded<string>` -> false
73
+ * - `IsBranded<string & Brand<"X">>` -> true
74
+ */
75
+ export type IsBranded<T> = T extends Brand<string> ? true : false;
package/src/Callbacks.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NanoIdLibDep } from "./NanoId.js";
2
- import { Brand } from "./Types.js";
2
+ import { Brand } from "./Brand.js";
3
3
 
4
4
  /**
5
5
  * Manages one-time callback functions.
package/src/Crypto.ts CHANGED
@@ -20,8 +20,9 @@ import {
20
20
  NonNegativeInt,
21
21
  Uint8Array,
22
22
  } from "./Type.js";
23
- import { Brand } from "./Types.js";
23
+ import { Brand } from "./Brand.js";
24
24
  import { assert } from "./Assert.js";
25
+ import { utf8ToBytes } from "./Buffer.js";
25
26
 
26
27
  /** `Uint8Array` created by {@link createRandomBytes}. */
27
28
  export type RandomBytes = Uint8Array & Brand<"RandomBytes">;
@@ -56,7 +57,7 @@ export const mnemonicToMnemonicSeed = (mnemonic: Mnemonic): MnemonicSeed =>
56
57
  * https://github.com/satoshilabs/slips/blob/master/slip-0021.md
57
58
  */
58
59
  export const createSlip21 = (
59
- seed: MnemonicSeed,
60
+ seed: Uint8Array,
60
61
  path: ReadonlyArray<string>,
61
62
  ): Uint8Array => {
62
63
  assert(
@@ -64,17 +65,29 @@ export const createSlip21 = (
64
65
  `Unusual SLIP-0021 seed length: ${seed.length} bytes`,
65
66
  );
66
67
 
67
- let m = hmac(sha512, "Symmetric key seed", seed);
68
+ let m = hmac(sha512, utf8ToBytes("Symmetric key seed"), seed);
68
69
  for (const component of path) {
69
- const p = new TextEncoder().encode(component);
70
- const e = new globalThis.Uint8Array(p.byteLength + 1);
71
- e[0] = 0;
72
- e.set(p, 1);
73
- m = hmac(sha512, m.slice(0, 32), e);
70
+ m = deriveSlip21Node(component, m);
74
71
  }
75
72
  return m.slice(32, 64);
76
73
  };
77
74
 
75
+ /**
76
+ * Derives a single node in the SLIP-21 hierarchical key derivation.
77
+ *
78
+ * @see {@link createSlip21}
79
+ */
80
+ export const deriveSlip21Node = (
81
+ component: string,
82
+ m: Uint8Array,
83
+ ): Uint8Array => {
84
+ const p = utf8ToBytes(component);
85
+ const e = new globalThis.Uint8Array(p.byteLength + 1);
86
+ e[0] = 0;
87
+ e.set(p, 1);
88
+ return hmac(sha512, m.slice(0, 32), e);
89
+ };
90
+
78
91
  /**
79
92
  * Creates a 21-character Base64URL ID (also known as nanoid) from a SLIP-21
80
93
  * derived key.
@@ -183,7 +183,8 @@ import {
183
183
  PositiveInt,
184
184
  record,
185
185
  } from "../Type.js";
186
- import { Brand, Predicate } from "../Types.js";
186
+ import { Predicate } from "../Types.js";
187
+ import { Brand } from "../Brand.js";
187
188
  import { Owner, OwnerId, WriteKey, writeKeyLength } from "./Owner.js";
188
189
  import {
189
190
  BinaryTimestamp,
@@ -8,7 +8,8 @@ import {
8
8
  SqliteValue,
9
9
  } from "../Sqlite.js";
10
10
  import { Store, StoreSubscribe } from "../Store.js";
11
- import { Brand, Simplify } from "../Types.js";
11
+ import { Simplify } from "../Types.js";
12
+ import { Brand } from "../Brand.js";
12
13
 
13
14
  /**
14
15
  * A type-safe SQL query.
@@ -29,7 +29,7 @@ import { RandomDep } from "../Random.js";
29
29
  import { ok, Result } from "../Result.js";
30
30
  import { sql, SqliteDep, SqliteError } from "../Sqlite.js";
31
31
  import { Int64String, NonNegativeInt, PositiveInt } from "../Type.js";
32
- import { Brand } from "../Types.js";
32
+ import { Brand } from "../Brand.js";
33
33
  import { OwnerId } from "./Owner.js";
34
34
  import {
35
35
  BinaryOwnerId,
@@ -13,7 +13,7 @@ import {
13
13
  regex,
14
14
  String,
15
15
  } from "../Type.js";
16
- import { Brand } from "../Types.js";
16
+ import { Brand } from "../Brand.js";
17
17
 
18
18
  export interface TimestampConfig {
19
19
  /**
package/src/Number.ts CHANGED
@@ -2,12 +2,8 @@ import { NonEmptyReadonlyArray } from "./Array.js";
2
2
  import { assertNonEmptyReadonlyArray } from "./Assert.js";
3
3
  import { err, ok, Result } from "./Result.js";
4
4
  import { NonNegativeInt, PositiveInt } from "./Type.js";
5
- import {
6
- IntentionalNever,
7
- IsBranded,
8
- Predicate,
9
- WidenLiteral,
10
- } from "./Types.js";
5
+ import { IntentionalNever, Predicate, WidenLiteral } from "./Types.js";
6
+ import { IsBranded } from "./Brand.js";
11
7
 
12
8
  export const increment = (n: number): number => n + 1;
13
9
 
package/src/Result.ts CHANGED
@@ -1,37 +1,17 @@
1
- /* eslint-disable jsdoc/no-undefined-types */
2
1
  /**
3
2
  * 🛡️ Type-safe errors
4
3
  *
5
- * ## Intro
6
- *
7
4
  * The problem with throwing an exception in JavaScript is that the caught error
8
5
  * is always of an unknown type. The unknown type is a problem because we can't
9
6
  * be sure all errors have been handled because the TypeScript compiler can't
10
- * help us.
11
- *
12
- * Some other languages like Rust 🦀 or Haskell 📚 use a type-safe approach to
13
- * error handling, where errors are explicitly represented as part of the return
14
- * type, such as Result or Either, allowing the developer to handle all errors
15
- * safely. ✅
16
- *
17
- * ✨ Evolu uses {@link Result}, and it looks like this:
18
- *
19
- * ```ts
20
- * type Result<T, E> = Ok<T> | Err<E>;
21
- *
22
- * interface Ok<T> {
23
- * readonly ok: true;
24
- * readonly value: T;
25
- * }
26
- *
27
- * interface Err<E> {
28
- * readonly ok: false;
29
- * readonly error: E;
30
- * }
7
+ * tell us. Some other languages like Rust 🦀 or Haskell 📚 use a type-safe
8
+ * approach to error handling, where errors are explicitly represented as part
9
+ * of the return type, such as Result or Either, allowing the developer to
10
+ * handle all errors safely.
31
11
  *
32
- * const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
33
- * const err = <E>(error: E): Err<E> => ({ ok: false, error });
34
- * ```
12
+ * This type models that approach in TypeScript. A `Result` can be either
13
+ * {@link Ok} (success) or {@link Err} (error). Use {@link ok} to create a
14
+ * successful result and {@link err} to create an error result.
35
15
  *
36
16
  * Now let's look at how `Result` can be used for safe JSON parsing:
37
17
  *
@@ -198,9 +178,8 @@
198
178
  * ### How do I handle an array of operations and short-circuit on the first error?
199
179
  *
200
180
  * If you have an array of operations (not results), you should make them
201
- * _lazy_—that is, represent each operation as a function (see `LazyValue` in
202
- * `Function.ts`). This way, you only execute each operation as needed, and can
203
- * stop on the first error:
181
+ * _lazy_—that is, represent each operation as a function. This way, you only
182
+ * execute each operation as needed, and can stop on the first error:
204
183
  *
205
184
  * ```ts
206
185
  * import type { LazyValue } from "./Function";
@@ -235,15 +214,6 @@
235
214
  * developers. While monads and functional helpers can be powerful, they often
236
215
  * obscure control flow and make debugging harder. Evolu's approach keeps error
237
216
  * handling explicit and straightforward.
238
- *
239
- * @module
240
- */
241
-
242
- /**
243
- * A `Result` can be either {@link Ok} (success) or {@link Err} (error).
244
- *
245
- * Use {@link ok} to create a successful result and {@link err} to create an error
246
- * result.
247
217
  */
248
218
  export type Result<T, E> = Ok<T> | Err<E>;
249
219
 
@@ -356,12 +326,14 @@ export const err = <E>(error: E): Err<E> => ({ ok: false, error });
356
326
  * const config = getOrThrow(loadConfig());
357
327
  * // Safe to use config here
358
328
  * ```
329
+ *
330
+ * Throws: `Error` with the original error attached as `cause`.
359
331
  */
360
332
  export const getOrThrow = <T, E>(result: Result<T, E>): T => {
361
333
  if (result.ok) {
362
334
  return result.value;
363
335
  } else {
364
- throw new Error(`Result error: ${JSON.stringify(result.error)}`);
336
+ throw new Error("getOrThrow failed", { cause: result.error });
365
337
  }
366
338
  };
367
339
 
package/src/Sqlite.ts CHANGED
@@ -11,7 +11,8 @@ import {
11
11
  Uint8Array,
12
12
  union,
13
13
  } from "./Type.js";
14
- import { Brand, Predicate, IntentionalNever } from "./Types.js";
14
+ import { Predicate, IntentionalNever } from "./Types.js";
15
+ import { Brand } from "./Brand.js";
15
16
 
16
17
  /**
17
18
  * SQLite driver interface. This is the minimal interface that platform-specific
package/src/Type.ts CHANGED
@@ -80,7 +80,8 @@ import { NanoIdLibDep } from "./NanoId.js";
80
80
  import { isPlainObject } from "./Object.js";
81
81
  import { Err, err, Ok, ok, Result, trySync } from "./Result.js";
82
82
  import { safelyStringifyUnknownValue } from "./String.js";
83
- import type { Brand, Literal, Simplify, WidenLiteral } from "./Types.js";
83
+ import type { Literal, Simplify, WidenLiteral } from "./Types.js";
84
+ import type { Brand } from "./Brand.js";
84
85
  import { IntentionalNever } from "./Types.js";
85
86
 
86
87
  export interface Type<
package/src/Types.ts CHANGED
@@ -78,82 +78,6 @@ export type NullablePartial<
78
78
  */
79
79
  export type IntentionalNever = never;
80
80
 
81
- /**
82
- * A utility interface for creating branded types.
83
- *
84
- * Branded types enhance type safety by differentiating otherwise identical base
85
- * types, such as `number` or `string`, to enforce stricter type checks.
86
- *
87
- * Supports multiple brands, allowing types to act like flags.
88
- *
89
- * ### Example 1: Single Brand
90
- *
91
- * ```ts
92
- * // A branded type definition
93
- * type UserId = number & Brand<"UserId">;
94
- *
95
- * // A function that creates `UserId` values.
96
- * // Casting with `as UserId` is unsafe, so `createUserId` must be unit-tested.
97
- * const createUserId = (): UserId => {
98
- * return 123 as UserId; // Unsafe casting
99
- * };
100
- *
101
- * const userId = createUserId();
102
- *
103
- * // A function that accepts only `UserId`.
104
- * const getUser = (id: UserId) => {
105
- * // Implementation
106
- * };
107
- *
108
- * getUser(userId); // ✅ Valid
109
- * getUser(123); // ❌ TypeScript error
110
- * getUser("123"); // ❌ TypeScript error
111
- * ```
112
- *
113
- * ### Example 2: Multiple Brands
114
- *
115
- * ```ts
116
- * // Define branded types
117
- * type Min1 = string & Brand<"Min1">;
118
- * type Max100 = string & Brand<"Max100">;
119
- * type Min1Max100 = string & Brand<"Min1" | "Max100">;
120
- *
121
- * // Functions requiring specific brands
122
- * const requiresMin1 = (value: Min1): void => {};
123
- * const requiresMax100 = (value: Max100): void => {};
124
- *
125
- * // Values with single brands
126
- * const min1Value: Min1 = "hello" as Min1;
127
- * const max100Value: Max100 = "world" as Max100;
128
- *
129
- * // Value with multiple brands
130
- * const min1Max100Value: Min1Max100 = "typescript" as Min1Max100;
131
- *
132
- * // Valid cases
133
- * requiresMin1(min1Value); // ✅ Valid
134
- * requiresMax100(max100Value); // ✅ Valid
135
- * requiresMin1(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Min1
136
- * requiresMax100(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Max100
137
- * ```
138
- */
139
- export interface Brand<B extends string> {
140
- readonly [__brand]: Readonly<Record<B, true>>;
141
- }
142
-
143
- declare const __brand: unique symbol;
144
-
145
- /**
146
- * Determines whether a type `T` is a branded type.
147
- *
148
- * Works with any base type intersected with a `Brand`.
149
- *
150
- * ### Examples
151
- *
152
- * - `IsBranded<string>` -> false
153
- * - `IsBranded<string & Brand<"X">>` -> true
154
- */
155
- export type IsBranded<T> = T extends Brand<string> ? true : false;
156
-
157
81
  /**
158
82
  * String | number | bigint | boolean | undefined | null
159
83
  *
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./Array.js";
2
2
  export * from "./Assert.js";
3
3
  export * from "./BigInt.js";
4
+ export * from "./Brand.js";
4
5
  export * from "./Buffer.js";
5
6
  export * from "./Callbacks.js";
6
7
  export * from "./Console.js";