@esportsplus/reactivity 0.31.3 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +43 -6
  2. package/bench/cellx.bench.ts +61 -0
  3. package/bench/dynamic.bench.ts +103 -0
  4. package/bench/kairo.bench.ts +368 -0
  5. package/bench/lib/reactive-adapter.ts +51 -0
  6. package/bench/molbench.bench.ts +75 -0
  7. package/{tests/bench/system.ts → bench/system.bench.ts} +3 -3
  8. package/build/compiler/array.d.ts +1 -1
  9. package/build/compiler/constants.d.ts +7 -6
  10. package/build/compiler/constants.js +6 -7
  11. package/build/compiler/object.d.ts +1 -1
  12. package/build/compiler/object.js +1 -2
  13. package/build/compiler/primitives.d.ts +1 -1
  14. package/build/constants.d.ts +3 -1
  15. package/build/constants.js +3 -1
  16. package/build/reactive/array.d.ts +7 -3
  17. package/build/reactive/array.js +32 -24
  18. package/build/reactive/index.d.ts +2 -2
  19. package/build/reactive/index.js +1 -1
  20. package/build/reactive/object.d.ts +4 -4
  21. package/build/reactive/object.js +8 -23
  22. package/build/system.d.ts +15 -6
  23. package/build/system.js +432 -69
  24. package/build/types.d.ts +22 -4
  25. package/package.json +10 -7
  26. package/src/compiler/array.ts +1 -1
  27. package/src/compiler/constants.ts +8 -6
  28. package/src/compiler/index.ts +2 -1
  29. package/src/compiler/object.ts +2 -2
  30. package/src/compiler/plugins/vite.ts +1 -0
  31. package/src/compiler/primitives.ts +1 -1
  32. package/src/constants.ts +6 -2
  33. package/src/reactive/array.ts +50 -35
  34. package/src/reactive/index.ts +6 -6
  35. package/src/reactive/object.ts +16 -41
  36. package/src/system.ts +635 -80
  37. package/src/types.ts +38 -9
  38. package/test/async-computed.test.ts +409 -0
  39. package/test/async-errors.test.ts +146 -0
  40. package/test/async-hardening.test.ts +73 -0
  41. package/test/async-iterable.test.ts +221 -0
  42. package/test/async-nested.test.ts +19 -0
  43. package/test/deep-graphs.test.ts +215 -0
  44. package/{tests/effects.ts → test/effects.test.ts} +122 -0
  45. package/test/equals.test.ts +142 -0
  46. package/test/errors.test.ts +201 -0
  47. package/test/flush.test.ts +185 -0
  48. package/test/glitch-freedom.test.ts +115 -0
  49. package/test/invalidate.test.ts +147 -0
  50. package/test/lib/wait-for.ts +28 -0
  51. package/test/pending-writes.test.ts +139 -0
  52. package/{tests/reactive.ts → test/reactive/reactive.test.ts} +20 -19
  53. package/test/read-dedup.test.ts +120 -0
  54. package/test/signal-selector.test.ts +187 -0
  55. package/{tests/system.ts → test/system.test.ts} +210 -19
  56. package/test/tsconfig.json +16 -0
  57. package/test/untrack.test.ts +146 -0
  58. package/vitest.config.ts +2 -2
  59. package/tests/async-computed.ts +0 -239
  60. /package/{tests/bench/array.ts → bench/reactive/array.bench.ts} +0 -0
  61. /package/{tests/bench/reactive-object.ts → bench/reactive/reactive-object.bench.ts} +0 -0
  62. /package/{tests → bench}/tsconfig.json +0 -0
  63. /package/{tests/compiler.ts → test/compiler/compiler.test.ts} +0 -0
  64. /package/{tests/primitives.ts → test/primitives.test.ts} +0 -0
  65. /package/{tests/array.ts → test/reactive/array.test.ts} +0 -0
  66. /package/{tests/nested.ts → test/reactive/nested.test.ts} +0 -0
  67. /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
