@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.
- package/dist/src/Array.d.ts +279 -173
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +105 -101
- package/dist/src/Assert.d.ts +44 -44
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +44 -32
- package/dist/src/BigInt.d.ts +6 -3
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -2
- package/dist/src/Brand.d.ts +27 -40
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Buffer.d.ts +27 -11
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Cache.d.ts +9 -4
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +8 -3
- package/dist/src/Callbacks.d.ts +30 -19
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Console.d.ts +145 -66
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +91 -43
- package/dist/src/Crypto.d.ts +85 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +44 -20
- package/dist/src/Eq.d.ts +87 -21
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +67 -19
- package/dist/src/Error.d.ts +5 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +16 -0
- package/dist/src/Function.d.ts +119 -58
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +86 -43
- package/dist/src/Http.d.ts +394 -0
- package/dist/src/Http.d.ts.map +1 -0
- package/dist/src/Http.js +102 -0
- package/dist/src/Identicon.d.ts +16 -7
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +14 -5
- package/dist/src/LeakDetector.d.ts +88 -0
- package/dist/src/LeakDetector.d.ts.map +1 -0
- package/dist/src/LeakDetector.js +86 -0
- package/dist/src/LockManager.d.ts +18 -9
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/LockManager.js +7 -8
- package/dist/src/Lookup.d.ts +43 -13
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +28 -8
- package/dist/src/Microtask.d.ts +1 -1
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +3 -7
- package/dist/src/Number.d.ts +49 -13
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +34 -5
- package/dist/src/Object.d.ts +160 -40
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +92 -46
- package/dist/src/Option.d.ts +10 -7
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +9 -6
- package/dist/src/Order.d.ts +21 -21
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +18 -21
- package/dist/src/Platform.d.ts +31 -0
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +98 -0
- package/dist/src/Random.d.ts +32 -17
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +12 -8
- package/dist/src/Redacted.d.ts +43 -29
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +14 -3
- package/dist/src/Ref.d.ts +11 -3
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +2 -2
- package/dist/src/RefCount.d.ts +2 -6
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +33 -90
- package/dist/src/Relation.d.ts +70 -5
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +77 -2
- package/dist/src/Resource.d.ts +590 -155
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +592 -343
- package/dist/src/Result.d.ts +838 -191
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +163 -102
- package/dist/src/Schedule.d.ts +684 -231
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +766 -295
- package/dist/src/Set.d.ts +89 -40
- package/dist/src/Set.d.ts.map +1 -1
- package/dist/src/Set.js +50 -29
- package/dist/src/Sqlite.d.ts +29 -72
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +41 -46
- package/dist/src/StackTrace.d.ts +40 -0
- package/dist/src/StackTrace.d.ts.map +1 -0
- package/dist/src/StackTrace.js +140 -0
- package/dist/src/Store.d.ts +5 -4
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +5 -4
- package/dist/src/String.d.ts +7 -1
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +20 -7
- package/dist/src/Task.d.ts +3572 -1770
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1879 -1274
- package/dist/src/Test.d.ts +17 -125
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +17 -68
- package/dist/src/Time.d.ts +108 -49
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +172 -73
- package/dist/src/Type.d.ts +3542 -1926
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +3070 -1784
- package/dist/src/Types.d.ts +200 -108
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +39 -8
- package/dist/src/WebSocket.d.ts +48 -23
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +15 -6
- package/dist/src/Worker.d.ts +94 -36
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +58 -24
- package/dist/src/index.d.ts +55 -52
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/intl/cs.d.ts +94 -0
- package/dist/src/intl/cs.d.ts.map +1 -0
- package/dist/src/intl/cs.js +220 -0
- package/dist/src/intl/index.d.ts +7 -0
- package/dist/src/intl/index.d.ts.map +1 -0
- package/dist/src/intl/index.js +6 -0
- package/dist/src/local-first/Db.d.ts +12 -8
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +109 -132
- package/dist/src/local-first/Error.d.ts +4 -4
- package/dist/src/local-first/Evolu.d.ts +338 -92
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +47 -55
- package/dist/src/local-first/LocalAuth.d.ts +4 -4
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/Owner.d.ts +61 -24
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +45 -8
- package/dist/src/local-first/Protocol.d.ts +44 -31
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +36 -13
- package/dist/src/local-first/Query.d.ts +147 -62
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +112 -58
- package/dist/src/local-first/Relay.d.ts +44 -14
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +92 -41
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +25 -8
- package/dist/src/local-first/Shared.d.ts +23 -21
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +100 -57
- package/dist/src/local-first/Storage.d.ts +53 -32
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +194 -234
- package/dist/src/local-first/Timestamp.d.ts +21 -21
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +2 -2
- package/dist/src/local-first/index.d.ts +11 -11
- package/package.json +14 -13
- package/src/Array.ts +306 -205
- package/src/Assert.ts +54 -69
- package/src/BigInt.ts +6 -3
- package/src/Brand.ts +27 -40
- package/src/Buffer.ts +28 -12
- package/src/Cache.ts +9 -4
- package/src/Callbacks.ts +30 -19
- package/src/Console.ts +156 -84
- package/src/Crypto.ts +83 -41
- package/src/Eq.ts +95 -22
- package/src/Error.ts +8 -4
- package/src/Function.ts +167 -59
- package/src/Http.ts +549 -0
- package/src/Identicon.ts +16 -7
- package/src/LeakDetector.ts +182 -0
- package/src/LockManager.ts +43 -39
- package/src/Lookup.ts +62 -28
- package/src/Microtask.ts +19 -23
- package/src/Number.ts +64 -12
- package/src/Object.ts +173 -49
- package/src/Option.ts +10 -7
- package/src/Order.ts +23 -21
- package/src/Platform.ts +136 -0
- package/src/Polyfills.ts +6 -12
- package/src/Random.ts +32 -19
- package/src/Redacted.ts +44 -30
- package/src/Ref.ts +14 -6
- package/src/RefCount.ts +38 -80
- package/src/Relation.ts +202 -10
- package/src/Resource.ts +1199 -591
- package/src/Result.ts +930 -271
- package/src/Schedule.ts +859 -343
- package/src/Set.ts +89 -40
- package/src/Sqlite.ts +144 -138
- package/src/StackTrace.ts +230 -0
- package/src/Store.ts +8 -7
- package/src/String.ts +22 -5
- package/src/Task.ts +5101 -2832
- package/src/Test.ts +20 -168
- package/src/Time.ts +293 -90
- package/src/Type.ts +10034 -4117
- package/src/Types.ts +227 -110
- package/src/WebSocket.ts +64 -36
- package/src/Worker.ts +180 -72
- package/src/index.ts +55 -52
- package/src/intl/cs.ts +388 -0
- package/src/intl/index.ts +7 -0
- package/src/local-first/Db.ts +152 -179
- package/src/local-first/Error.ts +4 -4
- package/src/local-first/Evolu.ts +495 -250
- package/src/local-first/LocalAuth.ts +6 -12
- package/src/local-first/Owner.ts +58 -21
- package/src/local-first/Protocol.ts +49 -31
- package/src/local-first/Query.ts +146 -61
- package/src/local-first/Relay.ts +51 -21
- package/src/local-first/Schema.ts +109 -51
- package/src/local-first/Shared.ts +266 -226
- package/src/local-first/Storage.ts +266 -264
- package/src/local-first/Timestamp.ts +25 -18
- package/src/local-first/index.ts +11 -11
- package/dist/src/Skiplist.d.ts +0 -28
- package/dist/src/Skiplist.d.ts.map +0 -1
- package/dist/src/Skiplist.js +0 -64
- package/dist/src/Tracer.d.ts +0 -48
- package/dist/src/Tracer.d.ts.map +0 -1
- package/dist/src/Tracer.js +0 -6
- package/src/Skiplist.ts +0 -108
- package/src/Tracer.ts +0 -54
package/src/Array.ts
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
*
|
|
21
21
|
* const scores = [3, 1, 2];
|
|
22
22
|
* const leaderboard = sortScores(scores);
|
|
23
|
-
* leaderboard
|
|
24
|
-
* scores
|
|
23
|
+
* expect(leaderboard).toEqual([1, 2, 3]);
|
|
24
|
+
* expect(scores).toEqual([1, 2, 3]);
|
|
25
|
+
* expect(leaderboard).toBe(scores);
|
|
25
26
|
* ```
|
|
26
27
|
*
|
|
27
28
|
* Imagine every method doing that.
|
|
@@ -30,71 +31,50 @@
|
|
|
30
31
|
* instead:
|
|
31
32
|
*
|
|
32
33
|
* ```ts
|
|
34
|
+
* import { sortArray } from "@evolu/common";
|
|
35
|
+
*
|
|
33
36
|
* const sortScores = (arr: ReadonlyArray<number>) =>
|
|
34
37
|
* sortArray(arr, (a, b) => a - b);
|
|
35
38
|
*
|
|
36
39
|
* const scores: ReadonlyArray<number> = [3, 1, 2];
|
|
37
40
|
* const leaderboard = sortScores(scores);
|
|
38
|
-
* leaderboard
|
|
39
|
-
* scores
|
|
41
|
+
* expect(leaderboard).toEqual([1, 2, 3]);
|
|
42
|
+
* expect(scores).toEqual([3, 1, 2]);
|
|
43
|
+
* expect(leaderboard).not.toBe(scores);
|
|
40
44
|
* ```
|
|
41
45
|
*
|
|
42
46
|
* Even better, require a {@link NonEmptyReadonlyArray} — there's nothing to sort
|
|
43
47
|
* if the array is empty anyway:
|
|
44
48
|
*
|
|
45
49
|
* ```ts
|
|
50
|
+
* import { sortArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
51
|
+
*
|
|
46
52
|
* const sortScores = (arr: NonEmptyReadonlyArray<number>) =>
|
|
47
53
|
* sortArray(arr, (a, b) => a - b);
|
|
54
|
+
*
|
|
55
|
+
* const leaderboard = sortScores([3, 1, 2]);
|
|
56
|
+
* expect(leaderboard).toEqual([1, 2, 3]);
|
|
57
|
+
* expectTypeOf(leaderboard).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
48
58
|
* ```
|
|
49
59
|
*
|
|
50
60
|
* Sorting an empty array isn't expensive, but functions can have side effects
|
|
51
61
|
* like database queries or network requests. Using non-empty arrays whenever
|
|
52
62
|
* possible is a good convention.
|
|
53
63
|
*
|
|
54
|
-
* For performance-critical cases where mutation is needed, Evolu provides
|
|
55
|
-
* {@link shiftFromArray} and {@link popFromArray} — but only because they improve
|
|
56
|
-
* type safety by returning a guaranteed `T` rather than an optional value.
|
|
57
|
-
*
|
|
58
64
|
* ### When to use native methods
|
|
59
65
|
*
|
|
60
66
|
* These helpers only exist where they add type-level value. Native methods like
|
|
61
67
|
* `find`, `some`, `every`, `includes`, `indexOf`, and `findIndex` work well on
|
|
62
68
|
* readonly arrays without mutation — use them directly.
|
|
63
69
|
*
|
|
64
|
-
* ## Examples
|
|
65
|
-
*
|
|
66
70
|
* ```ts
|
|
67
|
-
*
|
|
68
|
-
* const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
69
|
-
* // ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
|
|
70
|
-
* const _invalid: NonEmptyReadonlyArray<number> = [];
|
|
71
|
-
*
|
|
72
|
-
* // Type guards
|
|
73
|
-
* const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
74
|
-
* if (isNonEmptyArray(arr)) {
|
|
75
|
-
* firstInArray(arr);
|
|
76
|
-
* }
|
|
77
|
-
*
|
|
78
|
-
* // Transformations
|
|
79
|
-
* const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
|
|
80
|
-
* const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
|
|
81
|
-
* const readonly: ReadonlyArray<number> = [1, 2, 3];
|
|
82
|
-
* const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
|
|
83
|
-
* const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
|
|
84
|
-
* const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
|
|
85
|
-
* const [evens, odds] = partitionArray(
|
|
86
|
-
* [1, 2, 3, 4, 5],
|
|
87
|
-
* (x) => x % 2 === 0,
|
|
88
|
-
* );
|
|
71
|
+
* import { type NonEmptyReadonlyArray } from "@evolu/common";
|
|
89
72
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* const
|
|
73
|
+
* const valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
74
|
+
* // @ts-expect-error An empty array is not non-empty.
|
|
75
|
+
* const invalid: NonEmptyReadonlyArray<number> = [];
|
|
93
76
|
*
|
|
94
|
-
*
|
|
95
|
-
* const mutable: NonEmptyArray<number> = [1, 2, 3];
|
|
96
|
-
* shiftFromArray(mutable); // 1 (guaranteed to exist)
|
|
97
|
-
* mutable; // [2, 3]
|
|
77
|
+
* expect(valid.find((value) => value === 2)).toBe(2);
|
|
98
78
|
* ```
|
|
99
79
|
*
|
|
100
80
|
* ## Composition
|
|
@@ -103,16 +83,51 @@
|
|
|
103
83
|
* because it's natural for single operations:
|
|
104
84
|
*
|
|
105
85
|
* ```ts
|
|
86
|
+
* import { mapArray } from "@evolu/common";
|
|
87
|
+
*
|
|
88
|
+
* interface Message {
|
|
89
|
+
* readonly timestamp: number;
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* const messages: ReadonlyArray<Message> = [
|
|
93
|
+
* { timestamp: 10 },
|
|
94
|
+
* { timestamp: 20 },
|
|
95
|
+
* ];
|
|
106
96
|
* const timestamps = mapArray(messages, (m) => m.timestamp);
|
|
97
|
+
* expect(timestamps).toEqual([10, 20]);
|
|
107
98
|
* ```
|
|
108
99
|
*
|
|
109
100
|
* Data-first style also reads well for a few operations, often fitting on a
|
|
110
101
|
* line:
|
|
111
102
|
*
|
|
112
103
|
* ```ts
|
|
113
|
-
*
|
|
104
|
+
* import {
|
|
105
|
+
* dedupeArray,
|
|
106
|
+
* filterArray,
|
|
107
|
+
* firstInArray,
|
|
108
|
+
* isNonEmptyArray,
|
|
109
|
+
* lastInArray,
|
|
110
|
+
* mapArray,
|
|
111
|
+
* orderNumber,
|
|
112
|
+
* sortArray,
|
|
113
|
+
* } from "@evolu/common";
|
|
114
|
+
*
|
|
115
|
+
* const cheapest = firstInArray(sortArray([30, 10, 20], orderNumber));
|
|
116
|
+
* expect(cheapest).toBe(10);
|
|
117
|
+
*
|
|
118
|
+
* const users = [{ name: "Ada" }, { name: "Linus" }, { name: "Ada" }];
|
|
114
119
|
* const uniqueNames = dedupeArray(mapArray(users, (u) => u.name));
|
|
115
|
-
*
|
|
120
|
+
* expect(uniqueNames).toEqual(["Ada", "Linus"]);
|
|
121
|
+
*
|
|
122
|
+
* const jobs = [
|
|
123
|
+
* { id: 1, done: false },
|
|
124
|
+
* { id: 2, done: true },
|
|
125
|
+
* ];
|
|
126
|
+
* const completedJobs = filterArray(jobs, (job) => job.done);
|
|
127
|
+
* if (!isNonEmptyArray(completedJobs)) throw new Error("Expected a job");
|
|
128
|
+
* const latestDone = lastInArray(completedJobs);
|
|
129
|
+
*
|
|
130
|
+
* expect(latestDone).toEqual({ id: 2, done: true });
|
|
116
131
|
* ```
|
|
117
132
|
*
|
|
118
133
|
* For more operations, create a function like `getOldestActiveUser` or a
|
|
@@ -128,8 +143,7 @@
|
|
|
128
143
|
* @module
|
|
129
144
|
*/
|
|
130
145
|
|
|
131
|
-
import { identity } from "./Function.
|
|
132
|
-
import type { PredicateWithIndex, RefinementWithIndex } from "./Types.js";
|
|
146
|
+
import { identity } from "./Function.ts";
|
|
133
147
|
|
|
134
148
|
/**
|
|
135
149
|
* An array with at least one element.
|
|
@@ -145,6 +159,13 @@ export type NonEmptyArray<T> = [T, ...Array<T>];
|
|
|
145
159
|
*/
|
|
146
160
|
export type NonEmptyReadonlyArray<T> = readonly [T, ...ReadonlyArray<T>];
|
|
147
161
|
|
|
162
|
+
/**
|
|
163
|
+
* A readonly array with at least two elements.
|
|
164
|
+
*
|
|
165
|
+
* @group Types
|
|
166
|
+
*/
|
|
167
|
+
export type AtLeastTwoReadonlyArray<T> = readonly [T, T, ...ReadonlyArray<T>];
|
|
168
|
+
|
|
148
169
|
/**
|
|
149
170
|
* An empty readonly array.
|
|
150
171
|
*
|
|
@@ -160,14 +181,21 @@ export const emptyArray: ReadonlyArray<never> = [];
|
|
|
160
181
|
* - Returns readonly arrays
|
|
161
182
|
* - Accepts length directly: `arrayFrom(3, fn)` instead of `Array.from({ length:
|
|
162
183
|
* 3 }, fn)`
|
|
163
|
-
* -
|
|
184
|
+
* - Returns existing arrays unchanged
|
|
164
185
|
*
|
|
165
|
-
* ###
|
|
186
|
+
* ### Creating from iterables and lengths
|
|
166
187
|
*
|
|
167
188
|
* ```ts
|
|
168
|
-
* arrayFrom
|
|
169
|
-
*
|
|
170
|
-
* arrayFrom(
|
|
189
|
+
* import { arrayFrom } from "@evolu/common";
|
|
190
|
+
*
|
|
191
|
+
* const fromSet = arrayFrom(new Set([1, 2, 3]));
|
|
192
|
+
* expect(fromSet).toEqual([1, 2, 3]);
|
|
193
|
+
* expectTypeOf(fromSet).toEqualTypeOf<ReadonlyArray<number>>();
|
|
194
|
+
*
|
|
195
|
+
* expect(arrayFrom(3, (i) => i * 10)).toEqual([0, 10, 20]);
|
|
196
|
+
*
|
|
197
|
+
* const existing: ReadonlyArray<number> = [1, 2, 3];
|
|
198
|
+
* expect(arrayFrom(existing)).toBe(existing);
|
|
171
199
|
* ```
|
|
172
200
|
*
|
|
173
201
|
* Unlike `Array.from`, there's no map parameter for iterables — use
|
|
@@ -203,16 +231,22 @@ export function arrayFrom<T>(
|
|
|
203
231
|
* Returns a readonly array and awaits promised items from sync or async
|
|
204
232
|
* iterables.
|
|
205
233
|
*
|
|
206
|
-
* ###
|
|
234
|
+
* ### Awaiting sync and async iterables
|
|
207
235
|
*
|
|
208
236
|
* ```ts
|
|
209
|
-
*
|
|
210
|
-
*
|
|
237
|
+
* import { arrayFromAsync } from "@evolu/common";
|
|
238
|
+
*
|
|
239
|
+
* const fromIterable = await arrayFromAsync(new Set([1, 2, 3]));
|
|
240
|
+
* expect(fromIterable).toEqual([1, 2, 3]);
|
|
241
|
+
*
|
|
242
|
+
* const fromAsyncIterable = await arrayFromAsync(
|
|
211
243
|
* (async function* () {
|
|
212
244
|
* yield Promise.resolve(1);
|
|
213
245
|
* yield Promise.resolve(2);
|
|
214
246
|
* })(),
|
|
215
|
-
* );
|
|
247
|
+
* );
|
|
248
|
+
* expect(fromAsyncIterable).toEqual([1, 2]);
|
|
249
|
+
* expectTypeOf(fromAsyncIterable).toEqualTypeOf<ReadonlyArray<number>>();
|
|
216
250
|
* ```
|
|
217
251
|
*
|
|
218
252
|
* Unlike `Array.fromAsync`, there's no map parameter — map the result with
|
|
@@ -234,20 +268,25 @@ export const arrayFromAsync = async <T>(
|
|
|
234
268
|
* negated guard is better than `.length === 0` for early returns because
|
|
235
269
|
* TypeScript narrows the type after the check.
|
|
236
270
|
*
|
|
237
|
-
* ###
|
|
271
|
+
* ### Narrowing mutable and readonly arrays
|
|
238
272
|
*
|
|
239
273
|
* ```ts
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
274
|
+
* import {
|
|
275
|
+
* firstInArray,
|
|
276
|
+
* isNonEmptyArray,
|
|
277
|
+
* type NonEmptyArray,
|
|
278
|
+
* type NonEmptyReadonlyArray,
|
|
279
|
+
* } from "@evolu/common";
|
|
280
|
+
*
|
|
281
|
+
* const mutable: Array<number> = [1, 2, 3];
|
|
247
282
|
* const readonly: ReadonlyArray<number> = [1, 2, 3];
|
|
248
|
-
* if (isNonEmptyArray(readonly)) {
|
|
249
|
-
*
|
|
283
|
+
* if (!isNonEmptyArray(mutable) || !isNonEmptyArray(readonly)) {
|
|
284
|
+
* throw new Error("Expected values");
|
|
250
285
|
* }
|
|
286
|
+
*
|
|
287
|
+
* expectTypeOf(mutable).toEqualTypeOf<NonEmptyArray<number>>();
|
|
288
|
+
* expectTypeOf(readonly).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
289
|
+
* expect(firstInArray(readonly)).toBe(1);
|
|
251
290
|
* ```
|
|
252
291
|
*
|
|
253
292
|
* @group Types
|
|
@@ -266,10 +305,17 @@ export function isNonEmptyArray<T>(
|
|
|
266
305
|
/**
|
|
267
306
|
* Appends an item to an array, returning a new non-empty readonly array.
|
|
268
307
|
*
|
|
269
|
-
* ###
|
|
308
|
+
* ### Appending a value
|
|
270
309
|
*
|
|
271
310
|
* ```ts
|
|
272
|
-
*
|
|
311
|
+
* import {
|
|
312
|
+
* appendToArray,
|
|
313
|
+
* type NonEmptyReadonlyArray,
|
|
314
|
+
* } from "@evolu/common";
|
|
315
|
+
*
|
|
316
|
+
* const values = appendToArray([1, 2, 3], 4);
|
|
317
|
+
* expect(values).toEqual([1, 2, 3, 4]);
|
|
318
|
+
* expectTypeOf(values).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
273
319
|
* ```
|
|
274
320
|
*
|
|
275
321
|
* @group Transformations
|
|
@@ -278,15 +324,24 @@ export const appendToArray = <T>(
|
|
|
278
324
|
array: ReadonlyArray<T>,
|
|
279
325
|
item: T,
|
|
280
326
|
): NonEmptyReadonlyArray<T> =>
|
|
327
|
+
// The appended item guarantees non-emptiness, but TypeScript cannot infer a
|
|
328
|
+
// leading element after spreading a possibly empty array.
|
|
281
329
|
[...array, item] as ReadonlyArray<T> as NonEmptyReadonlyArray<T>;
|
|
282
330
|
|
|
283
331
|
/**
|
|
284
332
|
* Prepends an item to an array, returning a new non-empty readonly array.
|
|
285
333
|
*
|
|
286
|
-
* ###
|
|
334
|
+
* ### Prepending a value
|
|
287
335
|
*
|
|
288
336
|
* ```ts
|
|
289
|
-
*
|
|
337
|
+
* import {
|
|
338
|
+
* prependToArray,
|
|
339
|
+
* type NonEmptyReadonlyArray,
|
|
340
|
+
* } from "@evolu/common";
|
|
341
|
+
*
|
|
342
|
+
* const values = prependToArray([2, 3], 1);
|
|
343
|
+
* expect(values).toEqual([1, 2, 3]);
|
|
344
|
+
* expectTypeOf(values).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
290
345
|
* ```
|
|
291
346
|
*
|
|
292
347
|
* @group Transformations
|
|
@@ -294,39 +349,47 @@ export const appendToArray = <T>(
|
|
|
294
349
|
export const prependToArray = <T>(
|
|
295
350
|
array: ReadonlyArray<T>,
|
|
296
351
|
item: T,
|
|
297
|
-
): NonEmptyReadonlyArray<T> => [item, ...array]
|
|
352
|
+
): NonEmptyReadonlyArray<T> => [item, ...array];
|
|
298
353
|
|
|
299
354
|
/**
|
|
300
355
|
* Maps an array using a mapper function, returning a new readonly array.
|
|
301
356
|
*
|
|
302
357
|
* Preserves non-empty type.
|
|
303
358
|
*
|
|
304
|
-
* ###
|
|
359
|
+
* ### Mapping with indexes while preserving non-emptiness
|
|
305
360
|
*
|
|
306
361
|
* ```ts
|
|
307
|
-
*
|
|
362
|
+
* import { mapArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
363
|
+
*
|
|
364
|
+
* const values: ReadonlyArray<number> = [1, 2, 3];
|
|
365
|
+
* const indexed = mapArray(values, (value, index) => value + index);
|
|
366
|
+
* expect(indexed).toEqual([1, 3, 5]);
|
|
367
|
+
* expectTypeOf(indexed).toEqualTypeOf<ReadonlyArray<number>>();
|
|
368
|
+
*
|
|
369
|
+
* const nonEmpty: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
370
|
+
* expectTypeOf(mapArray(nonEmpty, (x) => x * 2)).toEqualTypeOf<
|
|
371
|
+
* NonEmptyReadonlyArray<number>
|
|
372
|
+
* >();
|
|
308
373
|
* ```
|
|
309
374
|
*
|
|
375
|
+
* The mapper receives `(item, index, array)`, matching native `Array.map`.
|
|
376
|
+
*
|
|
310
377
|
* @group Transformations
|
|
311
378
|
*/
|
|
312
379
|
export function mapArray<T, U>(
|
|
313
|
-
array: NonEmptyReadonlyArray<T
|
|
314
|
-
mapper: (item: T, index: number) => U,
|
|
380
|
+
array: NonEmptyReadonlyArray<T>,
|
|
381
|
+
mapper: (item: T, index: number, array: ReadonlyArray<T>) => U,
|
|
315
382
|
): NonEmptyReadonlyArray<U>;
|
|
316
383
|
/** Possibly empty array. */
|
|
317
384
|
export function mapArray<T, U>(
|
|
318
|
-
array: ReadonlyArray<T
|
|
319
|
-
mapper: (item: T, index: number) => U,
|
|
385
|
+
array: ReadonlyArray<T>,
|
|
386
|
+
mapper: (item: T, index: number, array: ReadonlyArray<T>) => U,
|
|
320
387
|
): ReadonlyArray<U>;
|
|
321
388
|
export function mapArray<T, U>(
|
|
322
|
-
array: ReadonlyArray<T
|
|
323
|
-
mapper: (item: T, index: number) => U,
|
|
389
|
+
array: ReadonlyArray<T>,
|
|
390
|
+
mapper: (item: T, index: number, array: ReadonlyArray<T>) => U,
|
|
324
391
|
): ReadonlyArray<U> {
|
|
325
|
-
|
|
326
|
-
const length = array.length;
|
|
327
|
-
const result = new Array<U>(length);
|
|
328
|
-
for (let i = 0; i < length; i++) result[i] = mapper(array[i], i);
|
|
329
|
-
return result;
|
|
392
|
+
return array.map(mapper);
|
|
330
393
|
}
|
|
331
394
|
|
|
332
395
|
/**
|
|
@@ -336,15 +399,23 @@ export function mapArray<T, U>(
|
|
|
336
399
|
* non-empty arrays. When called without a mapper, flattens nested arrays using
|
|
337
400
|
* {@link identity}.
|
|
338
401
|
*
|
|
339
|
-
* ###
|
|
402
|
+
* ### Flattening and expanding values
|
|
340
403
|
*
|
|
341
404
|
* ```ts
|
|
342
|
-
* flatMapArray
|
|
405
|
+
* import { flatMapArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
406
|
+
*
|
|
407
|
+
* const flattened = flatMapArray([
|
|
343
408
|
* [1, 2],
|
|
344
409
|
* [3, 4],
|
|
345
|
-
* ]);
|
|
346
|
-
*
|
|
347
|
-
*
|
|
410
|
+
* ]);
|
|
411
|
+
* const values: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
412
|
+
* const expanded = flatMapArray(
|
|
413
|
+
* values,
|
|
414
|
+
* (value, index): NonEmptyReadonlyArray<number> => [value, index],
|
|
415
|
+
* );
|
|
416
|
+
* expect(flattened).toEqual([1, 2, 3, 4]);
|
|
417
|
+
* expectTypeOf(expanded).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
418
|
+
* expect(expanded).toEqual([1, 0, 2, 1, 3, 2]);
|
|
348
419
|
* ```
|
|
349
420
|
*
|
|
350
421
|
* ### Filter and map in one pass
|
|
@@ -352,47 +423,54 @@ export function mapArray<T, U>(
|
|
|
352
423
|
* Return `[]` to filter out, `[value]` to keep:
|
|
353
424
|
*
|
|
354
425
|
* ```ts
|
|
426
|
+
* import { err, flatMapArray, ok } from "@evolu/common";
|
|
427
|
+
*
|
|
428
|
+
* const validate = (value: number) =>
|
|
429
|
+
* value > 0 ? ok(value) : err(`${value} is not positive`);
|
|
430
|
+
* const fields = [1, -2, 3, -4];
|
|
355
431
|
* const errors = flatMapArray(fields, (f) => {
|
|
356
432
|
* const result = validate(f);
|
|
357
433
|
* return result.ok ? [] : [result.error];
|
|
358
434
|
* });
|
|
435
|
+
* expect(errors).toEqual(["-2 is not positive", "-4 is not positive"]);
|
|
359
436
|
* ```
|
|
360
437
|
*
|
|
438
|
+
* The mapper receives `(item, index, array)`, matching native `Array.flatMap`.
|
|
439
|
+
*
|
|
361
440
|
* @group Transformations
|
|
362
441
|
*/
|
|
363
442
|
export function flatMapArray<T>(
|
|
364
|
-
array:
|
|
365
|
-
| NonEmptyReadonlyArray<NonEmptyReadonlyArray<T> | NonEmptyArray<T>>
|
|
366
|
-
| NonEmptyArray<NonEmptyReadonlyArray<T> | NonEmptyArray<T>>,
|
|
443
|
+
array: NonEmptyReadonlyArray<NonEmptyReadonlyArray<T>>,
|
|
367
444
|
): NonEmptyReadonlyArray<T>;
|
|
368
445
|
/** Possibly empty nested arrays. */
|
|
369
446
|
export function flatMapArray<T>(
|
|
370
|
-
array:
|
|
371
|
-
| ReadonlyArray<ReadonlyArray<T> | Array<T>>
|
|
372
|
-
| Array<ReadonlyArray<T> | Array<T>>,
|
|
447
|
+
array: ReadonlyArray<ReadonlyArray<T>>,
|
|
373
448
|
): ReadonlyArray<T>;
|
|
374
449
|
/** Non-empty with mapper returning non-empty. */
|
|
375
450
|
export function flatMapArray<T, U>(
|
|
376
|
-
array: NonEmptyReadonlyArray<T
|
|
451
|
+
array: NonEmptyReadonlyArray<T>,
|
|
377
452
|
mapper: (
|
|
378
453
|
item: T,
|
|
379
454
|
index: number,
|
|
380
|
-
|
|
455
|
+
array: ReadonlyArray<T>,
|
|
456
|
+
) => NonEmptyReadonlyArray<U>,
|
|
381
457
|
): NonEmptyReadonlyArray<U>;
|
|
382
458
|
/** With mapper function. */
|
|
383
459
|
export function flatMapArray<T, U>(
|
|
384
|
-
array: ReadonlyArray<T
|
|
385
|
-
mapper: (item: T, index: number
|
|
460
|
+
array: ReadonlyArray<T>,
|
|
461
|
+
mapper: (item: T, index: number, array: ReadonlyArray<T>) => ReadonlyArray<U>,
|
|
386
462
|
): ReadonlyArray<U>;
|
|
387
463
|
export function flatMapArray<T, U>(
|
|
388
|
-
array: ReadonlyArray<T
|
|
464
|
+
array: ReadonlyArray<T>,
|
|
389
465
|
mapper: (
|
|
390
466
|
item: T,
|
|
391
467
|
index: number,
|
|
392
|
-
|
|
468
|
+
array: ReadonlyArray<T>,
|
|
469
|
+
) => ReadonlyArray<U> = identity as (
|
|
393
470
|
item: T,
|
|
394
471
|
index: number,
|
|
395
|
-
|
|
472
|
+
array: ReadonlyArray<T>,
|
|
473
|
+
) => ReadonlyArray<U>,
|
|
396
474
|
): ReadonlyArray<U> {
|
|
397
475
|
return array.flatMap(mapper);
|
|
398
476
|
}
|
|
@@ -402,35 +480,42 @@ export function flatMapArray<T, U>(
|
|
|
402
480
|
*
|
|
403
481
|
* Returns a non-empty array when at least one input is non-empty.
|
|
404
482
|
*
|
|
405
|
-
* ###
|
|
483
|
+
* ### Concatenating non-empty inputs
|
|
406
484
|
*
|
|
407
485
|
* ```ts
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
486
|
+
* import { concatArrays, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
487
|
+
*
|
|
488
|
+
* const nonEmpty: NonEmptyReadonlyArray<number> = [1];
|
|
489
|
+
* const joined = concatArrays([1, 2], [3, 4]);
|
|
490
|
+
* const fromLeft = concatArrays(nonEmpty, []);
|
|
491
|
+
* const fromRight = concatArrays([], nonEmpty);
|
|
492
|
+
*
|
|
493
|
+
* expect(joined).toEqual([1, 2, 3, 4]);
|
|
494
|
+
* expectTypeOf(fromLeft).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
495
|
+
* expectTypeOf(fromRight).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
411
496
|
* ```
|
|
412
497
|
*
|
|
413
498
|
* @group Transformations
|
|
414
499
|
*/
|
|
415
500
|
export function concatArrays<T>(
|
|
416
|
-
first: NonEmptyReadonlyArray<T
|
|
417
|
-
second: ReadonlyArray<T
|
|
501
|
+
first: NonEmptyReadonlyArray<T>,
|
|
502
|
+
second: ReadonlyArray<T>,
|
|
418
503
|
): NonEmptyReadonlyArray<T>;
|
|
419
504
|
/** Second non-empty. */
|
|
420
505
|
export function concatArrays<T>(
|
|
421
|
-
first: ReadonlyArray<T
|
|
422
|
-
second: NonEmptyReadonlyArray<T
|
|
506
|
+
first: ReadonlyArray<T>,
|
|
507
|
+
second: NonEmptyReadonlyArray<T>,
|
|
423
508
|
): NonEmptyReadonlyArray<T>;
|
|
424
509
|
/** Both possibly empty. */
|
|
425
510
|
export function concatArrays<T>(
|
|
426
|
-
first: ReadonlyArray<T
|
|
427
|
-
second: ReadonlyArray<T
|
|
511
|
+
first: ReadonlyArray<T>,
|
|
512
|
+
second: ReadonlyArray<T>,
|
|
428
513
|
): ReadonlyArray<T>;
|
|
429
514
|
export function concatArrays<T>(
|
|
430
|
-
first: ReadonlyArray<T
|
|
431
|
-
second: ReadonlyArray<T
|
|
515
|
+
first: ReadonlyArray<T>,
|
|
516
|
+
second: ReadonlyArray<T>,
|
|
432
517
|
): ReadonlyArray<T> {
|
|
433
|
-
return [...first, ...second]
|
|
518
|
+
return [...first, ...second];
|
|
434
519
|
}
|
|
435
520
|
|
|
436
521
|
/**
|
|
@@ -441,77 +526,92 @@ export function concatArrays<T>(
|
|
|
441
526
|
* TypeScript will narrow the result type to the narrowed type, making it useful
|
|
442
527
|
* for filtering with Evolu Types like `PositiveInt.is`.
|
|
443
528
|
*
|
|
444
|
-
* ### Example
|
|
445
|
-
*
|
|
446
529
|
* ### With predicate
|
|
447
530
|
*
|
|
448
531
|
* ```ts
|
|
449
|
-
*
|
|
532
|
+
* import { filterArray } from "@evolu/common";
|
|
533
|
+
*
|
|
534
|
+
* const evens = filterArray([1, 2, 3, 4, 5], (x) => x % 2 === 0);
|
|
535
|
+
* expect(evens).toEqual([2, 4]);
|
|
450
536
|
* ```
|
|
451
537
|
*
|
|
452
538
|
* ### With refinement
|
|
453
539
|
*
|
|
454
540
|
* ```ts
|
|
455
|
-
*
|
|
456
|
-
*
|
|
541
|
+
* import {
|
|
542
|
+
* filterArray,
|
|
543
|
+
* NonEmptyTrimmedString,
|
|
544
|
+
* PositiveInt,
|
|
545
|
+
* } from "@evolu/common";
|
|
546
|
+
*
|
|
547
|
+
* const mixed: ReadonlyArray<NonEmptyTrimmedString | PositiveInt> = [
|
|
548
|
+
* NonEmptyTrimmedString.orThrow("hello"),
|
|
457
549
|
* PositiveInt.orThrow(42),
|
|
458
550
|
* ];
|
|
459
551
|
* const positiveInts = filterArray(mixed, PositiveInt.is);
|
|
460
|
-
*
|
|
552
|
+
* expect(positiveInts).toEqual([42]);
|
|
553
|
+
* expectTypeOf(positiveInts).toEqualTypeOf<ReadonlyArray<PositiveInt>>();
|
|
461
554
|
* ```
|
|
462
555
|
*
|
|
556
|
+
* The predicate receives `(item, index, array)`, matching native
|
|
557
|
+
* `Array.filter`.
|
|
558
|
+
*
|
|
463
559
|
* @group Transformations
|
|
464
560
|
*/
|
|
465
561
|
export function filterArray<T, S extends T>(
|
|
466
562
|
array: ReadonlyArray<T>,
|
|
467
|
-
refinement:
|
|
563
|
+
refinement: (item: T, index: number, array: ReadonlyArray<T>) => item is S,
|
|
468
564
|
): ReadonlyArray<S>;
|
|
469
565
|
/** With predicate. */
|
|
470
566
|
export function filterArray<T>(
|
|
471
567
|
array: ReadonlyArray<T>,
|
|
472
|
-
predicate:
|
|
568
|
+
predicate: (item: T, index: number, array: ReadonlyArray<T>) => boolean,
|
|
473
569
|
): ReadonlyArray<T>;
|
|
474
570
|
export function filterArray<T>(
|
|
475
571
|
array: ReadonlyArray<T>,
|
|
476
|
-
predicate:
|
|
572
|
+
predicate: (item: T, index: number, array: ReadonlyArray<T>) => boolean,
|
|
477
573
|
): ReadonlyArray<T> {
|
|
478
574
|
return array.filter(predicate);
|
|
479
575
|
}
|
|
480
576
|
|
|
481
577
|
/**
|
|
482
|
-
* Returns a new readonly array with duplicate items removed.
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
* when `by` is used).
|
|
578
|
+
* Returns a new readonly array with duplicate items removed. Items are compared
|
|
579
|
+
* using `Set` equality (SameValueZero): primitives by value, and objects and
|
|
580
|
+
* arrays by reference. If `by` is provided, it derives the comparison key.
|
|
486
581
|
*
|
|
487
582
|
* Preserves non-empty type.
|
|
488
583
|
*
|
|
489
|
-
* ###
|
|
584
|
+
* ### Deduplicating by value or key
|
|
490
585
|
*
|
|
491
586
|
* ```ts
|
|
492
|
-
*
|
|
493
|
-
* dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
|
|
587
|
+
* import { dedupeArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
494
588
|
*
|
|
495
|
-
*
|
|
496
|
-
* dedupeArray(
|
|
589
|
+
* const numbers = dedupeArray([1, 2, 1, 3, 2]);
|
|
590
|
+
* const people = dedupeArray(
|
|
497
591
|
* [
|
|
498
592
|
* { id: 1, name: "Alice" },
|
|
499
593
|
* { id: 2, name: "Bob" },
|
|
500
594
|
* { id: 1, name: "Alice 2" },
|
|
501
595
|
* ],
|
|
502
596
|
* (item) => item.id,
|
|
503
|
-
* );
|
|
597
|
+
* );
|
|
598
|
+
* expectTypeOf(numbers).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
599
|
+
* expect(numbers).toEqual([1, 2, 3]);
|
|
600
|
+
* expect(people).toEqual([
|
|
601
|
+
* { id: 1, name: "Alice" },
|
|
602
|
+
* { id: 2, name: "Bob" },
|
|
603
|
+
* ]);
|
|
504
604
|
* ```
|
|
505
605
|
*
|
|
506
606
|
* @group Transformations
|
|
507
607
|
*/
|
|
508
608
|
export function dedupeArray<T>(
|
|
509
|
-
array: NonEmptyReadonlyArray<T
|
|
609
|
+
array: NonEmptyReadonlyArray<T>,
|
|
510
610
|
by?: (item: T) => unknown,
|
|
511
611
|
): NonEmptyReadonlyArray<T>;
|
|
512
612
|
/** Possibly empty array. */
|
|
513
613
|
export function dedupeArray<T>(
|
|
514
|
-
array: ReadonlyArray<T
|
|
614
|
+
array: ReadonlyArray<T>,
|
|
515
615
|
by?: (item: T) => unknown,
|
|
516
616
|
): ReadonlyArray<T>;
|
|
517
617
|
export function dedupeArray<T>(
|
|
@@ -542,51 +642,62 @@ export function dedupeArray<T>(
|
|
|
542
642
|
* TypeScript will narrow the first array to the narrowed type, making it useful
|
|
543
643
|
* for filtering with Evolu Types like `PositiveInt.is`.
|
|
544
644
|
*
|
|
545
|
-
* ### Example
|
|
546
|
-
*
|
|
547
645
|
* ### With predicate
|
|
548
646
|
*
|
|
549
647
|
* ```ts
|
|
648
|
+
* import { partitionArray } from "@evolu/common";
|
|
649
|
+
*
|
|
550
650
|
* const [evens, odds] = partitionArray(
|
|
551
651
|
* [1, 2, 3, 4, 5],
|
|
552
652
|
* (x) => x % 2 === 0,
|
|
553
653
|
* );
|
|
554
|
-
* evens
|
|
555
|
-
* odds
|
|
654
|
+
* expect(evens).toEqual([2, 4]);
|
|
655
|
+
* expect(odds).toEqual([1, 3, 5]);
|
|
556
656
|
* ```
|
|
557
657
|
*
|
|
558
658
|
* ### With refinement
|
|
559
659
|
*
|
|
560
660
|
* ```ts
|
|
561
|
-
*
|
|
562
|
-
*
|
|
661
|
+
* import {
|
|
662
|
+
* NonEmptyTrimmedString,
|
|
663
|
+
* partitionArray,
|
|
664
|
+
* PositiveInt,
|
|
665
|
+
* } from "@evolu/common";
|
|
666
|
+
*
|
|
667
|
+
* const mixed: ReadonlyArray<NonEmptyTrimmedString | PositiveInt> = [
|
|
668
|
+
* NonEmptyTrimmedString.orThrow("hello"),
|
|
563
669
|
* PositiveInt.orThrow(42),
|
|
564
670
|
* ];
|
|
565
671
|
* const [positiveInts, strings] = partitionArray(mixed, PositiveInt.is);
|
|
566
|
-
*
|
|
567
|
-
*
|
|
672
|
+
* expect(positiveInts).toEqual([42]);
|
|
673
|
+
* expectTypeOf(positiveInts).toEqualTypeOf<ReadonlyArray<PositiveInt>>();
|
|
674
|
+
* expectTypeOf(strings).toEqualTypeOf<
|
|
675
|
+
* ReadonlyArray<NonEmptyTrimmedString>
|
|
676
|
+
* >();
|
|
568
677
|
* ```
|
|
569
678
|
*
|
|
679
|
+
* The predicate receives `(item, index, array)`.
|
|
680
|
+
*
|
|
570
681
|
* @group Transformations
|
|
571
682
|
*/
|
|
572
683
|
export function partitionArray<T, S extends T>(
|
|
573
684
|
array: ReadonlyArray<T>,
|
|
574
|
-
refinement:
|
|
685
|
+
refinement: (item: T, index: number, array: ReadonlyArray<T>) => item is S,
|
|
575
686
|
): readonly [ReadonlyArray<S>, ReadonlyArray<Exclude<T, S>>];
|
|
576
687
|
/** With predicate. */
|
|
577
688
|
export function partitionArray<T>(
|
|
578
689
|
array: ReadonlyArray<T>,
|
|
579
|
-
predicate:
|
|
690
|
+
predicate: (item: T, index: number, array: ReadonlyArray<T>) => boolean,
|
|
580
691
|
): readonly [ReadonlyArray<T>, ReadonlyArray<T>];
|
|
581
692
|
export function partitionArray<T>(
|
|
582
693
|
array: ReadonlyArray<T>,
|
|
583
|
-
predicate:
|
|
694
|
+
predicate: (item: T, index: number, array: ReadonlyArray<T>) => boolean,
|
|
584
695
|
): readonly [ReadonlyArray<T>, ReadonlyArray<T>] {
|
|
585
696
|
const trueArray: Array<T> = [];
|
|
586
697
|
const falseArray: Array<T> = [];
|
|
587
698
|
|
|
588
699
|
for (let i = 0; i < array.length; i++) {
|
|
589
|
-
if (predicate(array[i], i)) {
|
|
700
|
+
if (predicate(array[i], i, array)) {
|
|
590
701
|
trueArray.push(array[i]);
|
|
591
702
|
} else {
|
|
592
703
|
falseArray.push(array[i]);
|
|
@@ -601,10 +712,15 @@ export function partitionArray<T>(
|
|
|
601
712
|
*
|
|
602
713
|
* Wraps native `toSorted`. Preserves non-empty type.
|
|
603
714
|
*
|
|
604
|
-
* ###
|
|
715
|
+
* ### Sorting without mutation
|
|
605
716
|
*
|
|
606
717
|
* ```ts
|
|
607
|
-
*
|
|
718
|
+
* import { sortArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
719
|
+
*
|
|
720
|
+
* const values: NonEmptyReadonlyArray<number> = [3, 1, 2];
|
|
721
|
+
* const sorted = sortArray(values, (a, b) => a - b);
|
|
722
|
+
* expect(sorted).toEqual([1, 2, 3]);
|
|
723
|
+
* expectTypeOf(sorted).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
608
724
|
* ```
|
|
609
725
|
*
|
|
610
726
|
* @group Transformations
|
|
@@ -630,10 +746,15 @@ export function sortArray<T>(
|
|
|
630
746
|
*
|
|
631
747
|
* Wraps native `toReversed`. Preserves non-empty type.
|
|
632
748
|
*
|
|
633
|
-
* ###
|
|
749
|
+
* ### Reversing without mutation
|
|
634
750
|
*
|
|
635
751
|
* ```ts
|
|
636
|
-
* reverseArray
|
|
752
|
+
* import { reverseArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
753
|
+
*
|
|
754
|
+
* const values: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
755
|
+
* const reversed = reverseArray(values);
|
|
756
|
+
* expect(reversed).toEqual([3, 2, 1]);
|
|
757
|
+
* expectTypeOf(reversed).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
|
|
637
758
|
* ```
|
|
638
759
|
*
|
|
639
760
|
* @group Transformations
|
|
@@ -652,11 +773,15 @@ export function reverseArray<T>(array: ReadonlyArray<T>): ReadonlyArray<T> {
|
|
|
652
773
|
*
|
|
653
774
|
* Wraps native `toSpliced`.
|
|
654
775
|
*
|
|
655
|
-
* ###
|
|
776
|
+
* ### Removing and replacing values
|
|
656
777
|
*
|
|
657
778
|
* ```ts
|
|
658
|
-
*
|
|
659
|
-
*
|
|
779
|
+
* import { spliceArray } from "@evolu/common";
|
|
780
|
+
*
|
|
781
|
+
* const values: ReadonlyArray<number> = [1, 2, 3, 4];
|
|
782
|
+
* expect(spliceArray(values, 1, 2)).toEqual([1, 4]);
|
|
783
|
+
* expect(spliceArray([1, 2, 3], 1, 1, 10, 11)).toEqual([1, 10, 11, 3]);
|
|
784
|
+
* expect(values).toEqual([1, 2, 3, 4]);
|
|
660
785
|
* ```
|
|
661
786
|
*
|
|
662
787
|
* @group Transformations
|
|
@@ -685,21 +810,33 @@ export type ZipArrayResult<T extends ReadonlyArray<ReadonlyArray<unknown>>> = {
|
|
|
685
810
|
* {@link https://github.com/tc39/proposal-array-zip | TC39 Array.zip proposal}
|
|
686
811
|
* for the pattern this follows.
|
|
687
812
|
*
|
|
688
|
-
* ###
|
|
813
|
+
* ### Zipping to the shortest input
|
|
689
814
|
*
|
|
690
815
|
* ```ts
|
|
691
|
-
* zipArray
|
|
816
|
+
* import { zipArray, type NonEmptyReadonlyArray } from "@evolu/common";
|
|
817
|
+
*
|
|
818
|
+
* const pairs = zipArray([
|
|
692
819
|
* [1, 2, 3],
|
|
693
820
|
* ["a", "b", "c"],
|
|
694
821
|
* ]);
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
* [
|
|
699
|
-
* ["a", "b", "c"],
|
|
700
|
-
* [true, false],
|
|
822
|
+
* expect(pairs).toEqual([
|
|
823
|
+
* [1, "a"],
|
|
824
|
+
* [2, "b"],
|
|
825
|
+
* [3, "c"],
|
|
701
826
|
* ]);
|
|
702
|
-
*
|
|
827
|
+
* expect(
|
|
828
|
+
* zipArray([
|
|
829
|
+
* [1, 2],
|
|
830
|
+
* ["a", "b", "c"],
|
|
831
|
+
* [true, false],
|
|
832
|
+
* ]),
|
|
833
|
+
* ).toEqual([
|
|
834
|
+
* [1, "a", true],
|
|
835
|
+
* [2, "b", false],
|
|
836
|
+
* ]);
|
|
837
|
+
* expectTypeOf(pairs).toEqualTypeOf<
|
|
838
|
+
* NonEmptyReadonlyArray<Readonly<[number, string]>>
|
|
839
|
+
* >();
|
|
703
840
|
* ```
|
|
704
841
|
*
|
|
705
842
|
* @group Transformations
|
|
@@ -708,7 +845,7 @@ export function zipArray<
|
|
|
708
845
|
T extends NonEmptyReadonlyArray<NonEmptyReadonlyArray<unknown>>,
|
|
709
846
|
>(arrays: T): NonEmptyReadonlyArray<Readonly<ZipArrayResult<T>>>;
|
|
710
847
|
/** Possibly empty arrays. */
|
|
711
|
-
export function zipArray<T extends ReadonlyArray<ReadonlyArray<unknown>>>(
|
|
848
|
+
export function zipArray<const T extends ReadonlyArray<ReadonlyArray<unknown>>>(
|
|
712
849
|
arrays: T,
|
|
713
850
|
): ReadonlyArray<Readonly<ZipArrayResult<T>>>;
|
|
714
851
|
export function zipArray<T extends ReadonlyArray<ReadonlyArray<unknown>>>(
|
|
@@ -729,10 +866,12 @@ export function zipArray<T extends ReadonlyArray<ReadonlyArray<unknown>>>(
|
|
|
729
866
|
/**
|
|
730
867
|
* Returns the first element of a non-empty array.
|
|
731
868
|
*
|
|
732
|
-
* ###
|
|
869
|
+
* ### Reading the first value
|
|
733
870
|
*
|
|
734
871
|
* ```ts
|
|
735
|
-
* firstInArray
|
|
872
|
+
* import { firstInArray } from "@evolu/common";
|
|
873
|
+
*
|
|
874
|
+
* expect(firstInArray(["a", "b", "c"])).toBe("a");
|
|
736
875
|
* ```
|
|
737
876
|
*
|
|
738
877
|
* @group Accessors
|
|
@@ -742,53 +881,15 @@ export const firstInArray = <T>(array: NonEmptyReadonlyArray<T>): T => array[0];
|
|
|
742
881
|
/**
|
|
743
882
|
* Returns the last element of a non-empty array.
|
|
744
883
|
*
|
|
745
|
-
* ###
|
|
884
|
+
* ### Reading the last value
|
|
746
885
|
*
|
|
747
886
|
* ```ts
|
|
748
|
-
* lastInArray
|
|
887
|
+
* import { lastInArray } from "@evolu/common";
|
|
888
|
+
*
|
|
889
|
+
* expect(lastInArray(["a", "b", "c"])).toBe("c");
|
|
749
890
|
* ```
|
|
750
891
|
*
|
|
751
892
|
* @group Accessors
|
|
752
893
|
*/
|
|
753
894
|
export const lastInArray = <T>(array: NonEmptyReadonlyArray<T>): T =>
|
|
754
895
|
array[array.length - 1];
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Shifts (removes and returns) the first element from a non-empty mutable
|
|
758
|
-
* array.
|
|
759
|
-
*
|
|
760
|
-
* **Mutates** the original array.
|
|
761
|
-
*
|
|
762
|
-
* ### Example
|
|
763
|
-
*
|
|
764
|
-
* ```ts
|
|
765
|
-
* // Process a queue of callbacks
|
|
766
|
-
* const waitingQueue: Array<() => void> = [callback1, callback2];
|
|
767
|
-
* if (isNonEmptyArray(waitingQueue)) {
|
|
768
|
-
* shiftFromArray(waitingQueue)(); // Remove and immediately invoke
|
|
769
|
-
* }
|
|
770
|
-
* ```
|
|
771
|
-
*
|
|
772
|
-
* @group Mutations
|
|
773
|
-
*/
|
|
774
|
-
export const shiftFromArray = <T>(array: NonEmptyArray<T>): T =>
|
|
775
|
-
array.shift() as T;
|
|
776
|
-
|
|
777
|
-
/**
|
|
778
|
-
* Pops (removes and returns) the last element from a non-empty mutable array.
|
|
779
|
-
*
|
|
780
|
-
* **Mutates** the original array.
|
|
781
|
-
*
|
|
782
|
-
* ### Example
|
|
783
|
-
*
|
|
784
|
-
* ```ts
|
|
785
|
-
* // Process a stack of callbacks (LIFO)
|
|
786
|
-
* const callbackStack: Array<() => void> = [callback1, callback2];
|
|
787
|
-
* if (isNonEmptyArray(callbackStack)) {
|
|
788
|
-
* popFromArray(callbackStack)(); // Remove and immediately invoke
|
|
789
|
-
* }
|
|
790
|
-
* ```
|
|
791
|
-
*
|
|
792
|
-
* @group Mutations
|
|
793
|
-
*/
|
|
794
|
-
export const popFromArray = <T>(array: NonEmptyArray<T>): T => array.pop() as T;
|