@evolu/common 6.0.1-preview.9 → 7.1.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 (195) hide show
  1. package/README.md +27 -29
  2. package/dist/src/Array.d.ts +317 -12
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +219 -9
  5. package/dist/src/Assert.d.ts +0 -13
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +0 -15
  8. package/dist/src/Brand.d.ts +75 -0
  9. package/dist/src/Brand.d.ts.map +1 -0
  10. package/dist/src/Brand.js +1 -0
  11. package/dist/src/Buffer.d.ts +1 -1
  12. package/dist/src/Buffer.d.ts.map +1 -1
  13. package/dist/src/Buffer.js +8 -7
  14. package/dist/src/Cache.d.ts +39 -0
  15. package/dist/src/Cache.d.ts.map +1 -0
  16. package/dist/src/Cache.js +47 -0
  17. package/dist/src/Callbacks.d.ts +45 -12
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Callbacks.js +14 -7
  20. package/dist/src/Console.d.ts +32 -7
  21. package/dist/src/Console.d.ts.map +1 -1
  22. package/dist/src/Console.js +73 -10
  23. package/dist/src/Crypto.d.ts +56 -42
  24. package/dist/src/Crypto.d.ts.map +1 -1
  25. package/dist/src/Crypto.js +45 -54
  26. package/dist/src/Evolu/Db.d.ts +162 -74
  27. package/dist/src/Evolu/Db.d.ts.map +1 -1
  28. package/dist/src/Evolu/Db.js +284 -702
  29. package/dist/src/Evolu/Diff.d.ts +3 -3
  30. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  31. package/dist/src/Evolu/Diff.js +7 -5
  32. package/dist/src/Evolu/Evolu.d.ts +213 -133
  33. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  34. package/dist/src/Evolu/Evolu.js +189 -180
  35. package/dist/src/Evolu/Internal.d.ts +2 -4
  36. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  37. package/dist/src/Evolu/Internal.js +2 -4
  38. package/dist/src/Evolu/LocalAuth.d.ts +150 -0
  39. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  40. package/dist/src/Evolu/LocalAuth.js +174 -0
  41. package/dist/src/Evolu/Owner.d.ts +273 -120
  42. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  43. package/dist/src/Evolu/Owner.js +130 -104
  44. package/dist/src/Evolu/Platform.d.ts +9 -7
  45. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  46. package/dist/src/Evolu/Protocol.d.ts +268 -240
  47. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  48. package/dist/src/Evolu/Protocol.js +595 -447
  49. package/dist/src/Evolu/Public.d.ts +7 -9
  50. package/dist/src/Evolu/Public.d.ts.map +1 -1
  51. package/dist/src/Evolu/Public.js +3 -4
  52. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
  53. package/dist/src/Evolu/PublicKysely.js +3 -4
  54. package/dist/src/Evolu/Query.d.ts +2 -1
  55. package/dist/src/Evolu/Query.d.ts.map +1 -1
  56. package/dist/src/Evolu/Query.js +1 -1
  57. package/dist/src/Evolu/Relay.d.ts +91 -8
  58. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  59. package/dist/src/Evolu/Relay.js +214 -88
  60. package/dist/src/Evolu/Schema.d.ts +125 -47
  61. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  62. package/dist/src/Evolu/Schema.js +175 -31
  63. package/dist/src/Evolu/Storage.d.ts +249 -27
  64. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  65. package/dist/src/Evolu/Storage.js +198 -92
  66. package/dist/src/Evolu/Sync.d.ts +68 -13
  67. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  68. package/dist/src/Evolu/Sync.js +469 -20
  69. package/dist/src/Evolu/Timestamp.d.ts +83 -30
  70. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  71. package/dist/src/Evolu/Timestamp.js +79 -34
  72. package/dist/src/Identicon.d.ts +35 -0
  73. package/dist/src/Identicon.d.ts.map +1 -0
  74. package/dist/src/Identicon.js +143 -0
  75. package/dist/src/Instances.d.ts +34 -0
  76. package/dist/src/Instances.d.ts.map +1 -0
  77. package/dist/src/Instances.js +44 -0
  78. package/dist/src/Number.d.ts +4 -3
  79. package/dist/src/Number.d.ts.map +1 -1
  80. package/dist/src/Number.js +5 -4
  81. package/dist/src/Object.d.ts +10 -4
  82. package/dist/src/Object.d.ts.map +1 -1
  83. package/dist/src/Object.js +9 -3
  84. package/dist/src/Order.d.ts +0 -5
  85. package/dist/src/Order.d.ts.map +1 -1
  86. package/dist/src/Order.js +0 -5
  87. package/dist/src/Platform.d.ts +15 -0
  88. package/dist/src/Platform.d.ts.map +1 -0
  89. package/dist/src/Platform.js +17 -0
  90. package/dist/src/Random.d.ts +3 -7
  91. package/dist/src/Random.d.ts.map +1 -1
  92. package/dist/src/Random.js +0 -5
  93. package/dist/src/Ref.d.ts.map +1 -1
  94. package/dist/src/Relation.d.ts +79 -0
  95. package/dist/src/Relation.d.ts.map +1 -0
  96. package/dist/src/Relation.js +127 -0
  97. package/dist/src/Resources.d.ts +118 -0
  98. package/dist/src/Resources.d.ts.map +1 -0
  99. package/dist/src/Resources.js +197 -0
  100. package/dist/src/Result.d.ts +189 -59
  101. package/dist/src/Result.d.ts.map +1 -1
  102. package/dist/src/Result.js +30 -241
  103. package/dist/src/Skiplist.js +2 -1
  104. package/dist/src/Sqlite.d.ts +89 -5
  105. package/dist/src/Sqlite.d.ts.map +1 -1
  106. package/dist/src/Sqlite.js +136 -9
  107. package/dist/src/Store.d.ts +0 -5
  108. package/dist/src/Store.d.ts.map +1 -1
  109. package/dist/src/Store.js +0 -5
  110. package/dist/src/Task.d.ts +580 -0
  111. package/dist/src/Task.d.ts.map +1 -0
  112. package/dist/src/Task.js +464 -0
  113. package/dist/src/Time.d.ts +66 -6
  114. package/dist/src/Time.d.ts.map +1 -1
  115. package/dist/src/Time.js +96 -7
  116. package/dist/src/Type.d.ts +677 -344
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +718 -467
  119. package/dist/src/Types.d.ts +38 -75
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/WebSocket.d.ts +5 -7
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +12 -23
  124. package/dist/src/Worker.d.ts +39 -16
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +22 -9
  127. package/dist/src/index.d.ts +8 -3
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +8 -3
  130. package/package.json +14 -13
  131. package/src/Array.ts +396 -19
  132. package/src/Assert.ts +0 -35
  133. package/src/Brand.ts +75 -0
  134. package/src/Buffer.ts +7 -7
  135. package/src/Cache.ts +79 -0
  136. package/src/Callbacks.ts +62 -22
  137. package/src/Console.ts +92 -12
  138. package/src/Crypto.ts +99 -91
  139. package/src/Evolu/Db.ts +519 -1026
  140. package/src/Evolu/Diff.ts +7 -5
  141. package/src/Evolu/Evolu.ts +476 -360
  142. package/src/Evolu/Internal.ts +2 -4
  143. package/src/Evolu/LocalAuth.ts +463 -0
  144. package/src/Evolu/Owner.ts +355 -228
  145. package/src/Evolu/Platform.ts +9 -9
  146. package/src/Evolu/Protocol.ts +846 -742
  147. package/src/Evolu/Public.ts +8 -15
  148. package/src/Evolu/PublicKysely.ts +4 -5
  149. package/src/Evolu/Query.ts +3 -2
  150. package/src/Evolu/Relay.ts +406 -103
  151. package/src/Evolu/Schema.ts +323 -91
  152. package/src/Evolu/Storage.ts +559 -137
  153. package/src/Evolu/Sync.ts +819 -36
  154. package/src/Evolu/Timestamp.ts +90 -58
  155. package/src/Identicon.ts +197 -0
  156. package/src/Instances.ts +90 -0
  157. package/src/Number.ts +6 -10
  158. package/src/Object.ts +13 -5
  159. package/src/Order.ts +0 -6
  160. package/src/Platform.ts +20 -0
  161. package/src/Random.ts +3 -8
  162. package/src/Ref.ts +0 -5
  163. package/src/Relation.ts +234 -0
  164. package/src/Resources.ts +367 -0
  165. package/src/Result.ts +196 -61
  166. package/src/Skiplist.ts +1 -1
  167. package/src/Sqlite.ts +152 -17
  168. package/src/Store.ts +0 -5
  169. package/src/Task.ts +894 -0
  170. package/src/Time.ts +179 -10
  171. package/src/Type.ts +1136 -731
  172. package/src/Types.ts +43 -77
  173. package/src/WebSocket.ts +27 -31
  174. package/src/Worker.ts +72 -29
  175. package/src/index.ts +8 -3
  176. package/dist/src/Evolu/Config.d.ts +0 -69
  177. package/dist/src/Evolu/Config.d.ts.map +0 -1
  178. package/dist/src/Evolu/Config.js +0 -9
  179. package/dist/src/Evolu/Kysely.d.ts +0 -6
  180. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  181. package/dist/src/Evolu/Kysely.js +0 -21
  182. package/dist/src/ManyToManyMap.d.ts +0 -26
  183. package/dist/src/ManyToManyMap.d.ts.map +0 -1
  184. package/dist/src/ManyToManyMap.js +0 -92
  185. package/dist/src/NanoId.d.ts +0 -27
  186. package/dist/src/NanoId.d.ts.map +0 -1
  187. package/dist/src/NanoId.js +0 -6
  188. package/dist/src/Promise.d.ts +0 -180
  189. package/dist/src/Promise.d.ts.map +0 -1
  190. package/dist/src/Promise.js +0 -176
  191. package/src/Evolu/Config.ts +0 -83
  192. package/src/Evolu/Kysely.ts +0 -38
  193. package/src/ManyToManyMap.ts +0 -140
  194. package/src/NanoId.ts +0 -39
  195. package/src/Promise.ts +0 -295
