@graphrefly/graphrefly 0.18.0 → 0.19.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 (72) hide show
  1. package/dist/{chunk-TNKODJ6E.js → chunk-AHRKWMNI.js} +7 -3
  2. package/dist/{chunk-TNKODJ6E.js.map → chunk-AHRKWMNI.js.map} +1 -1
  3. package/dist/{chunk-76YPZQTW.js → chunk-BER7UYLM.js} +27 -26
  4. package/dist/chunk-BER7UYLM.js.map +1 -0
  5. package/dist/{chunk-F6ORUNO7.js → chunk-IRZAGZUB.js} +34 -2
  6. package/dist/{chunk-F6ORUNO7.js.map → chunk-IRZAGZUB.js.map} +1 -1
  7. package/dist/{chunk-LB3RYLSC.js → chunk-JC2SN46B.js} +197 -42
  8. package/dist/chunk-JC2SN46B.js.map +1 -0
  9. package/dist/{chunk-KJGUP35I.js → chunk-OO5QOAXI.js} +4 -4
  10. package/dist/{chunk-UVWEKTYC.js → chunk-UW77D7SP.js} +3 -3
  11. package/dist/{chunk-J7S54G7I.js → chunk-XUOY3YKN.js} +7 -2
  12. package/dist/chunk-XUOY3YKN.js.map +1 -0
  13. package/dist/chunk-YLR5JUJZ.js +111 -0
  14. package/dist/chunk-YLR5JUJZ.js.map +1 -0
  15. package/dist/{chunk-BV3TPSBK.js → chunk-YXR3WW3Q.js} +740 -755
  16. package/dist/chunk-YXR3WW3Q.js.map +1 -0
  17. package/dist/compat/nestjs/index.cjs +931 -784
  18. package/dist/compat/nestjs/index.cjs.map +1 -1
  19. package/dist/compat/nestjs/index.d.cts +4 -4
  20. package/dist/compat/nestjs/index.d.ts +4 -4
  21. package/dist/compat/nestjs/index.js +7 -7
  22. package/dist/core/index.cjs +651 -664
  23. package/dist/core/index.cjs.map +1 -1
  24. package/dist/core/index.d.cts +2 -2
  25. package/dist/core/index.d.ts +2 -2
  26. package/dist/core/index.js +7 -3
  27. package/dist/extra/index.cjs +686 -672
  28. package/dist/extra/index.cjs.map +1 -1
  29. package/dist/extra/index.d.cts +4 -4
  30. package/dist/extra/index.d.ts +4 -4
  31. package/dist/extra/index.js +5 -5
  32. package/dist/graph/index.cjs +836 -808
  33. package/dist/graph/index.cjs.map +1 -1
  34. package/dist/graph/index.d.cts +3 -3
  35. package/dist/graph/index.d.ts +3 -3
  36. package/dist/graph/index.js +8 -8
  37. package/dist/{graph-gISB9n3n.d.ts → graph-KsTe57nI.d.cts} +82 -8
  38. package/dist/{graph-BYFlyNpX.d.cts → graph-mILUUqW8.d.ts} +82 -8
  39. package/dist/{index-CgKPpiu8.d.ts → index-8a605sg9.d.ts} +2 -2
  40. package/dist/{index-DKaB2x0T.d.ts → index-B2SvPEbc.d.ts} +6 -65
  41. package/dist/{index-B80mMeuf.d.ts → index-BBUYZfJ1.d.cts} +122 -76
  42. package/dist/{index-D_tUMcpz.d.cts → index-Bjh5C1Tp.d.cts} +37 -32
  43. package/dist/{index-B43mC7uY.d.cts → index-BjtlNirP.d.cts} +3 -3
  44. package/dist/{index-7WnwgjMu.d.ts → index-BnkMgNNa.d.ts} +37 -32
  45. package/dist/{index-CEDaJaYE.d.ts → index-CgSiUouz.d.ts} +3 -3
  46. package/dist/{index-EmzYk-TG.d.cts → index-CvKzv0AW.d.ts} +122 -76
  47. package/dist/{index-Ci_vPaVm.d.cts → index-UudxGnzc.d.cts} +6 -65
  48. package/dist/{index-BqOWSFhr.d.cts → index-VHA43cGP.d.cts} +2 -2
  49. package/dist/index.cjs +5920 -5572
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +595 -399
  52. package/dist/index.d.ts +595 -399
  53. package/dist/index.js +4357 -4063
  54. package/dist/index.js.map +1 -1
  55. package/dist/{meta-npl5b97j.d.cts → meta-BnG7XAaE.d.cts} +394 -236
  56. package/dist/{meta-npl5b97j.d.ts → meta-BnG7XAaE.d.ts} +394 -236
  57. package/dist/{observable-DFBCBELR.d.cts → observable-C8Kx_O6k.d.cts} +1 -1
  58. package/dist/{observable-oAGygKvc.d.ts → observable-DcBwQY7t.d.ts} +1 -1
  59. package/dist/patterns/reactive-layout/index.cjs +865 -718
  60. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  61. package/dist/patterns/reactive-layout/index.d.cts +3 -3
  62. package/dist/patterns/reactive-layout/index.d.ts +3 -3
  63. package/dist/patterns/reactive-layout/index.js +4 -4
  64. package/package.json +1 -1
  65. package/dist/chunk-76YPZQTW.js.map +0 -1
  66. package/dist/chunk-BV3TPSBK.js.map +0 -1
  67. package/dist/chunk-FCLROC4Q.js +0 -231
  68. package/dist/chunk-FCLROC4Q.js.map +0 -1
  69. package/dist/chunk-J7S54G7I.js.map +0 -1
  70. package/dist/chunk-LB3RYLSC.js.map +0 -1
  71. /package/dist/{chunk-KJGUP35I.js.map → chunk-OO5QOAXI.js.map} +0 -0
  72. /package/dist/{chunk-UVWEKTYC.js.map → chunk-UW77D7SP.js.map} +0 -0
