@graphrefly/graphrefly 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +234 -0
  3. package/dist/chunk-5X3LAO3B.js +1571 -0
  4. package/dist/chunk-5X3LAO3B.js.map +1 -0
  5. package/dist/chunk-6W5SGIGB.js +1793 -0
  6. package/dist/chunk-6W5SGIGB.js.map +1 -0
  7. package/dist/chunk-CP6MNKAA.js +97 -0
  8. package/dist/chunk-CP6MNKAA.js.map +1 -0
  9. package/dist/chunk-HP7OKEOE.js +107 -0
  10. package/dist/chunk-HP7OKEOE.js.map +1 -0
  11. package/dist/chunk-KWXPDASV.js +781 -0
  12. package/dist/chunk-KWXPDASV.js.map +1 -0
  13. package/dist/chunk-O3PI7W45.js +68 -0
  14. package/dist/chunk-O3PI7W45.js.map +1 -0
  15. package/dist/chunk-QW7H3ICI.js +1372 -0
  16. package/dist/chunk-QW7H3ICI.js.map +1 -0
  17. package/dist/chunk-VPS7L64N.js +4785 -0
  18. package/dist/chunk-VPS7L64N.js.map +1 -0
  19. package/dist/chunk-Z4Y4FMQN.js +1097 -0
  20. package/dist/chunk-Z4Y4FMQN.js.map +1 -0
  21. package/dist/compat/nestjs/index.cjs +4883 -0
  22. package/dist/compat/nestjs/index.cjs.map +1 -0
  23. package/dist/compat/nestjs/index.d.cts +7 -0
  24. package/dist/compat/nestjs/index.d.ts +7 -0
  25. package/dist/compat/nestjs/index.js +84 -0
  26. package/dist/compat/nestjs/index.js.map +1 -0
  27. package/dist/core/index.cjs +1632 -0
  28. package/dist/core/index.cjs.map +1 -0
  29. package/dist/core/index.d.cts +2 -0
  30. package/dist/core/index.d.ts +2 -0
  31. package/dist/core/index.js +90 -0
  32. package/dist/core/index.js.map +1 -0
  33. package/dist/extra/index.cjs +6885 -0
  34. package/dist/extra/index.cjs.map +1 -0
  35. package/dist/extra/index.d.cts +5 -0
  36. package/dist/extra/index.d.ts +5 -0
  37. package/dist/extra/index.js +290 -0
  38. package/dist/extra/index.js.map +1 -0
  39. package/dist/graph/index.cjs +3225 -0
  40. package/dist/graph/index.cjs.map +1 -0
  41. package/dist/graph/index.d.cts +3 -0
  42. package/dist/graph/index.d.ts +3 -0
  43. package/dist/graph/index.js +25 -0
  44. package/dist/graph/index.js.map +1 -0
  45. package/dist/graph-CL_ZDAj9.d.cts +605 -0
  46. package/dist/graph-D18qmsNm.d.ts +605 -0
  47. package/dist/index-B6SsZs2h.d.cts +3463 -0
  48. package/dist/index-B7eOdgEx.d.ts +449 -0
  49. package/dist/index-BHUvlQ3v.d.ts +3463 -0
  50. package/dist/index-BtK55IE2.d.ts +231 -0
  51. package/dist/index-BvhgZRHK.d.cts +231 -0
  52. package/dist/index-Bvy_6CaN.d.ts +452 -0
  53. package/dist/index-C3BMRmmp.d.cts +449 -0
  54. package/dist/index-C5mqLhMX.d.cts +452 -0
  55. package/dist/index-CP_QvbWu.d.ts +940 -0
  56. package/dist/index-D_geH2Bm.d.cts +940 -0
  57. package/dist/index.cjs +14843 -0
  58. package/dist/index.cjs.map +1 -0
  59. package/dist/index.d.cts +1517 -0
  60. package/dist/index.d.ts +1517 -0
  61. package/dist/index.js +3649 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/meta-BsF6Sag9.d.cts +607 -0
  64. package/dist/meta-BsF6Sag9.d.ts +607 -0
  65. package/dist/patterns/reactive-layout/index.cjs +4143 -0
  66. package/dist/patterns/reactive-layout/index.cjs.map +1 -0
  67. package/dist/patterns/reactive-layout/index.d.cts +3 -0
  68. package/dist/patterns/reactive-layout/index.d.ts +3 -0
  69. package/dist/patterns/reactive-layout/index.js +38 -0
  70. package/dist/patterns/reactive-layout/index.js.map +1 -0
  71. package/dist/reactive-log-BfvfNWQh.d.cts +137 -0
  72. package/dist/reactive-log-ohLmTXoZ.d.ts +137 -0
  73. package/package.json +256 -0