package/src/Array.ts CHANGED
@@ -1,39 +1,416 @@
1
- /** An array with at least one element. */
1
+ /**
2
+ * Array types, type guards, operations, transformations, accessors, and
3
+ * mutations
4
+ *
5
+ * ### Example
6
+ *
7
+ * ```ts
8
+ * // Types - compile-time guarantee of at least one element
9
+ * const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
10
+ * // ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
11
+ * const _invalid: NonEmptyReadonlyArray<number> = [];
12
+ *
13
+ * // Type guards
14
+ * const arr: ReadonlyArray<number> = [1, 2, 3];
15
+ * if (isNonEmptyReadonlyArray(arr)) {
16
+ * firstInArray(arr);
17
+ * }
18
+ *
19
+ * // Operations
20
+ * const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
21
+ * const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
22
+ *
23
+ * // Transformations
24
+ * const readonly: ReadonlyArray<number> = [1, 2, 3];
25
+ * const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
26
+ * const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
27
+ * const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
28
+ * const [evens, odds] = partitionArray(
29
+ * [1, 2, 3, 4, 5],
30
+ * (x) => x % 2 === 0,
31
+ * ); // [[2, 4], [1, 3, 5]]
32
+ *
33
+ * // Accessors
34
+ * const first = firstInArray(["a", "b", "c"]); // "a"
35
+ * const last = lastInArray(["a", "b", "c"]); // "c"
36
+ *
37
+ * // Mutations
38
+ * const mutable: NonEmptyArray<number> = [1, 2, 3];
39
+ * shiftArray(mutable); // 1 (guaranteed to exist)
40
+ * mutable; // [2, 3]
41
+ * ```
42
+ *
43
+ * Functions are intentionally data-first to be prepared for the upcoming
44
+ * JavaScript pipe operator.
45
+ *
46
+ * ```ts
47
+ * // Data-first is natural for single operations.
48
+ * const timestamps = mapArray(messages, (m) => m.timestamp);
49
+ *
50
+ * // But data-first can be hard to read for nested calls.
51
+ * const result = firstInArray(
52
+ * mapArray(dedupeArray(appendToArray(value, 2)), (x) => x * 2),
53
+ * );
54
+ *
55
+ * // With the upcoming pipe operator, it's clear.
56
+ * // const result = value
57
+ * // |> appendToArray(%, 2)
58
+ * // |> dedupeArray(%)
59
+ * // |> mapArray(%, (x) => x * 2)
60
+ * // |> firstInArray(%);
61
+ *
62
+ * // Until the pipe operator lands, use nested calls or name each step:
63
+ * const appended = appendToArray(value, 2);
64
+ * const deduped = dedupeArray(appended);
65
+ * const mapped = mapArray(deduped, (x) => x * 2);
66
+ * const result = firstInArray(mapped);
67
+ * ```
68
+ *
69
+ * ### Why data-first?
70
+ *
71
+ * Evolu optimizes for consistent code style. We can't have both data-first
72
+ * single operations and curried data-last helpers without sacrificing
73
+ * consistency. We chose data-first because:
74
+ *
75
+ * - It's natural for single operations (for example `mapArray(messages, (m) =>
76
+ * m.timestamp)`).
77
+ * - It aligns with the upcoming JavaScript pipe operator.
78
+ *
79
+ * **Note**: Feel free to use Array instance methods (mutation) if you think
80
+ * it's better (performance, local scope, etc.).
81
+ *
82
+ * @module
83
+ */
84
+
85
+ import { PredicateWithIndex, RefinementWithIndex } from "./Types.js";
86
+
87
+ /**
88
+ * An array with at least one element.
89
+ *
90
+ * @category Types
91
+ */
2
92
  export type NonEmptyArray<T> = [T, ...Array<T>];
