@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.
Files changed (68) hide show
  1. package/.claude/CHANGELOG.md +43 -0
  2. package/README.md +13 -6
  3. package/bench/cellx.bench.ts +61 -0
  4. package/bench/dynamic.bench.ts +103 -0
  5. package/bench/kairo.bench.ts +368 -0
  6. package/bench/lib/reactive-adapter.ts +51 -0
  7. package/bench/molbench.bench.ts +75 -0
  8. package/{tests/bench/system.ts → bench/system.bench.ts} +3 -3
  9. package/build/compiler/array.d.ts +1 -1
  10. package/build/compiler/constants.d.ts +7 -6
  11. package/build/compiler/constants.js +6 -7
  12. package/build/compiler/object.d.ts +1 -1
  13. package/build/compiler/object.js +1 -2
  14. package/build/compiler/primitives.d.ts +1 -1
  15. package/build/constants.d.ts +3 -1
  16. package/build/constants.js +3 -1
  17. package/build/reactive/array.d.ts +8 -5
  18. package/build/reactive/array.js +14 -14
  19. package/build/reactive/index.d.ts +2 -2
  20. package/build/reactive/index.js +1 -1
  21. package/build/reactive/object.d.ts +4 -4
  22. package/build/reactive/object.js +8 -23
  23. package/build/system.d.ts +15 -6
  24. package/build/system.js +415 -69
  25. package/build/types.d.ts +16 -4
  26. package/package.json +5 -2
  27. package/src/compiler/array.ts +1 -1
  28. package/src/compiler/constants.ts +8 -6
  29. package/src/compiler/index.ts +2 -1
  30. package/src/compiler/object.ts +2 -2
  31. package/src/compiler/plugins/vite.ts +1 -0
  32. package/src/compiler/primitives.ts +1 -1
  33. package/src/constants.ts +6 -2
  34. package/src/reactive/array.ts +26 -23
  35. package/src/reactive/index.ts +6 -6
  36. package/src/reactive/object.ts +16 -41
  37. package/src/system.ts +612 -80
  38. package/src/types.ts +29 -9
  39. package/test/async-computed.test.ts +323 -0
  40. package/test/async-errors.test.ts +146 -0
  41. package/test/async-hardening.test.ts +73 -0
  42. package/test/async-iterable.test.ts +221 -0
  43. package/test/async-nested.test.ts +19 -0
  44. package/test/deep-graphs.test.ts +215 -0
  45. package/{tests/effects.ts → test/effects.test.ts} +60 -0
  46. package/test/equals.test.ts +142 -0
  47. package/test/errors.test.ts +201 -0
  48. package/test/flush.test.ts +185 -0
  49. package/test/glitch-freedom.test.ts +115 -0
  50. package/test/invalidate.test.ts +147 -0
  51. package/test/lib/wait-for.ts +28 -0
  52. package/test/pending-writes.test.ts +139 -0
  53. package/{tests/reactive.ts → test/reactive/reactive.test.ts} +20 -19
  54. package/test/read-dedup.test.ts +120 -0
  55. package/test/signal-selector.test.ts +187 -0
  56. package/{tests/system.ts → test/system.test.ts} +214 -23
  57. package/test/tsconfig.json +16 -0
  58. package/test/untrack.test.ts +146 -0
  59. package/vitest.config.ts +2 -2
  60. package/tests/async-computed.ts +0 -239
  61. /package/{tests/bench/array.ts → bench/reactive/array.bench.ts} +0 -0
  62. /package/{tests/bench/reactive-object.ts → bench/reactive/reactive-object.bench.ts} +0 -0
  63. /package/{tests → bench}/tsconfig.json +0 -0
  64. /package/{tests/compiler.ts → test/compiler/compiler.test.ts} +0 -0
  65. /package/{tests/primitives.ts → test/primitives.test.ts} +0 -0
  66. /package/{tests/array.ts → test/reactive/array.test.ts} +0 -0
  67. /package/{tests/nested.ts → test/reactive/nested.test.ts} +0 -0
  68. /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,123 @@ 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), 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
- return node;
261
- };
262
- const computed = (fn) => {
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
- deleteFromHeap(computed);
301
- let dep = computed.deps;
302
- while (dep) {
303
- dep = unlink(dep);
544
+ if (draining) {
545
+ disposeHead = walkPush(computed, null, disposeHead);
546
+ return;
304
547
  }
305
- computed.deps = null;
306
- if (computed.cleanup) {
307
- cleanup(computed);
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 = computed(fn);
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
- 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);
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
- if (signal.value === value) {
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
- for (let link = signal.subs; link; link = link.nextSub) {
401
- insertIntoHeap(link.sub);
745
+ if (signal.nextPending === null) {
746
+ signal.nextPending = pendingHead ?? signal;
747
+ pendingHead = signal;
402
748
  }
403
749
  schedule();
404
750
  };
405
- export { asyncComputed, computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, signal, write };
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: unknown[]) => Promise<infer R> ? (R | undefined) & {
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 };