@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,4883 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
8
+ var __typeError = (msg) => {
9
+ throw TypeError(msg);
10
+ };
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
27
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
28
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
29
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
30
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
31
+ var __runInitializers = (array, flags, self, value) => {
32
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
33
+ return value;
34
+ };
35
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
36
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
37
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
38
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
39
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
40
+ return __privateGet(this, extra);
41
+ }, set [name](x) {
42
+ return __privateSet(this, extra, x);
43
+ } }, name));
44
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
45
+ for (var i = decorators.length - 1; i >= 0; i--) {
46
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
47
+ if (k) {
48
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
49
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
50
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
51
+ }
52
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
53
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
54
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
55
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
56
+ }
57
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
58
+ };
59
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
60
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
61
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
62
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
63
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
64
+
65
+ // src/compat/nestjs/index.ts
66
+ var nestjs_exports = {};
67
+ __export(nestjs_exports, {
68
+ ACTOR_KEY: () => ACTOR_KEY,
69
+ COMMAND_HANDLERS: () => COMMAND_HANDLERS,
70
+ CQRS_EVENT_HANDLERS: () => CQRS_EVENT_HANDLERS,
71
+ CRON_HANDLERS: () => CRON_HANDLERS,
72
+ CommandHandler: () => CommandHandler,
73
+ EVENT_HANDLERS: () => EVENT_HANDLERS,
74
+ EventHandler: () => EventHandler,
75
+ GRAPHREFLY_REQUEST_GRAPH: () => GRAPHREFLY_REQUEST_GRAPH,
76
+ GRAPHREFLY_ROOT_GRAPH: () => GRAPHREFLY_ROOT_GRAPH,
77
+ GraphCron: () => GraphCron,
78
+ GraphInterval: () => GraphInterval,
79
+ GraphReflyEventExplorer: () => GraphReflyEventExplorer,
80
+ GraphReflyGuard: () => GraphReflyGuard,
81
+ GraphReflyGuardImpl: () => GraphReflyGuardImpl,
82
+ GraphReflyModule: () => GraphReflyModule,
83
+ INTERVAL_HANDLERS: () => INTERVAL_HANDLERS,
84
+ InjectCqrsGraph: () => InjectCqrsGraph,
85
+ InjectGraph: () => InjectGraph,
86
+ InjectNode: () => InjectNode,
87
+ ObserveGateway: () => ObserveGateway,
88
+ OnGraphEvent: () => OnGraphEvent,
89
+ QUERY_HANDLERS: () => QUERY_HANDLERS,
90
+ QueryHandler: () => QueryHandler,
91
+ SAGA_HANDLERS: () => SAGA_HANDLERS,
92
+ SagaHandler: () => SagaHandler,
93
+ fromHeader: () => fromHeader,
94
+ fromJwtPayload: () => fromJwtPayload,
95
+ getActor: () => getActor,
96
+ getGraphToken: () => getGraphToken,
97
+ getNodeToken: () => getNodeToken,
98
+ observeGraph$: () => observeGraph$,
99
+ observeNode$: () => observeNode$,
100
+ observeSSE: () => observeSSE,
101
+ observeSubscription: () => observeSubscription,
102
+ toMessages$: () => toMessages$,
103
+ toObservable: () => toObservable
104
+ });
105
+ module.exports = __toCommonJS(nestjs_exports);
106
+
107
+ // src/extra/observable.ts
108
+ var import_rxjs = require("rxjs");
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/extra/observable.ts
140
+ function toObservable(node2) {
141
+ return new import_rxjs.Observable((subscriber) => {
142
+ const unsub = node2.subscribe((msgs) => {
143
+ for (const m of msgs) {
144
+ if (subscriber.closed) return;
145
+ if (m[0] === DATA) {
146
+ subscriber.next(m[1]);
147
+ } else if (m[0] === ERROR) {
148
+ subscriber.error(m[1]);
149
+ return;
150
+ } else if (m[0] === COMPLETE) {
151
+ subscriber.complete();
152
+ return;
153
+ }
154
+ }
155
+ });
156
+ return unsub;
157
+ });
158
+ }
159
+ function toMessages$(node2) {
160
+ return new import_rxjs.Observable((subscriber) => {
161
+ const unsub = node2.subscribe((msgs) => {
162
+ if (subscriber.closed) return;
163
+ subscriber.next(msgs);
164
+ for (const m of msgs) {
165
+ if (m[0] === ERROR) {
166
+ subscriber.error(m[1]);
167
+ return;
168
+ }
169
+ if (m[0] === COMPLETE) {
170
+ subscriber.complete();
171
+ return;
172
+ }
173
+ }
174
+ });
175
+ return unsub;
176
+ });
177
+ }
178
+ function observeNode$(graph, path, options) {
179
+ return new import_rxjs.Observable((subscriber) => {
180
+ const handle = graph.observe(path, options);
181
+ const unsub = handle.subscribe((msgs) => {
182
+ for (const m of msgs) {
183
+ if (subscriber.closed) return;
184
+ if (m[0] === DATA) {
185
+ subscriber.next(m[1]);
186
+ } else if (m[0] === ERROR) {
187
+ subscriber.error(m[1]);
188
+ return;
189
+ } else if (m[0] === COMPLETE) {
190
+ subscriber.complete();
191
+ return;
192
+ }
193
+ }
194
+ });
195
+ return unsub;
196
+ });
197
+ }
198
+ function observeGraph$(graph, options) {
199
+ return new import_rxjs.Observable((subscriber) => {
200
+ const handle = graph.observe(options);
201
+ const unsub = handle.subscribe((nodePath, messages) => {
202
+ if (subscriber.closed) return;
203
+ subscriber.next({ path: nodePath, messages });
204
+ });
205
+ return unsub;
206
+ });
207
+ }
208
+
209
+ // src/compat/nestjs/decorators.ts
210
+ var import_common = require("@nestjs/common");
211
+
212
+ // src/compat/nestjs/tokens.ts
213
+ var GRAPHREFLY_ROOT_GRAPH = /* @__PURE__ */ Symbol.for("graphrefly:root-graph");
214
+ var GRAPHREFLY_REQUEST_GRAPH = /* @__PURE__ */ Symbol.for("graphrefly:request-graph");
215
+ function getGraphToken(name) {
216
+ return /* @__PURE__ */ Symbol.for(`graphrefly:graph:${name}`);
217
+ }
218
+ function getNodeToken(path) {
219
+ return /* @__PURE__ */ Symbol.for(`graphrefly:node:${path}`);
220
+ }
221
+
222
+ // src/compat/nestjs/decorators.ts
223
+ var EVENT_HANDLERS = /* @__PURE__ */ new Map();
224
+ var INTERVAL_HANDLERS = /* @__PURE__ */ new Map();
225
+ var CRON_HANDLERS = /* @__PURE__ */ new Map();
226
+ var COMMAND_HANDLERS = /* @__PURE__ */ new Map();
227
+ var CQRS_EVENT_HANDLERS = /* @__PURE__ */ new Map();
228
+ var QUERY_HANDLERS = /* @__PURE__ */ new Map();
229
+ var SAGA_HANDLERS = /* @__PURE__ */ new Map();
230
+ function InjectGraph(name) {
231
+ if (name === "request") return (0, import_common.Inject)(GRAPHREFLY_REQUEST_GRAPH);
232
+ return (0, import_common.Inject)(name ? getGraphToken(name) : GRAPHREFLY_ROOT_GRAPH);
233
+ }
234
+ function InjectCqrsGraph(name) {
235
+ return (0, import_common.Inject)(getGraphToken(name));
236
+ }
237
+ function InjectNode(path) {
238
+ return (0, import_common.Inject)(getNodeToken(path));
239
+ }
240
+ function OnGraphEvent(nodeName) {
241
+ return (_value, context) => {
242
+ const methodKey = context.name;
243
+ context.addInitializer(function() {
244
+ const ctor = this.constructor;
245
+ const existing = EVENT_HANDLERS.get(ctor) ?? [];
246
+ existing.push({ nodeName, methodKey });
247
+ EVENT_HANDLERS.set(ctor, existing);
248
+ });
249
+ };
250
+ }
251
+ function GraphInterval(ms) {
252
+ return (_value, context) => {
253
+ const methodKey = context.name;
254
+ context.addInitializer(function() {
255
+ const ctor = this.constructor;
256
+ const existing = INTERVAL_HANDLERS.get(ctor) ?? [];
257
+ existing.push({ ms, methodKey });
258
+ INTERVAL_HANDLERS.set(ctor, existing);
259
+ });
260
+ };
261
+ }
262
+ function GraphCron(expr) {
263
+ return (_value, context) => {
264
+ const methodKey = context.name;
265
+ context.addInitializer(function() {
266
+ const ctor = this.constructor;
267
+ const existing = CRON_HANDLERS.get(ctor) ?? [];
268
+ existing.push({ expr, methodKey });
269
+ CRON_HANDLERS.set(ctor, existing);
270
+ });
271
+ };
272
+ }
273
+ function CommandHandler(cqrsName, commandName) {
274
+ return (_value, context) => {
275
+ const methodKey = context.name;
276
+ context.addInitializer(function() {
277
+ const ctor = this.constructor;
278
+ const existing = COMMAND_HANDLERS.get(ctor) ?? [];
279
+ existing.push({ cqrsName, commandName, methodKey });
280
+ COMMAND_HANDLERS.set(ctor, existing);
281
+ });
282
+ };
283
+ }
284
+ function EventHandler(cqrsName, eventName) {
285
+ return (_value, context) => {
286
+ const methodKey = context.name;
287
+ context.addInitializer(function() {
288
+ const ctor = this.constructor;
289
+ const existing = CQRS_EVENT_HANDLERS.get(ctor) ?? [];
290
+ existing.push({ cqrsName, eventName, methodKey });
291
+ CQRS_EVENT_HANDLERS.set(ctor, existing);
292
+ });
293
+ };
294
+ }
295
+ function QueryHandler(cqrsName, projectionName) {
296
+ return (_value, context) => {
297
+ const methodKey = context.name;
298
+ context.addInitializer(function() {
299
+ const ctor = this.constructor;
300
+ const existing = QUERY_HANDLERS.get(ctor) ?? [];
301
+ existing.push({ cqrsName, projectionName, methodKey });
302
+ QUERY_HANDLERS.set(ctor, existing);
303
+ });
304
+ };
305
+ }
306
+ function SagaHandler(cqrsName, sagaName, eventNames) {
307
+ return (_value, context) => {
308
+ const methodKey = context.name;
309
+ context.addInitializer(function() {
310
+ const ctor = this.constructor;
311
+ const existing = SAGA_HANDLERS.get(ctor) ?? [];
312
+ existing.push({ cqrsName, eventNames, sagaName, methodKey });
313
+ SAGA_HANDLERS.set(ctor, existing);
314
+ });
315
+ };
316
+ }
317
+
318
+ // src/extra/sources.ts
319
+ var import_node_fs = require("fs");
320
+ var import_node_path = require("path");
321
+
322
+ // src/core/clock.ts
323
+ function monotonicNs() {
324
+ return Math.trunc(performance.now() * 1e6);
325
+ }
326
+ function wallClockNs() {
327
+ return Date.now() * 1e6;
328
+ }
329
+
330
+ // src/core/actor.ts
331
+ var DEFAULT_ACTOR = { type: "system", id: "" };
332
+ function normalizeActor(actor) {
333
+ if (actor == null) return DEFAULT_ACTOR;
334
+ const { type, id, ...rest } = actor;
335
+ return {
336
+ type: type ?? "system",
337
+ id: id ?? "",
338
+ ...rest
339
+ };
340
+ }
341
+
342
+ // src/core/batch.ts
343
+ var MAX_DRAIN_ITERATIONS = 1e3;
344
+ var batchDepth = 0;
345
+ var flushInProgress = false;
346
+ var pendingPhase2 = [];
347
+ var pendingPhase3 = [];
348
+ function isBatching() {
349
+ return batchDepth > 0 || flushInProgress;
350
+ }
351
+ function batch(fn) {
352
+ batchDepth += 1;
353
+ let threw = false;
354
+ try {
355
+ fn();
356
+ } catch (e) {
357
+ threw = true;
358
+ throw e;
359
+ } finally {
360
+ batchDepth -= 1;
361
+ if (batchDepth === 0) {
362
+ if (threw) {
363
+ if (!flushInProgress) {
364
+ pendingPhase2.length = 0;
365
+ pendingPhase3.length = 0;
366
+ }
367
+ } else {
368
+ drainPending();
369
+ }
370
+ }
371
+ }
372
+ }
373
+ function drainPending() {
374
+ const ownsFlush = !flushInProgress;
375
+ if (ownsFlush) {
376
+ flushInProgress = true;
377
+ }
378
+ let firstError;
379
+ let hasError = false;
380
+ try {
381
+ let iterations = 0;
382
+ while (pendingPhase2.length > 0 || pendingPhase3.length > 0) {
383
+ while (pendingPhase2.length > 0) {
384
+ iterations += 1;
385
+ if (iterations > MAX_DRAIN_ITERATIONS) {
386
+ pendingPhase2.length = 0;
387
+ pendingPhase3.length = 0;
388
+ throw new Error(
389
+ `batch drain exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely a reactive cycle`
390
+ );
391
+ }
392
+ const ops = pendingPhase2.splice(0);
393
+ for (const run of ops) {
394
+ try {
395
+ run();
396
+ } catch (e) {
397
+ if (!hasError) {
398
+ firstError = e;
399
+ hasError = true;
400
+ }
401
+ }
402
+ }
403
+ }
404
+ if (pendingPhase3.length > 0) {
405
+ iterations += 1;
406
+ if (iterations > MAX_DRAIN_ITERATIONS) {
407
+ pendingPhase2.length = 0;
408
+ pendingPhase3.length = 0;
409
+ throw new Error(
410
+ `batch drain exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely a reactive cycle`
411
+ );
412
+ }
413
+ const ops = pendingPhase3.splice(0);
414
+ for (const run of ops) {
415
+ try {
416
+ run();
417
+ } catch (e) {
418
+ if (!hasError) {
419
+ firstError = e;
420
+ hasError = true;
421
+ }
422
+ }
423
+ }
424
+ }
425
+ }
426
+ } finally {
427
+ if (ownsFlush) {
428
+ flushInProgress = false;
429
+ }
430
+ }
431
+ if (hasError) {
432
+ throw firstError;
433
+ }
434
+ }
435
+ function partitionForBatch(messages) {
436
+ const immediate = [];
437
+ const deferred = [];
438
+ const terminal = [];
439
+ for (const m of messages) {
440
+ if (isPhase2Message(m)) {
441
+ deferred.push(m);
442
+ } else if (isTerminalMessage(m[0])) {
443
+ terminal.push(m);
444
+ } else {
445
+ immediate.push(m);
446
+ }
447
+ }
448
+ return { immediate, deferred, terminal };
449
+ }
450
+ function emitWithBatch(emit, messages, phase = 2) {
451
+ if (messages.length === 0) {
452
+ return;
453
+ }
454
+ const queue = phase === 3 ? pendingPhase3 : pendingPhase2;
455
+ if (messages.length === 1) {
456
+ const t = messages[0][0];
457
+ if (t === DATA || t === RESOLVED) {
458
+ if (isBatching()) {
459
+ queue.push(() => emit(messages));
460
+ } else {
461
+ emit(messages);
462
+ }
463
+ } else if (isTerminalMessage(t)) {
464
+ if (isBatching()) {
465
+ queue.push(() => emit(messages));
466
+ } else {
467
+ emit(messages);
468
+ }
469
+ } else {
470
+ emit(messages);
471
+ }
472
+ return;
473
+ }
474
+ const { immediate, deferred, terminal } = partitionForBatch(messages);
475
+ if (immediate.length > 0) {
476
+ emit(immediate);
477
+ }
478
+ if (isBatching()) {
479
+ if (deferred.length > 0) {
480
+ queue.push(() => emit(deferred));
481
+ }
482
+ if (terminal.length > 0) {
483
+ queue.push(() => emit(terminal));
484
+ }
485
+ } else {
486
+ if (deferred.length > 0) {
487
+ emit(deferred);
488
+ }
489
+ if (terminal.length > 0) {
490
+ emit(terminal);
491
+ }
492
+ }
493
+ }
494
+
495
+ // src/core/guard.ts
496
+ var GuardDenied = class extends Error {
497
+ actor;
498
+ action;
499
+ nodeName;
500
+ /**
501
+ * @param details - Actor, action, and optional node name for the denial.
502
+ * @param message - Optional override for the default error message.
503
+ */
504
+ constructor(details, message) {
505
+ super(
506
+ message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
507
+ );
508
+ this.name = "GuardDenied";
509
+ this.actor = details.actor;
510
+ this.action = details.action;
511
+ this.nodeName = details.nodeName;
512
+ }
513
+ /** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
514
+ get node() {
515
+ return this.nodeName;
516
+ }
517
+ };
518
+ function normalizeActions(action) {
519
+ if (Array.isArray(action)) {
520
+ return [...action];
521
+ }
522
+ return [action];
523
+ }
524
+ function matchesActions(set, action) {
525
+ return set.has(action) || set.has("*");
526
+ }
527
+ function policy(build) {
528
+ const rules = [];
529
+ const allow = (action, opts) => {
530
+ rules.push({
531
+ kind: "allow",
532
+ actions: new Set(normalizeActions(action)),
533
+ where: opts?.where ?? (() => true)
534
+ });
535
+ };
536
+ const deny = (action, opts) => {
537
+ rules.push({
538
+ kind: "deny",
539
+ actions: new Set(normalizeActions(action)),
540
+ where: opts?.where ?? (() => true)
541
+ });
542
+ };
543
+ build(allow, deny);
544
+ return (actor, action) => {
545
+ let denied = false;
546
+ let allowed = false;
547
+ for (const r of rules) {
548
+ if (!matchesActions(r.actions, action)) continue;
549
+ if (!r.where(actor)) continue;
550
+ if (r.kind === "deny") {
551
+ denied = true;
552
+ } else {
553
+ allowed = true;
554
+ }
555
+ }
556
+ if (denied) return false;
557
+ return allowed;
558
+ };
559
+ }
560
+ var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
561
+ function accessHintForGuard(guard) {
562
+ const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
563
+ if (allowed.length === 0) return "restricted";
564
+ if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
565
+ return "both";
566
+ }
567
+ if (allowed.length === 1) return allowed[0];
568
+ return allowed.join("+");
569
+ }
570
+
571
+ // src/core/versioning.ts
572
+ var import_node_crypto = require("crypto");
573
+ function canonicalizeForHash(value) {
574
+ if (value === void 0) return null;
575
+ if (typeof value === "number") {
576
+ if (!Number.isFinite(value)) {
577
+ throw new TypeError(`Cannot hash non-finite number: ${value}`);
578
+ }
579
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
580
+ throw new TypeError(
581
+ `Cannot hash integer outside safe range (|n| > 2^53-1): ${value}. Cross-language cid parity is not guaranteed for unsafe integers.`
582
+ );
583
+ }
584
+ return value;
585
+ }
586
+ if (typeof value === "string" || typeof value === "boolean" || value === null) {
587
+ return value;
588
+ }
589
+ if (Array.isArray(value)) {
590
+ return value.map(canonicalizeForHash);
591
+ }
592
+ if (typeof value === "object" && value !== null) {
593
+ const sorted = {};
594
+ for (const k of Object.keys(value).sort()) {
595
+ sorted[k] = canonicalizeForHash(value[k]);
596
+ }
597
+ return sorted;
598
+ }
599
+ return null;
600
+ }
601
+ function defaultHash(value) {
602
+ const canonical = canonicalizeForHash(value ?? null);
603
+ const json = JSON.stringify(canonical);
604
+ return (0, import_node_crypto.createHash)("sha256").update(json).digest("hex").slice(0, 16);
605
+ }
606
+ function createVersioning(level, initialValue, opts) {
607
+ const id = opts?.id ?? (0, import_node_crypto.randomUUID)();
608
+ if (level === 0) {
609
+ return { id, version: 0 };
610
+ }
611
+ const hash = opts?.hash ?? defaultHash;
612
+ const cid = hash(initialValue);
613
+ return { id, version: 0, cid, prev: null };
614
+ }
615
+ function advanceVersion(info, newValue, hashFn) {
616
+ info.version += 1;
617
+ if ("cid" in info) {
618
+ info.prev = info.cid;
619
+ info.cid = hashFn(newValue);
620
+ }
621
+ }
622
+
623
+ // src/core/node.ts
624
+ function createIntBitSet() {
625
+ let bits = 0;
626
+ return {
627
+ set(i) {
628
+ bits |= 1 << i;
629
+ },
630
+ clear(i) {
631
+ bits &= ~(1 << i);
632
+ },
633
+ has(i) {
634
+ return (bits & 1 << i) !== 0;
635
+ },
636
+ covers(other) {
637
+ return (bits & other._bits()) === other._bits();
638
+ },
639
+ any() {
640
+ return bits !== 0;
641
+ },
642
+ reset() {
643
+ bits = 0;
644
+ },
645
+ _bits() {
646
+ return bits;
647
+ }
648
+ };
649
+ }
650
+ function createArrayBitSet(size) {
651
+ const words = new Uint32Array(Math.ceil(size / 32));
652
+ return {
653
+ set(i) {
654
+ words[i >>> 5] |= 1 << (i & 31);
655
+ },
656
+ clear(i) {
657
+ words[i >>> 5] &= ~(1 << (i & 31));
658
+ },
659
+ has(i) {
660
+ return (words[i >>> 5] & 1 << (i & 31)) !== 0;
661
+ },
662
+ covers(other) {
663
+ const ow = other._words;
664
+ for (let w = 0; w < words.length; w++) {
665
+ if ((words[w] & ow[w]) >>> 0 !== ow[w]) return false;
666
+ }
667
+ return true;
668
+ },
669
+ any() {
670
+ for (let w = 0; w < words.length; w++) {
671
+ if (words[w] !== 0) return true;
672
+ }
673
+ return false;
674
+ },
675
+ reset() {
676
+ words.fill(0);
677
+ },
678
+ _words: words
679
+ };
680
+ }
681
+ function createBitSet(size) {
682
+ return size <= 31 ? createIntBitSet() : createArrayBitSet(size);
683
+ }
684
+ var isNodeArray = (value) => Array.isArray(value);
685
+ var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
686
+ var isCleanupFn = (value) => typeof value === "function";
687
+ var statusAfterMessage = (status, msg) => {
688
+ const t = msg[0];
689
+ if (t === DIRTY) return "dirty";
690
+ if (t === DATA) return "settled";
691
+ if (t === RESOLVED) return "resolved";
692
+ if (t === COMPLETE) return "completed";
693
+ if (t === ERROR) return "errored";
694
+ if (t === INVALIDATE) return "dirty";
695
+ if (t === TEARDOWN) return "disconnected";
696
+ return status;
697
+ };
698
+ var NodeImpl = class {
699
+ // --- Configuration (set once, never reassigned) ---
700
+ _optsName;
701
+ _registryName;
702
+ /** @internal — read by {@link describeNode} before inference. */
703
+ _describeKind;
704
+ meta;
705
+ _deps;
706
+ _fn;
707
+ _opts;
708
+ _equals;
709
+ _onMessage;
710
+ /** @internal — read by {@link describeNode} for `accessHintForGuard`. */
711
+ _guard;
712
+ _lastMutation;
713
+ _hasDeps;
714
+ _autoComplete;
715
+ _isSingleDep;
716
+ // --- Mutable state ---
717
+ _cached;
718
+ _status;
719
+ _terminal = false;
720
+ _connected = false;
721
+ _producerStarted = false;
722
+ _connecting = false;
723
+ _manualEmitUsed = false;
724
+ _sinkCount = 0;
725
+ _singleDepSinkCount = 0;
726
+ // --- Object/collection state ---
727
+ _depDirtyMask;
728
+ _depSettledMask;
729
+ _depCompleteMask;
730
+ _allDepsCompleteMask;
731
+ _lastDepValues;
732
+ _cleanup;
733
+ _sinks = null;
734
+ _singleDepSinks = /* @__PURE__ */ new WeakSet();
735
+ _upstreamUnsubs = [];
736
+ _actions;
737
+ _boundEmitToSinks;
738
+ _inspectorHook;
739
+ _versioning;
740
+ _hashFn;
741
+ constructor(deps, fn, opts) {
742
+ this._deps = deps;
743
+ this._fn = fn;
744
+ this._opts = opts;
745
+ this._optsName = opts.name;
746
+ this._describeKind = opts.describeKind;
747
+ this._equals = opts.equals ?? Object.is;
748
+ this._onMessage = opts.onMessage;
749
+ this._guard = opts.guard;
750
+ this._hasDeps = deps.length > 0;
751
+ this._autoComplete = opts.completeWhenDepsComplete ?? true;
752
+ this._isSingleDep = deps.length === 1 && fn != null;
753
+ this._cached = opts.initial;
754
+ this._status = this._hasDeps ? "disconnected" : "settled";
755
+ this._hashFn = opts.versioningHash ?? defaultHash;
756
+ this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached, {
757
+ id: opts.versioningId,
758
+ hash: this._hashFn
759
+ }) : void 0;
760
+ this._depDirtyMask = createBitSet(deps.length);
761
+ this._depSettledMask = createBitSet(deps.length);
762
+ this._depCompleteMask = createBitSet(deps.length);
763
+ this._allDepsCompleteMask = createBitSet(deps.length);
764
+ for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
765
+ const meta = {};
766
+ for (const [k, v] of Object.entries(opts.meta ?? {})) {
767
+ meta[k] = node({
768
+ initial: v,
769
+ name: `${opts.name ?? "node"}:meta:${k}`,
770
+ describeKind: "state",
771
+ ...opts.guard != null ? { guard: opts.guard } : {}
772
+ });
773
+ }
774
+ Object.freeze(meta);
775
+ this.meta = meta;
776
+ const self = this;
777
+ this._actions = {
778
+ down(messages) {
779
+ self._manualEmitUsed = true;
780
+ self._downInternal(messages);
781
+ },
782
+ emit(value) {
783
+ self._manualEmitUsed = true;
784
+ self._emitAutoValue(value);
785
+ },
786
+ up(messages) {
787
+ self._upInternal(messages);
788
+ }
789
+ };
790
+ this.down = this.down.bind(this);
791
+ this.up = this.up.bind(this);
792
+ this._boundEmitToSinks = this._emitToSinks.bind(this);
793
+ }
794
+ get name() {
795
+ return this._registryName ?? this._optsName;
796
+ }
797
+ /**
798
+ * When a node is registered with {@link Graph.add} without an options `name`,
799
+ * the graph assigns the registry local name for introspection (parity with graphrefly-py).
800
+ */
801
+ _assignRegistryName(localName) {
802
+ if (this._optsName !== void 0 || this._registryName !== void 0) return;
803
+ this._registryName = localName;
804
+ }
805
+ /**
806
+ * @internal Attach/remove inspector hook for graph-level observability.
807
+ * Returns a disposer that restores the previous hook.
808
+ */
809
+ _setInspectorHook(hook) {
810
+ const prev = this._inspectorHook;
811
+ this._inspectorHook = hook;
812
+ return () => {
813
+ if (this._inspectorHook === hook) {
814
+ this._inspectorHook = prev;
815
+ }
816
+ };
817
+ }
818
+ // --- Public interface (Node<T>) ---
819
+ get status() {
820
+ return this._status;
821
+ }
822
+ get lastMutation() {
823
+ return this._lastMutation;
824
+ }
825
+ get v() {
826
+ return this._versioning;
827
+ }
828
+ /**
829
+ * Retroactively apply versioning to a node that was created without it.
830
+ * No-op if versioning is already enabled.
831
+ *
832
+ * Version starts at 0 regardless of prior DATA emissions — it tracks
833
+ * changes from the moment versioning is enabled, not historical ones.
834
+ *
835
+ * @internal — used by {@link Graph.setVersioning}.
836
+ */
837
+ _applyVersioning(level, opts) {
838
+ if (this._versioning != null) return;
839
+ this._hashFn = opts?.hash ?? this._hashFn;
840
+ this._versioning = createVersioning(level, this._cached, {
841
+ id: opts?.id,
842
+ hash: this._hashFn
843
+ });
844
+ }
845
+ hasGuard() {
846
+ return this._guard != null;
847
+ }
848
+ allowsObserve(actor) {
849
+ if (this._guard == null) return true;
850
+ return this._guard(normalizeActor(actor), "observe");
851
+ }
852
+ get() {
853
+ return this._cached;
854
+ }
855
+ down(messages, options) {
856
+ if (messages.length === 0) return;
857
+ if (!options?.internal && this._guard != null) {
858
+ const actor = normalizeActor(options?.actor);
859
+ const delivery = options?.delivery ?? "write";
860
+ const action = delivery === "signal" ? "signal" : "write";
861
+ if (!this._guard(actor, action)) {
862
+ throw new GuardDenied({ actor, action, nodeName: this.name });
863
+ }
864
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
865
+ }
866
+ this._downInternal(messages);
867
+ }
868
+ _downInternal(messages) {
869
+ if (messages.length === 0) return;
870
+ let lifecycleMessages = messages;
871
+ let sinkMessages = messages;
872
+ if (this._terminal && !this._opts.resubscribable) {
873
+ const terminalPassthrough = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
874
+ if (terminalPassthrough.length === 0) return;
875
+ lifecycleMessages = terminalPassthrough;
876
+ sinkMessages = terminalPassthrough;
877
+ }
878
+ this._handleLocalLifecycle(lifecycleMessages);
879
+ if (this._canSkipDirty()) {
880
+ let hasPhase2 = false;
881
+ for (let i = 0; i < sinkMessages.length; i++) {
882
+ const t = sinkMessages[i][0];
883
+ if (t === DATA || t === RESOLVED) {
884
+ hasPhase2 = true;
885
+ break;
886
+ }
887
+ }
888
+ if (hasPhase2) {
889
+ const filtered = [];
890
+ for (let i = 0; i < sinkMessages.length; i++) {
891
+ if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
892
+ }
893
+ if (filtered.length > 0) {
894
+ emitWithBatch(this._boundEmitToSinks, filtered);
895
+ }
896
+ return;
897
+ }
898
+ }
899
+ emitWithBatch(this._boundEmitToSinks, sinkMessages);
900
+ }
901
+ subscribe(sink, hints) {
902
+ if (hints?.actor != null && this._guard != null) {
903
+ const actor = normalizeActor(hints.actor);
904
+ if (!this._guard(actor, "observe")) {
905
+ throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
906
+ }
907
+ }
908
+ if (this._terminal && this._opts.resubscribable) {
909
+ this._terminal = false;
910
+ this._status = this._hasDeps ? "disconnected" : "settled";
911
+ this._opts.onResubscribe?.();
912
+ }
913
+ this._sinkCount += 1;
914
+ if (hints?.singleDep) {
915
+ this._singleDepSinkCount += 1;
916
+ this._singleDepSinks.add(sink);
917
+ }
918
+ if (this._sinks == null) {
919
+ this._sinks = sink;
920
+ } else if (typeof this._sinks === "function") {
921
+ this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
922
+ } else {
923
+ this._sinks.add(sink);
924
+ }
925
+ if (this._hasDeps) {
926
+ this._connectUpstream();
927
+ } else if (this._fn) {
928
+ this._startProducer();
929
+ }
930
+ let removed = false;
931
+ return () => {
932
+ if (removed) return;
933
+ removed = true;
934
+ this._sinkCount -= 1;
935
+ if (this._singleDepSinks.has(sink)) {
936
+ this._singleDepSinkCount -= 1;
937
+ this._singleDepSinks.delete(sink);
938
+ }
939
+ if (this._sinks == null) return;
940
+ if (typeof this._sinks === "function") {
941
+ if (this._sinks === sink) this._sinks = null;
942
+ } else {
943
+ this._sinks.delete(sink);
944
+ if (this._sinks.size === 1) {
945
+ const [only] = this._sinks;
946
+ this._sinks = only;
947
+ } else if (this._sinks.size === 0) {
948
+ this._sinks = null;
949
+ }
950
+ }
951
+ if (this._sinks == null) {
952
+ this._disconnectUpstream();
953
+ this._stopProducer();
954
+ }
955
+ };
956
+ }
957
+ up(messages, options) {
958
+ if (!this._hasDeps) return;
959
+ if (!options?.internal && this._guard != null) {
960
+ const actor = normalizeActor(options?.actor);
961
+ if (!this._guard(actor, "write")) {
962
+ throw new GuardDenied({ actor, action: "write", nodeName: this.name });
963
+ }
964
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
965
+ }
966
+ for (const dep of this._deps) {
967
+ if (options === void 0) {
968
+ dep.up?.(messages);
969
+ } else {
970
+ dep.up?.(messages, options);
971
+ }
972
+ }
973
+ }
974
+ _upInternal(messages) {
975
+ if (!this._hasDeps) return;
976
+ for (const dep of this._deps) {
977
+ dep.up?.(messages, { internal: true });
978
+ }
979
+ }
980
+ unsubscribe() {
981
+ if (!this._hasDeps) return;
982
+ this._disconnectUpstream();
983
+ }
984
+ // --- Private methods (prototype, _ prefix) ---
985
+ _emitToSinks(messages) {
986
+ if (this._sinks == null) return;
987
+ if (typeof this._sinks === "function") {
988
+ this._sinks(messages);
989
+ return;
990
+ }
991
+ const snapshot = [...this._sinks];
992
+ for (const sink of snapshot) {
993
+ sink(messages);
994
+ }
995
+ }
996
+ _handleLocalLifecycle(messages) {
997
+ for (const m of messages) {
998
+ const t = m[0];
999
+ if (t === DATA) {
1000
+ this._cached = m[1];
1001
+ if (this._versioning != null) {
1002
+ advanceVersion(this._versioning, m[1], this._hashFn);
1003
+ }
1004
+ }
1005
+ if (t === INVALIDATE) {
1006
+ const cleanupFn = this._cleanup;
1007
+ this._cleanup = void 0;
1008
+ cleanupFn?.();
1009
+ this._cached = void 0;
1010
+ this._lastDepValues = void 0;
1011
+ }
1012
+ this._status = statusAfterMessage(this._status, m);
1013
+ if (t === COMPLETE || t === ERROR) {
1014
+ this._terminal = true;
1015
+ }
1016
+ if (t === TEARDOWN) {
1017
+ if (this._opts.resetOnTeardown) {
1018
+ this._cached = void 0;
1019
+ }
1020
+ const teardownCleanup = this._cleanup;
1021
+ this._cleanup = void 0;
1022
+ teardownCleanup?.();
1023
+ try {
1024
+ this._propagateToMeta(t);
1025
+ } finally {
1026
+ this._disconnectUpstream();
1027
+ this._stopProducer();
1028
+ }
1029
+ }
1030
+ if (t !== TEARDOWN && propagatesToMeta(t)) {
1031
+ this._propagateToMeta(t);
1032
+ }
1033
+ }
1034
+ }
1035
+ /** Propagate a signal to all companion meta nodes (best-effort). */
1036
+ _propagateToMeta(t) {
1037
+ for (const metaNode of Object.values(this.meta)) {
1038
+ try {
1039
+ metaNode._downInternal([[t]]);
1040
+ } catch {
1041
+ }
1042
+ }
1043
+ }
1044
+ _canSkipDirty() {
1045
+ return this._sinkCount === 1 && this._singleDepSinkCount === 1;
1046
+ }
1047
+ _emitAutoValue(value) {
1048
+ const wasDirty = this._status === "dirty";
1049
+ const unchanged = this._equals(this._cached, value);
1050
+ if (unchanged) {
1051
+ this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
1052
+ return;
1053
+ }
1054
+ this._cached = value;
1055
+ this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
1056
+ }
1057
+ _runFn() {
1058
+ if (!this._fn) return;
1059
+ if (this._terminal && !this._opts.resubscribable) return;
1060
+ if (this._connecting) return;
1061
+ try {
1062
+ const n = this._deps.length;
1063
+ const depValues = new Array(n);
1064
+ for (let i = 0; i < n; i++) depValues[i] = this._deps[i].get();
1065
+ const prev = this._lastDepValues;
1066
+ if (n > 0 && prev != null && prev.length === n) {
1067
+ let allSame = true;
1068
+ for (let i = 0; i < n; i++) {
1069
+ if (!Object.is(depValues[i], prev[i])) {
1070
+ allSame = false;
1071
+ break;
1072
+ }
1073
+ }
1074
+ if (allSame) {
1075
+ if (this._status === "dirty") {
1076
+ this._downInternal([[RESOLVED]]);
1077
+ }
1078
+ return;
1079
+ }
1080
+ }
1081
+ const prevCleanup = this._cleanup;
1082
+ this._cleanup = void 0;
1083
+ prevCleanup?.();
1084
+ this._manualEmitUsed = false;
1085
+ this._lastDepValues = depValues;
1086
+ this._inspectorHook?.({ kind: "run", depValues });
1087
+ const out = this._fn(depValues, this._actions);
1088
+ if (isCleanupFn(out)) {
1089
+ this._cleanup = out;
1090
+ return;
1091
+ }
1092
+ if (this._manualEmitUsed) return;
1093
+ if (out === void 0) return;
1094
+ this._emitAutoValue(out);
1095
+ } catch (err) {
1096
+ this._downInternal([[ERROR, err]]);
1097
+ }
1098
+ }
1099
+ _onDepDirty(index) {
1100
+ const wasDirty = this._depDirtyMask.has(index);
1101
+ this._depDirtyMask.set(index);
1102
+ this._depSettledMask.clear(index);
1103
+ if (!wasDirty) {
1104
+ this._downInternal([[DIRTY]]);
1105
+ }
1106
+ }
1107
+ _onDepSettled(index) {
1108
+ if (!this._depDirtyMask.has(index)) {
1109
+ this._onDepDirty(index);
1110
+ }
1111
+ this._depSettledMask.set(index);
1112
+ if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
1113
+ this._depDirtyMask.reset();
1114
+ this._depSettledMask.reset();
1115
+ this._runFn();
1116
+ }
1117
+ }
1118
+ _maybeCompleteFromDeps() {
1119
+ if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
1120
+ this._downInternal([[COMPLETE]]);
1121
+ }
1122
+ }
1123
+ _handleDepMessages(index, messages) {
1124
+ for (const msg of messages) {
1125
+ this._inspectorHook?.({ kind: "dep_message", depIndex: index, message: msg });
1126
+ const t = msg[0];
1127
+ if (this._onMessage) {
1128
+ try {
1129
+ if (this._onMessage(msg, index, this._actions)) continue;
1130
+ } catch (err) {
1131
+ this._downInternal([[ERROR, err]]);
1132
+ return;
1133
+ }
1134
+ }
1135
+ if (!this._fn) {
1136
+ if (t === COMPLETE && this._deps.length > 1) {
1137
+ this._depCompleteMask.set(index);
1138
+ this._maybeCompleteFromDeps();
1139
+ continue;
1140
+ }
1141
+ this._downInternal([msg]);
1142
+ continue;
1143
+ }
1144
+ if (t === DIRTY) {
1145
+ this._onDepDirty(index);
1146
+ continue;
1147
+ }
1148
+ if (t === DATA || t === RESOLVED) {
1149
+ this._onDepSettled(index);
1150
+ continue;
1151
+ }
1152
+ if (t === COMPLETE) {
1153
+ this._depCompleteMask.set(index);
1154
+ this._depDirtyMask.clear(index);
1155
+ this._depSettledMask.clear(index);
1156
+ if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
1157
+ this._depDirtyMask.reset();
1158
+ this._depSettledMask.reset();
1159
+ this._runFn();
1160
+ } else if (!this._depDirtyMask.any() && this._status === "dirty") {
1161
+ this._depSettledMask.reset();
1162
+ this._runFn();
1163
+ }
1164
+ this._maybeCompleteFromDeps();
1165
+ continue;
1166
+ }
1167
+ if (t === ERROR) {
1168
+ this._downInternal([msg]);
1169
+ continue;
1170
+ }
1171
+ if (t === INVALIDATE || t === TEARDOWN || t === PAUSE || t === RESUME) {
1172
+ this._downInternal([msg]);
1173
+ continue;
1174
+ }
1175
+ this._downInternal([msg]);
1176
+ }
1177
+ }
1178
+ _connectUpstream() {
1179
+ if (!this._hasDeps || this._connected) return;
1180
+ this._connected = true;
1181
+ this._depDirtyMask.reset();
1182
+ this._depSettledMask.reset();
1183
+ this._depCompleteMask.reset();
1184
+ this._status = "settled";
1185
+ const subHints = this._isSingleDep ? { singleDep: true } : void 0;
1186
+ this._connecting = true;
1187
+ try {
1188
+ for (let i = 0; i < this._deps.length; i += 1) {
1189
+ const dep = this._deps[i];
1190
+ this._upstreamUnsubs.push(
1191
+ dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
1192
+ );
1193
+ }
1194
+ } finally {
1195
+ this._connecting = false;
1196
+ }
1197
+ if (this._fn) {
1198
+ this._runFn();
1199
+ }
1200
+ }
1201
+ _stopProducer() {
1202
+ if (!this._producerStarted) return;
1203
+ this._producerStarted = false;
1204
+ const producerCleanup = this._cleanup;
1205
+ this._cleanup = void 0;
1206
+ producerCleanup?.();
1207
+ }
1208
+ _startProducer() {
1209
+ if (this._deps.length !== 0 || !this._fn || this._producerStarted) return;
1210
+ this._producerStarted = true;
1211
+ this._runFn();
1212
+ }
1213
+ _disconnectUpstream() {
1214
+ if (!this._connected) return;
1215
+ for (const unsub of this._upstreamUnsubs.splice(0)) {
1216
+ unsub();
1217
+ }
1218
+ this._connected = false;
1219
+ this._depDirtyMask.reset();
1220
+ this._depSettledMask.reset();
1221
+ this._depCompleteMask.reset();
1222
+ this._status = "disconnected";
1223
+ }
1224
+ };
1225
+ function node(depsOrFn, fnOrOpts, optsArg) {
1226
+ const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
1227
+ const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
1228
+ let opts = {};
1229
+ if (isNodeArray(depsOrFn)) {
1230
+ opts = (isNodeOptions(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
1231
+ } else if (isNodeOptions(depsOrFn)) {
1232
+ opts = depsOrFn;
1233
+ } else {
1234
+ opts = (isNodeOptions(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
1235
+ }
1236
+ return new NodeImpl(deps, fn, opts);
1237
+ }
1238
+
1239
+ // src/core/sugar.ts
1240
+ function state(initial, opts) {
1241
+ return node([], { ...opts, initial });
1242
+ }
1243
+ function producer(fn, opts) {
1244
+ return node(fn, { describeKind: "producer", ...opts });
1245
+ }
1246
+ function derived(deps, fn, opts) {
1247
+ return node(deps, fn, { describeKind: "derived", ...opts });
1248
+ }
1249
+
1250
+ // src/extra/cron.ts
1251
+ function parseField(field, min, max) {
1252
+ const result = /* @__PURE__ */ new Set();
1253
+ for (const part of field.split(",")) {
1254
+ const [range, stepStr] = part.split("/");
1255
+ const step = stepStr ? Number.parseInt(stepStr, 10) : 1;
1256
+ if (Number.isNaN(step) || step < 1) throw new Error(`Invalid cron step: ${part}`);
1257
+ let start;
1258
+ let end;
1259
+ if (range === "*") {
1260
+ start = min;
1261
+ end = max;
1262
+ } else if (range.includes("-")) {
1263
+ const [a, b] = range.split("-");
1264
+ start = Number.parseInt(a, 10);
1265
+ end = Number.parseInt(b, 10);
1266
+ } else {
1267
+ start = Number.parseInt(range, 10);
1268
+ end = start;
1269
+ }
1270
+ if (Number.isNaN(start) || Number.isNaN(end)) throw new Error(`Invalid cron field: ${field}`);
1271
+ if (start < min || end > max)
1272
+ throw new Error(`Cron field out of range: ${field} (${min}-${max})`);
1273
+ if (start > end) throw new Error(`Invalid cron range: ${start}-${end} in ${field}`);
1274
+ for (let i = start; i <= end; i += step) result.add(i);
1275
+ }
1276
+ return result;
1277
+ }
1278
+ function parseCron(expr) {
1279
+ const parts = expr.trim().split(/\s+/);
1280
+ if (parts.length !== 5) throw new Error(`Invalid cron: expected 5 fields, got ${parts.length}`);
1281
+ return {
1282
+ minutes: parseField(parts[0], 0, 59),
1283
+ hours: parseField(parts[1], 0, 23),
1284
+ daysOfMonth: parseField(parts[2], 1, 31),
1285
+ months: parseField(parts[3], 1, 12),
1286
+ daysOfWeek: parseField(parts[4], 0, 6)
1287
+ };
1288
+ }
1289
+ function matchesCron(schedule, date) {
1290
+ return schedule.minutes.has(date.getMinutes()) && schedule.hours.has(date.getHours()) && schedule.daysOfMonth.has(date.getDate()) && schedule.months.has(date.getMonth() + 1) && schedule.daysOfWeek.has(date.getDay());
1291
+ }
1292
+
1293
+ // src/extra/sources.ts
1294
+ function sourceOpts(opts) {
1295
+ return { describeKind: "producer", ...opts };
1296
+ }
1297
+ function fromTimer(ms, opts) {
1298
+ const { signal, period, ...rest } = opts ?? {};
1299
+ return producer((_d, a) => {
1300
+ let done = false;
1301
+ let count = 0;
1302
+ let t;
1303
+ let iv;
1304
+ const cleanup = () => {
1305
+ done = true;
1306
+ if (t !== void 0) clearTimeout(t);
1307
+ if (iv !== void 0) clearInterval(iv);
1308
+ signal?.removeEventListener("abort", onAbort);
1309
+ };
1310
+ const finish = () => {
1311
+ if (done) return;
1312
+ a.emit(count++);
1313
+ if (period != null) {
1314
+ iv = setInterval(() => {
1315
+ if (done) return;
1316
+ a.emit(count++);
1317
+ }, period);
1318
+ } else {
1319
+ done = true;
1320
+ signal?.removeEventListener("abort", onAbort);
1321
+ queueMicrotask(() => a.down([[COMPLETE]]));
1322
+ }
1323
+ };
1324
+ const onAbort = () => {
1325
+ if (done) return;
1326
+ cleanup();
1327
+ a.down([[ERROR, signal.reason]]);
1328
+ };
1329
+ if (signal?.aborted) {
1330
+ onAbort();
1331
+ return;
1332
+ }
1333
+ t = setTimeout(finish, ms);
1334
+ signal?.addEventListener("abort", onAbort, { once: true });
1335
+ return cleanup;
1336
+ }, sourceOpts(rest));
1337
+ }
1338
+ function fromCron(expr, opts) {
1339
+ const schedule = parseCron(expr);
1340
+ const { tickMs: tickOpt, output, ...rest } = opts ?? {};
1341
+ const tickMs = tickOpt ?? 6e4;
1342
+ const emitDate = output === "date";
1343
+ return producer(
1344
+ (_d, a) => {
1345
+ let lastFiredKey = -1;
1346
+ const check = () => {
1347
+ const now = /* @__PURE__ */ new Date();
1348
+ const key = now.getFullYear() * 1e8 + (now.getMonth() + 1) * 1e6 + now.getDate() * 1e4 + now.getHours() * 100 + now.getMinutes();
1349
+ if (key !== lastFiredKey && matchesCron(schedule, now)) {
1350
+ lastFiredKey = key;
1351
+ a.emit(emitDate ? now : wallClockNs());
1352
+ }
1353
+ };
1354
+ check();
1355
+ const id = setInterval(check, tickMs);
1356
+ return () => clearInterval(id);
1357
+ },
1358
+ { ...sourceOpts(rest), name: rest.name ?? `cron:${expr}` }
1359
+ );
1360
+ }
1361
+
1362
+ // src/compat/nestjs/explorer.ts
1363
+ var scheduleSeq = 0;
1364
+ var GraphReflyEventExplorer = class {
1365
+ constructor(graph, moduleRef) {
1366
+ this.graph = graph;
1367
+ this.moduleRef = moduleRef;
1368
+ }
1369
+ disposers = [];
1370
+ scheduleNodeNames = [];
1371
+ onModuleInit() {
1372
+ this.wireEvents();
1373
+ this.wireIntervals();
1374
+ this.wireCrons();
1375
+ this.wireCqrsCommands();
1376
+ this.wireCqrsEvents();
1377
+ this.wireCqrsQueries();
1378
+ this.wireCqrsSagas();
1379
+ }
1380
+ onModuleDestroy() {
1381
+ for (const dispose of this.disposers) dispose();
1382
+ this.disposers.length = 0;
1383
+ for (const name of this.scheduleNodeNames) {
1384
+ try {
1385
+ this.graph.remove(name);
1386
+ } catch {
1387
+ }
1388
+ }
1389
+ this.scheduleNodeNames.length = 0;
1390
+ }
1391
+ // -----------------------------------------------------------------------
1392
+ // @OnGraphEvent — reactive subscription via graph.observe()
1393
+ // -----------------------------------------------------------------------
1394
+ wireEvents() {
1395
+ for (const [ctor, metas] of EVENT_HANDLERS) {
1396
+ const instance = this.resolveInstance(ctor);
1397
+ if (!instance) continue;
1398
+ for (const meta of metas) {
1399
+ this.wireEventHandler(instance, meta);
1400
+ }
1401
+ }
1402
+ }
1403
+ wireEventHandler(instance, meta) {
1404
+ const method = instance[meta.methodKey];
1405
+ if (typeof method !== "function") return;
1406
+ const bound = method.bind(instance);
1407
+ const handle = this.observeNode(meta.nodeName);
1408
+ const unsub = handle.subscribe((msgs) => {
1409
+ for (const m of msgs) {
1410
+ if (m[0] === DATA) {
1411
+ bound(m[1]);
1412
+ }
1413
+ }
1414
+ });
1415
+ this.disposers.push(unsub);
1416
+ }
1417
+ // -----------------------------------------------------------------------
1418
+ // @GraphInterval — reactive via fromTimer central timer primitive
1419
+ // -----------------------------------------------------------------------
1420
+ wireIntervals() {
1421
+ for (const [ctor, metas] of INTERVAL_HANDLERS) {
1422
+ const instance = this.resolveInstance(ctor);
1423
+ if (!instance) continue;
1424
+ for (const meta of metas) {
1425
+ this.wireIntervalHandler(instance, ctor, meta);
1426
+ }
1427
+ }
1428
+ }
1429
+ wireIntervalHandler(instance, ctor, meta) {
1430
+ const method = instance[meta.methodKey];
1431
+ if (typeof method !== "function") return;
1432
+ const bound = method.bind(instance);
1433
+ const className = ctor.name ?? "anonymous";
1434
+ const nodeName = `__schedule__.${className}.${String(meta.methodKey)}.${scheduleSeq++}`;
1435
+ const timerNode = fromTimer(meta.ms, { period: meta.ms, name: nodeName });
1436
+ this.graph.add(nodeName, timerNode);
1437
+ this.scheduleNodeNames.push(nodeName);
1438
+ const handle = this.observeNode(nodeName);
1439
+ const unsub = handle.subscribe((msgs) => {
1440
+ for (const m of msgs) {
1441
+ if (m[0] === DATA) bound(m[1]);
1442
+ }
1443
+ });
1444
+ this.disposers.push(unsub);
1445
+ }
1446
+ // -----------------------------------------------------------------------
1447
+ // @GraphCron — reactive via fromCron central timer primitive
1448
+ // -----------------------------------------------------------------------
1449
+ wireCrons() {
1450
+ for (const [ctor, metas] of CRON_HANDLERS) {
1451
+ const instance = this.resolveInstance(ctor);
1452
+ if (!instance) continue;
1453
+ for (const meta of metas) {
1454
+ this.wireCronHandler(instance, ctor, meta);
1455
+ }
1456
+ }
1457
+ }
1458
+ wireCronHandler(instance, ctor, meta) {
1459
+ const method = instance[meta.methodKey];
1460
+ if (typeof method !== "function") return;
1461
+ const bound = method.bind(instance);
1462
+ const className = ctor.name ?? "anonymous";
1463
+ const nodeName = `__schedule__.${className}.${String(meta.methodKey)}.${scheduleSeq++}`;
1464
+ const cronNode = fromCron(meta.expr, { name: nodeName });
1465
+ this.graph.add(nodeName, cronNode);
1466
+ this.scheduleNodeNames.push(nodeName);
1467
+ const handle = this.observeNode(nodeName);
1468
+ const unsub = handle.subscribe((msgs) => {
1469
+ for (const m of msgs) {
1470
+ if (m[0] === DATA) bound(m[1]);
1471
+ }
1472
+ });
1473
+ this.disposers.push(unsub);
1474
+ }
1475
+ // -----------------------------------------------------------------------
1476
+ // @CommandHandler — register method as CqrsGraph command handler
1477
+ // -----------------------------------------------------------------------
1478
+ wireCqrsCommands() {
1479
+ for (const [ctor, metas] of COMMAND_HANDLERS) {
1480
+ const instance = this.resolveInstance(ctor);
1481
+ if (!instance) continue;
1482
+ for (const meta of metas) {
1483
+ this.wireCqrsCommand(instance, meta);
1484
+ }
1485
+ }
1486
+ }
1487
+ wireCqrsCommand(instance, meta) {
1488
+ const method = instance[meta.methodKey];
1489
+ if (typeof method !== "function") return;
1490
+ const bound = method.bind(instance);
1491
+ const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
1492
+ if (!cqrsGraph) return;
1493
+ cqrsGraph.command(meta.commandName, bound);
1494
+ }
1495
+ // -----------------------------------------------------------------------
1496
+ // @EventHandler — subscribe method to CQRS event stream
1497
+ // -----------------------------------------------------------------------
1498
+ wireCqrsEvents() {
1499
+ for (const [ctor, metas] of CQRS_EVENT_HANDLERS) {
1500
+ const instance = this.resolveInstance(ctor);
1501
+ if (!instance) continue;
1502
+ for (const meta of metas) {
1503
+ this.wireCqrsEventHandler(instance, meta);
1504
+ }
1505
+ }
1506
+ }
1507
+ wireCqrsEventHandler(instance, meta) {
1508
+ const method = instance[meta.methodKey];
1509
+ if (typeof method !== "function") return;
1510
+ const bound = method.bind(instance);
1511
+ const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
1512
+ if (!cqrsGraph) return;
1513
+ cqrsGraph.event(meta.eventName);
1514
+ const eventNode = cqrsGraph.resolve(meta.eventName);
1515
+ const currentSnap = eventNode.get();
1516
+ const existingEntries = currentSnap?.value?.entries;
1517
+ let lastSeq = existingEntries && existingEntries.length > 0 ? existingEntries[existingEntries.length - 1].seq : 0;
1518
+ const handle = this.observeNodeOn(cqrsGraph, meta.eventName);
1519
+ const unsub = handle.subscribe((msgs) => {
1520
+ for (const m of msgs) {
1521
+ if (m[0] === DATA) {
1522
+ const snap = m[1];
1523
+ for (const entry of snap.value.entries) {
1524
+ if (entry.seq > lastSeq) {
1525
+ bound(entry);
1526
+ lastSeq = entry.seq;
1527
+ }
1528
+ }
1529
+ }
1530
+ }
1531
+ });
1532
+ this.disposers.push(unsub);
1533
+ }
1534
+ // -----------------------------------------------------------------------
1535
+ // @QueryHandler — subscribe method to CQRS projection changes
1536
+ // -----------------------------------------------------------------------
1537
+ wireCqrsQueries() {
1538
+ for (const [ctor, metas] of QUERY_HANDLERS) {
1539
+ const instance = this.resolveInstance(ctor);
1540
+ if (!instance) continue;
1541
+ for (const meta of metas) {
1542
+ this.wireCqrsQuery(instance, meta);
1543
+ }
1544
+ }
1545
+ }
1546
+ wireCqrsQuery(instance, meta) {
1547
+ const method = instance[meta.methodKey];
1548
+ if (typeof method !== "function") return;
1549
+ const bound = method.bind(instance);
1550
+ const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
1551
+ if (!cqrsGraph) return;
1552
+ const handle = this.observeNodeOn(cqrsGraph, meta.projectionName);
1553
+ const unsub = handle.subscribe((msgs) => {
1554
+ for (const m of msgs) {
1555
+ if (m[0] === DATA) {
1556
+ bound(m[1]);
1557
+ }
1558
+ }
1559
+ });
1560
+ this.disposers.push(unsub);
1561
+ }
1562
+ // -----------------------------------------------------------------------
1563
+ // @SagaHandler — register method as CqrsGraph saga (subgraph side effect)
1564
+ // -----------------------------------------------------------------------
1565
+ wireCqrsSagas() {
1566
+ for (const [ctor, metas] of SAGA_HANDLERS) {
1567
+ const instance = this.resolveInstance(ctor);
1568
+ if (!instance) continue;
1569
+ for (const meta of metas) {
1570
+ this.wireCqrsSaga(instance, meta);
1571
+ }
1572
+ }
1573
+ }
1574
+ wireCqrsSaga(instance, meta) {
1575
+ const method = instance[meta.methodKey];
1576
+ if (typeof method !== "function") return;
1577
+ const bound = method.bind(instance);
1578
+ const cqrsGraph = this.resolveCqrsGraph(meta.cqrsName);
1579
+ if (!cqrsGraph) return;
1580
+ cqrsGraph.saga(meta.sagaName, meta.eventNames, bound);
1581
+ }
1582
+ // -----------------------------------------------------------------------
1583
+ // Helpers
1584
+ // -----------------------------------------------------------------------
1585
+ observeNode(name) {
1586
+ return this.graph.observe(name);
1587
+ }
1588
+ observeNodeOn(graph, name) {
1589
+ return graph.observe(name);
1590
+ }
1591
+ resolveCqrsGraph(name) {
1592
+ try {
1593
+ return this.moduleRef.get(getGraphToken(name), { strict: false });
1594
+ } catch {
1595
+ console.warn(
1596
+ `[GraphReFly] CqrsGraph "${name}" not found in DI \u2014 did you import GraphReflyModule.forCqrs({ name: "${name}" })?`
1597
+ );
1598
+ return null;
1599
+ }
1600
+ }
1601
+ resolveInstance(ctor) {
1602
+ try {
1603
+ return this.moduleRef.get(ctor, { strict: false });
1604
+ } catch {
1605
+ return null;
1606
+ }
1607
+ }
1608
+ };
1609
+
1610
+ // src/extra/backpressure.ts
1611
+ var nextLockId = 0;
1612
+ function createWatermarkController(sendUp, opts) {
1613
+ if (opts.highWaterMark < 1) throw new RangeError("highWaterMark must be >= 1");
1614
+ if (opts.lowWaterMark < 0) throw new RangeError("lowWaterMark must be >= 0");
1615
+ if (opts.lowWaterMark >= opts.highWaterMark)
1616
+ throw new RangeError("lowWaterMark must be < highWaterMark");
1617
+ const lockId = /* @__PURE__ */ Symbol(`bp-${++nextLockId}`);
1618
+ let pending = 0;
1619
+ let paused = false;
1620
+ return {
1621
+ onEnqueue() {
1622
+ pending += 1;
1623
+ if (!paused && pending >= opts.highWaterMark) {
1624
+ paused = true;
1625
+ sendUp([[PAUSE, lockId]]);
1626
+ return true;
1627
+ }
1628
+ return false;
1629
+ },
1630
+ onDequeue() {
1631
+ if (pending > 0) pending -= 1;
1632
+ if (paused && pending <= opts.lowWaterMark) {
1633
+ paused = false;
1634
+ sendUp([[RESUME, lockId]]);
1635
+ return true;
1636
+ }
1637
+ return false;
1638
+ },
1639
+ get pending() {
1640
+ return pending;
1641
+ },
1642
+ get paused() {
1643
+ return paused;
1644
+ },
1645
+ dispose() {
1646
+ if (paused) {
1647
+ paused = false;
1648
+ sendUp([[RESUME, lockId]]);
1649
+ }
1650
+ }
1651
+ };
1652
+ }
1653
+
1654
+ // src/compat/nestjs/gateway.ts
1655
+ function observeSSE(graph, path, opts) {
1656
+ const { actor, serialize = defaultSerialize, keepAliveMs, signal } = opts ?? {};
1657
+ const encoder = new TextEncoder();
1658
+ let stop;
1659
+ const useBackpressure = opts?.highWaterMark != null;
1660
+ let wm;
1661
+ let pullResolve;
1662
+ const taggedBuf = [];
1663
+ let closed = false;
1664
+ return new ReadableStream({
1665
+ start(controller) {
1666
+ let keepAlive;
1667
+ let unsub = () => {
1668
+ };
1669
+ const close = () => {
1670
+ if (closed) return;
1671
+ closed = true;
1672
+ if (keepAlive !== void 0) clearInterval(keepAlive);
1673
+ signal?.removeEventListener("abort", onAbort);
1674
+ unsub();
1675
+ wm?.dispose();
1676
+ pullResolve?.();
1677
+ pullResolve = void 0;
1678
+ for (const entry of taggedBuf) controller.enqueue(entry.frame);
1679
+ taggedBuf.length = 0;
1680
+ controller.close();
1681
+ };
1682
+ stop = close;
1683
+ const onAbort = () => close();
1684
+ const handle = graph.observe(path, { actor });
1685
+ if (useBackpressure) {
1686
+ wm = createWatermarkController((msgs) => handle.up(msgs), {
1687
+ highWaterMark: opts.highWaterMark,
1688
+ lowWaterMark: opts.lowWaterMark ?? Math.floor(opts.highWaterMark / 2)
1689
+ });
1690
+ }
1691
+ unsub = handle.subscribe((msgs) => {
1692
+ for (const msg of msgs) {
1693
+ if (closed) return;
1694
+ const t = msg[0];
1695
+ if (t === DATA) {
1696
+ const frame = encoder.encode(sseFrame("data", serialize(msg[1])));
1697
+ if (useBackpressure) {
1698
+ taggedBuf.push({ frame, counted: true });
1699
+ wm.onEnqueue();
1700
+ pullResolve?.();
1701
+ pullResolve = void 0;
1702
+ } else {
1703
+ controller.enqueue(frame);
1704
+ }
1705
+ } else if (t === ERROR) {
1706
+ const frame = encoder.encode(sseFrame("error", serialize(msg[1])));
1707
+ if (useBackpressure) {
1708
+ taggedBuf.push({ frame, counted: false });
1709
+ pullResolve?.();
1710
+ pullResolve = void 0;
1711
+ } else {
1712
+ controller.enqueue(frame);
1713
+ }
1714
+ close();
1715
+ return;
1716
+ } else if (t === COMPLETE || t === TEARDOWN) {
1717
+ if (t === COMPLETE) {
1718
+ const frame = encoder.encode(sseFrame("complete"));
1719
+ if (useBackpressure) {
1720
+ taggedBuf.push({ frame, counted: false });
1721
+ pullResolve?.();
1722
+ pullResolve = void 0;
1723
+ } else {
1724
+ controller.enqueue(frame);
1725
+ }
1726
+ }
1727
+ close();
1728
+ return;
1729
+ }
1730
+ }
1731
+ });
1732
+ if (keepAliveMs !== void 0 && keepAliveMs > 0) {
1733
+ keepAlive = setInterval(() => {
1734
+ if (closed) return;
1735
+ if (useBackpressure) {
1736
+ taggedBuf.push({ frame: encoder.encode(": keepalive\n\n"), counted: false });
1737
+ pullResolve?.();
1738
+ pullResolve = void 0;
1739
+ } else {
1740
+ controller.enqueue(encoder.encode(": keepalive\n\n"));
1741
+ }
1742
+ }, keepAliveMs);
1743
+ }
1744
+ if (signal?.aborted) onAbort();
1745
+ else signal?.addEventListener("abort", onAbort, { once: true });
1746
+ },
1747
+ pull(controller) {
1748
+ if (!useBackpressure) return;
1749
+ if (closed) return;
1750
+ if (taggedBuf.length > 0) {
1751
+ const entry = taggedBuf.shift();
1752
+ controller.enqueue(entry.frame);
1753
+ if (entry.counted) wm.onDequeue();
1754
+ return;
1755
+ }
1756
+ return new Promise((resolve) => {
1757
+ pullResolve = resolve;
1758
+ });
1759
+ },
1760
+ cancel() {
1761
+ try {
1762
+ stop?.();
1763
+ } catch {
1764
+ }
1765
+ }
1766
+ });
1767
+ }
1768
+ function observeSubscription(graph, path, opts) {
1769
+ const { actor, filter } = opts ?? {};
1770
+ const queue = [];
1771
+ const waiters = [];
1772
+ let disposed = false;
1773
+ const handle = graph.observe(path, { actor });
1774
+ const wm = opts?.highWaterMark != null ? createWatermarkController((msgs) => handle.up(msgs), {
1775
+ highWaterMark: opts.highWaterMark,
1776
+ lowWaterMark: opts.lowWaterMark ?? Math.floor(opts.highWaterMark / 2)
1777
+ }) : void 0;
1778
+ const dispose = () => {
1779
+ if (disposed) return;
1780
+ disposed = true;
1781
+ wm?.dispose();
1782
+ unsub();
1783
+ };
1784
+ const push = (item) => {
1785
+ if (disposed) return;
1786
+ if (waiters.length > 0) {
1787
+ const w = waiters.shift();
1788
+ if (item.done && item.error) w.reject(item.error);
1789
+ else if (item.done) w.resolve({ done: true, value: void 0 });
1790
+ else w.resolve({ done: false, value: item.value });
1791
+ } else {
1792
+ queue.push(item);
1793
+ if (!item.done) wm?.onEnqueue();
1794
+ }
1795
+ };
1796
+ const unsub = handle.subscribe((msgs) => {
1797
+ for (const msg of msgs) {
1798
+ const t = msg[0];
1799
+ if (t === DATA) {
1800
+ const value = msg[1];
1801
+ if (filter && !filter(value)) continue;
1802
+ push({ done: false, value });
1803
+ } else if (t === ERROR) {
1804
+ const err = msg[1] instanceof Error ? msg[1] : new Error(String(msg[1]));
1805
+ push({ done: true, error: err });
1806
+ dispose();
1807
+ return;
1808
+ } else if (t === COMPLETE || t === TEARDOWN) {
1809
+ push({ done: true });
1810
+ dispose();
1811
+ return;
1812
+ }
1813
+ }
1814
+ });
1815
+ const iterator = {
1816
+ next() {
1817
+ if (queue.length > 0) {
1818
+ const item = queue.shift();
1819
+ if (!item.done) wm?.onDequeue();
1820
+ if (item.done && item.error) return Promise.reject(item.error);
1821
+ return Promise.resolve(
1822
+ item.done ? { done: true, value: void 0 } : { done: false, value: item.value }
1823
+ );
1824
+ }
1825
+ if (disposed) return Promise.resolve({ done: true, value: void 0 });
1826
+ return new Promise((resolve, reject) => {
1827
+ waiters.push({ resolve, reject });
1828
+ });
1829
+ },
1830
+ return() {
1831
+ dispose();
1832
+ for (const w of waiters) w.resolve({ done: true, value: void 0 });
1833
+ waiters.length = 0;
1834
+ return Promise.resolve({ done: true, value: void 0 });
1835
+ },
1836
+ throw(err) {
1837
+ dispose();
1838
+ return Promise.reject(err);
1839
+ },
1840
+ [Symbol.asyncIterator]() {
1841
+ return this;
1842
+ }
1843
+ };
1844
+ return iterator;
1845
+ }
1846
+ var ObserveGateway = class {
1847
+ constructor(graph, opts) {
1848
+ this.graph = graph;
1849
+ this.extractActor = opts?.extractActor ?? (() => void 0);
1850
+ this.parse = opts?.parse ?? defaultParseCommand;
1851
+ this.highWaterMark = opts?.highWaterMark;
1852
+ this.lowWaterMark = opts?.lowWaterMark;
1853
+ }
1854
+ clients = /* @__PURE__ */ new Map();
1855
+ extractActor;
1856
+ parse;
1857
+ highWaterMark;
1858
+ lowWaterMark;
1859
+ /**
1860
+ * Register a new client. Call from `handleConnection`.
1861
+ */
1862
+ handleConnection(client) {
1863
+ if (!this.clients.has(client)) {
1864
+ this.clients.set(client, /* @__PURE__ */ new Map());
1865
+ }
1866
+ }
1867
+ /**
1868
+ * Unregister a client and dispose all its subscriptions. Call from `handleDisconnect`.
1869
+ */
1870
+ handleDisconnect(client) {
1871
+ const subs = this.clients.get(client);
1872
+ if (!subs) return;
1873
+ for (const entry of subs.values()) {
1874
+ entry.wm?.dispose();
1875
+ entry.unsub();
1876
+ }
1877
+ this.clients.delete(client);
1878
+ }
1879
+ /**
1880
+ * Handle an incoming client message (subscribe/unsubscribe/ack command).
1881
+ *
1882
+ * @param client - The WebSocket client reference.
1883
+ * @param raw - Raw message data (string or parsed object).
1884
+ * @param send - Function to send a message back to the client.
1885
+ * Defaults to `client.send(JSON.stringify(msg))`.
1886
+ */
1887
+ handleMessage(client, raw, send) {
1888
+ const sender = send ?? defaultSend.bind(null, client);
1889
+ let cmd;
1890
+ try {
1891
+ cmd = typeof raw === "string" ? this.parse(raw) : raw;
1892
+ } catch {
1893
+ sender({ type: "err", message: "invalid command" });
1894
+ return;
1895
+ }
1896
+ if (cmd.type === "subscribe") {
1897
+ this.subscribe(client, cmd.path, sender);
1898
+ } else if (cmd.type === "unsubscribe") {
1899
+ this.unsubscribe(client, cmd.path, sender);
1900
+ } else if (cmd.type === "ack") {
1901
+ this.ack(client, cmd.path, cmd.count ?? 1);
1902
+ } else {
1903
+ sender({ type: "err", message: `unknown command type: ${cmd.type}` });
1904
+ }
1905
+ }
1906
+ /**
1907
+ * Number of active subscriptions for a client. Useful for tests.
1908
+ */
1909
+ subscriptionCount(client) {
1910
+ return this.clients.get(client)?.size ?? 0;
1911
+ }
1912
+ /**
1913
+ * Dispose all clients and subscriptions.
1914
+ */
1915
+ destroy() {
1916
+ for (const [client] of this.clients) {
1917
+ this.handleDisconnect(client);
1918
+ }
1919
+ }
1920
+ // -----------------------------------------------------------------------
1921
+ // Internal
1922
+ // -----------------------------------------------------------------------
1923
+ subscribe(client, path, send) {
1924
+ let subs = this.clients.get(client);
1925
+ if (!subs) {
1926
+ subs = /* @__PURE__ */ new Map();
1927
+ this.clients.set(client, subs);
1928
+ }
1929
+ if (subs.has(path)) {
1930
+ send({ type: "subscribed", path });
1931
+ return;
1932
+ }
1933
+ const actor = this.extractActor(client);
1934
+ let handle;
1935
+ try {
1936
+ handle = this.graph.observe(path, { actor });
1937
+ } catch (err) {
1938
+ const message = err instanceof Error ? err.message : String(err);
1939
+ send({ type: "err", message });
1940
+ return;
1941
+ }
1942
+ const wm = this.highWaterMark != null ? createWatermarkController((msgs) => handle.up(msgs), {
1943
+ highWaterMark: this.highWaterMark,
1944
+ lowWaterMark: this.lowWaterMark ?? Math.floor(this.highWaterMark / 2)
1945
+ }) : void 0;
1946
+ const cleanup = () => {
1947
+ wm?.dispose();
1948
+ unsub();
1949
+ subs.delete(path);
1950
+ };
1951
+ const unsub = handle.subscribe((msgs) => {
1952
+ for (const msg of msgs) {
1953
+ const t = msg[0];
1954
+ if (t === DATA) {
1955
+ wm?.onEnqueue();
1956
+ trySend(send, { type: "data", path, value: msg[1] });
1957
+ } else if (t === ERROR) {
1958
+ const errMsg = msg[1] instanceof Error ? msg[1].message : String(msg[1]);
1959
+ trySend(send, { type: "error", path, error: errMsg });
1960
+ cleanup();
1961
+ return;
1962
+ } else if (t === COMPLETE || t === TEARDOWN) {
1963
+ trySend(send, { type: "complete", path });
1964
+ cleanup();
1965
+ return;
1966
+ }
1967
+ }
1968
+ });
1969
+ subs.set(path, { unsub, wm });
1970
+ send({ type: "subscribed", path });
1971
+ }
1972
+ unsubscribe(client, path, send) {
1973
+ const subs = this.clients.get(client);
1974
+ const entry = subs?.get(path);
1975
+ if (entry) {
1976
+ entry.wm?.dispose();
1977
+ entry.unsub();
1978
+ subs.delete(path);
1979
+ }
1980
+ send({ type: "unsubscribed", path });
1981
+ }
1982
+ ack(client, path, count) {
1983
+ const entry = this.clients.get(client)?.get(path);
1984
+ if (!entry?.wm) return;
1985
+ const n = Math.min(Math.max(0, Math.floor(count)), 1024);
1986
+ for (let i = 0; i < n; i++) entry.wm.onDequeue();
1987
+ }
1988
+ };
1989
+ function defaultSerialize(value) {
1990
+ if (value instanceof Error) return value.message;
1991
+ try {
1992
+ return JSON.stringify(value);
1993
+ } catch {
1994
+ return String(value);
1995
+ }
1996
+ }
1997
+ function sseFrame(event, data) {
1998
+ let frame = `event: ${event}
1999
+ `;
2000
+ if (data !== void 0) {
2001
+ for (const line of data.split("\n")) {
2002
+ frame += `data: ${line}
2003
+ `;
2004
+ }
2005
+ }
2006
+ frame += "\n";
2007
+ return frame;
2008
+ }
2009
+ function defaultParseCommand(data) {
2010
+ return JSON.parse(data);
2011
+ }
2012
+ function defaultSend(client, msg) {
2013
+ try {
2014
+ client.send(JSON.stringify(msg));
2015
+ } catch {
2016
+ }
2017
+ }
2018
+ function trySend(send, msg) {
2019
+ try {
2020
+ send(msg);
2021
+ } catch {
2022
+ }
2023
+ }
2024
+
2025
+ // src/compat/nestjs/guard.ts
2026
+ var ACTOR_KEY = "graphReflyActor";
2027
+ function fromJwtPayload(mapping) {
2028
+ return (context) => {
2029
+ const req = context.switchToHttp().getRequest();
2030
+ const user = req?.user;
2031
+ if (user == null) return void 0;
2032
+ if (mapping) return mapping(user);
2033
+ return user;
2034
+ };
2035
+ }
2036
+ function fromHeader(headerName = "x-graphrefly-actor") {
2037
+ return (context) => {
2038
+ const req = context.switchToHttp().getRequest();
2039
+ const raw = req?.headers?.[headerName.toLowerCase()];
2040
+ if (typeof raw !== "string" || raw.length === 0) return void 0;
2041
+ try {
2042
+ return JSON.parse(raw);
2043
+ } catch {
2044
+ return void 0;
2045
+ }
2046
+ };
2047
+ }
2048
+ function getActor(req) {
2049
+ const actor = req?.[ACTOR_KEY];
2050
+ return actor != null ? normalizeActor(actor) : DEFAULT_ACTOR;
2051
+ }
2052
+ var GraphReflyGuardImpl = class {
2053
+ constructor(extractor) {
2054
+ this.extractor = extractor;
2055
+ }
2056
+ canActivate(context) {
2057
+ const actor = normalizeActor(this.extractor(context));
2058
+ const req = context.switchToHttp().getRequest();
2059
+ if (req != null) {
2060
+ req[ACTOR_KEY] = actor;
2061
+ }
2062
+ return true;
2063
+ }
2064
+ };
2065
+ function GraphReflyGuard(extractor) {
2066
+ return new GraphReflyGuardImpl(extractor ?? fromJwtPayload());
2067
+ }
2068
+
2069
+ // src/compat/nestjs/module.ts
2070
+ var import_common2 = require("@nestjs/common");
2071
+ var import_core2 = require("@nestjs/core");
2072
+
2073
+ // src/core/dynamic-node.ts
2074
+ var DynamicNodeImpl = class {
2075
+ _optsName;
2076
+ _registryName;
2077
+ _describeKind;
2078
+ meta;
2079
+ _fn;
2080
+ _equals;
2081
+ _resubscribable;
2082
+ _resetOnTeardown;
2083
+ _autoComplete;
2084
+ _onMessage;
2085
+ _onResubscribe;
2086
+ /** @internal — read by {@link describeNode} for `accessHintForGuard`. */
2087
+ _guard;
2088
+ _lastMutation;
2089
+ _inspectorHook;
2090
+ // Sink tracking
2091
+ _sinkCount = 0;
2092
+ _singleDepSinkCount = 0;
2093
+ _singleDepSinks = /* @__PURE__ */ new WeakSet();
2094
+ // Actions object (for onMessage handler)
2095
+ _actions;
2096
+ _boundEmitToSinks;
2097
+ // Mutable state
2098
+ _cached;
2099
+ _status = "disconnected";
2100
+ _terminal = false;
2101
+ _connected = false;
2102
+ _rewiring = false;
2103
+ // re-entrancy guard
2104
+ // Dynamic deps tracking
2105
+ _deps = [];
2106
+ _depUnsubs = [];
2107
+ _depIndexMap = /* @__PURE__ */ new Map();
2108
+ // node → index in _deps
2109
+ _dirtyBits = /* @__PURE__ */ new Set();
2110
+ _settledBits = /* @__PURE__ */ new Set();
2111
+ _completeBits = /* @__PURE__ */ new Set();
2112
+ // Sinks
2113
+ _sinks = null;
2114
+ constructor(fn, opts) {
2115
+ this._fn = fn;
2116
+ this._optsName = opts.name;
2117
+ this._describeKind = opts.describeKind;
2118
+ this._equals = opts.equals ?? Object.is;
2119
+ this._resubscribable = opts.resubscribable ?? false;
2120
+ this._resetOnTeardown = opts.resetOnTeardown ?? false;
2121
+ this._autoComplete = opts.completeWhenDepsComplete ?? true;
2122
+ this._onMessage = opts.onMessage;
2123
+ this._onResubscribe = opts.onResubscribe;
2124
+ this._guard = opts.guard;
2125
+ this._inspectorHook = void 0;
2126
+ const meta = {};
2127
+ for (const [k, v] of Object.entries(opts.meta ?? {})) {
2128
+ meta[k] = node({
2129
+ initial: v,
2130
+ name: `${opts.name ?? "dynamicNode"}:meta:${k}`,
2131
+ describeKind: "state",
2132
+ ...opts.guard != null ? { guard: opts.guard } : {}
2133
+ });
2134
+ }
2135
+ Object.freeze(meta);
2136
+ this.meta = meta;
2137
+ const self = this;
2138
+ this._actions = {
2139
+ down(messages) {
2140
+ self._downInternal(messages);
2141
+ },
2142
+ emit(value) {
2143
+ self._emitAutoValue(value);
2144
+ },
2145
+ up(messages) {
2146
+ for (const dep of self._deps) {
2147
+ dep.up?.(messages, { internal: true });
2148
+ }
2149
+ }
2150
+ };
2151
+ this._boundEmitToSinks = this._emitToSinks.bind(this);
2152
+ }
2153
+ get name() {
2154
+ return this._registryName ?? this._optsName;
2155
+ }
2156
+ /** @internal */
2157
+ _assignRegistryName(localName) {
2158
+ if (this._optsName !== void 0 || this._registryName !== void 0) return;
2159
+ this._registryName = localName;
2160
+ }
2161
+ /**
2162
+ * @internal Attach/remove inspector hook for graph-level observability.
2163
+ * Returns a disposer that restores the previous hook.
2164
+ */
2165
+ _setInspectorHook(hook) {
2166
+ const prev = this._inspectorHook;
2167
+ this._inspectorHook = hook;
2168
+ return () => {
2169
+ if (this._inspectorHook === hook) {
2170
+ this._inspectorHook = prev;
2171
+ }
2172
+ };
2173
+ }
2174
+ get status() {
2175
+ return this._status;
2176
+ }
2177
+ get lastMutation() {
2178
+ return this._lastMutation;
2179
+ }
2180
+ /** Versioning not yet supported on DynamicNodeImpl. */
2181
+ get v() {
2182
+ return void 0;
2183
+ }
2184
+ hasGuard() {
2185
+ return this._guard != null;
2186
+ }
2187
+ allowsObserve(actor) {
2188
+ if (this._guard == null) return true;
2189
+ return this._guard(normalizeActor(actor), "observe");
2190
+ }
2191
+ get() {
2192
+ return this._cached;
2193
+ }
2194
+ down(messages, options) {
2195
+ if (messages.length === 0) return;
2196
+ if (!options?.internal && this._guard != null) {
2197
+ const actor = normalizeActor(options?.actor);
2198
+ const delivery = options?.delivery ?? "write";
2199
+ const action = delivery === "signal" ? "signal" : "write";
2200
+ if (!this._guard(actor, action)) {
2201
+ throw new GuardDenied({ actor, action, nodeName: this.name });
2202
+ }
2203
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
2204
+ }
2205
+ this._downInternal(messages);
2206
+ }
2207
+ _downInternal(messages) {
2208
+ if (messages.length === 0) return;
2209
+ let sinkMessages = messages;
2210
+ if (this._terminal && !this._resubscribable) {
2211
+ const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
2212
+ if (pass.length === 0) return;
2213
+ sinkMessages = pass;
2214
+ }
2215
+ this._handleLocalLifecycle(sinkMessages);
2216
+ if (this._canSkipDirty()) {
2217
+ let hasPhase2 = false;
2218
+ for (let i = 0; i < sinkMessages.length; i++) {
2219
+ const t = sinkMessages[i][0];
2220
+ if (t === DATA || t === RESOLVED) {
2221
+ hasPhase2 = true;
2222
+ break;
2223
+ }
2224
+ }
2225
+ if (hasPhase2) {
2226
+ const filtered = [];
2227
+ for (let i = 0; i < sinkMessages.length; i++) {
2228
+ if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
2229
+ }
2230
+ if (filtered.length > 0) {
2231
+ emitWithBatch(this._boundEmitToSinks, filtered);
2232
+ }
2233
+ return;
2234
+ }
2235
+ }
2236
+ emitWithBatch(this._boundEmitToSinks, sinkMessages);
2237
+ }
2238
+ _canSkipDirty() {
2239
+ return this._sinkCount === 1 && this._singleDepSinkCount === 1;
2240
+ }
2241
+ subscribe(sink, hints) {
2242
+ if (hints?.actor != null && this._guard != null) {
2243
+ const actor = normalizeActor(hints.actor);
2244
+ if (!this._guard(actor, "observe")) {
2245
+ throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
2246
+ }
2247
+ }
2248
+ if (this._terminal && this._resubscribable) {
2249
+ this._terminal = false;
2250
+ this._status = "disconnected";
2251
+ this._onResubscribe?.();
2252
+ }
2253
+ this._sinkCount += 1;
2254
+ if (hints?.singleDep) {
2255
+ this._singleDepSinkCount += 1;
2256
+ this._singleDepSinks.add(sink);
2257
+ }
2258
+ if (this._sinks == null) {
2259
+ this._sinks = sink;
2260
+ } else if (typeof this._sinks === "function") {
2261
+ this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
2262
+ } else {
2263
+ this._sinks.add(sink);
2264
+ }
2265
+ if (!this._connected) {
2266
+ this._connect();
2267
+ }
2268
+ let removed = false;
2269
+ return () => {
2270
+ if (removed) return;
2271
+ removed = true;
2272
+ this._sinkCount -= 1;
2273
+ if (this._singleDepSinks.has(sink)) {
2274
+ this._singleDepSinkCount -= 1;
2275
+ this._singleDepSinks.delete(sink);
2276
+ }
2277
+ if (this._sinks == null) return;
2278
+ if (typeof this._sinks === "function") {
2279
+ if (this._sinks === sink) this._sinks = null;
2280
+ } else {
2281
+ this._sinks.delete(sink);
2282
+ if (this._sinks.size === 1) {
2283
+ const [only] = this._sinks;
2284
+ this._sinks = only;
2285
+ } else if (this._sinks.size === 0) {
2286
+ this._sinks = null;
2287
+ }
2288
+ }
2289
+ if (this._sinks == null) {
2290
+ this._disconnect();
2291
+ }
2292
+ };
2293
+ }
2294
+ up(messages, options) {
2295
+ if (this._deps.length === 0) return;
2296
+ if (!options?.internal && this._guard != null) {
2297
+ const actor = normalizeActor(options?.actor);
2298
+ if (!this._guard(actor, "write")) {
2299
+ throw new GuardDenied({ actor, action: "write", nodeName: this.name });
2300
+ }
2301
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
2302
+ }
2303
+ for (const dep of this._deps) {
2304
+ dep.up?.(messages, options);
2305
+ }
2306
+ }
2307
+ unsubscribe() {
2308
+ this._disconnect();
2309
+ }
2310
+ // --- Private methods ---
2311
+ _emitToSinks(messages) {
2312
+ if (this._sinks == null) return;
2313
+ if (typeof this._sinks === "function") {
2314
+ this._sinks(messages);
2315
+ return;
2316
+ }
2317
+ const snapshot = [...this._sinks];
2318
+ for (const sink of snapshot) {
2319
+ sink(messages);
2320
+ }
2321
+ }
2322
+ _handleLocalLifecycle(messages) {
2323
+ for (const m of messages) {
2324
+ const t = m[0];
2325
+ if (t === DATA) this._cached = m[1];
2326
+ if (t === INVALIDATE) {
2327
+ this._cached = void 0;
2328
+ }
2329
+ if (t === DATA || t === RESOLVED) {
2330
+ this._status = "settled";
2331
+ } else if (t === DIRTY) {
2332
+ this._status = "dirty";
2333
+ } else if (t === COMPLETE) {
2334
+ this._status = "completed";
2335
+ this._terminal = true;
2336
+ } else if (t === ERROR) {
2337
+ this._status = "errored";
2338
+ this._terminal = true;
2339
+ }
2340
+ if (t === TEARDOWN) {
2341
+ if (this._resetOnTeardown) this._cached = void 0;
2342
+ try {
2343
+ this._propagateToMeta(t);
2344
+ } finally {
2345
+ this._disconnect();
2346
+ }
2347
+ }
2348
+ if (t !== TEARDOWN && propagatesToMeta(t)) {
2349
+ this._propagateToMeta(t);
2350
+ }
2351
+ }
2352
+ }
2353
+ /** Propagate a signal to all companion meta nodes (best-effort). */
2354
+ _propagateToMeta(t) {
2355
+ for (const metaNode of Object.values(this.meta)) {
2356
+ try {
2357
+ metaNode.down([[t]], { internal: true });
2358
+ } catch {
2359
+ }
2360
+ }
2361
+ }
2362
+ _emitAutoValue(value) {
2363
+ const wasDirty = this._status === "dirty";
2364
+ const unchanged = this._equals(this._cached, value);
2365
+ if (unchanged) {
2366
+ this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
2367
+ return;
2368
+ }
2369
+ this._cached = value;
2370
+ this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
2371
+ }
2372
+ _connect() {
2373
+ if (this._connected) return;
2374
+ this._connected = true;
2375
+ this._status = "settled";
2376
+ this._dirtyBits.clear();
2377
+ this._settledBits.clear();
2378
+ this._completeBits.clear();
2379
+ this._runFn();
2380
+ }
2381
+ _disconnect() {
2382
+ if (!this._connected) return;
2383
+ for (const unsub of this._depUnsubs) unsub();
2384
+ this._depUnsubs = [];
2385
+ this._deps = [];
2386
+ this._depIndexMap.clear();
2387
+ this._dirtyBits.clear();
2388
+ this._settledBits.clear();
2389
+ this._completeBits.clear();
2390
+ this._connected = false;
2391
+ this._status = "disconnected";
2392
+ }
2393
+ _runFn() {
2394
+ if (this._terminal && !this._resubscribable) return;
2395
+ if (this._rewiring) return;
2396
+ const trackedDeps = [];
2397
+ const trackedSet = /* @__PURE__ */ new Set();
2398
+ const get = (dep) => {
2399
+ if (!trackedSet.has(dep)) {
2400
+ trackedSet.add(dep);
2401
+ trackedDeps.push(dep);
2402
+ }
2403
+ return dep.get();
2404
+ };
2405
+ try {
2406
+ const depValues = [];
2407
+ for (const dep of this._deps) {
2408
+ depValues.push(dep.get());
2409
+ }
2410
+ this._inspectorHook?.({ kind: "run", depValues });
2411
+ const result = this._fn(get);
2412
+ this._rewire(trackedDeps);
2413
+ if (result === void 0) return;
2414
+ this._emitAutoValue(result);
2415
+ } catch (err) {
2416
+ this._downInternal([[ERROR, err]]);
2417
+ }
2418
+ }
2419
+ _rewire(newDeps) {
2420
+ this._rewiring = true;
2421
+ try {
2422
+ const oldMap = this._depIndexMap;
2423
+ const newMap = /* @__PURE__ */ new Map();
2424
+ const newUnsubs = [];
2425
+ for (let i = 0; i < newDeps.length; i++) {
2426
+ const dep = newDeps[i];
2427
+ newMap.set(dep, i);
2428
+ const oldIdx = oldMap.get(dep);
2429
+ if (oldIdx !== void 0) {
2430
+ newUnsubs.push(this._depUnsubs[oldIdx]);
2431
+ this._depUnsubs[oldIdx] = () => {
2432
+ };
2433
+ } else {
2434
+ const idx = i;
2435
+ const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
2436
+ newUnsubs.push(unsub);
2437
+ }
2438
+ }
2439
+ for (const [dep, oldIdx] of oldMap) {
2440
+ if (!newMap.has(dep)) {
2441
+ this._depUnsubs[oldIdx]();
2442
+ }
2443
+ }
2444
+ this._deps = newDeps;
2445
+ this._depUnsubs = newUnsubs;
2446
+ this._depIndexMap = newMap;
2447
+ this._dirtyBits.clear();
2448
+ this._settledBits.clear();
2449
+ const newCompleteBits = /* @__PURE__ */ new Set();
2450
+ for (const oldIdx of this._completeBits) {
2451
+ const dep = [...oldMap.entries()].find(([, idx]) => idx === oldIdx)?.[0];
2452
+ if (dep && newMap.has(dep)) {
2453
+ newCompleteBits.add(newMap.get(dep));
2454
+ }
2455
+ }
2456
+ this._completeBits = newCompleteBits;
2457
+ } finally {
2458
+ this._rewiring = false;
2459
+ }
2460
+ }
2461
+ _handleDepMessages(index, messages) {
2462
+ if (this._rewiring) return;
2463
+ for (const msg of messages) {
2464
+ this._inspectorHook?.({ kind: "dep_message", depIndex: index, message: msg });
2465
+ const t = msg[0];
2466
+ if (this._onMessage) {
2467
+ try {
2468
+ if (this._onMessage(msg, index, this._actions)) continue;
2469
+ } catch (err) {
2470
+ this._downInternal([[ERROR, err]]);
2471
+ return;
2472
+ }
2473
+ }
2474
+ if (t === DIRTY) {
2475
+ this._dirtyBits.add(index);
2476
+ this._settledBits.delete(index);
2477
+ if (this._dirtyBits.size === 1) {
2478
+ emitWithBatch(this._boundEmitToSinks, [[DIRTY]]);
2479
+ }
2480
+ continue;
2481
+ }
2482
+ if (t === DATA || t === RESOLVED) {
2483
+ if (!this._dirtyBits.has(index)) {
2484
+ this._dirtyBits.add(index);
2485
+ emitWithBatch(this._boundEmitToSinks, [[DIRTY]]);
2486
+ }
2487
+ this._settledBits.add(index);
2488
+ if (this._allDirtySettled()) {
2489
+ this._dirtyBits.clear();
2490
+ this._settledBits.clear();
2491
+ this._runFn();
2492
+ }
2493
+ continue;
2494
+ }
2495
+ if (t === COMPLETE) {
2496
+ this._completeBits.add(index);
2497
+ this._dirtyBits.delete(index);
2498
+ this._settledBits.delete(index);
2499
+ if (this._allDirtySettled()) {
2500
+ this._dirtyBits.clear();
2501
+ this._settledBits.clear();
2502
+ this._runFn();
2503
+ }
2504
+ if (this._autoComplete && this._completeBits.size >= this._deps.length && this._deps.length > 0) {
2505
+ this._downInternal([[COMPLETE]]);
2506
+ }
2507
+ continue;
2508
+ }
2509
+ if (t === ERROR) {
2510
+ this._downInternal([msg]);
2511
+ continue;
2512
+ }
2513
+ if (t === INVALIDATE || t === TEARDOWN || t === PAUSE || t === RESUME) {
2514
+ this._downInternal([msg]);
2515
+ continue;
2516
+ }
2517
+ this._downInternal([msg]);
2518
+ }
2519
+ }
2520
+ _allDirtySettled() {
2521
+ if (this._dirtyBits.size === 0) return false;
2522
+ for (const idx of this._dirtyBits) {
2523
+ if (!this._settledBits.has(idx)) return false;
2524
+ }
2525
+ return true;
2526
+ }
2527
+ };
2528
+
2529
+ // src/core/meta.ts
2530
+ function inferDescribeType(n) {
2531
+ if (n._describeKind != null) return n._describeKind;
2532
+ if (!n._hasDeps) return n._fn != null ? "producer" : "state";
2533
+ if (n._fn == null) return "derived";
2534
+ if (n._manualEmitUsed) return "operator";
2535
+ return "derived";
2536
+ }
2537
+ function metaSnapshot(node2) {
2538
+ const out = {};
2539
+ for (const [key, child] of Object.entries(node2.meta)) {
2540
+ try {
2541
+ out[key] = child.get();
2542
+ } catch {
2543
+ }
2544
+ }
2545
+ return out;
2546
+ }
2547
+ function describeNode(node2) {
2548
+ const meta = { ...metaSnapshot(node2) };
2549
+ const guard = node2 instanceof NodeImpl && node2._guard || node2 instanceof DynamicNodeImpl && node2._guard || void 0;
2550
+ if (guard != null && meta.access === void 0) {
2551
+ meta.access = accessHintForGuard(guard);
2552
+ }
2553
+ let type = "state";
2554
+ let deps = [];
2555
+ if (node2 instanceof NodeImpl) {
2556
+ type = inferDescribeType(node2);
2557
+ deps = node2._deps.map((d) => d.name ?? "");
2558
+ } else if (node2 instanceof DynamicNodeImpl) {
2559
+ type = node2._describeKind ?? "derived";
2560
+ deps = [];
2561
+ }
2562
+ const out = {
2563
+ type,
2564
+ status: node2.status,
2565
+ deps,
2566
+ meta
2567
+ };
2568
+ if (node2.name != null) {
2569
+ out.name = node2.name;
2570
+ }
2571
+ try {
2572
+ out.value = node2.get();
2573
+ } catch {
2574
+ }
2575
+ if (node2.v != null) {
2576
+ const vInfo = { id: node2.v.id, version: node2.v.version };
2577
+ if ("cid" in node2.v) {
2578
+ vInfo.cid = node2.v.cid;
2579
+ vInfo.prev = node2.v.prev;
2580
+ }
2581
+ out.v = vInfo;
2582
+ }
2583
+ return out;
2584
+ }
2585
+
2586
+ // src/graph/graph.ts
2587
+ var PATH_SEP = "::";
2588
+ var GRAPH_META_SEGMENT = "__meta__";
2589
+ var SNAPSHOT_VERSION = 1;
2590
+ function parseSnapshotEnvelope(data) {
2591
+ if (data.version !== SNAPSHOT_VERSION) {
2592
+ throw new Error(
2593
+ `unsupported snapshot version ${String(data.version)} (expected ${SNAPSHOT_VERSION})`
2594
+ );
2595
+ }
2596
+ for (const key of ["name", "nodes", "edges", "subgraphs"]) {
2597
+ if (!(key in data)) {
2598
+ throw new Error(`snapshot missing required key "${key}"`);
2599
+ }
2600
+ }
2601
+ if (typeof data.name !== "string") {
2602
+ throw new TypeError(`snapshot 'name' must be a string`);
2603
+ }
2604
+ if (typeof data.nodes !== "object" || data.nodes === null || Array.isArray(data.nodes)) {
2605
+ throw new TypeError(`snapshot 'nodes' must be an object`);
2606
+ }
2607
+ if (!Array.isArray(data.edges)) {
2608
+ throw new TypeError(`snapshot 'edges' must be an array`);
2609
+ }
2610
+ if (!Array.isArray(data.subgraphs)) {
2611
+ throw new TypeError(`snapshot 'subgraphs' must be an array`);
2612
+ }
2613
+ }
2614
+ function sortJsonValue(value) {
2615
+ if (value === null || typeof value !== "object") {
2616
+ return value;
2617
+ }
2618
+ if (Array.isArray(value)) {
2619
+ return value.map(sortJsonValue);
2620
+ }
2621
+ const obj = value;
2622
+ const keys = Object.keys(obj).sort();
2623
+ const out = {};
2624
+ for (const k of keys) {
2625
+ out[k] = sortJsonValue(obj[k]);
2626
+ }
2627
+ return out;
2628
+ }
2629
+ function stableJsonStringify(value) {
2630
+ return `${JSON.stringify(sortJsonValue(value))}
2631
+ `;
2632
+ }
2633
+ function escapeMermaidLabel(value) {
2634
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
2635
+ }
2636
+ function escapeD2Label(value) {
2637
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
2638
+ }
2639
+ function d2DirectionFromGraphDirection(direction) {
2640
+ if (direction === "TD") return "down";
2641
+ if (direction === "BT") return "up";
2642
+ if (direction === "RL") return "left";
2643
+ return "right";
2644
+ }
2645
+ function collectDiagramArrows(described) {
2646
+ const seen = /* @__PURE__ */ new Set();
2647
+ const arrows = [];
2648
+ function add(from, to) {
2649
+ const key = `${from}\0${to}`;
2650
+ if (seen.has(key)) return;
2651
+ seen.add(key);
2652
+ arrows.push([from, to]);
2653
+ }
2654
+ for (const [path, info] of Object.entries(described.nodes)) {
2655
+ const deps = info.deps;
2656
+ if (deps) {
2657
+ for (const dep of deps) add(dep, path);
2658
+ }
2659
+ }
2660
+ for (const edge of described.edges) add(edge.from, edge.to);
2661
+ return arrows;
2662
+ }
2663
+ function normalizeDiagramDirection(direction) {
2664
+ if (direction === void 0) return "LR";
2665
+ if (direction === "TD" || direction === "LR" || direction === "BT" || direction === "RL") {
2666
+ return direction;
2667
+ }
2668
+ throw new Error(
2669
+ `invalid diagram direction ${String(direction)}; expected one of: TD, LR, BT, RL`
2670
+ );
2671
+ }
2672
+ function escapeRegexLiteral(value) {
2673
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2674
+ }
2675
+ function globToRegex(pattern) {
2676
+ let re = "^";
2677
+ for (let i = 0; i < pattern.length; i += 1) {
2678
+ const ch = pattern[i];
2679
+ if (ch === "*") {
2680
+ re += ".*";
2681
+ continue;
2682
+ }
2683
+ if (ch === "?") {
2684
+ re += ".";
2685
+ continue;
2686
+ }
2687
+ if (ch === "[") {
2688
+ const end = pattern.indexOf("]", i + 1);
2689
+ if (end <= i + 1) {
2690
+ re += "\\[";
2691
+ continue;
2692
+ }
2693
+ let cls = pattern.slice(i + 1, end);
2694
+ if (cls.startsWith("!")) cls = `^${cls.slice(1)}`;
2695
+ cls = cls.replace(/\\/g, "\\\\");
2696
+ re += `[${cls}]`;
2697
+ i = end;
2698
+ continue;
2699
+ }
2700
+ re += escapeRegexLiteral(ch);
2701
+ }
2702
+ re += "$";
2703
+ return new RegExp(re);
2704
+ }
2705
+ var RingBuffer = class {
2706
+ constructor(capacity) {
2707
+ this.capacity = capacity;
2708
+ this.buf = new Array(capacity);
2709
+ }
2710
+ buf;
2711
+ head = 0;
2712
+ _size = 0;
2713
+ get size() {
2714
+ return this._size;
2715
+ }
2716
+ push(item) {
2717
+ const idx = (this.head + this._size) % this.capacity;
2718
+ this.buf[idx] = item;
2719
+ if (this._size < this.capacity) this._size++;
2720
+ else this.head = (this.head + 1) % this.capacity;
2721
+ }
2722
+ toArray() {
2723
+ const result = [];
2724
+ for (let i = 0; i < this._size; i++) result.push(this.buf[(this.head + i) % this.capacity]);
2725
+ return result;
2726
+ }
2727
+ };
2728
+ var SPY_ANSI_THEME = {
2729
+ data: "\x1B[32m",
2730
+ dirty: "\x1B[33m",
2731
+ resolved: "\x1B[36m",
2732
+ complete: "\x1B[34m",
2733
+ error: "\x1B[31m",
2734
+ derived: "\x1B[35m",
2735
+ path: "\x1B[90m",
2736
+ reset: "\x1B[0m"
2737
+ };
2738
+ var SPY_NO_COLOR_THEME = {
2739
+ data: "",
2740
+ dirty: "",
2741
+ resolved: "",
2742
+ complete: "",
2743
+ error: "",
2744
+ derived: "",
2745
+ path: "",
2746
+ reset: ""
2747
+ };
2748
+ function describeData(value) {
2749
+ if (typeof value === "string") return JSON.stringify(value);
2750
+ if (typeof value === "number" || typeof value === "boolean" || value == null)
2751
+ return String(value);
2752
+ try {
2753
+ return JSON.stringify(value);
2754
+ } catch {
2755
+ return "[unserializable]";
2756
+ }
2757
+ }
2758
+ function resolveSpyTheme(theme) {
2759
+ if (theme === "none") return SPY_NO_COLOR_THEME;
2760
+ if (theme === "ansi" || theme == null) return SPY_ANSI_THEME;
2761
+ return {
2762
+ data: theme.data ?? "",
2763
+ dirty: theme.dirty ?? "",
2764
+ resolved: theme.resolved ?? "",
2765
+ complete: theme.complete ?? "",
2766
+ error: theme.error ?? "",
2767
+ derived: theme.derived ?? "",
2768
+ path: theme.path ?? "",
2769
+ reset: theme.reset ?? ""
2770
+ };
2771
+ }
2772
+ function assertLocalName(name, graphName, label) {
2773
+ if (name === "") {
2774
+ throw new Error(`Graph "${graphName}": ${label} name must be non-empty`);
2775
+ }
2776
+ }
2777
+ function assertNoPathSep(name, graphName, label) {
2778
+ if (name.includes(PATH_SEP)) {
2779
+ throw new Error(
2780
+ `Graph "${graphName}": ${label} "${name}" must not contain '${PATH_SEP}' (path separator)`
2781
+ );
2782
+ }
2783
+ }
2784
+ function assertNotReservedMetaSegment(name, graphName, label) {
2785
+ if (name === GRAPH_META_SEGMENT) {
2786
+ throw new Error(
2787
+ `Graph "${graphName}": ${label} name "${GRAPH_META_SEGMENT}" is reserved for meta companion paths`
2788
+ );
2789
+ }
2790
+ }
2791
+ function assertConnectPathNotMeta(path, graphName) {
2792
+ if (path.split(PATH_SEP).includes(GRAPH_META_SEGMENT)) {
2793
+ throw new Error(
2794
+ `Graph "${graphName}": connect/disconnect endpoints must be registered graph nodes, not meta paths (got "${path}")`
2795
+ );
2796
+ }
2797
+ }
2798
+ function splitPath(path, graphName) {
2799
+ if (path === "") {
2800
+ throw new Error(`Graph "${graphName}": resolve path must be non-empty`);
2801
+ }
2802
+ const segments = path.split(PATH_SEP);
2803
+ for (const s of segments) {
2804
+ if (s === "") {
2805
+ throw new Error(`Graph "${graphName}": resolve path has empty segment`);
2806
+ }
2807
+ }
2808
+ return segments;
2809
+ }
2810
+ function edgeKey(from, to) {
2811
+ return `${from} ${to}`;
2812
+ }
2813
+ function parseEdgeKey(key) {
2814
+ const i = key.indexOf(" ");
2815
+ return [key.slice(0, i), key.slice(i + 1)];
2816
+ }
2817
+ var META_FILTERED_TYPES = /* @__PURE__ */ new Set([TEARDOWN, INVALIDATE, COMPLETE, ERROR]);
2818
+ function filterMetaMessages(messages) {
2819
+ const kept = messages.filter((m) => !META_FILTERED_TYPES.has(m[0]));
2820
+ return kept;
2821
+ }
2822
+ function teardownMountedGraph(root) {
2823
+ for (const child of root._mounts.values()) {
2824
+ teardownMountedGraph(child);
2825
+ }
2826
+ for (const n of root._nodes.values()) {
2827
+ n.down([[TEARDOWN]], { internal: true });
2828
+ }
2829
+ }
2830
+ var Graph = class _Graph {
2831
+ static _factories = [];
2832
+ name;
2833
+ opts;
2834
+ /** @internal — exposed for {@link teardownMountedGraph} and cross-graph helpers. */
2835
+ _nodes = /* @__PURE__ */ new Map();
2836
+ _edges = /* @__PURE__ */ new Set();
2837
+ /** @internal — exposed for {@link teardownMountedGraph}. */
2838
+ _mounts = /* @__PURE__ */ new Map();
2839
+ _autoCheckpointDisposers = /* @__PURE__ */ new Set();
2840
+ _defaultVersioningLevel;
2841
+ static registerFactory(pattern, factory) {
2842
+ if (!pattern) {
2843
+ throw new Error("Graph.registerFactory requires a non-empty pattern");
2844
+ }
2845
+ _Graph.unregisterFactory(pattern);
2846
+ _Graph._factories.push({ pattern, re: globToRegex(pattern), factory });
2847
+ }
2848
+ static unregisterFactory(pattern) {
2849
+ const i = _Graph._factories.findIndex((entry) => entry.pattern === pattern);
2850
+ if (i >= 0) _Graph._factories.splice(i, 1);
2851
+ }
2852
+ /**
2853
+ * @param name - Non-empty graph id (must not contain `::`).
2854
+ * @param opts - Reserved for future hooks; currently unused.
2855
+ */
2856
+ constructor(name, opts) {
2857
+ if (name === "") {
2858
+ throw new Error("Graph name must be non-empty");
2859
+ }
2860
+ if (name.includes(PATH_SEP)) {
2861
+ throw new Error(`Graph name must not contain '${PATH_SEP}' (got "${name}")`);
2862
+ }
2863
+ this.name = name;
2864
+ this.opts = opts ?? {};
2865
+ }
2866
+ static _factoryForPath(path) {
2867
+ for (let i = _Graph._factories.length - 1; i >= 0; i -= 1) {
2868
+ const entry = _Graph._factories[i];
2869
+ if (entry.re.test(path)) return entry.factory;
2870
+ }
2871
+ return void 0;
2872
+ }
2873
+ static _ownerForPath(root, path) {
2874
+ const segments = path.split(PATH_SEP);
2875
+ const local = segments.pop();
2876
+ if (local == null || local.length === 0) {
2877
+ throw new Error(`invalid snapshot path "${path}"`);
2878
+ }
2879
+ let owner = root;
2880
+ for (const seg of segments) {
2881
+ const next = owner._mounts.get(seg);
2882
+ if (!next) throw new Error(`unknown mount "${seg}" in path "${path}"`);
2883
+ owner = next;
2884
+ }
2885
+ return [owner, local];
2886
+ }
2887
+ /**
2888
+ * Graphs reachable from this instance via nested {@link Graph.mount} (includes `this`).
2889
+ */
2890
+ _graphsReachableViaMounts(seen = /* @__PURE__ */ new Set()) {
2891
+ if (seen.has(this)) return seen;
2892
+ seen.add(this);
2893
+ for (const child of this._mounts.values()) {
2894
+ child._graphsReachableViaMounts(seen);
2895
+ }
2896
+ return seen;
2897
+ }
2898
+ /**
2899
+ * Resolve an endpoint: returns `[owningGraph, localName, node]`.
2900
+ * Accepts both local names and `::` qualified paths.
2901
+ */
2902
+ _resolveEndpoint(path) {
2903
+ if (!path.includes(PATH_SEP)) {
2904
+ const n = this._nodes.get(path);
2905
+ if (!n) {
2906
+ throw new Error(`Graph "${this.name}": unknown node "${path}"`);
2907
+ }
2908
+ return [this, path, n];
2909
+ }
2910
+ const segments = splitPath(path, this.name);
2911
+ return this._resolveEndpointFromSegments(segments, path);
2912
+ }
2913
+ _resolveEndpointFromSegments(segments, fullPath) {
2914
+ const head = segments[0];
2915
+ const rest = segments.slice(1);
2916
+ if (rest.length === 0) {
2917
+ const n = this._nodes.get(head);
2918
+ if (n) return [this, head, n];
2919
+ throw new Error(`Graph "${this.name}": unknown node "${head}" (from path "${fullPath}")`);
2920
+ }
2921
+ const localN = this._nodes.get(head);
2922
+ if (localN && rest.length > 0 && rest[0] === GRAPH_META_SEGMENT) {
2923
+ return this._resolveMetaEndpointKeys(localN, head, rest, fullPath);
2924
+ }
2925
+ const child = this._mounts.get(head);
2926
+ if (!child) {
2927
+ if (this._nodes.has(head)) {
2928
+ throw new Error(
2929
+ `Graph "${this.name}": "${head}" is a node; trailing path "${rest.join(PATH_SEP)}" is invalid`
2930
+ );
2931
+ }
2932
+ throw new Error(`Graph "${this.name}": unknown mount or node "${head}"`);
2933
+ }
2934
+ return child._resolveEndpointFromSegments(rest, fullPath);
2935
+ }
2936
+ // ——————————————————————————————————————————————————————————————
2937
+ // Node registry
2938
+ // ——————————————————————————————————————————————————————————————
2939
+ /**
2940
+ * Registers a node under a local name. Fails if the name is already used,
2941
+ * reserved by a mount, or the same node instance is already registered.
2942
+ *
2943
+ * @param name - Local key (no `::`).
2944
+ * @param node - Node instance to own.
2945
+ */
2946
+ add(name, node2) {
2947
+ assertLocalName(name, this.name, "add");
2948
+ assertNoPathSep(name, this.name, "add");
2949
+ assertNotReservedMetaSegment(name, this.name, "node");
2950
+ if (this._mounts.has(name)) {
2951
+ throw new Error(`Graph "${this.name}": name "${name}" is already a mount point`);
2952
+ }
2953
+ if (this._nodes.has(name)) {
2954
+ throw new Error(`Graph "${this.name}": node "${name}" already exists`);
2955
+ }
2956
+ for (const [existingName, existing] of this._nodes) {
2957
+ if (existing === node2) {
2958
+ throw new Error(
2959
+ `Graph "${this.name}": node instance already registered as "${existingName}"`
2960
+ );
2961
+ }
2962
+ }
2963
+ this._nodes.set(name, node2);
2964
+ if (node2 instanceof NodeImpl) {
2965
+ node2._assignRegistryName(name);
2966
+ if (this._defaultVersioningLevel != null) {
2967
+ node2._applyVersioning(this._defaultVersioningLevel);
2968
+ }
2969
+ }
2970
+ }
2971
+ /**
2972
+ * Set a default versioning level for all nodes added to this graph (roadmap §6.0).
2973
+ *
2974
+ * Nodes already registered are retroactively upgraded. Nodes added later via
2975
+ * {@link add} will inherit this level unless they already have versioning.
2976
+ *
2977
+ * **Scope:** Does not propagate to mounted subgraphs. Call `setVersioning`
2978
+ * on each child graph separately if needed.
2979
+ *
2980
+ * @param level - `0` for V0, `1` for V1, or `undefined` to clear.
2981
+ */
2982
+ setVersioning(level) {
2983
+ this._defaultVersioningLevel = level;
2984
+ if (level == null) return;
2985
+ for (const n of this._nodes.values()) {
2986
+ if (n instanceof NodeImpl) {
2987
+ n._applyVersioning(level);
2988
+ }
2989
+ }
2990
+ }
2991
+ /**
2992
+ * Unregisters a node or unmounts a subgraph, drops incident edges, and sends
2993
+ * `[[TEARDOWN]]` to the removed node or recursively through the mounted subtree (§3.2).
2994
+ *
2995
+ * @param name - Local mount or node name.
2996
+ */
2997
+ remove(name) {
2998
+ assertLocalName(name, this.name, "remove");
2999
+ assertNoPathSep(name, this.name, "remove");
3000
+ const child = this._mounts.get(name);
3001
+ if (child) {
3002
+ this._mounts.delete(name);
3003
+ const prefix = `${name}${PATH_SEP}`;
3004
+ for (const key of [...this._edges]) {
3005
+ const [from, to] = parseEdgeKey(key);
3006
+ if (from === name || to === name || from.startsWith(prefix) || to.startsWith(prefix)) {
3007
+ this._edges.delete(key);
3008
+ }
3009
+ }
3010
+ teardownMountedGraph(child);
3011
+ return;
3012
+ }
3013
+ const node2 = this._nodes.get(name);
3014
+ if (!node2) {
3015
+ throw new Error(`Graph "${this.name}": unknown node or mount "${name}"`);
3016
+ }
3017
+ this._nodes.delete(name);
3018
+ for (const key of [...this._edges]) {
3019
+ const [from, to] = parseEdgeKey(key);
3020
+ if (from === name || to === name) this._edges.delete(key);
3021
+ }
3022
+ node2.down([[TEARDOWN]], { internal: true });
3023
+ }
3024
+ /**
3025
+ * Returns a node by local name or `::` qualified path.
3026
+ * Local names are looked up directly; paths with `::` delegate to {@link resolve}.
3027
+ *
3028
+ * @param name - Local name or qualified path.
3029
+ */
3030
+ node(name) {
3031
+ if (name === "") {
3032
+ throw new Error(`Graph "${this.name}": node name must be non-empty`);
3033
+ }
3034
+ if (name.includes(PATH_SEP)) {
3035
+ return this.resolve(name);
3036
+ }
3037
+ const n = this._nodes.get(name);
3038
+ if (!n) {
3039
+ throw new Error(`Graph "${this.name}": unknown node "${name}"`);
3040
+ }
3041
+ return n;
3042
+ }
3043
+ /**
3044
+ * Reads `graph.node(name).get()` — accepts `::` qualified paths (§3.2).
3045
+ *
3046
+ * @param name - Local name or qualified path.
3047
+ * @returns Cached value or `undefined`.
3048
+ */
3049
+ get(name) {
3050
+ return this.node(name).get();
3051
+ }
3052
+ /**
3053
+ * Shorthand for `graph.node(name).down([[DATA, value]], { actor })` — accepts `::` qualified paths (§3.2).
3054
+ *
3055
+ * @param name - Local name or qualified path.
3056
+ * @param value - Next `DATA` payload.
3057
+ * @param options - Optional `actor` and `internal` guard bypass.
3058
+ */
3059
+ set(name, value, options) {
3060
+ const internal = options?.internal === true;
3061
+ this.node(name).down([[DATA, value]], {
3062
+ actor: options?.actor,
3063
+ internal,
3064
+ delivery: "write"
3065
+ });
3066
+ }
3067
+ // ——————————————————————————————————————————————————————————————
3068
+ // Edges
3069
+ // ——————————————————————————————————————————————————————————————
3070
+ /**
3071
+ * Record a wire from `fromPath` → `toPath` (§3.3). Accepts local names or
3072
+ * `::` qualified paths. The target must be a {@link NodeImpl} whose `_deps`
3073
+ * includes the source node (same reference). Idempotent.
3074
+ *
3075
+ * Same-owner edges are stored on the owning child graph; cross-subgraph edges
3076
+ * are stored on this (parent) graph's registry.
3077
+ *
3078
+ * @param fromPath - Source endpoint (local or qualified).
3079
+ * @param toPath - Target endpoint whose deps already include the source node.
3080
+ */
3081
+ connect(fromPath, toPath) {
3082
+ if (!fromPath || !toPath) {
3083
+ throw new Error(`Graph "${this.name}": connect paths must be non-empty`);
3084
+ }
3085
+ assertConnectPathNotMeta(fromPath, this.name);
3086
+ assertConnectPathNotMeta(toPath, this.name);
3087
+ const [fromGraph, fromLocal, fromNode] = this._resolveEndpoint(fromPath);
3088
+ const [toGraph, toLocal, toNode] = this._resolveEndpoint(toPath);
3089
+ if (fromNode === toNode) {
3090
+ throw new Error(`Graph "${this.name}": cannot connect a node to itself`);
3091
+ }
3092
+ if (!(toNode instanceof NodeImpl)) {
3093
+ throw new Error(
3094
+ `Graph "${this.name}": connect(${fromPath}, ${toPath}) requires the target to be a graphrefly NodeImpl so deps can be validated`
3095
+ );
3096
+ }
3097
+ if (!toNode._deps.includes(fromNode)) {
3098
+ throw new Error(
3099
+ `Graph "${this.name}": connect(${fromPath}, ${toPath}) \u2014 target must include source in its constructor deps (same node reference)`
3100
+ );
3101
+ }
3102
+ if (fromGraph === toGraph) {
3103
+ const key = edgeKey(fromLocal, toLocal);
3104
+ fromGraph._edges.add(key);
3105
+ } else {
3106
+ const key = edgeKey(fromPath, toPath);
3107
+ this._edges.add(key);
3108
+ }
3109
+ }
3110
+ /**
3111
+ * Remove a registered edge (§3.3). Accepts local names or `::` qualified paths.
3112
+ *
3113
+ * **Registry-only (§C resolved):** This drops the edge from the graph's edge
3114
+ * registry only. It does **not** mutate the target node's constructor-time
3115
+ * dependency list, bitmasks, or upstream subscriptions. Message flow follows
3116
+ * constructor-time deps, not the edge registry. For runtime dep rewiring, use
3117
+ * {@link dynamicNode}.
3118
+ *
3119
+ * @param fromPath - Registered edge tail.
3120
+ * @param toPath - Registered edge head.
3121
+ */
3122
+ disconnect(fromPath, toPath) {
3123
+ if (!fromPath || !toPath) {
3124
+ throw new Error(`Graph "${this.name}": disconnect paths must be non-empty`);
3125
+ }
3126
+ assertConnectPathNotMeta(fromPath, this.name);
3127
+ assertConnectPathNotMeta(toPath, this.name);
3128
+ const [fromGraph, fromLocal] = this._resolveEndpoint(fromPath);
3129
+ const [toGraph, toLocal] = this._resolveEndpoint(toPath);
3130
+ if (fromGraph === toGraph) {
3131
+ const key = edgeKey(fromLocal, toLocal);
3132
+ if (!fromGraph._edges.delete(key)) {
3133
+ throw new Error(`Graph "${this.name}": no registered edge ${fromPath} \u2192 ${toPath}`);
3134
+ }
3135
+ } else {
3136
+ const key = edgeKey(fromPath, toPath);
3137
+ if (!this._edges.delete(key)) {
3138
+ throw new Error(`Graph "${this.name}": no registered edge ${fromPath} \u2192 ${toPath}`);
3139
+ }
3140
+ }
3141
+ }
3142
+ /**
3143
+ * Returns registered `[from, to]` edge pairs (read-only snapshot).
3144
+ *
3145
+ * @returns Edge pairs recorded on this graph instance’s local `_edges` set.
3146
+ */
3147
+ edges() {
3148
+ const result = [];
3149
+ for (const key of this._edges) {
3150
+ result.push(parseEdgeKey(key));
3151
+ }
3152
+ return result;
3153
+ }
3154
+ // ——————————————————————————————————————————————————————————————
3155
+ // Composition
3156
+ // ——————————————————————————————————————————————————————————————
3157
+ /**
3158
+ * Embed a child graph at a local mount name (§3.4). Child nodes are reachable via
3159
+ * {@link Graph.resolve} using `::` delimited paths (§3.5). Lifecycle
3160
+ * {@link Graph.signal} visits mounted subgraphs recursively.
3161
+ *
3162
+ * Rejects: same name as existing node or mount, self-mount, mount cycles,
3163
+ * and the same child graph instance mounted twice on one parent.
3164
+ *
3165
+ * @param name - Local mount point.
3166
+ * @param child - Nested `Graph` instance.
3167
+ */
3168
+ mount(name, child) {
3169
+ assertLocalName(name, this.name, "mount");
3170
+ assertNoPathSep(name, this.name, "mount");
3171
+ assertNotReservedMetaSegment(name, this.name, "mount");
3172
+ if (this._nodes.has(name)) {
3173
+ throw new Error(
3174
+ `Graph "${this.name}": cannot mount at "${name}" \u2014 node with that name exists`
3175
+ );
3176
+ }
3177
+ if (this._mounts.has(name)) {
3178
+ throw new Error(`Graph "${this.name}": mount "${name}" already exists`);
3179
+ }
3180
+ if (child === this) {
3181
+ throw new Error(`Graph "${this.name}": cannot mount a graph into itself`);
3182
+ }
3183
+ for (const existing of this._mounts.values()) {
3184
+ if (existing === child) {
3185
+ throw new Error(`Graph "${this.name}": this child graph is already mounted on this graph`);
3186
+ }
3187
+ }
3188
+ if (child._graphsReachableViaMounts().has(this)) {
3189
+ throw new Error(`Graph "${this.name}": mount("${name}", \u2026) would create a mount cycle`);
3190
+ }
3191
+ this._mounts.set(name, child);
3192
+ }
3193
+ /**
3194
+ * Look up a node by qualified path (§3.5). Segments are separated by `::`.
3195
+ *
3196
+ * If the first segment equals this graph's {@link Graph.name}, it is stripped
3197
+ * (so `root.resolve("app::a")` works when `root.name === "app"`).
3198
+ *
3199
+ * @param path - Qualified `::` path or local name.
3200
+ * @returns The resolved `Node`.
3201
+ */
3202
+ resolve(path) {
3203
+ let segments = splitPath(path, this.name);
3204
+ if (segments[0] === this.name) {
3205
+ segments = segments.slice(1);
3206
+ if (segments.length === 0) {
3207
+ throw new Error(`Graph "${this.name}": resolve path ends at graph name only`);
3208
+ }
3209
+ }
3210
+ return this._resolveFromSegments(segments);
3211
+ }
3212
+ _resolveFromSegments(segments) {
3213
+ const head = segments[0];
3214
+ const rest = segments.slice(1);
3215
+ if (rest.length === 0) {
3216
+ const n = this._nodes.get(head);
3217
+ if (n) return n;
3218
+ if (this._mounts.has(head)) {
3219
+ throw new Error(
3220
+ `Graph "${this.name}": path ends at subgraph "${head}" \u2014 not a node (GRAPHREFLY-SPEC \xA73.5)`
3221
+ );
3222
+ }
3223
+ throw new Error(`Graph "${this.name}": unknown name "${head}"`);
3224
+ }
3225
+ const localN = this._nodes.get(head);
3226
+ if (localN && rest.length > 0 && rest[0] === GRAPH_META_SEGMENT) {
3227
+ return this._resolveMetaChainFromNode(localN, rest, segments.join(PATH_SEP));
3228
+ }
3229
+ const child = this._mounts.get(head);
3230
+ if (!child) {
3231
+ if (this._nodes.has(head)) {
3232
+ throw new Error(
3233
+ `Graph "${this.name}": "${head}" is a node; trailing path "${rest.join(PATH_SEP)}" is invalid`
3234
+ );
3235
+ }
3236
+ throw new Error(`Graph "${this.name}": unknown mount or node "${head}"`);
3237
+ }
3238
+ return child.resolve(rest.join(PATH_SEP));
3239
+ }
3240
+ /**
3241
+ * Resolve `::__meta__::key` segments from a registered primary node (possibly chained).
3242
+ */
3243
+ _resolveMetaChainFromNode(n, parts, fullPath) {
3244
+ let current = n;
3245
+ let i = 0;
3246
+ const p = [...parts];
3247
+ while (i < p.length) {
3248
+ if (p[i] !== GRAPH_META_SEGMENT) {
3249
+ throw new Error(
3250
+ `Graph "${this.name}": expected ${GRAPH_META_SEGMENT} segment in meta path "${fullPath}"`
3251
+ );
3252
+ }
3253
+ if (i + 1 >= p.length) {
3254
+ throw new Error(
3255
+ `Graph "${this.name}": meta path requires a key after ${GRAPH_META_SEGMENT} in "${fullPath}"`
3256
+ );
3257
+ }
3258
+ const key = p[i + 1];
3259
+ const next = current.meta[key];
3260
+ if (!next) {
3261
+ throw new Error(`Graph "${this.name}": unknown meta "${key}" in path "${fullPath}"`);
3262
+ }
3263
+ current = next;
3264
+ i += 2;
3265
+ }
3266
+ return current;
3267
+ }
3268
+ _resolveMetaEndpointKeys(baseNode, baseLocalKey, parts, fullPath) {
3269
+ let current = baseNode;
3270
+ let localKey = baseLocalKey;
3271
+ let i = 0;
3272
+ const p = [...parts];
3273
+ while (i < p.length) {
3274
+ if (p[i] !== GRAPH_META_SEGMENT) {
3275
+ throw new Error(
3276
+ `Graph "${this.name}": expected ${GRAPH_META_SEGMENT} segment in meta path "${fullPath}"`
3277
+ );
3278
+ }
3279
+ if (i + 1 >= p.length) {
3280
+ throw new Error(
3281
+ `Graph "${this.name}": meta path requires a key after ${GRAPH_META_SEGMENT} in "${fullPath}"`
3282
+ );
3283
+ }
3284
+ const metaKey = p[i + 1];
3285
+ const next = current.meta[metaKey];
3286
+ if (!next) {
3287
+ throw new Error(
3288
+ `Graph "${this.name}": unknown meta "${metaKey}" on node (in "${fullPath}")`
3289
+ );
3290
+ }
3291
+ localKey = `${localKey}${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}${metaKey}`;
3292
+ current = next;
3293
+ i += 2;
3294
+ }
3295
+ return [this, localKey, current];
3296
+ }
3297
+ /**
3298
+ * Deliver a message batch to every registered node in this graph and, recursively,
3299
+ * in mounted child graphs (§3.7). Recurses into mounts first, then delivers to
3300
+ * local nodes (sorted by name). Each {@link Node} receives at most one delivery
3301
+ * per call (deduped by reference).
3302
+ *
3303
+ * Companion `meta` nodes receive the same batch for control-plane types (e.g.
3304
+ * PAUSE) that the primary does not forward. **TEARDOWN-only** batches skip the
3305
+ * extra meta pass — the primary’s `down()` already cascades TEARDOWN to meta.
3306
+ *
3307
+ * @param messages - Batch to deliver to every registered node (and mounts, recursively).
3308
+ * @param options - Optional `actor` / `internal` for transport.
3309
+ */
3310
+ signal(messages, options) {
3311
+ this._signalDeliver(messages, options ?? {}, /* @__PURE__ */ new Set());
3312
+ }
3313
+ _signalDeliver(messages, opts, vis) {
3314
+ for (const sub of this._mounts.values()) {
3315
+ sub._signalDeliver(messages, opts, vis);
3316
+ }
3317
+ const internal = opts.internal === true;
3318
+ const downOpts = internal ? { internal: true } : { actor: opts.actor, delivery: "signal" };
3319
+ const metaMessages = filterMetaMessages(messages);
3320
+ for (const localName of [...this._nodes.keys()].sort()) {
3321
+ const n = this._nodes.get(localName);
3322
+ if (vis.has(n)) continue;
3323
+ vis.add(n);
3324
+ n.down(messages, downOpts);
3325
+ if (metaMessages.length === 0) continue;
3326
+ this._signalMetaSubtree(n, metaMessages, vis, downOpts);
3327
+ }
3328
+ }
3329
+ _signalMetaSubtree(root, messages, vis, downOpts) {
3330
+ for (const mk of Object.keys(root.meta).sort()) {
3331
+ const mnode = root.meta[mk];
3332
+ if (vis.has(mnode)) continue;
3333
+ vis.add(mnode);
3334
+ mnode.down(messages, downOpts);
3335
+ this._signalMetaSubtree(mnode, messages, vis, downOpts);
3336
+ }
3337
+ }
3338
+ /**
3339
+ * Static structure snapshot: qualified node keys, edges, mount names (GRAPHREFLY-SPEC §3.6, Appendix B).
3340
+ *
3341
+ * @param options - Optional `actor` for guard-scoped visibility and/or `filter` for selective output.
3342
+ * @returns JSON-shaped describe payload for this graph tree.
3343
+ *
3344
+ * @example
3345
+ * ```ts
3346
+ * graph.describe() // full snapshot
3347
+ * graph.describe({ actor: llm }) // guard-scoped
3348
+ * graph.describe({ filter: { status: "errored" } }) // only errored nodes
3349
+ * graph.describe({ filter: (n) => n.type === "state" }) // predicate filter
3350
+ * ```
3351
+ */
3352
+ describe(options) {
3353
+ const actor = options?.actor;
3354
+ const filter = options?.filter;
3355
+ const targets = [];
3356
+ this._collectObserveTargets("", targets);
3357
+ const nodeToPath = /* @__PURE__ */ new Map();
3358
+ for (const [p, n] of targets) {
3359
+ nodeToPath.set(n, p);
3360
+ }
3361
+ const nodes = {};
3362
+ for (const [p, n] of targets) {
3363
+ if (actor != null && !n.allowsObserve(actor)) continue;
3364
+ const raw = describeNode(n);
3365
+ const deps = n instanceof NodeImpl ? n._deps.map((d) => nodeToPath.get(d) ?? d.name ?? "") : [];
3366
+ const { name: _name, ...rest } = raw;
3367
+ const entry = { ...rest, deps };
3368
+ if (filter != null) {
3369
+ if (typeof filter === "function") {
3370
+ const fn = filter;
3371
+ const pass = fn.length >= 2 ? fn(p, entry) : fn(entry);
3372
+ if (!pass) continue;
3373
+ } else {
3374
+ let match = true;
3375
+ for (const [fk, fv] of Object.entries(filter)) {
3376
+ const normalizedKey = fk === "deps_includes" ? "depsIncludes" : fk === "meta_has" ? "metaHas" : fk;
3377
+ if (normalizedKey === "depsIncludes") {
3378
+ if (!entry.deps.includes(String(fv))) {
3379
+ match = false;
3380
+ break;
3381
+ }
3382
+ continue;
3383
+ }
3384
+ if (normalizedKey === "metaHas") {
3385
+ if (!Object.hasOwn(entry.meta, String(fv))) {
3386
+ match = false;
3387
+ break;
3388
+ }
3389
+ continue;
3390
+ }
3391
+ if (entry[normalizedKey] !== fv) {
3392
+ match = false;
3393
+ break;
3394
+ }
3395
+ }
3396
+ if (!match) continue;
3397
+ }
3398
+ }
3399
+ nodes[p] = entry;
3400
+ }
3401
+ const nodeKeys = new Set(Object.keys(nodes));
3402
+ let edges = this._collectAllEdges("");
3403
+ if (actor != null || filter != null) {
3404
+ edges = edges.filter((e) => nodeKeys.has(e.from) && nodeKeys.has(e.to));
3405
+ }
3406
+ edges.sort((a, b) => {
3407
+ if (a.from < b.from) return -1;
3408
+ if (a.from > b.from) return 1;
3409
+ if (a.to < b.to) return -1;
3410
+ if (a.to > b.to) return 1;
3411
+ return 0;
3412
+ });
3413
+ const allSubgraphs = this._collectSubgraphs("");
3414
+ const subgraphs = actor != null || filter != null ? allSubgraphs.filter((sg) => {
3415
+ const prefix = `${sg}${PATH_SEP}`;
3416
+ return [...nodeKeys].some((k) => k === sg || k.startsWith(prefix));
3417
+ }) : allSubgraphs;
3418
+ return {
3419
+ name: this.name,
3420
+ nodes,
3421
+ edges,
3422
+ subgraphs
3423
+ };
3424
+ }
3425
+ _collectSubgraphs(prefix) {
3426
+ const out = [];
3427
+ for (const m of [...this._mounts.keys()].sort()) {
3428
+ const q = prefix === "" ? m : `${prefix}${m}`;
3429
+ out.push(q);
3430
+ out.push(...this._mounts.get(m)._collectSubgraphs(`${q}${PATH_SEP}`));
3431
+ }
3432
+ return out;
3433
+ }
3434
+ _collectAllEdges(prefix) {
3435
+ const out = [];
3436
+ for (const m of [...this._mounts.keys()].sort()) {
3437
+ const p2 = prefix === "" ? m : `${prefix}${PATH_SEP}${m}`;
3438
+ out.push(...this._mounts.get(m)._collectAllEdges(p2));
3439
+ }
3440
+ for (const [f, t] of this.edges()) {
3441
+ out.push({
3442
+ from: this._qualifyEdgeEndpoint(f, prefix),
3443
+ to: this._qualifyEdgeEndpoint(t, prefix)
3444
+ });
3445
+ }
3446
+ return out;
3447
+ }
3448
+ _qualifyEdgeEndpoint(part, prefix) {
3449
+ if (part.includes(PATH_SEP)) return part;
3450
+ return prefix === "" ? part : `${prefix}${PATH_SEP}${part}`;
3451
+ }
3452
+ _collectObserveTargets(prefix, out) {
3453
+ for (const m of [...this._mounts.keys()].sort()) {
3454
+ const p2 = prefix === "" ? m : `${prefix}${PATH_SEP}${m}`;
3455
+ this._mounts.get(m)._collectObserveTargets(p2, out);
3456
+ }
3457
+ for (const loc of [...this._nodes.keys()].sort()) {
3458
+ const n = this._nodes.get(loc);
3459
+ const p = prefix === "" ? loc : `${prefix}${PATH_SEP}${loc}`;
3460
+ out.push([p, n]);
3461
+ this._appendMetaObserveTargets(p, n, out);
3462
+ }
3463
+ }
3464
+ _appendMetaObserveTargets(basePath, n, out) {
3465
+ for (const mk of Object.keys(n.meta).sort()) {
3466
+ const m = n.meta[mk];
3467
+ const mp = `${basePath}${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}${mk}`;
3468
+ out.push([mp, m]);
3469
+ this._appendMetaObserveTargets(mp, m, out);
3470
+ }
3471
+ }
3472
+ observe(pathOrOpts, options) {
3473
+ if (typeof pathOrOpts === "string") {
3474
+ const path = pathOrOpts;
3475
+ const actor2 = options?.actor;
3476
+ const target = this.resolve(path);
3477
+ if (actor2 != null && !target.allowsObserve(actor2)) {
3478
+ throw new GuardDenied({ actor: actor2, action: "observe", nodeName: path });
3479
+ }
3480
+ const wantsStructured2 = options?.structured === true || options?.timeline === true || options?.causal === true || options?.derived === true;
3481
+ if (wantsStructured2 && _Graph.inspectorEnabled) {
3482
+ return this._createObserveResult(path, target, options);
3483
+ }
3484
+ return {
3485
+ subscribe(sink) {
3486
+ return target.subscribe(sink);
3487
+ },
3488
+ up(messages) {
3489
+ try {
3490
+ target.up?.(messages);
3491
+ } catch (err) {
3492
+ if (err instanceof GuardDenied) return;
3493
+ throw err;
3494
+ }
3495
+ }
3496
+ };
3497
+ }
3498
+ const opts = pathOrOpts;
3499
+ const actor = opts?.actor;
3500
+ const wantsStructured = opts?.structured === true || opts?.timeline === true || opts?.causal === true || opts?.derived === true;
3501
+ if (wantsStructured && _Graph.inspectorEnabled) {
3502
+ return this._createObserveResultForAll(opts ?? {});
3503
+ }
3504
+ return {
3505
+ subscribe: (sink) => {
3506
+ const targets = [];
3507
+ this._collectObserveTargets("", targets);
3508
+ targets.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
3509
+ const picked = actor == null ? targets : targets.filter(([, nd]) => nd.allowsObserve(actor));
3510
+ const unsubs = picked.map(
3511
+ ([p, nd]) => nd.subscribe((msgs) => {
3512
+ sink(p, msgs);
3513
+ })
3514
+ );
3515
+ return () => {
3516
+ for (const u of unsubs) u();
3517
+ };
3518
+ },
3519
+ up: (upPath, messages) => {
3520
+ try {
3521
+ const nd = this.resolve(upPath);
3522
+ nd.up?.(messages);
3523
+ } catch (err) {
3524
+ if (err instanceof GuardDenied) return;
3525
+ throw err;
3526
+ }
3527
+ }
3528
+ };
3529
+ }
3530
+ _createObserveResult(path, target, options) {
3531
+ const timeline = options.timeline === true;
3532
+ const causal = options.causal === true;
3533
+ const derived2 = options.derived === true;
3534
+ const result = {
3535
+ values: {},
3536
+ dirtyCount: 0,
3537
+ resolvedCount: 0,
3538
+ events: [],
3539
+ completedCleanly: false,
3540
+ errored: false
3541
+ };
3542
+ let lastTriggerDepIndex;
3543
+ let lastRunDepValues;
3544
+ let detachInspectorHook;
3545
+ if ((causal || derived2) && target instanceof NodeImpl) {
3546
+ detachInspectorHook = target._setInspectorHook((event) => {
3547
+ if (event.kind === "dep_message") {
3548
+ lastTriggerDepIndex = event.depIndex;
3549
+ return;
3550
+ }
3551
+ lastRunDepValues = [...event.depValues];
3552
+ if (derived2) {
3553
+ result.events.push({
3554
+ type: "derived",
3555
+ path,
3556
+ dep_values: [...event.depValues],
3557
+ ...timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {}
3558
+ });
3559
+ }
3560
+ });
3561
+ }
3562
+ const unsub = target.subscribe((msgs) => {
3563
+ for (const m of msgs) {
3564
+ const t = m[0];
3565
+ const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
3566
+ const withCausal = causal && lastRunDepValues != null ? (() => {
3567
+ const triggerDep = lastTriggerDepIndex != null && lastTriggerDepIndex >= 0 && target instanceof NodeImpl ? target._deps[lastTriggerDepIndex] : void 0;
3568
+ const tv = triggerDep?.v;
3569
+ return {
3570
+ trigger_dep_index: lastTriggerDepIndex,
3571
+ trigger_dep_name: triggerDep?.name,
3572
+ ...tv != null ? { trigger_version: { id: tv.id, version: tv.version } } : {},
3573
+ dep_values: [...lastRunDepValues]
3574
+ };
3575
+ })() : {};
3576
+ if (t === DATA) {
3577
+ result.values[path] = m[1];
3578
+ result.events.push({ type: "data", path, data: m[1], ...base, ...withCausal });
3579
+ } else if (t === DIRTY) {
3580
+ result.dirtyCount++;
3581
+ result.events.push({ type: "dirty", path, ...base });
3582
+ } else if (t === RESOLVED) {
3583
+ result.resolvedCount++;
3584
+ result.events.push({ type: "resolved", path, ...base, ...withCausal });
3585
+ } else if (t === COMPLETE) {
3586
+ if (!result.errored) result.completedCleanly = true;
3587
+ result.events.push({ type: "complete", path, ...base });
3588
+ } else if (t === ERROR) {
3589
+ result.errored = true;
3590
+ result.events.push({ type: "error", path, data: m[1], ...base });
3591
+ }
3592
+ }
3593
+ });
3594
+ return {
3595
+ get values() {
3596
+ return result.values;
3597
+ },
3598
+ get dirtyCount() {
3599
+ return result.dirtyCount;
3600
+ },
3601
+ get resolvedCount() {
3602
+ return result.resolvedCount;
3603
+ },
3604
+ get events() {
3605
+ return result.events;
3606
+ },
3607
+ get completedCleanly() {
3608
+ return result.completedCleanly;
3609
+ },
3610
+ get errored() {
3611
+ return result.errored;
3612
+ },
3613
+ dispose() {
3614
+ unsub();
3615
+ detachInspectorHook?.();
3616
+ }
3617
+ };
3618
+ }
3619
+ _createObserveResultForAll(options) {
3620
+ const timeline = options.timeline === true;
3621
+ const result = {
3622
+ values: {},
3623
+ dirtyCount: 0,
3624
+ resolvedCount: 0,
3625
+ events: [],
3626
+ completedCleanly: false,
3627
+ errored: false
3628
+ };
3629
+ const actor = options.actor;
3630
+ const targets = [];
3631
+ this._collectObserveTargets("", targets);
3632
+ targets.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
3633
+ const picked = actor == null ? targets : targets.filter(([, nd]) => nd.allowsObserve(actor));
3634
+ const unsubs = picked.map(
3635
+ ([path, nd]) => nd.subscribe((msgs) => {
3636
+ for (const m of msgs) {
3637
+ const t = m[0];
3638
+ const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
3639
+ if (t === DATA) {
3640
+ result.values[path] = m[1];
3641
+ result.events.push({ type: "data", path, data: m[1], ...base });
3642
+ } else if (t === DIRTY) {
3643
+ result.dirtyCount++;
3644
+ result.events.push({ type: "dirty", path, ...base });
3645
+ } else if (t === RESOLVED) {
3646
+ result.resolvedCount++;
3647
+ result.events.push({ type: "resolved", path, ...base });
3648
+ } else if (t === COMPLETE) {
3649
+ if (!result.errored) result.completedCleanly = true;
3650
+ result.events.push({ type: "complete", path, ...base });
3651
+ } else if (t === ERROR) {
3652
+ result.errored = true;
3653
+ result.events.push({ type: "error", path, data: m[1], ...base });
3654
+ }
3655
+ }
3656
+ })
3657
+ );
3658
+ return {
3659
+ get values() {
3660
+ return result.values;
3661
+ },
3662
+ get dirtyCount() {
3663
+ return result.dirtyCount;
3664
+ },
3665
+ get resolvedCount() {
3666
+ return result.resolvedCount;
3667
+ },
3668
+ get events() {
3669
+ return result.events;
3670
+ },
3671
+ get completedCleanly() {
3672
+ return result.completedCleanly;
3673
+ },
3674
+ get errored() {
3675
+ return result.errored;
3676
+ },
3677
+ dispose() {
3678
+ for (const u of unsubs) u();
3679
+ }
3680
+ };
3681
+ }
3682
+ /**
3683
+ * Convenience live debugger over {@link Graph.observe}. Logs protocol events as they flow.
3684
+ *
3685
+ * Supports one-node (`path`) and graph-wide modes, event filtering, and JSON/pretty rendering.
3686
+ * Color themes are built in (`ansi` / `none`) to avoid external dependencies.
3687
+ *
3688
+ * @param options - Spy configuration.
3689
+ * @returns Disposable handle plus a structured observation accumulator.
3690
+ */
3691
+ spy(options = {}) {
3692
+ const include = options.includeTypes ? new Set(options.includeTypes) : null;
3693
+ const exclude = options.excludeTypes ? new Set(options.excludeTypes) : null;
3694
+ const theme = resolveSpyTheme(options.theme);
3695
+ const format = options.format ?? "pretty";
3696
+ const logger = options.logger ?? ((line) => console.log(line));
3697
+ const shouldLog = (type) => {
3698
+ if (include?.has(type) === false) return false;
3699
+ if (exclude?.has(type) === true) return false;
3700
+ return true;
3701
+ };
3702
+ const renderEvent = (event) => {
3703
+ if (format === "json") {
3704
+ try {
3705
+ return JSON.stringify(event);
3706
+ } catch {
3707
+ return JSON.stringify({
3708
+ type: event.type,
3709
+ path: event.path,
3710
+ data: "[unserializable]"
3711
+ });
3712
+ }
3713
+ }
3714
+ const color = theme[event.type] ?? "";
3715
+ const pathPart = event.path ? `${theme.path}${event.path}${theme.reset} ` : "";
3716
+ const dataPart = event.data !== void 0 ? ` ${describeData(event.data)}` : "";
3717
+ const triggerPart = event.trigger_dep_name != null ? ` <- ${event.trigger_dep_name}` : event.trigger_dep_index != null ? ` <- #${event.trigger_dep_index}` : "";
3718
+ const batchPart = event.in_batch ? " [batch]" : "";
3719
+ return `${pathPart}${color}${event.type.toUpperCase()}${theme.reset}${dataPart}${triggerPart}${batchPart}`;
3720
+ };
3721
+ if (!_Graph.inspectorEnabled) {
3722
+ const timeline = options.timeline ?? true;
3723
+ const acc = {
3724
+ values: {},
3725
+ dirtyCount: 0,
3726
+ resolvedCount: 0,
3727
+ events: [],
3728
+ completedCleanly: false,
3729
+ errored: false
3730
+ };
3731
+ let stop2 = () => {
3732
+ };
3733
+ const result2 = {
3734
+ get values() {
3735
+ return acc.values;
3736
+ },
3737
+ get dirtyCount() {
3738
+ return acc.dirtyCount;
3739
+ },
3740
+ get resolvedCount() {
3741
+ return acc.resolvedCount;
3742
+ },
3743
+ get events() {
3744
+ return acc.events;
3745
+ },
3746
+ get completedCleanly() {
3747
+ return acc.completedCleanly;
3748
+ },
3749
+ get errored() {
3750
+ return acc.errored;
3751
+ },
3752
+ dispose() {
3753
+ stop2();
3754
+ }
3755
+ };
3756
+ const pushEvent = (path, message) => {
3757
+ const t = message[0];
3758
+ const base = timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching() } : {};
3759
+ let event;
3760
+ if (t === DATA) {
3761
+ if (path != null) acc.values[path] = message[1];
3762
+ event = { type: "data", ...path != null ? { path } : {}, data: message[1], ...base };
3763
+ } else if (t === DIRTY) {
3764
+ acc.dirtyCount += 1;
3765
+ event = { type: "dirty", ...path != null ? { path } : {}, ...base };
3766
+ } else if (t === RESOLVED) {
3767
+ acc.resolvedCount += 1;
3768
+ event = { type: "resolved", ...path != null ? { path } : {}, ...base };
3769
+ } else if (t === COMPLETE) {
3770
+ if (!acc.errored) acc.completedCleanly = true;
3771
+ event = { type: "complete", ...path != null ? { path } : {}, ...base };
3772
+ } else if (t === ERROR) {
3773
+ acc.errored = true;
3774
+ event = {
3775
+ type: "error",
3776
+ ...path != null ? { path } : {},
3777
+ data: message[1],
3778
+ ...base
3779
+ };
3780
+ }
3781
+ if (!event) return;
3782
+ acc.events.push(event);
3783
+ if (!shouldLog(event.type)) return;
3784
+ logger(renderEvent(event), event);
3785
+ };
3786
+ if (options.path != null) {
3787
+ const stream2 = this.observe(options.path, {
3788
+ actor: options.actor,
3789
+ structured: false
3790
+ });
3791
+ stop2 = stream2.subscribe((messages) => {
3792
+ for (const m of messages) {
3793
+ pushEvent(options.path, m);
3794
+ }
3795
+ });
3796
+ } else {
3797
+ const stream2 = this.observe({ actor: options.actor, structured: false });
3798
+ stop2 = stream2.subscribe((path, messages) => {
3799
+ for (const m of messages) {
3800
+ pushEvent(path, m);
3801
+ }
3802
+ });
3803
+ }
3804
+ return {
3805
+ result: result2,
3806
+ dispose() {
3807
+ result2.dispose();
3808
+ }
3809
+ };
3810
+ }
3811
+ const structuredObserveOptions = {
3812
+ actor: options.actor,
3813
+ structured: true,
3814
+ ...options.timeline !== false ? { timeline: true } : {},
3815
+ ...options.causal ? { causal: true } : {},
3816
+ ...options.derived ? { derived: true } : {}
3817
+ };
3818
+ const result = options.path != null ? this.observe(options.path, structuredObserveOptions) : this.observe(structuredObserveOptions);
3819
+ let cursor = 0;
3820
+ const flushNewEvents = () => {
3821
+ const nextEvents = result.events.slice(cursor);
3822
+ cursor = result.events.length;
3823
+ for (const event of nextEvents) {
3824
+ if (!shouldLog(event.type)) continue;
3825
+ logger(renderEvent(event), event);
3826
+ }
3827
+ };
3828
+ const stream = options.path != null ? this.observe(options.path, { actor: options.actor, structured: false }) : this.observe({ actor: options.actor, structured: false });
3829
+ const stop = options.path != null ? stream.subscribe((messages) => {
3830
+ if (messages.length > 0) {
3831
+ flushNewEvents();
3832
+ }
3833
+ }) : stream.subscribe((_path, messages) => {
3834
+ if (messages.length > 0) {
3835
+ flushNewEvents();
3836
+ }
3837
+ });
3838
+ return {
3839
+ result,
3840
+ dispose() {
3841
+ stop();
3842
+ flushNewEvents();
3843
+ result.dispose();
3844
+ }
3845
+ };
3846
+ }
3847
+ /**
3848
+ * CLI/debug-friendly graph dump built on {@link Graph.describe}.
3849
+ *
3850
+ * @param options - Optional actor/filter/format toggles.
3851
+ * @returns Rendered graph text.
3852
+ */
3853
+ dumpGraph(options = {}) {
3854
+ const described = this.describe({
3855
+ actor: options.actor,
3856
+ filter: options.filter
3857
+ });
3858
+ const includeEdges = options.includeEdges ?? true;
3859
+ const includeSubgraphs = options.includeSubgraphs ?? true;
3860
+ if (options.format === "json") {
3861
+ const payload = {
3862
+ name: described.name,
3863
+ nodes: described.nodes,
3864
+ edges: includeEdges ? described.edges : [],
3865
+ subgraphs: includeSubgraphs ? described.subgraphs : []
3866
+ };
3867
+ const text2 = JSON.stringify(sortJsonValue(payload), null, options.indent ?? 2);
3868
+ options.logger?.(text2);
3869
+ return text2;
3870
+ }
3871
+ const lines = [];
3872
+ lines.push(`Graph ${described.name}`);
3873
+ lines.push("Nodes:");
3874
+ for (const path of Object.keys(described.nodes).sort()) {
3875
+ const n = described.nodes[path];
3876
+ lines.push(`- ${path} (${n.type}/${n.status}): ${describeData(n.value)}`);
3877
+ }
3878
+ if (includeEdges) {
3879
+ lines.push("Edges:");
3880
+ for (const edge of described.edges) {
3881
+ lines.push(`- ${edge.from} -> ${edge.to}`);
3882
+ }
3883
+ }
3884
+ if (includeSubgraphs) {
3885
+ lines.push("Subgraphs:");
3886
+ for (const sg of described.subgraphs) {
3887
+ lines.push(`- ${sg}`);
3888
+ }
3889
+ }
3890
+ const text = lines.join("\n");
3891
+ options.logger?.(text);
3892
+ return text;
3893
+ }
3894
+ // ——————————————————————————————————————————————————————————————
3895
+ // Lifecycle & persistence (§3.7–§3.8)
3896
+ // ——————————————————————————————————————————————————————————————
3897
+ /**
3898
+ * Sends `[[TEARDOWN]]` to all nodes, then clears registries on this graph and every
3899
+ * mounted subgraph (§3.7). The instance is left empty and may be reused with {@link Graph.add}.
3900
+ */
3901
+ destroy() {
3902
+ this.signal([[TEARDOWN]], { internal: true });
3903
+ for (const dispose of [...this._autoCheckpointDisposers]) {
3904
+ try {
3905
+ dispose();
3906
+ } catch {
3907
+ }
3908
+ }
3909
+ this._autoCheckpointDisposers.clear();
3910
+ for (const child of [...this._mounts.values()]) {
3911
+ child._destroyClearOnly();
3912
+ }
3913
+ this._mounts.clear();
3914
+ this._nodes.clear();
3915
+ this._edges.clear();
3916
+ }
3917
+ /** Clear structure after parent already signaled TEARDOWN through this subtree. */
3918
+ _destroyClearOnly() {
3919
+ for (const child of [...this._mounts.values()]) {
3920
+ child._destroyClearOnly();
3921
+ }
3922
+ this._mounts.clear();
3923
+ this._nodes.clear();
3924
+ this._edges.clear();
3925
+ }
3926
+ /**
3927
+ * Serializes structure and current values to JSON-shaped data (§3.8). Same information
3928
+ * as {@link Graph.describe} plus a `version` field for format evolution.
3929
+ *
3930
+ * @returns Persistable snapshot with sorted keys.
3931
+ */
3932
+ snapshot() {
3933
+ const d = this.describe();
3934
+ const sortedNodes = {};
3935
+ for (const key of Object.keys(d.nodes).sort()) {
3936
+ sortedNodes[key] = d.nodes[key];
3937
+ }
3938
+ const sortedSubgraphs = [...d.subgraphs].sort();
3939
+ return { ...d, version: 1, nodes: sortedNodes, subgraphs: sortedSubgraphs };
3940
+ }
3941
+ /**
3942
+ * Apply persisted values onto an existing graph whose topology matches the snapshot
3943
+ * (§3.8). Only {@link DescribeNodeOutput.type} `state` and `producer` entries with a
3944
+ * `value` field are written; `derived` / `operator` / `effect` are skipped so deps
3945
+ * drive recomputation. Unknown paths are ignored.
3946
+ *
3947
+ * @param data - Snapshot envelope with matching `name` and node slices.
3948
+ * @throws If `data.name` does not equal {@link Graph.name}.
3949
+ */
3950
+ restore(data, options) {
3951
+ parseSnapshotEnvelope(data);
3952
+ if (data.name !== this.name) {
3953
+ throw new Error(
3954
+ `Graph "${this.name}": restore snapshot name "${data.name}" does not match this graph`
3955
+ );
3956
+ }
3957
+ const onlyPatterns = options?.only == null ? null : (Array.isArray(options.only) ? options.only : [options.only]).map((p) => globToRegex(p));
3958
+ for (const path of Object.keys(data.nodes).sort()) {
3959
+ if (onlyPatterns !== null && !onlyPatterns.some((re) => re.test(path))) continue;
3960
+ const slice = data.nodes[path];
3961
+ if (slice === void 0 || slice.value === void 0) continue;
3962
+ if (slice.type === "derived" || slice.type === "operator" || slice.type === "effect") {
3963
+ continue;
3964
+ }
3965
+ try {
3966
+ this.set(path, slice.value);
3967
+ } catch {
3968
+ }
3969
+ }
3970
+ }
3971
+ /**
3972
+ * Creates a graph named from the snapshot, optionally runs `build` to register nodes
3973
+ * and mounts, then {@link Graph.restore} values (§3.8).
3974
+ *
3975
+ * @param data - Snapshot envelope (`version` checked).
3976
+ * @param build - Optional callback to construct topology before values are applied.
3977
+ * @returns Hydrated `Graph` instance.
3978
+ */
3979
+ static fromSnapshot(data, build) {
3980
+ parseSnapshotEnvelope(data);
3981
+ const g = new _Graph(data.name);
3982
+ if (build) {
3983
+ build(g);
3984
+ g.restore(data);
3985
+ return g;
3986
+ }
3987
+ for (const mount of [...data.subgraphs].sort((a, b) => {
3988
+ const da = a.split(PATH_SEP).length;
3989
+ const db = b.split(PATH_SEP).length;
3990
+ if (da !== db) return da - db;
3991
+ if (a < b) return -1;
3992
+ if (a > b) return 1;
3993
+ return 0;
3994
+ })) {
3995
+ const parts = mount.split(PATH_SEP);
3996
+ let target = g;
3997
+ for (const seg of parts) {
3998
+ if (!target._mounts.has(seg)) {
3999
+ target.mount(seg, new _Graph(seg));
4000
+ }
4001
+ target = target._mounts.get(seg);
4002
+ }
4003
+ }
4004
+ 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);
4005
+ const pending = new Map(primaryEntries);
4006
+ const created = /* @__PURE__ */ new Map();
4007
+ let progressed = true;
4008
+ while (pending.size > 0 && progressed) {
4009
+ progressed = false;
4010
+ for (const [path, slice] of [...pending.entries()]) {
4011
+ const deps = slice?.deps ?? [];
4012
+ if (!deps.every((dep) => created.has(dep))) continue;
4013
+ const [owner, localName] = _Graph._ownerForPath(g, path);
4014
+ const meta = { ...slice?.meta ?? {} };
4015
+ const factory = _Graph._factoryForPath(path);
4016
+ let node2;
4017
+ if (slice?.type === "state") {
4018
+ node2 = state(slice.value, { meta });
4019
+ } else {
4020
+ if (factory == null) continue;
4021
+ node2 = factory(localName, {
4022
+ path,
4023
+ type: slice.type,
4024
+ value: slice.value,
4025
+ meta,
4026
+ deps,
4027
+ resolvedDeps: deps.map((dep) => created.get(dep))
4028
+ });
4029
+ }
4030
+ owner.add(localName, node2);
4031
+ created.set(path, node2);
4032
+ pending.delete(path);
4033
+ progressed = true;
4034
+ }
4035
+ }
4036
+ if (pending.size > 0) {
4037
+ const unresolved = [...pending.keys()].sort().join(", ");
4038
+ throw new Error(
4039
+ `Graph.fromSnapshot could not reconstruct nodes without build callback: ${unresolved}. Register matching factories with Graph.registerFactory(pattern, factory).`
4040
+ );
4041
+ }
4042
+ for (const edge of data.edges) {
4043
+ try {
4044
+ g.connect(edge.from, edge.to);
4045
+ } catch {
4046
+ }
4047
+ }
4048
+ g.restore(data);
4049
+ return g;
4050
+ }
4051
+ /**
4052
+ * Plain snapshot with **recursively sorted object keys** for deterministic serialization (§3.8).
4053
+ *
4054
+ * @remarks
4055
+ * ECMAScript `JSON.stringify(graph)` invokes this method; it must return a plain object, not an
4056
+ * already-stringified JSON string (otherwise the graph would be double-encoded).
4057
+ * For a single UTF-8 string with a trailing newline (convenient for git), use {@link Graph.toJSONString}.
4058
+ *
4059
+ * @returns Same object as {@link Graph.snapshot}.
4060
+ */
4061
+ toJSON() {
4062
+ return this.snapshot();
4063
+ }
4064
+ /**
4065
+ * Deterministic JSON **text**: `JSON.stringify` of {@link Graph.toJSON} plus a trailing newline (§3.8).
4066
+ *
4067
+ * @returns Stable string suitable for diffs.
4068
+ */
4069
+ toJSONString() {
4070
+ return stableJsonStringify(this.snapshot());
4071
+ }
4072
+ /**
4073
+ * Debounced persistence wired to graph-wide observe stream (spec §3.8 auto-checkpoint).
4074
+ *
4075
+ * Checkpoint trigger uses {@link messageTier}: only batches containing tier >= 2 messages
4076
+ * schedule a save (`DATA`/`RESOLVED`/terminal/destruction), never pure tier-0/1 control waves.
4077
+ */
4078
+ autoCheckpoint(adapter, options = {}) {
4079
+ const debounceMs = Math.max(0, options.debounceMs ?? 500);
4080
+ const compactEvery = Math.max(1, options.compactEvery ?? 10);
4081
+ let timer;
4082
+ let seq = 0;
4083
+ let pending = false;
4084
+ let lastDescribe;
4085
+ const flush = () => {
4086
+ timer = void 0;
4087
+ if (!pending) return;
4088
+ pending = false;
4089
+ try {
4090
+ const described = this.describe();
4091
+ const snapshot = { ...described, version: SNAPSHOT_VERSION };
4092
+ seq += 1;
4093
+ const shouldCompact = lastDescribe == null || seq % compactEvery === 0;
4094
+ if (shouldCompact) {
4095
+ adapter.save({ mode: "full", snapshot, seq });
4096
+ } else {
4097
+ const previous = lastDescribe;
4098
+ if (previous == null) return;
4099
+ adapter.save({
4100
+ mode: "diff",
4101
+ diff: _Graph.diff(previous, described),
4102
+ snapshot,
4103
+ seq
4104
+ });
4105
+ }
4106
+ lastDescribe = described;
4107
+ } catch (error) {
4108
+ options.onError?.(error);
4109
+ }
4110
+ };
4111
+ const schedule = () => {
4112
+ pending = true;
4113
+ if (timer !== void 0) clearTimeout(timer);
4114
+ timer = setTimeout(flush, debounceMs);
4115
+ };
4116
+ const off = this.observe().subscribe((path, messages) => {
4117
+ const triggeredByTier = messages.some((m) => messageTier(m[0]) >= 2);
4118
+ if (!triggeredByTier) return;
4119
+ if (options.filter) {
4120
+ const described = this.describe().nodes[path];
4121
+ if (described == null || !options.filter(path, described)) return;
4122
+ }
4123
+ schedule();
4124
+ });
4125
+ const dispose = () => {
4126
+ off();
4127
+ if (timer !== void 0) {
4128
+ clearTimeout(timer);
4129
+ timer = void 0;
4130
+ }
4131
+ this._autoCheckpointDisposers.delete(dispose);
4132
+ };
4133
+ this._autoCheckpointDisposers.add(dispose);
4134
+ return { dispose };
4135
+ }
4136
+ /**
4137
+ * Export the current graph topology as Mermaid flowchart text.
4138
+ *
4139
+ * Renders qualified node paths and registered edges from {@link Graph.describe}.
4140
+ *
4141
+ * @param options - Optional diagram direction (`LR` by default).
4142
+ * @returns Mermaid flowchart source.
4143
+ */
4144
+ toMermaid(options) {
4145
+ const direction = normalizeDiagramDirection(options?.direction);
4146
+ const described = this.describe();
4147
+ const paths = Object.keys(described.nodes).sort();
4148
+ const ids = /* @__PURE__ */ new Map();
4149
+ for (let i = 0; i < paths.length; i += 1) {
4150
+ ids.set(paths[i], `n${i}`);
4151
+ }
4152
+ const lines = [`flowchart ${direction}`];
4153
+ for (const path of paths) {
4154
+ const id = ids.get(path);
4155
+ lines.push(` ${id}["${escapeMermaidLabel(path)}"]`);
4156
+ }
4157
+ for (const [from, to] of collectDiagramArrows(described)) {
4158
+ const fromId = ids.get(from);
4159
+ const toId = ids.get(to);
4160
+ if (!fromId || !toId) continue;
4161
+ lines.push(` ${fromId} --> ${toId}`);
4162
+ }
4163
+ return lines.join("\n");
4164
+ }
4165
+ /**
4166
+ * Export the current graph topology as D2 diagram text.
4167
+ *
4168
+ * Renders qualified node paths, constructor deps, and registered edges from {@link Graph.describe}.
4169
+ *
4170
+ * @param options - Optional diagram direction (`LR` by default).
4171
+ * @returns D2 source text.
4172
+ */
4173
+ toD2(options) {
4174
+ const direction = normalizeDiagramDirection(options?.direction);
4175
+ const described = this.describe();
4176
+ const paths = Object.keys(described.nodes).sort();
4177
+ const ids = /* @__PURE__ */ new Map();
4178
+ for (let i = 0; i < paths.length; i += 1) {
4179
+ ids.set(paths[i], `n${i}`);
4180
+ }
4181
+ const lines = [`direction: ${d2DirectionFromGraphDirection(direction)}`];
4182
+ for (const path of paths) {
4183
+ const id = ids.get(path);
4184
+ lines.push(`${id}: "${escapeD2Label(path)}"`);
4185
+ }
4186
+ for (const [from, to] of collectDiagramArrows(described)) {
4187
+ const fromId = ids.get(from);
4188
+ const toId = ids.get(to);
4189
+ if (!fromId || !toId) continue;
4190
+ lines.push(`${fromId} -> ${toId}`);
4191
+ }
4192
+ return lines.join("\n");
4193
+ }
4194
+ // ——————————————————————————————————————————————————————————————
4195
+ // Inspector (roadmap 3.3) — reasoning trace, overhead gating
4196
+ // ——————————————————————————————————————————————————————————————
4197
+ /**
4198
+ * When `false`, structured observation options (`causal`, `timeline`),
4199
+ * `annotate()`, and `traceLog()` are no-ops. Raw `observe()` always works.
4200
+ *
4201
+ * Default: `true` outside production (`process.env.NODE_ENV !== "production"`).
4202
+ */
4203
+ static inspectorEnabled = !(typeof process !== "undefined" && process.env?.NODE_ENV === "production");
4204
+ _annotations = /* @__PURE__ */ new Map();
4205
+ _traceRing = new RingBuffer(1e3);
4206
+ /**
4207
+ * Attaches a reasoning annotation to a node — captures *why* an AI agent set a value.
4208
+ *
4209
+ * No-op when {@link Graph.inspectorEnabled} is `false`.
4210
+ *
4211
+ * @param path - Qualified node path.
4212
+ * @param reason - Free-text note stored in the trace ring buffer.
4213
+ */
4214
+ annotate(path, reason) {
4215
+ if (!_Graph.inspectorEnabled) return;
4216
+ this.resolve(path);
4217
+ this._annotations.set(path, reason);
4218
+ this._traceRing.push({ path, reason, timestamp_ns: monotonicNs() });
4219
+ }
4220
+ /**
4221
+ * Returns a chronological log of all reasoning annotations (ring buffer).
4222
+ *
4223
+ * @returns `[]` when {@link Graph.inspectorEnabled} is `false`.
4224
+ */
4225
+ traceLog() {
4226
+ if (!_Graph.inspectorEnabled) return [];
4227
+ return this._traceRing.toArray();
4228
+ }
4229
+ /**
4230
+ * Computes structural + value diff between two {@link Graph.describe} snapshots.
4231
+ *
4232
+ * @param a - Earlier describe output.
4233
+ * @param b - Later describe output.
4234
+ * @returns Added/removed nodes, changed fields, and edge deltas.
4235
+ */
4236
+ static diff(a, b) {
4237
+ const aKeys = new Set(Object.keys(a.nodes));
4238
+ const bKeys = new Set(Object.keys(b.nodes));
4239
+ const nodesAdded = [...bKeys].filter((k) => !aKeys.has(k)).sort();
4240
+ const nodesRemoved = [...aKeys].filter((k) => !bKeys.has(k)).sort();
4241
+ const nodesChanged = [];
4242
+ for (const key of aKeys) {
4243
+ if (!bKeys.has(key)) continue;
4244
+ const na = a.nodes[key];
4245
+ const nb = b.nodes[key];
4246
+ const av = na.v;
4247
+ const bv = nb.v;
4248
+ if (av != null && bv != null && av.id === bv.id && av.version === bv.version) {
4249
+ for (const field of ["type", "status"]) {
4250
+ const va = na[field];
4251
+ const vb = nb[field];
4252
+ if (va !== vb) {
4253
+ nodesChanged.push({ path: key, field, from: va, to: vb });
4254
+ }
4255
+ }
4256
+ continue;
4257
+ }
4258
+ for (const field of ["type", "status", "value"]) {
4259
+ const va = na[field];
4260
+ const vb = nb[field];
4261
+ if (!Object.is(va, vb) && JSON.stringify(va) !== JSON.stringify(vb)) {
4262
+ nodesChanged.push({ path: key, field, from: va, to: vb });
4263
+ }
4264
+ }
4265
+ }
4266
+ const edgeKey2 = (e) => `${e.from} ${e.to}`;
4267
+ const aEdges = new Set(a.edges.map(edgeKey2));
4268
+ const bEdges = new Set(b.edges.map(edgeKey2));
4269
+ const edgesAdded = b.edges.filter((e) => !aEdges.has(edgeKey2(e)));
4270
+ const edgesRemoved = a.edges.filter((e) => !bEdges.has(edgeKey2(e)));
4271
+ const aSubgraphs = new Set(a.subgraphs);
4272
+ const bSubgraphs = new Set(b.subgraphs);
4273
+ const subgraphsAdded = [...bSubgraphs].filter((s) => !aSubgraphs.has(s)).sort();
4274
+ const subgraphsRemoved = [...aSubgraphs].filter((s) => !bSubgraphs.has(s)).sort();
4275
+ return {
4276
+ nodesAdded,
4277
+ nodesRemoved,
4278
+ nodesChanged,
4279
+ edgesAdded,
4280
+ edgesRemoved,
4281
+ subgraphsAdded,
4282
+ subgraphsRemoved
4283
+ };
4284
+ }
4285
+ };
4286
+
4287
+ // src/extra/reactive-base.ts
4288
+ function snapshotEqualsVersion(a, b) {
4289
+ if (typeof a !== "object" || a == null || typeof b !== "object" || b == null) {
4290
+ return Object.is(a, b);
4291
+ }
4292
+ if (!("version" in a) || !("version" in b)) return Object.is(a, b);
4293
+ return a.version === b.version;
4294
+ }
4295
+ function bumpVersion(current, nextValue, v0) {
4296
+ if (v0 != null) {
4297
+ return { version: current.version + 1, value: nextValue, v0 };
4298
+ }
4299
+ return { version: current.version + 1, value: nextValue };
4300
+ }
4301
+
4302
+ // src/extra/reactive-log.ts
4303
+ function emptySnapshot() {
4304
+ return { version: 0, value: { entries: [] } };
4305
+ }
4306
+ function keepaliveDerived(n) {
4307
+ return n.subscribe(() => {
4308
+ });
4309
+ }
4310
+ function reactiveLog(initial, options = {}) {
4311
+ const { name, maxSize } = options;
4312
+ if (maxSize !== void 0 && maxSize < 1) {
4313
+ throw new RangeError("maxSize must be >= 1");
4314
+ }
4315
+ const buf = initial ? [...initial] : [];
4316
+ if (maxSize !== void 0 && buf.length > maxSize) {
4317
+ buf.splice(0, buf.length - maxSize);
4318
+ }
4319
+ let current = buf.length > 0 ? { version: 1, value: { entries: [...buf] } } : emptySnapshot();
4320
+ const entries = state(current, {
4321
+ name,
4322
+ describeKind: "state",
4323
+ equals: snapshotEqualsVersion
4324
+ });
4325
+ function pushSnapshot() {
4326
+ const ev = entries.v;
4327
+ current = bumpVersion(
4328
+ current,
4329
+ { entries: [...buf] },
4330
+ ev ? { id: ev.id, version: ev.version } : void 0
4331
+ );
4332
+ batch(() => {
4333
+ entries.down([[DIRTY]]);
4334
+ entries.down([[DATA, current]]);
4335
+ });
4336
+ }
4337
+ function trimBuf() {
4338
+ if (maxSize !== void 0 && buf.length > maxSize) {
4339
+ buf.splice(0, buf.length - maxSize);
4340
+ }
4341
+ }
4342
+ const bundle = {
4343
+ entries,
4344
+ append(value) {
4345
+ buf.push(value);
4346
+ trimBuf();
4347
+ pushSnapshot();
4348
+ },
4349
+ appendMany(values) {
4350
+ if (values.length === 0) return;
4351
+ buf.push(...values);
4352
+ trimBuf();
4353
+ pushSnapshot();
4354
+ },
4355
+ clear() {
4356
+ if (buf.length === 0) return;
4357
+ buf.length = 0;
4358
+ pushSnapshot();
4359
+ },
4360
+ trimHead(n) {
4361
+ if (n < 0) {
4362
+ throw new RangeError("n must be >= 0");
4363
+ }
4364
+ if (n === 0) return;
4365
+ if (n >= buf.length) {
4366
+ if (buf.length === 0) return;
4367
+ buf.length = 0;
4368
+ } else {
4369
+ buf.splice(0, n);
4370
+ }
4371
+ pushSnapshot();
4372
+ },
4373
+ tail(n) {
4374
+ if (n < 0) {
4375
+ throw new RangeError("n must be >= 0");
4376
+ }
4377
+ const snap = entries.get();
4378
+ const e = snap.value.entries;
4379
+ const init = n === 0 ? [] : e.slice(Math.max(0, e.length - n));
4380
+ const out = derived(
4381
+ [entries],
4382
+ ([s]) => {
4383
+ const list = s.value.entries;
4384
+ return n === 0 ? [] : list.slice(Math.max(0, list.length - n));
4385
+ },
4386
+ { initial: init, describeKind: "derived" }
4387
+ );
4388
+ keepaliveDerived(out);
4389
+ return out;
4390
+ }
4391
+ };
4392
+ return bundle;
4393
+ }
4394
+
4395
+ // src/patterns/cqrs.ts
4396
+ var COMMAND_GUARD = policy((allow, deny) => {
4397
+ allow("write");
4398
+ allow("signal");
4399
+ deny("observe");
4400
+ });
4401
+ var PROJECTION_GUARD = policy((allow, deny) => {
4402
+ allow("observe");
4403
+ allow("signal");
4404
+ deny("write");
4405
+ });
4406
+ var EVENT_GUARD = policy((allow, deny) => {
4407
+ allow("observe");
4408
+ allow("signal");
4409
+ deny("write");
4410
+ });
4411
+ function cqrsMeta(kind, extra) {
4412
+ return { cqrs: true, cqrs_type: kind, ...extra ?? {} };
4413
+ }
4414
+ function keepalive(n) {
4415
+ return n.subscribe(() => {
4416
+ });
4417
+ }
4418
+ var CqrsGraph = class extends Graph {
4419
+ _eventLogs = /* @__PURE__ */ new Map();
4420
+ _commandHandlers = /* @__PURE__ */ new Map();
4421
+ _projections = /* @__PURE__ */ new Set();
4422
+ _sagas = /* @__PURE__ */ new Set();
4423
+ _keepaliveDisposers = [];
4424
+ _eventStore;
4425
+ _seq = 0;
4426
+ constructor(name, opts = {}) {
4427
+ super(name, opts.graph);
4428
+ }
4429
+ destroy() {
4430
+ for (const dispose of this._keepaliveDisposers) dispose();
4431
+ this._keepaliveDisposers.length = 0;
4432
+ super.destroy();
4433
+ }
4434
+ // -- Events ---------------------------------------------------------------
4435
+ /**
4436
+ * Register a named event stream backed by `reactiveLog`.
4437
+ * Guard denies external `write` — only commands append internally.
4438
+ */
4439
+ event(name) {
4440
+ const existing = this._eventLogs.get(name);
4441
+ if (existing) return existing.node;
4442
+ const log = reactiveLog([], { name });
4443
+ const entries = log.entries;
4444
+ const guarded = derived(
4445
+ [entries],
4446
+ ([snapshot]) => snapshot,
4447
+ {
4448
+ name,
4449
+ describeKind: "state",
4450
+ meta: cqrsMeta("event", { event_name: name }),
4451
+ guard: EVENT_GUARD,
4452
+ initial: entries.get()
4453
+ }
4454
+ );
4455
+ this.add(name, guarded);
4456
+ this._keepaliveDisposers.push(keepalive(guarded));
4457
+ this._eventLogs.set(name, { log, node: guarded });
4458
+ return guarded;
4459
+ }
4460
+ /** Internal: append to an event log, auto-registering if needed. */
4461
+ _appendEvent(eventName, payload) {
4462
+ let entry = this._eventLogs.get(eventName);
4463
+ if (!entry) {
4464
+ this.event(eventName);
4465
+ entry = this._eventLogs.get(eventName);
4466
+ }
4467
+ if (entry.node.status === "completed" || entry.node.status === "errored") {
4468
+ throw new Error(
4469
+ `Cannot dispatch to terminated event stream "${eventName}" (status: ${entry.node.status}).`
4470
+ );
4471
+ }
4472
+ const nv = entry.log.entries.v;
4473
+ const evt = {
4474
+ type: eventName,
4475
+ payload,
4476
+ timestampNs: wallClockNs(),
4477
+ seq: ++this._seq,
4478
+ ...nv != null ? { v0: { id: nv.id, version: nv.version } } : {}
4479
+ };
4480
+ entry.log.append(evt);
4481
+ if (this._eventStore) {
4482
+ this._eventStore.persist(evt);
4483
+ }
4484
+ }
4485
+ // -- Commands -------------------------------------------------------------
4486
+ /**
4487
+ * Register a command with its handler. Guard denies `observe` (write-only).
4488
+ * Use `dispatch(name, payload)` to execute.
4489
+ *
4490
+ * The command node carries dynamic `meta.error` — a reactive companion
4491
+ * that holds the last handler error (or `null` on success).
4492
+ */
4493
+ command(name, handler) {
4494
+ const cmdNode = state(void 0, {
4495
+ name,
4496
+ describeKind: "state",
4497
+ meta: {
4498
+ ...cqrsMeta("command", { command_name: name }),
4499
+ error: null
4500
+ },
4501
+ guard: COMMAND_GUARD
4502
+ });
4503
+ this.add(name, cmdNode);
4504
+ this._commandHandlers.set(name, handler);
4505
+ return cmdNode;
4506
+ }
4507
+ /**
4508
+ * Execute a registered command. Wraps the entire dispatch in `batch()` so
4509
+ * the command node DATA and all emitted events settle atomically.
4510
+ *
4511
+ * If the handler throws, `meta.error` on the command node is set to the
4512
+ * error and the exception is re-thrown.
4513
+ */
4514
+ dispatch(commandName, payload) {
4515
+ const handler = this._commandHandlers.get(commandName);
4516
+ if (!handler) {
4517
+ throw new Error(`Unknown command: "${commandName}". Register with .command() first.`);
4518
+ }
4519
+ const cmdNode = this.resolve(commandName);
4520
+ batch(() => {
4521
+ cmdNode.down([[DATA, payload]], { internal: true });
4522
+ try {
4523
+ handler(payload, { emit: (eName, data) => this._appendEvent(eName, data) });
4524
+ cmdNode.meta.error.down([[DATA, null]], { internal: true });
4525
+ } catch (err) {
4526
+ cmdNode.meta.error.down([[DATA, err]], { internal: true });
4527
+ throw err;
4528
+ }
4529
+ });
4530
+ }
4531
+ // -- Projections ----------------------------------------------------------
4532
+ /**
4533
+ * Register a read-only projection derived from event streams.
4534
+ * Guard denies `write` — value is computed from events only.
4535
+ *
4536
+ * **Purity contract:** The `reducer` must be a pure function — it receives
4537
+ * the original `initial` on every invocation (full event-sourcing replay).
4538
+ * Never mutate `initial`; always return a new value.
4539
+ */
4540
+ projection(name, eventNames, reducer, initial) {
4541
+ const eventNodes = eventNames.map((eName) => {
4542
+ if (!this._eventLogs.has(eName)) this.event(eName);
4543
+ return this._eventLogs.get(eName).node;
4544
+ });
4545
+ const projNode = derived(
4546
+ eventNodes,
4547
+ (snapshots) => {
4548
+ const allEvents = [];
4549
+ for (const snapshot of snapshots) {
4550
+ const snap = snapshot;
4551
+ allEvents.push(...snap.value.entries);
4552
+ }
4553
+ allEvents.sort((a, b) => a.timestampNs - b.timestampNs || a.seq - b.seq);
4554
+ return reducer(initial, allEvents);
4555
+ },
4556
+ {
4557
+ name,
4558
+ describeKind: "derived",
4559
+ meta: cqrsMeta("projection", { projection_name: name, source_events: eventNames }),
4560
+ guard: PROJECTION_GUARD,
4561
+ initial
4562
+ }
4563
+ );
4564
+ this.add(name, projNode);
4565
+ for (const eName of eventNames) this.connect(eName, name);
4566
+ this._keepaliveDisposers.push(keepalive(projNode));
4567
+ this._projections.add(name);
4568
+ return projNode;
4569
+ }
4570
+ // -- Sagas ----------------------------------------------------------------
4571
+ /**
4572
+ * Register an event-driven side effect. Runs handler for each **new** event
4573
+ * from the specified streams (tracks last-processed entry count per stream).
4574
+ *
4575
+ * The saga node carries dynamic `meta.error` — a reactive companion that
4576
+ * holds the last handler error (or `null` on success). Handler errors do
4577
+ * not propagate out of the saga run (the event cursor still advances so
4578
+ * the same entry is not delivered twice).
4579
+ */
4580
+ saga(name, eventNames, handler) {
4581
+ const eventNodes = eventNames.map((eName) => {
4582
+ if (!this._eventLogs.has(eName)) this.event(eName);
4583
+ return this._eventLogs.get(eName).node;
4584
+ });
4585
+ const lastCounts = /* @__PURE__ */ new Map();
4586
+ const sagaRef = {};
4587
+ const sagaNode = node(
4588
+ eventNodes,
4589
+ (snapshots) => {
4590
+ const errNode = sagaRef.n.meta.error;
4591
+ for (let i = 0; i < snapshots.length; i++) {
4592
+ const snap = snapshots[i];
4593
+ const eName = eventNames[i];
4594
+ const entries = snap.value.entries;
4595
+ const lastCount = lastCounts.get(eName) ?? 0;
4596
+ if (entries.length > lastCount) {
4597
+ const newEntries = entries.slice(lastCount);
4598
+ for (const entry of newEntries) {
4599
+ try {
4600
+ handler(entry);
4601
+ errNode.down([[DATA, null]], { internal: true });
4602
+ } catch (err) {
4603
+ errNode.down([[DATA, err]], { internal: true });
4604
+ }
4605
+ }
4606
+ lastCounts.set(eName, entries.length);
4607
+ }
4608
+ }
4609
+ },
4610
+ {
4611
+ name,
4612
+ describeKind: "effect",
4613
+ meta: {
4614
+ ...cqrsMeta("saga", { saga_name: name, source_events: eventNames }),
4615
+ error: null
4616
+ }
4617
+ }
4618
+ );
4619
+ sagaRef.n = sagaNode;
4620
+ this.add(name, sagaNode);
4621
+ for (const eName of eventNames) this.connect(eName, name);
4622
+ this._keepaliveDisposers.push(keepalive(sagaNode));
4623
+ this._sagas.add(name);
4624
+ return sagaNode;
4625
+ }
4626
+ // -- Event store ----------------------------------------------------------
4627
+ useEventStore(adapter) {
4628
+ this._eventStore = adapter;
4629
+ }
4630
+ /**
4631
+ * Replay persisted events through a reducer to rebuild a read model.
4632
+ * Requires an event store adapter wired via `useEventStore()`.
4633
+ */
4634
+ async rebuildProjection(eventNames, reducer, initial) {
4635
+ if (!this._eventStore) {
4636
+ throw new Error("No event store wired. Call useEventStore() first.");
4637
+ }
4638
+ const allEvents = [];
4639
+ for (const eName of eventNames) {
4640
+ const result = await this._eventStore.loadEvents(eName);
4641
+ allEvents.push(...result.events);
4642
+ }
4643
+ allEvents.sort((a, b) => a.timestampNs - b.timestampNs || a.seq - b.seq);
4644
+ return reducer(initial, allEvents);
4645
+ }
4646
+ };
4647
+ function cqrs(name, opts) {
4648
+ return new CqrsGraph(name, opts);
4649
+ }
4650
+
4651
+ // src/compat/nestjs/module.ts
4652
+ var GraphReflyRootLifecycle = class {
4653
+ constructor(graph) {
4654
+ this.graph = graph;
4655
+ }
4656
+ onModuleDestroy() {
4657
+ this.graph.destroy();
4658
+ }
4659
+ };
4660
+ var GraphReflyRequestLifecycle = class {
4661
+ graph = new Graph("request");
4662
+ onModuleDestroy() {
4663
+ this.graph.destroy();
4664
+ }
4665
+ };
4666
+ var _GraphReflyModule_decorators, _init;
4667
+ _GraphReflyModule_decorators = [(0, import_common2.Module)({})];
4668
+ var _GraphReflyModule = class _GraphReflyModule {
4669
+ /**
4670
+ * Register the root `Graph` singleton in the NestJS DI container.
4671
+ *
4672
+ * The root graph is `@Global()` — injectable everywhere without importing
4673
+ * the module again. Use `@InjectGraph()` to inject it.
4674
+ *
4675
+ * Lifecycle:
4676
+ * - **init:** Graph created in factory. If `build` is provided, it runs
4677
+ * first (registers nodes/mounts). If `snapshot` is provided, values
4678
+ * are restored via `graph.restore()`.
4679
+ * - **destroy:** Calls `graph.destroy()` — sends `[[TEARDOWN]]` to all
4680
+ * nodes, including mounted feature subgraphs (cascading teardown).
4681
+ */
4682
+ static forRoot(opts) {
4683
+ const options = opts ?? {};
4684
+ const graphName = options.name ?? "root";
4685
+ const providers = [
4686
+ {
4687
+ provide: GRAPHREFLY_ROOT_GRAPH,
4688
+ useFactory: () => {
4689
+ const g = new Graph(graphName);
4690
+ if (options.build) options.build(g);
4691
+ if (options.snapshot) g.restore(options.snapshot);
4692
+ return g;
4693
+ }
4694
+ },
4695
+ {
4696
+ provide: /* @__PURE__ */ Symbol.for("graphrefly:root-lifecycle"),
4697
+ useFactory: (graph) => new GraphReflyRootLifecycle(graph),
4698
+ inject: [GRAPHREFLY_ROOT_GRAPH]
4699
+ },
4700
+ {
4701
+ provide: GraphReflyEventExplorer,
4702
+ useFactory: (graph, moduleRef) => new GraphReflyEventExplorer(graph, moduleRef),
4703
+ inject: [GRAPHREFLY_ROOT_GRAPH, import_core2.ModuleRef]
4704
+ }
4705
+ ];
4706
+ if (options.nodes) {
4707
+ for (const path of options.nodes) {
4708
+ providers.push({
4709
+ provide: getNodeToken(path),
4710
+ useFactory: (graph) => graph.resolve(path),
4711
+ inject: [GRAPHREFLY_ROOT_GRAPH]
4712
+ });
4713
+ }
4714
+ }
4715
+ if (options.requestScope) {
4716
+ providers.push(
4717
+ {
4718
+ provide: /* @__PURE__ */ Symbol.for("graphrefly:request-lifecycle"),
4719
+ useFactory: () => new GraphReflyRequestLifecycle(),
4720
+ scope: import_common2.Scope.REQUEST
4721
+ },
4722
+ {
4723
+ provide: GRAPHREFLY_REQUEST_GRAPH,
4724
+ useFactory: (lifecycle) => lifecycle.graph,
4725
+ inject: [/* @__PURE__ */ Symbol.for("graphrefly:request-lifecycle")],
4726
+ scope: import_common2.Scope.REQUEST
4727
+ }
4728
+ );
4729
+ }
4730
+ return {
4731
+ module: _GraphReflyModule,
4732
+ global: true,
4733
+ providers,
4734
+ exports: [
4735
+ GRAPHREFLY_ROOT_GRAPH,
4736
+ ...(options.nodes ?? []).map(getNodeToken),
4737
+ ...options.requestScope ? [GRAPHREFLY_REQUEST_GRAPH] : []
4738
+ ]
4739
+ };
4740
+ }
4741
+ /**
4742
+ * Register a feature subgraph that auto-mounts into the root graph.
4743
+ *
4744
+ * The feature graph is created in the factory, built/restored, then
4745
+ * mounted into root via `root.mount(name, featureGraph)`. On app
4746
+ * shutdown, root's `graph.destroy()` cascades TEARDOWN through all
4747
+ * mounted subgraphs (no explicit remove needed).
4748
+ *
4749
+ * Node tokens are auto-qualified as `featureName::path` to prevent
4750
+ * collisions between features declaring nodes with the same local name.
4751
+ *
4752
+ * Injectable via `@InjectGraph(name)`.
4753
+ */
4754
+ static forFeature(opts) {
4755
+ const providers = [
4756
+ {
4757
+ provide: getGraphToken(opts.name),
4758
+ useFactory: (rootGraph) => {
4759
+ const g = new Graph(opts.name);
4760
+ if (opts.build) opts.build(g);
4761
+ if (opts.snapshot) g.restore(opts.snapshot);
4762
+ rootGraph.mount(opts.name, g);
4763
+ return g;
4764
+ },
4765
+ inject: [GRAPHREFLY_ROOT_GRAPH]
4766
+ }
4767
+ ];
4768
+ if (opts.nodes) {
4769
+ for (const path of opts.nodes) {
4770
+ providers.push({
4771
+ provide: getNodeToken(`${opts.name}::${path}`),
4772
+ useFactory: (graph) => graph.resolve(path),
4773
+ inject: [getGraphToken(opts.name)]
4774
+ });
4775
+ }
4776
+ }
4777
+ return {
4778
+ module: _GraphReflyModule,
4779
+ providers,
4780
+ exports: [
4781
+ getGraphToken(opts.name),
4782
+ ...(opts.nodes ?? []).map((p) => getNodeToken(`${opts.name}::${p}`))
4783
+ ]
4784
+ };
4785
+ }
4786
+ /**
4787
+ * Register a CQRS subgraph that auto-mounts into the root graph.
4788
+ *
4789
+ * Creates a `CqrsGraph` via the `cqrs()` factory (roadmap §4.5), mounts it
4790
+ * into the root graph, and exposes it for DI via `@InjectGraph(name)`.
4791
+ *
4792
+ * CQRS decorators (`@CommandHandler`, `@EventHandler`, `@QueryHandler`,
4793
+ * `@SagaHandler`) are discovered by the explorer and wired to this graph
4794
+ * on module init.
4795
+ *
4796
+ * @example
4797
+ * ```ts
4798
+ * GraphReflyModule.forCqrs({
4799
+ * name: "orders",
4800
+ * build: (g) => {
4801
+ * g.event("orderPlaced");
4802
+ * g.projection("orderCount", ["orderPlaced"], (_s, evts) => evts.length, 0);
4803
+ * },
4804
+ * })
4805
+ * ```
4806
+ */
4807
+ static forCqrs(opts) {
4808
+ const providers = [
4809
+ {
4810
+ provide: getGraphToken(opts.name),
4811
+ useFactory: (rootGraph) => {
4812
+ const g = cqrs(opts.name, opts.cqrs);
4813
+ if (opts.eventStore) g.useEventStore(opts.eventStore);
4814
+ if (opts.build) opts.build(g);
4815
+ rootGraph.mount(opts.name, g);
4816
+ return g;
4817
+ },
4818
+ inject: [GRAPHREFLY_ROOT_GRAPH]
4819
+ }
4820
+ ];
4821
+ if (opts.nodes) {
4822
+ for (const path of opts.nodes) {
4823
+ providers.push({
4824
+ provide: getNodeToken(`${opts.name}::${path}`),
4825
+ useFactory: (graph) => graph.resolve(path),
4826
+ inject: [getGraphToken(opts.name)]
4827
+ });
4828
+ }
4829
+ }
4830
+ return {
4831
+ module: _GraphReflyModule,
4832
+ providers,
4833
+ exports: [
4834
+ getGraphToken(opts.name),
4835
+ ...(opts.nodes ?? []).map((p) => getNodeToken(`${opts.name}::${p}`))
4836
+ ]
4837
+ };
4838
+ }
4839
+ };
4840
+ _init = __decoratorStart(null);
4841
+ _GraphReflyModule = __decorateElement(_init, 0, "GraphReflyModule", _GraphReflyModule_decorators, _GraphReflyModule);
4842
+ __runInitializers(_init, 1, _GraphReflyModule);
4843
+ var GraphReflyModule = _GraphReflyModule;
4844
+ // Annotate the CommonJS export names for ESM import in node:
4845
+ 0 && (module.exports = {
4846
+ ACTOR_KEY,
4847
+ COMMAND_HANDLERS,
4848
+ CQRS_EVENT_HANDLERS,
4849
+ CRON_HANDLERS,
4850
+ CommandHandler,
4851
+ EVENT_HANDLERS,
4852
+ EventHandler,
4853
+ GRAPHREFLY_REQUEST_GRAPH,
4854
+ GRAPHREFLY_ROOT_GRAPH,
4855
+ GraphCron,
4856
+ GraphInterval,
4857
+ GraphReflyEventExplorer,
4858
+ GraphReflyGuard,
4859
+ GraphReflyGuardImpl,
4860
+ GraphReflyModule,
4861
+ INTERVAL_HANDLERS,
4862
+ InjectCqrsGraph,
4863
+ InjectGraph,
4864
+ InjectNode,
4865
+ ObserveGateway,
4866
+ OnGraphEvent,
4867
+ QUERY_HANDLERS,
4868
+ QueryHandler,
4869
+ SAGA_HANDLERS,
4870
+ SagaHandler,
4871
+ fromHeader,
4872
+ fromJwtPayload,
4873
+ getActor,
4874
+ getGraphToken,
4875
+ getNodeToken,
4876
+ observeGraph$,
4877
+ observeNode$,
4878
+ observeSSE,
4879
+ observeSubscription,
4880
+ toMessages$,
4881
+ toObservable
4882
+ });
4883
+ //# sourceMappingURL=index.cjs.map