3
93
 
4
- /** Checks if an array is non-empty. */
94
+ /**
95
+ * A readonly array with at least one element.
96
+ *
97
+ * @category Types
98
+ */
99
+ export type NonEmptyReadonlyArray<T> = readonly [T, ...ReadonlyArray<T>];
100
+
101
+ /**
102
+ * Checks if an array is non-empty and narrows its type to {@link NonEmptyArray}.
103
+ *
104
+ * Use `if (!isNonEmptyArray(arr))` for empty checks.
105
+ *
106
+ * ### Example
107
+ *
108
+ * ```ts
109
+ * const arr: Array<number> = [1, 2, 3];
110
+ * if (isNonEmptyArray(arr)) {
111
+ * firstInArray(arr); // arr is NonEmptyArray<number>
112
+ * }
113
+ * ```
114
+ *
115
+ * @category Type Guards
116
+ */
5
117
  export const isNonEmptyArray = <T>(
6
118
  array: Array<T>,
7
119
  ): array is NonEmptyArray<T> => array.length > 0;
8
120
 
9
- /** A readonly array with at least one element. */
10
- export type NonEmptyReadonlyArray<T> = readonly [T, ...ReadonlyArray<T>];
11
-
12
- /** Checks if an array is non-empty. */
121
+ /**
122
+ * Checks if a readonly array is non-empty and narrows its type to
123
+ * {@link NonEmptyReadonlyArray}.
124
+ *
125
+ * Use `if (!isNonEmptyReadonlyArray(arr))` for empty checks.
126
+ *
127
+ * ### Example
128
+ *
129
+ * ```ts
130
+ * const arr: ReadonlyArray<number> = [1, 2, 3];
131
+ * if (isNonEmptyReadonlyArray(arr)) {
132
+ * firstInArray(arr); // arr is NonEmptyReadonlyArray<number>
133
+ * }
134
+ * ```
135
+ *
136
+ * @category Type Guards
137
+ */
13
138
  export const isNonEmptyReadonlyArray = <T>(
14
139
  array: ReadonlyArray<T>,
15
140
  ): array is NonEmptyReadonlyArray<T> => array.length > 0;
