@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
|
@@ -95,11 +95,8 @@ __export(nestjs_exports, {
|
|
|
95
95
|
getActor: () => getActor,
|
|
96
96
|
getGraphToken: () => getGraphToken,
|
|
97
97
|
getNodeToken: () => getNodeToken,
|
|
98
|
-
observeGraph$: () => observeGraph$,
|
|
99
|
-
observeNode$: () => observeNode$,
|
|
100
98
|
observeSSE: () => observeSSE,
|
|
101
99
|
observeSubscription: () => observeSubscription,
|
|
102
|
-
toMessages$: () => toMessages$,
|
|
103
100
|
toObservable: () => toObservable
|
|
104
101
|
});
|
|
105
102
|
module.exports = __toCommonJS(nestjs_exports);
|
|
@@ -108,6 +105,7 @@ module.exports = __toCommonJS(nestjs_exports);
|
|
|
108
105
|
var import_rxjs = require("rxjs");
|
|
109
106
|
|
|
110
107
|
// src/core/messages.ts
|
|
108
|
+
var START = /* @__PURE__ */ Symbol.for("graphrefly/START");
|
|
111
109
|
var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
|
|
112
110
|
var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
|
|
113
111
|
var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
|
|
@@ -117,13 +115,27 @@ var RESUME = /* @__PURE__ */ Symbol.for("graphrefly/RESUME");
|
|
|
117
115
|
var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
|
|
118
116
|
var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
|
|
119
117
|
var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
|
|
118
|
+
var knownMessageTypes = [
|
|
119
|
+
START,
|
|
120
|
+
DATA,
|
|
121
|
+
DIRTY,
|
|
122
|
+
RESOLVED,
|
|
123
|
+
INVALIDATE,
|
|
124
|
+
PAUSE,
|
|
125
|
+
RESUME,
|
|
126
|
+
TEARDOWN,
|
|
127
|
+
COMPLETE,
|
|
128
|
+
ERROR
|
|
129
|
+
];
|
|
130
|
+
var knownMessageSet = new Set(knownMessageTypes);
|
|
120
131
|
function messageTier(t) {
|
|
121
|
-
if (t ===
|
|
122
|
-
if (t ===
|
|
123
|
-
if (t ===
|
|
124
|
-
if (t ===
|
|
125
|
-
if (t ===
|
|
126
|
-
return
|
|
132
|
+
if (t === START) return 0;
|
|
133
|
+
if (t === DIRTY || t === INVALIDATE) return 1;
|
|
134
|
+
if (t === PAUSE || t === RESUME) return 2;
|
|
135
|
+
if (t === DATA || t === RESOLVED) return 3;
|
|
136
|
+
if (t === COMPLETE || t === ERROR) return 4;
|
|
137
|
+
if (t === TEARDOWN) return 5;
|
|
138
|
+
return 1;
|
|
127
139
|
}
|
|
128
140
|
function isPhase2Message(msg) {
|
|
129
141
|
const t = msg[0];
|
|
@@ -137,48 +149,28 @@ function propagatesToMeta(t) {
|
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
// src/extra/observable.ts
|
|
140
|
-
function toObservable(node2) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
function toObservable(node2, options) {
|
|
153
|
+
if (options?.raw) {
|
|
154
|
+
return new import_rxjs.Observable((subscriber) => {
|
|
155
|
+
const unsub = node2.subscribe((msgs) => {
|
|
144
156
|
if (subscriber.closed) return;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
subscriber.next(msgs);
|
|
158
|
+
for (const m of msgs) {
|
|
159
|
+
if (m[0] === ERROR) {
|
|
160
|
+
subscriber.error(m[1]);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (m[0] === COMPLETE) {
|
|
164
|
+
subscriber.complete();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
153
167
|
}
|
|
154
|
-
}
|
|
168
|
+
});
|
|
169
|
+
return unsub;
|
|
155
170
|
});
|
|
156
|
-
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
function toMessages$(node2) {
|
|
171
|
+
}
|
|
160
172
|
return new import_rxjs.Observable((subscriber) => {
|
|
161
173
|
const unsub = node2.subscribe((msgs) => {
|
|
162
|
-
if (subscriber.closed) return;
|
|
163
|
-
subscriber.next(msgs);
|
|
164
|
-
for (const m of msgs) {
|
|
165
|
-
if (m[0] === ERROR) {
|
|
166
|
-
subscriber.error(m[1]);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (m[0] === COMPLETE) {
|
|
170
|
-
subscriber.complete();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
return unsub;
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
function observeNode$(graph, path, options) {
|
|
179
|
-
return new import_rxjs.Observable((subscriber) => {
|
|
180
|
-
const handle = graph.observe(path, options);
|
|
181
|
-
const unsub = handle.subscribe((msgs) => {
|
|
182
174
|
for (const m of msgs) {
|
|
183
175
|
if (subscriber.closed) return;
|
|
184
176
|
if (m[0] === DATA) {
|
|
@@ -195,16 +187,6 @@ function observeNode$(graph, path, options) {
|
|
|
195
187
|
return unsub;
|
|
196
188
|
});
|
|
197
189
|
}
|
|
198
|
-
function observeGraph$(graph, options) {
|
|
199
|
-
return new import_rxjs.Observable((subscriber) => {
|
|
200
|
-
const handle = graph.observe(options);
|
|
201
|
-
const unsub = handle.subscribe((nodePath, messages) => {
|
|
202
|
-
if (subscriber.closed) return;
|
|
203
|
-
subscriber.next({ path: nodePath, messages });
|
|
204
|
-
});
|
|
205
|
-
return unsub;
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
190
|
|
|
209
191
|
// src/compat/nestjs/decorators.ts
|
|
210
192
|
var import_common = require("@nestjs/common");
|
|
@@ -339,6 +321,82 @@ function normalizeActor(actor) {
|
|
|
339
321
|
};
|
|
340
322
|
}
|
|
341
323
|
|
|
324
|
+
// src/core/guard.ts
|
|
325
|
+
var GuardDenied = class extends Error {
|
|
326
|
+
actor;
|
|
327
|
+
action;
|
|
328
|
+
nodeName;
|
|
329
|
+
/**
|
|
330
|
+
* @param details - Actor, action, and optional node name for the denial.
|
|
331
|
+
* @param message - Optional override for the default error message.
|
|
332
|
+
*/
|
|
333
|
+
constructor(details, message) {
|
|
334
|
+
super(
|
|
335
|
+
message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
|
|
336
|
+
);
|
|
337
|
+
this.name = "GuardDenied";
|
|
338
|
+
this.actor = details.actor;
|
|
339
|
+
this.action = details.action;
|
|
340
|
+
this.nodeName = details.nodeName;
|
|
341
|
+
}
|
|
342
|
+
/** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
|
|
343
|
+
get node() {
|
|
344
|
+
return this.nodeName;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
function normalizeActions(action) {
|
|
348
|
+
if (Array.isArray(action)) {
|
|
349
|
+
return [...action];
|
|
350
|
+
}
|
|
351
|
+
return [action];
|
|
352
|
+
}
|
|
353
|
+
function matchesActions(set, action) {
|
|
354
|
+
return set.has(action) || set.has("*");
|
|
355
|
+
}
|
|
356
|
+
function policy(build) {
|
|
357
|
+
const rules = [];
|
|
358
|
+
const allow = (action, opts) => {
|
|
359
|
+
rules.push({
|
|
360
|
+
kind: "allow",
|
|
361
|
+
actions: new Set(normalizeActions(action)),
|
|
362
|
+
where: opts?.where ?? (() => true)
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
const deny = (action, opts) => {
|
|
366
|
+
rules.push({
|
|
367
|
+
kind: "deny",
|
|
368
|
+
actions: new Set(normalizeActions(action)),
|
|
369
|
+
where: opts?.where ?? (() => true)
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
build(allow, deny);
|
|
373
|
+
return (actor, action) => {
|
|
374
|
+
let denied = false;
|
|
375
|
+
let allowed = false;
|
|
376
|
+
for (const r of rules) {
|
|
377
|
+
if (!matchesActions(r.actions, action)) continue;
|
|
378
|
+
if (!r.where(actor)) continue;
|
|
379
|
+
if (r.kind === "deny") {
|
|
380
|
+
denied = true;
|
|
381
|
+
} else {
|
|
382
|
+
allowed = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (denied) return false;
|
|
386
|
+
return allowed;
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
|
|
390
|
+
function accessHintForGuard(guard) {
|
|
391
|
+
const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
|
|
392
|
+
if (allowed.length === 0) return "restricted";
|
|
393
|
+
if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
|
|
394
|
+
return "both";
|
|
395
|
+
}
|
|
396
|
+
if (allowed.length === 1) return allowed[0];
|
|
397
|
+
return allowed.join("+");
|
|
398
|
+
}
|
|
399
|
+
|
|
342
400
|
// src/core/batch.ts
|
|
343
401
|
var MAX_DRAIN_ITERATIONS = 1e3;
|
|
344
402
|
var batchDepth = 0;
|
|
@@ -495,14 +553,14 @@ function _downSequential(sink, messages, phase = 2) {
|
|
|
495
553
|
const dataQueue = phase === 3 ? pendingPhase3 : pendingPhase2;
|
|
496
554
|
for (const msg of messages) {
|
|
497
555
|
const tier = messageTier(msg[0]);
|
|
498
|
-
if (tier ===
|
|
556
|
+
if (tier === 3) {
|
|
499
557
|
if (isBatching()) {
|
|
500
558
|
const m = msg;
|
|
501
559
|
dataQueue.push(() => sink([m]));
|
|
502
560
|
} else {
|
|
503
561
|
sink([msg]);
|
|
504
562
|
}
|
|
505
|
-
} else if (tier >=
|
|
563
|
+
} else if (tier >= 4) {
|
|
506
564
|
if (isBatching()) {
|
|
507
565
|
const m = msg;
|
|
508
566
|
pendingPhase3.push(() => sink([m]));
|
|
@@ -515,82 +573,6 @@ function _downSequential(sink, messages, phase = 2) {
|
|
|
515
573
|
}
|
|
516
574
|
}
|
|
517
575
|
|
|
518
|
-
// src/core/guard.ts
|
|
519
|
-
var GuardDenied = class extends Error {
|
|
520
|
-
actor;
|
|
521
|
-
action;
|
|
522
|
-
nodeName;
|
|
523
|
-
/**
|
|
524
|
-
* @param details - Actor, action, and optional node name for the denial.
|
|
525
|
-
* @param message - Optional override for the default error message.
|
|
526
|
-
*/
|
|
527
|
-
constructor(details, message) {
|
|
528
|
-
super(
|
|
529
|
-
message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
|
|
530
|
-
);
|
|
531
|
-
this.name = "GuardDenied";
|
|
532
|
-
this.actor = details.actor;
|
|
533
|
-
this.action = details.action;
|
|
534
|
-
this.nodeName = details.nodeName;
|
|
535
|
-
}
|
|
536
|
-
/** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
|
|
537
|
-
get node() {
|
|
538
|
-
return this.nodeName;
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
function normalizeActions(action) {
|
|
542
|
-
if (Array.isArray(action)) {
|
|
543
|
-
return [...action];
|
|
544
|
-
}
|
|
545
|
-
return [action];
|
|
546
|
-
}
|
|
547
|
-
function matchesActions(set, action) {
|
|
548
|
-
return set.has(action) || set.has("*");
|
|
549
|
-
}
|
|
550
|
-
function policy(build) {
|
|
551
|
-
const rules = [];
|
|
552
|
-
const allow = (action, opts) => {
|
|
553
|
-
rules.push({
|
|
554
|
-
kind: "allow",
|
|
555
|
-
actions: new Set(normalizeActions(action)),
|
|
556
|
-
where: opts?.where ?? (() => true)
|
|
557
|
-
});
|
|
558
|
-
};
|
|
559
|
-
const deny = (action, opts) => {
|
|
560
|
-
rules.push({
|
|
561
|
-
kind: "deny",
|
|
562
|
-
actions: new Set(normalizeActions(action)),
|
|
563
|
-
where: opts?.where ?? (() => true)
|
|
564
|
-
});
|
|
565
|
-
};
|
|
566
|
-
build(allow, deny);
|
|
567
|
-
return (actor, action) => {
|
|
568
|
-
let denied = false;
|
|
569
|
-
let allowed = false;
|
|
570
|
-
for (const r of rules) {
|
|
571
|
-
if (!matchesActions(r.actions, action)) continue;
|
|
572
|
-
if (!r.where(actor)) continue;
|
|
573
|
-
if (r.kind === "deny") {
|
|
574
|
-
denied = true;
|
|
575
|
-
} else {
|
|
576
|
-
allowed = true;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (denied) return false;
|
|
580
|
-
return allowed;
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
|
|
584
|
-
function accessHintForGuard(guard) {
|
|
585
|
-
const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
|
|
586
|
-
if (allowed.length === 0) return "restricted";
|
|
587
|
-
if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
|
|
588
|
-
return "both";
|
|
589
|
-
}
|
|
590
|
-
if (allowed.length === 1) return allowed[0];
|
|
591
|
-
return allowed.join("+");
|
|
592
|
-
}
|
|
593
|
-
|
|
594
576
|
// src/core/versioning.ts
|
|
595
577
|
var import_node_crypto = require("crypto");
|
|
596
578
|
function canonicalizeForHash(value) {
|
|
@@ -643,10 +625,24 @@ function advanceVersion(info, newValue, hashFn) {
|
|
|
643
625
|
}
|
|
644
626
|
}
|
|
645
627
|
|
|
646
|
-
// src/core/node.ts
|
|
628
|
+
// src/core/node-base.ts
|
|
647
629
|
var NO_VALUE = /* @__PURE__ */ Symbol.for("graphrefly/NO_VALUE");
|
|
648
630
|
var CLEANUP_RESULT = /* @__PURE__ */ Symbol.for("graphrefly/CLEANUP_RESULT");
|
|
649
|
-
|
|
631
|
+
var isCleanupResult = (value) => typeof value === "object" && value !== null && CLEANUP_RESULT in value;
|
|
632
|
+
var isCleanupFn = (value) => typeof value === "function";
|
|
633
|
+
function statusAfterMessage(status, msg) {
|
|
634
|
+
const t = msg[0];
|
|
635
|
+
if (t === DIRTY) return "dirty";
|
|
636
|
+
if (t === DATA) return "settled";
|
|
637
|
+
if (t === RESOLVED) return "resolved";
|
|
638
|
+
if (t === COMPLETE) return "completed";
|
|
639
|
+
if (t === ERROR) return "errored";
|
|
640
|
+
if (t === INVALIDATE) return "dirty";
|
|
641
|
+
if (t === TEARDOWN) return "disconnected";
|
|
642
|
+
return status;
|
|
643
|
+
}
|
|
644
|
+
function createIntBitSet(size) {
|
|
645
|
+
const fullMask = size >= 32 ? -1 : ~(-1 << size);
|
|
650
646
|
let bits = 0;
|
|
651
647
|
return {
|
|
652
648
|
set(i) {
|
|
@@ -659,7 +655,8 @@ function createIntBitSet() {
|
|
|
659
655
|
return (bits & 1 << i) !== 0;
|
|
660
656
|
},
|
|
661
657
|
covers(other) {
|
|
662
|
-
|
|
658
|
+
const otherBits = other._bits();
|
|
659
|
+
return (bits & otherBits) === otherBits;
|
|
663
660
|
},
|
|
664
661
|
any() {
|
|
665
662
|
return bits !== 0;
|
|
@@ -667,6 +664,9 @@ function createIntBitSet() {
|
|
|
667
664
|
reset() {
|
|
668
665
|
bits = 0;
|
|
669
666
|
},
|
|
667
|
+
setAll() {
|
|
668
|
+
bits = fullMask;
|
|
669
|
+
},
|
|
670
670
|
_bits() {
|
|
671
671
|
return bits;
|
|
672
672
|
}
|
|
@@ -674,6 +674,8 @@ function createIntBitSet() {
|
|
|
674
674
|
}
|
|
675
675
|
function createArrayBitSet(size) {
|
|
676
676
|
const words = new Uint32Array(Math.ceil(size / 32));
|
|
677
|
+
const lastBits = size % 32;
|
|
678
|
+
const lastWordMask = lastBits === 0 ? 4294967295 : (1 << lastBits) - 1 >>> 0;
|
|
677
679
|
return {
|
|
678
680
|
set(i) {
|
|
679
681
|
words[i >>> 5] |= 1 << (i & 31);
|
|
@@ -700,130 +702,103 @@ function createArrayBitSet(size) {
|
|
|
700
702
|
reset() {
|
|
701
703
|
words.fill(0);
|
|
702
704
|
},
|
|
705
|
+
setAll() {
|
|
706
|
+
for (let w = 0; w < words.length - 1; w++) words[w] = 4294967295;
|
|
707
|
+
if (words.length > 0) words[words.length - 1] = lastWordMask;
|
|
708
|
+
},
|
|
703
709
|
_words: words
|
|
704
710
|
};
|
|
705
711
|
}
|
|
706
712
|
function createBitSet(size) {
|
|
707
|
-
return size <= 31 ? createIntBitSet() : createArrayBitSet(size);
|
|
713
|
+
return size <= 31 ? createIntBitSet(size) : createArrayBitSet(size);
|
|
708
714
|
}
|
|
709
|
-
var
|
|
710
|
-
|
|
711
|
-
var isCleanupResult = (value) => typeof value === "object" && value !== null && CLEANUP_RESULT in value;
|
|
712
|
-
var isCleanupFn = (value) => typeof value === "function";
|
|
713
|
-
var statusAfterMessage = (status, msg) => {
|
|
714
|
-
const t = msg[0];
|
|
715
|
-
if (t === DIRTY) return "dirty";
|
|
716
|
-
if (t === DATA) return "settled";
|
|
717
|
-
if (t === RESOLVED) return "resolved";
|
|
718
|
-
if (t === COMPLETE) return "completed";
|
|
719
|
-
if (t === ERROR) return "errored";
|
|
720
|
-
if (t === INVALIDATE) return "dirty";
|
|
721
|
-
if (t === TEARDOWN) return "disconnected";
|
|
722
|
-
return status;
|
|
723
|
-
};
|
|
724
|
-
var NodeImpl = class {
|
|
725
|
-
// --- Configuration (set once, never reassigned) ---
|
|
715
|
+
var NodeBase = class {
|
|
716
|
+
// --- Identity (set once) ---
|
|
726
717
|
_optsName;
|
|
727
718
|
_registryName;
|
|
728
|
-
/** @internal
|
|
719
|
+
/** @internal Read by `describeNode` before inference. */
|
|
729
720
|
_describeKind;
|
|
730
721
|
meta;
|
|
731
|
-
|
|
732
|
-
_fn;
|
|
733
|
-
_opts;
|
|
722
|
+
// --- Options ---
|
|
734
723
|
_equals;
|
|
724
|
+
_resubscribable;
|
|
725
|
+
_resetOnTeardown;
|
|
726
|
+
_onResubscribe;
|
|
735
727
|
_onMessage;
|
|
736
|
-
/** @internal
|
|
728
|
+
/** @internal Read by `describeNode` for `accessHintForGuard`. */
|
|
737
729
|
_guard;
|
|
730
|
+
/** @internal Subclasses update this through {@link _recordMutation}. */
|
|
738
731
|
_lastMutation;
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
// ---
|
|
732
|
+
// --- Versioning ---
|
|
733
|
+
_hashFn;
|
|
734
|
+
_versioning;
|
|
735
|
+
// --- Lifecycle state ---
|
|
736
|
+
/** @internal Read by `describeNode` and `graph.ts`. */
|
|
743
737
|
_cached;
|
|
738
|
+
/** @internal Read externally via `get status()`. */
|
|
744
739
|
_status;
|
|
745
740
|
_terminal = false;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
_manualEmitUsed = false;
|
|
741
|
+
_active = false;
|
|
742
|
+
// --- Sink storage ---
|
|
743
|
+
/** @internal Read by `graph/profile.ts` for subscriber counts. */
|
|
750
744
|
_sinkCount = 0;
|
|
751
745
|
_singleDepSinkCount = 0;
|
|
752
|
-
// --- Object/collection state ---
|
|
753
|
-
_depDirtyMask;
|
|
754
|
-
_depSettledMask;
|
|
755
|
-
_depCompleteMask;
|
|
756
|
-
_allDepsCompleteMask;
|
|
757
|
-
_lastDepValues;
|
|
758
|
-
_cleanup;
|
|
759
|
-
_sinks = null;
|
|
760
746
|
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
761
|
-
|
|
747
|
+
_sinks = null;
|
|
748
|
+
// --- Actions + bound helpers ---
|
|
762
749
|
_actions;
|
|
763
750
|
_boundDownToSinks;
|
|
751
|
+
// --- Inspector hook (Graph observability) ---
|
|
764
752
|
_inspectorHook;
|
|
765
|
-
|
|
766
|
-
_hashFn;
|
|
767
|
-
constructor(deps, fn, opts) {
|
|
768
|
-
this._deps = deps;
|
|
769
|
-
this._fn = fn;
|
|
770
|
-
this._opts = opts;
|
|
753
|
+
constructor(opts) {
|
|
771
754
|
this._optsName = opts.name;
|
|
772
755
|
this._describeKind = opts.describeKind;
|
|
773
756
|
this._equals = opts.equals ?? Object.is;
|
|
757
|
+
this._resubscribable = opts.resubscribable ?? false;
|
|
758
|
+
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
759
|
+
this._onResubscribe = opts.onResubscribe;
|
|
774
760
|
this._onMessage = opts.onMessage;
|
|
775
761
|
this._guard = opts.guard;
|
|
776
|
-
this._hasDeps = deps.length > 0;
|
|
777
|
-
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
778
|
-
this._isSingleDep = deps.length === 1 && fn != null;
|
|
779
762
|
this._cached = "initial" in opts ? opts.initial : NO_VALUE;
|
|
780
|
-
this._status =
|
|
763
|
+
this._status = "disconnected";
|
|
781
764
|
this._hashFn = opts.versioningHash ?? defaultHash;
|
|
782
765
|
this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached === NO_VALUE ? void 0 : this._cached, {
|
|
783
766
|
id: opts.versioningId,
|
|
784
767
|
hash: this._hashFn
|
|
785
768
|
}) : void 0;
|
|
786
|
-
this._depDirtyMask = createBitSet(deps.length);
|
|
787
|
-
this._depSettledMask = createBitSet(deps.length);
|
|
788
|
-
this._depCompleteMask = createBitSet(deps.length);
|
|
789
|
-
this._allDepsCompleteMask = createBitSet(deps.length);
|
|
790
|
-
for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
|
|
791
769
|
const meta = {};
|
|
792
770
|
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
793
|
-
meta[k] =
|
|
794
|
-
initial: v,
|
|
795
|
-
name: `${opts.name ?? "node"}:meta:${k}`,
|
|
796
|
-
describeKind: "state",
|
|
797
|
-
...opts.guard != null ? { guard: opts.guard } : {}
|
|
798
|
-
});
|
|
771
|
+
meta[k] = this._createMetaNode(k, v, opts);
|
|
799
772
|
}
|
|
800
773
|
Object.freeze(meta);
|
|
801
774
|
this.meta = meta;
|
|
802
775
|
const self = this;
|
|
803
776
|
this._actions = {
|
|
804
777
|
down(messages) {
|
|
805
|
-
self.
|
|
778
|
+
self._onManualEmit();
|
|
806
779
|
self._downInternal(messages);
|
|
807
780
|
},
|
|
808
781
|
emit(value) {
|
|
809
|
-
self.
|
|
782
|
+
self._onManualEmit();
|
|
810
783
|
self._downAutoValue(value);
|
|
811
784
|
},
|
|
812
785
|
up(messages) {
|
|
813
786
|
self._upInternal(messages);
|
|
814
787
|
}
|
|
815
788
|
};
|
|
816
|
-
this.down = this.down.bind(this);
|
|
817
|
-
this.up = this.up.bind(this);
|
|
818
789
|
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
819
790
|
}
|
|
791
|
+
/**
|
|
792
|
+
* Subclass hook invoked by `actions.down` / `actions.emit`. Default no-op;
|
|
793
|
+
* {@link NodeImpl} overrides to set `_manualEmitUsed`.
|
|
794
|
+
*/
|
|
795
|
+
_onManualEmit() {
|
|
796
|
+
}
|
|
797
|
+
// --- Identity getters ---
|
|
820
798
|
get name() {
|
|
821
799
|
return this._registryName ?? this._optsName;
|
|
822
800
|
}
|
|
823
|
-
/**
|
|
824
|
-
* When a node is registered with {@link Graph.add} without an options `name`,
|
|
825
|
-
* the graph assigns the registry local name for introspection (parity with graphrefly-py).
|
|
826
|
-
*/
|
|
801
|
+
/** @internal Assigned by `Graph.add` when registered without an options `name`. */
|
|
827
802
|
_assignRegistryName(localName) {
|
|
828
803
|
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
829
804
|
this._registryName = localName;
|
|
@@ -841,7 +816,10 @@ var NodeImpl = class {
|
|
|
841
816
|
}
|
|
842
817
|
};
|
|
843
818
|
}
|
|
844
|
-
|
|
819
|
+
/** @internal Used by subclasses to surface inspector events. */
|
|
820
|
+
_emitInspectorHook(event) {
|
|
821
|
+
this._inspectorHook?.(event);
|
|
822
|
+
}
|
|
845
823
|
get status() {
|
|
846
824
|
return this._status;
|
|
847
825
|
}
|
|
@@ -851,15 +829,7 @@ var NodeImpl = class {
|
|
|
851
829
|
get v() {
|
|
852
830
|
return this._versioning;
|
|
853
831
|
}
|
|
854
|
-
/**
|
|
855
|
-
* Retroactively apply versioning to a node that was created without it.
|
|
856
|
-
* No-op if versioning is already enabled.
|
|
857
|
-
*
|
|
858
|
-
* Version starts at 0 regardless of prior DATA emissions — it tracks
|
|
859
|
-
* changes from the moment versioning is enabled, not historical ones.
|
|
860
|
-
*
|
|
861
|
-
* @internal — used by {@link Graph.setVersioning}.
|
|
862
|
-
*/
|
|
832
|
+
/** @internal Used by `Graph.setVersioning` to retroactively apply versioning. */
|
|
863
833
|
_applyVersioning(level, opts) {
|
|
864
834
|
if (this._versioning != null) return;
|
|
865
835
|
this._hashFn = opts?.hash ?? this._hashFn;
|
|
@@ -879,6 +849,7 @@ var NodeImpl = class {
|
|
|
879
849
|
if (this._guard == null) return true;
|
|
880
850
|
return this._guard(normalizeActor(actor), "observe");
|
|
881
851
|
}
|
|
852
|
+
// --- Public transport ---
|
|
882
853
|
get() {
|
|
883
854
|
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
884
855
|
}
|
|
@@ -891,43 +862,25 @@ var NodeImpl = class {
|
|
|
891
862
|
if (!this._guard(actor, action)) {
|
|
892
863
|
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
893
864
|
}
|
|
894
|
-
this.
|
|
865
|
+
this._recordMutation(actor);
|
|
895
866
|
}
|
|
896
867
|
this._downInternal(messages);
|
|
897
868
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
this.
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
912
|
-
const t = sinkMessages[i][0];
|
|
913
|
-
if (t === DATA || t === RESOLVED) {
|
|
914
|
-
hasPhase2 = true;
|
|
915
|
-
break;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
if (hasPhase2) {
|
|
919
|
-
const filtered = [];
|
|
920
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
921
|
-
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
922
|
-
}
|
|
923
|
-
if (filtered.length > 0) {
|
|
924
|
-
downWithBatch(this._boundDownToSinks, filtered);
|
|
925
|
-
}
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
869
|
+
/** @internal Record a successful guarded mutation (called by `down` and subclass `up`). */
|
|
870
|
+
_recordMutation(actor) {
|
|
871
|
+
this._lastMutation = { actor, timestamp_ns: wallClockNs() };
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* At-most-once deactivation guard. Both TEARDOWN (eager) and
|
|
875
|
+
* unsubscribe-body (lazy) call this. The `_active` flag ensures
|
|
876
|
+
* `_doDeactivate` runs exactly once per activation cycle.
|
|
877
|
+
*/
|
|
878
|
+
_onDeactivate() {
|
|
879
|
+
if (!this._active) return;
|
|
880
|
+
this._active = false;
|
|
881
|
+
this._doDeactivate();
|
|
930
882
|
}
|
|
883
|
+
// --- Subscribe (uniform across node shapes) ---
|
|
931
884
|
subscribe(sink, hints) {
|
|
932
885
|
if (hints?.actor != null && this._guard != null) {
|
|
933
886
|
const actor = normalizeActor(hints.actor);
|
|
@@ -935,17 +888,21 @@ var NodeImpl = class {
|
|
|
935
888
|
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
936
889
|
}
|
|
937
890
|
}
|
|
938
|
-
if (this._terminal && this.
|
|
891
|
+
if (this._terminal && this._resubscribable) {
|
|
939
892
|
this._terminal = false;
|
|
940
893
|
this._cached = NO_VALUE;
|
|
941
|
-
this._status =
|
|
942
|
-
this.
|
|
894
|
+
this._status = "disconnected";
|
|
895
|
+
this._onResubscribe?.();
|
|
943
896
|
}
|
|
944
897
|
this._sinkCount += 1;
|
|
945
898
|
if (hints?.singleDep) {
|
|
946
899
|
this._singleDepSinkCount += 1;
|
|
947
900
|
this._singleDepSinks.add(sink);
|
|
948
901
|
}
|
|
902
|
+
if (!this._terminal) {
|
|
903
|
+
const startMessages = this._cached === NO_VALUE ? [[START]] : [[START], [DATA, this._cached]];
|
|
904
|
+
downWithBatch(sink, startMessages);
|
|
905
|
+
}
|
|
949
906
|
if (this._sinks == null) {
|
|
950
907
|
this._sinks = sink;
|
|
951
908
|
} else if (typeof this._sinks === "function") {
|
|
@@ -953,10 +910,12 @@ var NodeImpl = class {
|
|
|
953
910
|
} else {
|
|
954
911
|
this._sinks.add(sink);
|
|
955
912
|
}
|
|
956
|
-
if (this.
|
|
957
|
-
this.
|
|
958
|
-
|
|
959
|
-
|
|
913
|
+
if (this._sinkCount === 1 && !this._terminal) {
|
|
914
|
+
this._active = true;
|
|
915
|
+
this._onActivate();
|
|
916
|
+
}
|
|
917
|
+
if (!this._terminal && this._status === "disconnected" && this._cached === NO_VALUE) {
|
|
918
|
+
this._status = "pending";
|
|
960
919
|
}
|
|
961
920
|
let removed = false;
|
|
962
921
|
return () => {
|
|
@@ -980,39 +939,49 @@ var NodeImpl = class {
|
|
|
980
939
|
}
|
|
981
940
|
}
|
|
982
941
|
if (this._sinks == null) {
|
|
983
|
-
this.
|
|
984
|
-
this._stopProducer();
|
|
942
|
+
this._onDeactivate();
|
|
985
943
|
}
|
|
986
944
|
};
|
|
987
945
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
946
|
+
// --- Down pipeline ---
|
|
947
|
+
/**
|
|
948
|
+
* Core outgoing dispatch. Applies terminal filter + local lifecycle
|
|
949
|
+
* update, then hands messages to `downWithBatch` for tier-aware delivery.
|
|
950
|
+
*/
|
|
951
|
+
_downInternal(messages) {
|
|
952
|
+
if (messages.length === 0) return;
|
|
953
|
+
let sinkMessages = messages;
|
|
954
|
+
if (this._terminal && !this._resubscribable) {
|
|
955
|
+
const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
|
|
956
|
+
if (pass.length === 0) return;
|
|
957
|
+
sinkMessages = pass;
|
|
996
958
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
959
|
+
this._handleLocalLifecycle(sinkMessages);
|
|
960
|
+
if (this._canSkipDirty()) {
|
|
961
|
+
let hasPhase2 = false;
|
|
962
|
+
for (let i = 0; i < sinkMessages.length; i++) {
|
|
963
|
+
const t = sinkMessages[i][0];
|
|
964
|
+
if (t === DATA || t === RESOLVED) {
|
|
965
|
+
hasPhase2 = true;
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (hasPhase2) {
|
|
970
|
+
const filtered = [];
|
|
971
|
+
for (let i = 0; i < sinkMessages.length; i++) {
|
|
972
|
+
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
973
|
+
}
|
|
974
|
+
if (filtered.length > 0) {
|
|
975
|
+
downWithBatch(this._boundDownToSinks, filtered);
|
|
976
|
+
}
|
|
977
|
+
return;
|
|
1002
978
|
}
|
|
1003
979
|
}
|
|
980
|
+
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
1004
981
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
for (const dep of this._deps) {
|
|
1008
|
-
dep.up?.(messages, { internal: true });
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
unsubscribe() {
|
|
1012
|
-
if (!this._hasDeps) return;
|
|
1013
|
-
this._disconnectUpstream();
|
|
982
|
+
_canSkipDirty() {
|
|
983
|
+
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
1014
984
|
}
|
|
1015
|
-
// --- Private methods (prototype, _ prefix) ---
|
|
1016
985
|
_downToSinks(messages) {
|
|
1017
986
|
if (this._sinks == null) return;
|
|
1018
987
|
if (typeof this._sinks === "function") {
|
|
@@ -1024,6 +993,11 @@ var NodeImpl = class {
|
|
|
1024
993
|
sink(messages);
|
|
1025
994
|
}
|
|
1026
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Update `_cached`, `_status`, `_terminal` from message batch before
|
|
998
|
+
* delivery. Subclass hooks `_onInvalidate` / `_onTeardown` let
|
|
999
|
+
* {@link NodeImpl} clear `_lastDepValues` and invoke cleanup fns.
|
|
1000
|
+
*/
|
|
1027
1001
|
_handleLocalLifecycle(messages) {
|
|
1028
1002
|
for (const m of messages) {
|
|
1029
1003
|
const t = m[0];
|
|
@@ -1037,28 +1011,22 @@ var NodeImpl = class {
|
|
|
1037
1011
|
}
|
|
1038
1012
|
}
|
|
1039
1013
|
if (t === INVALIDATE) {
|
|
1040
|
-
|
|
1041
|
-
this._cleanup = void 0;
|
|
1042
|
-
cleanupFn?.();
|
|
1014
|
+
this._onInvalidate();
|
|
1043
1015
|
this._cached = NO_VALUE;
|
|
1044
|
-
this._lastDepValues = void 0;
|
|
1045
1016
|
}
|
|
1046
1017
|
this._status = statusAfterMessage(this._status, m);
|
|
1047
1018
|
if (t === COMPLETE || t === ERROR) {
|
|
1048
1019
|
this._terminal = true;
|
|
1049
1020
|
}
|
|
1050
1021
|
if (t === TEARDOWN) {
|
|
1051
|
-
if (this.
|
|
1022
|
+
if (this._resetOnTeardown) {
|
|
1052
1023
|
this._cached = NO_VALUE;
|
|
1053
1024
|
}
|
|
1054
|
-
|
|
1055
|
-
this._cleanup = void 0;
|
|
1056
|
-
teardownCleanup?.();
|
|
1025
|
+
this._onTeardown();
|
|
1057
1026
|
try {
|
|
1058
1027
|
this._propagateToMeta(t);
|
|
1059
1028
|
} finally {
|
|
1060
|
-
this.
|
|
1061
|
-
this._stopProducer();
|
|
1029
|
+
this._onDeactivate();
|
|
1062
1030
|
}
|
|
1063
1031
|
}
|
|
1064
1032
|
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
@@ -1066,7 +1034,20 @@ var NodeImpl = class {
|
|
|
1066
1034
|
}
|
|
1067
1035
|
}
|
|
1068
1036
|
}
|
|
1069
|
-
/**
|
|
1037
|
+
/**
|
|
1038
|
+
* Subclass hook: invoked when INVALIDATE arrives (before `_cached` is
|
|
1039
|
+
* cleared). {@link NodeImpl} uses this to run the fn cleanup fn and
|
|
1040
|
+
* drop `_lastDepValues` so the next wave re-runs fn.
|
|
1041
|
+
*/
|
|
1042
|
+
_onInvalidate() {
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Subclass hook: invoked when TEARDOWN arrives, before `_onDeactivate`.
|
|
1046
|
+
* {@link NodeImpl} uses this to run any pending cleanup fn.
|
|
1047
|
+
*/
|
|
1048
|
+
_onTeardown() {
|
|
1049
|
+
}
|
|
1050
|
+
/** Forward a signal to all companion meta nodes (best-effort). */
|
|
1070
1051
|
_propagateToMeta(t) {
|
|
1071
1052
|
for (const metaNode of Object.values(this.meta)) {
|
|
1072
1053
|
try {
|
|
@@ -1075,9 +1056,10 @@ var NodeImpl = class {
|
|
|
1075
1056
|
}
|
|
1076
1057
|
}
|
|
1077
1058
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1059
|
+
/**
|
|
1060
|
+
* Frame a computed value into the right protocol messages and dispatch
|
|
1061
|
+
* via `_downInternal`. Used by `_runFn` and `actions.emit`.
|
|
1062
|
+
*/
|
|
1081
1063
|
_downAutoValue(value) {
|
|
1082
1064
|
const wasDirty = this._status === "dirty";
|
|
1083
1065
|
let unchanged;
|
|
@@ -1085,7 +1067,9 @@ var NodeImpl = class {
|
|
|
1085
1067
|
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
1086
1068
|
} catch (eqErr) {
|
|
1087
1069
|
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
1088
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
1070
|
+
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
1071
|
+
cause: eqErr
|
|
1072
|
+
});
|
|
1089
1073
|
this._downInternal([[ERROR, wrapped]]);
|
|
1090
1074
|
return;
|
|
1091
1075
|
}
|
|
@@ -1095,89 +1079,173 @@ var NodeImpl = class {
|
|
|
1095
1079
|
}
|
|
1096
1080
|
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
1097
1081
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
// src/core/node.ts
|
|
1085
|
+
var NodeImpl = class extends NodeBase {
|
|
1086
|
+
// --- Dep configuration (set once) ---
|
|
1087
|
+
_deps;
|
|
1088
|
+
_fn;
|
|
1089
|
+
_opts;
|
|
1090
|
+
_hasDeps;
|
|
1091
|
+
_isSingleDep;
|
|
1092
|
+
_autoComplete;
|
|
1093
|
+
// --- Wave tracking masks ---
|
|
1094
|
+
_depDirtyMask;
|
|
1095
|
+
_depSettledMask;
|
|
1096
|
+
_depCompleteMask;
|
|
1097
|
+
_allDepsCompleteMask;
|
|
1098
|
+
// --- Identity-skip optimization ---
|
|
1099
|
+
_lastDepValues;
|
|
1100
|
+
_cleanup;
|
|
1101
|
+
// --- Upstream bookkeeping ---
|
|
1102
|
+
_upstreamUnsubs = [];
|
|
1103
|
+
// --- Fn behavior flag ---
|
|
1104
|
+
/** @internal Read by `describeNode` to infer `"operator"` label. */
|
|
1105
|
+
_manualEmitUsed = false;
|
|
1106
|
+
constructor(deps, fn, opts) {
|
|
1107
|
+
super(opts);
|
|
1108
|
+
this._deps = deps;
|
|
1109
|
+
this._fn = fn;
|
|
1110
|
+
this._opts = opts;
|
|
1111
|
+
this._hasDeps = deps.length > 0;
|
|
1112
|
+
this._isSingleDep = deps.length === 1 && fn != null;
|
|
1113
|
+
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
1114
|
+
if (!this._hasDeps && fn == null && this._cached !== NO_VALUE) {
|
|
1115
|
+
this._status = "settled";
|
|
1116
|
+
}
|
|
1117
|
+
this._depDirtyMask = createBitSet(deps.length);
|
|
1118
|
+
this._depSettledMask = createBitSet(deps.length);
|
|
1119
|
+
this._depCompleteMask = createBitSet(deps.length);
|
|
1120
|
+
this._allDepsCompleteMask = createBitSet(deps.length);
|
|
1121
|
+
for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
|
|
1122
|
+
this.down = this.down.bind(this);
|
|
1123
|
+
this.up = this.up.bind(this);
|
|
1124
|
+
}
|
|
1125
|
+
// --- Meta node factory (called from base constructor) ---
|
|
1126
|
+
_createMetaNode(key, initialValue, opts) {
|
|
1127
|
+
return node({
|
|
1128
|
+
initial: initialValue,
|
|
1129
|
+
name: `${opts.name ?? "node"}:meta:${key}`,
|
|
1130
|
+
describeKind: "state",
|
|
1131
|
+
...opts.guard != null ? { guard: opts.guard } : {}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
// --- Manual emit tracker (set by actions.down / actions.emit) ---
|
|
1135
|
+
_onManualEmit() {
|
|
1136
|
+
this._manualEmitUsed = true;
|
|
1137
|
+
}
|
|
1138
|
+
// --- Up / unsubscribe ---
|
|
1139
|
+
up(messages, options) {
|
|
1140
|
+
if (!this._hasDeps) return;
|
|
1141
|
+
if (!options?.internal && this._guard != null) {
|
|
1142
|
+
const actor = normalizeActor(options?.actor);
|
|
1143
|
+
if (!this._guard(actor, "write")) {
|
|
1144
|
+
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
1136
1145
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1146
|
+
this._recordMutation(actor);
|
|
1147
|
+
}
|
|
1148
|
+
for (const dep of this._deps) {
|
|
1149
|
+
if (options === void 0) {
|
|
1150
|
+
dep.up?.(messages);
|
|
1151
|
+
} else {
|
|
1152
|
+
dep.up?.(messages, options);
|
|
1140
1153
|
}
|
|
1141
|
-
if (this._manualEmitUsed) return;
|
|
1142
|
-
if (out === void 0) return;
|
|
1143
|
-
this._downAutoValue(out);
|
|
1144
|
-
} catch (err) {
|
|
1145
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1146
|
-
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
|
|
1147
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
1148
1154
|
}
|
|
1149
1155
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
this.
|
|
1153
|
-
|
|
1154
|
-
if (!wasDirty) {
|
|
1155
|
-
this._downInternal([[DIRTY]]);
|
|
1156
|
+
_upInternal(messages) {
|
|
1157
|
+
if (!this._hasDeps) return;
|
|
1158
|
+
for (const dep of this._deps) {
|
|
1159
|
+
dep.up?.(messages, { internal: true });
|
|
1156
1160
|
}
|
|
1157
1161
|
}
|
|
1158
|
-
|
|
1159
|
-
if (!this.
|
|
1160
|
-
|
|
1162
|
+
unsubscribe() {
|
|
1163
|
+
if (!this._hasDeps) return;
|
|
1164
|
+
this._disconnectUpstream();
|
|
1165
|
+
}
|
|
1166
|
+
// --- Activation (first-subscriber / last-subscriber hooks) ---
|
|
1167
|
+
_onActivate() {
|
|
1168
|
+
if (this._hasDeps) {
|
|
1169
|
+
this._connectUpstream();
|
|
1170
|
+
return;
|
|
1161
1171
|
}
|
|
1162
|
-
this.
|
|
1163
|
-
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1164
|
-
this._depDirtyMask.reset();
|
|
1165
|
-
this._depSettledMask.reset();
|
|
1172
|
+
if (this._fn) {
|
|
1166
1173
|
this._runFn();
|
|
1174
|
+
return;
|
|
1167
1175
|
}
|
|
1168
1176
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1177
|
+
_doDeactivate() {
|
|
1178
|
+
this._disconnectUpstream();
|
|
1179
|
+
const cleanup = this._cleanup;
|
|
1180
|
+
this._cleanup = void 0;
|
|
1181
|
+
cleanup?.();
|
|
1182
|
+
if (this._fn != null) {
|
|
1183
|
+
this._cached = NO_VALUE;
|
|
1184
|
+
this._lastDepValues = void 0;
|
|
1172
1185
|
}
|
|
1186
|
+
if (this._hasDeps || this._fn != null) {
|
|
1187
|
+
this._status = "disconnected";
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
// --- INVALIDATE / TEARDOWN hooks (clear fn state) ---
|
|
1191
|
+
_onInvalidate() {
|
|
1192
|
+
const cleanup = this._cleanup;
|
|
1193
|
+
this._cleanup = void 0;
|
|
1194
|
+
cleanup?.();
|
|
1195
|
+
this._lastDepValues = void 0;
|
|
1196
|
+
}
|
|
1197
|
+
_onTeardown() {
|
|
1198
|
+
const cleanup = this._cleanup;
|
|
1199
|
+
this._cleanup = void 0;
|
|
1200
|
+
cleanup?.();
|
|
1173
1201
|
}
|
|
1202
|
+
// --- Upstream connect / disconnect ---
|
|
1203
|
+
_connectUpstream() {
|
|
1204
|
+
if (!this._hasDeps) return;
|
|
1205
|
+
if (this._upstreamUnsubs.length > 0) return;
|
|
1206
|
+
this._depDirtyMask.setAll();
|
|
1207
|
+
this._depSettledMask.reset();
|
|
1208
|
+
this._depCompleteMask.reset();
|
|
1209
|
+
const depValuesBefore = this._lastDepValues;
|
|
1210
|
+
const subHints = this._isSingleDep ? { singleDep: true } : void 0;
|
|
1211
|
+
for (let i = 0; i < this._deps.length; i += 1) {
|
|
1212
|
+
const dep = this._deps[i];
|
|
1213
|
+
this._upstreamUnsubs.push(
|
|
1214
|
+
dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
if (this._fn && this._onMessage && !this._terminal && this._lastDepValues === depValuesBefore) {
|
|
1218
|
+
this._runFn();
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
_disconnectUpstream() {
|
|
1222
|
+
if (this._upstreamUnsubs.length === 0) return;
|
|
1223
|
+
for (const unsub of this._upstreamUnsubs.splice(0)) {
|
|
1224
|
+
unsub();
|
|
1225
|
+
}
|
|
1226
|
+
this._depDirtyMask.reset();
|
|
1227
|
+
this._depSettledMask.reset();
|
|
1228
|
+
this._depCompleteMask.reset();
|
|
1229
|
+
}
|
|
1230
|
+
// --- Wave handling ---
|
|
1174
1231
|
_handleDepMessages(index, messages) {
|
|
1175
1232
|
for (const msg of messages) {
|
|
1176
|
-
this.
|
|
1233
|
+
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
1177
1234
|
const t = msg[0];
|
|
1178
1235
|
if (this._onMessage) {
|
|
1179
1236
|
try {
|
|
1180
|
-
|
|
1237
|
+
const consumed = this._onMessage(msg, index, this._actions);
|
|
1238
|
+
if (consumed) {
|
|
1239
|
+
if (t === START) {
|
|
1240
|
+
this._depDirtyMask.clear(index);
|
|
1241
|
+
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1242
|
+
this._depDirtyMask.reset();
|
|
1243
|
+
this._depSettledMask.reset();
|
|
1244
|
+
this._runFn();
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1181
1249
|
} catch (err) {
|
|
1182
1250
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1183
1251
|
const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
|
|
@@ -1187,6 +1255,7 @@ var NodeImpl = class {
|
|
|
1187
1255
|
return;
|
|
1188
1256
|
}
|
|
1189
1257
|
}
|
|
1258
|
+
if (messageTier(t) < 1) continue;
|
|
1190
1259
|
if (!this._fn) {
|
|
1191
1260
|
if (t === COMPLETE && this._deps.length > 1) {
|
|
1192
1261
|
this._depCompleteMask.set(index);
|
|
@@ -1230,53 +1299,85 @@ var NodeImpl = class {
|
|
|
1230
1299
|
this._downInternal([msg]);
|
|
1231
1300
|
}
|
|
1232
1301
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
this.
|
|
1236
|
-
this.
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
this._status = "settled";
|
|
1240
|
-
const subHints = this._isSingleDep ? { singleDep: true } : void 0;
|
|
1241
|
-
this._connecting = true;
|
|
1242
|
-
try {
|
|
1243
|
-
for (let i = 0; i < this._deps.length; i += 1) {
|
|
1244
|
-
const dep = this._deps[i];
|
|
1245
|
-
this._upstreamUnsubs.push(
|
|
1246
|
-
dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
|
|
1247
|
-
);
|
|
1248
|
-
}
|
|
1249
|
-
} finally {
|
|
1250
|
-
this._connecting = false;
|
|
1302
|
+
_onDepDirty(index) {
|
|
1303
|
+
const wasDirty = this._depDirtyMask.has(index);
|
|
1304
|
+
this._depDirtyMask.set(index);
|
|
1305
|
+
this._depSettledMask.clear(index);
|
|
1306
|
+
if (!wasDirty) {
|
|
1307
|
+
this._downInternal([[DIRTY]]);
|
|
1251
1308
|
}
|
|
1252
|
-
|
|
1309
|
+
}
|
|
1310
|
+
_onDepSettled(index) {
|
|
1311
|
+
if (!this._depDirtyMask.has(index)) {
|
|
1312
|
+
this._onDepDirty(index);
|
|
1313
|
+
}
|
|
1314
|
+
this._depSettledMask.set(index);
|
|
1315
|
+
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1316
|
+
this._depDirtyMask.reset();
|
|
1317
|
+
this._depSettledMask.reset();
|
|
1253
1318
|
this._runFn();
|
|
1254
1319
|
}
|
|
1255
1320
|
}
|
|
1256
|
-
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
this._cleanup = void 0;
|
|
1261
|
-
producerCleanup?.();
|
|
1262
|
-
}
|
|
1263
|
-
_startProducer() {
|
|
1264
|
-
if (this._deps.length !== 0 || !this._fn || this._producerStarted) return;
|
|
1265
|
-
this._producerStarted = true;
|
|
1266
|
-
this._runFn();
|
|
1321
|
+
_maybeCompleteFromDeps() {
|
|
1322
|
+
if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
|
|
1323
|
+
this._downInternal([[COMPLETE]]);
|
|
1324
|
+
}
|
|
1267
1325
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1326
|
+
// --- Fn execution ---
|
|
1327
|
+
_runFn() {
|
|
1328
|
+
if (!this._fn) return;
|
|
1329
|
+
if (this._terminal && !this._resubscribable) return;
|
|
1330
|
+
try {
|
|
1331
|
+
const n = this._deps.length;
|
|
1332
|
+
const depValues = new Array(n);
|
|
1333
|
+
for (let i = 0; i < n; i++) depValues[i] = this._deps[i].get();
|
|
1334
|
+
const prev = this._lastDepValues;
|
|
1335
|
+
if (n > 0 && prev != null && prev.length === n) {
|
|
1336
|
+
let allSame = true;
|
|
1337
|
+
for (let i = 0; i < n; i++) {
|
|
1338
|
+
if (!Object.is(depValues[i], prev[i])) {
|
|
1339
|
+
allSame = false;
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (allSame) {
|
|
1344
|
+
if (this._status === "dirty") {
|
|
1345
|
+
this._downInternal([[RESOLVED]]);
|
|
1346
|
+
}
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
const prevCleanup = this._cleanup;
|
|
1351
|
+
this._cleanup = void 0;
|
|
1352
|
+
prevCleanup?.();
|
|
1353
|
+
this._manualEmitUsed = false;
|
|
1354
|
+
this._lastDepValues = depValues;
|
|
1355
|
+
this._emitInspectorHook({ kind: "run", depValues });
|
|
1356
|
+
const out = this._fn(depValues, this._actions);
|
|
1357
|
+
if (isCleanupResult(out)) {
|
|
1358
|
+
this._cleanup = out.cleanup;
|
|
1359
|
+
if (this._manualEmitUsed) return;
|
|
1360
|
+
if ("value" in out) {
|
|
1361
|
+
this._downAutoValue(out.value);
|
|
1362
|
+
}
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (isCleanupFn(out)) {
|
|
1366
|
+
this._cleanup = out;
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (this._manualEmitUsed) return;
|
|
1370
|
+
if (out === void 0) return;
|
|
1371
|
+
this._downAutoValue(out);
|
|
1372
|
+
} catch (err) {
|
|
1373
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1374
|
+
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
|
|
1375
|
+
this._downInternal([[ERROR, wrapped]]);
|
|
1272
1376
|
}
|
|
1273
|
-
this._connected = false;
|
|
1274
|
-
this._depDirtyMask.reset();
|
|
1275
|
-
this._depSettledMask.reset();
|
|
1276
|
-
this._depCompleteMask.reset();
|
|
1277
|
-
this._status = "disconnected";
|
|
1278
1377
|
}
|
|
1279
1378
|
};
|
|
1379
|
+
var isNodeArray = (value) => Array.isArray(value);
|
|
1380
|
+
var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
|
|
1280
1381
|
function node(depsOrFn, fnOrOpts, optsArg) {
|
|
1281
1382
|
const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
|
|
1282
1383
|
const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
|
|
@@ -2125,449 +2226,272 @@ var import_common2 = require("@nestjs/common");
|
|
|
2125
2226
|
var import_core2 = require("@nestjs/core");
|
|
2126
2227
|
|
|
2127
2228
|
// src/core/dynamic-node.ts
|
|
2128
|
-
var
|
|
2129
|
-
|
|
2130
|
-
_registryName;
|
|
2131
|
-
_describeKind;
|
|
2132
|
-
meta;
|
|
2229
|
+
var MAX_RERUN = 16;
|
|
2230
|
+
var DynamicNodeImpl = class extends NodeBase {
|
|
2133
2231
|
_fn;
|
|
2134
|
-
_equals;
|
|
2135
|
-
_resubscribable;
|
|
2136
|
-
_resetOnTeardown;
|
|
2137
2232
|
_autoComplete;
|
|
2138
|
-
_onMessage;
|
|
2139
|
-
_onResubscribe;
|
|
2140
|
-
/** @internal — read by {@link describeNode} for `accessHintForGuard`. */
|
|
2141
|
-
_guard;
|
|
2142
|
-
_lastMutation;
|
|
2143
|
-
_inspectorHook;
|
|
2144
|
-
// Sink tracking
|
|
2145
|
-
_sinkCount = 0;
|
|
2146
|
-
_singleDepSinkCount = 0;
|
|
2147
|
-
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
2148
|
-
// Actions object (for onMessage handler)
|
|
2149
|
-
_actions;
|
|
2150
|
-
_boundDownToSinks;
|
|
2151
|
-
// Mutable state
|
|
2152
|
-
_cached = NO_VALUE;
|
|
2153
|
-
_status = "disconnected";
|
|
2154
|
-
_terminal = false;
|
|
2155
|
-
_connected = false;
|
|
2156
|
-
_rewiring = false;
|
|
2157
|
-
// re-entrancy guard
|
|
2158
2233
|
// Dynamic deps tracking
|
|
2234
|
+
/** @internal Read by `describeNode`. */
|
|
2159
2235
|
_deps = [];
|
|
2160
2236
|
_depUnsubs = [];
|
|
2161
2237
|
_depIndexMap = /* @__PURE__ */ new Map();
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2238
|
+
_depDirtyBits = /* @__PURE__ */ new Set();
|
|
2239
|
+
_depSettledBits = /* @__PURE__ */ new Set();
|
|
2240
|
+
_depCompleteBits = /* @__PURE__ */ new Set();
|
|
2241
|
+
// Execution state
|
|
2242
|
+
_running = false;
|
|
2243
|
+
_rewiring = false;
|
|
2244
|
+
_bufferedDepMessages = [];
|
|
2245
|
+
_trackedValues = /* @__PURE__ */ new Map();
|
|
2246
|
+
_rerunCount = 0;
|
|
2168
2247
|
constructor(fn, opts) {
|
|
2248
|
+
super(opts);
|
|
2169
2249
|
this._fn = fn;
|
|
2170
|
-
this._optsName = opts.name;
|
|
2171
|
-
this._describeKind = opts.describeKind;
|
|
2172
|
-
this._equals = opts.equals ?? Object.is;
|
|
2173
|
-
this._resubscribable = opts.resubscribable ?? false;
|
|
2174
|
-
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
2175
2250
|
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
2176
|
-
this.
|
|
2177
|
-
this.
|
|
2178
|
-
this._guard = opts.guard;
|
|
2179
|
-
this._inspectorHook = void 0;
|
|
2180
|
-
const meta = {};
|
|
2181
|
-
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
2182
|
-
meta[k] = node({
|
|
2183
|
-
initial: v,
|
|
2184
|
-
name: `${opts.name ?? "dynamicNode"}:meta:${k}`,
|
|
2185
|
-
describeKind: "state",
|
|
2186
|
-
...opts.guard != null ? { guard: opts.guard } : {}
|
|
2187
|
-
});
|
|
2188
|
-
}
|
|
2189
|
-
Object.freeze(meta);
|
|
2190
|
-
this.meta = meta;
|
|
2191
|
-
const self = this;
|
|
2192
|
-
this._actions = {
|
|
2193
|
-
down(messages) {
|
|
2194
|
-
self._downInternal(messages);
|
|
2195
|
-
},
|
|
2196
|
-
emit(value) {
|
|
2197
|
-
self._downAutoValue(value);
|
|
2198
|
-
},
|
|
2199
|
-
up(messages) {
|
|
2200
|
-
for (const dep of self._deps) {
|
|
2201
|
-
dep.up?.(messages, { internal: true });
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
};
|
|
2205
|
-
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
2206
|
-
}
|
|
2207
|
-
get name() {
|
|
2208
|
-
return this._registryName ?? this._optsName;
|
|
2209
|
-
}
|
|
2210
|
-
/** @internal */
|
|
2211
|
-
_assignRegistryName(localName) {
|
|
2212
|
-
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
2213
|
-
this._registryName = localName;
|
|
2214
|
-
}
|
|
2215
|
-
/**
|
|
2216
|
-
* @internal Attach/remove inspector hook for graph-level observability.
|
|
2217
|
-
* Returns a disposer that restores the previous hook.
|
|
2218
|
-
*/
|
|
2219
|
-
_setInspectorHook(hook) {
|
|
2220
|
-
const prev = this._inspectorHook;
|
|
2221
|
-
this._inspectorHook = hook;
|
|
2222
|
-
return () => {
|
|
2223
|
-
if (this._inspectorHook === hook) {
|
|
2224
|
-
this._inspectorHook = prev;
|
|
2225
|
-
}
|
|
2226
|
-
};
|
|
2227
|
-
}
|
|
2228
|
-
get status() {
|
|
2229
|
-
return this._status;
|
|
2230
|
-
}
|
|
2231
|
-
get lastMutation() {
|
|
2232
|
-
return this._lastMutation;
|
|
2233
|
-
}
|
|
2234
|
-
/** Versioning not yet supported on DynamicNodeImpl. */
|
|
2235
|
-
get v() {
|
|
2236
|
-
return void 0;
|
|
2237
|
-
}
|
|
2238
|
-
hasGuard() {
|
|
2239
|
-
return this._guard != null;
|
|
2240
|
-
}
|
|
2241
|
-
allowsObserve(actor) {
|
|
2242
|
-
if (this._guard == null) return true;
|
|
2243
|
-
return this._guard(normalizeActor(actor), "observe");
|
|
2244
|
-
}
|
|
2245
|
-
get() {
|
|
2246
|
-
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
2247
|
-
}
|
|
2248
|
-
down(messages, options) {
|
|
2249
|
-
if (messages.length === 0) return;
|
|
2250
|
-
if (!options?.internal && this._guard != null) {
|
|
2251
|
-
const actor = normalizeActor(options?.actor);
|
|
2252
|
-
const delivery = options?.delivery ?? "write";
|
|
2253
|
-
const action = delivery === "signal" ? "signal" : "write";
|
|
2254
|
-
if (!this._guard(actor, action)) {
|
|
2255
|
-
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
2256
|
-
}
|
|
2257
|
-
this._lastMutation = { actor, timestamp_ns: wallClockNs() };
|
|
2258
|
-
}
|
|
2259
|
-
this._downInternal(messages);
|
|
2260
|
-
}
|
|
2261
|
-
_downInternal(messages) {
|
|
2262
|
-
if (messages.length === 0) return;
|
|
2263
|
-
let sinkMessages = messages;
|
|
2264
|
-
if (this._terminal && !this._resubscribable) {
|
|
2265
|
-
const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
|
|
2266
|
-
if (pass.length === 0) return;
|
|
2267
|
-
sinkMessages = pass;
|
|
2268
|
-
}
|
|
2269
|
-
this._handleLocalLifecycle(sinkMessages);
|
|
2270
|
-
if (this._canSkipDirty()) {
|
|
2271
|
-
let hasPhase2 = false;
|
|
2272
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
2273
|
-
const t = sinkMessages[i][0];
|
|
2274
|
-
if (t === DATA || t === RESOLVED) {
|
|
2275
|
-
hasPhase2 = true;
|
|
2276
|
-
break;
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
if (hasPhase2) {
|
|
2280
|
-
const filtered = [];
|
|
2281
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
2282
|
-
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
2283
|
-
}
|
|
2284
|
-
if (filtered.length > 0) {
|
|
2285
|
-
downWithBatch(this._boundDownToSinks, filtered);
|
|
2286
|
-
}
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
2291
|
-
}
|
|
2292
|
-
_canSkipDirty() {
|
|
2293
|
-
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
2294
|
-
}
|
|
2295
|
-
subscribe(sink, hints) {
|
|
2296
|
-
if (hints?.actor != null && this._guard != null) {
|
|
2297
|
-
const actor = normalizeActor(hints.actor);
|
|
2298
|
-
if (!this._guard(actor, "observe")) {
|
|
2299
|
-
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
|
-
if (this._terminal && this._resubscribable) {
|
|
2303
|
-
this._terminal = false;
|
|
2304
|
-
this._cached = NO_VALUE;
|
|
2305
|
-
this._status = "disconnected";
|
|
2306
|
-
this._onResubscribe?.();
|
|
2307
|
-
}
|
|
2308
|
-
this._sinkCount += 1;
|
|
2309
|
-
if (hints?.singleDep) {
|
|
2310
|
-
this._singleDepSinkCount += 1;
|
|
2311
|
-
this._singleDepSinks.add(sink);
|
|
2312
|
-
}
|
|
2313
|
-
if (this._sinks == null) {
|
|
2314
|
-
this._sinks = sink;
|
|
2315
|
-
} else if (typeof this._sinks === "function") {
|
|
2316
|
-
this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
|
|
2317
|
-
} else {
|
|
2318
|
-
this._sinks.add(sink);
|
|
2319
|
-
}
|
|
2320
|
-
if (!this._connected) {
|
|
2321
|
-
this._connect();
|
|
2322
|
-
}
|
|
2323
|
-
let removed = false;
|
|
2324
|
-
return () => {
|
|
2325
|
-
if (removed) return;
|
|
2326
|
-
removed = true;
|
|
2327
|
-
this._sinkCount -= 1;
|
|
2328
|
-
if (this._singleDepSinks.has(sink)) {
|
|
2329
|
-
this._singleDepSinkCount -= 1;
|
|
2330
|
-
this._singleDepSinks.delete(sink);
|
|
2331
|
-
}
|
|
2332
|
-
if (this._sinks == null) return;
|
|
2333
|
-
if (typeof this._sinks === "function") {
|
|
2334
|
-
if (this._sinks === sink) this._sinks = null;
|
|
2335
|
-
} else {
|
|
2336
|
-
this._sinks.delete(sink);
|
|
2337
|
-
if (this._sinks.size === 1) {
|
|
2338
|
-
const [only] = this._sinks;
|
|
2339
|
-
this._sinks = only;
|
|
2340
|
-
} else if (this._sinks.size === 0) {
|
|
2341
|
-
this._sinks = null;
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
if (this._sinks == null) {
|
|
2345
|
-
this._disconnect();
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
}
|
|
2349
|
-
up(messages, options) {
|
|
2350
|
-
if (this._deps.length === 0) return;
|
|
2351
|
-
if (!options?.internal && this._guard != null) {
|
|
2352
|
-
const actor = normalizeActor(options?.actor);
|
|
2353
|
-
if (!this._guard(actor, "write")) {
|
|
2354
|
-
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
2355
|
-
}
|
|
2356
|
-
this._lastMutation = { actor, timestamp_ns: wallClockNs() };
|
|
2357
|
-
}
|
|
2358
|
-
for (const dep of this._deps) {
|
|
2359
|
-
dep.up?.(messages, options);
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
unsubscribe() {
|
|
2363
|
-
this._disconnect();
|
|
2364
|
-
}
|
|
2365
|
-
// --- Private methods ---
|
|
2366
|
-
_downToSinks(messages) {
|
|
2367
|
-
if (this._sinks == null) return;
|
|
2368
|
-
if (typeof this._sinks === "function") {
|
|
2369
|
-
this._sinks(messages);
|
|
2370
|
-
return;
|
|
2371
|
-
}
|
|
2372
|
-
const snapshot = [...this._sinks];
|
|
2373
|
-
for (const sink of snapshot) {
|
|
2374
|
-
sink(messages);
|
|
2375
|
-
}
|
|
2251
|
+
this.down = this.down.bind(this);
|
|
2252
|
+
this.up = this.up.bind(this);
|
|
2376
2253
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
}
|
|
2385
|
-
if (t === DATA) {
|
|
2386
|
-
this._status = "settled";
|
|
2387
|
-
} else if (t === RESOLVED) {
|
|
2388
|
-
this._status = "resolved";
|
|
2389
|
-
} else if (t === DIRTY) {
|
|
2390
|
-
this._status = "dirty";
|
|
2391
|
-
} else if (t === COMPLETE) {
|
|
2392
|
-
this._status = "completed";
|
|
2393
|
-
this._terminal = true;
|
|
2394
|
-
} else if (t === ERROR) {
|
|
2395
|
-
this._status = "errored";
|
|
2396
|
-
this._terminal = true;
|
|
2397
|
-
}
|
|
2398
|
-
if (t === TEARDOWN) {
|
|
2399
|
-
if (this._resetOnTeardown) this._cached = NO_VALUE;
|
|
2400
|
-
try {
|
|
2401
|
-
this._propagateToMeta(t);
|
|
2402
|
-
} finally {
|
|
2403
|
-
this._disconnect();
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
2407
|
-
this._propagateToMeta(t);
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2254
|
+
_createMetaNode(key, initialValue, opts) {
|
|
2255
|
+
return node({
|
|
2256
|
+
initial: initialValue,
|
|
2257
|
+
name: `${opts.name ?? "dynamicNode"}:meta:${key}`,
|
|
2258
|
+
describeKind: "state",
|
|
2259
|
+
...opts.guard != null ? { guard: opts.guard } : {}
|
|
2260
|
+
});
|
|
2410
2261
|
}
|
|
2411
|
-
/**
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2262
|
+
/** Versioning not supported on DynamicNodeImpl (override base). */
|
|
2263
|
+
get v() {
|
|
2264
|
+
return void 0;
|
|
2265
|
+
}
|
|
2266
|
+
// --- Up / unsubscribe ---
|
|
2267
|
+
up(messages, options) {
|
|
2268
|
+
if (this._deps.length === 0) return;
|
|
2269
|
+
if (!options?.internal && this._guard != null) {
|
|
2270
|
+
const actor = normalizeActor(options?.actor);
|
|
2271
|
+
if (!this._guard(actor, "write")) {
|
|
2272
|
+
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
2417
2273
|
}
|
|
2274
|
+
this._recordMutation(actor);
|
|
2418
2275
|
}
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
const wasDirty = this._status === "dirty";
|
|
2422
|
-
let unchanged;
|
|
2423
|
-
try {
|
|
2424
|
-
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
2425
|
-
} catch (eqErr) {
|
|
2426
|
-
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
2427
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
|
|
2428
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
2429
|
-
return;
|
|
2276
|
+
for (const dep of this._deps) {
|
|
2277
|
+
dep.up?.(messages, options);
|
|
2430
2278
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2279
|
+
}
|
|
2280
|
+
_upInternal(messages) {
|
|
2281
|
+
for (const dep of this._deps) {
|
|
2282
|
+
dep.up?.(messages, { internal: true });
|
|
2434
2283
|
}
|
|
2435
|
-
this._cached = value;
|
|
2436
|
-
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
2437
2284
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
this._settledBits.clear();
|
|
2444
|
-
this._completeBits.clear();
|
|
2285
|
+
unsubscribe() {
|
|
2286
|
+
this._disconnect();
|
|
2287
|
+
}
|
|
2288
|
+
// --- Activation hooks ---
|
|
2289
|
+
_onActivate() {
|
|
2445
2290
|
this._runFn();
|
|
2446
2291
|
}
|
|
2292
|
+
_doDeactivate() {
|
|
2293
|
+
this._disconnect();
|
|
2294
|
+
}
|
|
2447
2295
|
_disconnect() {
|
|
2448
|
-
if (!this._connected) return;
|
|
2449
2296
|
for (const unsub of this._depUnsubs) unsub();
|
|
2450
2297
|
this._depUnsubs = [];
|
|
2451
2298
|
this._deps = [];
|
|
2452
2299
|
this._depIndexMap.clear();
|
|
2453
|
-
this.
|
|
2454
|
-
this.
|
|
2455
|
-
this.
|
|
2456
|
-
this.
|
|
2300
|
+
this._depDirtyBits.clear();
|
|
2301
|
+
this._depSettledBits.clear();
|
|
2302
|
+
this._depCompleteBits.clear();
|
|
2303
|
+
this._cached = NO_VALUE;
|
|
2457
2304
|
this._status = "disconnected";
|
|
2458
2305
|
}
|
|
2306
|
+
// --- Fn execution with rewire buffer ---
|
|
2459
2307
|
_runFn() {
|
|
2460
2308
|
if (this._terminal && !this._resubscribable) return;
|
|
2461
|
-
if (this.
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
if (!trackedSet.has(dep)) {
|
|
2466
|
-
trackedSet.add(dep);
|
|
2467
|
-
trackedDeps.push(dep);
|
|
2468
|
-
}
|
|
2469
|
-
return dep.get();
|
|
2470
|
-
};
|
|
2309
|
+
if (this._running) return;
|
|
2310
|
+
this._running = true;
|
|
2311
|
+
this._rerunCount = 0;
|
|
2312
|
+
let result;
|
|
2471
2313
|
try {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2314
|
+
for (; ; ) {
|
|
2315
|
+
const trackedDeps = [];
|
|
2316
|
+
const trackedValuesMap = /* @__PURE__ */ new Map();
|
|
2317
|
+
const trackedSet = /* @__PURE__ */ new Set();
|
|
2318
|
+
const get = (dep) => {
|
|
2319
|
+
if (!trackedSet.has(dep)) {
|
|
2320
|
+
trackedSet.add(dep);
|
|
2321
|
+
trackedDeps.push(dep);
|
|
2322
|
+
trackedValuesMap.set(dep, dep.get());
|
|
2323
|
+
}
|
|
2324
|
+
return dep.get();
|
|
2325
|
+
};
|
|
2326
|
+
this._trackedValues = trackedValuesMap;
|
|
2327
|
+
const depValues = [];
|
|
2328
|
+
for (const dep of this._deps) depValues.push(dep.get());
|
|
2329
|
+
this._emitInspectorHook({ kind: "run", depValues });
|
|
2330
|
+
try {
|
|
2331
|
+
result = this._fn(get);
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2334
|
+
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, {
|
|
2335
|
+
cause: err
|
|
2336
|
+
});
|
|
2337
|
+
this._downInternal([[ERROR, wrapped]]);
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
this._rewiring = true;
|
|
2341
|
+
this._bufferedDepMessages = [];
|
|
2342
|
+
try {
|
|
2343
|
+
this._rewire(trackedDeps);
|
|
2344
|
+
} finally {
|
|
2345
|
+
this._rewiring = false;
|
|
2346
|
+
}
|
|
2347
|
+
let needsRerun = false;
|
|
2348
|
+
for (const entry of this._bufferedDepMessages) {
|
|
2349
|
+
for (const msg of entry.msgs) {
|
|
2350
|
+
if (msg[0] === DATA) {
|
|
2351
|
+
const dep = this._deps[entry.index];
|
|
2352
|
+
const trackedValue = dep != null ? this._trackedValues.get(dep) : void 0;
|
|
2353
|
+
const actualValue = msg[1];
|
|
2354
|
+
if (!this._equals(trackedValue, actualValue)) {
|
|
2355
|
+
needsRerun = true;
|
|
2356
|
+
break;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
if (needsRerun) break;
|
|
2361
|
+
}
|
|
2362
|
+
if (needsRerun) {
|
|
2363
|
+
this._rerunCount += 1;
|
|
2364
|
+
if (this._rerunCount > MAX_RERUN) {
|
|
2365
|
+
this._bufferedDepMessages = [];
|
|
2366
|
+
this._downInternal([
|
|
2367
|
+
[
|
|
2368
|
+
ERROR,
|
|
2369
|
+
new Error(
|
|
2370
|
+
`dynamicNode "${this.name ?? "anonymous"}": rewire did not stabilize within ${MAX_RERUN} iterations`
|
|
2371
|
+
)
|
|
2372
|
+
]
|
|
2373
|
+
]);
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
this._bufferedDepMessages = [];
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
const drain = this._bufferedDepMessages;
|
|
2380
|
+
this._bufferedDepMessages = [];
|
|
2381
|
+
for (const entry of drain) {
|
|
2382
|
+
for (const msg of entry.msgs) {
|
|
2383
|
+
this._updateMasksForMessage(entry.index, msg);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
this._depDirtyBits.clear();
|
|
2387
|
+
this._depSettledBits.clear();
|
|
2388
|
+
break;
|
|
2389
|
+
}
|
|
2390
|
+
} finally {
|
|
2391
|
+
this._running = false;
|
|
2483
2392
|
}
|
|
2393
|
+
this._downAutoValue(result);
|
|
2484
2394
|
}
|
|
2485
2395
|
_rewire(newDeps) {
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
const
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
|
|
2502
|
-
newUnsubs.push(unsub);
|
|
2503
|
-
}
|
|
2396
|
+
const oldMap = this._depIndexMap;
|
|
2397
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
2398
|
+
const newUnsubs = [];
|
|
2399
|
+
for (let i = 0; i < newDeps.length; i++) {
|
|
2400
|
+
const dep = newDeps[i];
|
|
2401
|
+
newMap.set(dep, i);
|
|
2402
|
+
const oldIdx = oldMap.get(dep);
|
|
2403
|
+
if (oldIdx !== void 0) {
|
|
2404
|
+
newUnsubs.push(this._depUnsubs[oldIdx]);
|
|
2405
|
+
this._depUnsubs[oldIdx] = () => {
|
|
2406
|
+
};
|
|
2407
|
+
} else {
|
|
2408
|
+
const idx = i;
|
|
2409
|
+
const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
|
|
2410
|
+
newUnsubs.push(unsub);
|
|
2504
2411
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2412
|
+
}
|
|
2413
|
+
for (const [dep, oldIdx] of oldMap) {
|
|
2414
|
+
if (!newMap.has(dep)) {
|
|
2415
|
+
this._depUnsubs[oldIdx]();
|
|
2509
2416
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2417
|
+
}
|
|
2418
|
+
this._deps = newDeps;
|
|
2419
|
+
this._depUnsubs = newUnsubs;
|
|
2420
|
+
this._depIndexMap = newMap;
|
|
2421
|
+
this._depDirtyBits.clear();
|
|
2422
|
+
this._depSettledBits.clear();
|
|
2423
|
+
const newCompleteBits = /* @__PURE__ */ new Set();
|
|
2424
|
+
for (const oldIdx of this._depCompleteBits) {
|
|
2425
|
+
for (const [dep, idx] of oldMap) {
|
|
2426
|
+
if (idx === oldIdx && newMap.has(dep)) {
|
|
2519
2427
|
newCompleteBits.add(newMap.get(dep));
|
|
2428
|
+
break;
|
|
2520
2429
|
}
|
|
2521
2430
|
}
|
|
2522
|
-
this._completeBits = newCompleteBits;
|
|
2523
|
-
} finally {
|
|
2524
|
-
this._rewiring = false;
|
|
2525
2431
|
}
|
|
2432
|
+
this._depCompleteBits = newCompleteBits;
|
|
2526
2433
|
}
|
|
2434
|
+
// --- Dep message handling ---
|
|
2527
2435
|
_handleDepMessages(index, messages) {
|
|
2528
|
-
if (this._rewiring)
|
|
2436
|
+
if (this._rewiring) {
|
|
2437
|
+
this._bufferedDepMessages.push({ index, msgs: messages });
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2529
2440
|
for (const msg of messages) {
|
|
2530
|
-
this.
|
|
2441
|
+
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
2531
2442
|
const t = msg[0];
|
|
2532
2443
|
if (this._onMessage) {
|
|
2533
2444
|
try {
|
|
2534
2445
|
if (this._onMessage(msg, index, this._actions)) continue;
|
|
2535
2446
|
} catch (err) {
|
|
2536
|
-
|
|
2447
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2448
|
+
const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
|
|
2449
|
+
cause: err
|
|
2450
|
+
});
|
|
2451
|
+
this._downInternal([[ERROR, wrapped]]);
|
|
2537
2452
|
return;
|
|
2538
2453
|
}
|
|
2539
2454
|
}
|
|
2455
|
+
if (messageTier(t) < 1) continue;
|
|
2540
2456
|
if (t === DIRTY) {
|
|
2541
|
-
this.
|
|
2542
|
-
this.
|
|
2543
|
-
|
|
2544
|
-
|
|
2457
|
+
const wasEmpty = this._depDirtyBits.size === 0;
|
|
2458
|
+
this._depDirtyBits.add(index);
|
|
2459
|
+
this._depSettledBits.delete(index);
|
|
2460
|
+
if (wasEmpty) {
|
|
2461
|
+
this._downInternal([[DIRTY]]);
|
|
2545
2462
|
}
|
|
2546
2463
|
continue;
|
|
2547
2464
|
}
|
|
2548
2465
|
if (t === DATA || t === RESOLVED) {
|
|
2549
|
-
if (!this.
|
|
2550
|
-
this.
|
|
2551
|
-
|
|
2466
|
+
if (!this._depDirtyBits.has(index)) {
|
|
2467
|
+
const wasEmpty = this._depDirtyBits.size === 0;
|
|
2468
|
+
this._depDirtyBits.add(index);
|
|
2469
|
+
if (wasEmpty) {
|
|
2470
|
+
this._downInternal([[DIRTY]]);
|
|
2471
|
+
}
|
|
2552
2472
|
}
|
|
2553
|
-
this.
|
|
2473
|
+
this._depSettledBits.add(index);
|
|
2554
2474
|
if (this._allDirtySettled()) {
|
|
2555
|
-
this.
|
|
2556
|
-
this.
|
|
2557
|
-
this.
|
|
2475
|
+
this._depDirtyBits.clear();
|
|
2476
|
+
this._depSettledBits.clear();
|
|
2477
|
+
if (!this._running) {
|
|
2478
|
+
if (this._depValuesDifferFromTracked()) {
|
|
2479
|
+
this._runFn();
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2558
2482
|
}
|
|
2559
2483
|
continue;
|
|
2560
2484
|
}
|
|
2561
2485
|
if (t === COMPLETE) {
|
|
2562
|
-
this.
|
|
2563
|
-
this.
|
|
2564
|
-
this.
|
|
2486
|
+
this._depCompleteBits.add(index);
|
|
2487
|
+
this._depDirtyBits.delete(index);
|
|
2488
|
+
this._depSettledBits.delete(index);
|
|
2565
2489
|
if (this._allDirtySettled()) {
|
|
2566
|
-
this.
|
|
2567
|
-
this.
|
|
2568
|
-
this._runFn();
|
|
2490
|
+
this._depDirtyBits.clear();
|
|
2491
|
+
this._depSettledBits.clear();
|
|
2492
|
+
if (!this._running) this._runFn();
|
|
2569
2493
|
}
|
|
2570
|
-
if (this._autoComplete && this.
|
|
2494
|
+
if (this._autoComplete && this._depCompleteBits.size >= this._deps.length && this._deps.length > 0) {
|
|
2571
2495
|
this._downInternal([[COMPLETE]]);
|
|
2572
2496
|
}
|
|
2573
2497
|
continue;
|
|
@@ -2583,13 +2507,46 @@ var DynamicNodeImpl = class {
|
|
|
2583
2507
|
this._downInternal([msg]);
|
|
2584
2508
|
}
|
|
2585
2509
|
}
|
|
2510
|
+
/**
|
|
2511
|
+
* Update dep masks for a message without triggering `_runFn` — used
|
|
2512
|
+
* during post-rewire drain so the wave state is consistent with the
|
|
2513
|
+
* buffered activation cascade without recursing.
|
|
2514
|
+
*/
|
|
2515
|
+
_updateMasksForMessage(index, msg) {
|
|
2516
|
+
const t = msg[0];
|
|
2517
|
+
if (t === DIRTY) {
|
|
2518
|
+
this._depDirtyBits.add(index);
|
|
2519
|
+
this._depSettledBits.delete(index);
|
|
2520
|
+
} else if (t === DATA || t === RESOLVED) {
|
|
2521
|
+
this._depDirtyBits.add(index);
|
|
2522
|
+
this._depSettledBits.add(index);
|
|
2523
|
+
} else if (t === COMPLETE) {
|
|
2524
|
+
this._depCompleteBits.add(index);
|
|
2525
|
+
this._depDirtyBits.delete(index);
|
|
2526
|
+
this._depSettledBits.delete(index);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2586
2529
|
_allDirtySettled() {
|
|
2587
|
-
if (this.
|
|
2588
|
-
for (const idx of this.
|
|
2589
|
-
if (!this.
|
|
2530
|
+
if (this._depDirtyBits.size === 0) return false;
|
|
2531
|
+
for (const idx of this._depDirtyBits) {
|
|
2532
|
+
if (!this._depSettledBits.has(idx)) return false;
|
|
2590
2533
|
}
|
|
2591
2534
|
return true;
|
|
2592
2535
|
}
|
|
2536
|
+
/**
|
|
2537
|
+
* True if any current dep value differs from what the last `_runFn`
|
|
2538
|
+
* saw via `get()`. Used to suppress redundant re-runs when deferred
|
|
2539
|
+
* handshake messages arrive after `_rewire` for a dep whose value
|
|
2540
|
+
* already matches `_trackedValues`.
|
|
2541
|
+
*/
|
|
2542
|
+
_depValuesDifferFromTracked() {
|
|
2543
|
+
for (const dep of this._deps) {
|
|
2544
|
+
const current = dep.get();
|
|
2545
|
+
const tracked = this._trackedValues.get(dep);
|
|
2546
|
+
if (!this._equals(current, tracked)) return true;
|
|
2547
|
+
}
|
|
2548
|
+
return false;
|
|
2549
|
+
}
|
|
2593
2550
|
};
|
|
2594
2551
|
|
|
2595
2552
|
// src/core/meta.ts
|
|
@@ -2659,6 +2616,10 @@ function describeNode(node2, includeFields) {
|
|
|
2659
2616
|
out.name = node2.name;
|
|
2660
2617
|
}
|
|
2661
2618
|
if (all || includeFields.has("value")) {
|
|
2619
|
+
const isSentinel = node2 instanceof NodeImpl && node2._cached === NO_VALUE || node2 instanceof DynamicNodeImpl && node2._cached === NO_VALUE;
|
|
2620
|
+
if (isSentinel) {
|
|
2621
|
+
out.sentinel = true;
|
|
2622
|
+
}
|
|
2662
2623
|
try {
|
|
2663
2624
|
out.value = node2.get();
|
|
2664
2625
|
} catch {
|
|
@@ -2685,6 +2646,129 @@ function describeNode(node2, includeFields) {
|
|
|
2685
2646
|
return out;
|
|
2686
2647
|
}
|
|
2687
2648
|
|
|
2649
|
+
// src/graph/sizeof.ts
|
|
2650
|
+
var OVERHEAD = {
|
|
2651
|
+
object: 56,
|
|
2652
|
+
array: 64,
|
|
2653
|
+
string: 40,
|
|
2654
|
+
// header; content added separately
|
|
2655
|
+
number: 8,
|
|
2656
|
+
boolean: 4,
|
|
2657
|
+
null: 0,
|
|
2658
|
+
undefined: 0,
|
|
2659
|
+
symbol: 40,
|
|
2660
|
+
bigint: 16,
|
|
2661
|
+
function: 120,
|
|
2662
|
+
map: 72,
|
|
2663
|
+
set: 72,
|
|
2664
|
+
mapEntry: 40,
|
|
2665
|
+
setEntry: 24
|
|
2666
|
+
};
|
|
2667
|
+
function sizeof(value) {
|
|
2668
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2669
|
+
return _sizeof(value, seen);
|
|
2670
|
+
}
|
|
2671
|
+
function _sizeof(value, seen) {
|
|
2672
|
+
if (value == null) return 0;
|
|
2673
|
+
const t = typeof value;
|
|
2674
|
+
switch (t) {
|
|
2675
|
+
case "number":
|
|
2676
|
+
return OVERHEAD.number;
|
|
2677
|
+
case "boolean":
|
|
2678
|
+
return OVERHEAD.boolean;
|
|
2679
|
+
case "string":
|
|
2680
|
+
return OVERHEAD.string + value.length * 2;
|
|
2681
|
+
// UTF-16
|
|
2682
|
+
case "bigint":
|
|
2683
|
+
return OVERHEAD.bigint;
|
|
2684
|
+
case "symbol":
|
|
2685
|
+
return OVERHEAD.symbol;
|
|
2686
|
+
case "function":
|
|
2687
|
+
if (seen.has(value)) return 0;
|
|
2688
|
+
seen.add(value);
|
|
2689
|
+
return OVERHEAD.function;
|
|
2690
|
+
case "undefined":
|
|
2691
|
+
return 0;
|
|
2692
|
+
}
|
|
2693
|
+
const obj = value;
|
|
2694
|
+
if (seen.has(obj)) return 0;
|
|
2695
|
+
seen.add(obj);
|
|
2696
|
+
if (obj instanceof Map) {
|
|
2697
|
+
let size2 = OVERHEAD.map;
|
|
2698
|
+
for (const [k, v] of obj) {
|
|
2699
|
+
size2 += OVERHEAD.mapEntry + _sizeof(k, seen) + _sizeof(v, seen);
|
|
2700
|
+
}
|
|
2701
|
+
return size2;
|
|
2702
|
+
}
|
|
2703
|
+
if (obj instanceof Set) {
|
|
2704
|
+
let size2 = OVERHEAD.set;
|
|
2705
|
+
for (const v of obj) {
|
|
2706
|
+
size2 += OVERHEAD.setEntry + _sizeof(v, seen);
|
|
2707
|
+
}
|
|
2708
|
+
return size2;
|
|
2709
|
+
}
|
|
2710
|
+
if (Array.isArray(obj)) {
|
|
2711
|
+
let size2 = OVERHEAD.array + obj.length * 8;
|
|
2712
|
+
for (const item of obj) {
|
|
2713
|
+
size2 += _sizeof(item, seen);
|
|
2714
|
+
}
|
|
2715
|
+
return size2;
|
|
2716
|
+
}
|
|
2717
|
+
if (obj instanceof ArrayBuffer) return obj.byteLength;
|
|
2718
|
+
if (ArrayBuffer.isView(obj)) return obj.byteLength;
|
|
2719
|
+
let size = OVERHEAD.object;
|
|
2720
|
+
const keys = Object.keys(obj);
|
|
2721
|
+
for (const key of keys) {
|
|
2722
|
+
size += OVERHEAD.string + key.length * 2;
|
|
2723
|
+
size += _sizeof(obj[key], seen);
|
|
2724
|
+
}
|
|
2725
|
+
return size;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
// src/graph/profile.ts
|
|
2729
|
+
function graphProfile(graph, opts) {
|
|
2730
|
+
const topN = opts?.topN ?? 10;
|
|
2731
|
+
const desc = graph.describe({ detail: "standard" });
|
|
2732
|
+
const targets = [];
|
|
2733
|
+
if (typeof graph._collectObserveTargets === "function") {
|
|
2734
|
+
graph._collectObserveTargets("", targets);
|
|
2735
|
+
}
|
|
2736
|
+
const pathToNode = /* @__PURE__ */ new Map();
|
|
2737
|
+
for (const [p, n] of targets) {
|
|
2738
|
+
pathToNode.set(p, n);
|
|
2739
|
+
}
|
|
2740
|
+
const profiles = [];
|
|
2741
|
+
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
2742
|
+
const nd = pathToNode.get(path);
|
|
2743
|
+
const impl = nd instanceof NodeImpl ? nd : null;
|
|
2744
|
+
const valueSizeBytes = impl ? sizeof(impl.get()) : 0;
|
|
2745
|
+
const subscriberCount = impl ? impl._sinkCount : 0;
|
|
2746
|
+
const depCount = nodeDesc.deps?.length ?? 0;
|
|
2747
|
+
const isOrphanEffect = nodeDesc.type === "effect" && subscriberCount === 0;
|
|
2748
|
+
profiles.push({
|
|
2749
|
+
path,
|
|
2750
|
+
type: nodeDesc.type,
|
|
2751
|
+
status: nodeDesc.status ?? "unknown",
|
|
2752
|
+
valueSizeBytes,
|
|
2753
|
+
subscriberCount,
|
|
2754
|
+
depCount,
|
|
2755
|
+
isOrphanEffect
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
const totalValueSizeBytes = profiles.reduce((sum, p) => sum + p.valueSizeBytes, 0);
|
|
2759
|
+
const hotspots = [...profiles].sort((a, b) => b.valueSizeBytes - a.valueSizeBytes).slice(0, topN);
|
|
2760
|
+
const orphanEffects = profiles.filter((p) => p.isOrphanEffect);
|
|
2761
|
+
return {
|
|
2762
|
+
nodeCount: profiles.length,
|
|
2763
|
+
edgeCount: desc.edges.length,
|
|
2764
|
+
subgraphCount: desc.subgraphs.length,
|
|
2765
|
+
nodes: profiles,
|
|
2766
|
+
totalValueSizeBytes,
|
|
2767
|
+
hotspots,
|
|
2768
|
+
orphanEffects
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2688
2772
|
// src/graph/graph.ts
|
|
2689
2773
|
var PATH_SEP = "::";
|
|
2690
2774
|
var GRAPH_META_SEGMENT = "__meta__";
|
|
@@ -2827,7 +2911,7 @@ var RingBuffer = class {
|
|
|
2827
2911
|
return result;
|
|
2828
2912
|
}
|
|
2829
2913
|
};
|
|
2830
|
-
var
|
|
2914
|
+
var OBSERVE_ANSI_THEME = {
|
|
2831
2915
|
data: "\x1B[32m",
|
|
2832
2916
|
dirty: "\x1B[33m",
|
|
2833
2917
|
resolved: "\x1B[36m",
|
|
@@ -2837,7 +2921,7 @@ var SPY_ANSI_THEME = {
|
|
|
2837
2921
|
path: "\x1B[90m",
|
|
2838
2922
|
reset: "\x1B[0m"
|
|
2839
2923
|
};
|
|
2840
|
-
var
|
|
2924
|
+
var OBSERVE_NO_COLOR_THEME = {
|
|
2841
2925
|
data: "",
|
|
2842
2926
|
dirty: "",
|
|
2843
2927
|
resolved: "",
|
|
@@ -2857,9 +2941,9 @@ function describeData(value) {
|
|
|
2857
2941
|
return "[unserializable]";
|
|
2858
2942
|
}
|
|
2859
2943
|
}
|
|
2860
|
-
function
|
|
2861
|
-
if (theme === "none") return
|
|
2862
|
-
if (theme === "ansi" || theme == null) return
|
|
2944
|
+
function resolveObserveTheme(theme) {
|
|
2945
|
+
if (theme === "none") return OBSERVE_NO_COLOR_THEME;
|
|
2946
|
+
if (theme === "ansi" || theme == null) return OBSERVE_ANSI_THEME;
|
|
2863
2947
|
return {
|
|
2864
2948
|
data: theme.data ?? "",
|
|
2865
2949
|
dirty: theme.dirty ?? "",
|
|
@@ -3597,6 +3681,16 @@ var Graph = class _Graph {
|
|
|
3597
3681
|
}
|
|
3598
3682
|
return out;
|
|
3599
3683
|
}
|
|
3684
|
+
/**
|
|
3685
|
+
* Snapshot-based resource profile: per-node stats, orphan effect detection,
|
|
3686
|
+
* memory hotspots. Zero runtime overhead — walks nodes on demand.
|
|
3687
|
+
*
|
|
3688
|
+
* @param opts - Optional `topN` for hotspot limit (default 10).
|
|
3689
|
+
* @returns Aggregate profile with per-node details, hotspots, and orphan effects.
|
|
3690
|
+
*/
|
|
3691
|
+
resourceProfile(opts) {
|
|
3692
|
+
return graphProfile(this, opts);
|
|
3693
|
+
}
|
|
3600
3694
|
_qualifyEdgeEndpoint(part, prefix) {
|
|
3601
3695
|
if (part.includes(PATH_SEP)) return part;
|
|
3602
3696
|
return prefix === "" ? part : `${prefix}${PATH_SEP}${part}`;
|
|
@@ -3630,9 +3724,13 @@ var Graph = class _Graph {
|
|
|
3630
3724
|
if (actor2 != null && !target.allowsObserve(actor2)) {
|
|
3631
3725
|
throw new GuardDenied({ actor: actor2, action: "observe", nodeName: path });
|
|
3632
3726
|
}
|
|
3633
|
-
const wantsStructured2 = resolved.structured === true || resolved.timeline === true || resolved.causal === true || resolved.derived === true || resolved.detail === "minimal" || resolved.detail === "full";
|
|
3634
|
-
if (wantsStructured2
|
|
3635
|
-
|
|
3727
|
+
const wantsStructured2 = resolved.structured === true || resolved.timeline === true || resolved.causal === true || resolved.derived === true || resolved.detail === "minimal" || resolved.detail === "full" || resolved.format != null;
|
|
3728
|
+
if (wantsStructured2) {
|
|
3729
|
+
const result = _Graph.inspectorEnabled ? this._createObserveResult(path, target, resolved) : this._createFallbackObserveResult(path, resolved);
|
|
3730
|
+
if (resolved.format != null) {
|
|
3731
|
+
this._attachFormatLogger(result, resolved);
|
|
3732
|
+
}
|
|
3733
|
+
return result;
|
|
3636
3734
|
}
|
|
3637
3735
|
return {
|
|
3638
3736
|
subscribe(sink) {
|
|
@@ -3650,9 +3748,13 @@ var Graph = class _Graph {
|
|
|
3650
3748
|
}
|
|
3651
3749
|
const opts = resolveObserveDetail(pathOrOpts);
|
|
3652
3750
|
const actor = opts.actor;
|
|
3653
|
-
const wantsStructured = opts.structured === true || opts.timeline === true || opts.causal === true || opts.derived === true || opts.detail === "minimal" || opts.detail === "full";
|
|
3654
|
-
if (wantsStructured
|
|
3655
|
-
|
|
3751
|
+
const wantsStructured = opts.structured === true || opts.timeline === true || opts.causal === true || opts.derived === true || opts.detail === "minimal" || opts.detail === "full" || opts.format != null;
|
|
3752
|
+
if (wantsStructured) {
|
|
3753
|
+
const result = _Graph.inspectorEnabled ? this._createObserveResultForAll(opts) : this._createFallbackObserveResultForAll(opts);
|
|
3754
|
+
if (opts.format != null) {
|
|
3755
|
+
this._attachFormatLogger(result, opts);
|
|
3756
|
+
}
|
|
3757
|
+
return result;
|
|
3656
3758
|
}
|
|
3657
3759
|
return {
|
|
3658
3760
|
subscribe: (sink) => {
|
|
@@ -3690,12 +3792,13 @@ var Graph = class _Graph {
|
|
|
3690
3792
|
dirtyCount: 0,
|
|
3691
3793
|
resolvedCount: 0,
|
|
3692
3794
|
events: [],
|
|
3693
|
-
|
|
3694
|
-
|
|
3795
|
+
anyCompletedCleanly: false,
|
|
3796
|
+
anyErrored: false
|
|
3695
3797
|
};
|
|
3696
3798
|
let lastTriggerDepIndex;
|
|
3697
3799
|
let lastRunDepValues;
|
|
3698
3800
|
let detachInspectorHook;
|
|
3801
|
+
let batchSeq = 0;
|
|
3699
3802
|
if ((causal || derived2) && target instanceof NodeImpl) {
|
|
3700
3803
|
detachInspectorHook = target._setInspectorHook((event) => {
|
|
3701
3804
|
if (event.kind === "dep_message") {
|
|
@@ -3708,15 +3811,16 @@ var Graph = class _Graph {
|
|
|
3708
3811
|
type: "derived",
|
|
3709
3812
|
path,
|
|
3710
3813
|
dep_values: [...event.depValues],
|
|
3711
|
-
...timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {}
|
|
3814
|
+
...timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching(), batch_id: batchSeq } : {}
|
|
3712
3815
|
});
|
|
3713
3816
|
}
|
|
3714
3817
|
});
|
|
3715
3818
|
}
|
|
3716
3819
|
const unsub = target.subscribe((msgs) => {
|
|
3820
|
+
batchSeq++;
|
|
3717
3821
|
for (const m of msgs) {
|
|
3718
3822
|
const t = m[0];
|
|
3719
|
-
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
|
|
3823
|
+
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching(), batch_id: batchSeq } : {};
|
|
3720
3824
|
const withCausal = causal && lastRunDepValues != null ? (() => {
|
|
3721
3825
|
const triggerDep = lastTriggerDepIndex != null && lastTriggerDepIndex >= 0 && target instanceof NodeImpl ? target._deps[lastTriggerDepIndex] : void 0;
|
|
3722
3826
|
const tv = triggerDep?.v;
|
|
@@ -3733,8 +3837,8 @@ var Graph = class _Graph {
|
|
|
3733
3837
|
} else if (minimal) {
|
|
3734
3838
|
if (t === DIRTY) result.dirtyCount++;
|
|
3735
3839
|
else if (t === RESOLVED) result.resolvedCount++;
|
|
3736
|
-
else if (t === COMPLETE && !result.
|
|
3737
|
-
else if (t === ERROR) result.
|
|
3840
|
+
else if (t === COMPLETE && !result.anyErrored) result.anyCompletedCleanly = true;
|
|
3841
|
+
else if (t === ERROR) result.anyErrored = true;
|
|
3738
3842
|
} else if (t === DIRTY) {
|
|
3739
3843
|
result.dirtyCount++;
|
|
3740
3844
|
result.events.push({ type: "dirty", path, ...base });
|
|
@@ -3742,10 +3846,10 @@ var Graph = class _Graph {
|
|
|
3742
3846
|
result.resolvedCount++;
|
|
3743
3847
|
result.events.push({ type: "resolved", path, ...base, ...withCausal });
|
|
3744
3848
|
} else if (t === COMPLETE) {
|
|
3745
|
-
if (!result.
|
|
3849
|
+
if (!result.anyErrored) result.anyCompletedCleanly = true;
|
|
3746
3850
|
result.events.push({ type: "complete", path, ...base });
|
|
3747
3851
|
} else if (t === ERROR) {
|
|
3748
|
-
result.
|
|
3852
|
+
result.anyErrored = true;
|
|
3749
3853
|
result.events.push({ type: "error", path, data: m[1], ...base });
|
|
3750
3854
|
}
|
|
3751
3855
|
}
|
|
@@ -3765,11 +3869,14 @@ var Graph = class _Graph {
|
|
|
3765
3869
|
get events() {
|
|
3766
3870
|
return result.events;
|
|
3767
3871
|
},
|
|
3768
|
-
get
|
|
3769
|
-
return result.
|
|
3872
|
+
get anyCompletedCleanly() {
|
|
3873
|
+
return result.anyCompletedCleanly;
|
|
3874
|
+
},
|
|
3875
|
+
get anyErrored() {
|
|
3876
|
+
return result.anyErrored;
|
|
3770
3877
|
},
|
|
3771
|
-
get
|
|
3772
|
-
return result.
|
|
3878
|
+
get completedWithoutErrors() {
|
|
3879
|
+
return result.anyCompletedCleanly && !result.anyErrored;
|
|
3773
3880
|
},
|
|
3774
3881
|
dispose() {
|
|
3775
3882
|
unsub();
|
|
@@ -3785,11 +3892,15 @@ var Graph = class _Graph {
|
|
|
3785
3892
|
Object.assign(merged, extra);
|
|
3786
3893
|
}
|
|
3787
3894
|
const resolvedTarget = graph.resolve(basePath);
|
|
3788
|
-
|
|
3895
|
+
const expanded = graph._createObserveResult(
|
|
3789
3896
|
basePath,
|
|
3790
3897
|
resolvedTarget,
|
|
3791
3898
|
resolveObserveDetail(merged)
|
|
3792
3899
|
);
|
|
3900
|
+
if (merged.format != null) {
|
|
3901
|
+
graph._attachFormatLogger(expanded, merged);
|
|
3902
|
+
}
|
|
3903
|
+
return expanded;
|
|
3793
3904
|
}
|
|
3794
3905
|
};
|
|
3795
3906
|
}
|
|
@@ -3801,27 +3912,33 @@ var Graph = class _Graph {
|
|
|
3801
3912
|
dirtyCount: 0,
|
|
3802
3913
|
resolvedCount: 0,
|
|
3803
3914
|
events: [],
|
|
3804
|
-
|
|
3805
|
-
|
|
3915
|
+
anyCompletedCleanly: false,
|
|
3916
|
+
anyErrored: false
|
|
3806
3917
|
};
|
|
3918
|
+
const nodeErrored = /* @__PURE__ */ new Set();
|
|
3807
3919
|
const actor = options.actor;
|
|
3808
3920
|
const targets = [];
|
|
3809
3921
|
this._collectObserveTargets("", targets);
|
|
3810
3922
|
targets.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
3811
3923
|
const picked = actor == null ? targets : targets.filter(([, nd]) => nd.allowsObserve(actor));
|
|
3924
|
+
let batchSeq = 0;
|
|
3812
3925
|
const unsubs = picked.map(
|
|
3813
3926
|
([path, nd]) => nd.subscribe((msgs) => {
|
|
3927
|
+
batchSeq++;
|
|
3814
3928
|
for (const m of msgs) {
|
|
3815
3929
|
const t = m[0];
|
|
3816
|
-
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
|
|
3930
|
+
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching(), batch_id: batchSeq } : {};
|
|
3817
3931
|
if (t === DATA) {
|
|
3818
3932
|
result.values[path] = m[1];
|
|
3819
3933
|
result.events.push({ type: "data", path, data: m[1], ...base });
|
|
3820
3934
|
} else if (minimal) {
|
|
3821
3935
|
if (t === DIRTY) result.dirtyCount++;
|
|
3822
3936
|
else if (t === RESOLVED) result.resolvedCount++;
|
|
3823
|
-
else if (t === COMPLETE && !
|
|
3824
|
-
else if (t === ERROR)
|
|
3937
|
+
else if (t === COMPLETE && !nodeErrored.has(path)) result.anyCompletedCleanly = true;
|
|
3938
|
+
else if (t === ERROR) {
|
|
3939
|
+
result.anyErrored = true;
|
|
3940
|
+
nodeErrored.add(path);
|
|
3941
|
+
}
|
|
3825
3942
|
} else if (t === DIRTY) {
|
|
3826
3943
|
result.dirtyCount++;
|
|
3827
3944
|
result.events.push({ type: "dirty", path, ...base });
|
|
@@ -3829,10 +3946,11 @@ var Graph = class _Graph {
|
|
|
3829
3946
|
result.resolvedCount++;
|
|
3830
3947
|
result.events.push({ type: "resolved", path, ...base });
|
|
3831
3948
|
} else if (t === COMPLETE) {
|
|
3832
|
-
if (!
|
|
3949
|
+
if (!nodeErrored.has(path)) result.anyCompletedCleanly = true;
|
|
3833
3950
|
result.events.push({ type: "complete", path, ...base });
|
|
3834
3951
|
} else if (t === ERROR) {
|
|
3835
|
-
result.
|
|
3952
|
+
result.anyErrored = true;
|
|
3953
|
+
nodeErrored.add(path);
|
|
3836
3954
|
result.events.push({ type: "error", path, data: m[1], ...base });
|
|
3837
3955
|
}
|
|
3838
3956
|
}
|
|
@@ -3852,11 +3970,14 @@ var Graph = class _Graph {
|
|
|
3852
3970
|
get events() {
|
|
3853
3971
|
return result.events;
|
|
3854
3972
|
},
|
|
3855
|
-
get
|
|
3856
|
-
return result.
|
|
3973
|
+
get anyCompletedCleanly() {
|
|
3974
|
+
return result.anyCompletedCleanly;
|
|
3857
3975
|
},
|
|
3858
|
-
get
|
|
3859
|
-
return result.
|
|
3976
|
+
get anyErrored() {
|
|
3977
|
+
return result.anyErrored;
|
|
3978
|
+
},
|
|
3979
|
+
get completedWithoutErrors() {
|
|
3980
|
+
return result.anyCompletedCleanly && !result.anyErrored;
|
|
3860
3981
|
},
|
|
3861
3982
|
dispose() {
|
|
3862
3983
|
for (const u of unsubs) u();
|
|
@@ -3869,25 +3990,169 @@ var Graph = class _Graph {
|
|
|
3869
3990
|
} else {
|
|
3870
3991
|
Object.assign(merged, extra);
|
|
3871
3992
|
}
|
|
3872
|
-
|
|
3993
|
+
const expanded = graph._createObserveResultForAll(resolveObserveDetail(merged));
|
|
3994
|
+
if (merged.format != null) {
|
|
3995
|
+
graph._attachFormatLogger(expanded, merged);
|
|
3996
|
+
}
|
|
3997
|
+
return expanded;
|
|
3873
3998
|
}
|
|
3874
3999
|
};
|
|
3875
4000
|
}
|
|
3876
4001
|
/**
|
|
3877
|
-
*
|
|
3878
|
-
*
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4002
|
+
* Fallback ObserveResult for single-node when inspector is disabled but `format` is requested.
|
|
4003
|
+
* Subscribes to raw messages and accumulates events with timeline info.
|
|
4004
|
+
*/
|
|
4005
|
+
_createFallbackObserveResult(path, options) {
|
|
4006
|
+
const timeline = options.timeline !== false;
|
|
4007
|
+
const acc = {
|
|
4008
|
+
values: {},
|
|
4009
|
+
dirtyCount: 0,
|
|
4010
|
+
resolvedCount: 0,
|
|
4011
|
+
events: [],
|
|
4012
|
+
anyCompletedCleanly: false,
|
|
4013
|
+
anyErrored: false
|
|
4014
|
+
};
|
|
4015
|
+
const target = this.resolve(path);
|
|
4016
|
+
let batchSeq = 0;
|
|
4017
|
+
const unsub = target.subscribe((msgs) => {
|
|
4018
|
+
batchSeq++;
|
|
4019
|
+
for (const m of msgs) {
|
|
4020
|
+
const t = m[0];
|
|
4021
|
+
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching(), batch_id: batchSeq } : {};
|
|
4022
|
+
if (t === DATA) {
|
|
4023
|
+
acc.values[path] = m[1];
|
|
4024
|
+
acc.events.push({ type: "data", path, data: m[1], ...base });
|
|
4025
|
+
} else if (t === DIRTY) {
|
|
4026
|
+
acc.dirtyCount++;
|
|
4027
|
+
acc.events.push({ type: "dirty", path, ...base });
|
|
4028
|
+
} else if (t === RESOLVED) {
|
|
4029
|
+
acc.resolvedCount++;
|
|
4030
|
+
acc.events.push({ type: "resolved", path, ...base });
|
|
4031
|
+
} else if (t === COMPLETE) {
|
|
4032
|
+
if (!acc.anyErrored) acc.anyCompletedCleanly = true;
|
|
4033
|
+
acc.events.push({ type: "complete", path, ...base });
|
|
4034
|
+
} else if (t === ERROR) {
|
|
4035
|
+
acc.anyErrored = true;
|
|
4036
|
+
acc.events.push({ type: "error", path, data: m[1], ...base });
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
});
|
|
4040
|
+
return {
|
|
4041
|
+
get values() {
|
|
4042
|
+
return acc.values;
|
|
4043
|
+
},
|
|
4044
|
+
get dirtyCount() {
|
|
4045
|
+
return acc.dirtyCount;
|
|
4046
|
+
},
|
|
4047
|
+
get resolvedCount() {
|
|
4048
|
+
return acc.resolvedCount;
|
|
4049
|
+
},
|
|
4050
|
+
get events() {
|
|
4051
|
+
return acc.events;
|
|
4052
|
+
},
|
|
4053
|
+
get anyCompletedCleanly() {
|
|
4054
|
+
return acc.anyCompletedCleanly;
|
|
4055
|
+
},
|
|
4056
|
+
get anyErrored() {
|
|
4057
|
+
return acc.anyErrored;
|
|
4058
|
+
},
|
|
4059
|
+
get completedWithoutErrors() {
|
|
4060
|
+
return acc.anyCompletedCleanly && !acc.anyErrored;
|
|
4061
|
+
},
|
|
4062
|
+
dispose() {
|
|
4063
|
+
unsub();
|
|
4064
|
+
},
|
|
4065
|
+
expand() {
|
|
4066
|
+
throw new Error("expand() requires inspector mode (Graph.inspectorEnabled = true)");
|
|
4067
|
+
}
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
/**
|
|
4071
|
+
* Fallback ObserveResult for graph-wide when inspector is disabled but `format` is requested.
|
|
4072
|
+
*/
|
|
4073
|
+
_createFallbackObserveResultForAll(options) {
|
|
4074
|
+
const timeline = options.timeline !== false;
|
|
4075
|
+
const actor = options.actor;
|
|
4076
|
+
const acc = {
|
|
4077
|
+
values: {},
|
|
4078
|
+
dirtyCount: 0,
|
|
4079
|
+
resolvedCount: 0,
|
|
4080
|
+
events: [],
|
|
4081
|
+
anyCompletedCleanly: false,
|
|
4082
|
+
anyErrored: false
|
|
4083
|
+
};
|
|
4084
|
+
const nodeErrored = /* @__PURE__ */ new Set();
|
|
4085
|
+
const targets = [];
|
|
4086
|
+
this._collectObserveTargets("", targets);
|
|
4087
|
+
targets.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
4088
|
+
const picked = actor == null ? targets : targets.filter(([, nd]) => nd.allowsObserve(actor));
|
|
4089
|
+
let batchSeq = 0;
|
|
4090
|
+
const unsubs = picked.map(
|
|
4091
|
+
([path, nd]) => nd.subscribe((msgs) => {
|
|
4092
|
+
batchSeq++;
|
|
4093
|
+
for (const m of msgs) {
|
|
4094
|
+
const t = m[0];
|
|
4095
|
+
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching(), batch_id: batchSeq } : {};
|
|
4096
|
+
if (t === DATA) {
|
|
4097
|
+
acc.values[path] = m[1];
|
|
4098
|
+
acc.events.push({ type: "data", path, data: m[1], ...base });
|
|
4099
|
+
} else if (t === DIRTY) {
|
|
4100
|
+
acc.dirtyCount++;
|
|
4101
|
+
acc.events.push({ type: "dirty", path, ...base });
|
|
4102
|
+
} else if (t === RESOLVED) {
|
|
4103
|
+
acc.resolvedCount++;
|
|
4104
|
+
acc.events.push({ type: "resolved", path, ...base });
|
|
4105
|
+
} else if (t === COMPLETE) {
|
|
4106
|
+
if (!nodeErrored.has(path)) acc.anyCompletedCleanly = true;
|
|
4107
|
+
acc.events.push({ type: "complete", path, ...base });
|
|
4108
|
+
} else if (t === ERROR) {
|
|
4109
|
+
acc.anyErrored = true;
|
|
4110
|
+
nodeErrored.add(path);
|
|
4111
|
+
acc.events.push({ type: "error", path, data: m[1], ...base });
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
})
|
|
4115
|
+
);
|
|
4116
|
+
return {
|
|
4117
|
+
get values() {
|
|
4118
|
+
return acc.values;
|
|
4119
|
+
},
|
|
4120
|
+
get dirtyCount() {
|
|
4121
|
+
return acc.dirtyCount;
|
|
4122
|
+
},
|
|
4123
|
+
get resolvedCount() {
|
|
4124
|
+
return acc.resolvedCount;
|
|
4125
|
+
},
|
|
4126
|
+
get events() {
|
|
4127
|
+
return acc.events;
|
|
4128
|
+
},
|
|
4129
|
+
get anyCompletedCleanly() {
|
|
4130
|
+
return acc.anyCompletedCleanly;
|
|
4131
|
+
},
|
|
4132
|
+
get anyErrored() {
|
|
4133
|
+
return acc.anyErrored;
|
|
4134
|
+
},
|
|
4135
|
+
get completedWithoutErrors() {
|
|
4136
|
+
return acc.anyCompletedCleanly && !acc.anyErrored;
|
|
4137
|
+
},
|
|
4138
|
+
dispose() {
|
|
4139
|
+
for (const u of unsubs) u();
|
|
4140
|
+
},
|
|
4141
|
+
expand() {
|
|
4142
|
+
throw new Error("expand() requires inspector mode (Graph.inspectorEnabled = true)");
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
/**
|
|
4147
|
+
* Attaches a format logger to an ObserveResult, rendering events as they arrive.
|
|
4148
|
+
* Wraps the result's dispose to flush pending events.
|
|
3884
4149
|
*/
|
|
3885
|
-
|
|
4150
|
+
_attachFormatLogger(result, options) {
|
|
4151
|
+
const format = options.format;
|
|
4152
|
+
const logger = options.logger ?? ((line) => console.log(line));
|
|
3886
4153
|
const include = options.includeTypes ? new Set(options.includeTypes) : null;
|
|
3887
4154
|
const exclude = options.excludeTypes ? new Set(options.excludeTypes) : null;
|
|
3888
|
-
const theme =
|
|
3889
|
-
const format = options.format ?? "pretty";
|
|
3890
|
-
const logger = options.logger ?? ((line) => console.log(line));
|
|
4155
|
+
const theme = resolveObserveTheme(options.theme);
|
|
3891
4156
|
const shouldLog = (type) => {
|
|
3892
4157
|
if (include?.has(type) === false) return false;
|
|
3893
4158
|
if (exclude?.has(type) === true) return false;
|
|
@@ -3912,133 +4177,26 @@ var Graph = class _Graph {
|
|
|
3912
4177
|
const batchPart = event.in_batch ? " [batch]" : "";
|
|
3913
4178
|
return `${pathPart}${color}${event.type.toUpperCase()}${theme.reset}${dataPart}${triggerPart}${batchPart}`;
|
|
3914
4179
|
};
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
const
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
completedCleanly: false,
|
|
3923
|
-
errored: false
|
|
3924
|
-
};
|
|
3925
|
-
let stop2 = () => {
|
|
3926
|
-
};
|
|
3927
|
-
const result2 = {
|
|
3928
|
-
get values() {
|
|
3929
|
-
return acc.values;
|
|
3930
|
-
},
|
|
3931
|
-
get dirtyCount() {
|
|
3932
|
-
return acc.dirtyCount;
|
|
3933
|
-
},
|
|
3934
|
-
get resolvedCount() {
|
|
3935
|
-
return acc.resolvedCount;
|
|
3936
|
-
},
|
|
3937
|
-
get events() {
|
|
3938
|
-
return acc.events;
|
|
3939
|
-
},
|
|
3940
|
-
get completedCleanly() {
|
|
3941
|
-
return acc.completedCleanly;
|
|
3942
|
-
},
|
|
3943
|
-
get errored() {
|
|
3944
|
-
return acc.errored;
|
|
3945
|
-
},
|
|
3946
|
-
dispose() {
|
|
3947
|
-
stop2();
|
|
3948
|
-
},
|
|
3949
|
-
expand() {
|
|
3950
|
-
throw new Error("expand() requires inspector mode (Graph.inspectorEnabled = true)");
|
|
3951
|
-
}
|
|
3952
|
-
};
|
|
3953
|
-
const pushEvent = (path, message) => {
|
|
3954
|
-
const t = message[0];
|
|
3955
|
-
const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
|
|
3956
|
-
let event;
|
|
3957
|
-
if (t === DATA) {
|
|
3958
|
-
if (path != null) acc.values[path] = message[1];
|
|
3959
|
-
event = { type: "data", ...path != null ? { path } : {}, data: message[1], ...base };
|
|
3960
|
-
} else if (t === DIRTY) {
|
|
3961
|
-
acc.dirtyCount += 1;
|
|
3962
|
-
event = { type: "dirty", ...path != null ? { path } : {}, ...base };
|
|
3963
|
-
} else if (t === RESOLVED) {
|
|
3964
|
-
acc.resolvedCount += 1;
|
|
3965
|
-
event = { type: "resolved", ...path != null ? { path } : {}, ...base };
|
|
3966
|
-
} else if (t === COMPLETE) {
|
|
3967
|
-
if (!acc.errored) acc.completedCleanly = true;
|
|
3968
|
-
event = { type: "complete", ...path != null ? { path } : {}, ...base };
|
|
3969
|
-
} else if (t === ERROR) {
|
|
3970
|
-
acc.errored = true;
|
|
3971
|
-
event = {
|
|
3972
|
-
type: "error",
|
|
3973
|
-
...path != null ? { path } : {},
|
|
3974
|
-
data: message[1],
|
|
3975
|
-
...base
|
|
3976
|
-
};
|
|
4180
|
+
let cursor = 0;
|
|
4181
|
+
const flush = () => {
|
|
4182
|
+
const events = result.events;
|
|
4183
|
+
while (cursor < events.length) {
|
|
4184
|
+
const event = events[cursor++];
|
|
4185
|
+
if (shouldLog(event.type)) {
|
|
4186
|
+
logger(renderEvent(event), event);
|
|
3977
4187
|
}
|
|
3978
|
-
if (!event) return;
|
|
3979
|
-
acc.events.push(event);
|
|
3980
|
-
if (!shouldLog(event.type)) return;
|
|
3981
|
-
logger(renderEvent(event), event);
|
|
3982
|
-
};
|
|
3983
|
-
if (options.path != null) {
|
|
3984
|
-
const stream2 = this.observe(options.path, {
|
|
3985
|
-
actor: options.actor,
|
|
3986
|
-
structured: false
|
|
3987
|
-
});
|
|
3988
|
-
stop2 = stream2.subscribe((messages) => {
|
|
3989
|
-
for (const m of messages) {
|
|
3990
|
-
pushEvent(options.path, m);
|
|
3991
|
-
}
|
|
3992
|
-
});
|
|
3993
|
-
} else {
|
|
3994
|
-
const stream2 = this.observe({ actor: options.actor, structured: false });
|
|
3995
|
-
stop2 = stream2.subscribe((path, messages) => {
|
|
3996
|
-
for (const m of messages) {
|
|
3997
|
-
pushEvent(path, m);
|
|
3998
|
-
}
|
|
3999
|
-
});
|
|
4000
4188
|
}
|
|
4001
|
-
return {
|
|
4002
|
-
result: result2,
|
|
4003
|
-
dispose() {
|
|
4004
|
-
result2.dispose();
|
|
4005
|
-
}
|
|
4006
|
-
};
|
|
4007
|
-
}
|
|
4008
|
-
const structuredObserveOptions = {
|
|
4009
|
-
actor: options.actor,
|
|
4010
|
-
structured: true,
|
|
4011
|
-
...options.timeline !== false ? { timeline: true } : {},
|
|
4012
|
-
...options.causal ? { causal: true } : {},
|
|
4013
|
-
...options.derived ? { derived: true } : {}
|
|
4014
4189
|
};
|
|
4015
|
-
const
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
for (const event of nextEvents) {
|
|
4021
|
-
if (!shouldLog(event.type)) continue;
|
|
4022
|
-
logger(renderEvent(event), event);
|
|
4023
|
-
}
|
|
4190
|
+
const origPush = result.events.push;
|
|
4191
|
+
result.events.push = function(...items) {
|
|
4192
|
+
const ret = origPush.apply(this, items);
|
|
4193
|
+
flush();
|
|
4194
|
+
return ret;
|
|
4024
4195
|
};
|
|
4025
|
-
const
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
}
|
|
4030
|
-
}) : stream.subscribe((_path, messages) => {
|
|
4031
|
-
if (messages.length > 0) {
|
|
4032
|
-
flushNewEvents();
|
|
4033
|
-
}
|
|
4034
|
-
});
|
|
4035
|
-
return {
|
|
4036
|
-
result,
|
|
4037
|
-
dispose() {
|
|
4038
|
-
stop();
|
|
4039
|
-
flushNewEvents();
|
|
4040
|
-
result.dispose();
|
|
4041
|
-
}
|
|
4196
|
+
const origDispose = result.dispose.bind(result);
|
|
4197
|
+
result.dispose = () => {
|
|
4198
|
+
origDispose();
|
|
4199
|
+
flush();
|
|
4042
4200
|
};
|
|
4043
4201
|
}
|
|
4044
4202
|
/**
|
|
@@ -4301,8 +4459,9 @@ var Graph = class _Graph {
|
|
|
4301
4459
|
/**
|
|
4302
4460
|
* Debounced persistence wired to graph-wide observe stream (spec §3.8 auto-checkpoint).
|
|
4303
4461
|
*
|
|
4304
|
-
* Checkpoint trigger uses {@link messageTier}: only batches containing tier >=
|
|
4305
|
-
* schedule a save (`DATA`/`RESOLVED`/terminal/destruction), never pure tier-0/1 control
|
|
4462
|
+
* Checkpoint trigger uses {@link messageTier}: only batches containing tier >= 3 messages
|
|
4463
|
+
* schedule a save (`DATA`/`RESOLVED`/terminal/destruction), never pure tier-0/1/2 control
|
|
4464
|
+
* waves (`START`/`DIRTY`/`INVALIDATE`/`PAUSE`/`RESUME`).
|
|
4306
4465
|
*/
|
|
4307
4466
|
autoCheckpoint(adapter, options = {}) {
|
|
4308
4467
|
const debounceMs = Math.max(0, options.debounceMs ?? 500);
|
|
@@ -4349,7 +4508,7 @@ var Graph = class _Graph {
|
|
|
4349
4508
|
timer = setTimeout(flush, debounceMs);
|
|
4350
4509
|
};
|
|
4351
4510
|
const off = this.observe().subscribe((path, messages) => {
|
|
4352
|
-
const triggeredByTier = messages.some((m) => messageTier(m[0]) >=
|
|
4511
|
+
const triggeredByTier = messages.some((m) => messageTier(m[0]) >= 3);
|
|
4353
4512
|
if (!triggeredByTier) return;
|
|
4354
4513
|
if (options.filter) {
|
|
4355
4514
|
const nd = this.resolve(path);
|
|
@@ -4433,33 +4592,21 @@ var Graph = class _Graph {
|
|
|
4433
4592
|
// ——————————————————————————————————————————————————————————————
|
|
4434
4593
|
/**
|
|
4435
4594
|
* When `false`, structured observation options (`causal`, `timeline`),
|
|
4436
|
-
*
|
|
4595
|
+
* and `trace()` writes are no-ops. Raw `observe()` always works.
|
|
4437
4596
|
*
|
|
4438
4597
|
* Default: `true` outside production (`process.env.NODE_ENV !== "production"`).
|
|
4439
4598
|
*/
|
|
4440
4599
|
static inspectorEnabled = !(typeof process !== "undefined" && process.env?.NODE_ENV === "production");
|
|
4441
4600
|
_annotations = /* @__PURE__ */ new Map();
|
|
4442
4601
|
_traceRing = new RingBuffer(1e3);
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
annotate(path, reason) {
|
|
4452
|
-
if (!_Graph.inspectorEnabled) return;
|
|
4453
|
-
this.resolve(path);
|
|
4454
|
-
this._annotations.set(path, reason);
|
|
4455
|
-
this._traceRing.push({ path, reason, timestamp_ns: monotonicNs() });
|
|
4456
|
-
}
|
|
4457
|
-
/**
|
|
4458
|
-
* Returns a chronological log of all reasoning annotations (ring buffer).
|
|
4459
|
-
*
|
|
4460
|
-
* @returns `[]` when {@link Graph.inspectorEnabled} is `false`.
|
|
4461
|
-
*/
|
|
4462
|
-
traceLog() {
|
|
4602
|
+
trace(path, reason) {
|
|
4603
|
+
if (path != null && reason != null) {
|
|
4604
|
+
if (!_Graph.inspectorEnabled) return;
|
|
4605
|
+
this.resolve(path);
|
|
4606
|
+
this._annotations.set(path, reason);
|
|
4607
|
+
this._traceRing.push({ path, reason, timestamp_ns: monotonicNs() });
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4463
4610
|
if (!_Graph.inspectorEnabled) return [];
|
|
4464
4611
|
return this._traceRing.toArray();
|
|
4465
4612
|
}
|
|
@@ -5084,11 +5231,8 @@ var GraphReflyModule = _GraphReflyModule;
|
|
|
5084
5231
|
getActor,
|
|
5085
5232
|
getGraphToken,
|
|
5086
5233
|
getNodeToken,
|
|
5087
|
-
observeGraph$,
|
|
5088
|
-
observeNode$,
|
|
5089
5234
|
observeSSE,
|
|
5090
5235
|
observeSubscription,
|
|
5091
|
-
toMessages$,
|
|
5092
5236
|
toObservable
|
|
5093
5237
|
});
|
|
5094
5238
|
//# sourceMappingURL=index.cjs.map
|