- value();
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
- value[i]();
39
+ try {
40
+ value[i]();
41
+ }
42
+ catch (e) {
43
+ errors.push(e);
44
+ }
15
45
  }
16
46
  }
17
- computed.cleanup = null;
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
- for (let link = computed.subs; link; link = link.nextSub) {
123
- notify(link.sub, STATE_CHECK);
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
- cleanup(computed);
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 && value !== computed.value) {
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
- for (let c = computed.subs; c; c = c.nextSub) {
167
- let s = c.sub, state = s.state;
168
- if (state & STATE_CHECK) {
169
- s.state = state | STATE_DIRTY;
170
- }
171
- insertIntoHeap(s);
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 && dep.state & STATE_COMPUTED) {
225
- dispose(dep);
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,137 @@ function unlink(link) {
230
357
  linkPoolHead = link;
231
358
  return nextDep;
232
359
  }
233
- function update(computed) {
234
- if (computed.state & STATE_CHECK) {
235
- for (let link = computed.deps; link; link = link.nextDep) {
236
- let dep = link.dep;
237
- if (dep.state & STATE_COMPUTED) {
238
- update(dep);
239
- if (computed.state & STATE_DIRTY) {
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
- const asyncComputed = (fn) => {
251
- let factory = computed(fn), node = signal(undefined), v = 0;
252
- onCleanup(effect(() => {
253
- let id = ++v;
254
- read(factory).then((value) => {
255
- if (id === v) {
256
- write(node, value);
414
+ function makeAsyncComputed(factory) {
415
+ let error = signal(undefined), node = signal(undefined), pending = signal(false), 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);
420
+ write(pending, false);
257
421
  }
258
- }, () => { });
259
- }));
260
- return node;
261
- };
262
- const computed = (fn) => {
422
+ }, id = ++v, result = read(factory);
423
+ if (isPromise(result)) {
424
+ if (id === v && !(factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
425
+ write(pending, true);
426
+ }
427
+ result.then((value) => {
428
+ if (id === v && !(factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
429
+ write(error, undefined);
430
+ write(node, value);
431
+ write(pending, false);
432
+ }
433
+ }, fail);
434
+ }
435
+ else if (result != null && typeof result[Symbol.asyncIterator] === 'function') {
436
+ let it = result[Symbol.asyncIterator]();
437
+ onCleanup(() => {
438
+ it.return?.();
439
+ });
440
+ let step = (r) => {
441
+ if (id !== v || (factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
442
+ return;
443
+ }
444
+ if (!r.done) {
445
+ write(error, undefined);
446
+ write(node, r.value);
447
+ write(pending, false);
448
+ it.next().then(step, fail);
449
+ }
450
+ else {
451
+ write(pending, false);
452
+ }
453
+ };
454
+ if (id === v && !(factory.state & (STATE_IN_HEAP | STATE_NOTIFY_MASK))) {
455
+ write(pending, true);
456
+ }
457
+ untrack(() => it.next()).then(step, fail);
458
+ }
459
+ else {
460
+ write(error, undefined);
461
+ write(node, result);
462
+ write(pending, false);
463
+ }
464
+ });
465
+ let wrapper = makeComputed(() => {
466
+ let e = read(error);
467
+ if (e !== undefined) {
468
+ throw e;
469
+ }
470
+ return read(node);
471
+ });
472
+ wrapper.pending = pending;
473
+ asyncMeta.set(wrapper, { factory: factory });
474
+ wrapper.disposal = stop;
475
+ return wrapper;
476
+ }
477
+ function makeComputed(fn, eager = false) {
263
478
  let self = {
264
479
  cleanup: null,
265
480
  deps: null,
266
481
  depsTail: null,
482
+ disposal: null,
483
+ equals: null,
484
+ error: null,
267
485
  fn: fn,
486
+ gv: 0,
268
487
  height: 0,
269
488
  nextHeap: undefined,
270
489
  prevHeap: null,
490
+ rv: 0,
271
491
  state: STATE_COMPUTED,
272
492
  subs: null,
273
493
  subsTail: null,
@@ -279,6 +499,10 @@ const computed = (fn) => {
279
499
  self.height = observer.height;
280
500
  recompute(self, false);
281
501
  }
502
+ else if (eager) {
503
+ self.height = observer.height + 1;
504
+ recompute(self, false);
505
+ }
282
506
  else {
283
507
  self.height = observer.height + 1;
284
508
  insertIntoHeap(self);
@@ -295,24 +519,101 @@ const computed = (fn) => {
295
519
  }
296
520
  }
297
521
  return self;
522
+ }
523
+ const batch = (fn) => {
524
+ depth++;
525
+ try {
526
+ return fn();
527
+ }
528
+ finally {
529
+ depth--;
530
+ if (!depth) {
531
+ schedule();
532
+ }
533
+ }
534
+ };
535
+ const computed = (fn, equals = null) => {
536
+ let o = observer, self = makeComputed(fn, true), value = self.value;
537
+ if (isPromise(value) || (value != null && typeof value[Symbol.asyncIterator] === 'function')) {
538
+ self = untrack(() => makeAsyncComputed(self));
539
+ if (o) {
540
+ onCleanup(() => dispose(self));
541
+ }
542
+ }
543
+ self.equals = equals;
544
+ return self;
545
+ };
546
+ computed.invalidate = (c) => {
547
+ let meta = asyncMeta.get(c);
548
+ if (meta) {
549
+ computed.invalidate(meta.factory);
550
+ return;
551
+ }
552
+ writes++;
553
+ c.state |= STATE_DIRTY;
554
+ insertIntoHeap(c);
555
+ schedule();
298
556
  };
299
557
  const dispose = (computed) => {
300
- deleteFromHeap(computed);
301
- let dep = computed.deps;
302
- while (dep) {
303
- dep = unlink(dep);
558
+ if (draining) {
559
+ disposeHead = walkPush(computed, null, disposeHead);
560
+ return;
304
561
  }
305
- computed.deps = null;
306
- if (computed.cleanup) {
307
- cleanup(computed);
562
+ draining = true;
563
+ let node = computed;
564
+ try {
565
+ for (;;) {
566
+ deleteFromHeap(node);
567
+ let dep = node.deps;
568
+ while (dep) {
569
+ dep = unlink(dep);
570
+ }
571
+ node.deps = null;
572
+ if (node.cleanup) {
573
+ cleanup(node);
574
+ }
575
+ if (node.disposal) {
576
+ let d = node.disposal;
577
+ node.disposal = null;
578
+ d();
579
+ }
580
+ if (disposeHead === null) {
581
+ break;
582
+ }
583
+ node = disposeHead.computed;
584
+ disposeHead = walkPop(disposeHead);
585
+ }
586
+ }
587
+ finally {
588
+ draining = false;
308
589
  }
309
590
  };
310
- const effect = (fn) => {
311
- let c = computed(fn);
591
+ const effect = (fn, apply) => {
592
+ let prev;
593
+ let c = makeComputed(apply
594
+ ? (o) => {
595
+ let v = fn(o);
596
+ untrack(() => apply(v, prev));
597
+ prev = v;
598
+ return v;
599
+ }
600
+ : fn);
601
+ c.state |= STATE_EFFECT;
602
+ if (c.state & STATE_ERROR) {
603
+ let err = c.error;
604
+ microtask(() => {
605
+ throw err;
606
+ });
607
+ }
312
608
  return () => {
313
609
  dispose(c);
314
610
  };
315
611
  };
612
+ const flush = () => {
613
+ while (stabilizer === STABILIZER_SCHEDULED) {
614
+ stabilize();
615
+ }
616
+ };
316
617
  const isComputed = (value) => {
317
618
  return isObject(value) && !!(value.state & STATE_COMPUTED);
318
619
  };
@@ -336,27 +637,37 @@ const onCleanup = (fn) => {
336
637
  }
337
638
  return fn;
338
639
  };
640
+ const peek = (node) => {
641
+ if (node.state & STATE_COMPUTED) {
642
+ if (pendingHead !== null) {
643
+ drainPending();
644
+ }
645
+ pull(node);
646
+ }
647
+ if (node.state & STATE_ERROR) {
648
+ throw node.error;
649
+ }
650
+ return node.value;
651
+ };
339
652
  const read = (node) => {
340
653
  if (observer) {
341
654
  link(node, observer);
342
655
  if (node.state & STATE_COMPUTED) {
656
+ if (pendingHead !== null) {
657
+ drainPending();
658
+ }
343
659
  let height = node.height;
344
660
  if (height >= observer.height) {
345
661
  observer.height = height + 1;
346
662
  }
347
663
  if (height >= heap_i || node.state & STATE_NOTIFY_MASK) {
348
- if (!notified) {
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);
664
+ pull(node);
357
665
  }
358
666
  }
359
667
  }
668
+ if (node.state & STATE_ERROR) {
669
+ throw node.error;
670
+ }
360
671
  return node.value;
361
672
  };
362
673
  const root = (fn) => {
@@ -380,26 +691,78 @@ const root = (fn) => {
380
691
  return value;
381
692
  };
382
693
  root.disposables = 0;
383
- const signal = (value) => {
694
+ const signal = (value, equals = null) => {
384
695
  return {
696
+ equals: equals,
697
+ key: undefined,
698
+ keys: null,
699
+ nextPending: null,
700
+ parent: undefined,
701
+ rv: 0,
702
+ state: 0,
385
703
  subs: null,
386
704
  subsTail: null,
387
705
  type: SIGNAL,
388
706
  value,
389
707
  };
390
708
  };
709
+ signal.selector = (node, key) => {
710
+ if (!observer) {
711
+ return node.value === key;
712
+ }
713
+ let keys = (node.keys ??= new Map()), entry = keys.get(key);
714
+ if (entry === undefined) {
715
+ keys.set(key, entry = {
716
+ equals: null,
717
+ key,
718
+ keys: null,
719
+ nextPending: null,
720
+ parent: node,
721
+ rv: 0,
722
+ state: 0,
723
+ subs: null,
724
+ subsTail: null,
725
+ type: SIGNAL,
726
+ value: node.value === key
727
+ });
728
+ }
729
+ return read(entry);
730
+ };
731
+ const untrack = (fn) => {
732
+ let o = observer;
733
+ observer = null;
734
+ try {
735
+ return fn();
736
+ }
737
+ finally {
738
+ observer = o;
739
+ }
740
+ };
391
741
  const write = (signal, value) => {
392
- if (signal.value === value) {
742
+ let prev = signal.value;
743
+ if (signal.equals === null ? prev === value : signal.equals(prev, value)) {
393
744
  return;
394
745
  }
395
746
  signal.value = value;
747
+ writes++;
748
+ if (signal.keys !== null) {
749
+ let entry = signal.keys.get(prev);
750
+ if (entry !== undefined) {
751
+ write(entry, false);
752
+ }
753
+ entry = signal.keys.get(value);
754
+ if (entry !== undefined) {
755
+ write(entry, true);
756
+ }
757
+ }
396
758
  if (signal.subs === null) {
397
759
  return;
398
760
  }
399
761
  notified = false;
400
- for (let link = signal.subs; link; link = link.nextSub) {
401
- insertIntoHeap(link.sub);
762
+ if (signal.nextPending === null) {
763
+ signal.nextPending = pendingHead ?? signal;
764
+ pendingHead = signal;
402
765
  }
403
766
  schedule();
404
767
  };
405
- export { asyncComputed, computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, signal, write };
768
+ export { batch, computed, dispose, effect, flush, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };