vestjs-runtime 1.7.0 → 2.0.1
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/IsolateSerializer/package.json +12 -8
- package/README.md +3 -1
- package/dist/IsolateKeys-B21aPuBk.mjs +23 -0
- package/dist/IsolateKeys-B21aPuBk.mjs.map +1 -0
- package/dist/IsolateKeys-CCvALpZC.cjs +35 -0
- package/dist/IsolateKeys-CCvALpZC.cjs.map +1 -0
- package/dist/IsolateSerializer-B1hE3gmT.mjs +1004 -0
- package/dist/IsolateSerializer-B1hE3gmT.mjs.map +1 -0
- package/dist/IsolateSerializer-pbEf5gB2.cjs +1121 -0
- package/dist/IsolateSerializer-pbEf5gB2.cjs.map +1 -0
- package/dist/chunk-CLMFDpHK.mjs +18 -0
- package/dist/exports/IsolateSerializer.cjs +4 -0
- package/dist/exports/IsolateSerializer.mjs +4 -0
- package/dist/exports/test-utils.cjs +21 -0
- package/dist/exports/test-utils.cjs.map +1 -0
- package/dist/exports/test-utils.mjs +21 -0
- package/dist/exports/test-utils.mjs.map +1 -0
- package/dist/vestjs-runtime.cjs +153 -0
- package/dist/vestjs-runtime.cjs.map +1 -0
- package/dist/vestjs-runtime.mjs +117 -0
- package/dist/vestjs-runtime.mjs.map +1 -0
- package/docs/IsolateRegistry.docs.md +146 -0
- package/docs/Isolates.md +97 -0
- package/package.json +43 -88
- package/src/Bus.ts +46 -0
- package/src/Isolate/Isolate.ts +163 -0
- package/src/Isolate/IsolateFocused.ts +93 -0
- package/src/Isolate/IsolateIndexer.ts +42 -0
- package/src/Isolate/IsolateInspector.ts +93 -0
- package/src/Isolate/IsolateKeys.ts +18 -0
- package/src/Isolate/IsolateMutator.ts +165 -0
- package/src/Isolate/IsolateRegistry.ts +176 -0
- package/src/Isolate/IsolateReorderable.ts +11 -0
- package/src/Isolate/IsolateSelectors.ts +25 -0
- package/src/Isolate/IsolateStateMachine.ts +30 -0
- package/src/Isolate/IsolateStatus.ts +8 -0
- package/src/Isolate/IsolateTransient.ts +27 -0
- package/src/Isolate/IsolateTypes.ts +33 -0
- package/src/Isolate/__tests__/Isolate.test.ts +123 -0
- package/src/Isolate/__tests__/IsolateFocused.test.ts +199 -0
- package/src/Isolate/__tests__/IsolateInspector.test.ts +136 -0
- package/src/Isolate/__tests__/IsolateMutator.test.ts +164 -0
- package/src/Isolate/__tests__/IsolatePropagation.test.ts +170 -0
- package/src/Isolate/__tests__/IsolateReorderable.test.ts +111 -0
- package/src/Isolate/__tests__/IsolateSelectors.test.ts +72 -0
- package/src/Isolate/__tests__/IsolateStatus.test.ts +44 -0
- package/src/Isolate/__tests__/IsolateTransient.test.ts +58 -0
- package/src/Isolate/__tests__/__snapshots__/asyncIsolate.test.ts.snap +71 -0
- package/src/Isolate/__tests__/asyncIsolate.test.ts +85 -0
- package/src/IsolateWalker.ts +359 -0
- package/src/Orchestrator/RuntimeStates.ts +4 -0
- package/src/Reconciler.ts +178 -0
- package/src/RuntimeEvents.ts +9 -0
- package/src/VestRuntime.ts +421 -0
- package/src/__tests__/Bus.test.ts +57 -0
- package/src/__tests__/IsolateWalker.iterative.test.ts +77 -0
- package/src/__tests__/IsolateWalker.test.ts +418 -0
- package/src/__tests__/Reconciler.test.ts +193 -0
- package/src/__tests__/Reconciler.transient.test.ts +166 -0
- package/src/__tests__/VestRuntime.test.ts +212 -0
- package/src/__tests__/VestRuntimeStateMachine.test.ts +36 -0
- package/src/__tests__/vestjs-runtime.test.ts +19 -0
- package/src/errors/ErrorStrings.ts +6 -0
- package/src/exports/IsolateSerializer.ts +131 -0
- package/src/exports/__tests__/IsolateSerializer.test.ts +334 -0
- package/src/exports/__tests__/IsolateSerializer.transient.test.ts +101 -0
- package/src/exports/__tests__/__snapshots__/IsolateSerializer.test.ts.snap +5 -0
- package/src/exports/test-utils.ts +17 -0
- package/src/vestjs-runtime.ts +28 -0
- package/test-utils/package.json +12 -8
- package/types/Isolate-DChR7h5K.d.mts +58 -0
- package/types/Isolate-DChR7h5K.d.mts.map +1 -0
- package/types/Isolate-HYIh82M8.d.cts +58 -0
- package/types/Isolate-HYIh82M8.d.cts.map +1 -0
- package/types/IsolateSerializer-BCg01Px5.d.mts +13 -0
- package/types/IsolateSerializer-BCg01Px5.d.mts.map +1 -0
- package/types/IsolateSerializer-CQpP6A4m.d.cts +13 -0
- package/types/IsolateSerializer-CQpP6A4m.d.cts.map +1 -0
- package/types/exports/IsolateSerializer.d.cts +3 -0
- package/types/exports/IsolateSerializer.d.mts +3 -0
- package/types/exports/test-utils.d.cts +7 -0
- package/types/exports/test-utils.d.cts.map +1 -0
- package/types/exports/test-utils.d.mts +7 -0
- package/types/exports/test-utils.d.mts.map +1 -0
- package/types/vestjs-runtime.d.cts +372 -0
- package/types/vestjs-runtime.d.cts.map +1 -0
- package/types/vestjs-runtime.d.mts +370 -0
- package/types/vestjs-runtime.d.mts.map +1 -0
- package/types/vestjs-runtime.d.ts +351 -257
- package/vitest.config.ts +9 -17
- package/dist/cjs/IsolateSerializer.development.js +0 -135
- package/dist/cjs/IsolateSerializer.development.js.map +0 -1
- package/dist/cjs/IsolateSerializer.js +0 -6
- package/dist/cjs/IsolateSerializer.production.js +0 -2
- package/dist/cjs/IsolateSerializer.production.js.map +0 -1
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/test-utils.development.js +0 -61
- package/dist/cjs/test-utils.development.js.map +0 -1
- package/dist/cjs/test-utils.js +0 -6
- package/dist/cjs/test-utils.production.js +0 -2
- package/dist/cjs/test-utils.production.js.map +0 -1
- package/dist/cjs/vestjs-runtime.development.js +0 -686
- package/dist/cjs/vestjs-runtime.development.js.map +0 -1
- package/dist/cjs/vestjs-runtime.js +0 -6
- package/dist/cjs/vestjs-runtime.production.js +0 -2
- package/dist/cjs/vestjs-runtime.production.js.map +0 -1
- package/dist/es/IsolateSerializer.development.js +0 -133
- package/dist/es/IsolateSerializer.development.js.map +0 -1
- package/dist/es/IsolateSerializer.production.js +0 -2
- package/dist/es/IsolateSerializer.production.js.map +0 -1
- package/dist/es/package.json +0 -1
- package/dist/es/test-utils.development.js +0 -59
- package/dist/es/test-utils.development.js.map +0 -1
- package/dist/es/test-utils.production.js +0 -2
- package/dist/es/test-utils.production.js.map +0 -1
- package/dist/es/vestjs-runtime.development.js +0 -675
- package/dist/es/vestjs-runtime.development.js.map +0 -1
- package/dist/es/vestjs-runtime.production.js +0 -2
- package/dist/es/vestjs-runtime.production.js.map +0 -1
- package/dist/umd/IsolateSerializer.development.js +0 -138
- package/dist/umd/IsolateSerializer.development.js.map +0 -1
- package/dist/umd/IsolateSerializer.production.js +0 -2
- package/dist/umd/IsolateSerializer.production.js.map +0 -1
- package/dist/umd/test-utils.development.js +0 -67
- package/dist/umd/test-utils.development.js.map +0 -1
- package/dist/umd/test-utils.production.js +0 -2
- package/dist/umd/test-utils.production.js.map +0 -1
- package/dist/umd/vestjs-runtime.development.js +0 -688
- package/dist/umd/vestjs-runtime.development.js.map +0 -1
- package/dist/umd/vestjs-runtime.production.js +0 -2
- package/dist/umd/vestjs-runtime.production.js.map +0 -1
- package/types/IsolateSerializer.d.ts +0 -42
- package/types/IsolateSerializer.d.ts.map +0 -1
- package/types/test-utils.d.ts +0 -37
- package/types/test-utils.d.ts.map +0 -1
- package/types/vestjs-runtime.d.ts.map +0 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Nullable,
|
|
3
|
+
isNullish,
|
|
4
|
+
dynamicValue,
|
|
5
|
+
Result,
|
|
6
|
+
makeResult,
|
|
7
|
+
isFailure,
|
|
8
|
+
} from 'vest-utils';
|
|
9
|
+
|
|
10
|
+
import { IsolateInspector } from './Isolate/IsolateInspector';
|
|
11
|
+
import { type TIsolate } from './Isolate/Isolate';
|
|
12
|
+
import { IsolateMutator } from './Isolate/IsolateMutator';
|
|
13
|
+
|
|
14
|
+
type VisitOnlyPredicate = (isolate: TIsolate) => boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Walks the isolate tree starting from the given node.
|
|
18
|
+
* @param startNode - The starting node for the traversal.
|
|
19
|
+
* @param callback - The callback function to be called for each visited node.
|
|
20
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
21
|
+
*/
|
|
22
|
+
export function walk(
|
|
23
|
+
startNode: TIsolate,
|
|
24
|
+
callback: (isolate: TIsolate) => Result<void>,
|
|
25
|
+
visitOnly?: VisitOnlyPredicate,
|
|
26
|
+
): Result<void> {
|
|
27
|
+
if (!startNode) return makeResult.Ok(undefined);
|
|
28
|
+
|
|
29
|
+
const stack = [startNode];
|
|
30
|
+
|
|
31
|
+
while (stack.length > 0) {
|
|
32
|
+
const node = stack.pop()!;
|
|
33
|
+
|
|
34
|
+
const res = visit(node, visitOnly, callback);
|
|
35
|
+
|
|
36
|
+
if (isFailure(res)) {
|
|
37
|
+
return res;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (node.children) {
|
|
41
|
+
pushChildren(stack, node.children);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return makeResult.Ok(undefined);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function visit(
|
|
49
|
+
node: TIsolate,
|
|
50
|
+
visitOnly: VisitOnlyPredicate | undefined,
|
|
51
|
+
callback: (isolate: TIsolate) => Result<void>,
|
|
52
|
+
): Result<void> {
|
|
53
|
+
if (shouldVisit(node, visitOnly)) {
|
|
54
|
+
return callback(node);
|
|
55
|
+
}
|
|
56
|
+
return makeResult.Ok(undefined);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function pushChildren(stack: TIsolate[], children: TIsolate[]): void {
|
|
60
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
61
|
+
stack.push(children[i]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function shouldVisit(node: TIsolate, visitOnly?: VisitOnlyPredicate): boolean {
|
|
66
|
+
return isNullish(visitOnly) || dynamicValue(visitOnly, node);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reduces the isolate tree to a single value.
|
|
71
|
+
* @param startNode - The starting node for the traversal.
|
|
72
|
+
* @param callback - The reducer function.
|
|
73
|
+
* @param initialValue - The initial value for the accumulator.
|
|
74
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
75
|
+
* @returns The final accumulated value.
|
|
76
|
+
*/
|
|
77
|
+
export function reduce<T>(
|
|
78
|
+
startNode: TIsolate,
|
|
79
|
+
callback: (acc: T, isolate: TIsolate) => Result<T>,
|
|
80
|
+
initialValue: T,
|
|
81
|
+
visitOnly?: VisitOnlyPredicate,
|
|
82
|
+
): T {
|
|
83
|
+
let acc = initialValue;
|
|
84
|
+
|
|
85
|
+
walk(
|
|
86
|
+
startNode,
|
|
87
|
+
node => {
|
|
88
|
+
const res = callback(acc, node);
|
|
89
|
+
|
|
90
|
+
if (isFailure(res)) {
|
|
91
|
+
return makeResult.Err(res.error);
|
|
92
|
+
}
|
|
93
|
+
acc = res.unwrap();
|
|
94
|
+
return makeResult.Ok(undefined);
|
|
95
|
+
},
|
|
96
|
+
visitOnly,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return acc;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Checks if any node in the tree satisfies the predicate.
|
|
104
|
+
* @param startNode - The starting node for the traversal.
|
|
105
|
+
* @param predicate - The predicate function to test each node.
|
|
106
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
107
|
+
* @returns True if any node satisfies the predicate, false otherwise.
|
|
108
|
+
*/
|
|
109
|
+
export function some(
|
|
110
|
+
startNode: TIsolate,
|
|
111
|
+
predicate: (node: TIsolate) => boolean,
|
|
112
|
+
visitOnly?: VisitOnlyPredicate,
|
|
113
|
+
): boolean {
|
|
114
|
+
let hasMatch = false;
|
|
115
|
+
|
|
116
|
+
// Call the walk function with a callback function that sets hasMatch to true if the predicate is satisfied.
|
|
117
|
+
walk(
|
|
118
|
+
startNode,
|
|
119
|
+
node => {
|
|
120
|
+
if (predicate(node)) {
|
|
121
|
+
hasMatch = true;
|
|
122
|
+
return makeResult.Err(undefined);
|
|
123
|
+
}
|
|
124
|
+
return makeResult.Ok(undefined);
|
|
125
|
+
},
|
|
126
|
+
visitOnly,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return hasMatch;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Checks if the tree contains a node that matches the predicate.
|
|
134
|
+
* @param startNode - The starting node for the traversal.
|
|
135
|
+
* @param match - The predicate function to match nodes.
|
|
136
|
+
* @returns True if a matching node is found, false otherwise.
|
|
137
|
+
*/
|
|
138
|
+
export function has(startNode: TIsolate, match: VisitOnlyPredicate): boolean {
|
|
139
|
+
return some(startNode, () => true, match);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Traverses up the tree to find the closest ancestor that satisfies the predicate,
|
|
144
|
+
* then returns the first direct descendant of that ancestor that satisfies the predicate.
|
|
145
|
+
* @param startNode - The starting node.
|
|
146
|
+
* @param predicate - The predicate to match.
|
|
147
|
+
* @returns The found node or null.
|
|
148
|
+
*/
|
|
149
|
+
export function findClosest<I extends TIsolate = TIsolate>(
|
|
150
|
+
startNode: TIsolate,
|
|
151
|
+
predicate: (node: TIsolate) => boolean,
|
|
152
|
+
): Nullable<I> {
|
|
153
|
+
let found: Nullable<TIsolate> = null;
|
|
154
|
+
let current: Nullable<TIsolate> = startNode;
|
|
155
|
+
|
|
156
|
+
while (current) {
|
|
157
|
+
found = current.children?.find(predicate) ?? null;
|
|
158
|
+
|
|
159
|
+
if (found) {
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
current = current.parent;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return found as Nullable<I>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Finds the first node in the tree that satisfies the predicate.
|
|
171
|
+
* @param startNode - The starting node.
|
|
172
|
+
* @param predicate - The predicate to match.
|
|
173
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
174
|
+
* @returns The found node or null.
|
|
175
|
+
*/
|
|
176
|
+
export function find(
|
|
177
|
+
startNode: TIsolate,
|
|
178
|
+
predicate: (node: TIsolate) => boolean,
|
|
179
|
+
visitOnly?: VisitOnlyPredicate,
|
|
180
|
+
): Nullable<TIsolate> {
|
|
181
|
+
let found = null;
|
|
182
|
+
|
|
183
|
+
walk(
|
|
184
|
+
startNode,
|
|
185
|
+
node => {
|
|
186
|
+
if (predicate(node)) {
|
|
187
|
+
found = node;
|
|
188
|
+
return makeResult.Err(undefined);
|
|
189
|
+
}
|
|
190
|
+
return makeResult.Ok(undefined);
|
|
191
|
+
},
|
|
192
|
+
visitOnly,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return found;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Finds all nodes in the tree that satisfy the predicate.
|
|
200
|
+
* @param startNode - The starting node.
|
|
201
|
+
* @param predicate - The predicate to match.
|
|
202
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
203
|
+
* @returns An array of found nodes.
|
|
204
|
+
*/
|
|
205
|
+
export function findAll(
|
|
206
|
+
startNode: TIsolate,
|
|
207
|
+
predicate: (node: TIsolate) => boolean,
|
|
208
|
+
visitOnly?: VisitOnlyPredicate,
|
|
209
|
+
): TIsolate[] {
|
|
210
|
+
const found: TIsolate[] = [];
|
|
211
|
+
|
|
212
|
+
walk(
|
|
213
|
+
startNode,
|
|
214
|
+
node => {
|
|
215
|
+
if (predicate(node)) {
|
|
216
|
+
found.push(node);
|
|
217
|
+
}
|
|
218
|
+
return makeResult.Ok(undefined);
|
|
219
|
+
},
|
|
220
|
+
visitOnly,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return found;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Checks if every node in the tree satisfies the predicate.
|
|
228
|
+
* @param startNode - The starting node.
|
|
229
|
+
* @param predicate - The predicate to match.
|
|
230
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
231
|
+
* @returns True if all nodes satisfy the predicate, false otherwise.
|
|
232
|
+
*/
|
|
233
|
+
export function every(
|
|
234
|
+
startNode: TIsolate,
|
|
235
|
+
predicate: (node: TIsolate) => boolean,
|
|
236
|
+
visitOnly?: VisitOnlyPredicate,
|
|
237
|
+
): boolean {
|
|
238
|
+
let hasMatch = true;
|
|
239
|
+
walk(
|
|
240
|
+
startNode,
|
|
241
|
+
node => {
|
|
242
|
+
if (!predicate(node)) {
|
|
243
|
+
hasMatch = false;
|
|
244
|
+
return makeResult.Err(undefined);
|
|
245
|
+
}
|
|
246
|
+
return makeResult.Ok(undefined);
|
|
247
|
+
},
|
|
248
|
+
visitOnly,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return hasMatch;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Removes nodes from the tree that satisfy the predicate.
|
|
256
|
+
* @param startNode - The starting node.
|
|
257
|
+
* @param predicate - The predicate to match nodes to remove.
|
|
258
|
+
* @param visitOnly - Optional predicate to filter which nodes to visit.
|
|
259
|
+
*/
|
|
260
|
+
export function pluck(
|
|
261
|
+
startNode: TIsolate,
|
|
262
|
+
predicate: (node: TIsolate) => boolean,
|
|
263
|
+
visitOnly?: VisitOnlyPredicate,
|
|
264
|
+
): void {
|
|
265
|
+
walk(
|
|
266
|
+
startNode,
|
|
267
|
+
node => {
|
|
268
|
+
if (predicate(node) && node.parent) {
|
|
269
|
+
IsolateMutator.removeChild(node.parent, node);
|
|
270
|
+
}
|
|
271
|
+
return makeResult.Ok(undefined);
|
|
272
|
+
},
|
|
273
|
+
visitOnly,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Finds the closest ancestor of the startNode that satisfies the predicate.
|
|
279
|
+
* @param startNode - The starting node.
|
|
280
|
+
* @param predicate - The predicate to match.
|
|
281
|
+
* @returns The found ancestor or null.
|
|
282
|
+
*/
|
|
283
|
+
export function closest<I extends TIsolate = TIsolate>(
|
|
284
|
+
startNode: TIsolate,
|
|
285
|
+
predicate: (node: TIsolate) => boolean,
|
|
286
|
+
): Nullable<I> {
|
|
287
|
+
let current: Nullable<TIsolate> = startNode;
|
|
288
|
+
do {
|
|
289
|
+
if (predicate(current)) {
|
|
290
|
+
return current as I;
|
|
291
|
+
}
|
|
292
|
+
current = current.parent;
|
|
293
|
+
} while (current);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Checks if an ancestor satisfying the predicate exists.
|
|
299
|
+
* @param startNode - The starting node.
|
|
300
|
+
* @param predicate - The predicate to match.
|
|
301
|
+
* @returns True if such an ancestor exists, false otherwise.
|
|
302
|
+
*/
|
|
303
|
+
export function closestExists(
|
|
304
|
+
startNode: TIsolate,
|
|
305
|
+
predicate: (node: TIsolate) => boolean,
|
|
306
|
+
): boolean {
|
|
307
|
+
return !!closest(startNode, predicate);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Traverses the tree and returns the first non-nullish value returned by the callback.
|
|
312
|
+
* It optimizes traversal by only visiting nodes that have pending isolates or are pending themselves.
|
|
313
|
+
*/
|
|
314
|
+
export function mapFirst<T>(
|
|
315
|
+
startNode: TIsolate,
|
|
316
|
+
callback: (isolate: TIsolate, breakout: (value: T) => void) => void,
|
|
317
|
+
): T | null {
|
|
318
|
+
const stack = [startNode];
|
|
319
|
+
let result: { value: T } | null = null;
|
|
320
|
+
const breakout = (value: T) => (result = { value });
|
|
321
|
+
|
|
322
|
+
while (stack.length > 0) {
|
|
323
|
+
if (result) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const node = stack.pop()!;
|
|
328
|
+
processNode(node, stack, callback, breakout);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return (result as { value: T } | null)?.value ?? null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function processNode<T>(
|
|
335
|
+
node: TIsolate,
|
|
336
|
+
stack: TIsolate[],
|
|
337
|
+
callback: (isolate: TIsolate, breakout: (value: T) => void) => void,
|
|
338
|
+
breakout: (value: T) => void,
|
|
339
|
+
): void {
|
|
340
|
+
if (!IsolateInspector.hasPending(node)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
runCallback(node, callback, breakout);
|
|
345
|
+
|
|
346
|
+
if (node.children) {
|
|
347
|
+
pushChildren(stack, node.children);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function runCallback<T>(
|
|
352
|
+
node: TIsolate,
|
|
353
|
+
callback: (isolate: TIsolate, breakout: (value: T) => void) => void,
|
|
354
|
+
breakout: (value: T) => void,
|
|
355
|
+
): void {
|
|
356
|
+
if (IsolateInspector.isPending(node)) {
|
|
357
|
+
callback(node, breakout);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Maybe,
|
|
3
|
+
Nullable,
|
|
4
|
+
invariant,
|
|
5
|
+
isNullish,
|
|
6
|
+
dynamicValue,
|
|
7
|
+
} from 'vest-utils';
|
|
8
|
+
|
|
9
|
+
import { type TIsolate } from './Isolate/Isolate';
|
|
10
|
+
import { createHistoryIndex } from './Isolate/IsolateIndexer';
|
|
11
|
+
import { IsolateInspector } from './Isolate/IsolateInspector';
|
|
12
|
+
import { IsolateMutator } from './Isolate/IsolateMutator';
|
|
13
|
+
import { isSameIsolateType } from './Isolate/IsolateSelectors';
|
|
14
|
+
import * as VestRuntime from './VestRuntime';
|
|
15
|
+
import { ErrorStrings } from './errors/ErrorStrings';
|
|
16
|
+
// import { isSameIsolateType } from 'IsolateSelectors';
|
|
17
|
+
|
|
18
|
+
// I would rather not use `any` here, but instead use `Isolate`.
|
|
19
|
+
// The problem is that it breaks the actual implementation of `Isolate` in `IsolateTest`
|
|
20
|
+
// As it is not properly extending `Isolate`.
|
|
21
|
+
export interface IReconciler<I = any> {
|
|
22
|
+
(currentNode: I, historyNode: I): Nullable<I>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function BaseReconciler(
|
|
26
|
+
currentNode: TIsolate,
|
|
27
|
+
historyNode: TIsolate,
|
|
28
|
+
): TIsolate {
|
|
29
|
+
if (isNullish(historyNode)) {
|
|
30
|
+
return currentNode;
|
|
31
|
+
}
|
|
32
|
+
return currentNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Reconciler {
|
|
36
|
+
/**
|
|
37
|
+
* Reconciles the current isolate with the history isolate.
|
|
38
|
+
* If the current isolate is of a different type than the history isolate,
|
|
39
|
+
* the current isolate is returned.
|
|
40
|
+
* Otherwise, the reconciler function is called to determine the next isolate.
|
|
41
|
+
* If the reconciler function returns null or undefined, the base reconciler is used.
|
|
42
|
+
* If no history isolate exists, the current isolate is returned.
|
|
43
|
+
* @param node The current isolate to reconcile.
|
|
44
|
+
* @returns The next isolate after reconciliation.
|
|
45
|
+
*/
|
|
46
|
+
static reconcile(node: TIsolate): TIsolate {
|
|
47
|
+
// If the node is transient, it should not be reconciled with history
|
|
48
|
+
// and should not consume the history cursor.
|
|
49
|
+
// This allows us to insert/remove transient nodes without affecting
|
|
50
|
+
// the stable identity of their siblings.
|
|
51
|
+
if (node.transient) {
|
|
52
|
+
return node;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const localHistoryNode = VestRuntime.useHistoryIsolateAtCurrentPosition();
|
|
56
|
+
|
|
57
|
+
const nextNodeResult = pickNextNode(node, localHistoryNode);
|
|
58
|
+
|
|
59
|
+
invariant(nextNodeResult, ErrorStrings.UNABLE_TO_PICK_NEXT_ISOLATE);
|
|
60
|
+
|
|
61
|
+
return nextNodeResult;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static dropNextNodesOnReorder<I extends TIsolate>(
|
|
65
|
+
reorderLogic: (newNode: I, prevNode: Maybe<TIsolate>) => boolean,
|
|
66
|
+
newNode: I,
|
|
67
|
+
prevNode: Maybe<TIsolate>,
|
|
68
|
+
): boolean {
|
|
69
|
+
const didReorder = reorderLogic(newNode, prevNode);
|
|
70
|
+
|
|
71
|
+
if (didReorder) {
|
|
72
|
+
removeAllNextNodesInIsolate();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return didReorder;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static handleIsolateNodeWithKey<I extends TIsolate>(
|
|
79
|
+
node: TIsolate,
|
|
80
|
+
|
|
81
|
+
// The revoke function allows the caller to revoke the previous node
|
|
82
|
+
revoke: ((node: I) => boolean) | false,
|
|
83
|
+
): TIsolate {
|
|
84
|
+
invariant(IsolateInspector.usesKey(node));
|
|
85
|
+
|
|
86
|
+
const prevNodeByKey = getNodeByKey(node.key);
|
|
87
|
+
let nextNode = node;
|
|
88
|
+
|
|
89
|
+
if (!isNullish(prevNodeByKey) && !dynamicValue(revoke, prevNodeByKey)) {
|
|
90
|
+
nextNode = prevNodeByKey;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
VestRuntime.useSetIsolateKey(node.key, nextNode);
|
|
94
|
+
|
|
95
|
+
return nextNode;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function pickNextNode(
|
|
100
|
+
currentNode: TIsolate,
|
|
101
|
+
historyNode: Nullable<TIsolate>,
|
|
102
|
+
): TIsolate {
|
|
103
|
+
if (isNullish(historyNode)) {
|
|
104
|
+
return handleNoHistoryNode(currentNode);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!isSameIsolateType(currentNode, historyNode)) {
|
|
108
|
+
return currentNode;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const reconciler = VestRuntime.useReconciler();
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
reconciler(currentNode, historyNode) ??
|
|
115
|
+
BaseReconciler(currentNode, historyNode)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleNoHistoryNode<I extends TIsolate>(newNode: I): I {
|
|
120
|
+
if (IsolateInspector.usesKey(newNode)) {
|
|
121
|
+
return Reconciler.handleIsolateNodeWithKey(newNode, false) as I;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return newNode;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function removeAllNextNodesInIsolate() {
|
|
128
|
+
const currentNode = VestRuntime.useIsolate();
|
|
129
|
+
const historyNode = VestRuntime.useHistoryIsolate();
|
|
130
|
+
|
|
131
|
+
if (!historyNode || !currentNode) {
|
|
132
|
+
// This is probably unreachable, but TS is not convinced.
|
|
133
|
+
// Let's play it safe.
|
|
134
|
+
/* istanbul ignore next */
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
IsolateMutator.slice(historyNode, IsolateInspector.cursor(currentNode));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const historyIndexCache = new WeakMap<
|
|
142
|
+
TIsolate,
|
|
143
|
+
Map<string, TIsolate | TIsolate[]>
|
|
144
|
+
>();
|
|
145
|
+
|
|
146
|
+
function getNodeByKey(key: Nullable<string>): Nullable<TIsolate> {
|
|
147
|
+
const historyParent = VestRuntime.useHistoryIsolate();
|
|
148
|
+
|
|
149
|
+
if (isNullish(key) || !historyParent) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const index = getHistoryIndex(historyParent);
|
|
154
|
+
const match = index.get(key);
|
|
155
|
+
|
|
156
|
+
if (!match) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (Array.isArray(match)) {
|
|
161
|
+
return match[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return match;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getHistoryIndex(
|
|
168
|
+
historyParent: TIsolate,
|
|
169
|
+
): Map<string, TIsolate | TIsolate[]> {
|
|
170
|
+
let index = historyIndexCache.get(historyParent);
|
|
171
|
+
|
|
172
|
+
if (!index) {
|
|
173
|
+
index = createHistoryIndex(historyParent.children);
|
|
174
|
+
historyIndexCache.set(historyParent, index);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return index;
|
|
178
|
+
}
|