16
141
 
17
- /** Appends an item to an array returning a non-empty array. */
142
+ /**
143
+ * Appends an item to an array, returning a new non-empty readonly array.
144
+ *
145
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
146
+ *
147
+ * ### Example
148
+ *
149
+ * ```ts
150
+ * appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
151
+ * ```
152
+ *
153
+ * @category Operations
154
+ */
18
155
  export const appendToArray = <T>(
19
- item: T,
20
156
  array: ReadonlyArray<T>,
157
+ item: T,
21
158
  ): NonEmptyReadonlyArray<T> =>
22
- [...array, item] as unknown as NonEmptyReadonlyArray<T>;
159
+ [...array, item] as ReadonlyArray<T> as NonEmptyReadonlyArray<T>;
23
160
 
24
- /** Prepends an item to an array returning a non-empty array. */
161
+ /**
162
+ * Prepends an item to an array, returning a new non-empty readonly array.
163
+ *
164
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
165
+ *
166
+ * ### Example
167
+ *
168
+ * ```ts
169
+ * prependToArray([2, 3], 1); // [1, 2, 3]
170
+ * ```
171
+ *
172
+ * @category Operations
173
+ */
25
174
  export const prependToArray = <T>(
26
- item: T,
27
175
  array: ReadonlyArray<T>,
28
- ): NonEmptyReadonlyArray<T> =>
29
- [item, ...array] as unknown as NonEmptyReadonlyArray<T>;
176
+ item: T,
177
+ ): NonEmptyReadonlyArray<T> => [item, ...array] as NonEmptyReadonlyArray<T>;
30
178
 