@@ -65,6 +65,7 @@ __export(extra_exports, {
65
65
  find: () => find,
66
66
  first: () => first,
67
67
  firstValueFrom: () => firstValueFrom,
68
+ firstWhere: () => firstWhere,
68
69
  flatMap: () => flatMap,
69
70
  forEach: () => forEach,
70
71
  fromAny: () => fromAny,
@@ -142,7 +143,6 @@ __export(extra_exports, {
142
143
  shareReplay: () => shareReplay,
143
144
  signalToName: () => signalToName,
144
145
  skip: () => skip,
145
- startWith: () => startWith,
146
146
  switchMap: () => switchMap,
147
147
  take: () => take,
148
148
  takeUntil: () => takeUntil,
@@ -189,6 +189,7 @@ __export(extra_exports, {
189
189
  module.exports = __toCommonJS(extra_exports);
190
190
 
191
191
  // src/core/messages.ts
192
+ var START = /* @__PURE__ */ Symbol.for("graphrefly/START");
192
193
  var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
193
194
  var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
194
195
  var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
@@ -199,6 +200,7 @@ var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
199
200
  var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
200
201
  var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
201
202
  var knownMessageTypes = [
203
+ START,
202
204
  DATA,
203
205
  DIRTY,
204
206
  RESOLVED,
@@ -209,13 +211,15 @@ var knownMessageTypes = [
209
211
  COMPLETE,
210
212
  ERROR
211
213
  ];
214
+ var knownMessageSet = new Set(knownMessageTypes);
212
215
  function messageTier(t) {
213
- if (t === DIRTY || t === INVALIDATE) return 0;
214
- if (t === PAUSE || t === RESUME) return 1;
215
- if (t === DATA || t === RESOLVED) return 2;
216
- if (t === COMPLETE || t === ERROR) return 3;
217
- if (t === TEARDOWN) return 4;
218
- return 0;
216
+ if (t === START) return 0;
217
+ if (t === DIRTY || t === INVALIDATE) return 1;
218
+ if (t === PAUSE || t === RESUME) return 2;
219
+ if (t === DATA || t === RESOLVED) return 3;
220
+ if (t === COMPLETE || t === ERROR) return 4;
221
+ if (t === TEARDOWN) return 5;
222
+ return 1;
219
223
  }
220
224
  function isPhase2Message(msg) {
221
225
  const t = msg[0];
@@ -224,6 +228,10 @@ function isPhase2Message(msg) {
224
228
  function isTerminalMessage(t) {
225
229
  return t === COMPLETE || t === ERROR;
226
230
  }
231
+ function isLocalOnly(t) {
232
+ if (!knownMessageSet.has(t)) return false;
233
+ return messageTier(t) < 3;
234
+ }
227
235
  function propagatesToMeta(t) {
228
236
  return t === TEARDOWN;
229
237
  }
@@ -384,14 +392,14 @@ function _downSequential(sink, messages, phase = 2) {
384
392
  const dataQueue = phase === 3 ? pendingPhase3 : pendingPhase2;
385
393
  for (const msg of messages) {
386
394
  const tier = messageTier(msg[0]);
387
- if (tier === 2) {
395
+ if (tier === 3) {
388
396
  if (isBatching()) {
389
397
  const m = msg;
390
398
  dataQueue.push(() => sink([m]));
391
399
  } else {
392
400
  sink([msg]);
393
401
  }
394
- } else if (tier >= 3) {
402
+ } else if (tier >= 4) {
395
403
  if (isBatching()) {
396
404
  const m = msg;
397
405
  pendingPhase3.push(() => sink([m]));
@@ -500,10 +508,24 @@ function advanceVersion(info, newValue, hashFn) {
500
508
  }
501
509
  }
502
510
 
503
- // src/core/node.ts
511
+ // src/core/node-base.ts
504
512
  var NO_VALUE = /* @__PURE__ */ Symbol.for("graphrefly/NO_VALUE");
505
513
  var CLEANUP_RESULT = /* @__PURE__ */ Symbol.for("graphrefly/CLEANUP_RESULT");
506
- function createIntBitSet() {
514
+ var isCleanupResult = (value) => typeof value === "object" && value !== null && CLEANUP_RESULT in value;
515
+ var isCleanupFn = (value) => typeof value === "function";
516
+ function statusAfterMessage(status, msg) {
517
+ const t = msg[0];
518
+ if (t === DIRTY) return "dirty";
519
+ if (t === DATA) return "settled";
520
+ if (t === RESOLVED) return "resolved";
521
+ if (t === COMPLETE) return "completed";
522
+ if (t === ERROR) return "errored";
523
+ if (t === INVALIDATE) return "dirty";
524
+ if (t === TEARDOWN) return "disconnected";
525
+ return status;
526
+ }
527
+ function createIntBitSet(size) {
528
+ const fullMask = size >= 32 ? -1 : ~(-1 << size);
507
529
  let bits = 0;
508
530
  return {
509
531
  set(i) {
@@ -516,7 +538,8 @@ function createIntBitSet() {
516
538
  return (bits & 1 << i) !== 0;
517
539
  },
518
540
  covers(other) {
519
- return (bits & other._bits()) === other._bits();
541
+ const otherBits = other._bits();
542
+ return (bits & otherBits) === otherBits;
520
543
  },
521
544
  any() {
522
545
  return bits !== 0;
@@ -524,6 +547,9 @@ function createIntBitSet() {
524
547
  reset() {
525
548
  bits = 0;
526
549
  },
550
+ setAll() {
551
+ bits = fullMask;
552
+ },
527
553
  _bits() {
528
554
  return bits;
529
555
  }
@@ -531,6 +557,8 @@ function createIntBitSet() {
531
557
  }
532
558
  function createArrayBitSet(size) {
533
559
  const words = new Uint32Array(Math.ceil(size / 32));
560
+ const lastBits = size % 32;
561
+ const lastWordMask = lastBits === 0 ? 4294967295 : (1 << lastBits) - 1 >>> 0;
534
562
  return {
535
563
  set(i) {
536
564
  words[i >>> 5] |= 1 << (i & 31);
@@ -557,130 +585,103 @@ function createArrayBitSet(size) {
557
585
  reset() {
558
586
  words.fill(0);
559
587
  },
588
+ setAll() {
589
+ for (let w = 0; w < words.length - 1; w++) words[w] = 4294967295;
590
+ if (words.length > 0) words[words.length - 1] = lastWordMask;
591
+ },
560
592
  _words: words
561
593
  };
562
594
  }
563
595
  function createBitSet(size) {
564
- return size <= 31 ? createIntBitSet() : createArrayBitSet(size);
596
+ return size <= 31 ? createIntBitSet(size) : createArrayBitSet(size);
565
597
  }
566
- var isNodeArray = (value) => Array.isArray(value);
567
- var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
568
- var isCleanupResult = (value) => typeof value === "object" && value !== null && CLEANUP_RESULT in value;
569
- var isCleanupFn = (value) => typeof value === "function";
570
- var statusAfterMessage = (status, msg) => {
571
- const t = msg[0];
572
- if (t === DIRTY) return "dirty";
573
- if (t === DATA) return "settled";
574
- if (t === RESOLVED) return "resolved";
575
- if (t === COMPLETE) return "completed";
576
- if (t === ERROR) return "errored";
577
- if (t === INVALIDATE) return "dirty";
578
- if (t === TEARDOWN) return "disconnected";
579
- return status;
580
- };
581
- var NodeImpl = class {
582
- // --- Configuration (set once, never reassigned) ---
598
+ var NodeBase = class {
599
+ // --- Identity (set once) ---
583
600
  _optsName;
584
601
  _registryName;
585
- /** @internal read by {@link describeNode} before inference. */
602
+ /** @internal Read by `describeNode` before inference. */
586
603
  _describeKind;
587
604
  meta;
588
- _deps;
589
- _fn;
590
- _opts;
605
+ // --- Options ---
591
606
  _equals;
607
+ _resubscribable;
608
+ _resetOnTeardown;
609
+ _onResubscribe;
592
610
  _onMessage;
593
- /** @internal read by {@link describeNode} for `accessHintForGuard`. */
611
+ /** @internal Read by `describeNode` for `accessHintForGuard`. */
594
612
  _guard;
613
+ /** @internal Subclasses update this through {@link _recordMutation}. */
595
614
  _lastMutation;
596
- _hasDeps;
597
- _autoComplete;
598
- _isSingleDep;
599
- // --- Mutable state ---
615
+ // --- Versioning ---
616
+ _hashFn;
617
+ _versioning;
618
+ // --- Lifecycle state ---
619
+ /** @internal Read by `describeNode` and `graph.ts`. */
600
620
  _cached;
621
+ /** @internal Read externally via `get status()`. */
601
622
  _status;
602
623
  _terminal = false;
603
- _connected = false;
604
- _producerStarted = false;
605
- _connecting = false;
606
- _manualEmitUsed = false;
624
+ _active = false;
625
+ // --- Sink storage ---
626
+ /** @internal Read by `graph/profile.ts` for subscriber counts. */
607
627
  _sinkCount = 0;
608
628
  _singleDepSinkCount = 0;
609
- // --- Object/collection state ---
610
- _depDirtyMask;
611
- _depSettledMask;
612
- _depCompleteMask;
613
- _allDepsCompleteMask;
614
- _lastDepValues;
615
- _cleanup;
616
- _sinks = null;
617
629
  _singleDepSinks = /* @__PURE__ */ new WeakSet();
618
- _upstreamUnsubs = [];
630
+ _sinks = null;
631
+ // --- Actions + bound helpers ---
619
632
  _actions;
620
633
  _boundDownToSinks;
634
+ // --- Inspector hook (Graph observability) ---
621
635
  _inspectorHook;
622
- _versioning;
623
- _hashFn;
624
- constructor(deps, fn, opts) {
625
- this._deps = deps;
626
- this._fn = fn;
627
- this._opts = opts;
636
+ constructor(opts) {
628
637
  this._optsName = opts.name;
629
638
  this._describeKind = opts.describeKind;
630
639
  this._equals = opts.equals ?? Object.is;
640
+ this._resubscribable = opts.resubscribable ?? false;
641
+ this._resetOnTeardown = opts.resetOnTeardown ?? false;
642
+ this._onResubscribe = opts.onResubscribe;
631
643
  this._onMessage = opts.onMessage;
632
644
  this._guard = opts.guard;
633
- this._hasDeps = deps.length > 0;
634
- this._autoComplete = opts.completeWhenDepsComplete ?? true;
635
- this._isSingleDep = deps.length === 1 && fn != null;
636
645
  this._cached = "initial" in opts ? opts.initial : NO_VALUE;
637
- this._status = this._hasDeps ? "disconnected" : "settled";
646
+ this._status = "disconnected";
638
647
  this._hashFn = opts.versioningHash ?? defaultHash;
639
648
  this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached === NO_VALUE ? void 0 : this._cached, {
640
649
  id: opts.versioningId,
641
650
  hash: this._hashFn
642
651
  }) : void 0;
643
- this._depDirtyMask = createBitSet(deps.length);
644
- this._depSettledMask = createBitSet(deps.length);
645
- this._depCompleteMask = createBitSet(deps.length);
646
- this._allDepsCompleteMask = createBitSet(deps.length);
647
- for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
648
652
  const meta = {};
649
653
  for (const [k, v] of Object.entries(opts.meta ?? {})) {
650
- meta[k] = node({
651
- initial: v,
652
- name: `${opts.name ?? "node"}:meta:${k}`,
653
- describeKind: "state",
654
- ...opts.guard != null ? { guard: opts.guard } : {}
655
- });
654
+ meta[k] = this._createMetaNode(k, v, opts);
656
655
  }
657
656
  Object.freeze(meta);
658
657
  this.meta = meta;
659
658
  const self = this;
660
659
  this._actions = {
661
660
  down(messages) {
662
- self._manualEmitUsed = true;
661
+ self._onManualEmit();
663
662
  self._downInternal(messages);
664
663
  },
665
664
  emit(value) {
666
- self._manualEmitUsed = true;
665
+ self._onManualEmit();
667
666
  self._downAutoValue(value);
668
667
  },
669
668
  up(messages) {
670
669
  self._upInternal(messages);
671
670
  }
672
671
  };
673
- this.down = this.down.bind(this);
674
- this.up = this.up.bind(this);
675
672
  this._boundDownToSinks = this._downToSinks.bind(this);
676
673
  }
674
+ /**
675
+ * Subclass hook invoked by `actions.down` / `actions.emit`. Default no-op;
676
+ * {@link NodeImpl} overrides to set `_manualEmitUsed`.
677
+ */
678
+ _onManualEmit() {
679
+ }
680
+ // --- Identity getters ---
677
681
  get name() {
678
682
  return this._registryName ?? this._optsName;
679
683
  }
680
- /**
681
- * When a node is registered with {@link Graph.add} without an options `name`,
682
- * the graph assigns the registry local name for introspection (parity with graphrefly-py).
683
- */
684
+ /** @internal Assigned by `Graph.add` when registered without an options `name`. */
684
685
  _assignRegistryName(localName) {
685
686
  if (this._optsName !== void 0 || this._registryName !== void 0) return;
686
687
  this._registryName = localName;
@@ -698,7 +699,10 @@ var NodeImpl = class {
698
699
  }
699
700
  };
700
701
  }
701
- // --- Public interface (Node<T>) ---
702
+ /** @internal Used by subclasses to surface inspector events. */
703
+ _emitInspectorHook(event) {
704
+ this._inspectorHook?.(event);
705
+ }
702
706
  get status() {
703
707
  return this._status;
704
708
  }
@@ -708,15 +712,7 @@ var NodeImpl = class {
708
712
  get v() {
709
713
  return this._versioning;
710
714
  }
711
- /**
712
- * Retroactively apply versioning to a node that was created without it.
713
- * No-op if versioning is already enabled.
714
- *
715
- * Version starts at 0 regardless of prior DATA emissions — it tracks
716
- * changes from the moment versioning is enabled, not historical ones.
717
- *
718
- * @internal — used by {@link Graph.setVersioning}.
719
- */
715
+ /** @internal Used by `Graph.setVersioning` to retroactively apply versioning. */
720
716
  _applyVersioning(level, opts) {
721
717
  if (this._versioning != null) return;
722
718
  this._hashFn = opts?.hash ?? this._hashFn;
@@ -736,6 +732,7 @@ var NodeImpl = class {
736
732
  if (this._guard == null) return true;
737
733
  return this._guard(normalizeActor(actor), "observe");
738
734
  }
735
+ // --- Public transport ---
739
736
  get() {
740
737
  return this._cached === NO_VALUE ? void 0 : this._cached;
741
738
  }
@@ -748,43 +745,25 @@ var NodeImpl = class {
748
745
  if (!this._guard(actor, action)) {
749
746
  throw new GuardDenied({ actor, action, nodeName: this.name });
750
747
  }
751
- this._lastMutation = { actor, timestamp_ns: wallClockNs() };
748
+ this._recordMutation(actor);
752
749
  }
753
750
  this._downInternal(messages);
754
751
  }
755
- _downInternal(messages) {
756
- if (messages.length === 0) return;
757
- let lifecycleMessages = messages;
758
- let sinkMessages = messages;
759
- if (this._terminal && !this._opts.resubscribable) {
760
- const terminalPassthrough = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
761
- if (terminalPassthrough.length === 0) return;
762
- lifecycleMessages = terminalPassthrough;
763
- sinkMessages = terminalPassthrough;
764
- }
765
- this._handleLocalLifecycle(lifecycleMessages);
766
- if (this._canSkipDirty()) {
767
- let hasPhase2 = false;
768
- for (let i = 0; i < sinkMessages.length; i++) {
769
- const t = sinkMessages[i][0];
770
- if (t === DATA || t === RESOLVED) {
771
- hasPhase2 = true;
772
- break;
773
- }
774
- }
775
- if (hasPhase2) {
776
- const filtered = [];
777
- for (let i = 0; i < sinkMessages.length; i++) {
778
- if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
779
- }
780
- if (filtered.length > 0) {
781
- downWithBatch(this._boundDownToSinks, filtered);
782
- }
783
- return;
784
- }
785
- }
786
- downWithBatch(this._boundDownToSinks, sinkMessages);
752
+ /** @internal Record a successful guarded mutation (called by `down` and subclass `up`). */
753
+ _recordMutation(actor) {
754
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
755
+ }
756
+ /**
757
+ * At-most-once deactivation guard. Both TEARDOWN (eager) and
758
+ * unsubscribe-body (lazy) call this. The `_active` flag ensures
759
+ * `_doDeactivate` runs exactly once per activation cycle.
760
+ */
761
+ _onDeactivate() {
762
+ if (!this._active) return;
763
+ this._active = false;
764
+ this._doDeactivate();
787
765
  }
766
+ // --- Subscribe (uniform across node shapes) ---
788
767
  subscribe(sink, hints) {
789
768
  if (hints?.actor != null && this._guard != null) {
790
769
  const actor = normalizeActor(hints.actor);
@@ -792,17 +771,21 @@ var NodeImpl = class {
792
771
  throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
793
772
  }
794
773
  }
795
- if (this._terminal && this._opts.resubscribable) {
774
+ if (this._terminal && this._resubscribable) {
796
775
  this._terminal = false;
797
776
  this._cached = NO_VALUE;
798
- this._status = this._hasDeps ? "disconnected" : "settled";
799
- this._opts.onResubscribe?.();
777
+ this._status = "disconnected";
778
+ this._onResubscribe?.();
800
779
  }
801
780
  this._sinkCount += 1;
802
781
  if (hints?.singleDep) {
803
782
  this._singleDepSinkCount += 1;
804
783
  this._singleDepSinks.add(sink);
805
784
  }
785
+ if (!this._terminal) {
786
+ const startMessages = this._cached === NO_VALUE ? [[START]] : [[START], [DATA, this._cached]];
787
+ downWithBatch(sink, startMessages);
788
+ }
806
789
  if (this._sinks == null) {
807
790
  this._sinks = sink;
808
791
  } else if (typeof this._sinks === "function") {
@@ -810,10 +793,12 @@ var NodeImpl = class {
810
793
  } else {
811
794
  this._sinks.add(sink);
812
795
  }
813
- if (this._hasDeps) {
814
- this._connectUpstream();
815
- } else if (this._fn) {
816
- this._startProducer();
796
+ if (this._sinkCount === 1 && !this._terminal) {
797
+ this._active = true;
798
+ this._onActivate();
799
+ }
800
+ if (!this._terminal && this._status === "disconnected" && this._cached === NO_VALUE) {
801
+ this._status = "pending";
817
802
  }
818
803
  let removed = false;
819
804
  return () => {
@@ -837,39 +822,49 @@ var NodeImpl = class {
837
822
  }
838
823
  }
839
824
  if (this._sinks == null) {
840
- this._disconnectUpstream();
841
- this._stopProducer();
825
+ this._onDeactivate();
842
826
  }
843
827
  };
844
828
  }
845
- up(messages, options) {
846
- if (!this._hasDeps) return;
847
- if (!options?.internal && this._guard != null) {
848
- const actor = normalizeActor(options?.actor);
849
- if (!this._guard(actor, "write")) {
850
- throw new GuardDenied({ actor, action: "write", nodeName: this.name });
851
- }
852
- this._lastMutation = { actor, timestamp_ns: wallClockNs() };
829
+ // --- Down pipeline ---
830
+ /**
831
+ * Core outgoing dispatch. Applies terminal filter + local lifecycle
832
+ * update, then hands messages to `downWithBatch` for tier-aware delivery.
833
+ */
834
+ _downInternal(messages) {
835
+ if (messages.length === 0) return;
836
+ let sinkMessages = messages;
837
+ if (this._terminal && !this._resubscribable) {
838
+ const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
839
+ if (pass.length === 0) return;
840
+ sinkMessages = pass;
853
841
  }
854
- for (const dep of this._deps) {
855
- if (options === void 0) {
856
- dep.up?.(messages);
857
- } else {
858
- dep.up?.(messages, options);
842
+ this._handleLocalLifecycle(sinkMessages);
843
+ if (this._canSkipDirty()) {
844
+ let hasPhase2 = false;
845
+ for (let i = 0; i < sinkMessages.length; i++) {
846
+ const t = sinkMessages[i][0];
847
+ if (t === DATA || t === RESOLVED) {
848
+ hasPhase2 = true;
849
+ break;
850
+ }
851
+ }
852
+ if (hasPhase2) {
853
+ const filtered = [];
854
+ for (let i = 0; i < sinkMessages.length; i++) {
855
+ if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
856
+ }
857
+ if (filtered.length > 0) {
858
+ downWithBatch(this._boundDownToSinks, filtered);
859
+ }
860
+ return;
859
861
  }
860
862
  }
863
+ downWithBatch(this._boundDownToSinks, sinkMessages);
861
864
  }
862
- _upInternal(messages) {
863
- if (!this._hasDeps) return;
864
- for (const dep of this._deps) {
865
- dep.up?.(messages, { internal: true });
866
- }
867
- }
868
- unsubscribe() {
869
- if (!this._hasDeps) return;
870
- this._disconnectUpstream();
865
+ _canSkipDirty() {
866
+ return this._sinkCount === 1 && this._singleDepSinkCount === 1;
871
867
  }
872
- // --- Private methods (prototype, _ prefix) ---
873
868
  _downToSinks(messages) {
874
869
  if (this._sinks == null) return;
875
870
  if (typeof this._sinks === "function") {
@@ -881,6 +876,11 @@ var NodeImpl = class {
881
876
  sink(messages);
882
877
  }
883
878
  }
879
+ /**
880
+ * Update `_cached`, `_status`, `_terminal` from message batch before
881
+ * delivery. Subclass hooks `_onInvalidate` / `_onTeardown` let
882
+ * {@link NodeImpl} clear `_lastDepValues` and invoke cleanup fns.
883
+ */
884
884
  _handleLocalLifecycle(messages) {
885
885
  for (const m of messages) {
886
886
  const t = m[0];
@@ -894,28 +894,22 @@ var NodeImpl = class {
894
894
  }
895
895
  }
896
896
  if (t === INVALIDATE) {
897
- const cleanupFn = this._cleanup;
898
- this._cleanup = void 0;
899
- cleanupFn?.();
897
+ this._onInvalidate();
900
898
  this._cached = NO_VALUE;
901
- this._lastDepValues = void 0;
902
899
  }
903
900
  this._status = statusAfterMessage(this._status, m);
904
901
  if (t === COMPLETE || t === ERROR) {
905
902
  this._terminal = true;
906
903
  }
907
904
  if (t === TEARDOWN) {
908
- if (this._opts.resetOnTeardown) {
905
+ if (this._resetOnTeardown) {
909
906
  this._cached = NO_VALUE;
910
907
  }
911
- const teardownCleanup = this._cleanup;
912
- this._cleanup = void 0;
913
- teardownCleanup?.();
908
+ this._onTeardown();
914
909
  try {
915
910
  this._propagateToMeta(t);
916
911
  } finally {
917
- this._disconnectUpstream();
918
- this._stopProducer();
912
+ this._onDeactivate();
919
913
  }
920
914
  }
921
915
  if (t !== TEARDOWN && propagatesToMeta(t)) {
@@ -923,7 +917,20 @@ var NodeImpl = class {
923
917
  }
924
918
  }
925
919
  }
926
- /** Propagate a signal to all companion meta nodes (best-effort). */
920
+ /**
921
+ * Subclass hook: invoked when INVALIDATE arrives (before `_cached` is
922
+ * cleared). {@link NodeImpl} uses this to run the fn cleanup fn and
923
+ * drop `_lastDepValues` so the next wave re-runs fn.
924
+ */
925
+ _onInvalidate() {
926
+ }
927
+ /**
928
+ * Subclass hook: invoked when TEARDOWN arrives, before `_onDeactivate`.
929
+ * {@link NodeImpl} uses this to run any pending cleanup fn.
930
+ */
931
+ _onTeardown() {
932
+ }
933
+ /** Forward a signal to all companion meta nodes (best-effort). */
927
934
  _propagateToMeta(t) {
928
935
  for (const metaNode of Object.values(this.meta)) {
929
936
  try {
@@ -932,9 +939,10 @@ var NodeImpl = class {
932
939
  }
933
940
  }
934
941
  }
935
- _canSkipDirty() {
936
- return this._sinkCount === 1 && this._singleDepSinkCount === 1;
937
- }
942
+ /**
943
+ * Frame a computed value into the right protocol messages and dispatch
944
+ * via `_downInternal`. Used by `_runFn` and `actions.emit`.
945
+ */
938
946
  _downAutoValue(value) {
939
947
  const wasDirty = this._status === "dirty";
940
948
  let unchanged;
@@ -942,7 +950,9 @@ var NodeImpl = class {
942
950
  unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
943
951
  } catch (eqErr) {
944
952
  const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
945
- const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
953
+ const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
954
+ cause: eqErr
955
+ });
946
956
  this._downInternal([[ERROR, wrapped]]);
947
957
  return;
948
958
  }
@@ -952,89 +962,173 @@ var NodeImpl = class {
952
962
  }
953
963
  this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
954
964
  }
955
- _runFn() {
956
- if (!this._fn) return;
957
- if (this._terminal && !this._opts.resubscribable) return;
958
- if (this._connecting) return;
959
- try {
960
- const n = this._deps.length;
961
- const depValues = new Array(n);
962
- for (let i = 0; i < n; i++) depValues[i] = this._deps[i].get();
963
- const prev = this._lastDepValues;
964
- if (n > 0 && prev != null && prev.length === n) {
965
- let allSame = true;
966
- for (let i = 0; i < n; i++) {
967
- if (!Object.is(depValues[i], prev[i])) {
968
- allSame = false;
969
- break;
970
- }
971
- }
972
- if (allSame) {
973
- if (this._status === "dirty") {
974
- this._downInternal([[RESOLVED]]);
975
- }
976
- return;
977
- }
978
- }
979
- const prevCleanup = this._cleanup;
980
- this._cleanup = void 0;
981
- prevCleanup?.();
982
- this._manualEmitUsed = false;
983
- this._lastDepValues = depValues;
984
- this._inspectorHook?.({ kind: "run", depValues });
985
- const out = this._fn(depValues, this._actions);
986
- if (isCleanupResult(out)) {
987
- this._cleanup = out.cleanup;
988
- if (this._manualEmitUsed) return;
989
- if ("value" in out) {
990
- this._downAutoValue(out.value);
991
- }
992
- return;
993
- }
994
- if (isCleanupFn(out)) {
995
- this._cleanup = out;
996
- return;
997
- }
998
- if (this._manualEmitUsed) return;
999
- if (out === void 0) return;
1000
- this._downAutoValue(out);
1001
- } catch (err) {
1002
- const errMsg = err instanceof Error ? err.message : String(err);
1003
- const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
1004
- this._downInternal([[ERROR, wrapped]]);
965
+ };
966
+
967
+ // src/core/node.ts
968
+ var NodeImpl = class extends NodeBase {
969
+ // --- Dep configuration (set once) ---
970
+ _deps;
971
+ _fn;
972
+ _opts;
973
+ _hasDeps;
974
+ _isSingleDep;
975
+ _autoComplete;
976
+ // --- Wave tracking masks ---
977
+ _depDirtyMask;
978
+ _depSettledMask;
979
+ _depCompleteMask;
980
+ _allDepsCompleteMask;
981
+ // --- Identity-skip optimization ---
982
+ _lastDepValues;
983
+ _cleanup;
984
+ // --- Upstream bookkeeping ---
985
+ _upstreamUnsubs = [];
986
+ // --- Fn behavior flag ---
987
+ /** @internal Read by `describeNode` to infer `"operator"` label. */
988
+ _manualEmitUsed = false;
989
+ constructor(deps, fn, opts) {
990
+ super(opts);
991
+ this._deps = deps;
992
+ this._fn = fn;
993
+ this._opts = opts;
994
+ this._hasDeps = deps.length > 0;
995
+ this._isSingleDep = deps.length === 1 && fn != null;
996
+ this._autoComplete = opts.completeWhenDepsComplete ?? true;
997
+ if (!this._hasDeps && fn == null && this._cached !== NO_VALUE) {
998
+ this._status = "settled";
1005
999
  }
1006
- }
1007
- _onDepDirty(index) {
1008
- const wasDirty = this._depDirtyMask.has(index);
1009
- this._depDirtyMask.set(index);
1010
- this._depSettledMask.clear(index);
1011
- if (!wasDirty) {
1012
- this._downInternal([[DIRTY]]);
1000
+ this._depDirtyMask = createBitSet(deps.length);
1001
+ this._depSettledMask = createBitSet(deps.length);
1002
+ this._depCompleteMask = createBitSet(deps.length);
1003
+ this._allDepsCompleteMask = createBitSet(deps.length);
1004
+ for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
1005
+ this.down = this.down.bind(this);
1006
+ this.up = this.up.bind(this);
1007
+ }
1008
+ // --- Meta node factory (called from base constructor) ---
1009
+ _createMetaNode(key, initialValue, opts) {
1010
+ return node({
1011
+ initial: initialValue,
1012
+ name: `${opts.name ?? "node"}:meta:${key}`,
1013
+ describeKind: "state",
1014
+ ...opts.guard != null ? { guard: opts.guard } : {}
1015
+ });
1016
+ }
1017
+ // --- Manual emit tracker (set by actions.down / actions.emit) ---
1018
+ _onManualEmit() {
1019
+ this._manualEmitUsed = true;
1020
+ }
1021
+ // --- Up / unsubscribe ---
1022
+ up(messages, options) {
1023
+ if (!this._hasDeps) return;
1024
+ if (!options?.internal && this._guard != null) {
1025
+ const actor = normalizeActor(options?.actor);
1026
+ if (!this._guard(actor, "write")) {
1027
+ throw new GuardDenied({ actor, action: "write", nodeName: this.name });
1028
+ }
1029
+ this._recordMutation(actor);
1030
+ }
1031
+ for (const dep of this._deps) {
1032
+ if (options === void 0) {
1033
+ dep.up?.(messages);
1034
+ } else {
1035
+ dep.up?.(messages, options);
1036
+ }
1013
1037
  }
1014
1038
  }
1015
- _onDepSettled(index) {
1016
- if (!this._depDirtyMask.has(index)) {
1017
- this._onDepDirty(index);
1039
+ _upInternal(messages) {
1040
+ if (!this._hasDeps) return;
1041
+ for (const dep of this._deps) {
1042
+ dep.up?.(messages, { internal: true });
1018
1043
  }
1019
- this._depSettledMask.set(index);
1020
- if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
1021
- this._depDirtyMask.reset();
1022
- this._depSettledMask.reset();
1044
+ }
1045
+ unsubscribe() {
1046
+ if (!this._hasDeps) return;
1047
+ this._disconnectUpstream();
1048
+ }
1049
+ // --- Activation (first-subscriber / last-subscriber hooks) ---
1050
+ _onActivate() {
1051
+ if (this._hasDeps) {
1052
+ this._connectUpstream();
1053
+ return;
1054
+ }
1055
+ if (this._fn) {
1023
1056
  this._runFn();
1057
+ return;
1024
1058
  }
1025
1059
  }
1026
- _maybeCompleteFromDeps() {
1027
- if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
1028
- this._downInternal([[COMPLETE]]);
1060
+ _doDeactivate() {
1061
+ this._disconnectUpstream();
1062
+ const cleanup = this._cleanup;
1063
+ this._cleanup = void 0;
1064
+ cleanup?.();
1065
+ if (this._fn != null) {
1066
+ this._cached = NO_VALUE;
1067
+ this._lastDepValues = void 0;
1068
+ }
1069
+ if (this._hasDeps || this._fn != null) {
1070
+ this._status = "disconnected";
1071
+ }
1072
+ }
1073
+ // --- INVALIDATE / TEARDOWN hooks (clear fn state) ---
1074
+ _onInvalidate() {
1075
+ const cleanup = this._cleanup;
1076
+ this._cleanup = void 0;
1077
+ cleanup?.();
1078
+ this._lastDepValues = void 0;
1079
+ }
1080
+ _onTeardown() {
1081
+ const cleanup = this._cleanup;
1082
+ this._cleanup = void 0;
1083
+ cleanup?.();
1084
+ }
1085
+ // --- Upstream connect / disconnect ---
1086
+ _connectUpstream() {
1087
+ if (!this._hasDeps) return;
1088
+ if (this._upstreamUnsubs.length > 0) return;
1089
+ this._depDirtyMask.setAll();
1090
+ this._depSettledMask.reset();
1091
+ this._depCompleteMask.reset();
1092
+ const depValuesBefore = this._lastDepValues;
1093
+ const subHints = this._isSingleDep ? { singleDep: true } : void 0;
1094
+ for (let i = 0; i < this._deps.length; i += 1) {
1095
+ const dep = this._deps[i];
1096
+ this._upstreamUnsubs.push(
1097
+ dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
1098
+ );
1099
+ }
1100
+ if (this._fn && this._onMessage && !this._terminal && this._lastDepValues === depValuesBefore) {
1101
+ this._runFn();
1029
1102
  }
1030
1103
  }
1104
+ _disconnectUpstream() {
1105
+ if (this._upstreamUnsubs.length === 0) return;
1106
+ for (const unsub of this._upstreamUnsubs.splice(0)) {
1107
+ unsub();
1108
+ }
1109
+ this._depDirtyMask.reset();
1110
+ this._depSettledMask.reset();
1111
+ this._depCompleteMask.reset();
1112
+ }
1113
+ // --- Wave handling ---
1031
1114
  _handleDepMessages(index, messages) {
1032
1115
  for (const msg of messages) {
1033
- this._inspectorHook?.({ kind: "dep_message", depIndex: index, message: msg });
1116
+ this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
1034
1117
  const t = msg[0];
1035
1118
  if (this._onMessage) {
1036
1119
  try {
1037
- if (this._onMessage(msg, index, this._actions)) continue;
1120
+ const consumed = this._onMessage(msg, index, this._actions);
1121
+ if (consumed) {
1122
+ if (t === START) {
1123
+ this._depDirtyMask.clear(index);
1124
+ if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
1125
+ this._depDirtyMask.reset();
1126
+ this._depSettledMask.reset();
1127
+ this._runFn();
1128
+ }
1129
+ }
1130
+ continue;
1131
+ }
1038
1132
  } catch (err) {
1039
1133
  const errMsg = err instanceof Error ? err.message : String(err);
1040
1134
  const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
@@ -1044,6 +1138,7 @@ var NodeImpl = class {
1044
1138
  return;
1045
1139
  }
1046
1140
  }
1141
+ if (messageTier(t) < 1) continue;
1047
1142
  if (!this._fn) {
1048
1143
  if (t === COMPLETE && this._deps.length > 1) {
1049
1144
  this._depCompleteMask.set(index);
@@ -1087,53 +1182,85 @@ var NodeImpl = class {
1087
1182
  this._downInternal([msg]);
1088
1183
  }
1089
1184
  }
1090
- _connectUpstream() {
1091
- if (!this._hasDeps || this._connected) return;
1092
- this._connected = true;
1093
- this._depDirtyMask.reset();
1094
- this._depSettledMask.reset();
1095
- this._depCompleteMask.reset();
1096
- this._status = "settled";
1097
- const subHints = this._isSingleDep ? { singleDep: true } : void 0;
1098
- this._connecting = true;
1099
- try {
1100
- for (let i = 0; i < this._deps.length; i += 1) {
1101
- const dep = this._deps[i];
1102
- this._upstreamUnsubs.push(
1103
- dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
1104
- );
1105
- }
1106
- } finally {
1107
- this._connecting = false;
1185
+ _onDepDirty(index) {
1186
+ const wasDirty = this._depDirtyMask.has(index);
1187
+ this._depDirtyMask.set(index);
1188
+ this._depSettledMask.clear(index);
1189
+ if (!wasDirty) {
1190
+ this._downInternal([[DIRTY]]);
1108
1191
  }
1109
- if (this._fn) {
1192
+ }
1193
+ _onDepSettled(index) {
1194
+ if (!this._depDirtyMask.has(index)) {
1195
+ this._onDepDirty(index);
1196
+ }
1197
+ this._depSettledMask.set(index);
1198
+ if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
1199
+ this._depDirtyMask.reset();
1200
+ this._depSettledMask.reset();
1110
1201
  this._runFn();
1111
1202
  }
1112
1203
  }
1113
- _stopProducer() {
1114
- if (!this._producerStarted) return;
1115
- this._producerStarted = false;
1116
- const producerCleanup = this._cleanup;
1117
- this._cleanup = void 0;
1118
- producerCleanup?.();
1119
- }
1120
- _startProducer() {
1121
- if (this._deps.length !== 0 || !this._fn || this._producerStarted) return;
1122
- this._producerStarted = true;
1123
- this._runFn();
1204
+ _maybeCompleteFromDeps() {
1205
+ if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
1206
+ this._downInternal([[COMPLETE]]);
1207
+ }
1124
1208
  }
1125
- _disconnectUpstream() {
1126
- if (!this._connected) return;
1127
- for (const unsub of this._upstreamUnsubs.splice(0)) {
1128
- unsub();
1209
+ // --- Fn execution ---
1210
+ _runFn() {
1211
+ if (!this._fn) return;
1212
+ if (this._terminal && !this._resubscribable) return;
1213
+ try {
1214
+ const n = this._deps.length;
1215
+ const depValues = new Array(n);
1216
+ for (let i = 0; i < n; i++) depValues[i] = this._deps[i].get();
1217
+ const prev = this._lastDepValues;
1218
+ if (n > 0 && prev != null && prev.length === n) {
1219
+ let allSame = true;
1220
+ for (let i = 0; i < n; i++) {
1221
+ if (!Object.is(depValues[i], prev[i])) {
1222
+ allSame = false;
1223
+ break;
1224
+ }
1225
+ }
1226
+ if (allSame) {
1227
+ if (this._status === "dirty") {
1228
+ this._downInternal([[RESOLVED]]);
1229
+ }
1230
+ return;
1231
+ }
1232
+ }
1233
+ const prevCleanup = this._cleanup;
1234
+ this._cleanup = void 0;
1235
+ prevCleanup?.();
1236
+ this._manualEmitUsed = false;
1237
+ this._lastDepValues = depValues;
1238
+ this._emitInspectorHook({ kind: "run", depValues });
1239
+ const out = this._fn(depValues, this._actions);
1240
+ if (isCleanupResult(out)) {
1241
+ this._cleanup = out.cleanup;
1242
+ if (this._manualEmitUsed) return;
1243
+ if ("value" in out) {
1244
+ this._downAutoValue(out.value);
1245
+ }
1246
+ return;
1247
+ }
1248
+ if (isCleanupFn(out)) {
1249
+ this._cleanup = out;
1250
+ return;
1251
+ }
1252
+ if (this._manualEmitUsed) return;
1253
+ if (out === void 0) return;
1254
+ this._downAutoValue(out);
1255
+ } catch (err) {
1256
+ const errMsg = err instanceof Error ? err.message : String(err);
1257
+ const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
1258
+ this._downInternal([[ERROR, wrapped]]);
1129
1259
  }
1130
- this._connected = false;
1131
- this._depDirtyMask.reset();
1132
- this._depSettledMask.reset();
1133
- this._depCompleteMask.reset();
1134
- this._status = "disconnected";
1135
1260
  }
1136
1261
  };
1262
+ var isNodeArray = (value) => Array.isArray(value);
1263
+ var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
1137
1264
  function node(depsOrFn, fnOrOpts, optsArg) {
1138
1265
  const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
1139
1266
  const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
@@ -2277,6 +2404,37 @@ function firstValueFrom(source) {
2277
2404
  });
2278
2405
  });
2279
2406
  }
2407
+ function firstWhere(source, predicate) {
2408
+ return new Promise((resolve, reject) => {
2409
+ let settled = false;
2410
+ const unsub = source.subscribe((msgs) => {
2411
+ for (const m of msgs) {
2412
+ if (settled) return;
2413
+ if (m[0] === DATA) {
2414
+ const v = m[1];
2415
+ if (predicate(v)) {
2416
+ settled = true;
2417
+ resolve(v);
2418
+ queueMicrotask(() => unsub());
2419
+ return;
2420
+ }
2421
+ }
2422
+ if (m[0] === ERROR) {
2423
+ settled = true;
2424
+ reject(m[1]);
2425
+ queueMicrotask(() => unsub());
2426
+ return;
2427
+ }
2428
+ if (m[0] === COMPLETE) {
2429
+ settled = true;
2430
+ reject(new Error("completed without matching value"));
2431
+ queueMicrotask(() => unsub());
2432
+ return;
2433
+ }
2434
+ }
2435
+ });
2436
+ });
2437
+ }
2280
2438
  var shareReplay = replay;
2281
2439
 
2282
2440
  // src/extra/adapters.ts
@@ -2519,6 +2677,10 @@ function toSSE(source, opts) {
2519
2677
  unsub = source.subscribe((msgs) => {
2520
2678
  for (const msg of msgs) {
2521
2679
  const t = msg[0];
2680
+ if (isLocalOnly(t)) {
2681
+ if (t === DIRTY && includeDirty) {
2682
+ } else continue;
2683
+ }
2522
2684
  if (t === DATA) {
2523
2685
  write(dataEvent, serializeSseData(msg[1], serialize));
2524
2686
  continue;
@@ -2534,7 +2696,6 @@ function toSSE(source, opts) {
2534
2696
  return;
2535
2697
  }
2536
2698
  if (!includeResolved && t === RESOLVED) continue;
2537
- if (!includeDirty && t === DIRTY) continue;
2538
2699
  write(
2539
2700
  eventNameResolver(t),
2540
2701
  msg.length > 1 ? serializeSseData(msg[1], serialize) : void 0
@@ -5121,230 +5282,47 @@ function restoreGraphCheckpointIndexedDb(graph, spec) {
5121
5282
  }
5122
5283
 
5123
5284
  // src/core/dynamic-node.ts
5285
+ var MAX_RERUN = 16;
5124
5286
  function dynamicNode(fn, opts) {
5125
5287
  return new DynamicNodeImpl(fn, opts ?? {});
5126
5288
  }
5127
- var DynamicNodeImpl = class {
5128
- _optsName;
5129
- _registryName;
5130
- _describeKind;
5131
- meta;
5289
+ var DynamicNodeImpl = class extends NodeBase {
5132
5290
  _fn;
5133
- _equals;
5134
- _resubscribable;
5135
- _resetOnTeardown;
5136
5291
  _autoComplete;
5137
- _onMessage;
5138
- _onResubscribe;
5139
- /** @internal — read by {@link describeNode} for `accessHintForGuard`. */
5140
- _guard;
5141
- _lastMutation;
5142
- _inspectorHook;
5143
- // Sink tracking
5144
- _sinkCount = 0;
5145
- _singleDepSinkCount = 0;
5146
- _singleDepSinks = /* @__PURE__ */ new WeakSet();
5147
- // Actions object (for onMessage handler)
5148
- _actions;
5149
- _boundDownToSinks;
5150
- // Mutable state
5151
- _cached = NO_VALUE;
5152
- _status = "disconnected";
5153
- _terminal = false;
5154
- _connected = false;
5155
- _rewiring = false;
5156
- // re-entrancy guard
5157
5292
  // Dynamic deps tracking
5293
+ /** @internal Read by `describeNode`. */
5158
5294
  _deps = [];
5159
5295
  _depUnsubs = [];
5160
5296
  _depIndexMap = /* @__PURE__ */ new Map();
5161
- // node index in _deps
5162
- _dirtyBits = /* @__PURE__ */ new Set();
5163
- _settledBits = /* @__PURE__ */ new Set();
5164
- _completeBits = /* @__PURE__ */ new Set();
5165
- // Sinks
5166
- _sinks = null;
5297
+ _depDirtyBits = /* @__PURE__ */ new Set();
5298
+ _depSettledBits = /* @__PURE__ */ new Set();
5299
+ _depCompleteBits = /* @__PURE__ */ new Set();
5300
+ // Execution state
5301
+ _running = false;
5302
+ _rewiring = false;
5303
+ _bufferedDepMessages = [];
5304
+ _trackedValues = /* @__PURE__ */ new Map();
5305
+ _rerunCount = 0;
5167
5306
  constructor(fn, opts) {
5307
+ super(opts);
5168
5308
  this._fn = fn;
5169
- this._optsName = opts.name;
5170
- this._describeKind = opts.describeKind;
5171
- this._equals = opts.equals ?? Object.is;
5172
- this._resubscribable = opts.resubscribable ?? false;
5173
- this._resetOnTeardown = opts.resetOnTeardown ?? false;
5174
5309
  this._autoComplete = opts.completeWhenDepsComplete ?? true;
5175
- this._onMessage = opts.onMessage;
5176
- this._onResubscribe = opts.onResubscribe;
5177
- this._guard = opts.guard;
5178
- this._inspectorHook = void 0;
5179
- const meta = {};
5180
- for (const [k, v] of Object.entries(opts.meta ?? {})) {
5181
- meta[k] = node({
5182
- initial: v,
5183
- name: `${opts.name ?? "dynamicNode"}:meta:${k}`,
5184
- describeKind: "state",
5185
- ...opts.guard != null ? { guard: opts.guard } : {}
5186
- });
5187
- }
5188
- Object.freeze(meta);
5189
- this.meta = meta;
5190
- const self = this;
5191
- this._actions = {
5192
- down(messages) {
5193
- self._downInternal(messages);
5194
- },
5195
- emit(value) {
5196
- self._downAutoValue(value);
5197
- },
5198
- up(messages) {
5199
- for (const dep of self._deps) {
5200
- dep.up?.(messages, { internal: true });
5201
- }
5202
- }
5203
- };
5204
- this._boundDownToSinks = this._downToSinks.bind(this);
5205
- }
5206
- get name() {
5207
- return this._registryName ?? this._optsName;
5310
+ this.down = this.down.bind(this);
5311
+ this.up = this.up.bind(this);
5208
5312
  }
5209
- /** @internal */
5210
- _assignRegistryName(localName) {
5211
- if (this._optsName !== void 0 || this._registryName !== void 0) return;
5212
- this._registryName = localName;
5313
+ _createMetaNode(key, initialValue, opts) {
5314
+ return node({
5315
+ initial: initialValue,
5316
+ name: `${opts.name ?? "dynamicNode"}:meta:${key}`,
5317
+ describeKind: "state",
5318
+ ...opts.guard != null ? { guard: opts.guard } : {}
5319
+ });
5213
5320
  }
5214
- /**
5215
- * @internal Attach/remove inspector hook for graph-level observability.
5216
- * Returns a disposer that restores the previous hook.
5217
- */
5218
- _setInspectorHook(hook) {
5219
- const prev = this._inspectorHook;
5220
- this._inspectorHook = hook;
5221
- return () => {
5222
- if (this._inspectorHook === hook) {
5223
- this._inspectorHook = prev;
5224
- }
5225
- };
5226
- }
5227
- get status() {
5228
- return this._status;
5229
- }
5230
- get lastMutation() {
5231
- return this._lastMutation;
5232
- }
5233
- /** Versioning not yet supported on DynamicNodeImpl. */
5321
+ /** Versioning not supported on DynamicNodeImpl (override base). */
5234
5322
  get v() {
5235
5323
  return void 0;
5236
5324
  }
5237
- hasGuard() {
5238
- return this._guard != null;
5239
- }
5240
- allowsObserve(actor) {
5241
- if (this._guard == null) return true;
5242
- return this._guard(normalizeActor(actor), "observe");
5243
- }
5244
- get() {
5245
- return this._cached === NO_VALUE ? void 0 : this._cached;
5246
- }
5247
- down(messages, options) {
5248
- if (messages.length === 0) return;
5249
- if (!options?.internal && this._guard != null) {
5250
- const actor = normalizeActor(options?.actor);
5251
- const delivery = options?.delivery ?? "write";
5252
- const action = delivery === "signal" ? "signal" : "write";
5253
- if (!this._guard(actor, action)) {
5254
- throw new GuardDenied({ actor, action, nodeName: this.name });
5255
- }
5256
- this._lastMutation = { actor, timestamp_ns: wallClockNs() };
5257
- }
5258
- this._downInternal(messages);
5259
- }
5260
- _downInternal(messages) {
5261
- if (messages.length === 0) return;
5262
- let sinkMessages = messages;
5263
- if (this._terminal && !this._resubscribable) {
5264
- const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
5265
- if (pass.length === 0) return;
5266
- sinkMessages = pass;
5267
- }
5268
- this._handleLocalLifecycle(sinkMessages);
5269
- if (this._canSkipDirty()) {
5270
- let hasPhase2 = false;
5271
- for (let i = 0; i < sinkMessages.length; i++) {
5272
- const t = sinkMessages[i][0];
5273
- if (t === DATA || t === RESOLVED) {
5274
- hasPhase2 = true;
5275
- break;
5276
- }
5277
- }
5278
- if (hasPhase2) {
5279
- const filtered = [];
5280
- for (let i = 0; i < sinkMessages.length; i++) {
5281
- if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
5282
- }
5283
- if (filtered.length > 0) {
5284
- downWithBatch(this._boundDownToSinks, filtered);
5285
- }
5286
- return;
5287
- }
5288
- }
5289
- downWithBatch(this._boundDownToSinks, sinkMessages);
5290
- }
5291
- _canSkipDirty() {
5292
- return this._sinkCount === 1 && this._singleDepSinkCount === 1;
5293
- }
5294
- subscribe(sink, hints) {
5295
- if (hints?.actor != null && this._guard != null) {
5296
- const actor = normalizeActor(hints.actor);
5297
- if (!this._guard(actor, "observe")) {
5298
- throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
5299
- }
5300
- }
5301
- if (this._terminal && this._resubscribable) {
5302
- this._terminal = false;
5303
- this._cached = NO_VALUE;
5304
- this._status = "disconnected";
5305
- this._onResubscribe?.();
5306
- }
5307
- this._sinkCount += 1;
5308
- if (hints?.singleDep) {
5309
- this._singleDepSinkCount += 1;
5310
- this._singleDepSinks.add(sink);
5311
- }
5312
- if (this._sinks == null) {
5313
- this._sinks = sink;
5314
- } else if (typeof this._sinks === "function") {
5315
- this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
5316
- } else {
5317
- this._sinks.add(sink);
5318
- }
5319
- if (!this._connected) {
5320
- this._connect();
5321
- }
5322
- let removed = false;
5323
- return () => {
5324
- if (removed) return;
5325
- removed = true;
5326
- this._sinkCount -= 1;
5327
- if (this._singleDepSinks.has(sink)) {
5328
- this._singleDepSinkCount -= 1;
5329
- this._singleDepSinks.delete(sink);
5330
- }
5331
- if (this._sinks == null) return;
5332
- if (typeof this._sinks === "function") {
5333
- if (this._sinks === sink) this._sinks = null;
5334
- } else {
5335
- this._sinks.delete(sink);
5336
- if (this._sinks.size === 1) {
5337
- const [only] = this._sinks;
5338
- this._sinks = only;
5339
- } else if (this._sinks.size === 0) {
5340
- this._sinks = null;
5341
- }
5342
- }
5343
- if (this._sinks == null) {
5344
- this._disconnect();
5345
- }
5346
- };
5347
- }
5325
+ // --- Up / unsubscribe ---
5348
5326
  up(messages, options) {
5349
5327
  if (this._deps.length === 0) return;
5350
5328
  if (!options?.internal && this._guard != null) {
@@ -5352,221 +5330,227 @@ var DynamicNodeImpl = class {
5352
5330
  if (!this._guard(actor, "write")) {
5353
5331
  throw new GuardDenied({ actor, action: "write", nodeName: this.name });
5354
5332
  }
5355
- this._lastMutation = { actor, timestamp_ns: wallClockNs() };
5333
+ this._recordMutation(actor);
5356
5334
  }
5357
5335
  for (const dep of this._deps) {
5358
5336
  dep.up?.(messages, options);
5359
5337
  }
5360
5338
  }
5361
- unsubscribe() {
5362
- this._disconnect();
5363
- }
5364
- // --- Private methods ---
5365
- _downToSinks(messages) {
5366
- if (this._sinks == null) return;
5367
- if (typeof this._sinks === "function") {
5368
- this._sinks(messages);
5369
- return;
5370
- }
5371
- const snapshot = [...this._sinks];
5372
- for (const sink of snapshot) {
5373
- sink(messages);
5374
- }
5375
- }
5376
- _handleLocalLifecycle(messages) {
5377
- for (const m of messages) {
5378
- const t = m[0];
5379
- if (t === DATA) this._cached = m[1];
5380
- if (t === INVALIDATE) {
5381
- this._cached = NO_VALUE;
5382
- this._status = "dirty";
5383
- }
5384
- if (t === DATA) {
5385
- this._status = "settled";
5386
- } else if (t === RESOLVED) {
5387
- this._status = "resolved";
5388
- } else if (t === DIRTY) {
5389
- this._status = "dirty";
5390
- } else if (t === COMPLETE) {
5391
- this._status = "completed";
5392
- this._terminal = true;
5393
- } else if (t === ERROR) {
5394
- this._status = "errored";
5395
- this._terminal = true;
5396
- }
5397
- if (t === TEARDOWN) {
5398
- if (this._resetOnTeardown) this._cached = NO_VALUE;
5399
- try {
5400
- this._propagateToMeta(t);
5401
- } finally {
5402
- this._disconnect();
5403
- }
5404
- }
5405
- if (t !== TEARDOWN && propagatesToMeta(t)) {
5406
- this._propagateToMeta(t);
5407
- }
5408
- }
5409
- }
5410
- /** Propagate a signal to all companion meta nodes (best-effort). */
5411
- _propagateToMeta(t) {
5412
- for (const metaNode of Object.values(this.meta)) {
5413
- try {
5414
- metaNode.down([[t]], { internal: true });
5415
- } catch {
5416
- }
5339
+ _upInternal(messages) {
5340
+ for (const dep of this._deps) {
5341
+ dep.up?.(messages, { internal: true });
5417
5342
  }
5418
5343
  }
5419
- _downAutoValue(value) {
5420
- const wasDirty = this._status === "dirty";
5421
- let unchanged;
5422
- try {
5423
- unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
5424
- } catch (eqErr) {
5425
- const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
5426
- const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
5427
- this._downInternal([[ERROR, wrapped]]);
5428
- return;
5429
- }
5430
- if (unchanged) {
5431
- this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
5432
- return;
5433
- }
5434
- this._cached = value;
5435
- this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
5344
+ unsubscribe() {
5345
+ this._disconnect();
5436
5346
  }
5437
- _connect() {
5438
- if (this._connected) return;
5439
- this._connected = true;
5440
- this._status = "settled";
5441
- this._dirtyBits.clear();
5442
- this._settledBits.clear();
5443
- this._completeBits.clear();
5347
+ // --- Activation hooks ---
5348
+ _onActivate() {
5444
5349
  this._runFn();
5445
5350
  }
5351
+ _doDeactivate() {
5352
+ this._disconnect();
5353
+ }
5446
5354
  _disconnect() {
5447
- if (!this._connected) return;
5448
5355
  for (const unsub of this._depUnsubs) unsub();
5449
5356
  this._depUnsubs = [];
5450
5357
  this._deps = [];
5451
5358
  this._depIndexMap.clear();
5452
- this._dirtyBits.clear();
5453
- this._settledBits.clear();
5454
- this._completeBits.clear();
5455
- this._connected = false;
5359
+ this._depDirtyBits.clear();
5360
+ this._depSettledBits.clear();
5361
+ this._depCompleteBits.clear();
5362
+ this._cached = NO_VALUE;
5456
5363
  this._status = "disconnected";
5457
5364
  }
5365
+ // --- Fn execution with rewire buffer ---
5458
5366
  _runFn() {
5459
5367
  if (this._terminal && !this._resubscribable) return;
5460
- if (this._rewiring) return;
5461
- const trackedDeps = [];
5462
- const trackedSet = /* @__PURE__ */ new Set();
5463
- const get = (dep) => {
5464
- if (!trackedSet.has(dep)) {
5465
- trackedSet.add(dep);
5466
- trackedDeps.push(dep);
5467
- }
5468
- return dep.get();
5469
- };
5368
+ if (this._running) return;
5369
+ this._running = true;
5370
+ this._rerunCount = 0;
5371
+ let result;
5470
5372
  try {
5471
- const depValues = [];
5472
- for (const dep of this._deps) {
5473
- depValues.push(dep.get());
5474
- }
5475
- this._inspectorHook?.({ kind: "run", depValues });
5476
- const result = this._fn(get);
5477
- this._rewire(trackedDeps);
5478
- if (result === void 0) return;
5479
- this._downAutoValue(result);
5480
- } catch (err) {
5481
- this._downInternal([[ERROR, err]]);
5373
+ for (; ; ) {
5374
+ const trackedDeps = [];
5375
+ const trackedValuesMap = /* @__PURE__ */ new Map();
5376
+ const trackedSet = /* @__PURE__ */ new Set();
5377
+ const get = (dep) => {
5378
+ if (!trackedSet.has(dep)) {
5379
+ trackedSet.add(dep);
5380
+ trackedDeps.push(dep);
5381
+ trackedValuesMap.set(dep, dep.get());
5382
+ }
5383
+ return dep.get();
5384
+ };
5385
+ this._trackedValues = trackedValuesMap;
5386
+ const depValues = [];
5387
+ for (const dep of this._deps) depValues.push(dep.get());
5388
+ this._emitInspectorHook({ kind: "run", depValues });
5389
+ try {
5390
+ result = this._fn(get);
5391
+ } catch (err) {
5392
+ const errMsg = err instanceof Error ? err.message : String(err);
5393
+ const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, {
5394
+ cause: err
5395
+ });
5396
+ this._downInternal([[ERROR, wrapped]]);
5397
+ return;
5398
+ }
5399
+ this._rewiring = true;
5400
+ this._bufferedDepMessages = [];
5401
+ try {
5402
+ this._rewire(trackedDeps);
5403
+ } finally {
5404
+ this._rewiring = false;
5405
+ }
5406
+ let needsRerun = false;
5407
+ for (const entry of this._bufferedDepMessages) {
5408
+ for (const msg of entry.msgs) {
5409
+ if (msg[0] === DATA) {
5410
+ const dep = this._deps[entry.index];
5411
+ const trackedValue = dep != null ? this._trackedValues.get(dep) : void 0;
5412
+ const actualValue = msg[1];
5413
+ if (!this._equals(trackedValue, actualValue)) {
5414
+ needsRerun = true;
5415
+ break;
5416
+ }
5417
+ }
5418
+ }
5419
+ if (needsRerun) break;
5420
+ }
5421
+ if (needsRerun) {
5422
+ this._rerunCount += 1;
5423
+ if (this._rerunCount > MAX_RERUN) {
5424
+ this._bufferedDepMessages = [];
5425
+ this._downInternal([
5426
+ [
5427
+ ERROR,
5428
+ new Error(
5429
+ `dynamicNode "${this.name ?? "anonymous"}": rewire did not stabilize within ${MAX_RERUN} iterations`
5430
+ )
5431
+ ]
5432
+ ]);
5433
+ return;
5434
+ }
5435
+ this._bufferedDepMessages = [];
5436
+ continue;
5437
+ }
5438
+ const drain = this._bufferedDepMessages;
5439
+ this._bufferedDepMessages = [];
5440
+ for (const entry of drain) {
5441
+ for (const msg of entry.msgs) {
5442
+ this._updateMasksForMessage(entry.index, msg);
5443
+ }
5444
+ }
5445
+ this._depDirtyBits.clear();
5446
+ this._depSettledBits.clear();
5447
+ break;
5448
+ }
5449
+ } finally {
5450
+ this._running = false;
5482
5451
  }
5452
+ this._downAutoValue(result);
5483
5453
  }
5484
5454
  _rewire(newDeps) {
5485
- this._rewiring = true;
5486
- try {
5487
- const oldMap = this._depIndexMap;
5488
- const newMap = /* @__PURE__ */ new Map();
5489
- const newUnsubs = [];
5490
- for (let i = 0; i < newDeps.length; i++) {
5491
- const dep = newDeps[i];
5492
- newMap.set(dep, i);
5493
- const oldIdx = oldMap.get(dep);
5494
- if (oldIdx !== void 0) {
5495
- newUnsubs.push(this._depUnsubs[oldIdx]);
5496
- this._depUnsubs[oldIdx] = () => {
5497
- };
5498
- } else {
5499
- const idx = i;
5500
- const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
5501
- newUnsubs.push(unsub);
5502
- }
5455
+ const oldMap = this._depIndexMap;
5456
+ const newMap = /* @__PURE__ */ new Map();
5457
+ const newUnsubs = [];
5458
+ for (let i = 0; i < newDeps.length; i++) {
5459
+ const dep = newDeps[i];
5460
+ newMap.set(dep, i);
5461
+ const oldIdx = oldMap.get(dep);
5462
+ if (oldIdx !== void 0) {
5463
+ newUnsubs.push(this._depUnsubs[oldIdx]);
5464
+ this._depUnsubs[oldIdx] = () => {
5465
+ };
5466
+ } else {
5467
+ const idx = i;
5468
+ const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
5469
+ newUnsubs.push(unsub);
5503
5470
  }
5504
- for (const [dep, oldIdx] of oldMap) {
5505
- if (!newMap.has(dep)) {
5506
- this._depUnsubs[oldIdx]();
5507
- }
5471
+ }
5472
+ for (const [dep, oldIdx] of oldMap) {
5473
+ if (!newMap.has(dep)) {
5474
+ this._depUnsubs[oldIdx]();
5508
5475
  }
5509
- this._deps = newDeps;
5510
- this._depUnsubs = newUnsubs;
5511
- this._depIndexMap = newMap;
5512
- this._dirtyBits.clear();
5513
- this._settledBits.clear();
5514
- const newCompleteBits = /* @__PURE__ */ new Set();
5515
- for (const oldIdx of this._completeBits) {
5516
- const dep = [...oldMap.entries()].find(([, idx]) => idx === oldIdx)?.[0];
5517
- if (dep && newMap.has(dep)) {
5476
+ }
5477
+ this._deps = newDeps;
5478
+ this._depUnsubs = newUnsubs;
5479
+ this._depIndexMap = newMap;
5480
+ this._depDirtyBits.clear();
5481
+ this._depSettledBits.clear();
5482
+ const newCompleteBits = /* @__PURE__ */ new Set();
5483
+ for (const oldIdx of this._depCompleteBits) {
5484
+ for (const [dep, idx] of oldMap) {
5485
+ if (idx === oldIdx && newMap.has(dep)) {
5518
5486
  newCompleteBits.add(newMap.get(dep));
5487
+ break;
5519
5488
  }
5520
5489
  }
5521
- this._completeBits = newCompleteBits;
5522
- } finally {
5523
- this._rewiring = false;
5524
5490
  }
5491
+ this._depCompleteBits = newCompleteBits;
5525
5492
  }
5493
+ // --- Dep message handling ---
5526
5494
  _handleDepMessages(index, messages) {
5527
- if (this._rewiring) return;
5495
+ if (this._rewiring) {
5496
+ this._bufferedDepMessages.push({ index, msgs: messages });
5497
+ return;
5498
+ }
5528
5499
  for (const msg of messages) {
5529
- this._inspectorHook?.({ kind: "dep_message", depIndex: index, message: msg });
5500
+ this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
5530
5501
  const t = msg[0];
5531
5502
  if (this._onMessage) {
5532
5503
  try {
5533
5504
  if (this._onMessage(msg, index, this._actions)) continue;
5534
5505
  } catch (err) {
5535
- this._downInternal([[ERROR, err]]);
5506
+ const errMsg = err instanceof Error ? err.message : String(err);
5507
+ const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
5508
+ cause: err
5509
+ });
5510
+ this._downInternal([[ERROR, wrapped]]);
5536
5511
  return;
5537
5512
  }
5538
5513
  }
5514
+ if (messageTier(t) < 1) continue;
5539
5515
  if (t === DIRTY) {
5540
- this._dirtyBits.add(index);
5541
- this._settledBits.delete(index);
5542
- if (this._dirtyBits.size === 1) {
5543
- downWithBatch(this._boundDownToSinks, [[DIRTY]]);
5516
+ const wasEmpty = this._depDirtyBits.size === 0;
5517
+ this._depDirtyBits.add(index);
5518
+ this._depSettledBits.delete(index);
5519
+ if (wasEmpty) {
5520
+ this._downInternal([[DIRTY]]);
5544
5521
  }
5545
5522
  continue;
5546
5523
  }
5547
5524
  if (t === DATA || t === RESOLVED) {
5548
- if (!this._dirtyBits.has(index)) {
5549
- this._dirtyBits.add(index);
5550
- downWithBatch(this._boundDownToSinks, [[DIRTY]]);
5525
+ if (!this._depDirtyBits.has(index)) {
5526
+ const wasEmpty = this._depDirtyBits.size === 0;
5527
+ this._depDirtyBits.add(index);
5528
+ if (wasEmpty) {
5529
+ this._downInternal([[DIRTY]]);
5530
+ }
5551
5531
  }
5552
- this._settledBits.add(index);
5532
+ this._depSettledBits.add(index);
5553
5533
  if (this._allDirtySettled()) {
5554
- this._dirtyBits.clear();
5555
- this._settledBits.clear();
5556
- this._runFn();
5534
+ this._depDirtyBits.clear();
5535
+ this._depSettledBits.clear();
5536
+ if (!this._running) {
5537
+ if (this._depValuesDifferFromTracked()) {
5538
+ this._runFn();
5539
+ }
5540
+ }
5557
5541
  }
5558
5542
  continue;
5559
5543
  }
5560
5544
  if (t === COMPLETE) {
5561
- this._completeBits.add(index);
5562
- this._dirtyBits.delete(index);
5563
- this._settledBits.delete(index);
5545
+ this._depCompleteBits.add(index);
5546
+ this._depDirtyBits.delete(index);
5547
+ this._depSettledBits.delete(index);
5564
5548
  if (this._allDirtySettled()) {
5565
- this._dirtyBits.clear();
5566
- this._settledBits.clear();
5567
- this._runFn();
5549
+ this._depDirtyBits.clear();
5550
+ this._depSettledBits.clear();
5551
+ if (!this._running) this._runFn();
5568
5552
  }
5569
- if (this._autoComplete && this._completeBits.size >= this._deps.length && this._deps.length > 0) {
5553
+ if (this._autoComplete && this._depCompleteBits.size >= this._deps.length && this._deps.length > 0) {
5570
5554
  this._downInternal([[COMPLETE]]);
5571
5555
  }
5572
5556
  continue;
@@ -5582,13 +5566,46 @@ var DynamicNodeImpl = class {
5582
5566
  this._downInternal([msg]);
5583
5567
  }
5584
5568
  }
5569
+ /**
5570
+ * Update dep masks for a message without triggering `_runFn` — used
5571
+ * during post-rewire drain so the wave state is consistent with the
5572
+ * buffered activation cascade without recursing.
5573
+ */
5574
+ _updateMasksForMessage(index, msg) {
5575
+ const t = msg[0];
5576
+ if (t === DIRTY) {
5577
+ this._depDirtyBits.add(index);
5578
+ this._depSettledBits.delete(index);
5579
+ } else if (t === DATA || t === RESOLVED) {
5580
+ this._depDirtyBits.add(index);
5581
+ this._depSettledBits.add(index);
5582
+ } else if (t === COMPLETE) {
5583
+ this._depCompleteBits.add(index);
5584
+ this._depDirtyBits.delete(index);
5585
+ this._depSettledBits.delete(index);
5586
+ }
5587
+ }
5585
5588
  _allDirtySettled() {
5586
- if (this._dirtyBits.size === 0) return false;
5587
- for (const idx of this._dirtyBits) {
5588
- if (!this._settledBits.has(idx)) return false;
5589
+ if (this._depDirtyBits.size === 0) return false;
5590
+ for (const idx of this._depDirtyBits) {
5591
+ if (!this._depSettledBits.has(idx)) return false;
5589
5592
  }
5590
5593
  return true;
5591
5594
  }
5595
+ /**
5596
+ * True if any current dep value differs from what the last `_runFn`
5597
+ * saw via `get()`. Used to suppress redundant re-runs when deferred
5598
+ * handshake messages arrive after `_rewire` for a dep whose value
5599
+ * already matches `_trackedValues`.
5600
+ */
5601
+ _depValuesDifferFromTracked() {
5602
+ for (const dep of this._deps) {
5603
+ const current = dep.get();
5604
+ const tracked = this._trackedValues.get(dep);
5605
+ if (!this._equals(current, tracked)) return true;
5606
+ }
5607
+ return false;
5608
+ }
5592
5609
  };
5593
5610
 
5594
5611
  // src/extra/operators.ts
@@ -5651,9 +5668,12 @@ function reduce(source, reducer, seed, opts) {
5651
5668
  }
5652
5669
  function take(source, count, opts) {
5653
5670
  if (count <= 0) {
5671
+ let completed = false;
5654
5672
  return node(
5655
5673
  [source],
5656
5674
  (_d, a) => {
5675
+ if (completed) return void 0;
5676
+ completed = true;
5657
5677
  a.down([[COMPLETE]]);
5658
5678
  return void 0;
5659
5679
  },
@@ -5661,8 +5681,15 @@ function take(source, count, opts) {
5661
5681
  ...operatorOpts3(opts),
5662
5682
  completeWhenDepsComplete: false,
5663
5683
  onMessage(msg, _i, a) {
5664
- if (msg[0] === COMPLETE) {
5684
+ if (msg[0] === START && !completed) {
5685
+ completed = true;
5686
+ a.down([[COMPLETE]]);
5687
+ return true;
5688
+ }
5689
+ if (msg[0] === COMPLETE && !completed) {
5690
+ completed = true;
5665
5691
  a.down([[COMPLETE]]);
5692
+ return true;
5666
5693
  }
5667
5694
  return true;
5668
5695
  }
@@ -5844,21 +5871,6 @@ function find(source, predicate, opts) {
5844
5871
  function elementAt(source, index, opts) {
5845
5872
  return take(skip(source, index, opts), 1, opts);
5846
5873
  }
5847
- function startWith(source, initial, opts) {
5848
- let prepended = false;
5849
- return node(
5850
- [source],
5851
- ([v], a) => {
5852
- if (!prepended) {
5853
- prepended = true;
5854
- a.emit(initial);
5855
- }
5856
- a.emit(v);
5857
- return void 0;
5858
- },
5859
- operatorOpts3(opts)
5860
- );
5861
- }
5862
5874
  function tap(source, fnOrObserver, opts) {
5863
5875
  if (typeof fnOrObserver === "function") {
5864
5876
  return derived(
@@ -6194,6 +6206,7 @@ function forwardInner(inner, a, onInnerComplete) {
6194
6206
  let sawError = false;
6195
6207
  const out = [];
6196
6208
  for (const m of msgs) {
6209
+ if (messageTier(m[0]) < 1) continue;
6197
6210
  if (m[0] === DATA) emitted = true;
6198
6211
  if (m[0] === COMPLETE) sawComplete = true;
6199
6212
  else {
@@ -6678,7 +6691,7 @@ function sample(source, notifier, opts) {
6678
6691
  if (terminated) return true;
6679
6692
  const t = msg[0];
6680
6693
  const tier = messageTier(t);
6681
- if (tier >= 3) {
6694
+ if (tier >= 4) {
6682
6695
  if (t === ERROR) {
6683
6696
  terminated = true;
6684
6697
  a.down([msg]);
@@ -6694,6 +6707,7 @@ function sample(source, notifier, opts) {
6694
6707
  a.down([msg]);
6695
6708
  return true;
6696
6709
  }
6710
+ terminated = true;
6697
6711
  a.down([msg]);
6698
6712
  return true;
6699
6713
  }
@@ -8007,7 +8021,7 @@ function workerBridge(target, opts) {
8007
8021
  for (const m of msgs) {
8008
8022
  const type = m[0];
8009
8023
  if (type === DATA) continue;
8010
- if (knownMessageTypes.includes(type) && messageTier(type) < 2) continue;
8024
+ if (isLocalOnly(type)) continue;
8011
8025
  if (type === ERROR) {
8012
8026
  transport.post({
8013
8027
  t: "e",
@@ -8133,7 +8147,7 @@ function workerSelf(target, opts) {
8133
8147
  for (const m of msgs) {
8134
8148
  const type = m[0];
8135
8149
  if (type === DATA) continue;
8136
- if (knownMessageTypes.includes(type) && messageTier(type) < 2) continue;
8150
+ if (isLocalOnly(type)) continue;
8137
8151
  if (type === ERROR) {
8138
8152
  transport.post({
8139
8153
  t: "e",
@@ -8277,6 +8291,7 @@ function workerSelf(target, opts) {
8277
8291
  find,
8278
8292
  first,
8279
8293
  firstValueFrom,
8294
+ firstWhere,
8280
8295
  flatMap,
8281
8296
  forEach,
8282
8297
  fromAny,
@@ -8354,7 +8369,6 @@ function workerSelf(target, opts) {
8354
8369
  shareReplay,
8355
8370
  signalToName,
8356
8371
  skip,
8357
- startWith,
8358
8372
  switchMap,
8359
8373
  take,
8360
8374
  takeUntil,