@evolu/common 8.0.0-next.4 → 8.0.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 (237) hide show
  1. package/dist/src/Array.d.ts +279 -173
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +105 -101
  4. package/dist/src/Assert.d.ts +44 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +44 -32
  7. package/dist/src/BigInt.d.ts +6 -3
  8. package/dist/src/BigInt.d.ts.map +1 -1
  9. package/dist/src/BigInt.js +5 -2
  10. package/dist/src/Brand.d.ts +27 -40
  11. package/dist/src/Brand.d.ts.map +1 -1
  12. package/dist/src/Buffer.d.ts +27 -11
  13. package/dist/src/Buffer.d.ts.map +1 -1
  14. package/dist/src/Cache.d.ts +9 -4
  15. package/dist/src/Cache.d.ts.map +1 -1
  16. package/dist/src/Cache.js +8 -3
  17. package/dist/src/Callbacks.d.ts +30 -19
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Console.d.ts +145 -66
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +91 -43
  22. package/dist/src/Crypto.d.ts +85 -42
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +44 -20
  25. package/dist/src/Eq.d.ts +87 -21
  26. package/dist/src/Eq.d.ts.map +1 -1
  27. package/dist/src/Eq.js +67 -19
  28. package/dist/src/Error.d.ts +5 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Error.js +16 -0
  31. package/dist/src/Function.d.ts +119 -58
  32. package/dist/src/Function.d.ts.map +1 -1
  33. package/dist/src/Function.js +86 -43
  34. package/dist/src/Http.d.ts +394 -0
  35. package/dist/src/Http.d.ts.map +1 -0
  36. package/dist/src/Http.js +102 -0
  37. package/dist/src/Identicon.d.ts +16 -7
  38. package/dist/src/Identicon.d.ts.map +1 -1
  39. package/dist/src/Identicon.js +14 -5
  40. package/dist/src/LeakDetector.d.ts +88 -0
  41. package/dist/src/LeakDetector.d.ts.map +1 -0
  42. package/dist/src/LeakDetector.js +86 -0
  43. package/dist/src/LockManager.d.ts +18 -9
  44. package/dist/src/LockManager.d.ts.map +1 -1
  45. package/dist/src/LockManager.js +7 -8
  46. package/dist/src/Lookup.d.ts +43 -13
  47. package/dist/src/Lookup.d.ts.map +1 -1
  48. package/dist/src/Lookup.js +28 -8
  49. package/dist/src/Microtask.d.ts +1 -1
  50. package/dist/src/Microtask.d.ts.map +1 -1
  51. package/dist/src/Microtask.js +3 -7
  52. package/dist/src/Number.d.ts +49 -13
  53. package/dist/src/Number.d.ts.map +1 -1
  54. package/dist/src/Number.js +34 -5
  55. package/dist/src/Object.d.ts +160 -40
  56. package/dist/src/Object.d.ts.map +1 -1
  57. package/dist/src/Object.js +92 -46
  58. package/dist/src/Option.d.ts +10 -7
  59. package/dist/src/Option.d.ts.map +1 -1
  60. package/dist/src/Option.js +9 -6
  61. package/dist/src/Order.d.ts +21 -21
  62. package/dist/src/Order.d.ts.map +1 -1
  63. package/dist/src/Order.js +18 -21
  64. package/dist/src/Platform.d.ts +31 -0
  65. package/dist/src/Platform.d.ts.map +1 -1
  66. package/dist/src/Platform.js +98 -0
  67. package/dist/src/Random.d.ts +32 -17
  68. package/dist/src/Random.d.ts.map +1 -1
  69. package/dist/src/Random.js +12 -8
  70. package/dist/src/Redacted.d.ts +43 -29
  71. package/dist/src/Redacted.d.ts.map +1 -1
  72. package/dist/src/Redacted.js +14 -3
  73. package/dist/src/Ref.d.ts +11 -3
  74. package/dist/src/Ref.d.ts.map +1 -1
  75. package/dist/src/Ref.js +2 -2
  76. package/dist/src/RefCount.d.ts +2 -6
  77. package/dist/src/RefCount.d.ts.map +1 -1
  78. package/dist/src/RefCount.js +33 -90
  79. package/dist/src/Relation.d.ts +70 -5
  80. package/dist/src/Relation.d.ts.map +1 -1
  81. package/dist/src/Relation.js +77 -2
  82. package/dist/src/Resource.d.ts +590 -155
  83. package/dist/src/Resource.d.ts.map +1 -1
  84. package/dist/src/Resource.js +592 -343
  85. package/dist/src/Result.d.ts +838 -191
  86. package/dist/src/Result.d.ts.map +1 -1
  87. package/dist/src/Result.js +163 -102
  88. package/dist/src/Schedule.d.ts +684 -231
  89. package/dist/src/Schedule.d.ts.map +1 -1
  90. package/dist/src/Schedule.js +766 -295
  91. package/dist/src/Set.d.ts +89 -40
  92. package/dist/src/Set.d.ts.map +1 -1
  93. package/dist/src/Set.js +50 -29
  94. package/dist/src/Sqlite.d.ts +29 -72
  95. package/dist/src/Sqlite.d.ts.map +1 -1
  96. package/dist/src/Sqlite.js +41 -46
  97. package/dist/src/StackTrace.d.ts +40 -0
  98. package/dist/src/StackTrace.d.ts.map +1 -0
  99. package/dist/src/StackTrace.js +140 -0
  100. package/dist/src/Store.d.ts +5 -4
  101. package/dist/src/Store.d.ts.map +1 -1
  102. package/dist/src/Store.js +5 -4
  103. package/dist/src/String.d.ts +7 -1
  104. package/dist/src/String.d.ts.map +1 -1
  105. package/dist/src/String.js +20 -7
  106. package/dist/src/Task.d.ts +3572 -1770
  107. package/dist/src/Task.d.ts.map +1 -1
  108. package/dist/src/Task.js +1879 -1274
  109. package/dist/src/Test.d.ts +17 -125
  110. package/dist/src/Test.d.ts.map +1 -1
  111. package/dist/src/Test.js +17 -68
  112. package/dist/src/Time.d.ts +108 -49
  113. package/dist/src/Time.d.ts.map +1 -1
  114. package/dist/src/Time.js +172 -73
  115. package/dist/src/Type.d.ts +3542 -1926
  116. package/dist/src/Type.d.ts.map +1 -1
  117. package/dist/src/Type.js +3070 -1784
  118. package/dist/src/Types.d.ts +200 -108
  119. package/dist/src/Types.d.ts.map +1 -1
  120. package/dist/src/Types.js +39 -8
  121. package/dist/src/WebSocket.d.ts +48 -23
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +15 -6
  124. package/dist/src/Worker.d.ts +94 -36
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +58 -24
  127. package/dist/src/index.d.ts +55 -52
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +3 -1
  130. package/dist/src/intl/cs.d.ts +94 -0
  131. package/dist/src/intl/cs.d.ts.map +1 -0
  132. package/dist/src/intl/cs.js +220 -0
  133. package/dist/src/intl/index.d.ts +7 -0
  134. package/dist/src/intl/index.d.ts.map +1 -0
  135. package/dist/src/intl/index.js +6 -0
  136. package/dist/src/local-first/Db.d.ts +12 -8
  137. package/dist/src/local-first/Db.d.ts.map +1 -1
  138. package/dist/src/local-first/Db.js +109 -132
  139. package/dist/src/local-first/Error.d.ts +4 -4
  140. package/dist/src/local-first/Evolu.d.ts +338 -92
  141. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  142. package/dist/src/local-first/Evolu.js +47 -55
  143. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  144. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.d.ts +61 -24
  146. package/dist/src/local-first/Owner.d.ts.map +1 -1
  147. package/dist/src/local-first/Owner.js +45 -8
  148. package/dist/src/local-first/Protocol.d.ts +44 -31
  149. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  150. package/dist/src/local-first/Protocol.js +36 -13
  151. package/dist/src/local-first/Query.d.ts +147 -62
  152. package/dist/src/local-first/Query.d.ts.map +1 -1
  153. package/dist/src/local-first/Query.js +112 -58
  154. package/dist/src/local-first/Relay.d.ts +44 -14
  155. package/dist/src/local-first/Relay.d.ts.map +1 -1
  156. package/dist/src/local-first/Schema.d.ts +92 -41
  157. package/dist/src/local-first/Schema.d.ts.map +1 -1
  158. package/dist/src/local-first/Schema.js +25 -8
  159. package/dist/src/local-first/Shared.d.ts +23 -21
  160. package/dist/src/local-first/Shared.d.ts.map +1 -1
  161. package/dist/src/local-first/Shared.js +100 -57
  162. package/dist/src/local-first/Storage.d.ts +53 -32
  163. package/dist/src/local-first/Storage.d.ts.map +1 -1
  164. package/dist/src/local-first/Storage.js +194 -234
  165. package/dist/src/local-first/Timestamp.d.ts +21 -21
  166. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  167. package/dist/src/local-first/Timestamp.js +2 -2
  168. package/dist/src/local-first/index.d.ts +11 -11
  169. package/package.json +14 -13
  170. package/src/Array.ts +306 -205
  171. package/src/Assert.ts +54 -69
  172. package/src/BigInt.ts +6 -3
  173. package/src/Brand.ts +27 -40
  174. package/src/Buffer.ts +28 -12
  175. package/src/Cache.ts +9 -4
  176. package/src/Callbacks.ts +30 -19
  177. package/src/Console.ts +156 -84
  178. package/src/Crypto.ts +83 -41
  179. package/src/Eq.ts +95 -22
  180. package/src/Error.ts +8 -4
  181. package/src/Function.ts +167 -59
  182. package/src/Http.ts +549 -0
  183. package/src/Identicon.ts +16 -7
  184. package/src/LeakDetector.ts +182 -0
  185. package/src/LockManager.ts +43 -39
  186. package/src/Lookup.ts +62 -28
  187. package/src/Microtask.ts +19 -23
  188. package/src/Number.ts +64 -12
  189. package/src/Object.ts +173 -49
  190. package/src/Option.ts +10 -7
  191. package/src/Order.ts +23 -21
  192. package/src/Platform.ts +136 -0
  193. package/src/Polyfills.ts +6 -12
  194. package/src/Random.ts +32 -19
  195. package/src/Redacted.ts +44 -30
  196. package/src/Ref.ts +14 -6
  197. package/src/RefCount.ts +38 -80
  198. package/src/Relation.ts +202 -10
  199. package/src/Resource.ts +1199 -591
  200. package/src/Result.ts +930 -271
  201. package/src/Schedule.ts +859 -343
  202. package/src/Set.ts +89 -40
  203. package/src/Sqlite.ts +144 -138
  204. package/src/StackTrace.ts +230 -0
  205. package/src/Store.ts +8 -7
  206. package/src/String.ts +22 -5
  207. package/src/Task.ts +5101 -2832
  208. package/src/Test.ts +20 -168
  209. package/src/Time.ts +293 -90
  210. package/src/Type.ts +10034 -4117
  211. package/src/Types.ts +227 -110
  212. package/src/WebSocket.ts +64 -36
  213. package/src/Worker.ts +180 -72
  214. package/src/index.ts +55 -52
  215. package/src/intl/cs.ts +388 -0
  216. package/src/intl/index.ts +7 -0
  217. package/src/local-first/Db.ts +152 -179
  218. package/src/local-first/Error.ts +4 -4
  219. package/src/local-first/Evolu.ts +495 -250
  220. package/src/local-first/LocalAuth.ts +6 -12
  221. package/src/local-first/Owner.ts +58 -21
  222. package/src/local-first/Protocol.ts +49 -31
  223. package/src/local-first/Query.ts +146 -61
  224. package/src/local-first/Relay.ts +51 -21
  225. package/src/local-first/Schema.ts +109 -51
  226. package/src/local-first/Shared.ts +266 -226
  227. package/src/local-first/Storage.ts +266 -264
  228. package/src/local-first/Timestamp.ts +25 -18
  229. package/src/local-first/index.ts +11 -11
  230. package/dist/src/Skiplist.d.ts +0 -28
  231. package/dist/src/Skiplist.d.ts.map +0 -1
  232. package/dist/src/Skiplist.js +0 -64
  233. package/dist/src/Tracer.d.ts +0 -48
  234. package/dist/src/Tracer.d.ts.map +0 -1
  235. package/dist/src/Tracer.js +0 -6
  236. package/src/Skiplist.ts +0 -108
  237. package/src/Tracer.ts +0 -54