31
- /** Maps a non-empty array using a mapper function. */
32
- export const mapNonEmptyArray = <T, U>(
33
- array: NonEmptyReadonlyArray<T>,
179
+ /**
180
+ * Maps an array using a mapper function.
181
+ *
182
+ * Accepts both mutable and readonly arrays. Preserves non-empty type.
183
+ *
184
+ * ### Example
185
+ *
186
+ * ```ts
187
+ * mapArray([1, 2, 3], (x) => x * 2); // [2, 4, 6]
188
+ * ```
189
+ *
190
+ * @category Transformations
191
+ */
192
+ export function mapArray<T, U>(
193
+ array: NonEmptyReadonlyArray<T> | NonEmptyArray<T>,
34
194
  mapper: (item: T, index: number) => U,
35
- ): NonEmptyReadonlyArray<U> =>
36
- array.map(mapper) as unknown as NonEmptyReadonlyArray<U>;
195
+ ): NonEmptyReadonlyArray<U>;
196
+ export function mapArray<T, U>(
197
+ array: ReadonlyArray<T> | Array<T>,
198
+ mapper: (item: T, index: number) => U,
199
+ ): ReadonlyArray<U>;
200
+ export function mapArray<T, U>(
201
+ array: ReadonlyArray<T> | Array<T>,
202
+ mapper: (item: T, index: number) => U,
203
+ ): ReadonlyArray<U> {
204
+ return array.map(mapper) as ReadonlyArray<U>;
205
+ }
206
+
207
+ /**
208
+ * Filters an array using a predicate or refinement function, returning a new
209
+ * readonly array.
210
+ *
211
+ * Accepts both mutable and readonly arrays. When used with a refinement
212
+ * function (with `value is Type` syntax), TypeScript will narrow the result
213
+ * type to the narrowed type, making it useful for filtering with Evolu Types
214
+ * like `PositiveInt.is`.
215
+ *
216
+ * ### Examples
217
+ *
218
+ * #### With predicate
219
+ *
220
+ * ```ts
221
+ * filterArray([1, 2, 3, 4, 5], (x) => x % 2 === 0); // [2, 4]
222
+ * ```
223
+ *
224
+ * #### With refinement
225
+ *
226
+ * ```ts
227
+ * const mixed: ReadonlyArray<NonEmptyString | PositiveInt> = [
228
+ * NonEmptyString.orThrow("hello"),
229
+ * PositiveInt.orThrow(42),
230
+ * ];
231
+ * const positiveInts = filterArray(mixed, PositiveInt.is);
232
+ * // positiveInts: ReadonlyArray<PositiveInt> (narrowed type)
233
+ * ```
234
+ *
235
+ * @category Transformations
236
+ */
237
+ export function filterArray<T, S extends T>(
238
+ array: ReadonlyArray<T>,
239
+ refinement: RefinementWithIndex<T, S>,
240
+ ): ReadonlyArray<S>;
241
+ export function filterArray<T>(
242
+ array: ReadonlyArray<T>,
243
+ predicate: PredicateWithIndex<T>,
244
+ ): ReadonlyArray<T>;
245
+ export function filterArray<T>(
246
+ array: ReadonlyArray<T>,
247
+ predicate: PredicateWithIndex<T>,
248
+ ): ReadonlyArray<T> {
249
+ return array.filter(predicate) as ReadonlyArray<T>;
250
+ }
251
+
252
+ /**
253
+ * Returns a new readonly array with duplicate items removed. If `by` is
254
+ * provided, it will be used to derive the key for uniqueness; otherwise values
255
+ * are used directly. Dedupes by reference equality of values (or extracted keys
256
+ * when `by` is used).
257
+ *
258
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
259
+ * Preserves non-empty type.
260
+ *
261
+ * ### Example
262
+ *
263
+ * ```ts
264
+ * // Dedupe primitives by value
265
+ * dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
266
+ *
267
+ * // Dedupe objects by property
268
+ * dedupeArray(
269
+ * [
270
+ * { id: 1, name: "Alice" },
271
+ * { id: 2, name: "Bob" },
272
+ * { id: 1, name: "Alice 2" },
273
+ * ],
274
+ * (item) => item.id,
275
+ * ); // [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
276
+ * ```
277
+ *
278
+ * @category Transformations
279
+ */
280
+ export function dedupeArray<T>(
281
+ array: NonEmptyReadonlyArray<T> | NonEmptyArray<T>,
282
+ by?: (item: T) => unknown,
283
+ ): NonEmptyReadonlyArray<T>;
284
+ export function dedupeArray<T>(
285
+ array: ReadonlyArray<T> | Array<T>,
286
+ by?: (item: T) => unknown,
287
+ ): ReadonlyArray<T>;
288
+ export function dedupeArray<T>(
289
+ array: ReadonlyArray<T>,
290
+ by?: (item: T) => unknown,
291
+ ): ReadonlyArray<T> {
292
+ if (by == null) {
293
+ return Array.from(new Set(array)) as ReadonlyArray<T>;
294
+ }
295
+
296
+ const seen = new Set<unknown>();
297
+ return array.filter((item) => {
298
+ const key = by(item);
299
+ if (seen.has(key)) return false;
300
+ seen.add(key);
301
+ return true;
302
+ }) as ReadonlyArray<T>;
303
+ }
304
+
305
+ /**
306
+ * Partitions an array into two arrays based on a predicate or refinement
307
+ * function.
308
+ *
309
+ * Returns a tuple where the first array contains elements that satisfy the
310
+ * predicate, and the second array contains elements that do not. Accepts both
311
+ * mutable and readonly arrays.
312
+ *
313
+ * When used with a refinement function (with `value is Type` syntax),
314
+ * TypeScript will narrow the first array to the narrowed type, making it useful
315
+ * for filtering with Evolu Types like `PositiveInt.is`.
316
+ *
317
+ * ### Examples
318
+ *
319
+ * #### With predicate
320
+ *
321
+ * ```ts
322
+ * const [evens, odds] = partitionArray(
323
+ * [1, 2, 3, 4, 5],
324
+ * (x) => x % 2 === 0,
325
+ * );
326
+ * evens; // [2, 4]
327
+ * odds; // [1, 3, 5]
328
+ * ```
329
+ *
330
+ * #### With refinement
331
+ *
332
+ * ```ts
333
+ * const mixed: ReadonlyArray<NonEmptyString | PositiveInt> = [
334
+ * NonEmptyString.orThrow("hello"),
335
+ * PositiveInt.orThrow(42),
336
+ * ];
337
+ * const [positiveInts, strings] = partitionArray(mixed, PositiveInt.is);
338
+ * // positiveInts: ReadonlyArray<PositiveInt> (narrowed type)
339
+ * // strings: ReadonlyArray<NonEmptyString> (Exclude<T, PositiveInt>)
340
+ * ```
341
+ *
342
+ * @category Transformations
343
+ */
344
+ export function partitionArray<T, S extends T>(
345
+ array: ReadonlyArray<T>,
346
+ refinement: RefinementWithIndex<T, S>,
347
+ ): readonly [ReadonlyArray<S>, ReadonlyArray<Exclude<T, S>>];
348
+ export function partitionArray<T>(
349
+ array: ReadonlyArray<T>,
350
+ predicate: PredicateWithIndex<T>,
351
+ ): readonly [ReadonlyArray<T>, ReadonlyArray<T>];
352
+ export function partitionArray<T>(
353
+ array: ReadonlyArray<T>,
354
+ predicate: PredicateWithIndex<T>,
355
+ ): readonly [ReadonlyArray<T>, ReadonlyArray<T>] {
356
+ const trueArray: Array<T> = [];
357
+ const falseArray: Array<T> = [];
358
+
359
+ for (let i = 0; i < array.length; i++) {
360
+ if (predicate(array[i], i)) {
361
+ trueArray.push(array[i]);
362
+ } else {
363
+ falseArray.push(array[i]);
364
+ }
365
+ }
366
+
367
+ return [trueArray as ReadonlyArray<T>, falseArray as ReadonlyArray<T>];
368
+ }
369
+
370
+ /**
371
+ * Returns the first element of a non-empty array.
372
+ *
373
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
374
+ *
375
+ * ### Example
376
+ *
377
+ * ```ts
378
+ * firstInArray(["a", "b", "c"]); // "a"
379
+ * ```
380
+ *
381
+ * @category Accessors
382
+ */
383
+ export const firstInArray = <T>(array: NonEmptyReadonlyArray<T>): T => array[0];
384
+
385
+ /**
386
+ * Returns the last element of a non-empty array.
387
+ *
388
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
389
+ *
390
+ * ### Example
391
+ *
392
+ * ```ts
393
+ * lastInArray(["a", "b", "c"]); // "c"
394
+ * ```
395
+ *
396
+ * @category Accessors
397
+ */
398
+ export const lastInArray = <T>(array: NonEmptyReadonlyArray<T>): T =>
399
+ array[array.length - 1];
37
400
 