@@ -0,0 +1,3225 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/graph/index.ts
21
+ var graph_exports = {};
22
+ __export(graph_exports, {
23
+ GRAPH_META_SEGMENT: () => GRAPH_META_SEGMENT,
24
+ Graph: () => Graph,
25
+ JsonCodec: () => JsonCodec,
26
+ createDagCborCodec: () => createDagCborCodec,
27
+ createDagCborZstdCodec: () => createDagCborZstdCodec,
28
+ negotiateCodec: () => negotiateCodec,
29
+ reachable: () => reachable,
30
+ replayWAL: () => replayWAL
31
+ });
32
+ module.exports = __toCommonJS(graph_exports);
33
+
34
+ // src/graph/codec.ts
35
+ var JsonCodec = {
36
+ contentType: "application/json",
37
+ name: "json",
38
+ encode(snapshot) {
39
+ const json = JSON.stringify(snapshot);
40
+ return new TextEncoder().encode(json);
41
+ },
42
+ decode(buffer) {
43
+ const json = new TextDecoder().decode(buffer);
44
+ return JSON.parse(json);
45
+ }
46
+ };
47
+ function createDagCborCodec(dagCbor) {
48
+ return {
49
+ contentType: "application/dag-cbor",
50
+ name: "dag-cbor",
51
+ encode: (snapshot) => dagCbor.encode(snapshot),
52
+ decode: (buffer) => dagCbor.decode(buffer)
53
+ };
54
+ }
55
+ function createDagCborZstdCodec(dagCbor, zstd) {
56
+ return {
57
+ contentType: "application/dag-cbor+zstd",
58
+ name: "dag-cbor-zstd",
59
+ encode: (snapshot) => zstd.compressSync(dagCbor.encode(snapshot)),
60
+ decode: (buffer) => dagCbor.decode(zstd.decompressSync(buffer))
61
+ };
62
+ }
63
+ function negotiateCodec(localPreference, remoteContentTypes) {
64
+ const remoteSet = new Set(remoteContentTypes);
65
+ for (const codec of localPreference) {
66
+ if (remoteSet.has(codec.contentType)) return codec;
67
+ }
68
+ return null;
69
+ }
70
+ function replayWAL(entries) {
71
+ if (entries.length === 0) {
72
+ throw new Error("WAL is empty \u2014 need at least one full snapshot");
73
+ }
74
+ const first = entries[0];
75
+ if (first.type !== "full") {
76
+ throw new Error("WAL must start with a full snapshot");
77
+ }
78
+ const result = JSON.parse(JSON.stringify(first.snapshot));
79
+ for (let i = 1; i < entries.length; i++) {
80
+ const entry = entries[i];
81
+ if (entry.type === "full") {
82
+ Object.assign(result, JSON.parse(JSON.stringify(entry.snapshot)));
83
+ continue;
84
+ }
85
+ const delta = entry.delta;
86
+ for (const [name, patch] of Object.entries(delta.nodes)) {
87
+ if (result.nodes[name]) {
88
+ result.nodes[name].value = patch.value;
89
+ if (patch.meta) {
90
+ result.nodes[name].meta = patch.meta;
91
+ }
92
+ }
93
+ }
94
+ for (const name of delta.removed) {
95
+ delete result.nodes[name];
96
+ }
97
+ const edges = [...result.edges];
98
+ for (const edge of delta.edgesRemoved) {
99
+ const idx = edges.findIndex((e) => e.from === edge.from && e.to === edge.to);
100
+ if (idx !== -1) edges.splice(idx, 1);
101
+ }
102
+ for (const edge of delta.edgesAdded) {
103
+ edges.push(edge);
104
+ }
105
+ result.edges = edges;
106
+ }
107
+ return result;
108
+ }
109
+
110
+ // src/core/messages.ts
111
+ var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
112
+ var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
113
+ var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
114
+ var INVALIDATE = /* @__PURE__ */ Symbol.for("graphrefly/INVALIDATE");
115
+ var PAUSE = /* @__PURE__ */ Symbol.for("graphrefly/PAUSE");
116
+ var RESUME = /* @__PURE__ */ Symbol.for("graphrefly/RESUME");
117
+ var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
118
+ var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
119
+ var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
120
+ function messageTier(t) {
121
+ if (t === DIRTY || t === INVALIDATE) return 0;
122
+ if (t === PAUSE || t === RESUME) return 1;
123
+ if (t === DATA || t === RESOLVED) return 2;
124
+ if (t === COMPLETE || t === ERROR) return 3;
125
+ if (t === TEARDOWN) return 4;
126
+ return 0;
127
+ }
128
+ function isPhase2Message(msg) {
129
+ const t = msg[0];
130
+ return t === DATA || t === RESOLVED;
131
+ }
132
+ function isTerminalMessage(t) {
133
+ return t === COMPLETE || t === ERROR;
134
+ }
135
+ function propagatesToMeta(t) {
136
+ return t === TEARDOWN;
137
+ }
138
+
139
+ // src/core/batch.ts
140
+ var batchDepth = 0;
141
+ var flushInProgress = false;
142
+ var pendingPhase2 = [];
143
+ var pendingPhase3 = [];
144
+ function isBatching() {
145
+ return batchDepth > 0 || flushInProgress;
146
+ }
147
+ function partitionForBatch(messages) {
148
+ const immediate = [];
149
+ const deferred = [];
150
+ const terminal = [];
151
+ for (const m of messages) {
152
+ if (isPhase2Message(m)) {
153
+ deferred.push(m);
154
+ } else if (isTerminalMessage(m[0])) {
155
+ terminal.push(m);
156
+ } else {
157
+ immediate.push(m);
158
+ }
159
+ }
160
+ return { immediate, deferred, terminal };
161
+ }
162
+ function emitWithBatch(emit, messages, phase = 2) {
163
+ if (messages.length === 0) {
164
+ return;
165
+ }
166
+ const queue = phase === 3 ? pendingPhase3 : pendingPhase2;
167
+ if (messages.length === 1) {
168
+ const t = messages[0][0];
169
+ if (t === DATA || t === RESOLVED) {
170
+ if (isBatching()) {
171
+ queue.push(() => emit(messages));
172
+ } else {
173
+ emit(messages);
174
+ }
175
+ } else if (isTerminalMessage(t)) {
176
+ if (isBatching()) {
177
+ queue.push(() => emit(messages));
178
+ } else {
179
+ emit(messages);
180
+ }
181
+ } else {
182
+ emit(messages);
183
+ }
184
+ return;
185
+ }
186
+ const { immediate, deferred, terminal } = partitionForBatch(messages);
187
+ if (immediate.length > 0) {
188
+ emit(immediate);
189
+ }
190
+ if (isBatching()) {
191
+ if (deferred.length > 0) {
192
+ queue.push(() => emit(deferred));
193
+ }
194
+ if (terminal.length > 0) {
195
+ queue.push(() => emit(terminal));
196
+ }
197
+ } else {
198
+ if (deferred.length > 0) {
199
+ emit(deferred);
200
+ }
201
+ if (terminal.length > 0) {
202
+ emit(terminal);
203
+ }
204
+ }
205
+ }
206
+
207
+ // src/core/clock.ts
208
+ function monotonicNs() {
209
+ return Math.trunc(performance.now() * 1e6);
210
+ }
211
+ function wallClockNs() {
212
+ return Date.now() * 1e6;
213
+ }
214
+
215
+ // src/core/guard.ts
216
+ var GuardDenied = class extends Error {
217
+ actor;
218
+ action;
219
+ nodeName;
220
+ /**
221
+ * @param details - Actor, action, and optional node name for the denial.
222
+ * @param message - Optional override for the default error message.
223
+ */
224
+ constructor(details, message) {
225
+ super(
226
+ message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
227
+ );
228
+ this.name = "GuardDenied";
229
+ this.actor = details.actor;
230
+ this.action = details.action;
231
+ this.nodeName = details.nodeName;
232
+ }
233
+ /** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
234
+ get node() {
235
+ return this.nodeName;
236
+ }
237
+ };
238
+ var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
239
+ function accessHintForGuard(guard) {
240
+ const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
241
+ if (allowed.length === 0) return "restricted";
242
+ if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
243
+ return "both";
244
+ }
245
+ if (allowed.length === 1) return allowed[0];
246
+ return allowed.join("+");
247
+ }
248
+
249
+ // src/core/actor.ts
250
+ var DEFAULT_ACTOR = { type: "system", id: "" };
251
+ function normalizeActor(actor) {
252
+ if (actor == null) return DEFAULT_ACTOR;
253
+ const { type, id, ...rest } = actor;
254
+ return {
255
+ type: type ?? "system",
256
+ id: id ?? "",
257
+ ...rest
258
+ };
259
+ }
260
+
261
+ // src/core/versioning.ts
262
+ var import_node_crypto = require("crypto");
263
+ function canonicalizeForHash(value) {
264
+ if (value === void 0) return null;
265
+ if (typeof value === "number") {
266
+ if (!Number.isFinite(value)) {
267
+ throw new TypeError(`Cannot hash non-finite number: ${value}`);
268
+ }
269
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
270
+ throw new TypeError(
271
+ `Cannot hash integer outside safe range (|n| > 2^53-1): ${value}. Cross-language cid parity is not guaranteed for unsafe integers.`
272
+ );
273
+ }
274
+ return value;
275
+ }
276
+ if (typeof value === "string" || typeof value === "boolean" || value === null) {
277
+ return value;
278
+ }
279
+ if (Array.isArray(value)) {
280
+ return value.map(canonicalizeForHash);
281
+ }
282
+ if (typeof value === "object" && value !== null) {
283
+ const sorted = {};
284
+ for (const k of Object.keys(value).sort()) {
285
+ sorted[k] = canonicalizeForHash(value[k]);
286
+ }
287
+ return sorted;
288
+ }
289
+ return null;
290
+ }
291
+ function defaultHash(value) {
292
+ const canonical = canonicalizeForHash(value ?? null);
293
+ const json = JSON.stringify(canonical);
294
+ return (0, import_node_crypto.createHash)("sha256").update(json).digest("hex").slice(0, 16);
295
+ }
296
+ function createVersioning(level, initialValue, opts) {
297
+ const id = opts?.id ?? (0, import_node_crypto.randomUUID)();
298
+ if (level === 0) {
299
+ return { id, version: 0 };
300
+ }
301
+ const hash = opts?.hash ?? defaultHash;
302
+ const cid = hash(initialValue);
303
+ return { id, version: 0, cid, prev: null };
304
+ }
305
+ function advanceVersion(info, newValue, hashFn) {
306
+ info.version += 1;
307
+ if ("cid" in info) {
308
+ info.prev = info.cid;
309
+ info.cid = hashFn(newValue);
310
+ }
311
+ }
312
+
313
+ // src/core/node.ts
314
+ function createIntBitSet() {
315
+ let bits = 0;
316
+ return {
317
+ set(i) {
318
+ bits |= 1 << i;
319
+ },
320
+ clear(i) {
321
+ bits &= ~(1 << i);
322
+ },
323
+ has(i) {
324
+ return (bits & 1 << i) !== 0;
325
+ },
326
+ covers(other) {
327
+ return (bits & other._bits()) === other._bits();
328
+ },
329
+ any() {
330
+ return bits !== 0;
331
+ },
332
+ reset() {
333
+ bits = 0;
334
+ },
335
+ _bits() {
336
+ return bits;
337
+ }
338
+ };
339
+ }
340
+ function createArrayBitSet(size) {
341
+ const words = new Uint32Array(Math.ceil(size / 32));
342
+ return {
343
+ set(i) {
344
+ words[i >>> 5] |= 1 << (i & 31);
345
+ },
346
+ clear(i) {
347
+ words[i >>> 5] &= ~(1 << (i & 31));
348
+ },
349
+ has(i) {
350
+ return (words[i >>> 5] & 1 << (i & 31)) !== 0;
351
+ },
352
+ covers(other) {
353
+ const ow = other._words;
354
+ for (let w = 0; w < words.length; w++) {
355
+ if ((words[w] & ow[w]) >>> 0 !== ow[w]) return false;
356
+ }
357
+ return true;
358
+ },
359
+ any() {
360
+ for (let w = 0; w < words.length; w++) {
361
+ if (words[w] !== 0) return true;
362
+ }
363
+ return false;
364
+ },
365
+ reset() {
366
+ words.fill(0);
367
+ },
368
+ _words: words
369
+ };
370
+ }
371
+ function createBitSet(size) {
372
+ return size <= 31 ? createIntBitSet() : createArrayBitSet(size);
373
+ }
374
+ var isNodeArray = (value) => Array.isArray(value);
375
+ var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
376
+ var isCleanupFn = (value) => typeof value === "function";
377
+ var statusAfterMessage = (status, msg) => {
378
+ const t = msg[0];
379
+ if (t === DIRTY) return "dirty";
380
+ if (t === DATA) return "settled";
381
+ if (t === RESOLVED) return "resolved";
382
+ if (t === COMPLETE) return "completed";
383
+ if (t === ERROR) return "errored";
384
+ if (t === INVALIDATE) return "dirty";
385
+ if (t === TEARDOWN) return "disconnected";
386
+ return status;
387
+ };
388
+ var NodeImpl = class {
389
+ // --- Configuration (set once, never reassigned) ---
390
+ _optsName;
391
+ _registryName;
392
+ /** @internal — read by {@link describeNode} before inference. */
393
+ _describeKind;
394
+ meta;
395
+ _deps;
396
+ _fn;
397
+ _opts;
398
+ _equals;
399
+ _onMessage;
400
+ /** @internal — read by {@link describeNode} for `accessHintForGuard`. */
401
+ _guard;
402
+ _lastMutation;
403
+ _hasDeps;
404
+ _autoComplete;
405
+ _isSingleDep;
406
+ // --- Mutable state ---
407
+ _cached;
408
+ _status;
409
+ _terminal = false;
410
+ _connected = false;
411
+ _producerStarted = false;
412
+ _connecting = false;
413
+ _manualEmitUsed = false;
414
+ _sinkCount = 0;
415
+ _singleDepSinkCount = 0;
416
+ // --- Object/collection state ---
417
+ _depDirtyMask;
418
+ _depSettledMask;
419
+ _depCompleteMask;
420
+ _allDepsCompleteMask;
421
+ _lastDepValues;
422
+ _cleanup;
423
+ _sinks = null;
424
+ _singleDepSinks = /* @__PURE__ */ new WeakSet();
425
+ _upstreamUnsubs = [];
426
+ _actions;
427
+ _boundEmitToSinks;
428
+ _inspectorHook;
429
+ _versioning;
430
+ _hashFn;
431
+ constructor(deps, fn, opts) {
432
+ this._deps = deps;
433
+ this._fn = fn;
434
+ this._opts = opts;
435
+ this._optsName = opts.name;
436
+ this._describeKind = opts.describeKind;
437
+ this._equals = opts.equals ?? Object.is;
438
+ this._onMessage = opts.onMessage;
439
+ this._guard = opts.guard;
440
+ this._hasDeps = deps.length > 0;
441
+ this._autoComplete = opts.completeWhenDepsComplete ?? true;
442
+ this._isSingleDep = deps.length === 1 && fn != null;
443
+ this._cached = opts.initial;
444
+ this._status = this._hasDeps ? "disconnected" : "settled";
445
+ this._hashFn = opts.versioningHash ?? defaultHash;
446
+ this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached, {
447
+ id: opts.versioningId,
448
+ hash: this._hashFn
449
+ }) : void 0;
450
+ this._depDirtyMask = createBitSet(deps.length);
451
+ this._depSettledMask = createBitSet(deps.length);
452
+ this._depCompleteMask = createBitSet(deps.length);
453
+ this._allDepsCompleteMask = createBitSet(deps.length);
454
+ for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
455
+ const meta = {};
456
+ for (const [k, v] of Object.entries(opts.meta ?? {})) {
457
+ meta[k] = node({
458
+ initial: v,
459
+ name: `${opts.name ?? "node"}:meta:${k}`,
460
+ describeKind: "state",
461
+ ...opts.guard != null ? { guard: opts.guard } : {}
462
+ });
463
+ }
464
+ Object.freeze(meta);
465
+ this.meta = meta;
466
+ const self = this;
467
+ this._actions = {
468
+ down(messages) {
469
+ self._manualEmitUsed = true;
470
+ self._downInternal(messages);
471
+ },
472
+ emit(value) {
473
+ self._manualEmitUsed = true;
474
+ self._emitAutoValue(value);
475
+ },
476
+ up(messages) {
477
+ self._upInternal(messages);
478
+ }
479
+ };
480
+ this.down = this.down.bind(this);
481
+ this.up = this.up.bind(this);
482
+ this._boundEmitToSinks = this._emitToSinks.bind(this);
483
+ }
484
+ get name() {
485
+ return this._registryName ?? this._optsName;
486
+ }
487
+ /**
488
+ * When a node is registered with {@link Graph.add} without an options `name`,
489
+ * the graph assigns the registry local name for introspection (parity with graphrefly-py).
490
+ */
491
+ _assignRegistryName(localName) {
492
+ if (this._optsName !== void 0 || this._registryName !== void 0) return;
493
+ this._registryName = localName;
494
+ }
495
+ /**
496
+ * @internal Attach/remove inspector hook for graph-level observability.
497
+ * Returns a disposer that restores the previous hook.
498
+ */
499
+ _setInspectorHook(hook) {
500
+ const prev = this._inspectorHook;
501
+ this._inspectorHook = hook;
502
+ return () => {
503
+ if (this._inspectorHook === hook) {
504
+ this._inspectorHook = prev;
505
+ }
506
+ };
507
+ }
508
+ // --- Public interface (Node<T>) ---
509
+ get status() {
510
+ return this._status;
511
+ }
512
+ get lastMutation() {
513
+ return this._lastMutation;
514
+ }
515
+ get v() {
516
+ return this._versioning;
517
+ }
518
+ /**
519
+ * Retroactively apply versioning to a node that was created without it.
520
+ * No-op if versioning is already enabled.
521
+ *
522
+ * Version starts at 0 regardless of prior DATA emissions — it tracks
523
+ * changes from the moment versioning is enabled, not historical ones.
524
+ *
525
+ * @internal — used by {@link Graph.setVersioning}.
526
+ */
527
+ _applyVersioning(level, opts) {
528
+ if (this._versioning != null) return;
529
+ this._hashFn = opts?.hash ?? this._hashFn;
530
+ this._versioning = createVersioning(level, this._cached, {
531
+ id: opts?.id,
532
+ hash: this._hashFn
533
+ });
534
+ }
535
+ hasGuard() {
536
+ return this._guard != null;
537
+ }
538
+ allowsObserve(actor) {
539
+ if (this._guard == null) return true;
540
+ return this._guard(normalizeActor(actor), "observe");
541
+ }
542
+ get() {
543
+ return this._cached;
544
+ }
545
+ down(messages, options) {
546
+ if (messages.length === 0) return;
547
+ if (!options?.internal && this._guard != null) {
548
+ const actor = normalizeActor(options?.actor);
549
+ const delivery = options?.delivery ?? "write";
550
+ const action = delivery === "signal" ? "signal" : "write";
551
+ if (!this._guard(actor, action)) {
552
+ throw new GuardDenied({ actor, action, nodeName: this.name });
553
+ }
554
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
555
+ }
556
+ this._downInternal(messages);
557
+ }
558
+ _downInternal(messages) {
559
+ if (messages.length === 0) return;
560
+ let lifecycleMessages = messages;
561
+ let sinkMessages = messages;
562
+ if (this._terminal && !this._opts.resubscribable) {
563
+ const terminalPassthrough = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
564
+ if (terminalPassthrough.length === 0) return;
565
+ lifecycleMessages = terminalPassthrough;
566
+ sinkMessages = terminalPassthrough;
567
+ }
568
+ this._handleLocalLifecycle(lifecycleMessages);
569
+ if (this._canSkipDirty()) {
570
+ let hasPhase2 = false;
571
+ for (let i = 0; i < sinkMessages.length; i++) {
572
+ const t = sinkMessages[i][0];
573
+ if (t === DATA || t === RESOLVED) {
574
+ hasPhase2 = true;
575
+ break;
576
+ }
577
+ }
578
+ if (hasPhase2) {
579
+ const filtered = [];
580
+ for (let i = 0; i < sinkMessages.length; i++) {
581
+ if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
582
+ }
583
+ if (filtered.length > 0) {
584
+ emitWithBatch(this._boundEmitToSinks, filtered);
585
+ }
586
+ return;
587
+ }
588
+ }
589
+ emitWithBatch(this._boundEmitToSinks, sinkMessages);
590
+ }
591
+ subscribe(sink, hints) {
592
+ if (hints?.actor != null && this._guard != null) {
593
+ const actor = normalizeActor(hints.actor);
594
+ if (!this._guard(actor, "observe")) {
595
+ throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
596
+ }
597
+ }
598
+ if (this._terminal && this._opts.resubscribable) {
599
+ this._terminal = false;
600
+ this._status = this._hasDeps ? "disconnected" : "settled";
601
+ this._opts.onResubscribe?.();
602
+ }
603
+ this._sinkCount += 1;
604
+ if (hints?.singleDep) {
605
+ this._singleDepSinkCount += 1;
606
+ this._singleDepSinks.add(sink);
607
+ }
608
+ if (this._sinks == null) {
609
+ this._sinks = sink;
610
+ } else if (typeof this._sinks === "function") {
611
+ this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
612
+ } else {
613
+ this._sinks.add(sink);
614
+ }
615
+ if (this._hasDeps) {
616
+ this._connectUpstream();
617
+ } else if (this._fn) {
618
+ this._startProducer();
619
+ }
620
+ let removed = false;
621
+ return () => {
622
+ if (removed) return;
623
+ removed = true;
624
+ this._sinkCount -= 1;
625
+ if (this._singleDepSinks.has(sink)) {
626
+ this._singleDepSinkCount -= 1;
627
+ this._singleDepSinks.delete(sink);
628
+ }
629
+ if (this._sinks == null) return;
630
+ if (typeof this._sinks === "function") {
631
+ if (this._sinks === sink) this._sinks = null;
632
+ } else {
633
+ this._sinks.delete(sink);
634
+ if (this._sinks.size === 1) {
635
+ const [only] = this._sinks;
636
+ this._sinks = only;
637
+ } else if (this._sinks.size === 0) {
638
+ this._sinks = null;
639
+ }
640
+ }
641
+ if (this._sinks == null) {
642
+ this._disconnectUpstream();
643
+ this._stopProducer();
644
+ }
645
+ };
646
+ }
647
+ up(messages, options) {
648
+ if (!this._hasDeps) return;
649
+ if (!options?.internal && this._guard != null) {
650
+ const actor = normalizeActor(options?.actor);
651
+ if (!this._guard(actor, "write")) {
652
+ throw new GuardDenied({ actor, action: "write", nodeName: this.name });
653
+ }
654
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
655
+ }
656
+ for (const dep of this._deps) {
657
+ if (options === void 0) {
658
+ dep.up?.(messages);
659
+ } else {
660
+ dep.up?.(messages, options);
661
+ }
662
+ }
663
+ }
664
+ _upInternal(messages) {
665
+ if (!this._hasDeps) return;
666
+ for (const dep of this._deps) {
667
+ dep.up?.(messages, { internal: true });
668
+ }
669
+ }
670
+ unsubscribe() {
671
+ if (!this._hasDeps) return;
672
+ this._disconnectUpstream();
673
+ }
674
+ // --- Private methods (prototype, _ prefix) ---
675
+ _emitToSinks(messages) {
676
+ if (this._sinks == null) return;
677
+ if (typeof this._sinks === "function") {
678
+ this._sinks(messages);
679
+ return;
680
+ }
681
+ const snapshot = [...this._sinks];
682
+ for (const sink of snapshot) {
683
+ sink(messages);
684
+ }
685
+ }
686
+ _handleLocalLifecycle(messages) {
687
+ for (const m of messages) {
688
+ const t = m[0];
689
+ if (t === DATA) {
690
+ this._cached = m[1];
691
+ if (this._versioning != null) {
692
+ advanceVersion(this._versioning, m[1], this._hashFn);
693
+ }
694
+ }
695
+ if (t === INVALIDATE) {
696
+ const cleanupFn = this._cleanup;
697
+ this._cleanup = void 0;
698
+ cleanupFn?.();
699
+ this._cached = void 0;
700
+ this._lastDepValues = void 0;
701
+ }
702
+ this._status = statusAfterMessage(this._status, m);
703
+ if (t === COMPLETE || t === ERROR) {
704
+ this._terminal = true;
705
+ }
706
+ if (t === TEARDOWN) {
707
+ if (this._opts.resetOnTeardown) {
708
+ this._cached = void 0;
709
+ }
710
+ const teardownCleanup = this._cleanup;
711
+ this._cleanup = void 0;
712
+ teardownCleanup?.();
713
+ try {
714
+ this._propagateToMeta(t);
715
+ } finally {
716
+ this._disconnectUpstream();
717
+ this._stopProducer();
718
+ }
719
+ }
720
+ if (t !== TEARDOWN && propagatesToMeta(t)) {
721
+ this._propagateToMeta(t);
722
+ }
723
+ }
724
+ }
725
+ /** Propagate a signal to all companion meta nodes (best-effort). */
726
+ _propagateToMeta(t) {
727
+ for (const metaNode of Object.values(this.meta)) {
728
+ try {
729
+ metaNode._downInternal([[t]]);
730
+ } catch {
731
+ }
732
+ }
733
+ }
734
+ _canSkipDirty() {
735
+ return this._sinkCount === 1 && this._singleDepSinkCount === 1;
736
+ }
737
+ _emitAutoValue(value) {
738
+ const wasDirty = this._status === "dirty";
739
+ const unchanged = this._equals(this._cached, value);
740
+ if (unchanged) {
741
+ this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
742
+ return;
743
+ }
744
+ this._cached = value;
745
+ this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
746
+ }
747
+ _runFn() {
748
+ if (!this._fn) return;
749
+ if (this._terminal && !this._opts.resubscribable) return;
750
+ if (this._connecting) return;
751
+ try {
752
+ const n = this._deps.length;
753
+ const depValues = new Array(n);
754
+ for (let i = 0; i < n; i++) depValues[i] = this._deps[i].get();
755
+ const prev = this._lastDepValues;
756
+ if (n > 0 && prev != null && prev.length === n) {
757
+ let allSame = true;
758
+ for (let i = 0; i < n; i++) {
759
+ if (!Object.is(depValues[i], prev[i])) {
760
+ allSame = false;
761
+ break;
762
+ }
763
+ }
764
+ if (allSame) {
765
+ if (this._status === "dirty") {
766
+ this._downInternal([[RESOLVED]]);
767
+ }
768
+ return;
769
+ }
770
+ }
771
+ const prevCleanup = this._cleanup;
772
+ this._cleanup = void 0;
773
+ prevCleanup?.();
774
+ this._manualEmitUsed = false;
775
+ this._lastDepValues = depValues;
776
+ this._inspectorHook?.({ kind: "run", depValues });
777
+ const out = this._fn(depValues, this._actions);
778
+ if (isCleanupFn(out)) {
779
+ this._cleanup = out;
780
+ return;
781
+ }
782
+ if (this._manualEmitUsed) return;
783
+ if (out === void 0) return;
784
+ this._emitAutoValue(out);
785
+ } catch (err) {
786
+ this._downInternal([[ERROR, err]]);
787
+ }
788
+ }
789
+ _onDepDirty(index) {
790
+ const wasDirty = this._depDirtyMask.has(index);
791
+ this._depDirtyMask.set(index);
792
+ this._depSettledMask.clear(index);
793
+ if (!wasDirty) {
794
+ this._downInternal([[DIRTY]]);
795
+ }
796
+ }
797
+ _onDepSettled(index) {
798
+ if (!this._depDirtyMask.has(index)) {
799
+ this._onDepDirty(index);
800
+ }
801
+ this._depSettledMask.set(index);
802
+ if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
803
+ this._depDirtyMask.reset();
804
+ this._depSettledMask.reset();
805
+ this._runFn();
806
+ }
807
+ }
808
+ _maybeCompleteFromDeps() {
809
+ if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
810
+ this._downInternal([[COMPLETE]]);
811
+ }
812
+ }
813
+ _handleDepMessages(index, messages) {
814
+ for (const msg of messages) {
815
+ this._inspectorHook?.({ kind: "dep_message", depIndex: index, message: msg });
816
+ const t = msg[0];
817
+ if (this._onMessage) {
818
+ try {
819
+ if (this._onMessage(msg, index, this._actions)) continue;
820
+ } catch (err) {
821
+ this._downInternal([[ERROR, err]]);
822
+ return;
823
+ }
824
+ }
825
+ if (!this._fn) {
826
+ if (t === COMPLETE && this._deps.length > 1) {
827
+ this._depCompleteMask.set(index);
828
+ this._maybeCompleteFromDeps();
829
+ continue;
830
+ }
831
+ this._downInternal([msg]);
832
+ continue;
833
+ }
834
+ if (t === DIRTY) {
835
+ this._onDepDirty(index);
836
+ continue;
837
+ }
838
+ if (t === DATA || t === RESOLVED) {
839
+ this._onDepSettled(index);
840
+ continue;
841
+ }
842
+ if (t === COMPLETE) {
843
+ this._depCompleteMask.set(index);
844
+ this._depDirtyMask.clear(index);
845
+ this._depSettledMask.clear(index);
846
+ if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
847
+ this._depDirtyMask.reset();
848
+ this._depSettledMask.reset();
849
+ this._runFn();
850
+ } else if (!this._depDirtyMask.any() && this._status === "dirty") {
851
+ this._depSettledMask.reset();
852
+ this._runFn();
853
+ }
854
+ this._maybeCompleteFromDeps();
855
+ continue;
856
+ }
857
+ if (t === ERROR) {
858
+ this._downInternal([msg]);
859
+ continue;
860
+ }
861
+ if (t === INVALIDATE || t === TEARDOWN || t === PAUSE || t === RESUME) {
862
+ this._downInternal([msg]);
863
+ continue;
864
+ }
865
+ this._downInternal([msg]);
866
+ }
867
+ }
868
+ _connectUpstream() {
869
+ if (!this._hasDeps || this._connected) return;
870
+ this._connected = true;
871
+ this._depDirtyMask.reset();
872
+ this._depSettledMask.reset();
873
+ this._depCompleteMask.reset();
874
+ this._status = "settled";
875
+ const subHints = this._isSingleDep ? { singleDep: true } : void 0;
876
+ this._connecting = true;
877
+ try {
878
+ for (let i = 0; i < this._deps.length; i += 1) {
879
+ const dep = this._deps[i];
880
+ this._upstreamUnsubs.push(
881
+ dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
882
+ );
883
+ }
884
+ } finally {
885
+ this._connecting = false;
886
+ }
887
+ if (this._fn) {
888
+ this._runFn();
889
+ }
890
+ }
891
+ _stopProducer() {
892
+ if (!this._producerStarted) return;
893
+ this._producerStarted = false;
894
+ const producerCleanup = this._cleanup;
895
+ this._cleanup = void 0;
896
+ producerCleanup?.();
897
+ }
898
+ _startProducer() {
899
+ if (this._deps.length !== 0 || !this._fn || this._producerStarted) return;
900
+ this._producerStarted = true;
901
+ this._runFn();
902
+ }
903
+ _disconnectUpstream() {
904
+ if (!this._connected) return;
905
+ for (const unsub of this._upstreamUnsubs.splice(0)) {
906
+ unsub();
907
+ }
908
+ this._connected = false;
909
+ this._depDirtyMask.reset();
910
+ this._depSettledMask.reset();
911
+ this._depCompleteMask.reset();
912
+ this._status = "disconnected";
913
+ }
914
+ };
915
+ function node(depsOrFn, fnOrOpts, optsArg) {
916
+ const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
917
+ const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
918
+ let opts = {};
919
+ if (isNodeArray(depsOrFn)) {
920
+ opts = (isNodeOptions(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
921
+ } else if (isNodeOptions(depsOrFn)) {
922
+ opts = depsOrFn;
923
+ } else {
924
+ opts = (isNodeOptions(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
925
+ }
926
+ return new NodeImpl(deps, fn, opts);
927
+ }
928
+
929
+ // src/core/dynamic-node.ts
930
+ var DynamicNodeImpl = class {
931
+ _optsName;
932
+ _registryName;
933
+ _describeKind;
934
+ meta;
935
+ _fn;
936
+ _equals;
937
+ _resubscribable;
938
+ _resetOnTeardown;
939
+ _autoComplete;
940
+ _onMessage;
941
+ _onResubscribe;
942
+ /** @internal — read by {@link describeNode} for `accessHintForGuard`. */
943
+ _guard;
944
+ _lastMutation;
945
+ _inspectorHook;
946
+ // Sink tracking
947
+ _sinkCount = 0;
948
+ _singleDepSinkCount = 0;
949
+ _singleDepSinks = /* @__PURE__ */ new WeakSet();
950
+ // Actions object (for onMessage handler)
951
+ _actions;
952
+ _boundEmitToSinks;
953
+ // Mutable state
954
+ _cached;
955
+ _status = "disconnected";
956
+ _terminal = false;
957
+ _connected = false;
958
+ _rewiring = false;
959
+ // re-entrancy guard
960
+ // Dynamic deps tracking
961
+ _deps = [];
962
+ _depUnsubs = [];
963
+ _depIndexMap = /* @__PURE__ */ new Map();
964
+ // node → index in _deps
965
+ _dirtyBits = /* @__PURE__ */ new Set();
966
+ _settledBits = /* @__PURE__ */ new Set();
967
+ _completeBits = /* @__PURE__ */ new Set();
968
+ // Sinks
969
+ _sinks = null;
970
+ constructor(fn, opts) {
971
+ this._fn = fn;
972
+ this._optsName = opts.name;
973
+ this._describeKind = opts.describeKind;
974
+ this._equals = opts.equals ?? Object.is;
975
+ this._resubscribable = opts.resubscribable ?? false;
976
+ this._resetOnTeardown = opts.resetOnTeardown ?? false;
977
+ this._autoComplete = opts.completeWhenDepsComplete ?? true;
978
+ this._onMessage = opts.onMessage;
979
+ this._onResubscribe = opts.onResubscribe;
980
+ this._guard = opts.guard;
981
+ this._inspectorHook = void 0;
982
+ const meta = {};
983
+ for (const [k, v] of Object.entries(opts.meta ?? {})) {
984
+ meta[k] = node({
985
+ initial: v,
986
+ name: `${opts.name ?? "dynamicNode"}:meta:${k}`,
987
+ describeKind: "state",
988
+ ...opts.guard != null ? { guard: opts.guard } : {}
989
+ });
990
+ }
991
+ Object.freeze(meta);
992
+ this.meta = meta;
993
+ const self = this;
994
+ this._actions = {
995
+ down(messages) {
996
+ self._downInternal(messages);
997
+ },
998
+ emit(value) {
999
+ self._emitAutoValue(value);
1000
+ },
1001
+ up(messages) {
1002
+ for (const dep of self._deps) {
1003
+ dep.up?.(messages, { internal: true });
1004
+ }
1005
+ }
1006
+ };
1007
+ this._boundEmitToSinks = this._emitToSinks.bind(this);
1008
+ }
1009
+ get name() {
1010
+ return this._registryName ?? this._optsName;
1011
+ }
1012
+ /** @internal */
1013
+ _assignRegistryName(localName) {
1014
+ if (this._optsName !== void 0 || this._registryName !== void 0) return;
1015
+ this._registryName = localName;
1016
+ }
1017
+ /**
1018
+ * @internal Attach/remove inspector hook for graph-level observability.
1019
+ * Returns a disposer that restores the previous hook.
1020
+ */
1021
+ _setInspectorHook(hook) {
1022
+ const prev = this._inspectorHook;
1023
+ this._inspectorHook = hook;
1024
+ return () => {
1025
+ if (this._inspectorHook === hook) {
1026
+ this._inspectorHook = prev;
1027
+ }
1028
+ };
1029
+ }
1030
+ get status() {
1031
+ return this._status;
1032
+ }
1033
+ get lastMutation() {
1034
+ return this._lastMutation;
1035
+ }
1036
+ /** Versioning not yet supported on DynamicNodeImpl. */
1037
+ get v() {
1038
+ return void 0;
1039
+ }
1040
+ hasGuard() {
1041
+ return this._guard != null;
1042
+ }
1043
+ allowsObserve(actor) {
1044
+ if (this._guard == null) return true;
1045
+ return this._guard(normalizeActor(actor), "observe");
1046
+ }
1047
+ get() {
1048
+ return this._cached;
1049
+ }
1050
+ down(messages, options) {
1051
+ if (messages.length === 0) return;
1052
+ if (!options?.internal && this._guard != null) {
1053
+ const actor = normalizeActor(options?.actor);
1054
+ const delivery = options?.delivery ?? "write";
1055
+ const action = delivery === "signal" ? "signal" : "write";
1056
+ if (!this._guard(actor, action)) {
1057
+ throw new GuardDenied({ actor, action, nodeName: this.name });
1058
+ }
1059
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
1060
+ }
1061
+ this._downInternal(messages);
1062
+ }
1063
+ _downInternal(messages) {
1064
+ if (messages.length === 0) return;
1065
+ let sinkMessages = messages;
1066
+ if (this._terminal && !this._resubscribable) {
1067
+ const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
1068
+ if (pass.length === 0) return;
1069
+ sinkMessages = pass;
1070
+ }
1071
+ this._handleLocalLifecycle(sinkMessages);
1072
+ if (this._canSkipDirty()) {
1073
+ let hasPhase2 = false;
1074
+ for (let i = 0; i < sinkMessages.length; i++) {
1075
+ const t = sinkMessages[i][0];
1076
+ if (t === DATA || t === RESOLVED) {
1077
+ hasPhase2 = true;
1078
+ break;
1079
+ }
1080
+ }
1081
+ if (hasPhase2) {
1082
+ const filtered = [];
1083
+ for (let i = 0; i < sinkMessages.length; i++) {
1084
+ if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
1085
+ }
1086
+ if (filtered.length > 0) {
1087
+ emitWithBatch(this._boundEmitToSinks, filtered);
1088
+ }
1089
+ return;
1090
+ }
1091
+ }
1092
+ emitWithBatch(this._boundEmitToSinks, sinkMessages);
1093
+ }
1094
+ _canSkipDirty() {
1095
+ return this._sinkCount === 1 && this._singleDepSinkCount === 1;
1096
+ }
1097
+ subscribe(sink, hints) {
1098
+ if (hints?.actor != null && this._guard != null) {
1099
+ const actor = normalizeActor(hints.actor);
1100
+ if (!this._guard(actor, "observe")) {
1101
+ throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
1102
+ }
1103
+ }
1104
+ if (this._terminal && this._resubscribable) {
1105
+ this._terminal = false;
1106
+ this._status = "disconnected";
1107
+ this._onResubscribe?.();
1108
+ }
1109
+ this._sinkCount += 1;
1110
+ if (hints?.singleDep) {
1111
+ this._singleDepSinkCount += 1;
1112
+ this._singleDepSinks.add(sink);
1113
+ }
1114
+ if (this._sinks == null) {
1115
+ this._sinks = sink;
1116
+ } else if (typeof this._sinks === "function") {
1117
+ this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
1118
+ } else {
1119
+ this._sinks.add(sink);
1120
+ }
1121
+ if (!this._connected) {
1122
+ this._connect();
1123
+ }
1124
+ let removed = false;
1125
+ return () => {
1126
+ if (removed) return;
1127
+ removed = true;
1128
+ this._sinkCount -= 1;
1129
+ if (this._singleDepSinks.has(sink)) {
1130
+ this._singleDepSinkCount -= 1;
1131
+ this._singleDepSinks.delete(sink);
1132
+ }
1133
+ if (this._sinks == null) return;
1134
+ if (typeof this._sinks === "function") {
1135
+ if (this._sinks === sink) this._sinks = null;
1136
+ } else {
1137
+ this._sinks.delete(sink);
1138
+ if (this._sinks.size === 1) {
1139
+ const [only] = this._sinks;
1140
+ this._sinks = only;
1141
+ } else if (this._sinks.size === 0) {
1142
+ this._sinks = null;
1143
+ }
1144
+ }
1145
+ if (this._sinks == null) {
1146
+ this._disconnect();
1147
+ }
1148
+ };
1149
+ }
1150
+ up(messages, options) {
1151
+ if (this._deps.length === 0) return;
1152
+ if (!options?.internal && this._guard != null) {
1153
+ const actor = normalizeActor(options?.actor);
1154
+ if (!this._guard(actor, "write")) {
1155
+ throw new GuardDenied({ actor, action: "write", nodeName: this.name });
1156
+ }
1157
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
1158
+ }
1159
+ for (const dep of this._deps) {
1160
+ dep.up?.(messages, options);
1161
+ }
1162
+ }
1163
+ unsubscribe() {
1164
+ this._disconnect();
1165
+ }
1166
+ // --- Private methods ---
1167
+ _emitToSinks(messages) {
1168
+ if (this._sinks == null) return;
1169
+ if (typeof this._sinks === "function") {
1170
+ this._sinks(messages);
1171
+ return;
1172
+ }
1173
+ const snapshot = [...this._sinks];
1174
+ for (const sink of snapshot) {
1175
+ sink(messages);
1176
+ }
1177
+ }
1178
+ _handleLocalLifecycle(messages) {
1179
+ for (const m of messages) {
1180
+ const t = m[0];
1181
+ if (t === DATA) this._cached = m[1];
1182
+ if (t === INVALIDATE) {
1183
+ this._cached = void 0;
1184
+ }
1185
+ if (t === DATA || t === RESOLVED) {
1186
+ this._status = "settled";
1187
+ } else if (t === DIRTY) {
1188
+ this._status = "dirty";
1189
+ } else if (t === COMPLETE) {
1190
+ this._status = "completed";
1191
+ this._terminal = true;
1192
+ } else if (t === ERROR) {
1193
+ this._status = "errored";
1194
+ this._terminal = true;
1195
+ }
1196
+ if (t === TEARDOWN) {
1197
+ if (this._resetOnTeardown) this._cached = void 0;
1198
+ try {
1199
+ this._propagateToMeta(t);
1200
+ } finally {
1201
+ this._disconnect();
1202
+ }
1203
+ }
1204
+ if (t !== TEARDOWN && propagatesToMeta(t)) {
1205
+ this._propagateToMeta(t);
1206
+ }
1207
+ }
1208
+ }
1209
+ /** Propagate a signal to all companion meta nodes (best-effort). */
1210
+ _propagateToMeta(t) {
1211
+ for (const metaNode of Object.values(this.meta)) {
1212
+ try {
1213
+ metaNode.down([[t]], { internal: true });
1214
+ } catch {
1215
+ }
1216
+ }
1217
+ }
1218
+ _emitAutoValue(value) {
1219
+ const wasDirty = this._status === "dirty";
1220
+ const unchanged = this._equals(this._cached, value);
1221
+ if (unchanged) {
1222
+ this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
1223
+ return;
1224
+ }
1225
+ this._cached = value;
1226
+ this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
1227
+ }
1228
+ _connect() {
1229
+ if (this._connected) return;
1230
+ this._connected = true;
1231
+ this._status = "settled";
1232
+ this._dirtyBits.clear();
1233
+ this._settledBits.clear();
1234
+ this._completeBits.clear();
1235
+ this._runFn();
1236
+ }
1237
+ _disconnect() {
1238
+ if (!this._connected) return;
1239
+ for (const unsub of this._depUnsubs) unsub();
1240
+ this._depUnsubs = [];
1241
+ this._deps = [];
1242
+ this._depIndexMap.clear();
1243
+ this._dirtyBits.clear();
1244
+ this._settledBits.clear();
1245
+ this._completeBits.clear();
1246
+ this._connected = false;
1247
+ this._status = "disconnected";
1248
+ }
1249
+ _runFn() {
1250
+ if (this._terminal && !this._resubscribable) return;
1251
+ if (this._rewiring) return;
1252
+ const trackedDeps = [];
1253
+ const trackedSet = /* @__PURE__ */ new Set();
1254
+ const get = (dep) => {
1255
+ if (!trackedSet.has(dep)) {
1256
+ trackedSet.add(dep);
1257
+ trackedDeps.push(dep);
1258
+ }
1259
+ return dep.get();
1260
+ };
1261
+ try {
1262
+ const depValues = [];
1263
+ for (const dep of this._deps) {
1264
+ depValues.push(dep.get());
1265
+ }
1266
+ this._inspectorHook?.({ kind: "run", depValues });
1267
+ const result = this._fn(get);
1268
+ this._rewire(trackedDeps);
1269
+ if (result === void 0) return;
1270
+ this._emitAutoValue(result);
1271
+ } catch (err) {
1272
+ this._downInternal([[ERROR, err]]);
1273
+ }
1274
+ }
1275
+ _rewire(newDeps) {
1276
+ this._rewiring = true;
1277
+ try {
1278
+ const oldMap = this._depIndexMap;
1279
+ const newMap = /* @__PURE__ */ new Map();
1280
+ const newUnsubs = [];
1281
+ for (let i = 0; i < newDeps.length; i++) {
1282
+ const dep = newDeps[i];
1283
+ newMap.set(dep, i);
1284
+ const oldIdx = oldMap.get(dep);
1285
+ if (oldIdx !== void 0) {
1286
+ newUnsubs.push(this._depUnsubs[oldIdx]);
1287
+ this._depUnsubs[oldIdx] = () => {
1288
+ };
1289
+ } else {
1290
+ const idx = i;
1291
+ const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
1292
+ newUnsubs.push(unsub);
1293
+ }
1294
+ }
1295
+ for (const [dep, oldIdx] of oldMap) {
1296
+ if (!newMap.has(dep)) {
1297
+ this._depUnsubs[oldIdx]();
1298
+ }
1299
+ }
1300
+ this._deps = newDeps;
1301
+ this._depUnsubs = newUnsubs;
1302
+ this._depIndexMap = newMap;
1303
+ this._dirtyBits.clear();
1304
+ this._settledBits.clear();
1305
+ const newCompleteBits = /* @__PURE__ */ new Set();
1306
+ for (const oldIdx of this._completeBits) {
1307
+ const dep = [...oldMap.entries()].find(([, idx]) => idx === oldIdx)?.[0];
1308
+ if (dep && newMap.has(dep)) {
1309
+ newCompleteBits.add(newMap.get(dep));
1310
+ }
1311
+ }
1312
+ this._completeBits = newCompleteBits;
1313
+ } finally {
1314
+ this._rewiring = false;
1315
+ }
1316
+ }
1317
+ _handleDepMessages(index, messages) {
1318
+ if (this._rewiring) return;
1319
+ for (const msg of messages) {
1320
+ this._inspectorHook?.({ kind: "dep_message", depIndex: index, message: msg });
1321
+ const t = msg[0];
1322
+ if (this._onMessage) {
1323
+ try {
1324
+ if (this._onMessage(msg, index, this._actions)) continue;
1325
+ } catch (err) {
1326
+ this._downInternal([[ERROR, err]]);
1327
+ return;
1328
+ }
1329
+ }
1330
+ if (t === DIRTY) {
1331
+ this._dirtyBits.add(index);
1332
+ this._settledBits.delete(index);
1333
+ if (this._dirtyBits.size === 1) {
1334
+ emitWithBatch(this._boundEmitToSinks, [[DIRTY]]);
1335
+ }
1336
+ continue;
1337
+ }
1338
+ if (t === DATA || t === RESOLVED) {
1339
+ if (!this._dirtyBits.has(index)) {
1340
+ this._dirtyBits.add(index);
1341
+ emitWithBatch(this._boundEmitToSinks, [[DIRTY]]);
1342
+ }
1343
+ this._settledBits.add(index);
1344
+ if (this._allDirtySettled()) {
1345
+ this._dirtyBits.clear();
1346
+ this._settledBits.clear();
1347
+ this._runFn();
1348
+ }
1349
+ continue;
1350
+ }
1351
+ if (t === COMPLETE) {
1352
+ this._completeBits.add(index);
1353
+ this._dirtyBits.delete(index);
1354
+ this._settledBits.delete(index);
1355
+ if (this._allDirtySettled()) {
1356
+ this._dirtyBits.clear();
1357
+ this._settledBits.clear();
1358
+ this._runFn();
1359
+ }
1360
+ if (this._autoComplete && this._completeBits.size >= this._deps.length && this._deps.length > 0) {
1361
+ this._downInternal([[COMPLETE]]);
1362
+ }
1363
+ continue;
1364
+ }
1365
+ if (t === ERROR) {
1366
+ this._downInternal([msg]);
1367
+ continue;
1368
+ }
1369
+ if (t === INVALIDATE || t === TEARDOWN || t === PAUSE || t === RESUME) {
1370
+ this._downInternal([msg]);
1371
+ continue;
1372
+ }
1373
+ this._downInternal([msg]);
1374
+ }
1375
+ }
1376
+ _allDirtySettled() {
1377
+ if (this._dirtyBits.size === 0) return false;
1378
+ for (const idx of this._dirtyBits) {
1379
+ if (!this._settledBits.has(idx)) return false;
1380
+ }
1381
+ return true;
1382
+ }
1383
+ };
1384
+
1385
+ // src/core/meta.ts
1386
+ function inferDescribeType(n) {
1387
+ if (n._describeKind != null) return n._describeKind;
1388
+ if (!n._hasDeps) return n._fn != null ? "producer" : "state";
1389
+ if (n._fn == null) return "derived";
1390
+ if (n._manualEmitUsed) return "operator";
1391
+ return "derived";
1392
+ }
1393
+ function metaSnapshot(node2) {
1394
+ const out = {};
1395
+ for (const [key, child] of Object.entries(node2.meta)) {
1396
+ try {
1397
+ out[key] = child.get();
1398
+ } catch {
1399
+ }
1400
+ }
1401
+ return out;
1402
+ }
1403
+ function describeNode(node2) {
1404
+ const meta = { ...metaSnapshot(node2) };
1405
+ const guard = node2 instanceof NodeImpl && node2._guard || node2 instanceof DynamicNodeImpl && node2._guard || void 0;
1406
+ if (guard != null && meta.access === void 0) {
1407
+ meta.access = accessHintForGuard(guard);
1408
+ }
1409
+ let type = "state";
1410
+ let deps = [];
1411
+ if (node2 instanceof NodeImpl) {
1412
+ type = inferDescribeType(node2);
1413
+ deps = node2._deps.map((d) => d.name ?? "");
1414
+ } else if (node2 instanceof DynamicNodeImpl) {
1415
+ type = node2._describeKind ?? "derived";
1416
+ deps = [];
1417
+ }
1418
+ const out = {
1419
+ type,
1420
+ status: node2.status,
1421
+ deps,
1422
+ meta
1423
+ };
1424
+ if (node2.name != null) {
1425
+ out.name = node2.name;
1426
+ }
1427
+ try {
1428
+ out.value = node2.get();
1429
+ } catch {
1430
+ }
1431
+ if (node2.v != null) {
1432
+ const vInfo = { id: node2.v.id, version: node2.v.version };
1433
+ if ("cid" in node2.v) {
1434
+ vInfo.cid = node2.v.cid;
1435
+ vInfo.prev = node2.v.prev;
1436
+ }
1437
+ out.v = vInfo;
1438
+ }
1439
+ return out;
1440
+ }
1441
+
1442
+ // src/core/sugar.ts
1443
+ function state(initial, opts) {
1444
+ return node([], { ...opts, initial });
1445
+ }
1446
+
1447
+ // src/graph/graph.ts
1448
+ var PATH_SEP = "::";
1449
+ var GRAPH_META_SEGMENT = "__meta__";
1450
+ var SNAPSHOT_VERSION = 1;
1451
+ function parseSnapshotEnvelope(data) {
1452
+ if (data.version !== SNAPSHOT_VERSION) {
1453
+ throw new Error(
1454
+ `unsupported snapshot version ${String(data.version)} (expected ${SNAPSHOT_VERSION})`
1455
+ );
1456
+ }
1457
+ for (const key of ["name", "nodes", "edges", "subgraphs"]) {
1458
+ if (!(key in data)) {
1459
+ throw new Error(`snapshot missing required key "${key}"`);
1460
+ }
1461
+ }
1462
+ if (typeof data.name !== "string") {
1463
+ throw new TypeError(`snapshot 'name' must be a string`);
1464
+ }
1465
+ if (typeof data.nodes !== "object" || data.nodes === null || Array.isArray(data.nodes)) {
1466
+ throw new TypeError(`snapshot 'nodes' must be an object`);
1467
+ }
1468
+ if (!Array.isArray(data.edges)) {
1469
+ throw new TypeError(`snapshot 'edges' must be an array`);
1470
+ }
1471
+ if (!Array.isArray(data.subgraphs)) {
1472
+ throw new TypeError(`snapshot 'subgraphs' must be an array`);
1473
+ }
1474
+ }
1475
+ function sortJsonValue(value) {
1476
+ if (value === null || typeof value !== "object") {
1477
+ return value;
1478
+ }
1479
+ if (Array.isArray(value)) {
1480
+ return value.map(sortJsonValue);
1481
+ }
1482
+ const obj = value;
1483
+ const keys = Object.keys(obj).sort();
1484
+ const out = {};
1485
+ for (const k of keys) {
1486
+ out[k] = sortJsonValue(obj[k]);
1487
+ }
1488
+ return out;
1489
+ }
1490
+ function stableJsonStringify(value) {
1491
+ return `${JSON.stringify(sortJsonValue(value))}
1492
+ `;
1493
+ }
1494
+ function escapeMermaidLabel(value) {
1495
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
1496
+ }
1497
+ function escapeD2Label(value) {
1498
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
1499
+ }
1500
+ function d2DirectionFromGraphDirection(direction) {
1501
+ if (direction === "TD") return "down";
1502
+ if (direction === "BT") return "up";
1503
+ if (direction === "RL") return "left";
1504
+ return "right";
1505
+ }
1506
+ function collectDiagramArrows(described) {
1507
+ const seen = /* @__PURE__ */ new Set();
1508
+ const arrows = [];
1509
+ function add(from, to) {
1510
+ const key = `${from}\0${to}`;
1511
+ if (seen.has(key)) return;
1512
+ seen.add(key);
1513
+ arrows.push([from, to]);
1514
+ }
1515
+ for (const [path, info] of Object.entries(described.nodes)) {
1516
+ const deps = info.deps;
1517
+ if (deps) {
1518
+ for (const dep of deps) add(dep, path);
1519
+ }
1520
+ }
1521
+ for (const edge of described.edges) add(edge.from, edge.to);
1522
+ return arrows;
1523
+ }
1524
+ function normalizeDiagramDirection(direction) {
1525
+ if (direction === void 0) return "LR";
1526
+ if (direction === "TD" || direction === "LR" || direction === "BT" || direction === "RL") {
1527
+ return direction;
1528
+ }
1529
+ throw new Error(
1530
+ `invalid diagram direction ${String(direction)}; expected one of: TD, LR, BT, RL`
1531
+ );
1532
+ }
1533
+ function escapeRegexLiteral(value) {
1534
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1535
+ }
1536
+ function globToRegex(pattern) {
1537
+ let re = "^";
1538
+ for (let i = 0; i < pattern.length; i += 1) {
1539
+ const ch = pattern[i];
1540
+ if (ch === "*") {
1541
+ re += ".*";
1542
+ continue;
1543
+ }
1544
+ if (ch === "?") {
1545
+ re += ".";
1546
+ continue;
1547
+ }
1548
+ if (ch === "[") {
1549
+ const end = pattern.indexOf("]", i + 1);
1550
+ if (end <= i + 1) {
1551
+ re += "\\[";
1552
+ continue;
1553
+ }
1554
+ let cls = pattern.slice(i + 1, end);
1555
+ if (cls.startsWith("!")) cls = `^${cls.slice(1)}`;
1556
+ cls = cls.replace(/\\/g, "\\\\");
1557
+ re += `[${cls}]`;
1558
+ i = end;
1559
+ continue;
1560
+ }
1561
+ re += escapeRegexLiteral(ch);
1562
+ }
1563
+ re += "$";
1564
+ return new RegExp(re);
1565
+ }
1566
+ var RingBuffer = class {
1567
+ constructor(capacity) {
1568
+ this.capacity = capacity;
1569
+ this.buf = new Array(capacity);
1570
+ }
1571
+ buf;
1572
+ head = 0;
1573
+ _size = 0;
1574
+ get size() {
1575
+ return this._size;
1576
+ }
1577
+ push(item) {
1578
+ const idx = (this.head + this._size) % this.capacity;
1579
+ this.buf[idx] = item;
1580
+ if (this._size < this.capacity) this._size++;
1581
+ else this.head = (this.head + 1) % this.capacity;
1582
+ }
1583
+ toArray() {
1584
+ const result = [];
1585
+ for (let i = 0; i < this._size; i++) result.push(this.buf[(this.head + i) % this.capacity]);
1586
+ return result;
1587
+ }
1588
+ };
1589
+ var SPY_ANSI_THEME = {
1590
+ data: "\x1B[32m",
1591
+ dirty: "\x1B[33m",
1592
+ resolved: "\x1B[36m",
1593
+ complete: "\x1B[34m",
1594
+ error: "\x1B[31m",
1595
+ derived: "\x1B[35m",
1596
+ path: "\x1B[90m",
1597
+ reset: "\x1B[0m"
1598
+ };
1599
+ var SPY_NO_COLOR_THEME = {
1600
+ data: "",
1601
+ dirty: "",
1602
+ resolved: "",
1603
+ complete: "",
1604
+ error: "",
1605
+ derived: "",
1606
+ path: "",
1607
+ reset: ""
1608
+ };
1609
+ function describeData(value) {
1610
+ if (typeof value === "string") return JSON.stringify(value);
1611
+ if (typeof value === "number" || typeof value === "boolean" || value == null)
1612
+ return String(value);
1613
+ try {
1614
+ return JSON.stringify(value);
1615
+ } catch {
1616
+ return "[unserializable]";
1617
+ }
1618
+ }
1619
+ function resolveSpyTheme(theme) {
1620
+ if (theme === "none") return SPY_NO_COLOR_THEME;
1621
+ if (theme === "ansi" || theme == null) return SPY_ANSI_THEME;
1622
+ return {
1623
+ data: theme.data ?? "",
1624
+ dirty: theme.dirty ?? "",
1625
+ resolved: theme.resolved ?? "",
1626
+ complete: theme.complete ?? "",
1627
+ error: theme.error ?? "",
1628
+ derived: theme.derived ?? "",
1629
+ path: theme.path ?? "",
1630
+ reset: theme.reset ?? ""
1631
+ };
1632
+ }
1633
+ function assertLocalName(name, graphName, label) {
1634
+ if (name === "") {
1635
+ throw new Error(`Graph "${graphName}": ${label} name must be non-empty`);
1636
+ }
1637
+ }
1638
+ function assertNoPathSep(name, graphName, label) {
1639
+ if (name.includes(PATH_SEP)) {
1640
+ throw new Error(
1641
+ `Graph "${graphName}": ${label} "${name}" must not contain '${PATH_SEP}' (path separator)`
1642
+ );
1643
+ }
1644
+ }
1645
+ function assertNotReservedMetaSegment(name, graphName, label) {
1646
+ if (name === GRAPH_META_SEGMENT) {
1647
+ throw new Error(
1648
+ `Graph "${graphName}": ${label} name "${GRAPH_META_SEGMENT}" is reserved for meta companion paths`
1649
+ );
1650
+ }
1651
+ }
1652
+ function assertConnectPathNotMeta(path, graphName) {
1653
+ if (path.split(PATH_SEP).includes(GRAPH_META_SEGMENT)) {
1654
+ throw new Error(
1655
+ `Graph "${graphName}": connect/disconnect endpoints must be registered graph nodes, not meta paths (got "${path}")`
1656
+ );
1657
+ }
1658
+ }
1659
+ function splitPath(path, graphName) {
1660
+ if (path === "") {
1661
+ throw new Error(`Graph "${graphName}": resolve path must be non-empty`);
1662
+ }
1663
+ const segments = path.split(PATH_SEP);
1664
+ for (const s of segments) {
1665
+ if (s === "") {
1666
+ throw new Error(`Graph "${graphName}": resolve path has empty segment`);
1667
+ }
1668
+ }
1669
+ return segments;
1670
+ }
1671
+ function edgeKey(from, to) {
1672
+ return `${from} ${to}`;
1673
+ }
1674
+ function parseEdgeKey(key) {
1675
+ const i = key.indexOf(" ");
1676
+ return [key.slice(0, i), key.slice(i + 1)];
1677
+ }
1678
+ var META_FILTERED_TYPES = /* @__PURE__ */ new Set([TEARDOWN, INVALIDATE, COMPLETE, ERROR]);
1679
+ function filterMetaMessages(messages) {
1680
+ const kept = messages.filter((m) => !META_FILTERED_TYPES.has(m[0]));
1681
+ return kept;
1682
+ }
1683
+ function teardownMountedGraph(root) {
1684
+ for (const child of root._mounts.values()) {
1685
+ teardownMountedGraph(child);
1686
+ }
1687
+ for (const n of root._nodes.values()) {
1688
+ n.down([[TEARDOWN]], { internal: true });
1689
+ }
1690
+ }
1691
+ var Graph = class _Graph {
1692
+ static _factories = [];
1693
+ name;
1694
+ opts;
1695
+ /** @internal — exposed for {@link teardownMountedGraph} and cross-graph helpers. */
1696
+ _nodes = /* @__PURE__ */ new Map();
1697
+ _edges = /* @__PURE__ */ new Set();
1698
+ /** @internal — exposed for {@link teardownMountedGraph}. */
1699
+ _mounts = /* @__PURE__ */ new Map();
1700
+ _autoCheckpointDisposers = /* @__PURE__ */ new Set();
1701
+ _defaultVersioningLevel;
1702
+ static registerFactory(pattern, factory) {
1703
+ if (!pattern) {
1704
+ throw new Error("Graph.registerFactory requires a non-empty pattern");
1705
+ }
1706
+ _Graph.unregisterFactory(pattern);
1707
+ _Graph._factories.push({ pattern, re: globToRegex(pattern), factory });
1708
+ }
1709
+ static unregisterFactory(pattern) {
1710
+ const i = _Graph._factories.findIndex((entry) => entry.pattern === pattern);
1711
+ if (i >= 0) _Graph._factories.splice(i, 1);
1712
+ }
1713
+ /**
1714
+ * @param name - Non-empty graph id (must not contain `::`).
1715
+ * @param opts - Reserved for future hooks; currently unused.
1716
+ */
1717
+ constructor(name, opts) {
1718
+ if (name === "") {
1719
+ throw new Error("Graph name must be non-empty");
1720
+ }
1721
+ if (name.includes(PATH_SEP)) {
1722
+ throw new Error(`Graph name must not contain '${PATH_SEP}' (got "${name}")`);
1723
+ }
1724
+ this.name = name;
1725
+ this.opts = opts ?? {};
1726
+ }
1727
+ static _factoryForPath(path) {
1728
+ for (let i = _Graph._factories.length - 1; i >= 0; i -= 1) {
1729
+ const entry = _Graph._factories[i];
1730
+ if (entry.re.test(path)) return entry.factory;
1731
+ }
1732
+ return void 0;
1733
+ }
1734
+ static _ownerForPath(root, path) {
1735
+ const segments = path.split(PATH_SEP);
1736
+ const local = segments.pop();
1737
+ if (local == null || local.length === 0) {
1738
+ throw new Error(`invalid snapshot path "${path}"`);
1739
+ }
1740
+ let owner = root;
1741
+ for (const seg of segments) {
1742
+ const next = owner._mounts.get(seg);
1743
+ if (!next) throw new Error(`unknown mount "${seg}" in path "${path}"`);
1744
+ owner = next;
1745
+ }
1746
+ return [owner, local];
1747
+ }
1748
+ /**
1749
+ * Graphs reachable from this instance via nested {@link Graph.mount} (includes `this`).
1750
+ */
1751
+ _graphsReachableViaMounts(seen = /* @__PURE__ */ new Set()) {
1752
+ if (seen.has(this)) return seen;
1753
+ seen.add(this);
1754
+ for (const child of this._mounts.values()) {
1755
+ child._graphsReachableViaMounts(seen);
1756
+ }
1757
+ return seen;
1758
+ }
1759
+ /**
1760
+ * Resolve an endpoint: returns `[owningGraph, localName, node]`.
1761
+ * Accepts both local names and `::` qualified paths.
1762
+ */
1763
+ _resolveEndpoint(path) {
1764
+ if (!path.includes(PATH_SEP)) {
1765
+ const n = this._nodes.get(path);
1766
+ if (!n) {
1767
+ throw new Error(`Graph "${this.name}": unknown node "${path}"`);
1768
+ }
1769
+ return [this, path, n];
1770
+ }
1771
+ const segments = splitPath(path, this.name);
1772
+ return this._resolveEndpointFromSegments(segments, path);
1773
+ }
1774
+ _resolveEndpointFromSegments(segments, fullPath) {
1775
+ const head = segments[0];
1776
+ const rest = segments.slice(1);
1777
+ if (rest.length === 0) {
1778
+ const n = this._nodes.get(head);
1779
+ if (n) return [this, head, n];
1780
+ throw new Error(`Graph "${this.name}": unknown node "${head}" (from path "${fullPath}")`);
1781
+ }
1782
+ const localN = this._nodes.get(head);
1783
+ if (localN && rest.length > 0 && rest[0] === GRAPH_META_SEGMENT) {
1784
+ return this._resolveMetaEndpointKeys(localN, head, rest, fullPath);
1785
+ }
1786
+ const child = this._mounts.get(head);
1787
+ if (!child) {
1788
+ if (this._nodes.has(head)) {
1789
+ throw new Error(
1790
+ `Graph "${this.name}": "${head}" is a node; trailing path "${rest.join(PATH_SEP)}" is invalid`
1791
+ );
1792
+ }
1793
+ throw new Error(`Graph "${this.name}": unknown mount or node "${head}"`);
1794
+ }
1795
+ return child._resolveEndpointFromSegments(rest, fullPath);
1796
+ }
1797
+ // ——————————————————————————————————————————————————————————————
1798
+ // Node registry
1799
+ // ——————————————————————————————————————————————————————————————
1800
+ /**
1801
+ * Registers a node under a local name. Fails if the name is already used,
1802
+ * reserved by a mount, or the same node instance is already registered.
1803
+ *
1804
+ * @param name - Local key (no `::`).
1805
+ * @param node - Node instance to own.
1806
+ */
1807
+ add(name, node2) {
1808
+ assertLocalName(name, this.name, "add");
1809
+ assertNoPathSep(name, this.name, "add");
1810
+ assertNotReservedMetaSegment(name, this.name, "node");
1811
+ if (this._mounts.has(name)) {
1812
+ throw new Error(`Graph "${this.name}": name "${name}" is already a mount point`);
1813
+ }
1814
+ if (this._nodes.has(name)) {
1815
+ throw new Error(`Graph "${this.name}": node "${name}" already exists`);
1816
+ }
1817
+ for (const [existingName, existing] of this._nodes) {
1818
+ if (existing === node2) {
1819
+ throw new Error(
1820
+ `Graph "${this.name}": node instance already registered as "${existingName}"`
1821
+ );
1822
+ }
1823
+ }
1824
+ this._nodes.set(name, node2);
1825
+ if (node2 instanceof NodeImpl) {
1826
+ node2._assignRegistryName(name);
1827
+ if (this._defaultVersioningLevel != null) {
1828
+ node2._applyVersioning(this._defaultVersioningLevel);
1829
+ }
1830
+ }
1831
+ }
1832
+ /**
1833
+ * Set a default versioning level for all nodes added to this graph (roadmap §6.0).
1834
+ *
1835
+ * Nodes already registered are retroactively upgraded. Nodes added later via
1836
+ * {@link add} will inherit this level unless they already have versioning.
1837
+ *
1838
+ * **Scope:** Does not propagate to mounted subgraphs. Call `setVersioning`
1839
+ * on each child graph separately if needed.
1840
+ *
1841
+ * @param level - `0` for V0, `1` for V1, or `undefined` to clear.
1842
+ */
1843
+ setVersioning(level) {
1844
+ this._defaultVersioningLevel = level;
1845
+ if (level == null) return;
1846
+ for (const n of this._nodes.values()) {
1847
+ if (n instanceof NodeImpl) {
1848
+ n._applyVersioning(level);
1849
+ }
1850
+ }
1851
+ }
1852
+ /**
1853
+ * Unregisters a node or unmounts a subgraph, drops incident edges, and sends
1854
+ * `[[TEARDOWN]]` to the removed node or recursively through the mounted subtree (§3.2).
1855
+ *
1856
+ * @param name - Local mount or node name.
1857
+ */
1858
+ remove(name) {
1859
+ assertLocalName(name, this.name, "remove");
1860
+ assertNoPathSep(name, this.name, "remove");
1861
+ const child = this._mounts.get(name);
1862
+ if (child) {
1863
+ this._mounts.delete(name);
1864
+ const prefix = `${name}${PATH_SEP}`;
1865
+ for (const key of [...this._edges]) {
1866
+ const [from, to] = parseEdgeKey(key);
1867
+ if (from === name || to === name || from.startsWith(prefix) || to.startsWith(prefix)) {
1868
+ this._edges.delete(key);
1869
+ }
1870
+ }
1871
+ teardownMountedGraph(child);
1872
+ return;
1873
+ }
1874
+ const node2 = this._nodes.get(name);
1875
+ if (!node2) {
1876
+ throw new Error(`Graph "${this.name}": unknown node or mount "${name}"`);
1877
+ }
1878
+ this._nodes.delete(name);
1879
+ for (const key of [...this._edges]) {
1880
+ const [from, to] = parseEdgeKey(key);
1881
+ if (from === name || to === name) this._edges.delete(key);
1882
+ }
1883
+ node2.down([[TEARDOWN]], { internal: true });
1884
+ }
1885
+ /**
1886
+ * Returns a node by local name or `::` qualified path.
1887
+ * Local names are looked up directly; paths with `::` delegate to {@link resolve}.
1888
+ *
1889
+ * @param name - Local name or qualified path.
1890
+ */
1891
+ node(name) {
1892
+ if (name === "") {
1893
+ throw new Error(`Graph "${this.name}": node name must be non-empty`);
1894
+ }
1895
+ if (name.includes(PATH_SEP)) {
1896
+ return this.resolve(name);
1897
+ }
1898
+ const n = this._nodes.get(name);
1899
+ if (!n) {
1900
+ throw new Error(`Graph "${this.name}": unknown node "${name}"`);
1901
+ }
1902
+ return n;
1903
+ }
1904
+ /**
1905
+ * Reads `graph.node(name).get()` — accepts `::` qualified paths (§3.2).
1906
+ *
1907
+ * @param name - Local name or qualified path.
1908
+ * @returns Cached value or `undefined`.
1909
+ */
1910
+ get(name) {
1911
+ return this.node(name).get();
1912
+ }
1913
+ /**
1914
+ * Shorthand for `graph.node(name).down([[DATA, value]], { actor })` — accepts `::` qualified paths (§3.2).
1915
+ *
1916
+ * @param name - Local name or qualified path.
1917
+ * @param value - Next `DATA` payload.
1918
+ * @param options - Optional `actor` and `internal` guard bypass.
1919
+ */
1920
+ set(name, value, options) {
1921
+ const internal = options?.internal === true;
1922
+ this.node(name).down([[DATA, value]], {
1923
+ actor: options?.actor,
1924
+ internal,
1925
+ delivery: "write"
1926
+ });
1927
+ }
1928
+ // ——————————————————————————————————————————————————————————————
1929
+ // Edges
1930
+ // ——————————————————————————————————————————————————————————————
1931
+ /**
1932
+ * Record a wire from `fromPath` → `toPath` (§3.3). Accepts local names or
1933
+ * `::` qualified paths. The target must be a {@link NodeImpl} whose `_deps`
1934
+ * includes the source node (same reference). Idempotent.
1935
+ *
1936
+ * Same-owner edges are stored on the owning child graph; cross-subgraph edges
1937
+ * are stored on this (parent) graph's registry.
1938
+ *
1939
+ * @param fromPath - Source endpoint (local or qualified).
1940
+ * @param toPath - Target endpoint whose deps already include the source node.
1941
+ */
1942
+ connect(fromPath, toPath) {
1943
+ if (!fromPath || !toPath) {
1944
+ throw new Error(`Graph "${this.name}": connect paths must be non-empty`);
1945
+ }
1946
+ assertConnectPathNotMeta(fromPath, this.name);
1947
+ assertConnectPathNotMeta(toPath, this.name);
1948
+ const [fromGraph, fromLocal, fromNode] = this._resolveEndpoint(fromPath);
1949
+ const [toGraph, toLocal, toNode] = this._resolveEndpoint(toPath);
1950
+ if (fromNode === toNode) {
1951
+ throw new Error(`Graph "${this.name}": cannot connect a node to itself`);
1952
+ }
1953
+ if (!(toNode instanceof NodeImpl)) {
1954
+ throw new Error(
1955
+ `Graph "${this.name}": connect(${fromPath}, ${toPath}) requires the target to be a graphrefly NodeImpl so deps can be validated`
1956
+ );
1957
+ }
1958
+ if (!toNode._deps.includes(fromNode)) {
1959
+ throw new Error(
1960
+ `Graph "${this.name}": connect(${fromPath}, ${toPath}) \u2014 target must include source in its constructor deps (same node reference)`
1961
+ );
1962
+ }
1963
+ if (fromGraph === toGraph) {
1964
+ const key = edgeKey(fromLocal, toLocal);
1965
+ fromGraph._edges.add(key);
1966
+ } else {
1967
+ const key = edgeKey(fromPath, toPath);
1968
+ this._edges.add(key);
1969
+ }
1970
+ }
1971
+ /**
1972
+ * Remove a registered edge (§3.3). Accepts local names or `::` qualified paths.
1973
+ *
1974
+ * **Registry-only (§C resolved):** This drops the edge from the graph's edge
1975
+ * registry only. It does **not** mutate the target node's constructor-time
1976
+ * dependency list, bitmasks, or upstream subscriptions. Message flow follows
1977
+ * constructor-time deps, not the edge registry. For runtime dep rewiring, use
1978
+ * {@link dynamicNode}.
1979
+ *
1980
+ * @param fromPath - Registered edge tail.
1981
+ * @param toPath - Registered edge head.
1982
+ */
1983
+ disconnect(fromPath, toPath) {
1984
+ if (!fromPath || !toPath) {
1985
+ throw new Error(`Graph "${this.name}": disconnect paths must be non-empty`);
1986
+ }
1987
+ assertConnectPathNotMeta(fromPath, this.name);
1988
+ assertConnectPathNotMeta(toPath, this.name);
1989
+ const [fromGraph, fromLocal] = this._resolveEndpoint(fromPath);
1990
+ const [toGraph, toLocal] = this._resolveEndpoint(toPath);
1991
+ if (fromGraph === toGraph) {
1992
+ const key = edgeKey(fromLocal, toLocal);
1993
+ if (!fromGraph._edges.delete(key)) {
1994
+ throw new Error(`Graph "${this.name}": no registered edge ${fromPath} \u2192 ${toPath}`);
1995
+ }
1996
+ } else {
1997
+ const key = edgeKey(fromPath, toPath);
1998
+ if (!this._edges.delete(key)) {
1999
+ throw new Error(`Graph "${this.name}": no registered edge ${fromPath} \u2192 ${toPath}`);
2000
+ }
2001
+ }
2002
+ }
2003
+ /**
2004
+ * Returns registered `[from, to]` edge pairs (read-only snapshot).
2005
+ *
2006
+ * @returns Edge pairs recorded on this graph instance’s local `_edges` set.
2007
+ */
2008
+ edges() {
2009
+ const result = [];
2010
+ for (const key of this._edges) {
2011
+ result.push(parseEdgeKey(key));
2012
+ }
2013
+ return result;
2014
+ }
2015
+ // ——————————————————————————————————————————————————————————————
2016
+ // Composition
2017
+ // ——————————————————————————————————————————————————————————————
2018
+ /**
2019
+ * Embed a child graph at a local mount name (§3.4). Child nodes are reachable via
2020
+ * {@link Graph.resolve} using `::` delimited paths (§3.5). Lifecycle
2021
+ * {@link Graph.signal} visits mounted subgraphs recursively.
2022
+ *
2023
+ * Rejects: same name as existing node or mount, self-mount, mount cycles,
2024
+ * and the same child graph instance mounted twice on one parent.
2025
+ *
2026
+ * @param name - Local mount point.
2027
+ * @param child - Nested `Graph` instance.
2028
+ */
2029
+ mount(name, child) {
2030
+ assertLocalName(name, this.name, "mount");
2031
+ assertNoPathSep(name, this.name, "mount");
2032
+ assertNotReservedMetaSegment(name, this.name, "mount");
2033
+ if (this._nodes.has(name)) {
2034
+ throw new Error(
2035
+ `Graph "${this.name}": cannot mount at "${name}" \u2014 node with that name exists`
2036
+ );
2037
+ }
2038
+ if (this._mounts.has(name)) {
2039
+ throw new Error(`Graph "${this.name}": mount "${name}" already exists`);
2040
+ }
2041
+ if (child === this) {
2042
+ throw new Error(`Graph "${this.name}": cannot mount a graph into itself`);
2043
+ }
2044
+ for (const existing of this._mounts.values()) {
2045
+ if (existing === child) {
2046
+ throw new Error(`Graph "${this.name}": this child graph is already mounted on this graph`);
2047
+ }
2048
+ }
2049
+ if (child._graphsReachableViaMounts().has(this)) {
2050
+ throw new Error(`Graph "${this.name}": mount("${name}", \u2026) would create a mount cycle`);
2051
+ }
2052
+ this._mounts.set(name, child);
2053
+ }
2054
+ /**
2055
+ * Look up a node by qualified path (§3.5). Segments are separated by `::`.
2056
+ *
2057
+ * If the first segment equals this graph's {@link Graph.name}, it is stripped
2058
+ * (so `root.resolve("app::a")` works when `root.name === "app"`).
2059
+ *
2060
+ * @param path - Qualified `::` path or local name.
2061
+ * @returns The resolved `Node`.
2062
+ */
2063
+ resolve(path) {
2064
+ let segments = splitPath(path, this.name);
2065
+ if (segments[0] === this.name) {
2066
+ segments = segments.slice(1);
2067
+ if (segments.length === 0) {
2068
+ throw new Error(`Graph "${this.name}": resolve path ends at graph name only`);
2069
+ }
2070
+ }
2071
+ return this._resolveFromSegments(segments);
2072
+ }
2073
+ _resolveFromSegments(segments) {
2074
+ const head = segments[0];
2075
+ const rest = segments.slice(1);
2076
+ if (rest.length === 0) {
2077
+ const n = this._nodes.get(head);
2078
+ if (n) return n;
2079
+ if (this._mounts.has(head)) {
2080
+ throw new Error(
2081
+ `Graph "${this.name}": path ends at subgraph "${head}" \u2014 not a node (GRAPHREFLY-SPEC \xA73.5)`
2082
+ );
2083
+ }
2084
+ throw new Error(`Graph "${this.name}": unknown name "${head}"`);
2085
+ }
2086
+ const localN = this._nodes.get(head);
2087
+ if (localN && rest.length > 0 && rest[0] === GRAPH_META_SEGMENT) {
2088
+ return this._resolveMetaChainFromNode(localN, rest, segments.join(PATH_SEP));
2089
+ }
2090
+ const child = this._mounts.get(head);
2091
+ if (!child) {
2092
+ if (this._nodes.has(head)) {
2093
+ throw new Error(
2094
+ `Graph "${this.name}": "${head}" is a node; trailing path "${rest.join(PATH_SEP)}" is invalid`
2095
+ );
2096
+ }
2097
+ throw new Error(`Graph "${this.name}": unknown mount or node "${head}"`);
2098
+ }
2099
+ return child.resolve(rest.join(PATH_SEP));
2100
+ }
2101
+ /**
2102
+ * Resolve `::__meta__::key` segments from a registered primary node (possibly chained).
2103
+ */
2104
+ _resolveMetaChainFromNode(n, parts, fullPath) {
2105
+ let current = n;
2106
+ let i = 0;
2107
+ const p = [...parts];
2108
+ while (i < p.length) {
2109
+ if (p[i] !== GRAPH_META_SEGMENT) {
2110
+ throw new Error(
2111
+ `Graph "${this.name}": expected ${GRAPH_META_SEGMENT} segment in meta path "${fullPath}"`
2112
+ );
2113
+ }
2114
+ if (i + 1 >= p.length) {
2115
+ throw new Error(
2116
+ `Graph "${this.name}": meta path requires a key after ${GRAPH_META_SEGMENT} in "${fullPath}"`
2117
+ );
2118
+ }
2119
+ const key = p[i + 1];
2120
+ const next = current.meta[key];
2121
+ if (!next) {
2122
+ throw new Error(`Graph "${this.name}": unknown meta "${key}" in path "${fullPath}"`);
2123
+ }
2124
+ current = next;
2125
+ i += 2;
2126
+ }
2127
+ return current;
2128
+ }
2129
+ _resolveMetaEndpointKeys(baseNode, baseLocalKey, parts, fullPath) {
2130
+ let current = baseNode;
2131
+ let localKey = baseLocalKey;
2132
+ let i = 0;
2133
+ const p = [...parts];
2134
+ while (i < p.length) {
2135
+ if (p[i] !== GRAPH_META_SEGMENT) {
2136
+ throw new Error(
2137
+ `Graph "${this.name}": expected ${GRAPH_META_SEGMENT} segment in meta path "${fullPath}"`
2138
+ );
2139
+ }
2140
+ if (i + 1 >= p.length) {
2141
+ throw new Error(
2142
+ `Graph "${this.name}": meta path requires a key after ${GRAPH_META_SEGMENT} in "${fullPath}"`
2143
+ );
2144
+ }
2145
+ const metaKey = p[i + 1];
2146
+ const next = current.meta[metaKey];
2147
+ if (!next) {
2148
+ throw new Error(
2149
+ `Graph "${this.name}": unknown meta "${metaKey}" on node (in "${fullPath}")`
2150
+ );
2151
+ }
2152
+ localKey = `${localKey}${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}${metaKey}`;
2153
+ current = next;
2154
+ i += 2;
2155
+ }
2156
+ return [this, localKey, current];
2157
+ }
2158
+ /**
2159
+ * Deliver a message batch to every registered node in this graph and, recursively,
2160
+ * in mounted child graphs (§3.7). Recurses into mounts first, then delivers to
2161
+ * local nodes (sorted by name). Each {@link Node} receives at most one delivery
2162
+ * per call (deduped by reference).
2163
+ *
2164
+ * Companion `meta` nodes receive the same batch for control-plane types (e.g.
2165
+ * PAUSE) that the primary does not forward. **TEARDOWN-only** batches skip the
2166
+ * extra meta pass — the primary’s `down()` already cascades TEARDOWN to meta.
2167
+ *
2168
+ * @param messages - Batch to deliver to every registered node (and mounts, recursively).
2169
+ * @param options - Optional `actor` / `internal` for transport.
2170
+ */
2171
+ signal(messages, options) {
2172
+ this._signalDeliver(messages, options ?? {}, /* @__PURE__ */ new Set());
2173
+ }
2174
+ _signalDeliver(messages, opts, vis) {
2175
+ for (const sub of this._mounts.values()) {
2176
+ sub._signalDeliver(messages, opts, vis);
2177
+ }
2178
+ const internal = opts.internal === true;
2179
+ const downOpts = internal ? { internal: true } : { actor: opts.actor, delivery: "signal" };
2180
+ const metaMessages = filterMetaMessages(messages);
2181
+ for (const localName of [...this._nodes.keys()].sort()) {
2182
+ const n = this._nodes.get(localName);
2183
+ if (vis.has(n)) continue;
2184
+ vis.add(n);
2185
+ n.down(messages, downOpts);
2186
+ if (metaMessages.length === 0) continue;
2187
+ this._signalMetaSubtree(n, metaMessages, vis, downOpts);
2188
+ }
2189
+ }
2190
+ _signalMetaSubtree(root, messages, vis, downOpts) {
2191
+ for (const mk of Object.keys(root.meta).sort()) {
2192
+ const mnode = root.meta[mk];
2193
+ if (vis.has(mnode)) continue;
2194
+ vis.add(mnode);
2195
+ mnode.down(messages, downOpts);
2196
+ this._signalMetaSubtree(mnode, messages, vis, downOpts);
2197
+ }
2198
+ }
2199
+ /**
2200
+ * Static structure snapshot: qualified node keys, edges, mount names (GRAPHREFLY-SPEC §3.6, Appendix B).
2201
+ *
2202
+ * @param options - Optional `actor` for guard-scoped visibility and/or `filter` for selective output.
2203
+ * @returns JSON-shaped describe payload for this graph tree.
2204
+ *
2205
+ * @example
2206
+ * ```ts
2207
+ * graph.describe() // full snapshot
2208
+ * graph.describe({ actor: llm }) // guard-scoped
2209
+ * graph.describe({ filter: { status: "errored" } }) // only errored nodes
2210
+ * graph.describe({ filter: (n) => n.type === "state" }) // predicate filter
2211
+ * ```
2212
+ */
2213
+ describe(options) {
2214
+ const actor = options?.actor;
2215
+ const filter = options?.filter;
2216
+ const targets = [];
2217
+ this._collectObserveTargets("", targets);
2218
+ const nodeToPath = /* @__PURE__ */ new Map();
2219
+ for (const [p, n] of targets) {
2220
+ nodeToPath.set(n, p);
2221
+ }
2222
+ const nodes = {};
2223
+ for (const [p, n] of targets) {
2224
+ if (actor != null && !n.allowsObserve(actor)) continue;
2225
+ const raw = describeNode(n);
2226
+ const deps = n instanceof NodeImpl ? n._deps.map((d) => nodeToPath.get(d) ?? d.name ?? "") : [];
2227
+ const { name: _name, ...rest } = raw;
2228
+ const entry = { ...rest, deps };
2229
+ if (filter != null) {
2230
+ if (typeof filter === "function") {
2231
+ const fn = filter;
2232
+ const pass = fn.length >= 2 ? fn(p, entry) : fn(entry);
2233
+ if (!pass) continue;
2234
+ } else {
2235
+ let match = true;
2236
+ for (const [fk, fv] of Object.entries(filter)) {
2237
+ const normalizedKey = fk === "deps_includes" ? "depsIncludes" : fk === "meta_has" ? "metaHas" : fk;
2238
+ if (normalizedKey === "depsIncludes") {
2239
+ if (!entry.deps.includes(String(fv))) {
2240
+ match = false;
2241
+ break;
2242
+ }
2243
+ continue;
2244
+ }
2245
+ if (normalizedKey === "metaHas") {
2246
+ if (!Object.hasOwn(entry.meta, String(fv))) {
2247
+ match = false;
2248
+ break;
2249
+ }
2250
+ continue;
2251
+ }
2252
+ if (entry[normalizedKey] !== fv) {
2253
+ match = false;
2254
+ break;
2255
+ }
2256
+ }
2257
+ if (!match) continue;
2258
+ }
2259
+ }
2260
+ nodes[p] = entry;
2261
+ }
2262
+ const nodeKeys = new Set(Object.keys(nodes));
2263
+ let edges = this._collectAllEdges("");
2264
+ if (actor != null || filter != null) {
2265
+ edges = edges.filter((e) => nodeKeys.has(e.from) && nodeKeys.has(e.to));
2266
+ }
2267
+ edges.sort((a, b) => {
2268
+ if (a.from < b.from) return -1;
2269
+ if (a.from > b.from) return 1;
2270
+ if (a.to < b.to) return -1;
2271
+ if (a.to > b.to) return 1;
2272
+ return 0;
2273
+ });
2274
+ const allSubgraphs = this._collectSubgraphs("");
2275
+ const subgraphs = actor != null || filter != null ? allSubgraphs.filter((sg) => {
2276
+ const prefix = `${sg}${PATH_SEP}`;
2277
+ return [...nodeKeys].some((k) => k === sg || k.startsWith(prefix));
2278
+ }) : allSubgraphs;
2279
+ return {
2280
+ name: this.name,
2281
+ nodes,
2282
+ edges,
2283
+ subgraphs
2284
+ };
2285
+ }
2286
+ _collectSubgraphs(prefix) {
2287
+ const out = [];
2288
+ for (const m of [...this._mounts.keys()].sort()) {
2289
+ const q = prefix === "" ? m : `${prefix}${m}`;
2290
+ out.push(q);
2291
+ out.push(...this._mounts.get(m)._collectSubgraphs(`${q}${PATH_SEP}`));
2292
+ }
2293
+ return out;
2294
+ }
2295
+ _collectAllEdges(prefix) {
2296
+ const out = [];
2297
+ for (const m of [...this._mounts.keys()].sort()) {
2298
+ const p2 = prefix === "" ? m : `${prefix}${PATH_SEP}${m}`;
2299
+ out.push(...this._mounts.get(m)._collectAllEdges(p2));
2300
+ }
2301
+ for (const [f, t] of this.edges()) {
2302
+ out.push({
2303
+ from: this._qualifyEdgeEndpoint(f, prefix),
2304
+ to: this._qualifyEdgeEndpoint(t, prefix)
2305
+ });
2306
+ }
2307
+ return out;
2308
+ }
2309
+ _qualifyEdgeEndpoint(part, prefix) {
2310
+ if (part.includes(PATH_SEP)) return part;
2311
+ return prefix === "" ? part : `${prefix}${PATH_SEP}${part}`;
2312
+ }
2313
+ _collectObserveTargets(prefix, out) {
2314
+ for (const m of [...this._mounts.keys()].sort()) {
2315
+ const p2 = prefix === "" ? m : `${prefix}${PATH_SEP}${m}`;
2316
+ this._mounts.get(m)._collectObserveTargets(p2, out);
2317
+ }
2318
+ for (const loc of [...this._nodes.keys()].sort()) {
2319
+ const n = this._nodes.get(loc);
2320
+ const p = prefix === "" ? loc : `${prefix}${PATH_SEP}${loc}`;
2321
+ out.push([p, n]);
2322
+ this._appendMetaObserveTargets(p, n, out);
2323
+ }
2324
+ }
2325
+ _appendMetaObserveTargets(basePath, n, out) {
2326
+ for (const mk of Object.keys(n.meta).sort()) {
2327
+ const m = n.meta[mk];
2328
+ const mp = `${basePath}${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}${mk}`;
2329
+ out.push([mp, m]);
2330
+ this._appendMetaObserveTargets(mp, m, out);
2331
+ }
2332
+ }
2333
+ observe(pathOrOpts, options) {
2334
+ if (typeof pathOrOpts === "string") {
2335
+ const path = pathOrOpts;
2336
+ const actor2 = options?.actor;
2337
+ const target = this.resolve(path);
2338
+ if (actor2 != null && !target.allowsObserve(actor2)) {
2339
+ throw new GuardDenied({ actor: actor2, action: "observe", nodeName: path });
2340
+ }
2341
+ const wantsStructured2 = options?.structured === true || options?.timeline === true || options?.causal === true || options?.derived === true;
2342
+ if (wantsStructured2 && _Graph.inspectorEnabled) {
2343
+ return this._createObserveResult(path, target, options);
2344
+ }
2345
+ return {
2346
+ subscribe(sink) {
2347
+ return target.subscribe(sink);
2348
+ },
2349
+ up(messages) {
2350
+ try {
2351
+ target.up?.(messages);
2352
+ } catch (err) {
2353
+ if (err instanceof GuardDenied) return;
2354
+ throw err;
2355
+ }
2356
+ }
2357
+ };
2358
+ }
2359
+ const opts = pathOrOpts;
2360
+ const actor = opts?.actor;
2361
+ const wantsStructured = opts?.structured === true || opts?.timeline === true || opts?.causal === true || opts?.derived === true;
2362
+ if (wantsStructured && _Graph.inspectorEnabled) {
2363
+ return this._createObserveResultForAll(opts ?? {});
2364
+ }
2365
+ return {
2366
+ subscribe: (sink) => {
2367
+ const targets = [];
2368
+ this._collectObserveTargets("", targets);
2369
+ targets.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2370
+ const picked = actor == null ? targets : targets.filter(([, nd]) => nd.allowsObserve(actor));
2371
+ const unsubs = picked.map(
2372
+ ([p, nd]) => nd.subscribe((msgs) => {
2373
+ sink(p, msgs);
2374
+ })
2375
+ );
2376
+ return () => {
2377
+ for (const u of unsubs) u();
2378
+ };
2379
+ },
2380
+ up: (upPath, messages) => {
2381
+ try {
2382
+ const nd = this.resolve(upPath);
2383
+ nd.up?.(messages);
2384
+ } catch (err) {
2385
+ if (err instanceof GuardDenied) return;
2386
+ throw err;
2387
+ }
2388
+ }
2389
+ };
2390
+ }
2391
+ _createObserveResult(path, target, options) {
2392
+ const timeline = options.timeline === true;
2393
+ const causal = options.causal === true;
2394
+ const derived = options.derived === true;
2395
+ const result = {
2396
+ values: {},
2397
+ dirtyCount: 0,
2398
+ resolvedCount: 0,
2399
+ events: [],
2400
+ completedCleanly: false,
2401
+ errored: false
2402
+ };
2403
+ let lastTriggerDepIndex;
2404
+ let lastRunDepValues;
2405
+ let detachInspectorHook;
2406
+ if ((causal || derived) && target instanceof NodeImpl) {
2407
+ detachInspectorHook = target._setInspectorHook((event) => {
2408
+ if (event.kind === "dep_message") {
2409
+ lastTriggerDepIndex = event.depIndex;
2410
+ return;
2411
+ }
2412
+ lastRunDepValues = [...event.depValues];
2413
+ if (derived) {
2414
+ result.events.push({
2415
+ type: "derived",
2416
+ path,
2417
+ dep_values: [...event.depValues],
2418
+ ...timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {}
2419
+ });
2420
+ }
2421
+ });
2422
+ }
2423
+ const unsub = target.subscribe((msgs) => {
2424
+ for (const m of msgs) {
2425
+ const t = m[0];
2426
+ const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
2427
+ const withCausal = causal && lastRunDepValues != null ? (() => {
2428
+ const triggerDep = lastTriggerDepIndex != null && lastTriggerDepIndex >= 0 && target instanceof NodeImpl ? target._deps[lastTriggerDepIndex] : void 0;
2429
+ const tv = triggerDep?.v;
2430
+ return {
2431
+ trigger_dep_index: lastTriggerDepIndex,
2432
+ trigger_dep_name: triggerDep?.name,
2433
+ ...tv != null ? { trigger_version: { id: tv.id, version: tv.version } } : {},
2434
+ dep_values: [...lastRunDepValues]
2435
+ };
2436
+ })() : {};
2437
+ if (t === DATA) {
2438
+ result.values[path] = m[1];
2439
+ result.events.push({ type: "data", path, data: m[1], ...base, ...withCausal });
2440
+ } else if (t === DIRTY) {
2441
+ result.dirtyCount++;
2442
+ result.events.push({ type: "dirty", path, ...base });
2443
+ } else if (t === RESOLVED) {
2444
+ result.resolvedCount++;
2445
+ result.events.push({ type: "resolved", path, ...base, ...withCausal });
2446
+ } else if (t === COMPLETE) {
2447
+ if (!result.errored) result.completedCleanly = true;
2448
+ result.events.push({ type: "complete", path, ...base });
2449
+ } else if (t === ERROR) {
2450
+ result.errored = true;
2451
+ result.events.push({ type: "error", path, data: m[1], ...base });
2452
+ }
2453
+ }
2454
+ });
2455
+ return {
2456
+ get values() {
2457
+ return result.values;
2458
+ },
2459
+ get dirtyCount() {
2460
+ return result.dirtyCount;
2461
+ },
2462
+ get resolvedCount() {
2463
+ return result.resolvedCount;
2464
+ },
2465
+ get events() {
2466
+ return result.events;
2467
+ },
2468
+ get completedCleanly() {
2469
+ return result.completedCleanly;
2470
+ },
2471
+ get errored() {
2472
+ return result.errored;
2473
+ },
2474
+ dispose() {
2475
+ unsub();
2476
+ detachInspectorHook?.();
2477
+ }
2478
+ };
2479
+ }
2480
+ _createObserveResultForAll(options) {
2481
+ const timeline = options.timeline === true;
2482
+ const result = {
2483
+ values: {},
2484
+ dirtyCount: 0,
2485
+ resolvedCount: 0,
2486
+ events: [],
2487
+ completedCleanly: false,
2488
+ errored: false
2489
+ };
2490
+ const actor = options.actor;
2491
+ const targets = [];
2492
+ this._collectObserveTargets("", targets);
2493
+ targets.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2494
+ const picked = actor == null ? targets : targets.filter(([, nd]) => nd.allowsObserve(actor));
2495
+ const unsubs = picked.map(
2496
+ ([path, nd]) => nd.subscribe((msgs) => {
2497
+ for (const m of msgs) {
2498
+ const t = m[0];
2499
+ const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
2500
+ if (t === DATA) {
2501
+ result.values[path] = m[1];
2502
+ result.events.push({ type: "data", path, data: m[1], ...base });
2503
+ } else if (t === DIRTY) {
2504
+ result.dirtyCount++;
2505
+ result.events.push({ type: "dirty", path, ...base });
2506
+ } else if (t === RESOLVED) {
2507
+ result.resolvedCount++;
2508
+ result.events.push({ type: "resolved", path, ...base });
2509
+ } else if (t === COMPLETE) {
2510
+ if (!result.errored) result.completedCleanly = true;
2511
+ result.events.push({ type: "complete", path, ...base });
2512
+ } else if (t === ERROR) {
2513
+ result.errored = true;
2514
+ result.events.push({ type: "error", path, data: m[1], ...base });
2515
+ }
2516
+ }
2517
+ })
2518
+ );
2519
+ return {
2520
+ get values() {
2521
+ return result.values;
2522
+ },
2523
+ get dirtyCount() {
2524
+ return result.dirtyCount;
2525
+ },
2526
+ get resolvedCount() {
2527
+ return result.resolvedCount;
2528
+ },
2529
+ get events() {
2530
+ return result.events;
2531
+ },
2532
+ get completedCleanly() {
2533
+ return result.completedCleanly;
2534
+ },
2535
+ get errored() {
2536
+ return result.errored;
2537
+ },
2538
+ dispose() {
2539
+ for (const u of unsubs) u();
2540
+ }
2541
+ };
2542
+ }
2543
+ /**
2544
+ * Convenience live debugger over {@link Graph.observe}. Logs protocol events as they flow.
2545
+ *
2546
+ * Supports one-node (`path`) and graph-wide modes, event filtering, and JSON/pretty rendering.
2547
+ * Color themes are built in (`ansi` / `none`) to avoid external dependencies.
2548
+ *
2549
+ * @param options - Spy configuration.
2550
+ * @returns Disposable handle plus a structured observation accumulator.
2551
+ */
2552
+ spy(options = {}) {
2553
+ const include = options.includeTypes ? new Set(options.includeTypes) : null;
2554
+ const exclude = options.excludeTypes ? new Set(options.excludeTypes) : null;
2555
+ const theme = resolveSpyTheme(options.theme);
2556
+ const format = options.format ?? "pretty";
2557
+ const logger = options.logger ?? ((line) => console.log(line));
2558
+ const shouldLog = (type) => {
2559
+ if (include?.has(type) === false) return false;
2560
+ if (exclude?.has(type) === true) return false;
2561
+ return true;
2562
+ };
2563
+ const renderEvent = (event) => {
2564
+ if (format === "json") {
2565
+ try {
2566
+ return JSON.stringify(event);
2567
+ } catch {
2568
+ return JSON.stringify({
2569
+ type: event.type,
2570
+ path: event.path,
2571
+ data: "[unserializable]"
2572
+ });
2573
+ }
2574
+ }
2575
+ const color = theme[event.type] ?? "";
2576
+ const pathPart = event.path ? `${theme.path}${event.path}${theme.reset} ` : "";
2577
+ const dataPart = event.data !== void 0 ? ` ${describeData(event.data)}` : "";
2578
+ const triggerPart = event.trigger_dep_name != null ? ` <- ${event.trigger_dep_name}` : event.trigger_dep_index != null ? ` <- #${event.trigger_dep_index}` : "";
2579
+ const batchPart = event.in_batch ? " [batch]" : "";
2580
+ return `${pathPart}${color}${event.type.toUpperCase()}${theme.reset}${dataPart}${triggerPart}${batchPart}`;
2581
+ };
2582
+ if (!_Graph.inspectorEnabled) {
2583
+ const timeline = options.timeline ?? true;
2584
+ const acc = {
2585
+ values: {},
2586
+ dirtyCount: 0,
2587
+ resolvedCount: 0,
2588
+ events: [],
2589
+ completedCleanly: false,
2590
+ errored: false
2591
+ };
2592
+ let stop2 = () => {
2593
+ };
2594
+ const result2 = {
2595
+ get values() {
2596
+ return acc.values;
2597
+ },
2598
+ get dirtyCount() {
2599
+ return acc.dirtyCount;
2600
+ },
2601
+ get resolvedCount() {
2602
+ return acc.resolvedCount;
2603
+ },
2604
+ get events() {
2605
+ return acc.events;
2606
+ },
2607
+ get completedCleanly() {
2608
+ return acc.completedCleanly;
2609
+ },
2610
+ get errored() {
2611
+ return acc.errored;
2612
+ },
2613
+ dispose() {
2614
+ stop2();
2615
+ }
2616
+ };
2617
+ const pushEvent = (path, message) => {
2618
+ const t = message[0];
2619
+ const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
2620
+ let event;
2621
+ if (t === DATA) {
2622
+ if (path != null) acc.values[path] = message[1];
2623
+ event = { type: "data", ...path != null ? { path } : {}, data: message[1], ...base };
2624
+ } else if (t === DIRTY) {
2625
+ acc.dirtyCount += 1;
2626
+ event = { type: "dirty", ...path != null ? { path } : {}, ...base };
2627
+ } else if (t === RESOLVED) {
2628
+ acc.resolvedCount += 1;
2629
+ event = { type: "resolved", ...path != null ? { path } : {}, ...base };
2630
+ } else if (t === COMPLETE) {
2631
+ if (!acc.errored) acc.completedCleanly = true;
2632
+ event = { type: "complete", ...path != null ? { path } : {}, ...base };
2633
+ } else if (t === ERROR) {
2634
+ acc.errored = true;
2635
+ event = {
2636
+ type: "error",
2637
+ ...path != null ? { path } : {},
2638
+ data: message[1],
2639
+ ...base
2640
+ };
2641
+ }
2642
+ if (!event) return;
2643
+ acc.events.push(event);
2644
+ if (!shouldLog(event.type)) return;
2645
+ logger(renderEvent(event), event);
2646
+ };
2647
+ if (options.path != null) {
2648
+ const stream2 = this.observe(options.path, {
2649
+ actor: options.actor,
2650
+ structured: false
2651
+ });
2652
+ stop2 = stream2.subscribe((messages) => {
2653
+ for (const m of messages) {
2654
+ pushEvent(options.path, m);
2655
+ }
2656
+ });
2657
+ } else {
2658
+ const stream2 = this.observe({ actor: options.actor, structured: false });
2659
+ stop2 = stream2.subscribe((path, messages) => {
2660
+ for (const m of messages) {
2661
+ pushEvent(path, m);
2662
+ }
2663
+ });
2664
+ }
2665
+ return {
2666
+ result: result2,
2667
+ dispose() {
2668
+ result2.dispose();
2669
+ }
2670
+ };
2671
+ }
2672
+ const structuredObserveOptions = {
2673
+ actor: options.actor,
2674
+ structured: true,
2675
+ ...options.timeline !== false ? { timeline: true } : {},
2676
+ ...options.causal ? { causal: true } : {},
2677
+ ...options.derived ? { derived: true } : {}
2678
+ };
2679
+ const result = options.path != null ? this.observe(options.path, structuredObserveOptions) : this.observe(structuredObserveOptions);
2680
+ let cursor = 0;
2681
+ const flushNewEvents = () => {
2682
+ const nextEvents = result.events.slice(cursor);
2683
+ cursor = result.events.length;
2684
+ for (const event of nextEvents) {
2685
+ if (!shouldLog(event.type)) continue;
2686
+ logger(renderEvent(event), event);
2687
+ }
2688
+ };
2689
+ const stream = options.path != null ? this.observe(options.path, { actor: options.actor, structured: false }) : this.observe({ actor: options.actor, structured: false });
2690
+ const stop = options.path != null ? stream.subscribe((messages) => {
2691
+ if (messages.length > 0) {
2692
+ flushNewEvents();
2693
+ }
2694
+ }) : stream.subscribe((_path, messages) => {
2695
+ if (messages.length > 0) {
2696
+ flushNewEvents();
2697
+ }
2698
+ });
2699
+ return {
2700
+ result,
2701
+ dispose() {
2702
+ stop();
2703
+ flushNewEvents();
2704
+ result.dispose();
2705
+ }
2706
+ };
2707
+ }
2708
+ /**
2709
+ * CLI/debug-friendly graph dump built on {@link Graph.describe}.
2710
+ *
2711
+ * @param options - Optional actor/filter/format toggles.
2712
+ * @returns Rendered graph text.
2713
+ */
2714
+ dumpGraph(options = {}) {
2715
+ const described = this.describe({
2716
+ actor: options.actor,
2717
+ filter: options.filter
2718
+ });
2719
+ const includeEdges = options.includeEdges ?? true;
2720
+ const includeSubgraphs = options.includeSubgraphs ?? true;
2721
+ if (options.format === "json") {
2722
+ const payload = {
2723
+ name: described.name,
2724
+ nodes: described.nodes,
2725
+ edges: includeEdges ? described.edges : [],
2726
+ subgraphs: includeSubgraphs ? described.subgraphs : []
2727
+ };
2728
+ const text2 = JSON.stringify(sortJsonValue(payload), null, options.indent ?? 2);
2729
+ options.logger?.(text2);
2730
+ return text2;
2731
+ }
2732
+ const lines = [];
2733
+ lines.push(`Graph ${described.name}`);
2734
+ lines.push("Nodes:");
2735
+ for (const path of Object.keys(described.nodes).sort()) {
2736
+ const n = described.nodes[path];
2737
+ lines.push(`- ${path} (${n.type}/${n.status}): ${describeData(n.value)}`);
2738
+ }
2739
+ if (includeEdges) {
2740
+ lines.push("Edges:");
2741
+ for (const edge of described.edges) {
2742
+ lines.push(`- ${edge.from} -> ${edge.to}`);
2743
+ }
2744
+ }
2745
+ if (includeSubgraphs) {
2746
+ lines.push("Subgraphs:");
2747
+ for (const sg of described.subgraphs) {
2748
+ lines.push(`- ${sg}`);
2749
+ }
2750
+ }
2751
+ const text = lines.join("\n");
2752
+ options.logger?.(text);
2753
+ return text;
2754
+ }
2755
+ // ——————————————————————————————————————————————————————————————
2756
+ // Lifecycle & persistence (§3.7–§3.8)
2757
+ // ——————————————————————————————————————————————————————————————
2758
+ /**
2759
+ * Sends `[[TEARDOWN]]` to all nodes, then clears registries on this graph and every
2760
+ * mounted subgraph (§3.7). The instance is left empty and may be reused with {@link Graph.add}.
2761
+ */
2762
+ destroy() {
2763
+ this.signal([[TEARDOWN]], { internal: true });
2764
+ for (const dispose of [...this._autoCheckpointDisposers]) {
2765
+ try {
2766
+ dispose();
2767
+ } catch {
2768
+ }
2769
+ }
2770
+ this._autoCheckpointDisposers.clear();
2771
+ for (const child of [...this._mounts.values()]) {
2772
+ child._destroyClearOnly();
2773
+ }
2774
+ this._mounts.clear();
2775
+ this._nodes.clear();
2776
+ this._edges.clear();
2777
+ }
2778
+ /** Clear structure after parent already signaled TEARDOWN through this subtree. */
2779
+ _destroyClearOnly() {
2780
+ for (const child of [...this._mounts.values()]) {
2781
+ child._destroyClearOnly();
2782
+ }
2783
+ this._mounts.clear();
2784
+ this._nodes.clear();
2785
+ this._edges.clear();
2786
+ }
2787
+ /**
2788
+ * Serializes structure and current values to JSON-shaped data (§3.8). Same information
2789
+ * as {@link Graph.describe} plus a `version` field for format evolution.
2790
+ *
2791
+ * @returns Persistable snapshot with sorted keys.
2792
+ */
2793
+ snapshot() {
2794
+ const d = this.describe();
2795
+ const sortedNodes = {};
2796
+ for (const key of Object.keys(d.nodes).sort()) {
2797
+ sortedNodes[key] = d.nodes[key];
2798
+ }
2799
+ const sortedSubgraphs = [...d.subgraphs].sort();
2800
+ return { ...d, version: 1, nodes: sortedNodes, subgraphs: sortedSubgraphs };
2801
+ }
2802
+ /**
2803
+ * Apply persisted values onto an existing graph whose topology matches the snapshot
2804
+ * (§3.8). Only {@link DescribeNodeOutput.type} `state` and `producer` entries with a
2805
+ * `value` field are written; `derived` / `operator` / `effect` are skipped so deps
2806
+ * drive recomputation. Unknown paths are ignored.
2807
+ *
2808
+ * @param data - Snapshot envelope with matching `name` and node slices.
2809
+ * @throws If `data.name` does not equal {@link Graph.name}.
2810
+ */
2811
+ restore(data, options) {
2812
+ parseSnapshotEnvelope(data);
2813
+ if (data.name !== this.name) {
2814
+ throw new Error(
2815
+ `Graph "${this.name}": restore snapshot name "${data.name}" does not match this graph`
2816
+ );
2817
+ }
2818
+ const onlyPatterns = options?.only == null ? null : (Array.isArray(options.only) ? options.only : [options.only]).map((p) => globToRegex(p));
2819
+ for (const path of Object.keys(data.nodes).sort()) {
2820
+ if (onlyPatterns !== null && !onlyPatterns.some((re) => re.test(path))) continue;
2821
+ const slice = data.nodes[path];
2822
+ if (slice === void 0 || slice.value === void 0) continue;
2823
+ if (slice.type === "derived" || slice.type === "operator" || slice.type === "effect") {
2824
+ continue;
2825
+ }
2826
+ try {
2827
+ this.set(path, slice.value);
2828
+ } catch {
2829
+ }
2830
+ }
2831
+ }
2832
+ /**
2833
+ * Creates a graph named from the snapshot, optionally runs `build` to register nodes
2834
+ * and mounts, then {@link Graph.restore} values (§3.8).
2835
+ *
2836
+ * @param data - Snapshot envelope (`version` checked).
2837
+ * @param build - Optional callback to construct topology before values are applied.
2838
+ * @returns Hydrated `Graph` instance.
2839
+ */
2840
+ static fromSnapshot(data, build) {
2841
+ parseSnapshotEnvelope(data);
2842
+ const g = new _Graph(data.name);
2843
+ if (build) {
2844
+ build(g);
2845
+ g.restore(data);
2846
+ return g;
2847
+ }
2848
+ for (const mount of [...data.subgraphs].sort((a, b) => {
2849
+ const da = a.split(PATH_SEP).length;
2850
+ const db = b.split(PATH_SEP).length;
2851
+ if (da !== db) return da - db;
2852
+ if (a < b) return -1;
2853
+ if (a > b) return 1;
2854
+ return 0;
2855
+ })) {
2856
+ const parts = mount.split(PATH_SEP);
2857
+ let target = g;
2858
+ for (const seg of parts) {
2859
+ if (!target._mounts.has(seg)) {
2860
+ target.mount(seg, new _Graph(seg));
2861
+ }
2862
+ target = target._mounts.get(seg);
2863
+ }
2864
+ }
2865
+ const primaryEntries = Object.entries(data.nodes).filter(([path]) => !path.includes(`${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}`)).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2866
+ const pending = new Map(primaryEntries);
2867
+ const created = /* @__PURE__ */ new Map();
2868
+ let progressed = true;
2869
+ while (pending.size > 0 && progressed) {
2870
+ progressed = false;
2871
+ for (const [path, slice] of [...pending.entries()]) {
2872
+ const deps = slice?.deps ?? [];
2873
+ if (!deps.every((dep) => created.has(dep))) continue;
2874
+ const [owner, localName] = _Graph._ownerForPath(g, path);
2875
+ const meta = { ...slice?.meta ?? {} };
2876
+ const factory = _Graph._factoryForPath(path);
2877
+ let node2;
2878
+ if (slice?.type === "state") {
2879
+ node2 = state(slice.value, { meta });
2880
+ } else {
2881
+ if (factory == null) continue;
2882
+ node2 = factory(localName, {
2883
+ path,
2884
+ type: slice.type,
2885
+ value: slice.value,
2886
+ meta,
2887
+ deps,
2888
+ resolvedDeps: deps.map((dep) => created.get(dep))
2889
+ });
2890
+ }
2891
+ owner.add(localName, node2);
2892
+ created.set(path, node2);
2893
+ pending.delete(path);
2894
+ progressed = true;
2895
+ }
2896
+ }
2897
+ if (pending.size > 0) {
2898
+ const unresolved = [...pending.keys()].sort().join(", ");
2899
+ throw new Error(
2900
+ `Graph.fromSnapshot could not reconstruct nodes without build callback: ${unresolved}. Register matching factories with Graph.registerFactory(pattern, factory).`
2901
+ );
2902
+ }
2903
+ for (const edge of data.edges) {
2904
+ try {
2905
+ g.connect(edge.from, edge.to);
2906
+ } catch {
2907
+ }
2908
+ }
2909
+ g.restore(data);
2910
+ return g;
2911
+ }
2912
+ /**
2913
+ * Plain snapshot with **recursively sorted object keys** for deterministic serialization (§3.8).
2914
+ *
2915
+ * @remarks
2916
+ * ECMAScript `JSON.stringify(graph)` invokes this method; it must return a plain object, not an
2917
+ * already-stringified JSON string (otherwise the graph would be double-encoded).
2918
+ * For a single UTF-8 string with a trailing newline (convenient for git), use {@link Graph.toJSONString}.
2919
+ *
2920
+ * @returns Same object as {@link Graph.snapshot}.
2921
+ */
2922
+ toJSON() {
2923
+ return this.snapshot();
2924
+ }
2925
+ /**
2926
+ * Deterministic JSON **text**: `JSON.stringify` of {@link Graph.toJSON} plus a trailing newline (§3.8).
2927
+ *
2928
+ * @returns Stable string suitable for diffs.
2929
+ */
2930
+ toJSONString() {
2931
+ return stableJsonStringify(this.snapshot());
2932
+ }
2933
+ /**
2934
+ * Debounced persistence wired to graph-wide observe stream (spec §3.8 auto-checkpoint).
2935
+ *
2936
+ * Checkpoint trigger uses {@link messageTier}: only batches containing tier >= 2 messages
2937
+ * schedule a save (`DATA`/`RESOLVED`/terminal/destruction), never pure tier-0/1 control waves.
2938
+ */
2939
+ autoCheckpoint(adapter, options = {}) {
2940
+ const debounceMs = Math.max(0, options.debounceMs ?? 500);
2941
+ const compactEvery = Math.max(1, options.compactEvery ?? 10);
2942
+ let timer;
2943
+ let seq = 0;
2944
+ let pending = false;
2945
+ let lastDescribe;
2946
+ const flush = () => {
2947
+ timer = void 0;
2948
+ if (!pending) return;
2949
+ pending = false;
2950
+ try {
2951
+ const described = this.describe();
2952
+ const snapshot = { ...described, version: SNAPSHOT_VERSION };
2953
+ seq += 1;
2954
+ const shouldCompact = lastDescribe == null || seq % compactEvery === 0;
2955
+ if (shouldCompact) {
2956
+ adapter.save({ mode: "full", snapshot, seq });
2957
+ } else {
2958
+ const previous = lastDescribe;
2959
+ if (previous == null) return;
2960
+ adapter.save({
2961
+ mode: "diff",
2962
+ diff: _Graph.diff(previous, described),
2963
+ snapshot,
2964
+ seq
2965
+ });
2966
+ }
2967
+ lastDescribe = described;
2968
+ } catch (error) {
2969
+ options.onError?.(error);
2970
+ }
2971
+ };
2972
+ const schedule = () => {
2973
+ pending = true;
2974
+ if (timer !== void 0) clearTimeout(timer);
2975
+ timer = setTimeout(flush, debounceMs);
2976
+ };
2977
+ const off = this.observe().subscribe((path, messages) => {
2978
+ const triggeredByTier = messages.some((m) => messageTier(m[0]) >= 2);
2979
+ if (!triggeredByTier) return;
2980
+ if (options.filter) {
2981
+ const described = this.describe().nodes[path];
2982
+ if (described == null || !options.filter(path, described)) return;
2983
+ }
2984
+ schedule();
2985
+ });
2986
+ const dispose = () => {
2987
+ off();
2988
+ if (timer !== void 0) {
2989
+ clearTimeout(timer);
2990
+ timer = void 0;
2991
+ }
2992
+ this._autoCheckpointDisposers.delete(dispose);
2993
+ };
2994
+ this._autoCheckpointDisposers.add(dispose);
2995
+ return { dispose };
2996
+ }
2997
+ /**
2998
+ * Export the current graph topology as Mermaid flowchart text.
2999
+ *
3000
+ * Renders qualified node paths and registered edges from {@link Graph.describe}.
3001
+ *
3002
+ * @param options - Optional diagram direction (`LR` by default).
3003
+ * @returns Mermaid flowchart source.
3004
+ */
3005
+ toMermaid(options) {
3006
+ const direction = normalizeDiagramDirection(options?.direction);
3007
+ const described = this.describe();
3008
+ const paths = Object.keys(described.nodes).sort();
3009
+ const ids = /* @__PURE__ */ new Map();
3010
+ for (let i = 0; i < paths.length; i += 1) {
3011
+ ids.set(paths[i], `n${i}`);
3012
+ }
3013
+ const lines = [`flowchart ${direction}`];
3014
+ for (const path of paths) {
3015
+ const id = ids.get(path);
3016
+ lines.push(` ${id}["${escapeMermaidLabel(path)}"]`);
3017
+ }
3018
+ for (const [from, to] of collectDiagramArrows(described)) {
3019
+ const fromId = ids.get(from);
3020
+ const toId = ids.get(to);
3021
+ if (!fromId || !toId) continue;
3022
+ lines.push(` ${fromId} --> ${toId}`);
3023
+ }
3024
+ return lines.join("\n");
3025
+ }
3026
+ /**
3027
+ * Export the current graph topology as D2 diagram text.
3028
+ *
3029
+ * Renders qualified node paths, constructor deps, and registered edges from {@link Graph.describe}.
3030
+ *
3031
+ * @param options - Optional diagram direction (`LR` by default).
3032
+ * @returns D2 source text.
3033
+ */
3034
+ toD2(options) {
3035
+ const direction = normalizeDiagramDirection(options?.direction);
3036
+ const described = this.describe();
3037
+ const paths = Object.keys(described.nodes).sort();
3038
+ const ids = /* @__PURE__ */ new Map();
3039
+ for (let i = 0; i < paths.length; i += 1) {
3040
+ ids.set(paths[i], `n${i}`);
3041
+ }
3042
+ const lines = [`direction: ${d2DirectionFromGraphDirection(direction)}`];
3043
+ for (const path of paths) {
3044
+ const id = ids.get(path);
3045
+ lines.push(`${id}: "${escapeD2Label(path)}"`);
3046
+ }
3047
+ for (const [from, to] of collectDiagramArrows(described)) {
3048
+ const fromId = ids.get(from);
3049
+ const toId = ids.get(to);
3050
+ if (!fromId || !toId) continue;
3051
+ lines.push(`${fromId} -> ${toId}`);
3052
+ }
3053
+ return lines.join("\n");
3054
+ }
3055
+ // ——————————————————————————————————————————————————————————————
3056
+ // Inspector (roadmap 3.3) — reasoning trace, overhead gating
3057
+ // ——————————————————————————————————————————————————————————————
3058
+ /**
3059
+ * When `false`, structured observation options (`causal`, `timeline`),
3060
+ * `annotate()`, and `traceLog()` are no-ops. Raw `observe()` always works.
3061
+ *
3062
+ * Default: `true` outside production (`process.env.NODE_ENV !== "production"`).
3063
+ */
3064
+ static inspectorEnabled = !(typeof process !== "undefined" && process.env?.NODE_ENV === "production");
3065
+ _annotations = /* @__PURE__ */ new Map();
3066
+ _traceRing = new RingBuffer(1e3);
3067
+ /**
3068
+ * Attaches a reasoning annotation to a node — captures *why* an AI agent set a value.
3069
+ *
3070
+ * No-op when {@link Graph.inspectorEnabled} is `false`.
3071
+ *
3072
+ * @param path - Qualified node path.
3073
+ * @param reason - Free-text note stored in the trace ring buffer.
3074
+ */
3075
+ annotate(path, reason) {
3076
+ if (!_Graph.inspectorEnabled) return;
3077
+ this.resolve(path);
3078
+ this._annotations.set(path, reason);
3079
+ this._traceRing.push({ path, reason, timestamp_ns: monotonicNs() });
3080
+ }
3081
+ /**
3082
+ * Returns a chronological log of all reasoning annotations (ring buffer).
3083
+ *
3084
+ * @returns `[]` when {@link Graph.inspectorEnabled} is `false`.
3085
+ */
3086
+ traceLog() {
3087
+ if (!_Graph.inspectorEnabled) return [];
3088
+ return this._traceRing.toArray();
3089
+ }
3090
+ /**
3091
+ * Computes structural + value diff between two {@link Graph.describe} snapshots.
3092
+ *
3093
+ * @param a - Earlier describe output.
3094
+ * @param b - Later describe output.
3095
+ * @returns Added/removed nodes, changed fields, and edge deltas.
3096
+ */
3097
+ static diff(a, b) {
3098
+ const aKeys = new Set(Object.keys(a.nodes));
3099
+ const bKeys = new Set(Object.keys(b.nodes));
3100
+ const nodesAdded = [...bKeys].filter((k) => !aKeys.has(k)).sort();
3101
+ const nodesRemoved = [...aKeys].filter((k) => !bKeys.has(k)).sort();
3102
+ const nodesChanged = [];
3103
+ for (const key of aKeys) {
3104
+ if (!bKeys.has(key)) continue;
3105
+ const na = a.nodes[key];
3106
+ const nb = b.nodes[key];
3107
+ const av = na.v;
3108
+ const bv = nb.v;
3109
+ if (av != null && bv != null && av.id === bv.id && av.version === bv.version) {
3110
+ for (const field of ["type", "status"]) {
3111
+ const va = na[field];
3112
+ const vb = nb[field];
3113
+ if (va !== vb) {
3114
+ nodesChanged.push({ path: key, field, from: va, to: vb });
3115
+ }
3116
+ }
3117
+ continue;
3118
+ }
3119
+ for (const field of ["type", "status", "value"]) {
3120
+ const va = na[field];
3121
+ const vb = nb[field];
3122
+ if (!Object.is(va, vb) && JSON.stringify(va) !== JSON.stringify(vb)) {
3123
+ nodesChanged.push({ path: key, field, from: va, to: vb });
3124
+ }
3125
+ }
3126
+ }
3127
+ const edgeKey2 = (e) => `${e.from} ${e.to}`;
3128
+ const aEdges = new Set(a.edges.map(edgeKey2));
3129
+ const bEdges = new Set(b.edges.map(edgeKey2));
3130
+ const edgesAdded = b.edges.filter((e) => !aEdges.has(edgeKey2(e)));
3131
+ const edgesRemoved = a.edges.filter((e) => !bEdges.has(edgeKey2(e)));
3132
+ const aSubgraphs = new Set(a.subgraphs);
3133
+ const bSubgraphs = new Set(b.subgraphs);
3134
+ const subgraphsAdded = [...bSubgraphs].filter((s) => !aSubgraphs.has(s)).sort();
3135
+ const subgraphsRemoved = [...aSubgraphs].filter((s) => !bSubgraphs.has(s)).sort();
3136
+ return {
3137
+ nodesAdded,
3138
+ nodesRemoved,
3139
+ nodesChanged,
3140
+ edgesAdded,
3141
+ edgesRemoved,
3142
+ subgraphsAdded,
3143
+ subgraphsRemoved
3144
+ };
3145
+ }
3146
+ };
3147
+ function reachable(described, from, direction, options = {}) {
3148
+ if (!from) return [];
3149
+ if (direction !== "upstream" && direction !== "downstream") {
3150
+ throw new Error(`reachable: direction must be "upstream" or "downstream"`);
3151
+ }
3152
+ const maxDepth = options.maxDepth;
3153
+ if (maxDepth != null && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
3154
+ throw new Error(`reachable: maxDepth must be an integer >= 0`);
3155
+ }
3156
+ if (maxDepth === 0) return [];
3157
+ const depsByPath = /* @__PURE__ */ new Map();
3158
+ const reverseDeps = /* @__PURE__ */ new Map();
3159
+ const incomingEdges = /* @__PURE__ */ new Map();
3160
+ const outgoingEdges = /* @__PURE__ */ new Map();
3161
+ const universe = /* @__PURE__ */ new Set();
3162
+ const nodesRaw = described != null && typeof described === "object" && "nodes" in described && typeof described.nodes === "object" && described.nodes !== null && !Array.isArray(described.nodes) ? described.nodes : {};
3163
+ const edgesRaw = described != null && typeof described === "object" && "edges" in described && Array.isArray(described.edges) ? described.edges : [];
3164
+ for (const [path, node2] of Object.entries(nodesRaw)) {
3165
+ if (!path) continue;
3166
+ universe.add(path);
3167
+ const deps = node2 != null && typeof node2 === "object" && Array.isArray(node2.deps) ? node2.deps : [];
3168
+ const cleanDeps = deps.filter((d) => typeof d === "string" && d.length > 0);
3169
+ depsByPath.set(path, cleanDeps);
3170
+ for (const dep of cleanDeps) {
3171
+ universe.add(dep);
3172
+ if (!reverseDeps.has(dep)) reverseDeps.set(dep, /* @__PURE__ */ new Set());
3173
+ reverseDeps.get(dep).add(path);
3174
+ }
3175
+ }
3176
+ for (const edge of edgesRaw) {
3177
+ if (edge == null || typeof edge !== "object") continue;
3178
+ const edgeFrom = "from" in edge && typeof edge.from === "string" ? edge.from : "";
3179
+ const edgeTo = "to" in edge && typeof edge.to === "string" ? edge.to : "";
3180
+ if (!edgeFrom || !edgeTo) continue;
3181
+ universe.add(edgeFrom);
3182
+ universe.add(edgeTo);
3183
+ if (!outgoingEdges.has(edgeFrom)) outgoingEdges.set(edgeFrom, /* @__PURE__ */ new Set());
3184
+ outgoingEdges.get(edgeFrom).add(edgeTo);
3185
+ if (!incomingEdges.has(edgeTo)) incomingEdges.set(edgeTo, /* @__PURE__ */ new Set());
3186
+ incomingEdges.get(edgeTo).add(edgeFrom);
3187
+ }
3188
+ if (!universe.has(from)) return [];
3189
+ const neighbors = (path) => {
3190
+ if (direction === "upstream") {
3191
+ const depNeighbors2 = depsByPath.get(path) ?? [];
3192
+ const edgeNeighbors2 = [...incomingEdges.get(path) ?? []];
3193
+ return [...depNeighbors2, ...edgeNeighbors2];
3194
+ }
3195
+ const depNeighbors = [...reverseDeps.get(path) ?? []];
3196
+ const edgeNeighbors = [...outgoingEdges.get(path) ?? []];
3197
+ return [...depNeighbors, ...edgeNeighbors];
3198
+ };
3199
+ const visited = /* @__PURE__ */ new Set([from]);
3200
+ const out = /* @__PURE__ */ new Set();
3201
+ const queue = [{ path: from, depth: 0 }];
3202
+ while (queue.length > 0) {
3203
+ const next = queue.shift();
3204
+ if (maxDepth != null && next.depth >= maxDepth) continue;
3205
+ for (const nb of neighbors(next.path)) {
3206
+ if (!nb || visited.has(nb)) continue;
3207
+ visited.add(nb);
3208
+ out.add(nb);
3209
+ queue.push({ path: nb, depth: next.depth + 1 });
3210
+ }
3211
+ }
3212
+ return [...out].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
3213
+ }
3214
+ // Annotate the CommonJS export names for ESM import in node:
3215
+ 0 && (module.exports = {
3216
+ GRAPH_META_SEGMENT,
3217
+ Graph,
3218
+ JsonCodec,
3219
+ createDagCborCodec,
3220
+ createDagCborZstdCodec,
3221
+ negotiateCodec,
3222
+ reachable,
3223
+ replayWAL
3224
+ });
3225
+ //# sourceMappingURL=index.cjs.map