package/src/Types.ts CHANGED
@@ -4,19 +4,29 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import * as Kysely from "kysely";
8
- import type { TypeName } from "./Type.js";
9
-
10
7
  /**
11
8
  * A function that receives a value and returns nothing.
12
9
  *
13
10
  * Use for event handlers, observers, and async completion handlers.
14
11
  *
15
- * ### Example
12
+ * ### Completion callbacks
16
13
  *
17
14
  * ```ts
18
- * const onComplete: Callback<string> = (value) => console.log(value);
19
- * const queue = new Set<Callback<Result<Data, Error>>>();
15
+ * import { ok, type Callback, type Result } from "@evolu/common";
16
+ *
17
+ * const completedValues: Array<string> = [];
18
+ * const onComplete: Callback<string> = (value) => {
19
+ * completedValues.push(value);
20
+ * };
21
+ * const queue = new Set<Callback<Result<string, Error>>>();
22
+ * queue.add((result) => {
23
+ * if (result.ok) completedValues.push(result.value);
24
+ * });
25
+ *
26
+ * onComplete("direct");
27
+ * for (const callback of queue) callback(ok("queued"));
28
+ *
29
+ * expect(completedValues).toEqual(["direct", "queued"]);
20
30
  * ```
21
31
  */
22
32
  export type Callback<T> = (value: T) => void;
@@ -26,13 +36,29 @@ export type Callback<T> = (value: T) => void;
26
36
  *
27
37
  * Use for subscriptions or callbacks that need abort-time teardown.
28
38
  *
29
- * ### Example
39
+ * ### Subscription teardown
30
40
  *
31
41
  * ```ts
42
+ * import type { CallbackWithTeardown } from "@evolu/common";
43
+ *
44
+ * interface EventSource {
45
+ * readonly start: () => void;
46
+ * readonly stop: () => void;
47
+ * }
48
+ *
49
+ * const events: Array<string> = [];
50
+ * const source: EventSource = {
51
+ * start: () => events.push("started"),
52
+ * stop: () => events.push("stopped"),
53
+ * };
32
54
  * const subscribe: CallbackWithTeardown<EventSource> = (source) => {
33
55
  * source.start();
34
- * return () => source.stop();
56
+ * return source.stop;
35
57
  * };
58
+ * const teardown = subscribe(source);
59
+ * if (teardown) teardown();
60
+ *
61
+ * expect(events).toEqual(["started", "stopped"]);
36
62
  * ```
37
63
  */
38
64
  export type CallbackWithTeardown<T> = (value: T) => void | (() => void);
@@ -42,13 +68,14 @@ export type CallbackWithTeardown<T> = (value: T) => void | (() => void);
42
68
  *