38
- /** Shifts an item from a non-empty array, guaranteed to return T. */
401
+ /**
402
+ * Shifts an item from a non-empty mutable array, guaranteed to return T.
403
+ *
404
+ * **Mutates** the original array.
405
+ *
406
+ * ### Example
407
+ *
408
+ * ```ts
409
+ * const arr: NonEmptyArray<number> = [1, 2, 3];
410
+ * shiftArray(arr); // 1
411
+ * arr; // [2, 3]
412
+ * ```
413
+ *
414
+ * @category Mutations
415
+ */
39
416
  export const shiftArray = <T>(array: NonEmptyArray<T>): T => array.shift() as T;
package/src/Assert.ts CHANGED
@@ -1,17 +1,3 @@
1
- /**
2
- * 🚨
3
- *
4
- * This module provides assertion utilities to prevent invalid states from
5
- * propagating through the system by halting execution when a condition fails,
6
- * improving reliability and debuggability.
7
- *
8
- * **Warning**: Do not use this instead of {@link Type}. Assertions are intended
9
- * for conditions that are logically guaranteed but not statically known by
10
- * TypeScript, or for catching and signaling developer mistakes eagerly (e.g.,
11
- * invalid configuration).
12
- *
13
- * @module
14
- */
15
1
  import type { Type } from "./Type.js";
