@graphrefly/graphrefly 0.17.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.
- package/dist/{chunk-R6OHUUYB.js → chunk-AHRKWMNI.js} +7 -7
- package/dist/chunk-AHRKWMNI.js.map +1 -0
- package/dist/{chunk-2PORF4RP.js → chunk-BER7UYLM.js} +27 -32
- package/dist/chunk-BER7UYLM.js.map +1 -0
- package/dist/{chunk-646OG3PO.js → chunk-IRZAGZUB.js} +51 -52
- package/dist/chunk-IRZAGZUB.js.map +1 -0
- package/dist/{chunk-IHJHBADD.js → chunk-JC2SN46B.js} +385 -197
- package/dist/chunk-JC2SN46B.js.map +1 -0
- package/dist/{chunk-XJ6EMQ22.js → chunk-OO5QOAXI.js} +4 -10
- package/dist/chunk-OO5QOAXI.js.map +1 -0
- package/dist/{chunk-YXROQFXZ.js → chunk-UW77D7SP.js} +3 -3
- package/dist/{chunk-F2ULI3Q3.js → chunk-XUOY3YKN.js} +7 -3
- package/dist/chunk-XUOY3YKN.js.map +1 -0
- package/dist/chunk-YLR5JUJZ.js +111 -0
- package/dist/chunk-YLR5JUJZ.js.map +1 -0
- package/dist/{chunk-BV3TPSBK.js → chunk-YXR3WW3Q.js} +740 -755
- package/dist/chunk-YXR3WW3Q.js.map +1 -0
- package/dist/compat/nestjs/index.cjs +1127 -983
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +4 -4
- package/dist/compat/nestjs/index.d.ts +4 -4
- package/dist/compat/nestjs/index.js +7 -13
- package/dist/core/index.cjs +653 -749
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +7 -7
- package/dist/extra/index.cjs +773 -795
- package/dist/extra/index.cjs.map +1 -1
- package/dist/extra/index.d.cts +4 -4
- package/dist/extra/index.d.ts +4 -4
- package/dist/extra/index.js +5 -11
- package/dist/graph/index.cjs +1036 -975
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +3 -3
- package/dist/graph/index.d.ts +3 -3
- package/dist/graph/index.js +8 -8
- package/dist/{graph-fCsaaVIa.d.cts → graph-KsTe57nI.d.cts} +127 -51
- package/dist/{graph-Dc-P9BVm.d.ts → graph-mILUUqW8.d.ts} +127 -51
- package/dist/{index-DhXznWyH.d.ts → index-8a605sg9.d.ts} +2 -2
- package/dist/{index-D7y9Q8W4.d.ts → index-B2SvPEbc.d.ts} +8 -69
- package/dist/{index-YlOH1Gw6.d.cts → index-BBUYZfJ1.d.cts} +122 -78
- package/dist/{index-ClaKZFPl.d.cts → index-Bjh5C1Tp.d.cts} +38 -35
- package/dist/{index-DWq0P9T6.d.ts → index-BjtlNirP.d.cts} +5 -7
- package/dist/{index-N704txAA.d.ts → index-BnkMgNNa.d.ts} +38 -35
- package/dist/{index-BBVBYPxr.d.cts → index-CgSiUouz.d.ts} +5 -7
- package/dist/{index-BmoUvOGN.d.ts → index-CvKzv0AW.d.ts} +122 -78
- package/dist/{index-4OIX-q0C.d.cts → index-UudxGnzc.d.cts} +8 -69
- package/dist/{index-DlGMf_Qe.d.cts → index-VHA43cGP.d.cts} +2 -2
- package/dist/index.cjs +6146 -5725
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +617 -383
- package/dist/index.d.ts +617 -383
- package/dist/index.js +4401 -4028
- package/dist/index.js.map +1 -1
- package/dist/{meta-BV4pj9ML.d.cts → meta-BnG7XAaE.d.cts} +395 -289
- package/dist/{meta-BV4pj9ML.d.ts → meta-BnG7XAaE.d.ts} +395 -289
- package/dist/observable-C8Kx_O6k.d.cts +36 -0
- package/dist/observable-DcBwQY7t.d.ts +36 -0
- package/dist/patterns/reactive-layout/index.cjs +1037 -857
- package/dist/patterns/reactive-layout/index.cjs.map +1 -1
- package/dist/patterns/reactive-layout/index.d.cts +3 -3
- package/dist/patterns/reactive-layout/index.d.ts +3 -3
- package/dist/patterns/reactive-layout/index.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-2PORF4RP.js.map +0 -1
- package/dist/chunk-646OG3PO.js.map +0 -1
- package/dist/chunk-BV3TPSBK.js.map +0 -1
- package/dist/chunk-EBNKJULL.js +0 -231
- package/dist/chunk-EBNKJULL.js.map +0 -1
- package/dist/chunk-F2ULI3Q3.js.map +0 -1
- package/dist/chunk-IHJHBADD.js.map +0 -1
- package/dist/chunk-R6OHUUYB.js.map +0 -1
- package/dist/chunk-XJ6EMQ22.js.map +0 -1
- package/dist/observable-Cz-AWhwR.d.cts +0 -42
- package/dist/observable-DCqlwGyl.d.ts +0 -42
- /package/dist/{chunk-YXROQFXZ.js.map → chunk-UW77D7SP.js.map} +0 -0
package/dist/extra/index.cjs
CHANGED
|
@@ -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,
|
|
@@ -111,8 +112,6 @@ __export(extra_exports, {
|
|
|
111
112
|
mergeMap: () => mergeMap,
|
|
112
113
|
nameToSignal: () => nameToSignal,
|
|
113
114
|
never: () => never,
|
|
114
|
-
observeGraph$: () => observeGraph$,
|
|
115
|
-
observeNode$: () => observeNode$,
|
|
116
115
|
of: () => of,
|
|
117
116
|
pairwise: () => pairwise,
|
|
118
117
|
parseCron: () => parseCron,
|
|
@@ -144,7 +143,6 @@ __export(extra_exports, {
|
|
|
144
143
|
shareReplay: () => shareReplay,
|
|
145
144
|
signalToName: () => signalToName,
|
|
146
145
|
skip: () => skip,
|
|
147
|
-
startWith: () => startWith,
|
|
148
146
|
switchMap: () => switchMap,
|
|
149
147
|
take: () => take,
|
|
150
148
|
takeUntil: () => takeUntil,
|
|
@@ -161,7 +159,6 @@ __export(extra_exports, {
|
|
|
161
159
|
toFile: () => toFile,
|
|
162
160
|
toKafka: () => toKafka,
|
|
163
161
|
toLoki: () => toLoki,
|
|
164
|
-
toMessages$: () => toMessages$,
|
|
165
162
|
toMongo: () => toMongo,
|
|
166
163
|
toNATS: () => toNATS,
|
|
167
164
|
toObservable: () => toObservable,
|
|
@@ -192,6 +189,7 @@ __export(extra_exports, {
|
|
|
192
189
|
module.exports = __toCommonJS(extra_exports);
|
|
193
190
|
|
|
194
191
|
// src/core/messages.ts
|
|
192
|
+
var START = /* @__PURE__ */ Symbol.for("graphrefly/START");
|
|
195
193
|
var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
|
|
196
194
|
var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
|
|
197
195
|
var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
|
|
@@ -202,6 +200,7 @@ var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
|
|
|
202
200
|
var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
|
|
203
201
|
var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
|
|
204
202
|
var knownMessageTypes = [
|
|
203
|
+
START,
|
|
205
204
|
DATA,
|
|
206
205
|
DIRTY,
|
|
207
206
|
RESOLVED,
|
|
@@ -212,13 +211,15 @@ var knownMessageTypes = [
|
|
|
212
211
|
COMPLETE,
|
|
213
212
|
ERROR
|
|
214
213
|
];
|
|
214
|
+
var knownMessageSet = new Set(knownMessageTypes);
|
|
215
215
|
function messageTier(t) {
|
|
216
|
-
if (t ===
|
|
217
|
-
if (t ===
|
|
218
|
-
if (t ===
|
|
219
|
-
if (t ===
|
|
220
|
-
if (t ===
|
|
221
|
-
return
|
|
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;
|
|
222
223
|
}
|
|
223
224
|
function isPhase2Message(msg) {
|
|
224
225
|
const t = msg[0];
|
|
@@ -227,6 +228,10 @@ function isPhase2Message(msg) {
|
|
|
227
228
|
function isTerminalMessage(t) {
|
|
228
229
|
return t === COMPLETE || t === ERROR;
|
|
229
230
|
}
|
|
231
|
+
function isLocalOnly(t) {
|
|
232
|
+
if (!knownMessageSet.has(t)) return false;
|
|
233
|
+
return messageTier(t) < 3;
|
|
234
|
+
}
|
|
230
235
|
function propagatesToMeta(t) {
|
|
231
236
|
return t === TEARDOWN;
|
|
232
237
|
}
|
|
@@ -387,14 +392,14 @@ function _downSequential(sink, messages, phase = 2) {
|
|
|
387
392
|
const dataQueue = phase === 3 ? pendingPhase3 : pendingPhase2;
|
|
388
393
|
for (const msg of messages) {
|
|
389
394
|
const tier = messageTier(msg[0]);
|
|
390
|
-
if (tier ===
|
|
395
|
+
if (tier === 3) {
|
|
391
396
|
if (isBatching()) {
|
|
392
397
|
const m = msg;
|
|
393
398
|
dataQueue.push(() => sink([m]));
|
|
394
399
|
} else {
|
|
395
400
|
sink([msg]);
|
|
396
401
|
}
|
|
397
|
-
} else if (tier >=
|
|
402
|
+
} else if (tier >= 4) {
|
|
398
403
|
if (isBatching()) {
|
|
399
404
|
const m = msg;
|
|
400
405
|
pendingPhase3.push(() => sink([m]));
|
|
@@ -503,10 +508,24 @@ function advanceVersion(info, newValue, hashFn) {
|
|
|
503
508
|
}
|
|
504
509
|
}
|
|
505
510
|
|
|
506
|
-
// src/core/node.ts
|
|
511
|
+
// src/core/node-base.ts
|
|
507
512
|
var NO_VALUE = /* @__PURE__ */ Symbol.for("graphrefly/NO_VALUE");
|
|
508
513
|
var CLEANUP_RESULT = /* @__PURE__ */ Symbol.for("graphrefly/CLEANUP_RESULT");
|
|
509
|
-
|
|
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);
|
|
510
529
|
let bits = 0;
|
|
511
530
|
return {
|
|
512
531
|
set(i) {
|
|
@@ -519,7 +538,8 @@ function createIntBitSet() {
|
|
|
519
538
|
return (bits & 1 << i) !== 0;
|
|
520
539
|
},
|
|
521
540
|
covers(other) {
|
|
522
|
-
|
|
541
|
+
const otherBits = other._bits();
|
|
542
|
+
return (bits & otherBits) === otherBits;
|
|
523
543
|
},
|
|
524
544
|
any() {
|
|
525
545
|
return bits !== 0;
|
|
@@ -527,6 +547,9 @@ function createIntBitSet() {
|
|
|
527
547
|
reset() {
|
|
528
548
|
bits = 0;
|
|
529
549
|
},
|
|
550
|
+
setAll() {
|
|
551
|
+
bits = fullMask;
|
|
552
|
+
},
|
|
530
553
|
_bits() {
|
|
531
554
|
return bits;
|
|
532
555
|
}
|
|
@@ -534,6 +557,8 @@ function createIntBitSet() {
|
|
|
534
557
|
}
|
|
535
558
|
function createArrayBitSet(size) {
|
|
536
559
|
const words = new Uint32Array(Math.ceil(size / 32));
|
|
560
|
+
const lastBits = size % 32;
|
|
561
|
+
const lastWordMask = lastBits === 0 ? 4294967295 : (1 << lastBits) - 1 >>> 0;
|
|
537
562
|
return {
|
|
538
563
|
set(i) {
|
|
539
564
|
words[i >>> 5] |= 1 << (i & 31);
|
|
@@ -560,130 +585,103 @@ function createArrayBitSet(size) {
|
|
|
560
585
|
reset() {
|
|
561
586
|
words.fill(0);
|
|
562
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
|
+
},
|
|
563
592
|
_words: words
|
|
564
593
|
};
|
|
565
594
|
}
|
|
566
595
|
function createBitSet(size) {
|
|
567
|
-
return size <= 31 ? createIntBitSet() : createArrayBitSet(size);
|
|
596
|
+
return size <= 31 ? createIntBitSet(size) : createArrayBitSet(size);
|
|
568
597
|
}
|
|
569
|
-
var
|
|
570
|
-
|
|
571
|
-
var isCleanupResult = (value) => typeof value === "object" && value !== null && CLEANUP_RESULT in value;
|
|
572
|
-
var isCleanupFn = (value) => typeof value === "function";
|
|
573
|
-
var statusAfterMessage = (status, msg) => {
|
|
574
|
-
const t = msg[0];
|
|
575
|
-
if (t === DIRTY) return "dirty";
|
|
576
|
-
if (t === DATA) return "settled";
|
|
577
|
-
if (t === RESOLVED) return "resolved";
|
|
578
|
-
if (t === COMPLETE) return "completed";
|
|
579
|
-
if (t === ERROR) return "errored";
|
|
580
|
-
if (t === INVALIDATE) return "dirty";
|
|
581
|
-
if (t === TEARDOWN) return "disconnected";
|
|
582
|
-
return status;
|
|
583
|
-
};
|
|
584
|
-
var NodeImpl = class {
|
|
585
|
-
// --- Configuration (set once, never reassigned) ---
|
|
598
|
+
var NodeBase = class {
|
|
599
|
+
// --- Identity (set once) ---
|
|
586
600
|
_optsName;
|
|
587
601
|
_registryName;
|
|
588
|
-
/** @internal
|
|
602
|
+
/** @internal Read by `describeNode` before inference. */
|
|
589
603
|
_describeKind;
|
|
590
604
|
meta;
|
|
591
|
-
|
|
592
|
-
_fn;
|
|
593
|
-
_opts;
|
|
605
|
+
// --- Options ---
|
|
594
606
|
_equals;
|
|
607
|
+
_resubscribable;
|
|
608
|
+
_resetOnTeardown;
|
|
609
|
+
_onResubscribe;
|
|
595
610
|
_onMessage;
|
|
596
|
-
/** @internal
|
|
611
|
+
/** @internal Read by `describeNode` for `accessHintForGuard`. */
|
|
597
612
|
_guard;
|
|
613
|
+
/** @internal Subclasses update this through {@link _recordMutation}. */
|
|
598
614
|
_lastMutation;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
// ---
|
|
615
|
+
// --- Versioning ---
|
|
616
|
+
_hashFn;
|
|
617
|
+
_versioning;
|
|
618
|
+
// --- Lifecycle state ---
|
|
619
|
+
/** @internal Read by `describeNode` and `graph.ts`. */
|
|
603
620
|
_cached;
|
|
621
|
+
/** @internal Read externally via `get status()`. */
|
|
604
622
|
_status;
|
|
605
623
|
_terminal = false;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
_manualEmitUsed = false;
|
|
624
|
+
_active = false;
|
|
625
|
+
// --- Sink storage ---
|
|
626
|
+
/** @internal Read by `graph/profile.ts` for subscriber counts. */
|
|
610
627
|
_sinkCount = 0;
|
|
611
628
|
_singleDepSinkCount = 0;
|
|
612
|
-
// --- Object/collection state ---
|
|
613
|
-
_depDirtyMask;
|
|
614
|
-
_depSettledMask;
|
|
615
|
-
_depCompleteMask;
|
|
616
|
-
_allDepsCompleteMask;
|
|
617
|
-
_lastDepValues;
|
|
618
|
-
_cleanup;
|
|
619
|
-
_sinks = null;
|
|
620
629
|
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
621
|
-
|
|
630
|
+
_sinks = null;
|
|
631
|
+
// --- Actions + bound helpers ---
|
|
622
632
|
_actions;
|
|
623
633
|
_boundDownToSinks;
|
|
634
|
+
// --- Inspector hook (Graph observability) ---
|
|
624
635
|
_inspectorHook;
|
|
625
|
-
|
|
626
|
-
_hashFn;
|
|
627
|
-
constructor(deps, fn, opts) {
|
|
628
|
-
this._deps = deps;
|
|
629
|
-
this._fn = fn;
|
|
630
|
-
this._opts = opts;
|
|
636
|
+
constructor(opts) {
|
|
631
637
|
this._optsName = opts.name;
|
|
632
638
|
this._describeKind = opts.describeKind;
|
|
633
639
|
this._equals = opts.equals ?? Object.is;
|
|
640
|
+
this._resubscribable = opts.resubscribable ?? false;
|
|
641
|
+
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
642
|
+
this._onResubscribe = opts.onResubscribe;
|
|
634
643
|
this._onMessage = opts.onMessage;
|
|
635
644
|
this._guard = opts.guard;
|
|
636
|
-
this._hasDeps = deps.length > 0;
|
|
637
|
-
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
638
|
-
this._isSingleDep = deps.length === 1 && fn != null;
|
|
639
645
|
this._cached = "initial" in opts ? opts.initial : NO_VALUE;
|
|
640
|
-
this._status =
|
|
646
|
+
this._status = "disconnected";
|
|
641
647
|
this._hashFn = opts.versioningHash ?? defaultHash;
|
|
642
648
|
this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached === NO_VALUE ? void 0 : this._cached, {
|
|
643
649
|
id: opts.versioningId,
|
|
644
650
|
hash: this._hashFn
|
|
645
651
|
}) : void 0;
|
|
646
|
-
this._depDirtyMask = createBitSet(deps.length);
|
|
647
|
-
this._depSettledMask = createBitSet(deps.length);
|
|
648
|
-
this._depCompleteMask = createBitSet(deps.length);
|
|
649
|
-
this._allDepsCompleteMask = createBitSet(deps.length);
|
|
650
|
-
for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
|
|
651
652
|
const meta = {};
|
|
652
653
|
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
653
|
-
meta[k] =
|
|
654
|
-
initial: v,
|
|
655
|
-
name: `${opts.name ?? "node"}:meta:${k}`,
|
|
656
|
-
describeKind: "state",
|
|
657
|
-
...opts.guard != null ? { guard: opts.guard } : {}
|
|
658
|
-
});
|
|
654
|
+
meta[k] = this._createMetaNode(k, v, opts);
|
|
659
655
|
}
|
|
660
656
|
Object.freeze(meta);
|
|
661
657
|
this.meta = meta;
|
|
662
658
|
const self = this;
|
|
663
659
|
this._actions = {
|
|
664
660
|
down(messages) {
|
|
665
|
-
self.
|
|
661
|
+
self._onManualEmit();
|
|
666
662
|
self._downInternal(messages);
|
|
667
663
|
},
|
|
668
664
|
emit(value) {
|
|
669
|
-
self.
|
|
665
|
+
self._onManualEmit();
|
|
670
666
|
self._downAutoValue(value);
|
|
671
667
|
},
|
|
672
668
|
up(messages) {
|
|
673
669
|
self._upInternal(messages);
|
|
674
670
|
}
|
|
675
671
|
};
|
|
676
|
-
this.down = this.down.bind(this);
|
|
677
|
-
this.up = this.up.bind(this);
|
|
678
672
|
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
679
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 ---
|
|
680
681
|
get name() {
|
|
681
682
|
return this._registryName ?? this._optsName;
|
|
682
683
|
}
|
|
683
|
-
/**
|
|
684
|
-
* When a node is registered with {@link Graph.add} without an options `name`,
|
|
685
|
-
* the graph assigns the registry local name for introspection (parity with graphrefly-py).
|
|
686
|
-
*/
|
|
684
|
+
/** @internal Assigned by `Graph.add` when registered without an options `name`. */
|
|
687
685
|
_assignRegistryName(localName) {
|
|
688
686
|
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
689
687
|
this._registryName = localName;
|
|
@@ -701,7 +699,10 @@ var NodeImpl = class {
|
|
|
701
699
|
}
|
|
702
700
|
};
|
|
703
701
|
}
|
|
704
|
-
|
|
702
|
+
/** @internal Used by subclasses to surface inspector events. */
|
|
703
|
+
_emitInspectorHook(event) {
|
|
704
|
+
this._inspectorHook?.(event);
|
|
705
|
+
}
|
|
705
706
|
get status() {
|
|
706
707
|
return this._status;
|
|
707
708
|
}
|
|
@@ -711,15 +712,7 @@ var NodeImpl = class {
|
|
|
711
712
|
get v() {
|
|
712
713
|
return this._versioning;
|
|
713
714
|
}
|
|
714
|
-
/**
|
|
715
|
-
* Retroactively apply versioning to a node that was created without it.
|
|
716
|
-
* No-op if versioning is already enabled.
|
|
717
|
-
*
|
|
718
|
-
* Version starts at 0 regardless of prior DATA emissions — it tracks
|
|
719
|
-
* changes from the moment versioning is enabled, not historical ones.
|
|
720
|
-
*
|
|
721
|
-
* @internal — used by {@link Graph.setVersioning}.
|
|
722
|
-
*/
|
|
715
|
+
/** @internal Used by `Graph.setVersioning` to retroactively apply versioning. */
|
|
723
716
|
_applyVersioning(level, opts) {
|
|
724
717
|
if (this._versioning != null) return;
|
|
725
718
|
this._hashFn = opts?.hash ?? this._hashFn;
|
|
@@ -739,6 +732,7 @@ var NodeImpl = class {
|
|
|
739
732
|
if (this._guard == null) return true;
|
|
740
733
|
return this._guard(normalizeActor(actor), "observe");
|
|
741
734
|
}
|
|
735
|
+
// --- Public transport ---
|
|
742
736
|
get() {
|
|
743
737
|
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
744
738
|
}
|
|
@@ -751,43 +745,25 @@ var NodeImpl = class {
|
|
|
751
745
|
if (!this._guard(actor, action)) {
|
|
752
746
|
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
753
747
|
}
|
|
754
|
-
this.
|
|
748
|
+
this._recordMutation(actor);
|
|
755
749
|
}
|
|
756
750
|
this._downInternal(messages);
|
|
757
751
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
let sinkMessages = messages;
|
|
762
|
-
if (this._terminal && !this._opts.resubscribable) {
|
|
763
|
-
const terminalPassthrough = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
|
|
764
|
-
if (terminalPassthrough.length === 0) return;
|
|
765
|
-
lifecycleMessages = terminalPassthrough;
|
|
766
|
-
sinkMessages = terminalPassthrough;
|
|
767
|
-
}
|
|
768
|
-
this._handleLocalLifecycle(lifecycleMessages);
|
|
769
|
-
if (this._canSkipDirty()) {
|
|
770
|
-
let hasPhase2 = false;
|
|
771
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
772
|
-
const t = sinkMessages[i][0];
|
|
773
|
-
if (t === DATA || t === RESOLVED) {
|
|
774
|
-
hasPhase2 = true;
|
|
775
|
-
break;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
if (hasPhase2) {
|
|
779
|
-
const filtered = [];
|
|
780
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
781
|
-
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
782
|
-
}
|
|
783
|
-
if (filtered.length > 0) {
|
|
784
|
-
downWithBatch(this._boundDownToSinks, filtered);
|
|
785
|
-
}
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
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() };
|
|
790
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();
|
|
765
|
+
}
|
|
766
|
+
// --- Subscribe (uniform across node shapes) ---
|
|
791
767
|
subscribe(sink, hints) {
|
|
792
768
|
if (hints?.actor != null && this._guard != null) {
|
|
793
769
|
const actor = normalizeActor(hints.actor);
|
|
@@ -795,17 +771,21 @@ var NodeImpl = class {
|
|
|
795
771
|
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
796
772
|
}
|
|
797
773
|
}
|
|
798
|
-
if (this._terminal && this.
|
|
774
|
+
if (this._terminal && this._resubscribable) {
|
|
799
775
|
this._terminal = false;
|
|
800
776
|
this._cached = NO_VALUE;
|
|
801
|
-
this._status =
|
|
802
|
-
this.
|
|
777
|
+
this._status = "disconnected";
|
|
778
|
+
this._onResubscribe?.();
|
|
803
779
|
}
|
|
804
780
|
this._sinkCount += 1;
|
|
805
781
|
if (hints?.singleDep) {
|
|
806
782
|
this._singleDepSinkCount += 1;
|
|
807
783
|
this._singleDepSinks.add(sink);
|
|
808
784
|
}
|
|
785
|
+
if (!this._terminal) {
|
|
786
|
+
const startMessages = this._cached === NO_VALUE ? [[START]] : [[START], [DATA, this._cached]];
|
|
787
|
+
downWithBatch(sink, startMessages);
|
|
788
|
+
}
|
|
809
789
|
if (this._sinks == null) {
|
|
810
790
|
this._sinks = sink;
|
|
811
791
|
} else if (typeof this._sinks === "function") {
|
|
@@ -813,10 +793,12 @@ var NodeImpl = class {
|
|
|
813
793
|
} else {
|
|
814
794
|
this._sinks.add(sink);
|
|
815
795
|
}
|
|
816
|
-
if (this.
|
|
817
|
-
this.
|
|
818
|
-
|
|
819
|
-
|
|
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";
|
|
820
802
|
}
|
|
821
803
|
let removed = false;
|
|
822
804
|
return () => {
|
|
@@ -840,39 +822,49 @@ var NodeImpl = class {
|
|
|
840
822
|
}
|
|
841
823
|
}
|
|
842
824
|
if (this._sinks == null) {
|
|
843
|
-
this.
|
|
844
|
-
this._stopProducer();
|
|
825
|
+
this._onDeactivate();
|
|
845
826
|
}
|
|
846
827
|
};
|
|
847
828
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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;
|
|
856
841
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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;
|
|
862
861
|
}
|
|
863
862
|
}
|
|
863
|
+
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
864
864
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
for (const dep of this._deps) {
|
|
868
|
-
dep.up?.(messages, { internal: true });
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
unsubscribe() {
|
|
872
|
-
if (!this._hasDeps) return;
|
|
873
|
-
this._disconnectUpstream();
|
|
865
|
+
_canSkipDirty() {
|
|
866
|
+
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
874
867
|
}
|
|
875
|
-
// --- Private methods (prototype, _ prefix) ---
|
|
876
868
|
_downToSinks(messages) {
|
|
877
869
|
if (this._sinks == null) return;
|
|
878
870
|
if (typeof this._sinks === "function") {
|
|
@@ -884,6 +876,11 @@ var NodeImpl = class {
|
|
|
884
876
|
sink(messages);
|
|
885
877
|
}
|
|
886
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
|
+
*/
|
|
887
884
|
_handleLocalLifecycle(messages) {
|
|
888
885
|
for (const m of messages) {
|
|
889
886
|
const t = m[0];
|
|
@@ -897,28 +894,22 @@ var NodeImpl = class {
|
|
|
897
894
|
}
|
|
898
895
|
}
|
|
899
896
|
if (t === INVALIDATE) {
|
|
900
|
-
|
|
901
|
-
this._cleanup = void 0;
|
|
902
|
-
cleanupFn?.();
|
|
897
|
+
this._onInvalidate();
|
|
903
898
|
this._cached = NO_VALUE;
|
|
904
|
-
this._lastDepValues = void 0;
|
|
905
899
|
}
|
|
906
900
|
this._status = statusAfterMessage(this._status, m);
|
|
907
901
|
if (t === COMPLETE || t === ERROR) {
|
|
908
902
|
this._terminal = true;
|
|
909
903
|
}
|
|
910
904
|
if (t === TEARDOWN) {
|
|
911
|
-
if (this.
|
|
905
|
+
if (this._resetOnTeardown) {
|
|
912
906
|
this._cached = NO_VALUE;
|
|
913
907
|
}
|
|
914
|
-
|
|
915
|
-
this._cleanup = void 0;
|
|
916
|
-
teardownCleanup?.();
|
|
908
|
+
this._onTeardown();
|
|
917
909
|
try {
|
|
918
910
|
this._propagateToMeta(t);
|
|
919
911
|
} finally {
|
|
920
|
-
this.
|
|
921
|
-
this._stopProducer();
|
|
912
|
+
this._onDeactivate();
|
|
922
913
|
}
|
|
923
914
|
}
|
|
924
915
|
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
@@ -926,7 +917,20 @@ var NodeImpl = class {
|
|
|
926
917
|
}
|
|
927
918
|
}
|
|
928
919
|
}
|
|
929
|
-
/**
|
|
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). */
|
|
930
934
|
_propagateToMeta(t) {
|
|
931
935
|
for (const metaNode of Object.values(this.meta)) {
|
|
932
936
|
try {
|
|
@@ -935,9 +939,10 @@ var NodeImpl = class {
|
|
|
935
939
|
}
|
|
936
940
|
}
|
|
937
941
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
942
|
+
/**
|
|
943
|
+
* Frame a computed value into the right protocol messages and dispatch
|
|
944
|
+
* via `_downInternal`. Used by `_runFn` and `actions.emit`.
|
|
945
|
+
*/
|
|
941
946
|
_downAutoValue(value) {
|
|
942
947
|
const wasDirty = this._status === "dirty";
|
|
943
948
|
let unchanged;
|
|
@@ -945,7 +950,9 @@ var NodeImpl = class {
|
|
|
945
950
|
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
946
951
|
} catch (eqErr) {
|
|
947
952
|
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
948
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
953
|
+
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
954
|
+
cause: eqErr
|
|
955
|
+
});
|
|
949
956
|
this._downInternal([[ERROR, wrapped]]);
|
|
950
957
|
return;
|
|
951
958
|
}
|
|
@@ -955,89 +962,173 @@ var NodeImpl = class {
|
|
|
955
962
|
}
|
|
956
963
|
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
957
964
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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";
|
|
999
|
+
}
|
|
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 });
|
|
996
1028
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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);
|
|
1000
1036
|
}
|
|
1001
|
-
if (this._manualEmitUsed) return;
|
|
1002
|
-
if (out === void 0) return;
|
|
1003
|
-
this._downAutoValue(out);
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1006
|
-
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
|
|
1007
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
1008
1037
|
}
|
|
1009
1038
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
this.
|
|
1013
|
-
|
|
1014
|
-
if (!wasDirty) {
|
|
1015
|
-
this._downInternal([[DIRTY]]);
|
|
1039
|
+
_upInternal(messages) {
|
|
1040
|
+
if (!this._hasDeps) return;
|
|
1041
|
+
for (const dep of this._deps) {
|
|
1042
|
+
dep.up?.(messages, { internal: true });
|
|
1016
1043
|
}
|
|
1017
1044
|
}
|
|
1018
|
-
|
|
1019
|
-
if (!this.
|
|
1020
|
-
|
|
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;
|
|
1021
1054
|
}
|
|
1022
|
-
this.
|
|
1023
|
-
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1024
|
-
this._depDirtyMask.reset();
|
|
1025
|
-
this._depSettledMask.reset();
|
|
1055
|
+
if (this._fn) {
|
|
1026
1056
|
this._runFn();
|
|
1057
|
+
return;
|
|
1027
1058
|
}
|
|
1028
1059
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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;
|
|
1032
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;
|
|
1033
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();
|
|
1102
|
+
}
|
|
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 ---
|
|
1034
1114
|
_handleDepMessages(index, messages) {
|
|
1035
1115
|
for (const msg of messages) {
|
|
1036
|
-
this.
|
|
1116
|
+
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
1037
1117
|
const t = msg[0];
|
|
1038
1118
|
if (this._onMessage) {
|
|
1039
1119
|
try {
|
|
1040
|
-
|
|
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
|
+
}
|
|
1041
1132
|
} catch (err) {
|
|
1042
1133
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1043
1134
|
const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
|
|
@@ -1047,6 +1138,7 @@ var NodeImpl = class {
|
|
|
1047
1138
|
return;
|
|
1048
1139
|
}
|
|
1049
1140
|
}
|
|
1141
|
+
if (messageTier(t) < 1) continue;
|
|
1050
1142
|
if (!this._fn) {
|
|
1051
1143
|
if (t === COMPLETE && this._deps.length > 1) {
|
|
1052
1144
|
this._depCompleteMask.set(index);
|
|
@@ -1090,53 +1182,85 @@ var NodeImpl = class {
|
|
|
1090
1182
|
this._downInternal([msg]);
|
|
1091
1183
|
}
|
|
1092
1184
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
this.
|
|
1096
|
-
this.
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
this._status = "settled";
|
|
1100
|
-
const subHints = this._isSingleDep ? { singleDep: true } : void 0;
|
|
1101
|
-
this._connecting = true;
|
|
1102
|
-
try {
|
|
1103
|
-
for (let i = 0; i < this._deps.length; i += 1) {
|
|
1104
|
-
const dep = this._deps[i];
|
|
1105
|
-
this._upstreamUnsubs.push(
|
|
1106
|
-
dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
|
|
1107
|
-
);
|
|
1108
|
-
}
|
|
1109
|
-
} finally {
|
|
1110
|
-
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]]);
|
|
1111
1191
|
}
|
|
1112
|
-
|
|
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();
|
|
1113
1201
|
this._runFn();
|
|
1114
1202
|
}
|
|
1115
1203
|
}
|
|
1116
|
-
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
this._cleanup = void 0;
|
|
1121
|
-
producerCleanup?.();
|
|
1122
|
-
}
|
|
1123
|
-
_startProducer() {
|
|
1124
|
-
if (this._deps.length !== 0 || !this._fn || this._producerStarted) return;
|
|
1125
|
-
this._producerStarted = true;
|
|
1126
|
-
this._runFn();
|
|
1204
|
+
_maybeCompleteFromDeps() {
|
|
1205
|
+
if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
|
|
1206
|
+
this._downInternal([[COMPLETE]]);
|
|
1207
|
+
}
|
|
1127
1208
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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]]);
|
|
1132
1259
|
}
|
|
1133
|
-
this._connected = false;
|
|
1134
|
-
this._depDirtyMask.reset();
|
|
1135
|
-
this._depSettledMask.reset();
|
|
1136
|
-
this._depCompleteMask.reset();
|
|
1137
|
-
this._status = "disconnected";
|
|
1138
1260
|
}
|
|
1139
1261
|
};
|
|
1262
|
+
var isNodeArray = (value) => Array.isArray(value);
|
|
1263
|
+
var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
|
|
1140
1264
|
function node(depsOrFn, fnOrOpts, optsArg) {
|
|
1141
1265
|
const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
|
|
1142
1266
|
const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
|
|
@@ -2272,7 +2396,38 @@ function firstValueFrom(source) {
|
|
|
2272
2396
|
}
|
|
2273
2397
|
if (m[0] === COMPLETE) {
|
|
2274
2398
|
settled = true;
|
|
2275
|
-
reject(new Error("completed without DATA"));
|
|
2399
|
+
reject(new Error("completed without DATA"));
|
|
2400
|
+
queueMicrotask(() => unsub());
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2405
|
+
});
|
|
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"));
|
|
2276
2431
|
queueMicrotask(() => unsub());
|
|
2277
2432
|
return;
|
|
2278
2433
|
}
|
|
@@ -2522,6 +2677,10 @@ function toSSE(source, opts) {
|
|
|
2522
2677
|
unsub = source.subscribe((msgs) => {
|
|
2523
2678
|
for (const msg of msgs) {
|
|
2524
2679
|
const t = msg[0];
|
|
2680
|
+
if (isLocalOnly(t)) {
|
|
2681
|
+
if (t === DIRTY && includeDirty) {
|
|
2682
|
+
} else continue;
|
|
2683
|
+
}
|
|
2525
2684
|
if (t === DATA) {
|
|
2526
2685
|
write(dataEvent, serializeSseData(msg[1], serialize));
|
|
2527
2686
|
continue;
|
|
@@ -2537,7 +2696,6 @@ function toSSE(source, opts) {
|
|
|
2537
2696
|
return;
|
|
2538
2697
|
}
|
|
2539
2698
|
if (!includeResolved && t === RESOLVED) continue;
|
|
2540
|
-
if (!includeDirty && t === DIRTY) continue;
|
|
2541
2699
|
write(
|
|
2542
2700
|
eventNameResolver(t),
|
|
2543
2701
|
msg.length > 1 ? serializeSseData(msg[1], serialize) : void 0
|
|
@@ -5065,289 +5223,106 @@ function restoreGraphCheckpointIndexedDb(graph, spec) {
|
|
|
5065
5223
|
}
|
|
5066
5224
|
if (requestValue === void 0 || requestValue === null) {
|
|
5067
5225
|
finishWith([[DATA, false], [COMPLETE]]);
|
|
5068
|
-
return;
|
|
5069
|
-
}
|
|
5070
|
-
if (typeof requestValue !== "object" || Array.isArray(requestValue)) {
|
|
5071
|
-
finishWith([[DATA, false], [COMPLETE]]);
|
|
5072
|
-
return;
|
|
5073
|
-
}
|
|
5074
|
-
graph.restore(requestValue);
|
|
5075
|
-
finishWith([[DATA, true], [COMPLETE]]);
|
|
5076
|
-
};
|
|
5077
|
-
const startRead = () => {
|
|
5078
|
-
if (db === void 0 || reqUnsub !== void 0 || txUnsub !== void 0) return;
|
|
5079
|
-
const tx = db.transaction(spec.storeName, "readonly");
|
|
5080
|
-
const store = tx.objectStore(spec.storeName);
|
|
5081
|
-
reqUnsub = fromIDBRequest(store.get(key)).subscribe((msgs) => {
|
|
5082
|
-
for (const m of msgs) {
|
|
5083
|
-
if (m[0] === DATA) requestValue = m[1];
|
|
5084
|
-
if (m[0] === ERROR) requestError = m[1];
|
|
5085
|
-
if (m[0] === COMPLETE || m[0] === ERROR) requestDone = true;
|
|
5086
|
-
}
|
|
5087
|
-
maybeEmitResult();
|
|
5088
|
-
});
|
|
5089
|
-
txUnsub = fromIDBTransaction(tx).subscribe((msgs) => {
|
|
5090
|
-
for (const m of msgs) {
|
|
5091
|
-
if (m[0] === ERROR) {
|
|
5092
|
-
finishWith([[ERROR, m[1]]]);
|
|
5093
|
-
return;
|
|
5094
|
-
}
|
|
5095
|
-
if (m[0] === COMPLETE) txDone = true;
|
|
5096
|
-
}
|
|
5097
|
-
maybeEmitResult();
|
|
5098
|
-
});
|
|
5099
|
-
};
|
|
5100
|
-
const openUnsub = openIdbNode(spec.dbName, spec.storeName, spec.version ?? 1).subscribe(
|
|
5101
|
-
(msgs) => {
|
|
5102
|
-
for (const m of msgs) {
|
|
5103
|
-
if (m[0] === DATA) {
|
|
5104
|
-
db = m[1];
|
|
5105
|
-
startRead();
|
|
5106
|
-
continue;
|
|
5107
|
-
}
|
|
5108
|
-
if (m[0] === ERROR) {
|
|
5109
|
-
finishWith([[ERROR, m[1]]]);
|
|
5110
|
-
return;
|
|
5111
|
-
}
|
|
5112
|
-
}
|
|
5113
|
-
}
|
|
5114
|
-
);
|
|
5115
|
-
return () => {
|
|
5116
|
-
reqUnsub?.();
|
|
5117
|
-
reqUnsub = void 0;
|
|
5118
|
-
txUnsub?.();
|
|
5119
|
-
txUnsub = void 0;
|
|
5120
|
-
openUnsub();
|
|
5121
|
-
close();
|
|
5122
|
-
};
|
|
5123
|
-
});
|
|
5124
|
-
}
|
|
5125
|
-
|
|
5126
|
-
// src/core/dynamic-node.ts
|
|
5127
|
-
function dynamicNode(fn, opts) {
|
|
5128
|
-
return new DynamicNodeImpl(fn, opts ?? {});
|
|
5129
|
-
}
|
|
5130
|
-
var DynamicNodeImpl = class {
|
|
5131
|
-
_optsName;
|
|
5132
|
-
_registryName;
|
|
5133
|
-
_describeKind;
|
|
5134
|
-
meta;
|
|
5135
|
-
_fn;
|
|
5136
|
-
_equals;
|
|
5137
|
-
_resubscribable;
|
|
5138
|
-
_resetOnTeardown;
|
|
5139
|
-
_autoComplete;
|
|
5140
|
-
_onMessage;
|
|
5141
|
-
_onResubscribe;
|
|
5142
|
-
/** @internal — read by {@link describeNode} for `accessHintForGuard`. */
|
|
5143
|
-
_guard;
|
|
5144
|
-
_lastMutation;
|
|
5145
|
-
_inspectorHook;
|
|
5146
|
-
// Sink tracking
|
|
5147
|
-
_sinkCount = 0;
|
|
5148
|
-
_singleDepSinkCount = 0;
|
|
5149
|
-
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
5150
|
-
// Actions object (for onMessage handler)
|
|
5151
|
-
_actions;
|
|
5152
|
-
_boundDownToSinks;
|
|
5153
|
-
// Mutable state
|
|
5154
|
-
_cached = NO_VALUE;
|
|
5155
|
-
_status = "disconnected";
|
|
5156
|
-
_terminal = false;
|
|
5157
|
-
_connected = false;
|
|
5158
|
-
_rewiring = false;
|
|
5159
|
-
// re-entrancy guard
|
|
5160
|
-
// Dynamic deps tracking
|
|
5161
|
-
_deps = [];
|
|
5162
|
-
_depUnsubs = [];
|
|
5163
|
-
_depIndexMap = /* @__PURE__ */ new Map();
|
|
5164
|
-
// node → index in _deps
|
|
5165
|
-
_dirtyBits = /* @__PURE__ */ new Set();
|
|
5166
|
-
_settledBits = /* @__PURE__ */ new Set();
|
|
5167
|
-
_completeBits = /* @__PURE__ */ new Set();
|
|
5168
|
-
// Sinks
|
|
5169
|
-
_sinks = null;
|
|
5170
|
-
constructor(fn, opts) {
|
|
5171
|
-
this._fn = fn;
|
|
5172
|
-
this._optsName = opts.name;
|
|
5173
|
-
this._describeKind = opts.describeKind;
|
|
5174
|
-
this._equals = opts.equals ?? Object.is;
|
|
5175
|
-
this._resubscribable = opts.resubscribable ?? false;
|
|
5176
|
-
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
5177
|
-
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
5178
|
-
this._onMessage = opts.onMessage;
|
|
5179
|
-
this._onResubscribe = opts.onResubscribe;
|
|
5180
|
-
this._guard = opts.guard;
|
|
5181
|
-
this._inspectorHook = void 0;
|
|
5182
|
-
const meta = {};
|
|
5183
|
-
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
5184
|
-
meta[k] = node({
|
|
5185
|
-
initial: v,
|
|
5186
|
-
name: `${opts.name ?? "dynamicNode"}:meta:${k}`,
|
|
5187
|
-
describeKind: "state",
|
|
5188
|
-
...opts.guard != null ? { guard: opts.guard } : {}
|
|
5189
|
-
});
|
|
5190
|
-
}
|
|
5191
|
-
Object.freeze(meta);
|
|
5192
|
-
this.meta = meta;
|
|
5193
|
-
const self = this;
|
|
5194
|
-
this._actions = {
|
|
5195
|
-
down(messages) {
|
|
5196
|
-
self._downInternal(messages);
|
|
5197
|
-
},
|
|
5198
|
-
emit(value) {
|
|
5199
|
-
self._downAutoValue(value);
|
|
5200
|
-
},
|
|
5201
|
-
up(messages) {
|
|
5202
|
-
for (const dep of self._deps) {
|
|
5203
|
-
dep.up?.(messages, { internal: true });
|
|
5204
|
-
}
|
|
5205
|
-
}
|
|
5206
|
-
};
|
|
5207
|
-
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
5208
|
-
}
|
|
5209
|
-
get name() {
|
|
5210
|
-
return this._registryName ?? this._optsName;
|
|
5211
|
-
}
|
|
5212
|
-
/** @internal */
|
|
5213
|
-
_assignRegistryName(localName) {
|
|
5214
|
-
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
5215
|
-
this._registryName = localName;
|
|
5216
|
-
}
|
|
5217
|
-
/**
|
|
5218
|
-
* @internal Attach/remove inspector hook for graph-level observability.
|
|
5219
|
-
* Returns a disposer that restores the previous hook.
|
|
5220
|
-
*/
|
|
5221
|
-
_setInspectorHook(hook) {
|
|
5222
|
-
const prev = this._inspectorHook;
|
|
5223
|
-
this._inspectorHook = hook;
|
|
5224
|
-
return () => {
|
|
5225
|
-
if (this._inspectorHook === hook) {
|
|
5226
|
-
this._inspectorHook = prev;
|
|
5227
|
-
}
|
|
5228
|
-
};
|
|
5229
|
-
}
|
|
5230
|
-
get status() {
|
|
5231
|
-
return this._status;
|
|
5232
|
-
}
|
|
5233
|
-
get lastMutation() {
|
|
5234
|
-
return this._lastMutation;
|
|
5235
|
-
}
|
|
5236
|
-
/** Versioning not yet supported on DynamicNodeImpl. */
|
|
5237
|
-
get v() {
|
|
5238
|
-
return void 0;
|
|
5239
|
-
}
|
|
5240
|
-
hasGuard() {
|
|
5241
|
-
return this._guard != null;
|
|
5242
|
-
}
|
|
5243
|
-
allowsObserve(actor) {
|
|
5244
|
-
if (this._guard == null) return true;
|
|
5245
|
-
return this._guard(normalizeActor(actor), "observe");
|
|
5246
|
-
}
|
|
5247
|
-
get() {
|
|
5248
|
-
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
5249
|
-
}
|
|
5250
|
-
down(messages, options) {
|
|
5251
|
-
if (messages.length === 0) return;
|
|
5252
|
-
if (!options?.internal && this._guard != null) {
|
|
5253
|
-
const actor = normalizeActor(options?.actor);
|
|
5254
|
-
const delivery = options?.delivery ?? "write";
|
|
5255
|
-
const action = delivery === "signal" ? "signal" : "write";
|
|
5256
|
-
if (!this._guard(actor, action)) {
|
|
5257
|
-
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
5258
|
-
}
|
|
5259
|
-
this._lastMutation = { actor, timestamp_ns: wallClockNs() };
|
|
5260
|
-
}
|
|
5261
|
-
this._downInternal(messages);
|
|
5262
|
-
}
|
|
5263
|
-
_downInternal(messages) {
|
|
5264
|
-
if (messages.length === 0) return;
|
|
5265
|
-
let sinkMessages = messages;
|
|
5266
|
-
if (this._terminal && !this._resubscribable) {
|
|
5267
|
-
const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
|
|
5268
|
-
if (pass.length === 0) return;
|
|
5269
|
-
sinkMessages = pass;
|
|
5270
|
-
}
|
|
5271
|
-
this._handleLocalLifecycle(sinkMessages);
|
|
5272
|
-
if (this._canSkipDirty()) {
|
|
5273
|
-
let hasPhase2 = false;
|
|
5274
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
5275
|
-
const t = sinkMessages[i][0];
|
|
5276
|
-
if (t === DATA || t === RESOLVED) {
|
|
5277
|
-
hasPhase2 = true;
|
|
5278
|
-
break;
|
|
5279
|
-
}
|
|
5280
|
-
}
|
|
5281
|
-
if (hasPhase2) {
|
|
5282
|
-
const filtered = [];
|
|
5283
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
5284
|
-
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
5285
|
-
}
|
|
5286
|
-
if (filtered.length > 0) {
|
|
5287
|
-
downWithBatch(this._boundDownToSinks, filtered);
|
|
5288
|
-
}
|
|
5289
|
-
return;
|
|
5290
|
-
}
|
|
5291
|
-
}
|
|
5292
|
-
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
5293
|
-
}
|
|
5294
|
-
_canSkipDirty() {
|
|
5295
|
-
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
5296
|
-
}
|
|
5297
|
-
subscribe(sink, hints) {
|
|
5298
|
-
if (hints?.actor != null && this._guard != null) {
|
|
5299
|
-
const actor = normalizeActor(hints.actor);
|
|
5300
|
-
if (!this._guard(actor, "observe")) {
|
|
5301
|
-
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
5302
|
-
}
|
|
5303
|
-
}
|
|
5304
|
-
if (this._terminal && this._resubscribable) {
|
|
5305
|
-
this._terminal = false;
|
|
5306
|
-
this._cached = NO_VALUE;
|
|
5307
|
-
this._status = "disconnected";
|
|
5308
|
-
this._onResubscribe?.();
|
|
5309
|
-
}
|
|
5310
|
-
this._sinkCount += 1;
|
|
5311
|
-
if (hints?.singleDep) {
|
|
5312
|
-
this._singleDepSinkCount += 1;
|
|
5313
|
-
this._singleDepSinks.add(sink);
|
|
5314
|
-
}
|
|
5315
|
-
if (this._sinks == null) {
|
|
5316
|
-
this._sinks = sink;
|
|
5317
|
-
} else if (typeof this._sinks === "function") {
|
|
5318
|
-
this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
|
|
5319
|
-
} else {
|
|
5320
|
-
this._sinks.add(sink);
|
|
5321
|
-
}
|
|
5322
|
-
if (!this._connected) {
|
|
5323
|
-
this._connect();
|
|
5324
|
-
}
|
|
5325
|
-
let removed = false;
|
|
5326
|
-
return () => {
|
|
5327
|
-
if (removed) return;
|
|
5328
|
-
removed = true;
|
|
5329
|
-
this._sinkCount -= 1;
|
|
5330
|
-
if (this._singleDepSinks.has(sink)) {
|
|
5331
|
-
this._singleDepSinkCount -= 1;
|
|
5332
|
-
this._singleDepSinks.delete(sink);
|
|
5333
|
-
}
|
|
5334
|
-
if (this._sinks == null) return;
|
|
5335
|
-
if (typeof this._sinks === "function") {
|
|
5336
|
-
if (this._sinks === sink) this._sinks = null;
|
|
5337
|
-
} else {
|
|
5338
|
-
this._sinks.delete(sink);
|
|
5339
|
-
if (this._sinks.size === 1) {
|
|
5340
|
-
const [only] = this._sinks;
|
|
5341
|
-
this._sinks = only;
|
|
5342
|
-
} else if (this._sinks.size === 0) {
|
|
5343
|
-
this._sinks = null;
|
|
5344
|
-
}
|
|
5226
|
+
return;
|
|
5345
5227
|
}
|
|
5346
|
-
if (
|
|
5347
|
-
|
|
5228
|
+
if (typeof requestValue !== "object" || Array.isArray(requestValue)) {
|
|
5229
|
+
finishWith([[DATA, false], [COMPLETE]]);
|
|
5230
|
+
return;
|
|
5231
|
+
}
|
|
5232
|
+
graph.restore(requestValue);
|
|
5233
|
+
finishWith([[DATA, true], [COMPLETE]]);
|
|
5234
|
+
};
|
|
5235
|
+
const startRead = () => {
|
|
5236
|
+
if (db === void 0 || reqUnsub !== void 0 || txUnsub !== void 0) return;
|
|
5237
|
+
const tx = db.transaction(spec.storeName, "readonly");
|
|
5238
|
+
const store = tx.objectStore(spec.storeName);
|
|
5239
|
+
reqUnsub = fromIDBRequest(store.get(key)).subscribe((msgs) => {
|
|
5240
|
+
for (const m of msgs) {
|
|
5241
|
+
if (m[0] === DATA) requestValue = m[1];
|
|
5242
|
+
if (m[0] === ERROR) requestError = m[1];
|
|
5243
|
+
if (m[0] === COMPLETE || m[0] === ERROR) requestDone = true;
|
|
5244
|
+
}
|
|
5245
|
+
maybeEmitResult();
|
|
5246
|
+
});
|
|
5247
|
+
txUnsub = fromIDBTransaction(tx).subscribe((msgs) => {
|
|
5248
|
+
for (const m of msgs) {
|
|
5249
|
+
if (m[0] === ERROR) {
|
|
5250
|
+
finishWith([[ERROR, m[1]]]);
|
|
5251
|
+
return;
|
|
5252
|
+
}
|
|
5253
|
+
if (m[0] === COMPLETE) txDone = true;
|
|
5254
|
+
}
|
|
5255
|
+
maybeEmitResult();
|
|
5256
|
+
});
|
|
5257
|
+
};
|
|
5258
|
+
const openUnsub = openIdbNode(spec.dbName, spec.storeName, spec.version ?? 1).subscribe(
|
|
5259
|
+
(msgs) => {
|
|
5260
|
+
for (const m of msgs) {
|
|
5261
|
+
if (m[0] === DATA) {
|
|
5262
|
+
db = m[1];
|
|
5263
|
+
startRead();
|
|
5264
|
+
continue;
|
|
5265
|
+
}
|
|
5266
|
+
if (m[0] === ERROR) {
|
|
5267
|
+
finishWith([[ERROR, m[1]]]);
|
|
5268
|
+
return;
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5348
5271
|
}
|
|
5272
|
+
);
|
|
5273
|
+
return () => {
|
|
5274
|
+
reqUnsub?.();
|
|
5275
|
+
reqUnsub = void 0;
|
|
5276
|
+
txUnsub?.();
|
|
5277
|
+
txUnsub = void 0;
|
|
5278
|
+
openUnsub();
|
|
5279
|
+
close();
|
|
5349
5280
|
};
|
|
5281
|
+
});
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
// src/core/dynamic-node.ts
|
|
5285
|
+
var MAX_RERUN = 16;
|
|
5286
|
+
function dynamicNode(fn, opts) {
|
|
5287
|
+
return new DynamicNodeImpl(fn, opts ?? {});
|
|
5288
|
+
}
|
|
5289
|
+
var DynamicNodeImpl = class extends NodeBase {
|
|
5290
|
+
_fn;
|
|
5291
|
+
_autoComplete;
|
|
5292
|
+
// Dynamic deps tracking
|
|
5293
|
+
/** @internal Read by `describeNode`. */
|
|
5294
|
+
_deps = [];
|
|
5295
|
+
_depUnsubs = [];
|
|
5296
|
+
_depIndexMap = /* @__PURE__ */ new Map();
|
|
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;
|
|
5306
|
+
constructor(fn, opts) {
|
|
5307
|
+
super(opts);
|
|
5308
|
+
this._fn = fn;
|
|
5309
|
+
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
5310
|
+
this.down = this.down.bind(this);
|
|
5311
|
+
this.up = this.up.bind(this);
|
|
5350
5312
|
}
|
|
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
|
+
});
|
|
5320
|
+
}
|
|
5321
|
+
/** Versioning not supported on DynamicNodeImpl (override base). */
|
|
5322
|
+
get v() {
|
|
5323
|
+
return void 0;
|
|
5324
|
+
}
|
|
5325
|
+
// --- Up / unsubscribe ---
|
|
5351
5326
|
up(messages, options) {
|
|
5352
5327
|
if (this._deps.length === 0) return;
|
|
5353
5328
|
if (!options?.internal && this._guard != null) {
|
|
@@ -5355,221 +5330,227 @@ var DynamicNodeImpl = class {
|
|
|
5355
5330
|
if (!this._guard(actor, "write")) {
|
|
5356
5331
|
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
5357
5332
|
}
|
|
5358
|
-
this.
|
|
5333
|
+
this._recordMutation(actor);
|
|
5359
5334
|
}
|
|
5360
5335
|
for (const dep of this._deps) {
|
|
5361
5336
|
dep.up?.(messages, options);
|
|
5362
5337
|
}
|
|
5363
5338
|
}
|
|
5364
|
-
|
|
5365
|
-
this.
|
|
5366
|
-
|
|
5367
|
-
// --- Private methods ---
|
|
5368
|
-
_downToSinks(messages) {
|
|
5369
|
-
if (this._sinks == null) return;
|
|
5370
|
-
if (typeof this._sinks === "function") {
|
|
5371
|
-
this._sinks(messages);
|
|
5372
|
-
return;
|
|
5373
|
-
}
|
|
5374
|
-
const snapshot = [...this._sinks];
|
|
5375
|
-
for (const sink of snapshot) {
|
|
5376
|
-
sink(messages);
|
|
5377
|
-
}
|
|
5378
|
-
}
|
|
5379
|
-
_handleLocalLifecycle(messages) {
|
|
5380
|
-
for (const m of messages) {
|
|
5381
|
-
const t = m[0];
|
|
5382
|
-
if (t === DATA) this._cached = m[1];
|
|
5383
|
-
if (t === INVALIDATE) {
|
|
5384
|
-
this._cached = NO_VALUE;
|
|
5385
|
-
this._status = "dirty";
|
|
5386
|
-
}
|
|
5387
|
-
if (t === DATA) {
|
|
5388
|
-
this._status = "settled";
|
|
5389
|
-
} else if (t === RESOLVED) {
|
|
5390
|
-
this._status = "resolved";
|
|
5391
|
-
} else if (t === DIRTY) {
|
|
5392
|
-
this._status = "dirty";
|
|
5393
|
-
} else if (t === COMPLETE) {
|
|
5394
|
-
this._status = "completed";
|
|
5395
|
-
this._terminal = true;
|
|
5396
|
-
} else if (t === ERROR) {
|
|
5397
|
-
this._status = "errored";
|
|
5398
|
-
this._terminal = true;
|
|
5399
|
-
}
|
|
5400
|
-
if (t === TEARDOWN) {
|
|
5401
|
-
if (this._resetOnTeardown) this._cached = NO_VALUE;
|
|
5402
|
-
try {
|
|
5403
|
-
this._propagateToMeta(t);
|
|
5404
|
-
} finally {
|
|
5405
|
-
this._disconnect();
|
|
5406
|
-
}
|
|
5407
|
-
}
|
|
5408
|
-
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
5409
|
-
this._propagateToMeta(t);
|
|
5410
|
-
}
|
|
5411
|
-
}
|
|
5412
|
-
}
|
|
5413
|
-
/** Propagate a signal to all companion meta nodes (best-effort). */
|
|
5414
|
-
_propagateToMeta(t) {
|
|
5415
|
-
for (const metaNode of Object.values(this.meta)) {
|
|
5416
|
-
try {
|
|
5417
|
-
metaNode.down([[t]], { internal: true });
|
|
5418
|
-
} catch {
|
|
5419
|
-
}
|
|
5339
|
+
_upInternal(messages) {
|
|
5340
|
+
for (const dep of this._deps) {
|
|
5341
|
+
dep.up?.(messages, { internal: true });
|
|
5420
5342
|
}
|
|
5421
5343
|
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
let unchanged;
|
|
5425
|
-
try {
|
|
5426
|
-
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
5427
|
-
} catch (eqErr) {
|
|
5428
|
-
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
5429
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
|
|
5430
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
5431
|
-
return;
|
|
5432
|
-
}
|
|
5433
|
-
if (unchanged) {
|
|
5434
|
-
this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
|
|
5435
|
-
return;
|
|
5436
|
-
}
|
|
5437
|
-
this._cached = value;
|
|
5438
|
-
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
5344
|
+
unsubscribe() {
|
|
5345
|
+
this._disconnect();
|
|
5439
5346
|
}
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
this._connected = true;
|
|
5443
|
-
this._status = "settled";
|
|
5444
|
-
this._dirtyBits.clear();
|
|
5445
|
-
this._settledBits.clear();
|
|
5446
|
-
this._completeBits.clear();
|
|
5347
|
+
// --- Activation hooks ---
|
|
5348
|
+
_onActivate() {
|
|
5447
5349
|
this._runFn();
|
|
5448
5350
|
}
|
|
5351
|
+
_doDeactivate() {
|
|
5352
|
+
this._disconnect();
|
|
5353
|
+
}
|
|
5449
5354
|
_disconnect() {
|
|
5450
|
-
if (!this._connected) return;
|
|
5451
5355
|
for (const unsub of this._depUnsubs) unsub();
|
|
5452
5356
|
this._depUnsubs = [];
|
|
5453
5357
|
this._deps = [];
|
|
5454
5358
|
this._depIndexMap.clear();
|
|
5455
|
-
this.
|
|
5456
|
-
this.
|
|
5457
|
-
this.
|
|
5458
|
-
this.
|
|
5359
|
+
this._depDirtyBits.clear();
|
|
5360
|
+
this._depSettledBits.clear();
|
|
5361
|
+
this._depCompleteBits.clear();
|
|
5362
|
+
this._cached = NO_VALUE;
|
|
5459
5363
|
this._status = "disconnected";
|
|
5460
5364
|
}
|
|
5365
|
+
// --- Fn execution with rewire buffer ---
|
|
5461
5366
|
_runFn() {
|
|
5462
5367
|
if (this._terminal && !this._resubscribable) return;
|
|
5463
|
-
if (this.
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
if (!trackedSet.has(dep)) {
|
|
5468
|
-
trackedSet.add(dep);
|
|
5469
|
-
trackedDeps.push(dep);
|
|
5470
|
-
}
|
|
5471
|
-
return dep.get();
|
|
5472
|
-
};
|
|
5368
|
+
if (this._running) return;
|
|
5369
|
+
this._running = true;
|
|
5370
|
+
this._rerunCount = 0;
|
|
5371
|
+
let result;
|
|
5473
5372
|
try {
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
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;
|
|
5485
5451
|
}
|
|
5452
|
+
this._downAutoValue(result);
|
|
5486
5453
|
}
|
|
5487
5454
|
_rewire(newDeps) {
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
const
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
|
|
5504
|
-
newUnsubs.push(unsub);
|
|
5505
|
-
}
|
|
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);
|
|
5506
5470
|
}
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5471
|
+
}
|
|
5472
|
+
for (const [dep, oldIdx] of oldMap) {
|
|
5473
|
+
if (!newMap.has(dep)) {
|
|
5474
|
+
this._depUnsubs[oldIdx]();
|
|
5511
5475
|
}
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
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)) {
|
|
5521
5486
|
newCompleteBits.add(newMap.get(dep));
|
|
5487
|
+
break;
|
|
5522
5488
|
}
|
|
5523
5489
|
}
|
|
5524
|
-
this._completeBits = newCompleteBits;
|
|
5525
|
-
} finally {
|
|
5526
|
-
this._rewiring = false;
|
|
5527
5490
|
}
|
|
5491
|
+
this._depCompleteBits = newCompleteBits;
|
|
5528
5492
|
}
|
|
5493
|
+
// --- Dep message handling ---
|
|
5529
5494
|
_handleDepMessages(index, messages) {
|
|
5530
|
-
if (this._rewiring)
|
|
5495
|
+
if (this._rewiring) {
|
|
5496
|
+
this._bufferedDepMessages.push({ index, msgs: messages });
|
|
5497
|
+
return;
|
|
5498
|
+
}
|
|
5531
5499
|
for (const msg of messages) {
|
|
5532
|
-
this.
|
|
5500
|
+
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
5533
5501
|
const t = msg[0];
|
|
5534
5502
|
if (this._onMessage) {
|
|
5535
5503
|
try {
|
|
5536
5504
|
if (this._onMessage(msg, index, this._actions)) continue;
|
|
5537
5505
|
} catch (err) {
|
|
5538
|
-
|
|
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]]);
|
|
5539
5511
|
return;
|
|
5540
5512
|
}
|
|
5541
5513
|
}
|
|
5514
|
+
if (messageTier(t) < 1) continue;
|
|
5542
5515
|
if (t === DIRTY) {
|
|
5543
|
-
this.
|
|
5544
|
-
this.
|
|
5545
|
-
|
|
5546
|
-
|
|
5516
|
+
const wasEmpty = this._depDirtyBits.size === 0;
|
|
5517
|
+
this._depDirtyBits.add(index);
|
|
5518
|
+
this._depSettledBits.delete(index);
|
|
5519
|
+
if (wasEmpty) {
|
|
5520
|
+
this._downInternal([[DIRTY]]);
|
|
5547
5521
|
}
|
|
5548
5522
|
continue;
|
|
5549
5523
|
}
|
|
5550
5524
|
if (t === DATA || t === RESOLVED) {
|
|
5551
|
-
if (!this.
|
|
5552
|
-
this.
|
|
5553
|
-
|
|
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
|
+
}
|
|
5554
5531
|
}
|
|
5555
|
-
this.
|
|
5532
|
+
this._depSettledBits.add(index);
|
|
5556
5533
|
if (this._allDirtySettled()) {
|
|
5557
|
-
this.
|
|
5558
|
-
this.
|
|
5559
|
-
this.
|
|
5534
|
+
this._depDirtyBits.clear();
|
|
5535
|
+
this._depSettledBits.clear();
|
|
5536
|
+
if (!this._running) {
|
|
5537
|
+
if (this._depValuesDifferFromTracked()) {
|
|
5538
|
+
this._runFn();
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5560
5541
|
}
|
|
5561
5542
|
continue;
|
|
5562
5543
|
}
|
|
5563
5544
|
if (t === COMPLETE) {
|
|
5564
|
-
this.
|
|
5565
|
-
this.
|
|
5566
|
-
this.
|
|
5545
|
+
this._depCompleteBits.add(index);
|
|
5546
|
+
this._depDirtyBits.delete(index);
|
|
5547
|
+
this._depSettledBits.delete(index);
|
|
5567
5548
|
if (this._allDirtySettled()) {
|
|
5568
|
-
this.
|
|
5569
|
-
this.
|
|
5570
|
-
this._runFn();
|
|
5549
|
+
this._depDirtyBits.clear();
|
|
5550
|
+
this._depSettledBits.clear();
|
|
5551
|
+
if (!this._running) this._runFn();
|
|
5571
5552
|
}
|
|
5572
|
-
if (this._autoComplete && this.
|
|
5553
|
+
if (this._autoComplete && this._depCompleteBits.size >= this._deps.length && this._deps.length > 0) {
|
|
5573
5554
|
this._downInternal([[COMPLETE]]);
|
|
5574
5555
|
}
|
|
5575
5556
|
continue;
|
|
@@ -5585,13 +5566,46 @@ var DynamicNodeImpl = class {
|
|
|
5585
5566
|
this._downInternal([msg]);
|
|
5586
5567
|
}
|
|
5587
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
|
+
}
|
|
5588
5588
|
_allDirtySettled() {
|
|
5589
|
-
if (this.
|
|
5590
|
-
for (const idx of this.
|
|
5591
|
-
if (!this.
|
|
5589
|
+
if (this._depDirtyBits.size === 0) return false;
|
|
5590
|
+
for (const idx of this._depDirtyBits) {
|
|
5591
|
+
if (!this._depSettledBits.has(idx)) return false;
|
|
5592
5592
|
}
|
|
5593
5593
|
return true;
|
|
5594
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
|
+
}
|
|
5595
5609
|
};
|
|
5596
5610
|
|
|
5597
5611
|
// src/extra/operators.ts
|
|
@@ -5654,9 +5668,12 @@ function reduce(source, reducer, seed, opts) {
|
|
|
5654
5668
|
}
|
|
5655
5669
|
function take(source, count, opts) {
|
|
5656
5670
|
if (count <= 0) {
|
|
5671
|
+
let completed = false;
|
|
5657
5672
|
return node(
|
|
5658
5673
|
[source],
|
|
5659
5674
|
(_d, a) => {
|
|
5675
|
+
if (completed) return void 0;
|
|
5676
|
+
completed = true;
|
|
5660
5677
|
a.down([[COMPLETE]]);
|
|
5661
5678
|
return void 0;
|
|
5662
5679
|
},
|
|
@@ -5664,8 +5681,15 @@ function take(source, count, opts) {
|
|
|
5664
5681
|
...operatorOpts3(opts),
|
|
5665
5682
|
completeWhenDepsComplete: false,
|
|
5666
5683
|
onMessage(msg, _i, a) {
|
|
5667
|
-
if (msg[0] ===
|
|
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;
|
|
5668
5691
|
a.down([[COMPLETE]]);
|
|
5692
|
+
return true;
|
|
5669
5693
|
}
|
|
5670
5694
|
return true;
|
|
5671
5695
|
}
|
|
@@ -5847,21 +5871,6 @@ function find(source, predicate, opts) {
|
|
|
5847
5871
|
function elementAt(source, index, opts) {
|
|
5848
5872
|
return take(skip(source, index, opts), 1, opts);
|
|
5849
5873
|
}
|
|
5850
|
-
function startWith(source, initial, opts) {
|
|
5851
|
-
let prepended = false;
|
|
5852
|
-
return node(
|
|
5853
|
-
[source],
|
|
5854
|
-
([v], a) => {
|
|
5855
|
-
if (!prepended) {
|
|
5856
|
-
prepended = true;
|
|
5857
|
-
a.emit(initial);
|
|
5858
|
-
}
|
|
5859
|
-
a.emit(v);
|
|
5860
|
-
return void 0;
|
|
5861
|
-
},
|
|
5862
|
-
operatorOpts3(opts)
|
|
5863
|
-
);
|
|
5864
|
-
}
|
|
5865
5874
|
function tap(source, fnOrObserver, opts) {
|
|
5866
5875
|
if (typeof fnOrObserver === "function") {
|
|
5867
5876
|
return derived(
|
|
@@ -6197,6 +6206,7 @@ function forwardInner(inner, a, onInnerComplete) {
|
|
|
6197
6206
|
let sawError = false;
|
|
6198
6207
|
const out = [];
|
|
6199
6208
|
for (const m of msgs) {
|
|
6209
|
+
if (messageTier(m[0]) < 1) continue;
|
|
6200
6210
|
if (m[0] === DATA) emitted = true;
|
|
6201
6211
|
if (m[0] === COMPLETE) sawComplete = true;
|
|
6202
6212
|
else {
|
|
@@ -6681,7 +6691,7 @@ function sample(source, notifier, opts) {
|
|
|
6681
6691
|
if (terminated) return true;
|
|
6682
6692
|
const t = msg[0];
|
|
6683
6693
|
const tier = messageTier(t);
|
|
6684
|
-
if (tier >=
|
|
6694
|
+
if (tier >= 4) {
|
|
6685
6695
|
if (t === ERROR) {
|
|
6686
6696
|
terminated = true;
|
|
6687
6697
|
a.down([msg]);
|
|
@@ -6697,6 +6707,7 @@ function sample(source, notifier, opts) {
|
|
|
6697
6707
|
a.down([msg]);
|
|
6698
6708
|
return true;
|
|
6699
6709
|
}
|
|
6710
|
+
terminated = true;
|
|
6700
6711
|
a.down([msg]);
|
|
6701
6712
|
return true;
|
|
6702
6713
|
}
|
|
@@ -7445,48 +7456,28 @@ function distill(source, extractFn, opts) {
|
|
|
7445
7456
|
|
|
7446
7457
|
// src/extra/observable.ts
|
|
7447
7458
|
var import_rxjs = require("rxjs");
|
|
7448
|
-
function toObservable(node2) {
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7459
|
+
function toObservable(node2, options) {
|
|
7460
|
+
if (options?.raw) {
|
|
7461
|
+
return new import_rxjs.Observable((subscriber) => {
|
|
7462
|
+
const unsub = node2.subscribe((msgs) => {
|
|
7452
7463
|
if (subscriber.closed) return;
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7464
|
+
subscriber.next(msgs);
|
|
7465
|
+
for (const m of msgs) {
|
|
7466
|
+
if (m[0] === ERROR) {
|
|
7467
|
+
subscriber.error(m[1]);
|
|
7468
|
+
return;
|
|
7469
|
+
}
|
|
7470
|
+
if (m[0] === COMPLETE) {
|
|
7471
|
+
subscriber.complete();
|
|
7472
|
+
return;
|
|
7473
|
+
}
|
|
7461
7474
|
}
|
|
7462
|
-
}
|
|
7475
|
+
});
|
|
7476
|
+
return unsub;
|
|
7463
7477
|
});
|
|
7464
|
-
|
|
7465
|
-
});
|
|
7466
|
-
}
|
|
7467
|
-
function toMessages$(node2) {
|
|
7478
|
+
}
|
|
7468
7479
|
return new import_rxjs.Observable((subscriber) => {
|
|
7469
7480
|
const unsub = node2.subscribe((msgs) => {
|
|
7470
|
-
if (subscriber.closed) return;
|
|
7471
|
-
subscriber.next(msgs);
|
|
7472
|
-
for (const m of msgs) {
|
|
7473
|
-
if (m[0] === ERROR) {
|
|
7474
|
-
subscriber.error(m[1]);
|
|
7475
|
-
return;
|
|
7476
|
-
}
|
|
7477
|
-
if (m[0] === COMPLETE) {
|
|
7478
|
-
subscriber.complete();
|
|
7479
|
-
return;
|
|
7480
|
-
}
|
|
7481
|
-
}
|
|
7482
|
-
});
|
|
7483
|
-
return unsub;
|
|
7484
|
-
});
|
|
7485
|
-
}
|
|
7486
|
-
function observeNode$(graph, path, options) {
|
|
7487
|
-
return new import_rxjs.Observable((subscriber) => {
|
|
7488
|
-
const handle = graph.observe(path, options);
|
|
7489
|
-
const unsub = handle.subscribe((msgs) => {
|
|
7490
7481
|
for (const m of msgs) {
|
|
7491
7482
|
if (subscriber.closed) return;
|
|
7492
7483
|
if (m[0] === DATA) {
|
|
@@ -7503,16 +7494,6 @@ function observeNode$(graph, path, options) {
|
|
|
7503
7494
|
return unsub;
|
|
7504
7495
|
});
|
|
7505
7496
|
}
|
|
7506
|
-
function observeGraph$(graph, options) {
|
|
7507
|
-
return new import_rxjs.Observable((subscriber) => {
|
|
7508
|
-
const handle = graph.observe(options);
|
|
7509
|
-
const unsub = handle.subscribe((nodePath, messages) => {
|
|
7510
|
-
if (subscriber.closed) return;
|
|
7511
|
-
subscriber.next({ path: nodePath, messages });
|
|
7512
|
-
});
|
|
7513
|
-
return unsub;
|
|
7514
|
-
});
|
|
7515
|
-
}
|
|
7516
7497
|
|
|
7517
7498
|
// src/extra/pubsub.ts
|
|
7518
7499
|
var PubSubHubImpl = class {
|
|
@@ -8040,7 +8021,7 @@ function workerBridge(target, opts) {
|
|
|
8040
8021
|
for (const m of msgs) {
|
|
8041
8022
|
const type = m[0];
|
|
8042
8023
|
if (type === DATA) continue;
|
|
8043
|
-
if (
|
|
8024
|
+
if (isLocalOnly(type)) continue;
|
|
8044
8025
|
if (type === ERROR) {
|
|
8045
8026
|
transport.post({
|
|
8046
8027
|
t: "e",
|
|
@@ -8166,7 +8147,7 @@ function workerSelf(target, opts) {
|
|
|
8166
8147
|
for (const m of msgs) {
|
|
8167
8148
|
const type = m[0];
|
|
8168
8149
|
if (type === DATA) continue;
|
|
8169
|
-
if (
|
|
8150
|
+
if (isLocalOnly(type)) continue;
|
|
8170
8151
|
if (type === ERROR) {
|
|
8171
8152
|
transport.post({
|
|
8172
8153
|
t: "e",
|
|
@@ -8310,6 +8291,7 @@ function workerSelf(target, opts) {
|
|
|
8310
8291
|
find,
|
|
8311
8292
|
first,
|
|
8312
8293
|
firstValueFrom,
|
|
8294
|
+
firstWhere,
|
|
8313
8295
|
flatMap,
|
|
8314
8296
|
forEach,
|
|
8315
8297
|
fromAny,
|
|
@@ -8356,8 +8338,6 @@ function workerSelf(target, opts) {
|
|
|
8356
8338
|
mergeMap,
|
|
8357
8339
|
nameToSignal,
|
|
8358
8340
|
never,
|
|
8359
|
-
observeGraph$,
|
|
8360
|
-
observeNode$,
|
|
8361
8341
|
of,
|
|
8362
8342
|
pairwise,
|
|
8363
8343
|
parseCron,
|
|
@@ -8389,7 +8369,6 @@ function workerSelf(target, opts) {
|
|
|
8389
8369
|
shareReplay,
|
|
8390
8370
|
signalToName,
|
|
8391
8371
|
skip,
|
|
8392
|
-
startWith,
|
|
8393
8372
|
switchMap,
|
|
8394
8373
|
take,
|
|
8395
8374
|
takeUntil,
|
|
@@ -8406,7 +8385,6 @@ function workerSelf(target, opts) {
|
|
|
8406
8385
|
toFile,
|
|
8407
8386
|
toKafka,
|
|
8408
8387
|
toLoki,
|
|
8409
|
-
toMessages$,
|
|
8410
8388
|
toMongo,
|
|
8411
8389
|
toNATS,
|
|
8412
8390
|
toObservable,
|