43
69
  * A predicate starts with an 'is' prefix, e.g., `isEven`.
44
70
  *
45
- * ### Example
71
+ * ### Filtering values
46
72
  *
47
73
  * ```ts
74
+ * import type { Predicate } from "@evolu/common";
75
+ *
48
76
  * const isEven: Predicate<number> = (n) => n % 2 === 0;
49
77
  *
50
- * const numbers = [1, 2, 3, 4];
51
- * const evenNumbers = numbers.filter(isEven); // [2, 4]
78
+ * expect([1, 2, 3, 4].filter(isEven)).toEqual([2, 4]);
52
79
  * ```
53
80
  */
54
81
  export type Predicate<T> = (value: T) => boolean;
@@ -58,14 +85,15 @@ export type Predicate<T> = (value: T) => boolean;
58
85
  *
59
86
  * Useful for callbacks that need both the element and its position.
60
87
  *
61
- * ### Example
88
+ * ### Filtering by position
62
89
  *
63
90
  * ```ts
91
+ * import type { PredicateWithIndex } from "@evolu/common";
92
+ *
64
93
  * const isEvenIndex: PredicateWithIndex<string> = (value, index) =>
65
94
  * index % 2 === 0;
66
95
  *
67
- * const items = ["a", "b", "c", "d"];
68
- * const evenIndexItems = items.filter(isEvenIndex); // ["a", "c"]
96
+ * expect(["a", "b", "c", "d"].filter(isEvenIndex)).toEqual(["a", "c"]);
69
97
  * ```
70
98
  */
71
99
  export type PredicateWithIndex<T> = (value: T, index: number) => boolean;
