@evolu/common 6.0.1-preview.8 → 7.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.
- package/README.md +2 -2
- package/dist/src/Array.d.ts +256 -12
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +215 -9
- package/dist/src/Assert.d.ts +0 -13
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +0 -15
- package/dist/src/Brand.d.ts +75 -0
- package/dist/src/Brand.d.ts.map +1 -0
- package/dist/src/Brand.js +1 -0
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +8 -7
- package/dist/src/Cache.d.ts +44 -0
- package/dist/src/Cache.d.ts.map +1 -0
- package/dist/src/Cache.js +52 -0
- package/dist/src/Callbacks.d.ts +45 -12
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +14 -7
- package/dist/src/Console.d.ts +31 -6
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +72 -9
- package/dist/src/Crypto.d.ts +56 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +40 -53
- package/dist/src/Evolu/Db.d.ts +162 -74
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +284 -702
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +214 -134
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +189 -180
- package/dist/src/Evolu/Internal.d.ts +0 -2
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +0 -2
- package/dist/src/Evolu/LocalAuth.d.ts +150 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +174 -0
- package/dist/src/Evolu/Owner.d.ts +273 -120
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +130 -104
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +268 -240
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +600 -454
- package/dist/src/Evolu/Public.d.ts +6 -8
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +2 -3
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
- package/dist/src/Evolu/PublicKysely.js +3 -4
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Relay.d.ts +91 -8
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +214 -88
- package/dist/src/Evolu/Schema.d.ts +125 -47
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +175 -31
- package/dist/src/Evolu/Storage.d.ts +249 -27
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +198 -92
- package/dist/src/Evolu/Sync.d.ts +68 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +469 -20
- package/dist/src/Evolu/Timestamp.d.ts +83 -30
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +79 -34
- package/dist/src/Identicon.d.ts +35 -0
- package/dist/src/Identicon.d.ts.map +1 -0
- package/dist/src/Identicon.js +143 -0
- package/dist/src/Instances.d.ts +34 -0
- package/dist/src/Instances.d.ts.map +1 -0
- package/dist/src/Instances.js +44 -0
- package/dist/src/Number.d.ts +4 -3
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +5 -4
- package/dist/src/Object.d.ts +10 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +9 -3
- package/dist/src/Platform.d.ts +20 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +22 -0
- package/dist/src/Random.d.ts +3 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +79 -0
- package/dist/src/Relation.d.ts.map +1 -0
- package/dist/src/Relation.js +127 -0
- package/dist/src/Resources.d.ts +118 -0
- package/dist/src/Resources.d.ts.map +1 -0
- package/dist/src/Resources.js +197 -0
- package/dist/src/Result.d.ts +184 -52
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +30 -241
- package/dist/src/Skiplist.js +2 -1
- package/dist/src/Sqlite.d.ts +89 -5
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +136 -9
- package/dist/src/Task.d.ts +586 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +469 -0
- package/dist/src/Time.d.ts +66 -1
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +99 -5
- package/dist/src/Type.d.ts +676 -343
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +718 -467
- package/dist/src/Types.d.ts +1 -75
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -2
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -18
- package/dist/src/Worker.d.ts +39 -11
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -4
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/package.json +15 -14
- package/src/Array.ts +301 -19
- package/src/Assert.ts +0 -21
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +7 -7
- package/src/Cache.ts +85 -0
- package/src/Callbacks.ts +62 -22
- package/src/Console.ts +91 -11
- package/src/Crypto.ts +94 -90
- package/src/Evolu/Db.ts +519 -1026
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +477 -361
- package/src/Evolu/Internal.ts +0 -2
- package/src/Evolu/LocalAuth.ts +463 -0
- package/src/Evolu/Owner.ts +355 -228
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +842 -742
- package/src/Evolu/Public.ts +7 -14
- package/src/Evolu/PublicKysely.ts +4 -5
- package/src/Evolu/Query.ts +3 -2
- package/src/Evolu/Relay.ts +406 -103
- package/src/Evolu/Schema.ts +323 -91
- package/src/Evolu/Storage.ts +559 -137
- package/src/Evolu/Sync.ts +819 -36
- package/src/Evolu/Timestamp.ts +90 -58
- package/src/Identicon.ts +197 -0
- package/src/Instances.ts +90 -0
- package/src/Number.ts +6 -10
- package/src/Object.ts +13 -5
- package/src/Platform.ts +26 -0
- package/src/Random.ts +3 -2
- package/src/Relation.ts +234 -0
- package/src/Resources.ts +367 -0
- package/src/Result.ts +191 -54
- package/src/Skiplist.ts +1 -1
- package/src/Sqlite.ts +152 -17
- package/src/Task.ts +901 -0
- package/src/Time.ts +180 -5
- package/src/Type.ts +1135 -730
- package/src/Types.ts +1 -77
- package/src/WebSocket.ts +27 -25
- package/src/Worker.ts +72 -23
- package/src/index.ts +8 -3
- package/dist/src/Evolu/Config.d.ts +0 -69
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/ManyToManyMap.d.ts +0 -26
- package/dist/src/ManyToManyMap.d.ts.map +0 -1
- package/dist/src/ManyToManyMap.js +0 -92
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Evolu/Config.ts +0 -83
- package/src/Evolu/Kysely.ts +0 -38
- package/src/ManyToManyMap.ts +0 -140
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/src/Array.ts
CHANGED
|
@@ -1,39 +1,321 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* 🔒 Type-safe array helpers that do not mutate
|
|
3
|
+
*
|
|
4
|
+
* Array types, guards, operations, transformations, accessors, and (rare)
|
|
5
|
+
* mutations.
|
|
6
|
+
*
|
|
7
|
+
* Functions are intentionally data-first to be prepared for the upcoming
|
|
8
|
+
* JavaScript pipe operator.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Data-first is natural for single operations.
|
|
12
|
+
* const timestamps = mapArray(messages, (m) => m.timestamp);
|
|
13
|
+
*
|
|
14
|
+
* // But data-first can be hard to read for nested calls.
|
|
15
|
+
* const result = firstInArray(
|
|
16
|
+
* mapArray(dedupeArray(appendToArray(value, 2)), (x) => x * 2),
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* // With the upcoming pipe operator, it's clear.
|
|
20
|
+
* // const result = value
|
|
21
|
+
* // |> appendToArray(%, 2)
|
|
22
|
+
* // |> dedupeArray(%)
|
|
23
|
+
* // |> mapArray(%, (x) => x * 2)
|
|
24
|
+
* // |> firstInArray(%);
|
|
25
|
+
*
|
|
26
|
+
* // Until the pipe operator lands, use nested calls or name each step:
|
|
27
|
+
* const appended = appendToArray(value, 2);
|
|
28
|
+
* const deduped = dedupeArray(appended);
|
|
29
|
+
* const mapped = mapArray(deduped, (x) => x * 2);
|
|
30
|
+
* const result = firstInArray(mapped);
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* ### Why data-first?
|
|
34
|
+
*
|
|
35
|
+
* Evolu optimizes for consistent code style. We can't have both data-first
|
|
36
|
+
* single operations and curried data-last helpers without sacrificing
|
|
37
|
+
* consistency. We chose data-first because:
|
|
38
|
+
*
|
|
39
|
+
* - It's natural for single operations (for example `mapArray(messages, (m) =>
|
|
40
|
+
* m.timestamp)`).
|
|
41
|
+
* - It aligns with the upcoming JavaScript pipe operator.
|
|
42
|
+
*
|
|
43
|
+
* **Note**: Feel free to use Array instance methods (mutation) if you think
|
|
44
|
+
* it's better (performance, local scope, etc.).
|
|
45
|
+
*
|
|
46
|
+
* ### Example
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* // Types - compile-time guarantee of at least one element
|
|
50
|
+
* const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
51
|
+
* // ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
|
|
52
|
+
* const _invalid: NonEmptyReadonlyArray<number> = [];
|
|
53
|
+
*
|
|
54
|
+
* // Guards
|
|
55
|
+
* const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
56
|
+
* if (isNonEmptyReadonlyArray(arr)) {
|
|
57
|
+
* firstInArray(arr);
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* // Operations
|
|
61
|
+
* const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
|
|
62
|
+
* const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
|
|
63
|
+
*
|
|
64
|
+
* // Transformations
|
|
65
|
+
* const readonly: ReadonlyArray<number> = [1, 2, 3];
|
|
66
|
+
* const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
|
|
67
|
+
* const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
|
|
68
|
+
* const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
|
|
69
|
+
*
|
|
70
|
+
* // Accessors
|
|
71
|
+
* const first = firstInArray(["a", "b", "c"]); // "a"
|
|
72
|
+
* const last = lastInArray(["a", "b", "c"]); // "c"
|
|
73
|
+
*
|
|
74
|
+
* // Mutations
|
|
75
|
+
* const mutable: NonEmptyArray<number> = [1, 2, 3];
|
|
76
|
+
* shiftArray(mutable); // 1 (guaranteed to exist)
|
|
77
|
+
* mutable; // [2, 3]
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @module
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* An array with at least one element.
|
|
85
|
+
*
|
|
86
|
+
* @category Types
|
|
87
|
+
*/
|
|
2
88
|
export type NonEmptyArray<T> = [T, ...Array<T>];
|
|
3
89
|
|
|
4
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* A readonly array with at least one element.
|
|
92
|
+
*
|
|
93
|
+
* @category Types
|
|
94
|
+
*/
|
|
95
|
+
export type NonEmptyReadonlyArray<T> = readonly [T, ...ReadonlyArray<T>];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks if an array is non-empty and narrows its type to {@link NonEmptyArray}.
|
|
99
|
+
*
|
|
100
|
+
* Use `if (!isNonEmptyArray(arr))` for empty checks.
|
|
101
|
+
*
|
|
102
|
+
* ### Example
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const arr: Array<number> = [1, 2, 3];
|
|
106
|
+
* if (isNonEmptyArray(arr)) {
|
|
107
|
+
* firstInArray(arr); // arr is NonEmptyArray<number>
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @category Type Guards
|
|
112
|
+
*/
|
|
5
113
|
export const isNonEmptyArray = <T>(
|
|
6
114
|
array: Array<T>,
|
|
7
115
|
): array is NonEmptyArray<T> => array.length > 0;
|
|
8
116
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Checks if a readonly array is non-empty and narrows its type to
|
|
119
|
+
* {@link NonEmptyReadonlyArray}.
|
|
120
|
+
*
|
|
121
|
+
* Use `if (!isNonEmptyReadonlyArray(arr))` for empty checks.
|
|
122
|
+
*
|
|
123
|
+
* ### Example
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
127
|
+
* if (isNonEmptyReadonlyArray(arr)) {
|
|
128
|
+
* firstInArray(arr); // arr is NonEmptyReadonlyArray<number>
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*
|
|
132
|
+
* @category Type Guards
|
|
133
|
+
*/
|
|
13
134
|
export const isNonEmptyReadonlyArray = <T>(
|
|
14
135
|
array: ReadonlyArray<T>,
|
|
15
136
|
): array is NonEmptyReadonlyArray<T> => array.length > 0;
|
|
16
137
|
|
|
17
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Appends an item to an array, returning a new non-empty readonly array.
|
|
140
|
+
*
|
|
141
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
142
|
+
*
|
|
143
|
+
* ### Example
|
|
144
|
+
*
|
|
145
|
+
* ```ts
|
|
146
|
+
* appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @category Operations
|
|
150
|
+
*/
|
|
18
151
|
export const appendToArray = <T>(
|
|
19
|
-
item: T,
|
|
20
152
|
array: ReadonlyArray<T>,
|
|
153
|
+
item: T,
|
|
21
154
|
): NonEmptyReadonlyArray<T> =>
|
|
22
|
-
[...array, item] as
|
|
155
|
+
[...array, item] as ReadonlyArray<T> as NonEmptyReadonlyArray<T>;
|
|
23
156
|
|
|
24
|
-
/**
|
|
157
|
+
/**
|
|
158
|
+
* Prepends an item to an array, returning a new non-empty readonly array.
|
|
159
|
+
*
|
|
160
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
161
|
+
*
|
|
162
|
+
* ### Example
|
|
163
|
+
*
|
|
164
|
+
* ```ts
|
|
165
|
+
* prependToArray([2, 3], 1); // [1, 2, 3]
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @category Operations
|
|
169
|
+
*/
|
|
25
170
|
export const prependToArray = <T>(
|
|
26
|
-
item: T,
|
|
27
171
|
array: ReadonlyArray<T>,
|
|
28
|
-
|
|
29
|
-
|
|
172
|
+
item: T,
|
|
173
|
+
): NonEmptyReadonlyArray<T> => [item, ...array] as NonEmptyReadonlyArray<T>;
|
|
30
174
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Maps an array using a mapper function.
|
|
177
|
+
*
|
|
178
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
179
|
+
* Preserves non-empty type.
|
|
180
|
+
*
|
|
181
|
+
* ### Example
|
|
182
|
+
*
|
|
183
|
+
* ```ts
|
|
184
|
+
* mapArray([1, 2, 3], (x) => x * 2); // [2, 4, 6]
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* @category Transformations
|
|
188
|
+
*/
|
|
189
|
+
export function mapArray<T, U>(
|
|
190
|
+
array: NonEmptyReadonlyArray<T> | NonEmptyArray<T>,
|
|
34
191
|
mapper: (item: T, index: number) => U,
|
|
35
|
-
): NonEmptyReadonlyArray<U
|
|
36
|
-
|
|
192
|
+
): NonEmptyReadonlyArray<U>;
|
|
193
|
+
export function mapArray<T, U>(
|
|
194
|
+
array: ReadonlyArray<T> | Array<T>,
|
|
195
|
+
mapper: (item: T, index: number) => U,
|
|
196
|
+
): ReadonlyArray<U>;
|
|
197
|
+
export function mapArray<T, U>(
|
|
198
|
+
array: ReadonlyArray<T> | Array<T>,
|
|
199
|
+
mapper: (item: T, index: number) => U,
|
|
200
|
+
): ReadonlyArray<U> {
|
|
201
|
+
return array.map(mapper) as ReadonlyArray<U>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Filters an array using a predicate function, returning a new readonly array.
|
|
206
|
+
*
|
|
207
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
208
|
+
*
|
|
209
|
+
* ### Example
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* filterArray([1, 2, 3, 4, 5], (x) => x % 2 === 0); // [2, 4]
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* @category Transformations
|
|
216
|
+
*/
|
|
217
|
+
export const filterArray = <T>(
|
|
218
|
+
array: ReadonlyArray<T>,
|
|
219
|
+
predicate: (item: T, index: number) => boolean,
|
|
220
|
+
): ReadonlyArray<T> => array.filter(predicate) as ReadonlyArray<T>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Returns a new readonly array with duplicate items removed. If `by` is
|
|
224
|
+
* provided, it will be used to derive the key for uniqueness; otherwise values
|
|
225
|
+
* are used directly. Dedupes by reference equality of values (or extracted keys
|
|
226
|
+
* when `by` is used).
|
|
227
|
+
*
|
|
228
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
229
|
+
* Preserves non-empty type.
|
|
230
|
+
*
|
|
231
|
+
* ### Example
|
|
232
|
+
*
|
|
233
|
+
* ```ts
|
|
234
|
+
* // Dedupe primitives by value
|
|
235
|
+
* dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
|
|
236
|
+
*
|
|
237
|
+
* // Dedupe objects by property
|
|
238
|
+
* dedupeArray(
|
|
239
|
+
* [
|
|
240
|
+
* { id: 1, name: "Alice" },
|
|
241
|
+
* { id: 2, name: "Bob" },
|
|
242
|
+
* { id: 1, name: "Alice 2" },
|
|
243
|
+
* ],
|
|
244
|
+
* (item) => item.id,
|
|
245
|
+
* ); // [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @category Transformations
|
|
249
|
+
*/
|
|
250
|
+
export function dedupeArray<T>(
|
|
251
|
+
array: NonEmptyReadonlyArray<T> | NonEmptyArray<T>,
|
|
252
|
+
by?: (item: T) => unknown,
|
|
253
|
+
): NonEmptyReadonlyArray<T>;
|
|
254
|
+
export function dedupeArray<T>(
|
|
255
|
+
array: ReadonlyArray<T> | Array<T>,
|
|
256
|
+
by?: (item: T) => unknown,
|
|
257
|
+
): ReadonlyArray<T>;
|
|
258
|
+
export function dedupeArray<T>(
|
|
259
|
+
array: ReadonlyArray<T>,
|
|
260
|
+
by?: (item: T) => unknown,
|
|
261
|
+
): ReadonlyArray<T> {
|
|
262
|
+
if (by == null) {
|
|
263
|
+
return Array.from(new Set(array)) as ReadonlyArray<T>;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const seen = new Set<unknown>();
|
|
267
|
+
return array.filter((item) => {
|
|
268
|
+
const key = by(item);
|
|
269
|
+
if (seen.has(key)) return false;
|
|
270
|
+
seen.add(key);
|
|
271
|
+
return true;
|
|
272
|
+
}) as ReadonlyArray<T>;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns the first element of a non-empty array.
|
|
277
|
+
*
|
|
278
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
279
|
+
*
|
|
280
|
+
* ### Example
|
|
281
|
+
*
|
|
282
|
+
* ```ts
|
|
283
|
+
* firstInArray(["a", "b", "c"]); // "a"
|
|
284
|
+
* ```
|
|
285
|
+
*
|
|
286
|
+
* @category Accessors
|
|
287
|
+
*/
|
|
288
|
+
export const firstInArray = <T>(array: NonEmptyReadonlyArray<T>): T => array[0];
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Returns the last element of a non-empty array.
|
|
292
|
+
*
|
|
293
|
+
* Accepts both mutable and readonly arrays. Does not mutate the original array.
|
|
294
|
+
*
|
|
295
|
+
* ### Example
|
|
296
|
+
*
|
|
297
|
+
* ```ts
|
|
298
|
+
* lastInArray(["a", "b", "c"]); // "c"
|
|
299
|
+
* ```
|
|
300
|
+
*
|
|
301
|
+
* @category Accessors
|
|
302
|
+
*/
|
|
303
|
+
export const lastInArray = <T>(array: NonEmptyReadonlyArray<T>): T =>
|
|
304
|
+
array[array.length - 1];
|
|
37
305
|
|
|
38
|
-
/**
|
|
306
|
+
/**
|
|
307
|
+
* Shifts an item from a non-empty mutable array, guaranteed to return T.
|
|
308
|
+
*
|
|
309
|
+
* **Mutates** the original array.
|
|
310
|
+
*
|
|
311
|
+
* ### Example
|
|
312
|
+
*
|
|
313
|
+
* ```ts
|
|
314
|
+
* const arr: NonEmptyArray<number> = [1, 2, 3];
|
|
315
|
+
* shiftArray(arr); // 1
|
|
316
|
+
* arr; // [2, 3]
|
|
317
|
+
* ```
|
|
318
|
+
*
|
|
319
|
+
* @category Mutations
|
|
320
|
+
*/
|
|
39
321
|
export const shiftArray = <T>(array: NonEmptyArray<T>): T => array.shift() as T;
|
package/src/Assert.ts
CHANGED
|
@@ -96,24 +96,3 @@ export const assertNonEmptyReadonlyArray: <T>(
|
|
|
96
96
|
) => {
|
|
97
97
|
assert(arr.length > 0, message);
|
|
98
98
|
};
|
|
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
|
+
* ### 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/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)
|
|
119
|
+
let length = NonNegativeInt.orThrow(arrayLike ? arrayLike.length : 0);
|
|
120
120
|
|
|
121
121
|
const buffer: Buffer = {
|
|
122
|
-
getCapacity: () => value.length
|
|
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)
|
|
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
|
|
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)
|
|
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
|
|
168
|
+
length = NonNegativeInt.orThrow(0);
|
|
169
169
|
},
|
|
170
170
|
|
|
171
171
|
unwrap: () => value.subarray(0, length),
|
package/src/Cache.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🗄️ Generic cache interface and LRU cache implementation.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PositiveInt } from "./Type.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generic cache interface providing basic key-value storage operations.
|
|
11
|
+
*
|
|
12
|
+
* Keys are compared by reference (standard Map semantics).
|
|
13
|
+
*
|
|
14
|
+
* Note: Cache does not extend Map because eviction policies (like in LRU)
|
|
15
|
+
* violate the Liskov Substitution Principle.
|
|
16
|
+
*/
|
|
17
|
+
export interface Cache<K, V> {
|
|
18
|
+
/** Checks if a key exists in the cache. */
|
|
19
|
+
has: (key: K) => boolean;
|
|
20
|
+
|
|
21
|
+
/** Retrieves the value for a key, or undefined if not present. */
|
|
22
|
+
get: (key: K) => V | undefined;
|
|
23
|
+
|
|
24
|
+
/** Stores a key-value pair in the cache. */
|
|
25
|
+
set: (key: K, val: V) => void;
|
|
26
|
+
|
|
27
|
+
/** Removes a key from the cache. */
|
|
28
|
+
delete: (key: K) => void;
|
|
29
|
+
|
|
30
|
+
/** Returns a readonly view of the internal Map. */
|
|
31
|
+
readonly map: ReadonlyMap<K, V>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a Least Recently Used (LRU) cache with a maximum capacity.
|
|
36
|
+
*
|
|
37
|
+
* When the cache reaches capacity, the least recently used entry is evicted.
|
|
38
|
+
* Both `get` and `set` operations update the access order.
|
|
39
|
+
*
|
|
40
|
+
* ### Example
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* const cache = createLruCache<string, number>(2);
|
|
44
|
+
* cache.set("a", 1);
|
|
45
|
+
* cache.set("b", 2);
|
|
46
|
+
* cache.set("c", 3); // Evicts "a"
|
|
47
|
+
* cache.has("a"); // false
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export const createLruCache = <K, V>(capacity: PositiveInt): Cache<K, V> => {
|
|
51
|
+
const internalMap = new Map<K, V>();
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
has: (key) => internalMap.has(key),
|
|
55
|
+
|
|
56
|
+
get: (key) => {
|
|
57
|
+
const value = internalMap.get(key);
|
|
58
|
+
if (value === undefined) return undefined;
|
|
59
|
+
|
|
60
|
+
// Move to end (most recently used)
|
|
61
|
+
internalMap.delete(key);
|
|
62
|
+
internalMap.set(key, value);
|
|
63
|
+
return value;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
set: (key, val) => {
|
|
67
|
+
// If key exists, delete it first to update order
|
|
68
|
+
if (internalMap.has(key)) {
|
|
69
|
+
internalMap.delete(key);
|
|
70
|
+
} else if (internalMap.size === capacity) {
|
|
71
|
+
// Evict least recently used (first entry)
|
|
72
|
+
const firstKey = internalMap.keys().next().value as K;
|
|
73
|
+
internalMap.delete(firstKey);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
internalMap.set(key, val);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
delete: (key) => {
|
|
80
|
+
internalMap.delete(key);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
map: internalMap,
|
|
84
|
+
};
|
|
85
|
+
};
|
package/src/Callbacks.ts
CHANGED
|
@@ -1,43 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Brand } from "./Brand.js";
|
|
2
|
+
import { RandomBytesDep } from "./Crypto.js";
|
|
3
|
+
import { Result } from "./Result.js";
|
|
4
|
+
import { createId, Id } from "./Type.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* Request-response correlation for callbacks across boundaries.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* Stores callbacks with unique IDs and executes them once with an optional
|
|
10
|
+
* argument. Executed callbacks are automatically removed.
|
|
9
11
|
*
|
|
10
|
-
* This is useful for
|
|
11
|
-
*
|
|
12
|
+
* This is useful for correlating asynchronous request-response operations
|
|
13
|
+
* across boundaries where callback functions cannot be passed directly (e.g.,
|
|
14
|
+
* web workers, message queues).
|
|
15
|
+
*
|
|
16
|
+
* The `execute` method intentionally does not use try-catch or {@link Result}
|
|
17
|
+
* because it's the callback's responsibility to handle its own errors.
|
|
18
|
+
*
|
|
19
|
+
* ### Example
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* // No-argument callbacks
|
|
23
|
+
* const callbacks = createCallbacks(deps);
|
|
24
|
+
* const id = callbacks.register(() => console.log("called"));
|
|
25
|
+
* callbacks.execute(id);
|
|
26
|
+
*
|
|
27
|
+
* // With argument callbacks
|
|
28
|
+
* const stringCallbacks = createCallbacks<string>(deps);
|
|
29
|
+
* const id = stringCallbacks.register((value) => {
|
|
30
|
+
* console.log(value);
|
|
31
|
+
* });
|
|
32
|
+
* stringCallbacks.execute(id, "hello");
|
|
33
|
+
*
|
|
34
|
+
* // Promise.withResolvers pattern
|
|
35
|
+
* const promiseCallbacks = createCallbacks<string>(deps);
|
|
36
|
+
* const { promise, resolve } = Promise.withResolvers<string>();
|
|
37
|
+
* const id = promiseCallbacks.register(resolve);
|
|
38
|
+
* promiseCallbacks.execute(id, "resolved value");
|
|
39
|
+
* await promise; // "resolved value"
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @template T - The type of argument passed to callbacks (defaults to undefined
|
|
43
|
+
* for no-argument callbacks)
|
|
12
44
|
*/
|
|
13
|
-
export interface Callbacks {
|
|
45
|
+
export interface Callbacks<T = undefined> {
|
|
14
46
|
/** Registers a callback function and returns a unique ID. */
|
|
15
|
-
readonly register: (callback: (arg
|
|
47
|
+
readonly register: (callback: (arg: T) => void) => CallbackId;
|
|
16
48
|
|
|
17
49
|
/** Executes and removes a callback associated with the given ID. */
|
|
18
|
-
readonly execute:
|
|
50
|
+
readonly execute: T extends undefined
|
|
51
|
+
? (id: CallbackId) => undefined
|
|
52
|
+
: (id: CallbackId, arg: T) => undefined;
|
|
19
53
|
}
|
|
20
54
|
|
|
21
|
-
|
|
55
|
+
/** Unique identifier for a callback in {@link Callbacks}. */
|
|
56
|
+
export type CallbackId = Id & Brand<"Callback">;
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
/** Creates a new {@link Callbacks}. */
|
|
59
|
+
export const createCallbacks = <T = undefined>(
|
|
60
|
+
deps: RandomBytesDep,
|
|
61
|
+
): Callbacks<T> => {
|
|
62
|
+
const callbackMap = new Map<CallbackId, (arg: T) => void>();
|
|
25
63
|
|
|
26
|
-
|
|
64
|
+
return {
|
|
27
65
|
register: (callback) => {
|
|
28
|
-
const id = deps
|
|
66
|
+
const id = createId<"Callback">(deps);
|
|
29
67
|
callbackMap.set(id, callback);
|
|
30
68
|
return id;
|
|
31
69
|
},
|
|
32
70
|
|
|
33
|
-
execute: (id,
|
|
71
|
+
execute: (id: CallbackId, ...args: T extends undefined ? [] : [T]) => {
|
|
34
72
|
const callback = callbackMap.get(id);
|
|
35
|
-
if (callback)
|
|
36
|
-
|
|
37
|
-
|
|
73
|
+
if (!callback) return;
|
|
74
|
+
callbackMap.delete(id);
|
|
75
|
+
if (args.length === 0) {
|
|
76
|
+
// Called without argument (undefined case)
|
|
77
|
+
(callback as () => void)();
|
|
78
|
+
} else {
|
|
79
|
+
callback(args[0]);
|
|
38
80
|
}
|
|
39
81
|
},
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return callbacks;
|
|
82
|
+
} as Callbacks<T>;
|
|
43
83
|
};
|