@esportsplus/reactivity 0.31.3 → 0.32.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/.claude/CHANGELOG.md +43 -0
- package/README.md +13 -6
- package/bench/cellx.bench.ts +61 -0
- package/bench/dynamic.bench.ts +103 -0
- package/bench/kairo.bench.ts +368 -0
- package/bench/lib/reactive-adapter.ts +51 -0
- package/bench/molbench.bench.ts +75 -0
- package/{tests/bench/system.ts → bench/system.bench.ts} +3 -3
- package/build/compiler/array.d.ts +1 -1
- package/build/compiler/constants.d.ts +7 -6
- package/build/compiler/constants.js +6 -7
- package/build/compiler/object.d.ts +1 -1
- package/build/compiler/object.js +1 -2
- package/build/compiler/primitives.d.ts +1 -1
- package/build/constants.d.ts +3 -1
- package/build/constants.js +3 -1
- package/build/reactive/array.d.ts +8 -5
- package/build/reactive/array.js +14 -14
- package/build/reactive/index.d.ts +2 -2
- package/build/reactive/index.js +1 -1
- package/build/reactive/object.d.ts +4 -4
- package/build/reactive/object.js +8 -23
- package/build/system.d.ts +15 -6
- package/build/system.js +415 -69
- package/build/types.d.ts +16 -4
- package/package.json +5 -2
- package/src/compiler/array.ts +1 -1
- package/src/compiler/constants.ts +8 -6
- package/src/compiler/index.ts +2 -1
- package/src/compiler/object.ts +2 -2
- package/src/compiler/plugins/vite.ts +1 -0
- package/src/compiler/primitives.ts +1 -1
- package/src/constants.ts +6 -2
- package/src/reactive/array.ts +26 -23
- package/src/reactive/index.ts +6 -6
- package/src/reactive/object.ts +16 -41
- package/src/system.ts +612 -80
- package/src/types.ts +29 -9
- package/test/async-computed.test.ts +323 -0
- package/test/async-errors.test.ts +146 -0
- package/test/async-hardening.test.ts +73 -0
- package/test/async-iterable.test.ts +221 -0
- package/test/async-nested.test.ts +19 -0
- package/test/deep-graphs.test.ts +215 -0
- package/{tests/effects.ts → test/effects.test.ts} +60 -0
- package/test/equals.test.ts +142 -0
- package/test/errors.test.ts +201 -0
- package/test/flush.test.ts +185 -0
- package/test/glitch-freedom.test.ts +115 -0
- package/test/invalidate.test.ts +147 -0
- package/test/lib/wait-for.ts +28 -0
- package/test/pending-writes.test.ts +139 -0
- package/{tests/reactive.ts → test/reactive/reactive.test.ts} +20 -19
- package/test/read-dedup.test.ts +120 -0
- package/test/signal-selector.test.ts +187 -0
- package/{tests/system.ts → test/system.test.ts} +214 -23
- package/test/tsconfig.json +16 -0
- package/test/untrack.test.ts +146 -0
- package/vitest.config.ts +2 -2
- package/tests/async-computed.ts +0 -239
- /package/{tests/bench/array.ts → bench/reactive/array.bench.ts} +0 -0
- /package/{tests/bench/reactive-object.ts → bench/reactive/reactive-object.bench.ts} +0 -0
- /package/{tests → bench}/tsconfig.json +0 -0
- /package/{tests/compiler.ts → test/compiler/compiler.test.ts} +0 -0
- /package/{tests/primitives.ts → test/primitives.test.ts} +0 -0
- /package/{tests/array.ts → test/reactive/array.test.ts} +0 -0
- /package/{tests/nested.ts → test/reactive/nested.test.ts} +0 -0
- /package/{tests/objects.ts → test/reactive/objects.test.ts} +0 -0
package/build/system.js
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
|
-
import { SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
|
|
2
|
-
import { isObject } from '@esportsplus/utilities';
|
|
3
|
-
let depth = 0, heap = new Array(64), heap_i = 0, heap_n = 0, linkPoolHead = null, microtask = queueMicrotask, notified = false, observer = null, scope = null, stabilizer = STABILIZER_IDLE, version = 0;
|
|
1
|
+
import { PACKAGE_NAME, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
|
|
2
|
+
import { isObject, isPromise } from '@esportsplus/utilities';
|
|
3
|
+
let asyncMeta = new WeakMap(), depth = 0, disposeHead = null, draining = false, heap = new Array(64), heap_i = 0, heap_n = 0, linkPoolHead = null, microtask = queueMicrotask, notified = false, observer = null, pendingHead = null, scope = null, stabilizer = STABILIZER_IDLE, version = 0, walkPoolHead = null, writes = 0;
|
|
4
|
+
function walkPop(walk) {
|
|
5
|
+
let prev = walk.prev;
|
|
6
|
+
walk.computed = null;
|
|
7
|
+
walk.link = null;
|
|
8
|
+
walk.prev = walkPoolHead;
|
|
9
|
+
walkPoolHead = walk;
|
|
10
|
+
return prev;
|
|
11
|
+
}
|
|
12
|
+
function walkPush(computed, link, prev) {
|
|
13
|
+
let walk = walkPoolHead;
|
|
14
|
+
if (walk !== null) {
|
|
15
|
+
walkPoolHead = walk.prev;
|
|
16
|
+
walk.computed = computed;
|
|
17
|
+
walk.link = link;
|
|
18
|
+
walk.prev = prev;
|
|
19
|
+
return walk;
|
|
20
|
+
}
|
|
21
|
+
return { computed, link, prev };
|
|
22
|
+
}
|
|
4
23
|
function cleanup(computed) {
|
|
5
24
|
if (!computed.cleanup) {
|
|
6
25
|
return;
|
|
7
26
|
}
|
|
8
|
-
let value = computed.cleanup;
|
|
27
|
+
let errors = [], value = computed.cleanup;
|
|
28
|
+
computed.cleanup = null;
|
|
9
29
|
if (typeof value === 'function') {
|
|
10
|
-
|
|
30
|
+
try {
|
|
31
|
+
value();
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
errors.push(e);
|
|
35
|
+
}
|
|
11
36
|
}
|
|
12
37
|
else {
|
|
13
38
|
for (let i = 0, n = value.length; i < n; i++) {
|
|
14
|
-
|
|
39
|
+
try {
|
|
40
|
+
value[i]();
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
errors.push(e);
|
|
44
|
+
}
|
|
15
45
|
}
|
|
16
46
|
}
|
|
17
|
-
|
|
47
|
+
if (errors.length) {
|
|
48
|
+
throw errors.length === 1 ? errors[0] : new AggregateError(errors, `${PACKAGE_NAME}: cleanup produced multiple errors`);
|
|
49
|
+
}
|
|
18
50
|
}
|
|
19
51
|
function deleteFromHeap(computed) {
|
|
20
52
|
let state = computed.state;
|
|
@@ -39,6 +71,18 @@ function deleteFromHeap(computed) {
|
|
|
39
71
|
computed.nextHeap = undefined;
|
|
40
72
|
computed.prevHeap = computed;
|
|
41
73
|
}
|
|
74
|
+
function drainPending() {
|
|
75
|
+
let node = pendingHead;
|
|
76
|
+
pendingHead = null;
|
|
77
|
+
while (node !== null) {
|
|
78
|
+
let next = node.nextPending === node ? null : node.nextPending;
|
|
79
|
+
node.nextPending = null;
|
|
80
|
+
for (let link = node.subs; link; link = link.nextSub) {
|
|
81
|
+
insertIntoHeap(link.sub);
|
|
82
|
+
}
|
|
83
|
+
node = next;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
42
86
|
function insertIntoHeap(computed) {
|
|
43
87
|
let state = computed.state;
|
|
44
88
|
if (state & STATE_IN_HEAP) {
|
|
@@ -63,14 +107,19 @@ function insertIntoHeap(computed) {
|
|
|
63
107
|
}
|
|
64
108
|
}
|
|
65
109
|
function link(dep, sub) {
|
|
110
|
+
if (dep.rv === version) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
66
113
|
let prevDep = sub.depsTail;
|
|
67
114
|
if (prevDep && prevDep.dep === dep) {
|
|
115
|
+
dep.rv = version;
|
|
68
116
|
return;
|
|
69
117
|
}
|
|
70
118
|
let nextDep = null;
|
|
71
119
|
if (sub.state & STATE_RECOMPUTING) {
|
|
72
120
|
nextDep = prevDep ? prevDep.nextDep : sub.deps;
|
|
73
121
|
if (nextDep && nextDep.dep === dep) {
|
|
122
|
+
dep.rv = version;
|
|
74
123
|
nextDep.version = version;
|
|
75
124
|
sub.depsTail = nextDep;
|
|
76
125
|
return;
|
|
@@ -80,8 +129,10 @@ function link(dep, sub) {
|
|
|
80
129
|
if (prevSub &&
|
|
81
130
|
prevSub.version === version &&
|
|
82
131
|
prevSub.sub === sub) {
|
|
132
|
+
dep.rv = version;
|
|
83
133
|
return;
|
|
84
134
|
}
|
|
135
|
+
dep.rv = version;
|
|
85
136
|
let pooled = linkPoolHead, newLink = sub.depsTail =
|
|
86
137
|
dep.subsTail = pooled
|
|
87
138
|
? (linkPoolHead = pooled.nextDep,
|
|
@@ -119,9 +170,52 @@ function notify(computed, newState) {
|
|
|
119
170
|
return;
|
|
120
171
|
}
|
|
121
172
|
computed.state = state | newState;
|
|
122
|
-
|
|
123
|
-
|
|
173
|
+
let link = computed.subs, stack = null;
|
|
174
|
+
for (;;) {
|
|
175
|
+
while (link !== null) {
|
|
176
|
+
let sub = link.sub, subState = sub.state;
|
|
177
|
+
if ((subState & STATE_NOTIFY_MASK) < STATE_CHECK) {
|
|
178
|
+
sub.state = subState | STATE_CHECK;
|
|
179
|
+
if (sub.subs !== null) {
|
|
180
|
+
if (link.nextSub !== null) {
|
|
181
|
+
stack = walkPush(null, link.nextSub, stack);
|
|
182
|
+
}
|
|
183
|
+
link = sub.subs;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
link = link.nextSub;
|
|
188
|
+
}
|
|
189
|
+
if (stack === null) {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
link = stack.link;
|
|
193
|
+
stack = walkPop(stack);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function pull(node) {
|
|
197
|
+
if (!notified) {
|
|
198
|
+
notified = true;
|
|
199
|
+
for (let i = 0; i <= heap_n; i++) {
|
|
200
|
+
for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
|
|
201
|
+
notify(computed, STATE_DIRTY);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
124
204
|
}
|
|
205
|
+
let o = observer;
|
|
206
|
+
observer = null;
|
|
207
|
+
update(node);
|
|
208
|
+
observer = o;
|
|
209
|
+
}
|
|
210
|
+
function propagate(computed) {
|
|
211
|
+
for (let c = computed.subs; c; c = c.nextSub) {
|
|
212
|
+
let s = c.sub, state = s.state;
|
|
213
|
+
if (state & STATE_CHECK) {
|
|
214
|
+
s.state = state | STATE_DIRTY;
|
|
215
|
+
}
|
|
216
|
+
insertIntoHeap(s);
|
|
217
|
+
}
|
|
218
|
+
schedule();
|
|
125
219
|
}
|
|
126
220
|
function recompute(computed, del) {
|
|
127
221
|
if (del) {
|
|
@@ -132,12 +226,19 @@ function recompute(computed, del) {
|
|
|
132
226
|
computed.prevHeap = computed;
|
|
133
227
|
}
|
|
134
228
|
if (computed.cleanup) {
|
|
135
|
-
|
|
229
|
+
try {
|
|
230
|
+
cleanup(computed);
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
microtask(() => {
|
|
234
|
+
throw e;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
136
237
|
}
|
|
137
|
-
let o = observer, ok = true, value;
|
|
238
|
+
let err, flags = computed.state & STATE_EFFECT, hadError = computed.state & STATE_ERROR, o = observer, ok = true, value, w = writes;
|
|
138
239
|
observer = computed;
|
|
139
240
|
computed.depsTail = null;
|
|
140
|
-
computed.state = STATE_COMPUTED | STATE_RECOMPUTING;
|
|
241
|
+
computed.state = STATE_COMPUTED | STATE_RECOMPUTING | flags;
|
|
141
242
|
depth++;
|
|
142
243
|
version++;
|
|
143
244
|
try {
|
|
@@ -145,10 +246,13 @@ function recompute(computed, del) {
|
|
|
145
246
|
}
|
|
146
247
|
catch (e) {
|
|
147
248
|
ok = false;
|
|
249
|
+
err = e;
|
|
148
250
|
}
|
|
149
251
|
depth--;
|
|
252
|
+
version++;
|
|
150
253
|
observer = o;
|
|
151
|
-
computed.state = STATE_COMPUTED;
|
|
254
|
+
computed.state = STATE_COMPUTED | flags;
|
|
255
|
+
computed.gv = w;
|
|
152
256
|
let depsTail = computed.depsTail, remove = depsTail ? depsTail.nextDep : computed.deps;
|
|
153
257
|
if (remove) {
|
|
154
258
|
do {
|
|
@@ -161,16 +265,27 @@ function recompute(computed, del) {
|
|
|
161
265
|
computed.deps = null;
|
|
162
266
|
}
|
|
163
267
|
}
|
|
164
|
-
if (ok
|
|
268
|
+
if (ok) {
|
|
269
|
+
computed.error = null;
|
|
270
|
+
let changed = computed.equals === null || computed.value === undefined
|
|
271
|
+
? value !== computed.value
|
|
272
|
+
: !computed.equals(computed.value, value);
|
|
165
273
|
computed.value = value;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
274
|
+
if (changed || hadError) {
|
|
275
|
+
propagate(computed);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
computed.error = err;
|
|
280
|
+
computed.state |= STATE_ERROR;
|
|
281
|
+
if (flags) {
|
|
282
|
+
microtask(() => {
|
|
283
|
+
throw err;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
propagate(computed);
|
|
172
288
|
}
|
|
173
|
-
schedule();
|
|
174
289
|
}
|
|
175
290
|
}
|
|
176
291
|
function schedule() {
|
|
@@ -190,6 +305,9 @@ function stabilize() {
|
|
|
190
305
|
observer = null;
|
|
191
306
|
stabilizer = STABILIZER_RUNNING;
|
|
192
307
|
for (heap_i = 0; heap_i <= heap_n; heap_i++) {
|
|
308
|
+
if (pendingHead !== null) {
|
|
309
|
+
drainPending();
|
|
310
|
+
}
|
|
193
311
|
let computed = heap[heap_i];
|
|
194
312
|
heap[heap_i] = undefined;
|
|
195
313
|
while (computed !== undefined) {
|
|
@@ -221,8 +339,17 @@ function unlink(link) {
|
|
|
221
339
|
if (prevSub) {
|
|
222
340
|
prevSub.nextSub = nextSub;
|
|
223
341
|
}
|
|
224
|
-
else if ((dep.subs = nextSub) === null
|
|
225
|
-
|
|
342
|
+
else if ((dep.subs = nextSub) === null) {
|
|
343
|
+
if (dep.state & STATE_COMPUTED) {
|
|
344
|
+
dispose(dep);
|
|
345
|
+
}
|
|
346
|
+
else if (dep.parent !== undefined) {
|
|
347
|
+
let parent = dep.parent;
|
|
348
|
+
parent.keys.delete(dep.key);
|
|
349
|
+
if (parent.keys.size === 0) {
|
|
350
|
+
parent.keys = null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
226
353
|
}
|
|
227
354
|
link.dep = link.sub = null;
|
|
228
355
|
link.nextSub = link.prevSub = null;
|
|
@@ -230,44 +357,123 @@ function unlink(link) {
|
|
|
230
357
|
linkPoolHead = link;
|
|
231
358
|
return nextDep;
|
|
232
359
|
}
|
|
233
|
-
function update(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
360
|
+
function update(root) {
|
|
361
|
+
let link = null, node = root, resuming = false, stack = null, w = writes;
|
|
362
|
+
for (;;) {
|
|
363
|
+
if (!resuming) {
|
|
364
|
+
if (node.gv === w) {
|
|
365
|
+
node.state &= ~STATE_NOTIFY_MASK;
|
|
366
|
+
}
|
|
367
|
+
else if (node.state & STATE_CHECK) {
|
|
368
|
+
link = node.deps;
|
|
369
|
+
resuming = true;
|
|
370
|
+
}
|
|
371
|
+
else if (node.state & STATE_DIRTY) {
|
|
372
|
+
recompute(node, true);
|
|
373
|
+
node.state &= ~STATE_NOTIFY_MASK;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
node.gv = w;
|
|
377
|
+
node.state &= ~STATE_NOTIFY_MASK;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (resuming) {
|
|
381
|
+
let descended = false;
|
|
382
|
+
while (link !== null && !(node.state & STATE_DIRTY)) {
|
|
383
|
+
if (link.dep.state & STATE_COMPUTED) {
|
|
384
|
+
stack = walkPush(node, link.nextDep, stack);
|
|
385
|
+
node = link.dep;
|
|
386
|
+
link = null;
|
|
387
|
+
resuming = false;
|
|
388
|
+
descended = true;
|
|
240
389
|
break;
|
|
241
390
|
}
|
|
391
|
+
link = link.nextDep;
|
|
392
|
+
}
|
|
393
|
+
if (descended) {
|
|
394
|
+
continue;
|
|
242
395
|
}
|
|
396
|
+
if (node.state & STATE_DIRTY) {
|
|
397
|
+
recompute(node, true);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
node.gv = w;
|
|
401
|
+
}
|
|
402
|
+
node.state &= ~STATE_NOTIFY_MASK;
|
|
403
|
+
resuming = false;
|
|
243
404
|
}
|
|
405
|
+
if (stack === null) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
node = stack.computed;
|
|
409
|
+
link = stack.link;
|
|
410
|
+
stack = walkPop(stack);
|
|
411
|
+
resuming = true;
|
|
244
412
|
}
|
|
245
|
-
if (computed.state & STATE_DIRTY) {
|
|
246
|
-
recompute(computed, true);
|
|
247
|
-
}
|
|
248
|
-
computed.state = STATE_COMPUTED;
|
|
249
413
|
}
|
|
250
|
-
|
|
251
|
-
let
|
|
252
|
-
|
|
253
|
-
let
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
write(node, value);
|
|
414
|
+
function makeAsyncComputed(factory) {
|
|
415
|
+
let error = signal(undefined), node = signal(undefined), v = 0;
|
|
416
|
+
let stop = effect(() => {
|
|
417
|
+
let fail = (e) => {
|
|
418
|
+
if (id === v && !(factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
|
|
419
|
+
write(error, e === undefined ? new Error('reactivity: async computed rejected with undefined') : e);
|
|
257
420
|
}
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
421
|
+
}, id = ++v, result = read(factory);
|
|
422
|
+
if (isPromise(result)) {
|
|
423
|
+
result.then((value) => {
|
|
424
|
+
if (id === v && !(factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
|
|
425
|
+
write(error, undefined);
|
|
426
|
+
write(node, value);
|
|
427
|
+
}
|
|
428
|
+
}, fail);
|
|
429
|
+
}
|
|
430
|
+
else if (result != null && typeof result[Symbol.asyncIterator] === 'function') {
|
|
431
|
+
let it = result[Symbol.asyncIterator]();
|
|
432
|
+
onCleanup(() => {
|
|
433
|
+
it.return?.();
|
|
434
|
+
});
|
|
435
|
+
let step = (r) => {
|
|
436
|
+
if (id !== v || (factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!r.done) {
|
|
440
|
+
write(error, undefined);
|
|
441
|
+
write(node, r.value);
|
|
442
|
+
it.next().then(step, fail);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
untrack(() => it.next()).then(step, fail);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
write(error, undefined);
|
|
449
|
+
write(node, result);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
let wrapper = makeComputed(() => {
|
|
453
|
+
let e = read(error);
|
|
454
|
+
if (e !== undefined) {
|
|
455
|
+
throw e;
|
|
456
|
+
}
|
|
457
|
+
return read(node);
|
|
458
|
+
});
|
|
459
|
+
asyncMeta.set(wrapper, { factory: factory });
|
|
460
|
+
wrapper.disposal = stop;
|
|
461
|
+
return wrapper;
|
|
462
|
+
}
|
|
463
|
+
function makeComputed(fn, eager = false) {
|
|
263
464
|
let self = {
|
|
264
465
|
cleanup: null,
|
|
265
466
|
deps: null,
|
|
266
467
|
depsTail: null,
|
|
468
|
+
disposal: null,
|
|
469
|
+
equals: null,
|
|
470
|
+
error: null,
|
|
267
471
|
fn: fn,
|
|
472
|
+
gv: 0,
|
|
268
473
|
height: 0,
|
|
269
474
|
nextHeap: undefined,
|
|
270
475
|
prevHeap: null,
|
|
476
|
+
rv: 0,
|
|
271
477
|
state: STATE_COMPUTED,
|
|
272
478
|
subs: null,
|
|
273
479
|
subsTail: null,
|
|
@@ -279,6 +485,10 @@ const computed = (fn) => {
|
|
|
279
485
|
self.height = observer.height;
|
|
280
486
|
recompute(self, false);
|
|
281
487
|
}
|
|
488
|
+
else if (eager) {
|
|
489
|
+
self.height = observer.height + 1;
|
|
490
|
+
recompute(self, false);
|
|
491
|
+
}
|
|
282
492
|
else {
|
|
283
493
|
self.height = observer.height + 1;
|
|
284
494
|
insertIntoHeap(self);
|
|
@@ -295,24 +505,102 @@ const computed = (fn) => {
|
|
|
295
505
|
}
|
|
296
506
|
}
|
|
297
507
|
return self;
|
|
508
|
+
}
|
|
509
|
+
const batch = (fn) => {
|
|
510
|
+
depth++;
|
|
511
|
+
try {
|
|
512
|
+
return fn();
|
|
513
|
+
}
|
|
514
|
+
finally {
|
|
515
|
+
depth--;
|
|
516
|
+
if (!depth) {
|
|
517
|
+
schedule();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
const computed = (fn, equals = null) => {
|
|
522
|
+
let o = observer, self = makeComputed(fn, true), value = self.value;
|
|
523
|
+
if (isPromise(value) || (value != null && typeof value[Symbol.asyncIterator] === 'function')) {
|
|
524
|
+
self = untrack(() => makeAsyncComputed(self));
|
|
525
|
+
if (o) {
|
|
526
|
+
onCleanup(() => dispose(self));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
self.equals = equals;
|
|
530
|
+
return self;
|
|
531
|
+
};
|
|
532
|
+
computed.invalidate = (c) => {
|
|
533
|
+
let meta = asyncMeta.get(c);
|
|
534
|
+
if (meta) {
|
|
535
|
+
computed.invalidate(meta.factory);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
writes++;
|
|
539
|
+
c.state |= STATE_DIRTY;
|
|
540
|
+
insertIntoHeap(c);
|
|
541
|
+
schedule();
|
|
298
542
|
};
|
|
299
543
|
const dispose = (computed) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
dep = unlink(dep);
|
|
544
|
+
if (draining) {
|
|
545
|
+
disposeHead = walkPush(computed, null, disposeHead);
|
|
546
|
+
return;
|
|
304
547
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
548
|
+
draining = true;
|
|
549
|
+
let node = computed;
|
|
550
|
+
try {
|
|
551
|
+
for (;;) {
|
|
552
|
+
deleteFromHeap(node);
|
|
553
|
+
let dep = node.deps;
|
|
554
|
+
while (dep) {
|
|
555
|
+
dep = unlink(dep);
|
|
556
|
+
}
|
|
557
|
+
node.deps = null;
|
|
558
|
+
if (node.cleanup) {
|
|
559
|
+
cleanup(node);
|
|
560
|
+
}
|
|
561
|
+
if (node.disposal) {
|
|
562
|
+
let d = node.disposal;
|
|
563
|
+
node.disposal = null;
|
|
564
|
+
d();
|
|
565
|
+
}
|
|
566
|
+
if (disposeHead === null) {
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
node = disposeHead.computed;
|
|
570
|
+
disposeHead = walkPop(disposeHead);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
draining = false;
|
|
308
575
|
}
|
|
309
576
|
};
|
|
310
|
-
const effect = (fn) => {
|
|
311
|
-
let c =
|
|
577
|
+
const effect = (fn, onError) => {
|
|
578
|
+
let c = makeComputed(onError
|
|
579
|
+
? (o) => {
|
|
580
|
+
try {
|
|
581
|
+
return fn(o);
|
|
582
|
+
}
|
|
583
|
+
catch (e) {
|
|
584
|
+
onError(e);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
: fn);
|
|
588
|
+
c.state |= STATE_EFFECT;
|
|
589
|
+
if (c.state & STATE_ERROR) {
|
|
590
|
+
let err = c.error;
|
|
591
|
+
microtask(() => {
|
|
592
|
+
throw err;
|
|
593
|
+
});
|
|
594
|
+
}
|
|
312
595
|
return () => {
|
|
313
596
|
dispose(c);
|
|
314
597
|
};
|
|
315
598
|
};
|
|
599
|
+
const flush = () => {
|
|
600
|
+
while (stabilizer === STABILIZER_SCHEDULED) {
|
|
601
|
+
stabilize();
|
|
602
|
+
}
|
|
603
|
+
};
|
|
316
604
|
const isComputed = (value) => {
|
|
317
605
|
return isObject(value) && !!(value.state & STATE_COMPUTED);
|
|
318
606
|
};
|
|
@@ -336,27 +624,37 @@ const onCleanup = (fn) => {
|
|
|
336
624
|
}
|
|
337
625
|
return fn;
|
|
338
626
|
};
|
|
627
|
+
const peek = (node) => {
|
|
628
|
+
if (node.state & STATE_COMPUTED) {
|
|
629
|
+
if (pendingHead !== null) {
|
|
630
|
+
drainPending();
|
|
631
|
+
}
|
|
632
|
+
pull(node);
|
|
633
|
+
}
|
|
634
|
+
if (node.state & STATE_ERROR) {
|
|
635
|
+
throw node.error;
|
|
636
|
+
}
|
|
637
|
+
return node.value;
|
|
638
|
+
};
|
|
339
639
|
const read = (node) => {
|
|
340
640
|
if (observer) {
|
|
341
641
|
link(node, observer);
|
|
342
642
|
if (node.state & STATE_COMPUTED) {
|
|
643
|
+
if (pendingHead !== null) {
|
|
644
|
+
drainPending();
|
|
645
|
+
}
|
|
343
646
|
let height = node.height;
|
|
344
647
|
if (height >= observer.height) {
|
|
345
648
|
observer.height = height + 1;
|
|
346
649
|
}
|
|
347
650
|
if (height >= heap_i || node.state & STATE_NOTIFY_MASK) {
|
|
348
|
-
|
|
349
|
-
notified = true;
|
|
350
|
-
for (let i = 0; i <= heap_n; i++) {
|
|
351
|
-
for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
|
|
352
|
-
notify(computed, STATE_DIRTY);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
update(node);
|
|
651
|
+
pull(node);
|
|
357
652
|
}
|
|
358
653
|
}
|
|
359
654
|
}
|
|
655
|
+
if (node.state & STATE_ERROR) {
|
|
656
|
+
throw node.error;
|
|
657
|
+
}
|
|
360
658
|
return node.value;
|
|
361
659
|
};
|
|
362
660
|
const root = (fn) => {
|
|
@@ -380,26 +678,74 @@ const root = (fn) => {
|
|
|
380
678
|
return value;
|
|
381
679
|
};
|
|
382
680
|
root.disposables = 0;
|
|
383
|
-
const signal = (value) => {
|
|
681
|
+
const signal = (value, equals = null) => {
|
|
384
682
|
return {
|
|
683
|
+
equals: equals,
|
|
684
|
+
keys: null,
|
|
685
|
+
nextPending: null,
|
|
686
|
+
rv: 0,
|
|
385
687
|
subs: null,
|
|
386
688
|
subsTail: null,
|
|
387
689
|
type: SIGNAL,
|
|
388
690
|
value,
|
|
389
691
|
};
|
|
390
692
|
};
|
|
693
|
+
signal.selector = (node, key) => {
|
|
694
|
+
if (!observer) {
|
|
695
|
+
return node.value === key;
|
|
696
|
+
}
|
|
697
|
+
let keys = (node.keys ??= new Map()), entry = keys.get(key);
|
|
698
|
+
if (entry === undefined) {
|
|
699
|
+
keys.set(key, entry = {
|
|
700
|
+
equals: null,
|
|
701
|
+
key,
|
|
702
|
+
keys: null,
|
|
703
|
+
nextPending: null,
|
|
704
|
+
parent: node,
|
|
705
|
+
rv: 0,
|
|
706
|
+
subs: null,
|
|
707
|
+
subsTail: null,
|
|
708
|
+
type: SIGNAL,
|
|
709
|
+
value: node.value === key
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return read(entry);
|
|
713
|
+
};
|
|
714
|
+
const untrack = (fn) => {
|
|
715
|
+
let o = observer;
|
|
716
|
+
observer = null;
|
|
717
|
+
try {
|
|
718
|
+
return fn();
|
|
719
|
+
}
|
|
720
|
+
finally {
|
|
721
|
+
observer = o;
|
|
722
|
+
}
|
|
723
|
+
};
|
|
391
724
|
const write = (signal, value) => {
|
|
392
|
-
|
|
725
|
+
let prev = signal.value;
|
|
726
|
+
if (signal.equals === null ? prev === value : signal.equals(prev, value)) {
|
|
393
727
|
return;
|
|
394
728
|
}
|
|
395
729
|
signal.value = value;
|
|
730
|
+
writes++;
|
|
731
|
+
if (signal.keys !== null) {
|
|
732
|
+
let entry = signal.keys.get(prev);
|
|
733
|
+
if (entry !== undefined) {
|
|
734
|
+
write(entry, false);
|
|
735
|
+
}
|
|
736
|
+
entry = signal.keys.get(value);
|
|
737
|
+
if (entry !== undefined) {
|
|
738
|
+
write(entry, true);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
396
741
|
if (signal.subs === null) {
|
|
397
742
|
return;
|
|
398
743
|
}
|
|
399
744
|
notified = false;
|
|
400
|
-
|
|
401
|
-
|
|
745
|
+
if (signal.nextPending === null) {
|
|
746
|
+
signal.nextPending = pendingHead ?? signal;
|
|
747
|
+
pendingHead = signal;
|
|
402
748
|
}
|
|
403
749
|
schedule();
|
|
404
750
|
};
|
|
405
|
-
export {
|
|
751
|
+
export { batch, computed, dispose, effect, flush, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
package/build/types.d.ts
CHANGED
|
@@ -5,10 +5,15 @@ interface Computed<T> {
|
|
|
5
5
|
cleanup: VoidFunction | VoidFunction[] | null;
|
|
6
6
|
deps: Link | null;
|
|
7
7
|
depsTail: Link | null;
|
|
8
|
+
disposal: VoidFunction | null;
|
|
9
|
+
equals: ((a: unknown, b: unknown) => boolean) | null;
|
|
10
|
+
error: unknown;
|
|
8
11
|
fn: (onCleanup: (fn: VoidFunction) => typeof fn) => T;
|
|
12
|
+
gv: number;
|
|
9
13
|
height: number;
|
|
10
14
|
nextHeap: Computed<unknown> | undefined;
|
|
11
15
|
prevHeap: Computed<unknown>;
|
|
16
|
+
rv: number;
|
|
12
17
|
state: number;
|
|
13
18
|
subs: Link | null;
|
|
14
19
|
subsTail: Link | null;
|
|
@@ -23,16 +28,23 @@ interface Link {
|
|
|
23
28
|
version: number;
|
|
24
29
|
}
|
|
25
30
|
declare const READONLY: unique symbol;
|
|
26
|
-
type Reactive<T> = T extends (...args:
|
|
27
|
-
readonly [READONLY]: true;
|
|
28
|
-
} : T extends (...args: any[]) => infer R ? R & {
|
|
31
|
+
type Reactive<T> = T extends (...args: never[]) => infer R ? Settled<R> & {
|
|
29
32
|
readonly [READONLY]: true;
|
|
30
33
|
} : T extends (infer U)[] ? U[] & Pick<ReactiveArray<U>, 'clear' | 'dispose' | 'on' | 'once'> : T extends Record<PropertyKey, unknown> ? {
|
|
31
34
|
[K in keyof T]: T[K] extends (infer U)[] ? Reactive<U[]> : T[K];
|
|
32
35
|
} & {
|
|
33
36
|
dispose: VoidFunction;
|
|
34
37
|
} : T;
|
|
38
|
+
type SelectorSignal<T> = Signal<boolean> & {
|
|
39
|
+
key: T;
|
|
40
|
+
parent: Signal<T>;
|
|
41
|
+
};
|
|
42
|
+
type Settled<T> = T extends Promise<infer U> ? Awaited<U> | undefined : T extends AsyncIterable<infer U> ? U | undefined : T;
|
|
35
43
|
type Signal<T> = {
|
|
44
|
+
equals: ((a: unknown, b: unknown) => boolean) | null;
|
|
45
|
+
keys: Map<T, SelectorSignal<T>> | null;
|
|
46
|
+
nextPending: Signal<unknown> | null;
|
|
47
|
+
rv: number;
|
|
36
48
|
subs: Link | null;
|
|
37
49
|
subsTail: Link | null;
|
|
38
50
|
type: typeof SIGNAL;
|
|
@@ -43,4 +55,4 @@ interface TransformResult {
|
|
|
43
55
|
code: string;
|
|
44
56
|
sourceFile: ts.SourceFile;
|
|
45
57
|
}
|
|
46
|
-
export type { Computed, Link, Reactive, Signal, TransformResult };
|
|
58
|
+
export type { Computed, Link, Reactive, SelectorSignal, Settled, Signal, TransformResult };
|