@@ -73,19 +101,25 @@ export type PredicateWithIndex<T> = (value: T, index: number) => boolean;
73
101
  /**
74
102
  * A type guard function that refines type `A` to a narrower type `B`.
75
103
  *
76
- * ### Example
104
+ * ### Narrowing a value
77
105
  *
78
106
  * ```ts
79
- * type Animal = { name: string };
80
- * type Dog = Animal & { breed: string };
107
+ * import type { Refinement } from "@evolu/common";
108
+ *
109
+ * interface Animal {
110
+ * readonly name: string;
111
+ * }
112
+ * interface Dog extends Animal {
113
+ * readonly breed: string;
114
+ * }
81
115
  *
82
116
  * const isDog: Refinement<Animal, Dog> = (animal): animal is Dog =>
83
117
  * "breed" in animal;
118
+ * const dog: Dog = { name: "Dog", breed: "Beagle" };
119
+ * const animal: Animal = dog;
120
+ * if (!isDog(animal)) throw new Error("Expected a dog");
84
121
  *
85
- * const animal: Animal = { name: "Dog", breed: "Beagle" };
86
- * if (isDog(animal)) {
87
- * console.log(animal.breed); // Safe access to `breed`
88
- * }
122
+ * expect(animal.breed).toBe("Beagle");
89
123
  * ```
90
124
  */
91
125
  export type Refinement<in A, out B extends A> = (a: A) => a is B;
@@ -97,17 +131,30 @@ export type Refinement<in A, out B extends A> = (a: A) => a is B;
97
131
  * Useful for callbacks that need both the element and its position while
98
132
  * maintaining type narrowing.
99
133
  *
100
- * ### Example
134
+ * ### Indexed refinement
101
135
  *
102
136
  * ```ts
103
- * type Item = { type: "number" | "string"; value: unknown };
137
+ * import { partitionArray, type RefinementWithIndex } from "@evolu/common";
104
138
  *
105
- * const isNumberItem: RefinementWithIndex<Item, Item & { type: "number" }> =
106
- * (item, index): item is Item & { type: "number" } =>
107
- * index > 0 && item.type === "number";
108
- *
109
- * const items: ReadonlyArray<Item> = [...];
139
+ * type Item = {
140
+ * readonly type: "number" | "string";
141
+ * readonly value: unknown;
142
+ * };
143
+ * type NumberItem = Item & { readonly type: "number" };
144
+ *
145
+ * const isNumberItem: RefinementWithIndex<Item, NumberItem> = (
146
+ * item,
147
+ * index,
148
+ * ): item is NumberItem => index > 0 && item.type === "number";
149
+ * const items: ReadonlyArray<Item> = [
150
+ * { type: "number", value: 1 },
151
+ * { type: "number", value: 2 },
152
+ * ];
110
153
  * const [numbers, others] = partitionArray(items, isNumberItem);
154
+ *
155
+ * expectTypeOf(numbers).toEqualTypeOf<ReadonlyArray<NumberItem>>();
156
+ * expect(numbers[0]?.value).toBe(2);
157
+ * expect(others[0]?.value).toBe(1);
111
158
  * ```
112
159
  */