16
2
 
17
3
  /**
@@ -96,24 +82,3 @@ export const assertNonEmptyReadonlyArray: <T>(
96
82
  ) => {
97
83
  assert(arr.length > 0, message);
98
84
  };
99
-
100
- /**
101
- * Asserts no error reaches a `.catch` block, throwing a developer error if it
102
- * does. Used in Promise chains where errors indicate bugs to be fixed.
103
- *
104
- * ### Example
105
- *
106
- * ```ts
107
- * Promise.reject("test").catch((e) =>
108
- * assertNoErrorInCatch("WebSocket retry", e),
109
- * );
110
- * ```
111
- */
112
- export function assertNoErrorInCatch(context: string, error: unknown): never {
113
- throw Object.assign(
114
- new Error(
115
- `Error in ${context}: an unexpected error reached a catch block and requires a fix`,
116
- ),
117
- { cause: error },
118
- );
119
- }
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
+ * ### Example
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/Buffer.ts CHANGED
@@ -6,7 +6,7 @@ export {
6
6
  concatBytes,
7
7
  hexToBytes,
8
8
  utf8ToBytes,
9
- } from "@noble/ciphers/utils";
9
+ } from "@noble/ciphers/utils.js";
10
10
 
11
11
  /**
12
12
  * Custom error for {@link Buffer}-related failures like premature end of data.
@@ -116,10 +116,10 @@ export const createBuffer = (
116
116
  let value = arrayLike
117
117
  ? new globalThis.Uint8Array(arrayLike)
118
118
  : new globalThis.Uint8Array(512);
119
- let length = (arrayLike ? arrayLike.length : 0) as NonNegativeInt;
119
+ let length = NonNegativeInt.orThrow(arrayLike ? arrayLike.length : 0);
120
120
 
121
121
  const buffer: Buffer = {
122
- getCapacity: () => value.length as NonNegativeInt,
122
+ getCapacity: () => NonNegativeInt.orThrow(value.length),
123
123
 
124
124
  getLength: () => length,
125
125
 
@@ -132,7 +132,7 @@ export const createBuffer = (
132
132
  value.set(oldValue);
133
133
  }
134
134
  value.set(arg, length);
135
- length = (length + arg.length) as NonNegativeInt;
135
+ length = NonNegativeInt.orThrow(length + arg.length);
136
136
  },
137
137
 
138
138
  shift: () => {
@@ -142,7 +142,7 @@ export const createBuffer = (
142
142
  const first = value[0];
143
143
  value = value.subarray(1);
144
144
  length--;
145
- return first as NonNegativeInt;
145
+ return NonNegativeInt.orThrow(first);
146
146
  },
147
147
 
148
148
  shiftN: (n) => {
@@ -151,7 +151,7 @@ export const createBuffer = (
151
151
  }
152
152
  const subarray = value.subarray(0, n);
153
153
  value = value.subarray(n);
154
- length = (length - n) as NonNegativeInt;
154
+ length = NonNegativeInt.orThrow(length - n);
155
155
  return subarray;
156
156
  },
157
157
 
@@ -165,7 +165,7 @@ export const createBuffer = (
165
165
  },
166
166
 
167
167
  reset: () => {
168
- length = 0 as NonNegativeInt;
168
+ length = NonNegativeInt.orThrow(0);
169
169
  },
170
170
 
171
171
  unwrap: () => value.subarray(0, length),
package/src/Cache.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { PositiveInt } from "./Type.js";
2
+
3
+ /**
4
+ * Generic cache interface providing basic key-value storage operations.
5
+ *
6
+ * Keys are compared by reference (standard Map semantics).
7
+ *
8
+ * Note: Cache does not extend Map because eviction policies (like in LRU)
9
+ * violate the Liskov Substitution Principle.
10
+ */
11
+ export interface Cache<K, V> {
12
+ /** Checks if a key exists in the cache. */
13
+ has: (key: K) => boolean;
14
+
15
+ /** Retrieves the value for a key, or undefined if not present. */
16
+ get: (key: K) => V | undefined;
17
+
18
+ /** Stores a key-value pair in the cache. */
19
+ set: (key: K, val: V) => void;
20
+
21
+ /** Removes a key from the cache. */
22
+ delete: (key: K) => void;
23
+
24
+ /** Returns a readonly view of the internal Map. */
25
+ readonly map: ReadonlyMap<K, V>;
26
+ }
27
+
28
+ /**
29
+ * Creates an LRU (least recently used) cache with a maximum capacity.
30
+ *
31
+ * When the cache reaches capacity, the least recently used entry is evicted.
32
+ * Both `get` and `set` operations update the access order.
33
+ *
34
+ * ### Example
35
+ *
36
+ * ```ts
37
+ * const cache = createLruCache<string, number>(2);
38
+ * cache.set("a", 1);
39
+ * cache.set("b", 2);
40
+ * cache.set("c", 3); // Evicts "a"
41
+ * cache.has("a"); // false
42
+ * ```
43
+ */
44
+ export const createLruCache = <K, V>(capacity: PositiveInt): Cache<K, V> => {
45
+ const internalMap = new Map<K, V>();
46
+
47
+ return {
48
+ has: (key) => internalMap.has(key),
49
+
50
+ get: (key) => {
51
+ const value = internalMap.get(key);
52
+ if (value === undefined) return undefined;
53
+
54
+ // Move to end (most recently used)
55
+ internalMap.delete(key);
56
+ internalMap.set(key, value);
57
+ return value;
58
+ },
59
+
60
+ set: (key, val) => {
61
+ // If key exists, delete it first to update order
62
+ if (internalMap.has(key)) {
63
+ internalMap.delete(key);
64
+ } else if (internalMap.size === capacity) {
65
+ // Evict least recently used (first entry)
66
+ const firstKey = internalMap.keys().next().value as K;
67
+ internalMap.delete(firstKey);
68
+ }
69
+
70
+ internalMap.set(key, val);
71
+ },
72
+
73
+ delete: (key) => {
74
+ internalMap.delete(key);
75
+ },
76
+
77
+ map: internalMap,
78
+ };
79
+ };