113
160
  export type RefinementWithIndex<in A, out B extends A> = (
@@ -115,26 +162,96 @@ export type RefinementWithIndex<in A, out B extends A> = (
115
162
  index: number,
116
163
  ) => a is B;
117
164
 
165
+ /**
166
+ * Realm-neutral runtime identity for a TypeScript interface.
167
+ *
168
+ * Extend this interface and add its runtime evidence with {@link instance} when
169
+ * constructing a value. Unlike JavaScript `instanceof`, the identity does not
170
+ * depend on a constructor or prototype and therefore survives realms, package
171
+ * duplication, object spreading, and structured cloning.
172
+ *
173
+ * The marker is intentionally forgeable. It identifies values created by
174
+ * trusted constructors; it is not structural validation or a security
175
+ * boundary.
176
+ *
177
+ * ### Adding runtime identity
178
+ *
179
+ * ```ts
180
+ * import { instance, type Instance } from "@evolu/common";
181
+ *
182
+ * interface Foo extends Instance<"Foo"> {
183
+ * readonly value: string;
184
+ * }
185
+ *
186
+ * const foo: Foo = {
187
+ * ...instance("Foo"),
188
+ * value: "value",
189
+ * };
190
+ *
191
+ * expect(foo["~evolu/instance"]).toBe("Foo");
192
+ * ```
193
+ */
194
+ export interface Instance<Name extends string> {
195
+ readonly "~evolu/instance": Name;
196
+ }
197
+
198
+ /** Creates the runtime identity property required by {@link Instance}. */
199
+ export const instance = <const Name extends string>(
200
+ name: Name,
201
+ ): Instance<Name> => ({ "~evolu/instance": name });
202
+
203
+ /**
204
+ * Creates a realm-neutral predicate for one {@link Instance} name.
205
+ *
206
+ * The identity must be stored directly on the value; inherited markers are
207
+ * ignored.
208
+ *
209
+ * The explicit value type can include the rest of an interface whose trusted
210
+ * constructors attach the matching identity.
211
+ *
212
+ * ### Checking runtime identity
213
+ *
214
+ * ```ts
215
+ * import { instance, isInstance, type Instance } from "@evolu/common";
216
+ *
217
+ * interface Foo extends Instance<"Foo"> {
218
+ * readonly value: string;
219
+ * }
220
+ *
221
+ * const isFoo = isInstance<Foo>("Foo");
222
+ * const value: unknown = { ...instance("Foo"), value: "value" };
223
+ *
224
+ * expect(isFoo(value)).toBe(true);
225
+ * ```
226
+ */
227
+ export const isInstance =
228
+ <Value extends Instance<string>>(name: Value["~evolu/instance"]) =>
229
+ (value: unknown): value is Value =>
230
+ value !== null &&
231
+ typeof value === "object" &&
232
+ globalThis.Object.hasOwn(value, "~evolu/instance") &&
233
+ (value as Instance<string>)["~evolu/instance"] === name;
234
+
118
235
  /**
119
236
  * Makes properties optional if they accept `null` as a value.
120
237
  *
121
238
  * For each property in `T`, if `null` is a valid value for that property, the
122
239
  * property will be made optional in the resulting type.
123
240
  *
124
- * ### Example
241
+ * ### Optional nullable properties
125
242
  *
126
243
  * ```ts
244
+ * import type { NullablePartial } from "@evolu/common";
245
+ *
127
246
  * type Example = {
128
247
  * required: string;
129
248
  * optionalWithNull: string | null;
130
249
  * };
131
250
  *
132
- * type Result = NullablePartial<Example>;
133
- * // Result is:
134
- * // {
135
- * // required: string;
136
- * // optionalWithNull?: string | null;
137
- * // }
251
+ * expectTypeOf<NullablePartial<Example>>().toEqualTypeOf<{
252
+ * required: string;
253
+ * optionalWithNull?: string | null;
254
+ * }>();
138
255
  * ```
139
256
  */
140
257
  export type NullablePartial<
@@ -145,6 +262,11 @@ export type NullablePartial<
145
262
  NP = Pick<T, Exclude<keyof T, NK>> & Partial<Pick<T, NK>>,
146
263
  > = { [K in keyof NP]: NP[K] };
147
264
 
265
+ /** A value with a numeric length. */
266
+ export interface ValueWithLength {
267
+ readonly length: number;
268
+ }
269
+
148
270
  /**
149
271
  * String, number, bigint, boolean, undefined, null
150
272
  *
@@ -172,13 +294,8 @@ export type WidenLiteral<T extends Literal> = T extends string
172
294
  ? bigint
173
295
  : T;
174
296
 
175
- /**
176
- * Removes `readonly` modifier from all properties of a type.
177
- *
178
- * Useful for constructing immutable objects step-by-step (e.g. builder pattern)
179
- * before casting them back to the readonly type.
180
- */
181
- export type Mutable<T> = {
297
+ /** Removes `readonly` modifier from all properties of a type. */
298
+ export type Writable<T> = {
182
299
  -readonly [P in keyof T]: T[P];
183
300
  };
184
301
 
@@ -188,19 +305,23 @@ export type Mutable<T> = {
188
305
  * This utility forces TypeScript to "flatten" an intersection type into a
189
306
  * single object type so that tooltips and error messages are easier to read.
190
307
  *
191
- * ### Example
308
+ * ### Flattening an intersection
192
309
  *
193
310
  * ```ts
194
- * type A = { a: string } & { b: number };
195
- * // Without Simplify, TypeScript may display A as:
196
- * // { a: string } & { b: number }
311
+ * import type { Simplify } from "@evolu/common";
197
312
  *
313
+ * type A = { a: string } & { b: number };
198
314
  * type B = Simplify<A>;
199
- * // B is equivalent to:
200
- * // { a: string; b: number }
315
+ *
316
+ * expectTypeOf<B>().toEqualTypeOf<{
317
+ * a: string;
318
+ * b: number;
319
+ * }>();
201
320
  * ```
202
321
  */
203
- export type Simplify<T> = Kysely.Simplify<T>;
322
+ export type Simplify<T> = {
323
+ [K in keyof T]: T[K];
324
+ } & {};
204
325
 
205
326
  /**
206
327
  * Makes a specific property of an object optional while keeping others
@@ -215,21 +336,21 @@ export type PartialProp<T, K extends keyof T> = Omit<T, K> &
215
336
  * Use when a function may complete synchronously or asynchronously depending on
216
337
  * runtime conditions (e.g., cache hit vs network fetch).
217
338
  *
218
- * ### Example
339
+ * ### Sync and async completion
219
340
  *
220
341
  * ```ts
221
- * const getData = (id: string): Awaitable<Data> => {
222
- * const cached = cache.get(id);
223
- * if (cached) return cached; // Sync path
224
- * return fetchData(id); // Async path
225
- * };
342
+ * import { isPromiseLike, type Awaitable } from "@evolu/common";
343
+ *
344
+ * const cache = new Map([["cached", "from cache"]]);
345
+ * const getData = (id: string): Awaitable<string> =>
346
+ * cache.get(id) ?? Promise.resolve(`fetched ${id}`);
226
347
  *
227
- * // Always works
228
- * const data = await getData(id);
348
+ * const fetched = await getData("missing");
349
+ * const result = getData("cached");
350
+ * const cached = isPromiseLike(result) ? await result : result;
229
351
  *
230
- * // Or optimize for sync path
231
- * const result = getData(id);
232
- * const data = isPromiseLike(result) ? await result : result;
352
+ * expect(fetched).toBe("fetched missing");
353
+ * expect(cached).toBe("from cache");
233
354
  * ```
234
355
  */
235
356
  export type Awaitable<T> = T | PromiseLike<T>;
@@ -240,17 +361,19 @@ export type Awaitable<T> = T | PromiseLike<T>;
240
361
  * Use with {@link Awaitable} to conditionally `await` only when necessary,
241
362
  * avoiding microtask overhead for synchronous values.
242
363
  *
243
- * ### Example
364
+ * ### Conditional awaiting
244
365
  *
245
366
  * ```ts
246
- * const validate = (id: string): Awaitable<boolean> => {
247
- * const cached = cache.get(id);
248
- * if (cached !== undefined) return cached; // Sync path
249
- * return fetchValidation(id); // Async path
250
- * };
367
+ * import { isPromiseLike, type Awaitable } from "@evolu/common";
368
+ *
369
+ * const cache = new Map([["cached", true]]);
370
+ * const validate = (id: string): Awaitable<boolean> =>
371
+ * cache.get(id) ?? Promise.resolve(false);
251
372
  *
252
- * const result = validate(id);
373
+ * const result = validate("cached");
253
374
  * const isValid = isPromiseLike(result) ? await result : result;
375
+ *
376
+ * expect(isValid).toBe(true);
254
377
  * ```
255
378
  */
256
379
  export const isPromiseLike = <T>(
@@ -258,33 +381,33 @@ export const isPromiseLike = <T>(
258
381
  ): value is PromiseLike<T> =>
259
382
  typeof (value as PromiseLike<T> | null | undefined)?.then === "function";
260
383
 
261
- /** Single digit 0-9. Useful for template literal type validation. */
384
+ /** Decimal digit from `"0"` to `"9"`. */
262
385
  export type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
263
386
 
264
- /** Digit 1-9. Useful for template literal type validation. */
387
+ /** Decimal digit from `"1"` to `"9"`. */
265
388
  export type Digit1To9 = Exclude<Digit, "0">;
266
389
 
267
- /** Numeric string 1-6. Useful for days validation. */
390
+ /** Decimal string from `"1"` to `"6"`. */
268
391
  export type Digit1To6 = "1" | "2" | "3" | "4" | "5" | "6";
269
392
 
270
- /** Numeric string 1-23. Useful for hours validation. */
393
+ /** Decimal string from `"1"` to `"23"`. */
271
394
  export type Digit1To23 =
272
395
  | Digit1To9 // 1-9
273
396
  | `1${Digit}` // 10-19
274
397
  | `2${"0" | "1" | "2" | "3"}`; // 20-23
275
398
 
276
- /** Numeric string 1-51. Useful for weeks validation. */
399
+ /** Decimal string from `"1"` to `"51"`. */
277
400
  export type Digit1To51 =
278
401
  | Digit1To9 // 1-9
279
402
  | `${"1" | "2" | "3" | "4"}${Digit}` // 10-49
280
403
  | `5${"0" | "1"}`; // 50-51
281
404
 
282
- /** Numeric string 1-99. Useful for years validation. */
405
+ /** Decimal string from `"1"` to `"99"`. */
283
406
  export type Digit1To99 =
284
407
  | Digit1To9 // 1-9
285
408
  | `${Digit1To9}${Digit}`; // 10-99
286
409
 
287
- /** Numeric string 1-59. Useful for minutes, seconds validation. */
410
+ /** Decimal string from `"1"` to `"59"`. */
288
411
  export type Digit1To59 =
289
412
  | Digit1To9 // 1-9
290
413
  | `1${Digit}` // 10-19
@@ -307,6 +430,24 @@ export type Int1To100 = Int1To99 | 100;
307
430
  export type NumberFromString<T extends string> =
308
431
  T extends `${infer N extends number}` ? N : never;
309
432
 
433
+ /** Creates a readable compiler-facing error message. */
434
+ export type CompileTimeError<
435
+ Context extends string,
436
+ Message extends string,
437
+ > = `⛔ ${Context} error: ${Message}`;
438
+
439
+ /** Returns whether a type is a union. */
440
+ export type IsUnion<T, Whole = T> = [T] extends [never]
441
+ ? false
442
+ : T extends Whole
443
+ ? [Whole] extends [T]
444
+ ? false
445
+ : true
446
+ : never;
447
+
448
+ /** Returns every property key present in any member of a union. */
449
+ export type KeysOfUnion<T> = T extends T ? keyof T : never;
450
+
310
451
  /** Converts a union to an intersection. */
311
452
  export type UnionToIntersection<U> = (
312
453
  U extends unknown ? (k: U) => void : never
@@ -314,57 +455,33 @@ export type UnionToIntersection<U> = (
314
455
  ? I
315
456
  : never;
316
457
 
458
+ /** Intersects the parameter types of a union of unary functions. */
459
+ export type ParameterIntersection<T> = [T] extends [(value: infer I) => void]
460
+ ? I
461
+ : unknown;
462
+
317
463
  /**
318
464
  * Removes keys from each member of a union.
319
465
  *
320
466
  * Use when {@link Omit} would collapse a discriminated union into a single
321
467
  * shared shape.
322
468
  *
323
- * ### Example
469
+ * ### Preserving discriminated unions
324
470
  *
325
471
  * ```ts
472
+ * import type { DistributiveOmit } from "@evolu/common";
473
+ *
326
474
  * type Event =
327
475
  * | { type: "a"; a: string; shared: number }
328
476
  * | { type: "b"; b: number; shared: number };
329
477
  *
330
478
  * type Payload = DistributiveOmit<Event, "shared">;
331
- * // { type: "a"; a: string } | { type: "b"; b: number }
479
+ *
480
+ * expectTypeOf<Payload>().toEqualTypeOf<
481
+ * { type: "a"; a: string } | { type: "b"; b: number }
482
+ * >();
332
483
  * ```
333
484
  */
334
485
  export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
335
486
  ? Omit<T, K>
336
487
  : never;
337
-
338
- /**
339
- * Extracts members of a discriminated union by their `type` literal.
340
- *
341
- * Constrains `TType` to valid `type` values, so typos fail at the type argument
342
- * instead of silently producing `never`.
343
- *
344
- * ### Example
345
- *
346
- * ```ts
347
- * type Message =
348
- * | { readonly type: "Create"; readonly id: string }
349
- * | { readonly type: "Delete"; readonly id: string };
350
- *
351
- * type CreateMessage = ExtractType<Message, "Create">;
352
- * // { readonly type: "Create"; readonly id: string }
353
- *
354
- * // Type error: "Cretae" is not a valid Message type
355
- * type _Typo = ExtractType<Message, "Cretae">;
356
- * ```
357
- */
358
- export type ExtractType<
359
- TUnion extends { readonly type: TypeName },
360
- TType extends TUnion["type"],
361
- > = Extract<TUnion, { readonly type: TType }>;
362
-
363
- /**
364
- * Constrains `T` to only contain keys not present in `Existing`.
365
- *
366
- * Use as a generic constraint to prevent overriding existing properties.
367
- */
368
- export type NewKeys<T, Existing> = {
369
- [K in keyof T]: K extends keyof Existing ? never : T[K];
370
- };