@graphrefly/graphrefly 0.21.0 → 0.23.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 (101) hide show
  1. package/README.md +7 -5
  2. package/dist/chunk-263BEJJO.js +115 -0
  3. package/dist/chunk-263BEJJO.js.map +1 -0
  4. package/dist/chunk-2GQLMQVJ.js +47 -0
  5. package/dist/chunk-2GQLMQVJ.js.map +1 -0
  6. package/dist/chunk-32N5A454.js +36 -0
  7. package/dist/chunk-32N5A454.js.map +1 -0
  8. package/dist/chunk-7TAQJHQV.js +103 -0
  9. package/dist/chunk-7TAQJHQV.js.map +1 -0
  10. package/dist/{chunk-VOQFK7YN.js → chunk-CWYPA63G.js} +109 -259
  11. package/dist/chunk-CWYPA63G.js.map +1 -0
  12. package/dist/{chunk-7IGHIFTT.js → chunk-HVBX5KIW.js} +15 -26
  13. package/dist/chunk-HVBX5KIW.js.map +1 -0
  14. package/dist/chunk-JFONSPNF.js +391 -0
  15. package/dist/chunk-JFONSPNF.js.map +1 -0
  16. package/dist/chunk-NZMBRXQV.js +2330 -0
  17. package/dist/chunk-NZMBRXQV.js.map +1 -0
  18. package/dist/{chunk-XWBVAO2R.js → chunk-PNUZM7PC.js} +20 -30
  19. package/dist/chunk-PNUZM7PC.js.map +1 -0
  20. package/dist/{chunk-ZTCDY5NQ.js → chunk-PX6PDUJ5.js} +34 -50
  21. package/dist/chunk-PX6PDUJ5.js.map +1 -0
  22. package/dist/chunk-XRFJJ2IU.js +2417 -0
  23. package/dist/chunk-XRFJJ2IU.js.map +1 -0
  24. package/dist/chunk-XTLYW4FR.js +6829 -0
  25. package/dist/chunk-XTLYW4FR.js.map +1 -0
  26. package/dist/compat/nestjs/index.cjs +3489 -2286
  27. package/dist/compat/nestjs/index.cjs.map +1 -1
  28. package/dist/compat/nestjs/index.d.cts +6 -4
  29. package/dist/compat/nestjs/index.d.ts +6 -4
  30. package/dist/compat/nestjs/index.js +10 -8
  31. package/dist/core/index.cjs +1706 -1217
  32. package/dist/core/index.cjs.map +1 -1
  33. package/dist/core/index.d.cts +3 -2
  34. package/dist/core/index.d.ts +3 -2
  35. package/dist/core/index.js +37 -34
  36. package/dist/extra/index.cjs +7519 -6125
  37. package/dist/extra/index.cjs.map +1 -1
  38. package/dist/extra/index.d.cts +4 -4
  39. package/dist/extra/index.d.ts +4 -4
  40. package/dist/extra/index.js +63 -34
  41. package/dist/graph/index.cjs +3199 -2212
  42. package/dist/graph/index.cjs.map +1 -1
  43. package/dist/graph/index.d.cts +5 -3
  44. package/dist/graph/index.d.ts +5 -3
  45. package/dist/graph/index.js +24 -11
  46. package/dist/graph-BtdSRHUc.d.cts +1128 -0
  47. package/dist/graph-CEO2FkLY.d.ts +1128 -0
  48. package/dist/{index-DuN3bhtm.d.ts → index-B0tfuXwV.d.cts} +1697 -586
  49. package/dist/index-BFGjXbiP.d.cts +315 -0
  50. package/dist/{index-CgSiUouz.d.ts → index-BPlWVAKY.d.cts} +4 -4
  51. package/dist/index-BUj3ASVe.d.cts +406 -0
  52. package/dist/{index-VHA43cGP.d.cts → index-C59uSJAH.d.cts} +2 -2
  53. package/dist/index-CkElcUY6.d.ts +315 -0
  54. package/dist/index-DSPc5rkv.d.ts +406 -0
  55. package/dist/{index-BjtlNirP.d.cts → index-DgscL7v0.d.ts} +4 -4
  56. package/dist/{index-SFzE_KTa.d.cts → index-RXN94sHK.d.ts} +1697 -586
  57. package/dist/{index-8a605sg9.d.ts → index-jEtF4N7L.d.ts} +2 -2
  58. package/dist/index.cjs +9947 -7949
  59. package/dist/index.cjs.map +1 -1
  60. package/dist/index.d.cts +214 -37
  61. package/dist/index.d.ts +214 -37
  62. package/dist/index.js +919 -648
  63. package/dist/index.js.map +1 -1
  64. package/dist/meta-3QjzotRv.d.ts +41 -0
  65. package/dist/meta-B-Lbs4-O.d.cts +41 -0
  66. package/dist/node-C7PD3sn9.d.cts +1188 -0
  67. package/dist/node-C7PD3sn9.d.ts +1188 -0
  68. package/dist/{observable-DcBwQY7t.d.ts → observable-EyO-moQY.d.ts} +1 -1
  69. package/dist/{observable-C8Kx_O6k.d.cts → observable-axpzv1K2.d.cts} +1 -1
  70. package/dist/patterns/reactive-layout/index.cjs +3205 -2138
  71. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  72. package/dist/patterns/reactive-layout/index.d.cts +5 -3
  73. package/dist/patterns/reactive-layout/index.d.ts +5 -3
  74. package/dist/patterns/reactive-layout/index.js +7 -4
  75. package/dist/storage-CHT5WE9m.d.ts +182 -0
  76. package/dist/storage-DIgAr7M_.d.cts +182 -0
  77. package/package.json +2 -1
  78. package/dist/chunk-2UDLYZHT.js +0 -2117
  79. package/dist/chunk-2UDLYZHT.js.map +0 -1
  80. package/dist/chunk-4MQ2J6IG.js +0 -1631
  81. package/dist/chunk-4MQ2J6IG.js.map +0 -1
  82. package/dist/chunk-7IGHIFTT.js.map +0 -1
  83. package/dist/chunk-DOSLSFKL.js +0 -162
  84. package/dist/chunk-DOSLSFKL.js.map +0 -1
  85. package/dist/chunk-ECN37NVS.js +0 -6227
  86. package/dist/chunk-ECN37NVS.js.map +0 -1
  87. package/dist/chunk-G66H6ZRK.js +0 -111
  88. package/dist/chunk-G66H6ZRK.js.map +0 -1
  89. package/dist/chunk-VOQFK7YN.js.map +0 -1
  90. package/dist/chunk-WZ2Z2CRV.js +0 -32
  91. package/dist/chunk-WZ2Z2CRV.js.map +0 -1
  92. package/dist/chunk-XWBVAO2R.js.map +0 -1
  93. package/dist/chunk-ZTCDY5NQ.js.map +0 -1
  94. package/dist/graph-KsTe57nI.d.cts +0 -750
  95. package/dist/graph-mILUUqW8.d.ts +0 -750
  96. package/dist/index-B2SvPEbc.d.ts +0 -257
  97. package/dist/index-BHfg_Ez3.d.ts +0 -629
  98. package/dist/index-Bc_diYYJ.d.cts +0 -629
  99. package/dist/index-UudxGnzc.d.cts +0 -257
  100. package/dist/meta-BnG7XAaE.d.cts +0 -778
  101. package/dist/meta-BnG7XAaE.d.ts +0 -778
@@ -0,0 +1,2330 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
+ }) : x)(function(x) {
13
+ if (typeof require !== "undefined") return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
21
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
22
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
23
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
24
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
25
+ var __runInitializers = (array, flags, self, value) => {
26
+ 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);
27
+ return value;
28
+ };
29
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
30
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
31
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
32
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
33
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
34
+ return __privateGet(this, extra);
35
+ }, set [name](x) {
36
+ return __privateSet(this, extra, x);
37
+ } }, name));
38
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
39
+ for (var i = decorators.length - 1; i >= 0; i--) {
40
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
41
+ if (k) {
42
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
43
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
44
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
45
+ }
46
+ 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;
47
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
48
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
49
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
50
+ }
51
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
52
+ };
53
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
54
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
55
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
56
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
57
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
58
+
59
+ // src/core/messages.ts
60
+ var START = /* @__PURE__ */ Symbol.for("graphrefly/START");
61
+ var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
62
+ var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
63
+ var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
64
+ var INVALIDATE = /* @__PURE__ */ Symbol.for("graphrefly/INVALIDATE");
65
+ var PAUSE = /* @__PURE__ */ Symbol.for("graphrefly/PAUSE");
66
+ var RESUME = /* @__PURE__ */ Symbol.for("graphrefly/RESUME");
67
+ var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
68
+ var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
69
+ var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
70
+ var DIRTY_MSG = Object.freeze([DIRTY]);
71
+ var RESOLVED_MSG = Object.freeze([RESOLVED]);
72
+ var INVALIDATE_MSG = Object.freeze([INVALIDATE]);
73
+ var START_MSG = Object.freeze([START]);
74
+ var COMPLETE_MSG = Object.freeze([COMPLETE]);
75
+ var TEARDOWN_MSG = Object.freeze([TEARDOWN]);
76
+ var DIRTY_ONLY_BATCH = Object.freeze([DIRTY_MSG]);
77
+ var RESOLVED_ONLY_BATCH = Object.freeze([RESOLVED_MSG]);
78
+ var INVALIDATE_ONLY_BATCH = Object.freeze([INVALIDATE_MSG]);
79
+ var COMPLETE_ONLY_BATCH = Object.freeze([COMPLETE_MSG]);
80
+ var TEARDOWN_ONLY_BATCH = Object.freeze([TEARDOWN_MSG]);
81
+
82
+ // src/graph/codec.ts
83
+ var JsonCodec = {
84
+ name: "json",
85
+ version: 1,
86
+ contentType: "application/json",
87
+ encode(snapshot) {
88
+ const json = JSON.stringify(snapshot);
89
+ return new TextEncoder().encode(json);
90
+ },
91
+ decode(buffer, _codecVersion) {
92
+ const json = new TextDecoder().decode(buffer);
93
+ return JSON.parse(json);
94
+ }
95
+ };
96
+ function createDagCborCodec(dagCbor) {
97
+ return {
98
+ name: "dag-cbor",
99
+ version: 1,
100
+ contentType: "application/dag-cbor",
101
+ encode: (snapshot) => dagCbor.encode(snapshot),
102
+ decode: (buffer, _codecVersion) => dagCbor.decode(buffer)
103
+ };
104
+ }
105
+ function createDagCborZstdCodec(dagCbor, zstd) {
106
+ return {
107
+ name: "dag-cbor-zstd",
108
+ version: 1,
109
+ contentType: "application/dag-cbor+zstd",
110
+ encode: (snapshot) => zstd.compressSync(dagCbor.encode(snapshot)),
111
+ decode: (buffer, _codecVersion) => dagCbor.decode(zstd.decompressSync(buffer))
112
+ };
113
+ }
114
+ var ENVELOPE_VERSION = 1;
115
+ var ENVELOPE_MIN_LEN = 4;
116
+ function encodeEnvelope(codec, payload) {
117
+ const nameBytes = new TextEncoder().encode(codec.name);
118
+ if (nameBytes.length === 0 || nameBytes.length > 255) {
119
+ throw new Error(
120
+ `encodeEnvelope: codec name "${codec.name}" encodes to ${nameBytes.length} bytes (must be 1\u2013255)`
121
+ );
122
+ }
123
+ const cv = codec.version;
124
+ if (!Number.isInteger(cv) || cv < 0 || cv > 65535) {
125
+ throw new Error(
126
+ `encodeEnvelope: codec.version ${cv} out of u16 range (expected integer 0\u201365535)`
127
+ );
128
+ }
129
+ const totalLen = 1 + 1 + nameBytes.length + 2 + payload.length;
130
+ if (totalLen > 4294967295) {
131
+ throw new Error(
132
+ `encodeEnvelope: total envelope size ${totalLen} exceeds 2^32-1 bytes (payload ${payload.length} bytes)`
133
+ );
134
+ }
135
+ const out = new Uint8Array(totalLen);
136
+ let i = 0;
137
+ out[i++] = ENVELOPE_VERSION;
138
+ out[i++] = nameBytes.length;
139
+ out.set(nameBytes, i);
140
+ i += nameBytes.length;
141
+ out[i++] = cv >>> 8 & 255;
142
+ out[i++] = cv & 255;
143
+ out.set(payload, i);
144
+ return out;
145
+ }
146
+ function decodeEnvelope(bytes, config) {
147
+ if (bytes.length < ENVELOPE_MIN_LEN) {
148
+ throw new Error(`decodeEnvelope: bytes too short (${bytes.length} < ${ENVELOPE_MIN_LEN})`);
149
+ }
150
+ let i = 0;
151
+ const envVersion = bytes[i++];
152
+ if (envVersion !== ENVELOPE_VERSION) {
153
+ throw new Error(
154
+ `decodeEnvelope: unsupported envelope version ${envVersion} (expected ${ENVELOPE_VERSION})`
155
+ );
156
+ }
157
+ const nameLen = bytes[i++];
158
+ if (nameLen === 0) {
159
+ throw new Error("decodeEnvelope: name_len must be >= 1");
160
+ }
161
+ if (i + nameLen + 2 > bytes.length) {
162
+ throw new Error(
163
+ `decodeEnvelope: envelope truncated (need ${i + nameLen + 2} bytes, have ${bytes.length})`
164
+ );
165
+ }
166
+ const name = new TextDecoder().decode(bytes.subarray(i, i + nameLen));
167
+ i += nameLen;
168
+ const codecVersion = (bytes[i] << 8 | bytes[i + 1]) >>> 0;
169
+ i += 2;
170
+ const payload = bytes.subarray(i);
171
+ const codec = config.lookupCodec(name);
172
+ if (codec == null) {
173
+ throw new Error(
174
+ `decodeEnvelope: codec "${name}" not registered (envelope codec_v=${codecVersion})`
175
+ );
176
+ }
177
+ return { codec, codecVersion, payload };
178
+ }
179
+ function registerBuiltinCodecs(config) {
180
+ config.registerCodec(JsonCodec);
181
+ }
182
+ function replayWAL(entries) {
183
+ if (entries.length === 0) {
184
+ throw new Error("WAL is empty \u2014 need at least one full snapshot");
185
+ }
186
+ const first = entries[0];
187
+ if (first.mode !== "full") {
188
+ throw new Error("WAL must start with a full record carrying a baseline snapshot");
189
+ }
190
+ let result = JSON.parse(JSON.stringify(first.snapshot));
191
+ let prevSeq = first.seq;
192
+ for (let i = 1; i < entries.length; i++) {
193
+ const entry = entries[i];
194
+ if (entry.seq <= prevSeq) {
195
+ throw new Error(
196
+ `WAL chain broken at index ${i}: seq=${entry.seq} must exceed prev seq=${prevSeq}`
197
+ );
198
+ }
199
+ if (entry.mode === "full") {
200
+ result = JSON.parse(JSON.stringify(entry.snapshot));
201
+ prevSeq = entry.seq;
202
+ continue;
203
+ }
204
+ const diff = entry.diff;
205
+ for (const path of diff.nodesRemoved) {
206
+ delete result.nodes[path];
207
+ }
208
+ const addedFull = diff.nodesAddedFull;
209
+ if (addedFull != null) {
210
+ for (const [path, slice] of Object.entries(addedFull)) {
211
+ result.nodes[path] = JSON.parse(JSON.stringify(slice));
212
+ }
213
+ }
214
+ for (const change of diff.nodesChanged) {
215
+ const existing = result.nodes[change.path];
216
+ if (existing == null) continue;
217
+ existing[change.field] = change.to;
218
+ }
219
+ prevSeq = entry.seq;
220
+ }
221
+ return result;
222
+ }
223
+
224
+ // src/core/actor.ts
225
+ var DEFAULT_ACTOR = { type: "system", id: "" };
226
+ function normalizeActor(actor) {
227
+ if (actor == null) return DEFAULT_ACTOR;
228
+ const { type, id, ...rest } = actor;
229
+ return {
230
+ type: type ?? "system",
231
+ id: id ?? "",
232
+ ...rest
233
+ };
234
+ }
235
+
236
+ // src/core/batch.ts
237
+ var MAX_DRAIN_ITERATIONS = 1e3;
238
+ var batchDepth = 0;
239
+ var flushInProgress = false;
240
+ var drainPhase2 = [];
241
+ var drainPhase3 = [];
242
+ var drainPhase4 = [];
243
+ var flushHooks = [];
244
+ function isBatching() {
245
+ return batchDepth > 0 || flushInProgress;
246
+ }
247
+ function isExplicitlyBatching() {
248
+ return batchDepth > 0;
249
+ }
250
+ function registerBatchFlushHook(hook) {
251
+ if (batchDepth > 0) {
252
+ flushHooks.push(hook);
253
+ } else {
254
+ hook();
255
+ }
256
+ }
257
+ function batch(fn) {
258
+ batchDepth += 1;
259
+ let threw = false;
260
+ try {
261
+ fn();
262
+ } catch (e) {
263
+ threw = true;
264
+ throw e;
265
+ } finally {
266
+ batchDepth -= 1;
267
+ if (batchDepth === 0) {
268
+ if (threw) {
269
+ if (!flushInProgress) {
270
+ const hooks = flushHooks.splice(0);
271
+ for (const h of hooks) {
272
+ try {
273
+ h();
274
+ } catch {
275
+ }
276
+ }
277
+ drainPhase2.length = 0;
278
+ drainPhase3.length = 0;
279
+ drainPhase4.length = 0;
280
+ }
281
+ } else {
282
+ drainPending();
283
+ }
284
+ }
285
+ }
286
+ }
287
+ function drainPending() {
288
+ const ownsFlush = !flushInProgress;
289
+ if (ownsFlush) flushInProgress = true;
290
+ const errors = [];
291
+ let iterations = 0;
292
+ try {
293
+ while (drainPhase2.length > 0 || drainPhase3.length > 0 || drainPhase4.length > 0 || ownsFlush && flushHooks.length > 0) {
294
+ if (ownsFlush && flushHooks.length > 0) {
295
+ const hooks = flushHooks.splice(0);
296
+ for (const h of hooks) {
297
+ try {
298
+ h();
299
+ } catch (e) {
300
+ errors.push(e);
301
+ }
302
+ }
303
+ continue;
304
+ }
305
+ iterations += 1;
306
+ if (iterations > MAX_DRAIN_ITERATIONS) {
307
+ drainPhase2.length = 0;
308
+ drainPhase3.length = 0;
309
+ drainPhase4.length = 0;
310
+ throw new Error(
311
+ `batch drain exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely a reactive cycle`
312
+ );
313
+ }
314
+ const queue = drainPhase2.length > 0 ? drainPhase2 : drainPhase3.length > 0 ? drainPhase3 : drainPhase4;
315
+ const ops = queue.splice(0);
316
+ for (const run of ops) {
317
+ try {
318
+ run();
319
+ } catch (e) {
320
+ errors.push(e);
321
+ }
322
+ }
323
+ }
324
+ } finally {
325
+ if (ownsFlush) flushInProgress = false;
326
+ }
327
+ if (errors.length === 1) throw errors[0];
328
+ if (errors.length > 1) {
329
+ throw new AggregateError(errors, "batch drain: multiple callbacks threw");
330
+ }
331
+ }
332
+ function downWithBatch(sink, messages, tierOf) {
333
+ if (messages.length === 0) return;
334
+ if (messages.length === 1) {
335
+ const tier = tierOf(messages[0][0]);
336
+ if (tier < 3 || !isBatching()) {
337
+ sink(messages);
338
+ return;
339
+ }
340
+ const queue = tier >= 5 ? drainPhase4 : tier === 4 ? drainPhase3 : drainPhase2;
341
+ queue.push(() => sink(messages));
342
+ return;
343
+ }
344
+ const n = messages.length;
345
+ let phase2Start = n;
346
+ let phase3Start = n;
347
+ let phase4Start = n;
348
+ let i = 0;
349
+ while (i < n && tierOf(messages[i][0]) < 3) i++;
350
+ phase2Start = i;
351
+ while (i < n && tierOf(messages[i][0]) === 3) i++;
352
+ phase3Start = i;
353
+ while (i < n && tierOf(messages[i][0]) === 4) i++;
354
+ phase4Start = i;
355
+ const batching = isBatching();
356
+ if (phase2Start > 0) {
357
+ const immediate = messages.slice(0, phase2Start);
358
+ sink(immediate);
359
+ }
360
+ if (phase3Start > phase2Start) {
361
+ const phase2 = messages.slice(phase2Start, phase3Start);
362
+ if (batching) drainPhase2.push(() => sink(phase2));
363
+ else sink(phase2);
364
+ }
365
+ if (phase4Start > phase3Start) {
366
+ const phase3 = messages.slice(phase3Start, phase4Start);
367
+ if (batching) drainPhase3.push(() => sink(phase3));
368
+ else sink(phase3);
369
+ }
370
+ if (n > phase4Start) {
371
+ const phase4 = messages.slice(phase4Start, n);
372
+ if (batching) drainPhase4.push(() => sink(phase4));
373
+ else sink(phase4);
374
+ }
375
+ }
376
+
377
+ // src/core/clock.ts
378
+ function monotonicNs() {
379
+ return Math.trunc(performance.now() * 1e6);
380
+ }
381
+ function wallClockNs() {
382
+ return Date.now() * 1e6;
383
+ }
384
+
385
+ // src/core/config.ts
386
+ var GraphReFlyConfig = class {
387
+ _messageTypes = /* @__PURE__ */ new Map();
388
+ _codecs = /* @__PURE__ */ new Map();
389
+ _onMessage;
390
+ _onSubscribe;
391
+ _defaultVersioning;
392
+ _defaultHashFn;
393
+ _inspectorEnabled = !(typeof process !== "undefined" && process.env?.NODE_ENV === "production");
394
+ _globalInspector;
395
+ _frozen = false;
396
+ /**
397
+ * Pre-bound tier lookup — shared by every node bound to this config. Since
398
+ * the registry is frozen on first hook access, this closure can be built
399
+ * once in the constructor and handed directly to `downWithBatch` /
400
+ * `_frameBatch` paths without per-node or per-emission `.bind(config)`
401
+ * allocation.
402
+ */
403
+ tierOf;
404
+ constructor(init) {
405
+ this._onMessage = init.onMessage;
406
+ this._onSubscribe = init.onSubscribe;
407
+ this._defaultVersioning = init.defaultVersioning;
408
+ this._defaultHashFn = init.defaultHashFn;
409
+ this.tierOf = (t) => {
410
+ const reg = this._messageTypes.get(t);
411
+ return reg != null ? reg.tier : 1;
412
+ };
413
+ }
414
+ // --- Hook getters (freeze on read) ---
415
+ get onMessage() {
416
+ this._frozen = true;
417
+ return this._onMessage;
418
+ }
419
+ get onSubscribe() {
420
+ this._frozen = true;
421
+ return this._onSubscribe;
422
+ }
423
+ // --- Hook setters (throw when frozen) ---
424
+ set onMessage(v) {
425
+ this._assertUnfrozen();
426
+ this._onMessage = v;
427
+ }
428
+ set onSubscribe(v) {
429
+ this._assertUnfrozen();
430
+ this._onSubscribe = v;
431
+ }
432
+ /**
433
+ * Default versioning level applied to every node bound to this config,
434
+ * unless the node's own `opts.versioning` provides an explicit override.
435
+ * Setting this is only allowed before the config freezes (i.e., before
436
+ * the first node is created) so every node in the graph sees a
437
+ * consistent starting level. Individual nodes can still opt into a
438
+ * higher level via `opts.versioning`, or post-hoc via
439
+ * `NodeImpl._applyVersioning(level)` when the node is quiescent.
440
+ *
441
+ * v0 is the minimum opt-in — unversioned nodes (`undefined`) skip
442
+ * the version counter entirely. v1 adds content-addressed cid.
443
+ * Future levels (v2, v3) are reserved for linked-history and
444
+ * cryptographic attestation extensions.
445
+ */
446
+ get defaultVersioning() {
447
+ return this._defaultVersioning;
448
+ }
449
+ set defaultVersioning(v) {
450
+ this._assertUnfrozen();
451
+ this._defaultVersioning = v;
452
+ }
453
+ /**
454
+ * Default content-hash function applied to every versioned node bound
455
+ * to this config, unless the node's own `opts.versioningHash` provides
456
+ * an explicit override. Use this when a graph needs a non-default hash
457
+ * — e.g., swap the vendored sync SHA-256 for a faster non-crypto hash
458
+ * (xxHash, FNV-1a) in hot-path workloads, or a stronger hash when
459
+ * versioning v1 cids are used as audit anchors.
460
+ *
461
+ * Only settable before the config freezes. Individual nodes can still
462
+ * override via `opts.versioningHash`.
463
+ */
464
+ get defaultHashFn() {
465
+ return this._defaultHashFn;
466
+ }
467
+ set defaultHashFn(v) {
468
+ this._assertUnfrozen();
469
+ this._defaultHashFn = v;
470
+ }
471
+ /**
472
+ * When `false`, structured observation options (`causal`, `timeline`)
473
+ * and `Graph.trace()` writes are no-ops. Raw `Graph.observe()` always
474
+ * works. Default: `true` outside production (`NODE_ENV !== "production"`).
475
+ *
476
+ * Settable at any time — inspector gating is an operational concern, not
477
+ * a protocol invariant, so it does NOT require freeze before node creation.
478
+ */
479
+ get inspectorEnabled() {
480
+ return this._inspectorEnabled;
481
+ }
482
+ set inspectorEnabled(v) {
483
+ this._inspectorEnabled = v;
484
+ }
485
+ /**
486
+ * Process-global observability hook (Redux-DevTools-style full-graph
487
+ * tracer). Fires once per outgoing batch from every node bound to this
488
+ * config, gated by {@link inspectorEnabled}. See {@link GlobalInspectorHook}.
489
+ *
490
+ * Settable at any time — like {@link inspectorEnabled} this is operational,
491
+ * not protocol-shaping, so it does NOT trigger config freeze.
492
+ */
493
+ get globalInspector() {
494
+ return this._globalInspector;
495
+ }
496
+ set globalInspector(v) {
497
+ this._globalInspector = v;
498
+ }
499
+ // --- Registry (writes require unfrozen; reads are free lookups) ---
500
+ /**
501
+ * Register a custom message type. Must be called before any node that
502
+ * uses this config has been created — otherwise throws. Default
503
+ * `wireCrossing` is `tier >= 3`.
504
+ */
505
+ registerMessageType(t, input) {
506
+ this._assertUnfrozen();
507
+ this._messageTypes.set(t, {
508
+ tier: input.tier,
509
+ wireCrossing: input.wireCrossing ?? input.tier >= 3,
510
+ metaPassthrough: input.metaPassthrough ?? true
511
+ });
512
+ return this;
513
+ }
514
+ /** Tier for `t`. Unknown types default to tier 1 (immediate, after START). */
515
+ messageTier(t) {
516
+ const reg = this._messageTypes.get(t);
517
+ return reg != null ? reg.tier : 1;
518
+ }
519
+ /**
520
+ * Whether `t` is registered as wire-crossing. Unknown types default to
521
+ * `true` (spec §1.3.6 forward-compat — unknowns cross the wire).
522
+ */
523
+ isWireCrossing(t) {
524
+ const reg = this._messageTypes.get(t);
525
+ return reg != null ? reg.wireCrossing : true;
526
+ }
527
+ /** Convenience inverse of {@link isWireCrossing}. */
528
+ isLocalOnly(t) {
529
+ return !this.isWireCrossing(t);
530
+ }
531
+ /**
532
+ * Whether `t` is forwarded to meta companions by `Graph.signal`. Defaults
533
+ * to `true` for unknowns (forward-compat — new types pass through meta by
534
+ * default; opt-in filter via `registerMessageType({metaPassthrough: false})`).
535
+ */
536
+ isMetaPassthrough(t) {
537
+ const reg = this._messageTypes.get(t);
538
+ return reg != null ? reg.metaPassthrough : true;
539
+ }
540
+ /** Whether `t` is a registered (built-in or custom) type. */
541
+ isKnownMessageType(t) {
542
+ return this._messageTypes.has(t);
543
+ }
544
+ // --- Codec registry (writes require unfrozen; reads are free lookups) ---
545
+ /**
546
+ * Register a graph codec by `codec.name`. Used by the envelope-based
547
+ * `graph.snapshot({format: "bytes", codec: name})` path and
548
+ * `Graph.decode(bytes)` auto-dispatch. Must be called before any node
549
+ * bound to this config is created — otherwise throws.
550
+ *
551
+ * Re-registering the same name overwrites, so user codecs can shadow
552
+ * built-in ones before freeze (e.g., to swap a zstd-wrapped dag-cbor in
553
+ * for `"dag-cbor"`).
554
+ */
555
+ registerCodec(codec) {
556
+ this._assertUnfrozen();
557
+ this._codecs.set(codec.name, codec);
558
+ return this;
559
+ }
560
+ /**
561
+ * Resolve a registered codec by name. Returns `undefined` for unknown
562
+ * names. Typed callers cast to their concrete codec interface (e.g.,
563
+ * `config.lookupCodec<GraphCodec>("json")`) — this method stays
564
+ * layer-pure (no import of graph-layer types into `core/`).
565
+ */
566
+ lookupCodec(name) {
567
+ return this._codecs.get(name);
568
+ }
569
+ /** @internal Used by tests and dev tooling — check freeze state without triggering it. */
570
+ _isFrozen() {
571
+ return this._frozen;
572
+ }
573
+ _assertUnfrozen() {
574
+ if (this._frozen) {
575
+ throw new Error(
576
+ "GraphReFlyConfig is frozen: a node has already captured this config. Register custom types and set hooks before creating any node."
577
+ );
578
+ }
579
+ }
580
+ };
581
+ function registerBuiltins(cfg) {
582
+ cfg.registerMessageType(START, { tier: 0, wireCrossing: false });
583
+ cfg.registerMessageType(DIRTY, { tier: 1, wireCrossing: false });
584
+ cfg.registerMessageType(INVALIDATE, {
585
+ tier: 1,
586
+ wireCrossing: false,
587
+ metaPassthrough: false
588
+ });
589
+ cfg.registerMessageType(PAUSE, { tier: 2, wireCrossing: false });
590
+ cfg.registerMessageType(RESUME, { tier: 2, wireCrossing: false });
591
+ cfg.registerMessageType(DATA, { tier: 3, wireCrossing: true });
592
+ cfg.registerMessageType(RESOLVED, { tier: 3, wireCrossing: true });
593
+ cfg.registerMessageType(COMPLETE, {
594
+ tier: 4,
595
+ wireCrossing: true,
596
+ metaPassthrough: false
597
+ });
598
+ cfg.registerMessageType(ERROR, {
599
+ tier: 4,
600
+ wireCrossing: true,
601
+ metaPassthrough: false
602
+ });
603
+ cfg.registerMessageType(TEARDOWN, {
604
+ tier: 5,
605
+ wireCrossing: true,
606
+ metaPassthrough: false
607
+ });
608
+ }
609
+
610
+ // src/core/guard.ts
611
+ var GuardDenied = class extends Error {
612
+ actor;
613
+ action;
614
+ nodeName;
615
+ /**
616
+ * @param details - Actor, action, and optional node name for the denial.
617
+ * @param message - Optional override for the default error message.
618
+ */
619
+ constructor(details, message) {
620
+ super(
621
+ message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
622
+ );
623
+ this.name = "GuardDenied";
624
+ this.actor = details.actor;
625
+ this.action = details.action;
626
+ this.nodeName = details.nodeName;
627
+ }
628
+ /** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
629
+ get node() {
630
+ return this.nodeName;
631
+ }
632
+ };
633
+ function normalizeActions(action) {
634
+ if (Array.isArray(action)) {
635
+ return [...action];
636
+ }
637
+ return [action];
638
+ }
639
+ function matchesActions(set, action) {
640
+ return set.has(action) || set.has("*");
641
+ }
642
+ function policy(build) {
643
+ const rules = [];
644
+ const allow = (action, opts) => {
645
+ rules.push({
646
+ kind: "allow",
647
+ actions: new Set(normalizeActions(action)),
648
+ where: opts?.where ?? (() => true)
649
+ });
650
+ };
651
+ const deny = (action, opts) => {
652
+ rules.push({
653
+ kind: "deny",
654
+ actions: new Set(normalizeActions(action)),
655
+ where: opts?.where ?? (() => true)
656
+ });
657
+ };
658
+ build(allow, deny);
659
+ return (actor, action) => {
660
+ let denied = false;
661
+ let allowed = false;
662
+ for (const r of rules) {
663
+ if (!matchesActions(r.actions, action)) continue;
664
+ if (!r.where(actor)) continue;
665
+ if (r.kind === "deny") {
666
+ denied = true;
667
+ } else {
668
+ allowed = true;
669
+ }
670
+ }
671
+ if (denied) return false;
672
+ return allowed;
673
+ };
674
+ }
675
+ function policyFromRules(rules) {
676
+ return policy((allow, deny) => {
677
+ for (const rule of rules) {
678
+ const actorTypes = rule.actorType == null ? null : new Set(Array.isArray(rule.actorType) ? rule.actorType : [rule.actorType]);
679
+ const actorIds = rule.actorId == null ? null : new Set(Array.isArray(rule.actorId) ? rule.actorId : [rule.actorId]);
680
+ const claimEntries = Object.entries(rule.claims ?? {});
681
+ const where = (actor) => {
682
+ if (actorTypes !== null && !actorTypes.has(String(actor.type))) return false;
683
+ if (actorIds !== null && !actorIds.has(String(actor.id ?? ""))) return false;
684
+ for (const [key, value] of claimEntries) {
685
+ if (actor[key] !== value) return false;
686
+ }
687
+ return true;
688
+ };
689
+ if (rule.effect === "deny") {
690
+ deny(rule.action, { where });
691
+ } else {
692
+ allow(rule.action, { where });
693
+ }
694
+ }
695
+ });
696
+ }
697
+ var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
698
+ function accessHintForGuard(guard) {
699
+ const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
700
+ if (allowed.length === 0) return "restricted";
701
+ if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
702
+ return "both";
703
+ }
704
+ if (allowed.length === 1) return allowed[0];
705
+ return allowed.join("+");
706
+ }
707
+
708
+ // src/core/versioning.ts
709
+ function canonicalizeForHash(value) {
710
+ if (value === void 0) return null;
711
+ if (typeof value === "number") {
712
+ if (!Number.isFinite(value)) {
713
+ throw new TypeError(`Cannot hash non-finite number: ${value}`);
714
+ }
715
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
716
+ throw new TypeError(
717
+ `Cannot hash integer outside safe range (|n| > 2^53-1): ${value}. Cross-language cid parity is not guaranteed for unsafe integers.`
718
+ );
719
+ }
720
+ return value;
721
+ }
722
+ if (typeof value === "string" || typeof value === "boolean" || value === null) {
723
+ return value;
724
+ }
725
+ if (Array.isArray(value)) {
726
+ return value.map(canonicalizeForHash);
727
+ }
728
+ if (typeof value === "object" && value !== null) {
729
+ const sorted = {};
730
+ for (const k of Object.keys(value).sort()) {
731
+ sorted[k] = canonicalizeForHash(value[k]);
732
+ }
733
+ return sorted;
734
+ }
735
+ return null;
736
+ }
737
+ var SHA256_K = /* @__PURE__ */ new Uint32Array([
738
+ 1116352408,
739
+ 1899447441,
740
+ 3049323471,
741
+ 3921009573,
742
+ 961987163,
743
+ 1508970993,
744
+ 2453635748,
745
+ 2870763221,
746
+ 3624381080,
747
+ 310598401,
748
+ 607225278,
749
+ 1426881987,
750
+ 1925078388,
751
+ 2162078206,
752
+ 2614888103,
753
+ 3248222580,
754
+ 3835390401,
755
+ 4022224774,
756
+ 264347078,
757
+ 604807628,
758
+ 770255983,
759
+ 1249150122,
760
+ 1555081692,
761
+ 1996064986,
762
+ 2554220882,
763
+ 2821834349,
764
+ 2952996808,
765
+ 3210313671,
766
+ 3336571891,
767
+ 3584528711,
768
+ 113926993,
769
+ 338241895,
770
+ 666307205,
771
+ 773529912,
772
+ 1294757372,
773
+ 1396182291,
774
+ 1695183700,
775
+ 1986661051,
776
+ 2177026350,
777
+ 2456956037,
778
+ 2730485921,
779
+ 2820302411,
780
+ 3259730800,
781
+ 3345764771,
782
+ 3516065817,
783
+ 3600352804,
784
+ 4094571909,
785
+ 275423344,
786
+ 430227734,
787
+ 506948616,
788
+ 659060556,
789
+ 883997877,
790
+ 958139571,
791
+ 1322822218,
792
+ 1537002063,
793
+ 1747873779,
794
+ 1955562222,
795
+ 2024104815,
796
+ 2227730452,
797
+ 2361852424,
798
+ 2428436474,
799
+ 2756734187,
800
+ 3204031479,
801
+ 3329325298
802
+ ]);
803
+ var UTF8_ENCODER = /* @__PURE__ */ new TextEncoder();
804
+ function sha256Hex(msg) {
805
+ const bytes = UTF8_ENCODER.encode(msg);
806
+ const msgLen = bytes.length;
807
+ const bitLen = msgLen * 8;
808
+ const totalLen = msgLen + 9 + 63 & ~63;
809
+ const padded = new Uint8Array(totalLen);
810
+ padded.set(bytes);
811
+ padded[msgLen] = 128;
812
+ const dv = new DataView(padded.buffer);
813
+ dv.setUint32(totalLen - 4, bitLen >>> 0, false);
814
+ dv.setUint32(totalLen - 8, Math.floor(bitLen / 4294967296) >>> 0, false);
815
+ let h0 = 1779033703;
816
+ let h1 = 3144134277;
817
+ let h2 = 1013904242;
818
+ let h3 = 2773480762;
819
+ let h4 = 1359893119;
820
+ let h5 = 2600822924;
821
+ let h6 = 528734635;
822
+ let h7 = 1541459225;
823
+ const W = new Uint32Array(64);
824
+ const rotr = (x, n) => x >>> n | x << 32 - n;
825
+ for (let off = 0; off < totalLen; off += 64) {
826
+ for (let i = 0; i < 16; i++) W[i] = dv.getUint32(off + i * 4, false);
827
+ for (let i = 16; i < 64; i++) {
828
+ const w15 = W[i - 15];
829
+ const w2 = W[i - 2];
830
+ const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
831
+ const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
832
+ W[i] = W[i - 16] + s0 + W[i - 7] + s1 >>> 0;
833
+ }
834
+ let a = h0;
835
+ let b = h1;
836
+ let c = h2;
837
+ let d = h3;
838
+ let e = h4;
839
+ let f = h5;
840
+ let g = h6;
841
+ let h = h7;
842
+ for (let i = 0; i < 64; i++) {
843
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
844
+ const ch = e & f ^ ~e & g;
845
+ const t1 = h + S1 + ch + SHA256_K[i] + W[i] >>> 0;
846
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
847
+ const mj = a & b ^ a & c ^ b & c;
848
+ const t2 = S0 + mj >>> 0;
849
+ h = g;
850
+ g = f;
851
+ f = e;
852
+ e = d + t1 >>> 0;
853
+ d = c;
854
+ c = b;
855
+ b = a;
856
+ a = t1 + t2 >>> 0;
857
+ }
858
+ h0 = h0 + a >>> 0;
859
+ h1 = h1 + b >>> 0;
860
+ h2 = h2 + c >>> 0;
861
+ h3 = h3 + d >>> 0;
862
+ h4 = h4 + e >>> 0;
863
+ h5 = h5 + f >>> 0;
864
+ h6 = h6 + g >>> 0;
865
+ h7 = h7 + h >>> 0;
866
+ }
867
+ const toHex = (x) => x.toString(16).padStart(8, "0");
868
+ return toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4) + toHex(h5) + toHex(h6) + toHex(h7);
869
+ }
870
+ function defaultHash(value) {
871
+ const canonical = canonicalizeForHash(value ?? null);
872
+ const json = JSON.stringify(canonical);
873
+ return sha256Hex(json).slice(0, 16);
874
+ }
875
+ function randomUuid() {
876
+ const c = globalThis.crypto;
877
+ if (c?.randomUUID) return c.randomUUID();
878
+ const r = () => Math.floor(Math.random() * 4294967296).toString(16).padStart(8, "0");
879
+ const hex = r() + r() + r() + r();
880
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${(parseInt(hex.slice(16, 17), 16) & 3 | 8).toString(16)}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
881
+ }
882
+ function createVersioning(level, initialValue, opts) {
883
+ const id = opts?.id ?? randomUuid();
884
+ if (level === 0) {
885
+ return { id, version: 0 };
886
+ }
887
+ const hash = opts?.hash ?? defaultHash;
888
+ const cid = hash(initialValue);
889
+ return { id, version: 0, cid, prev: null };
890
+ }
891
+ function advanceVersion(info, newValue, hashFn) {
892
+ info.version += 1;
893
+ if ("cid" in info) {
894
+ info.prev = info.cid;
895
+ info.cid = hashFn(newValue);
896
+ }
897
+ }
898
+ function isV1(info) {
899
+ return "cid" in info;
900
+ }
901
+
902
+ // src/core/node.ts
903
+ var noopUnsub = () => {
904
+ };
905
+ var MAX_RERUN_DEPTH = 100;
906
+ function createDepRecord(n) {
907
+ return {
908
+ node: n,
909
+ unsub: null,
910
+ prevData: void 0,
911
+ dirty: false,
912
+ involvedThisWave: false,
913
+ dataBatch: [],
914
+ terminal: void 0
915
+ };
916
+ }
917
+ function resetDepRecord(d) {
918
+ d.prevData = void 0;
919
+ d.dirty = false;
920
+ d.involvedThisWave = false;
921
+ d.dataBatch.length = 0;
922
+ d.terminal = void 0;
923
+ }
924
+ function normalizeMessages(input) {
925
+ if (input.length === 0) return input;
926
+ return typeof input[0] === "symbol" ? [input] : input;
927
+ }
928
+ var defaultOnMessage = (node2, msg, ctx, _actions) => {
929
+ if (ctx.direction === "down-in") {
930
+ node2._onDepMessage(ctx.depIndex, msg);
931
+ }
932
+ return void 0;
933
+ };
934
+ var defaultOnSubscribe = (node2, sink, _ctx, _actions) => {
935
+ const impl = node2;
936
+ if (impl._status === "completed" || impl._status === "errored") return;
937
+ const cached = impl._cached;
938
+ const initial = cached === void 0 ? [START_MSG] : [START_MSG, [DATA, cached]];
939
+ if (impl._status === "dirty") initial.push(DIRTY_MSG);
940
+ downWithBatch(sink, initial, impl._config.tierOf);
941
+ };
942
+ var defaultConfig = new GraphReFlyConfig({
943
+ onMessage: defaultOnMessage,
944
+ onSubscribe: defaultOnSubscribe
945
+ });
946
+ registerBuiltins(defaultConfig);
947
+ registerBuiltinCodecs(defaultConfig);
948
+ function configure(fn) {
949
+ if (defaultConfig._isFrozen()) {
950
+ throw new Error(
951
+ "configure() called after a node was created \u2014 the default GraphReFlyConfig is frozen. Call configure(...) at application startup, before any node factories run."
952
+ );
953
+ }
954
+ fn(defaultConfig);
955
+ }
956
+ var NodeImpl = class _NodeImpl {
957
+ // --- Identity ---
958
+ _optsName;
959
+ _describeKind;
960
+ meta;
961
+ /**
962
+ * Cached `Object.keys(meta).length > 0` check. `meta` is frozen at
963
+ * construction so this boolean never flips. Used by `_emit` to skip
964
+ * the meta TEARDOWN fan-out block allocation on the common "no meta"
965
+ * hot path.
966
+ */
967
+ _hasMeta;
968
+ // --- Config ---
969
+ _config;
970
+ // --- Topology ---
971
+ /** Mutable for autoTrackNode / Graph.connect() post-construction dep addition. */
972
+ _deps;
973
+ _sinks = null;
974
+ _sinkCount = 0;
975
+ // --- State ---
976
+ _cached;
977
+ _status;
978
+ _cleanup;
979
+ _store = {};
980
+ _waveHasNewData = false;
981
+ _hasNewTerminal = false;
982
+ _hasCalledFnOnce = false;
983
+ _paused = false;
984
+ _pendingWave = false;
985
+ _isExecutingFn = false;
986
+ _pendingRerun = false;
987
+ _rerunDepth = 0;
988
+ // --- Settlement counter (A3) ---
989
+ /**
990
+ * Count of deps currently in `dirty === true`. `_maybeRunFnOnSettlement`
991
+ * treats `0` as "wave settled" — O(1) check for full dep settlement.
992
+ */
993
+ _dirtyDepCount = 0;
994
+ // --- Per-batch emit accumulator (Bug 2: K+1 fan-in fix) ---
995
+ /**
996
+ * Inside an explicit `batch(() => ...)` scope, every `_emit` accumulates
997
+ * its already-framed messages here instead of dispatching synchronously.
998
+ * At batch end, `_flushBatchPending` runs (registered via
999
+ * `registerBatchFlushHook`) and delivers the whole accumulated batch as
1000
+ * one `downWithBatch` call — collapsing what would otherwise be K
1001
+ * separate sink invocations into one. This is the fix for the diamond
1002
+ * fan-in K+1 over-fire.
1003
+ *
1004
+ * `null` outside batch (or after flush). Only ever appended to within
1005
+ * a single explicit batch lifetime; reset to `null` on flush. State
1006
+ * updates (cache, version, status) still happen per-emit via
1007
+ * `_updateState` — only the downstream delivery is coalesced.
1008
+ */
1009
+ _batchPendingMessages = null;
1010
+ // --- PAUSE/RESUME lock tracking (C0) ---
1011
+ /**
1012
+ * Set of active pause locks held against this node. Every `[PAUSE, lockId]`
1013
+ * adds its `lockId` to the set; every `[RESUME, lockId]` removes it.
1014
+ * `_paused` is a derived quantity: `_pauseLocks.size > 0`. Multi-pauser
1015
+ * correctness — one controller releasing its lock does NOT resume the
1016
+ * node while another controller still holds its lock.
1017
+ */
1018
+ _pauseLocks = null;
1019
+ /**
1020
+ * Buffered DATA messages held while paused. Only populated when
1021
+ * `_pausable === "resumeAll"` (bufferAll mode). On final lock release
1022
+ * the buffer is replayed through the node's outgoing pipeline in the
1023
+ * order received. Non-bufferAll pause mode drops DATA on the floor
1024
+ * (upstream is expected to honor PAUSE by suppressing production).
1025
+ */
1026
+ _pauseBuffer = null;
1027
+ // --- Options (frozen at construction) ---
1028
+ _fn;
1029
+ _equals;
1030
+ _resubscribable;
1031
+ _resetOnTeardown;
1032
+ _autoComplete;
1033
+ _autoError;
1034
+ _pausable;
1035
+ _guard;
1036
+ _hashFn;
1037
+ _versioning;
1038
+ /**
1039
+ * Explicit versioning level, tracked separately from `_versioning` so
1040
+ * monotonicity checks and future v2/v3 extensions don't rely on the
1041
+ * fragile `"cid" in _versioning` shape discriminator. `undefined` means
1042
+ * the node has no versioning attached; `0` / `1` / future levels name
1043
+ * the tier. Mutated in lockstep with `_versioning` by the constructor
1044
+ * and by `_applyVersioning`.
1045
+ */
1046
+ _versioningLevel;
1047
+ // --- ABAC ---
1048
+ _lastMutation;
1049
+ /**
1050
+ * @internal Per-node inspector hooks for `Graph.observe(path,
1051
+ * { causal, derived })`. Fires in `_onDepMessage` and `_execFn`.
1052
+ * Attached via `_setInspectorHook` (returns a disposer). Multiple
1053
+ * observers can attach simultaneously — all registered hooks fire for
1054
+ * every event.
1055
+ */
1056
+ _inspectorHooks;
1057
+ // --- Actions (built once in the constructor) ---
1058
+ _actions;
1059
+ constructor(deps, fn, opts) {
1060
+ this._config = opts.config ?? defaultConfig;
1061
+ void this._config.onMessage;
1062
+ this._optsName = opts.name;
1063
+ this._describeKind = opts.describeKind;
1064
+ this._equals = opts.equals ?? Object.is;
1065
+ this._resubscribable = opts.resubscribable ?? false;
1066
+ this._resetOnTeardown = opts.resetOnTeardown ?? false;
1067
+ this._autoComplete = opts.completeWhenDepsComplete ?? true;
1068
+ this._autoError = opts.errorWhenDepsError ?? true;
1069
+ this._pausable = opts.pausable ?? true;
1070
+ this._guard = opts.guard;
1071
+ this._fn = fn;
1072
+ this._cached = opts.initial !== void 0 ? opts.initial : void 0;
1073
+ this._status = deps.length === 0 && fn == null && this._cached !== void 0 ? "settled" : "sentinel";
1074
+ this._hashFn = opts.versioningHash ?? this._config.defaultHashFn ?? defaultHash;
1075
+ const versioningLevel = opts.versioning ?? this._config.defaultVersioning;
1076
+ this._versioningLevel = versioningLevel;
1077
+ this._versioning = versioningLevel != null ? createVersioning(versioningLevel, this._cached === void 0 ? void 0 : this._cached, {
1078
+ id: opts.versioningId,
1079
+ hash: this._hashFn
1080
+ }) : void 0;
1081
+ this._deps = deps.map(createDepRecord);
1082
+ const meta = {};
1083
+ for (const [k, v] of Object.entries(opts.meta ?? {})) {
1084
+ const metaOpts = {
1085
+ initial: v,
1086
+ name: `${opts.name ?? "node"}:meta:${k}`,
1087
+ describeKind: "state",
1088
+ config: this._config
1089
+ };
1090
+ if (opts.guard != null) metaOpts.guard = opts.guard;
1091
+ meta[k] = new _NodeImpl([], void 0, metaOpts);
1092
+ }
1093
+ Object.freeze(meta);
1094
+ this.meta = meta;
1095
+ this._hasMeta = Object.keys(meta).length > 0;
1096
+ const self = this;
1097
+ this._actions = {
1098
+ emit(value) {
1099
+ self._emit([[DATA, value]]);
1100
+ },
1101
+ down(messageOrMessages) {
1102
+ self._emit(normalizeMessages(messageOrMessages));
1103
+ },
1104
+ up(messageOrMessages) {
1105
+ self._emitUp(normalizeMessages(messageOrMessages));
1106
+ }
1107
+ };
1108
+ this.down = this.down.bind(this);
1109
+ this.up = this.up.bind(this);
1110
+ }
1111
+ // --- Derived state ---
1112
+ get _isTerminal() {
1113
+ return this._status === "completed" || this._status === "errored";
1114
+ }
1115
+ // --- Public getters ---
1116
+ get name() {
1117
+ return this._optsName;
1118
+ }
1119
+ get status() {
1120
+ return this._status;
1121
+ }
1122
+ get cache() {
1123
+ return this._cached === void 0 ? void 0 : this._cached;
1124
+ }
1125
+ get lastMutation() {
1126
+ return this._lastMutation;
1127
+ }
1128
+ get v() {
1129
+ return this._versioning;
1130
+ }
1131
+ hasGuard() {
1132
+ return this._guard != null;
1133
+ }
1134
+ /**
1135
+ * @internal Retroactively attach (or upgrade) versioning state on this
1136
+ * node. Intended for `Graph.setVersioning(level)` bulk application and
1137
+ * for rare cases where a specific node needs to be bumped to a higher
1138
+ * level (e.g., `v0 → v1`) after construction.
1139
+ *
1140
+ * **Safety:** the mutation is rejected mid-wave. Specifically,
1141
+ * throws if the node is currently executing its fn (`_isExecutingFn`).
1142
+ * Callers at quiescent points — before the first sink subscribes, or
1143
+ * after all sinks unsubscribe, or between external `down()` / `emit()`
1144
+ * invocations — are safe. The re-entrance window that motivated §10.6.4
1145
+ * removal was the "transition `_versioning` from `undefined` to a fresh
1146
+ * object mid-`_updateState`" case; that path is now guarded.
1147
+ *
1148
+ * **Monotonicity:** levels can only go up. Downgrade (e.g., `v1 → v0`)
1149
+ * is a no-op — once a node carries higher-level metadata, dropping it
1150
+ * mid-graph would tear the linked-history invariant for v1 and above.
1151
+ *
1152
+ * **Linked-history boundary (D1, 2026-04-13):** upgrading v0 → v1
1153
+ * produces a **fresh history root**. The new v1 state has `cid =
1154
+ * hash(currentCachedValue)` and `prev = null`, not a synthetic `prev`
1155
+ * anchored to any previous v0 value. The v0 monotonic `version` counter
1156
+ * is preserved across the upgrade, but the linked-cid chain (spec §7)
1157
+ * starts fresh at the upgrade point. Downstream audit tools that walk
1158
+ * `v.cid.prev` backwards through time will see a `null` boundary at
1159
+ * the upgrade — **this is intentional**: v0 had no cid to link to, and
1160
+ * fabricating one would lie about the hash. Callers that require an
1161
+ * unbroken cid chain from birth must attach versioning at construction
1162
+ * via `opts.versioning` or `config.defaultVersioning`, not retroactively.
1163
+ *
1164
+ * @param level - New minimum versioning level.
1165
+ * @param opts - Optional id / hash overrides; applied only if the
1166
+ * node currently has no versioning state.
1167
+ */
1168
+ _applyVersioning(level, opts) {
1169
+ if (this._isExecutingFn) {
1170
+ throw new Error(
1171
+ `Node "${this.name}": _applyVersioning cannot run mid-fn \u2014 call it outside of \`_execFn\` (typically at graph setup time before the first subscribe).`
1172
+ );
1173
+ }
1174
+ const currentLevel = this._versioningLevel;
1175
+ if (currentLevel != null && level <= currentLevel) {
1176
+ return;
1177
+ }
1178
+ const hash = opts?.hash ?? this._hashFn;
1179
+ if (hash !== this._hashFn) this._hashFn = hash;
1180
+ const initialValue = this._cached === void 0 ? void 0 : this._cached;
1181
+ const current = this._versioning;
1182
+ const preservedId = current?.id ?? opts?.id;
1183
+ const preservedVersion = current?.version ?? 0;
1184
+ const fresh = createVersioning(level, initialValue, {
1185
+ id: preservedId,
1186
+ hash
1187
+ });
1188
+ fresh.version = preservedVersion;
1189
+ this._versioning = fresh;
1190
+ this._versioningLevel = level;
1191
+ }
1192
+ /**
1193
+ * @internal Attach an inspector hook. Returns a disposer that removes
1194
+ * the hook. Used by `Graph.observe(path, { causal, derived })` to build
1195
+ * causal traces. Multiple hooks may be attached concurrently — all fire
1196
+ * for every event in registration order. Passing `undefined` is a no-op
1197
+ * and returns a no-op disposer.
1198
+ */
1199
+ _setInspectorHook(hook) {
1200
+ if (hook == null) return () => {
1201
+ };
1202
+ if (this._inspectorHooks == null) this._inspectorHooks = /* @__PURE__ */ new Set();
1203
+ this._inspectorHooks.add(hook);
1204
+ return () => {
1205
+ this._inspectorHooks?.delete(hook);
1206
+ if (this._inspectorHooks?.size === 0) this._inspectorHooks = void 0;
1207
+ };
1208
+ }
1209
+ allowsObserve(actor) {
1210
+ if (this._guard == null) return true;
1211
+ return this._guard(normalizeActor(actor), "observe");
1212
+ }
1213
+ // --- Guard helper ---
1214
+ _checkGuard(options) {
1215
+ if (options?.internal || this._guard == null) return;
1216
+ const actor = normalizeActor(options?.actor);
1217
+ const action = options?.delivery === "signal" ? "signal" : "write";
1218
+ if (!this._guard(actor, action)) {
1219
+ throw new GuardDenied({ actor, action, nodeName: this.name });
1220
+ }
1221
+ this._lastMutation = { actor, timestamp_ns: wallClockNs() };
1222
+ }
1223
+ // --- Public transport ---
1224
+ down(messageOrMessages, options) {
1225
+ const messages = normalizeMessages(messageOrMessages);
1226
+ if (messages.length === 0) return;
1227
+ this._checkGuard(options);
1228
+ this._emit(messages);
1229
+ }
1230
+ emit(value, options) {
1231
+ this._checkGuard(options);
1232
+ this._emit([[DATA, value]]);
1233
+ }
1234
+ up(messageOrMessages, options) {
1235
+ if (this._deps.length === 0) return;
1236
+ const messages = normalizeMessages(messageOrMessages);
1237
+ if (messages.length === 0) return;
1238
+ this._checkGuard(options);
1239
+ const forwardOpts = options ?? { internal: true };
1240
+ this._validateUpTiers(messages);
1241
+ for (const d of this._deps) {
1242
+ d.node.up?.(messages, forwardOpts);
1243
+ }
1244
+ }
1245
+ /**
1246
+ * @internal Internal up-path used by `actions.up(...)` from inside fn.
1247
+ * Same tier validation as public `up`, but bypasses the guard check
1248
+ * since the fn context is already inside an authorized operation.
1249
+ */
1250
+ _emitUp(messages) {
1251
+ if (this._deps.length === 0) return;
1252
+ if (messages.length === 0) return;
1253
+ this._validateUpTiers(messages);
1254
+ for (const d of this._deps) {
1255
+ d.node.up?.(messages, { internal: true });
1256
+ }
1257
+ }
1258
+ /**
1259
+ * @internal Enforce spec §1.2 — up-direction messages are restricted to
1260
+ * tier 0–2 and tier 5 (START, DIRTY, INVALIDATE, PAUSE, RESUME,
1261
+ * TEARDOWN). Tier 3 (DATA/RESOLVED) and tier 4 (COMPLETE/ERROR) are
1262
+ * downstream-only. Emitting tier-3/4 via `up` would bypass equals
1263
+ * substitution and cache advance entirely and is a protocol bug.
1264
+ */
1265
+ _validateUpTiers(messages) {
1266
+ const tierOf = this._config.tierOf;
1267
+ for (const m of messages) {
1268
+ const tier = tierOf(m[0]);
1269
+ if (tier === 3 || tier === 4) {
1270
+ throw new Error(
1271
+ `Node "${this.name}": tier-${tier} messages cannot flow up \u2014 DATA/RESOLVED/COMPLETE/ERROR are downstream-only. Use \`down(...)\` for value delivery; \`up(...)\` is for control signals (DIRTY, INVALIDATE, PAUSE, RESUME, TEARDOWN).`
1272
+ );
1273
+ }
1274
+ }
1275
+ }
1276
+ subscribe(sink, actor) {
1277
+ if (actor != null && this._guard != null) {
1278
+ const a = normalizeActor(actor);
1279
+ if (!this._guard(a, "observe")) {
1280
+ throw new GuardDenied({ actor: a, action: "observe", nodeName: this.name });
1281
+ }
1282
+ }
1283
+ const wasTerminal = this._isTerminal;
1284
+ const afterTerminalReset = wasTerminal && this._resubscribable;
1285
+ if (afterTerminalReset) {
1286
+ this._cached = void 0;
1287
+ this._status = "sentinel";
1288
+ this._store = {};
1289
+ this._hasCalledFnOnce = false;
1290
+ this._waveHasNewData = false;
1291
+ this._hasNewTerminal = false;
1292
+ this._paused = false;
1293
+ this._pendingWave = false;
1294
+ this._pendingRerun = false;
1295
+ this._isExecutingFn = false;
1296
+ this._rerunDepth = 0;
1297
+ this._dirtyDepCount = 0;
1298
+ this._pauseLocks = null;
1299
+ this._pauseBuffer = null;
1300
+ for (const d of this._deps) resetDepRecord(d);
1301
+ }
1302
+ this._sinkCount += 1;
1303
+ let subCleanup;
1304
+ try {
1305
+ subCleanup = this._config.onSubscribe(
1306
+ this,
1307
+ sink,
1308
+ { sinkCount: this._sinkCount, afterTerminalReset },
1309
+ this._actions
1310
+ );
1311
+ } catch (err) {
1312
+ this._sinkCount -= 1;
1313
+ throw err;
1314
+ }
1315
+ if (this._sinks == null) {
1316
+ this._sinks = sink;
1317
+ } else if (typeof this._sinks === "function") {
1318
+ this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
1319
+ } else {
1320
+ this._sinks.add(sink);
1321
+ }
1322
+ const isTerminalNow = this._isTerminal;
1323
+ if (this._sinkCount === 1 && !isTerminalNow) {
1324
+ try {
1325
+ this._activate();
1326
+ } catch (err) {
1327
+ this._sinkCount -= 1;
1328
+ this._removeSink(sink);
1329
+ if (this._sinkCount === 0) this._status = "sentinel";
1330
+ if (typeof subCleanup === "function") {
1331
+ try {
1332
+ subCleanup();
1333
+ } catch {
1334
+ }
1335
+ }
1336
+ throw err;
1337
+ }
1338
+ }
1339
+ if (this._status === "sentinel" && this._cached === void 0) {
1340
+ this._status = "pending";
1341
+ }
1342
+ let removed = false;
1343
+ return () => {
1344
+ if (removed) return;
1345
+ removed = true;
1346
+ this._sinkCount -= 1;
1347
+ this._removeSink(sink);
1348
+ if (typeof subCleanup === "function") subCleanup();
1349
+ if (this._sinks == null) this._deactivate();
1350
+ };
1351
+ }
1352
+ _removeSink(sink) {
1353
+ if (this._sinks === sink) {
1354
+ this._sinks = null;
1355
+ } else if (this._sinks != null && typeof this._sinks !== "function") {
1356
+ this._sinks.delete(sink);
1357
+ if (this._sinks.size === 1) {
1358
+ const [only] = this._sinks;
1359
+ this._sinks = only;
1360
+ } else if (this._sinks.size === 0) {
1361
+ this._sinks = null;
1362
+ }
1363
+ }
1364
+ }
1365
+ // --- Lifecycle ---
1366
+ /**
1367
+ * @internal First-sink activation. For a producer (no deps + fn),
1368
+ * invokes fn once. For a compute node (has deps), subscribes to every
1369
+ * dep with the pre-set-dirty trick so the first-run gate waits for
1370
+ * every dep to settle at least once.
1371
+ */
1372
+ _activate() {
1373
+ if (this._deps.length === 0) {
1374
+ if (this._fn) this._execFn();
1375
+ return;
1376
+ }
1377
+ this._dirtyDepCount = 0;
1378
+ const initialLen = this._deps.length;
1379
+ let subscribedCount = 0;
1380
+ try {
1381
+ for (let i = 0; i < initialLen; i++) {
1382
+ const depIdx = i;
1383
+ const dep = this._deps[i];
1384
+ dep.unsub = noopUnsub;
1385
+ dep.unsub = dep.node.subscribe((msgs) => {
1386
+ if (dep.unsub === null) return;
1387
+ const tierOf = this._config.tierOf;
1388
+ let sawSettlement = false;
1389
+ for (const m of msgs) {
1390
+ if (tierOf(m[0]) >= 3) sawSettlement = true;
1391
+ this._config.onMessage(
1392
+ this,
1393
+ m,
1394
+ { direction: "down-in", depIndex: depIdx },
1395
+ this._actions
1396
+ );
1397
+ }
1398
+ if (sawSettlement) this._maybeRunFnOnSettlement();
1399
+ });
1400
+ subscribedCount++;
1401
+ }
1402
+ } catch (err) {
1403
+ this._deps[subscribedCount].unsub = null;
1404
+ for (let j = 0; j < subscribedCount; j++) {
1405
+ const d = this._deps[j];
1406
+ if (d.unsub != null) {
1407
+ const u = d.unsub;
1408
+ d.unsub = null;
1409
+ try {
1410
+ u();
1411
+ } catch {
1412
+ }
1413
+ resetDepRecord(d);
1414
+ }
1415
+ }
1416
+ this._dirtyDepCount = 0;
1417
+ throw err;
1418
+ }
1419
+ }
1420
+ /**
1421
+ * @internal Append a dep post-construction. Used by `autoTrackNode`
1422
+ * (runtime dep discovery) and `Graph.connect()` (post-construction
1423
+ * wiring). Subscribes immediately — if DATA arrives synchronously
1424
+ * during subscribe and fn is currently executing, the re-run is
1425
+ * deferred via `_pendingRerun` flag (see `_execFn` guard).
1426
+ *
1427
+ * **Dedup:** idempotent on duplicate `depNode` — if `depNode` is
1428
+ * already in `_deps`, returns the existing index without mutating
1429
+ * state. Callers can safely invoke `_addDep` without their own
1430
+ * "already added" check. `autoTrackNode` still keeps a `depIndexMap`
1431
+ * as a fast-path lookup for known deps (returning cached `data[idx]`
1432
+ * without calling `_addDep` at all); this internal dedup is the
1433
+ * backstop for any caller that doesn't track its own dep set.
1434
+ *
1435
+ * @returns The index of the new dep in `_deps`, or the existing index
1436
+ * if the dep was already present.
1437
+ */
1438
+ _addDep(depNode) {
1439
+ for (let i = 0; i < this._deps.length; i++) {
1440
+ if (this._deps[i].node === depNode) return i;
1441
+ }
1442
+ const depIdx = this._deps.length;
1443
+ const record = createDepRecord(depNode);
1444
+ this._deps.push(record);
1445
+ if (this._sinks == null) return depIdx;
1446
+ record.dirty = true;
1447
+ this._dirtyDepCount++;
1448
+ if (this._status !== "dirty") this._emit(DIRTY_ONLY_BATCH);
1449
+ record.unsub = noopUnsub;
1450
+ try {
1451
+ record.unsub = depNode.subscribe((msgs) => {
1452
+ if (record.unsub === null) return;
1453
+ const tierOf = this._config.tierOf;
1454
+ let sawSettlement = false;
1455
+ for (const m of msgs) {
1456
+ if (tierOf(m[0]) >= 3) sawSettlement = true;
1457
+ this._config.onMessage(
1458
+ this,
1459
+ m,
1460
+ { direction: "down-in", depIndex: depIdx },
1461
+ this._actions
1462
+ );
1463
+ }
1464
+ if (sawSettlement) this._maybeRunFnOnSettlement();
1465
+ });
1466
+ } catch (err) {
1467
+ record.unsub = null;
1468
+ this._deps.pop();
1469
+ this._dirtyDepCount--;
1470
+ throw err;
1471
+ }
1472
+ return depIdx;
1473
+ }
1474
+ /**
1475
+ * @internal Unsubscribes from deps, fires fn cleanup (both shapes),
1476
+ * clears wave/store state, and (for compute nodes) drops `_cached` per
1477
+ * the ROM/RAM rule. Idempotent: second call is a no-op.
1478
+ *
1479
+ * @param skipStatusUpdate — When `true`, the caller takes responsibility
1480
+ * for setting `_status` after deactivation (e.g. TEARDOWN always sets
1481
+ * `"sentinel"` unconditionally). When `false` (default), deactivation
1482
+ * applies the ROM rule: compute nodes → `"sentinel"`, state nodes
1483
+ * preserve their current status.
1484
+ */
1485
+ _deactivate(skipStatusUpdate = false) {
1486
+ const cleanup = this._cleanup;
1487
+ this._cleanup = void 0;
1488
+ if (typeof cleanup === "function") {
1489
+ try {
1490
+ cleanup();
1491
+ } catch (err) {
1492
+ this._emit([[ERROR, this._wrapFnError("cleanup threw", err)]]);
1493
+ }
1494
+ } else if (cleanup != null && typeof cleanup.deactivation === "function") {
1495
+ try {
1496
+ cleanup.deactivation();
1497
+ } catch (err) {
1498
+ this._emit([[ERROR, this._wrapFnError("cleanup.deactivation threw", err)]]);
1499
+ }
1500
+ }
1501
+ for (const d of this._deps) {
1502
+ if (d.unsub != null) {
1503
+ const u = d.unsub;
1504
+ d.unsub = null;
1505
+ try {
1506
+ u();
1507
+ } catch {
1508
+ }
1509
+ }
1510
+ resetDepRecord(d);
1511
+ }
1512
+ this._waveHasNewData = false;
1513
+ this._hasNewTerminal = false;
1514
+ this._hasCalledFnOnce = false;
1515
+ this._paused = false;
1516
+ this._pendingWave = false;
1517
+ this._pendingRerun = false;
1518
+ this._rerunDepth = 0;
1519
+ this._store = {};
1520
+ this._dirtyDepCount = 0;
1521
+ this._pauseLocks = null;
1522
+ this._pauseBuffer = null;
1523
+ if (this._fn != null) {
1524
+ this._cached = void 0;
1525
+ }
1526
+ if (!skipStatusUpdate) {
1527
+ if (this._fn != null || this._deps.length > 0) {
1528
+ if (!this._isTerminal || this._resubscribable) {
1529
+ this._status = "sentinel";
1530
+ }
1531
+ }
1532
+ }
1533
+ }
1534
+ // --- Dep message dispatch (§3.5 singleton default) ---
1535
+ /**
1536
+ * @internal Default per-tier dispatch for incoming dep messages. Called
1537
+ * by `defaultOnMessage`. Updates the DepRecord, triggers wave
1538
+ * completion, and forwards passthrough traffic.
1539
+ */
1540
+ _onDepMessage(depIndex, msg) {
1541
+ const dep = this._deps[depIndex];
1542
+ const t = msg[0];
1543
+ if (this._inspectorHooks != null) {
1544
+ const ev = { kind: "dep_message", depIndex, message: msg };
1545
+ for (const hook of this._inspectorHooks) hook(ev);
1546
+ }
1547
+ if (t === START) return;
1548
+ if (t === DIRTY) {
1549
+ this._depDirtied(dep);
1550
+ return;
1551
+ }
1552
+ if (t === INVALIDATE) {
1553
+ this._depInvalidated(dep);
1554
+ this._emit(INVALIDATE_ONLY_BATCH);
1555
+ return;
1556
+ }
1557
+ if (t === PAUSE || t === RESUME) {
1558
+ this._emit([msg]);
1559
+ return;
1560
+ }
1561
+ if (t === TEARDOWN) {
1562
+ this._emit(TEARDOWN_ONLY_BATCH);
1563
+ return;
1564
+ }
1565
+ if (t === DATA) {
1566
+ this._depSettledAsData(dep, msg[1]);
1567
+ } else if (t === RESOLVED) {
1568
+ this._depSettledAsResolved(dep);
1569
+ } else if (t === COMPLETE) {
1570
+ this._depSettledAsTerminal(dep, true);
1571
+ } else if (t === ERROR) {
1572
+ this._depSettledAsTerminal(dep, msg[1]);
1573
+ } else {
1574
+ this._emit([msg]);
1575
+ return;
1576
+ }
1577
+ if (!this._fn) {
1578
+ if (t === DATA || t === RESOLVED) {
1579
+ this._emit([msg]);
1580
+ }
1581
+ if (t === COMPLETE || t === ERROR) {
1582
+ this._maybeAutoTerminalAfterWave();
1583
+ }
1584
+ return;
1585
+ }
1586
+ }
1587
+ // --- Centralized dep-state transitions (A3 settlement counters) ---
1588
+ //
1589
+ // Every mutation to `DepRecord.dirty` / `DepRecord.prevData` /
1590
+ // `DepRecord.terminal` must go through one of these helpers so the
1591
+ // `_dirtyDepCount` and `_sentinelDepCount` counters stay in sync with
1592
+ // the per-record flags. `_maybeRunFnOnSettlement` reads the counters
1593
+ // and never re-scans the `_deps` array.
1594
+ /**
1595
+ * Called when a dep transitions `dirty: false → true` (either from an
1596
+ * incoming DIRTY, or pre-set during `_activate` / `_addDep` /
1597
+ * `_depInvalidated`). No-op if the dep is already dirty. Fires the
1598
+ * downstream DIRTY emit if we're the first to dirty this wave.
1599
+ */
1600
+ _depDirtied(dep) {
1601
+ if (dep.dirty) return;
1602
+ dep.dirty = true;
1603
+ dep.involvedThisWave = true;
1604
+ this._dirtyDepCount++;
1605
+ if (this._status !== "dirty") {
1606
+ this._emit(DIRTY_ONLY_BATCH);
1607
+ }
1608
+ }
1609
+ /**
1610
+ * Called when a dep delivers new DATA: clears dirty, stores the payload,
1611
+ * marks wave-has-data, and — if this is the dep's first DATA — clears
1612
+ * its sentinel slot so the first-run gate can open.
1613
+ */
1614
+ _depSettledAsData(dep, value) {
1615
+ if (dep.dirty) {
1616
+ dep.dirty = false;
1617
+ this._dirtyDepCount--;
1618
+ }
1619
+ dep.involvedThisWave = true;
1620
+ dep.dataBatch.push(value);
1621
+ this._waveHasNewData = true;
1622
+ }
1623
+ /**
1624
+ * Called when a dep emits RESOLVED (wave settled, value unchanged).
1625
+ * Clears dirty; does NOT touch `prevData` / `terminal` / sentinel
1626
+ * count — sentinel only exits on first DATA or terminal, not RESOLVED.
1627
+ */
1628
+ _depSettledAsResolved(dep) {
1629
+ if (dep.dirty) {
1630
+ dep.dirty = false;
1631
+ this._dirtyDepCount--;
1632
+ }
1633
+ }
1634
+ /**
1635
+ * Called when a dep delivers COMPLETE (`terminal = true`) or ERROR
1636
+ * (`terminal = errorPayload`). Clears dirty, stores the terminal, and
1637
+ * — if the dep had never contributed a DATA yet — leaves sentinel
1638
+ * since the gate treats "terminated without data" as gate-open too.
1639
+ */
1640
+ _depSettledAsTerminal(dep, terminal) {
1641
+ if (dep.dirty) {
1642
+ dep.dirty = false;
1643
+ this._dirtyDepCount--;
1644
+ }
1645
+ dep.terminal = terminal;
1646
+ dep.involvedThisWave = true;
1647
+ this._hasNewTerminal = true;
1648
+ }
1649
+ /**
1650
+ * Called when a dep emits INVALIDATE: clears prevData, terminal, and
1651
+ * dataBatch. The dep is now back in the "never delivered a real value"
1652
+ * state — `prevData === undefined` so the sentinel check in fn will fire.
1653
+ */
1654
+ _depInvalidated(dep) {
1655
+ dep.prevData = void 0;
1656
+ dep.terminal = void 0;
1657
+ dep.dataBatch.length = 0;
1658
+ if (!dep.dirty) {
1659
+ dep.dirty = true;
1660
+ dep.involvedThisWave = true;
1661
+ this._dirtyDepCount++;
1662
+ } else {
1663
+ dep.involvedThisWave = false;
1664
+ }
1665
+ }
1666
+ _maybeRunFnOnSettlement() {
1667
+ if (this._isTerminal && !this._resubscribable) return;
1668
+ if (this._dirtyDepCount > 0) return;
1669
+ if (this._paused) {
1670
+ this._pendingWave = true;
1671
+ return;
1672
+ }
1673
+ if (!this._waveHasNewData && !this._hasNewTerminal && this._hasCalledFnOnce) {
1674
+ this._clearWaveFlags();
1675
+ this._emit(RESOLVED_ONLY_BATCH);
1676
+ this._maybeAutoTerminalAfterWave();
1677
+ return;
1678
+ }
1679
+ if (this._fn) this._execFn();
1680
+ this._maybeAutoTerminalAfterWave();
1681
+ }
1682
+ _maybeAutoTerminalAfterWave() {
1683
+ if (this._deps.length === 0) return;
1684
+ if (this._isTerminal) return;
1685
+ const erroredDep = this._deps.find((d) => d.terminal !== void 0 && d.terminal !== true);
1686
+ if (erroredDep != null) {
1687
+ if (this._autoError) {
1688
+ this._emit([[ERROR, erroredDep.terminal]]);
1689
+ }
1690
+ return;
1691
+ }
1692
+ if (this._autoComplete && this._deps.every((d) => d.terminal !== void 0)) {
1693
+ this._emit(COMPLETE_ONLY_BATCH);
1694
+ }
1695
+ }
1696
+ // --- Fn execution ---
1697
+ /**
1698
+ * @internal Runs the node fn once. Default cleanup (function form) fires
1699
+ * before the new run; `{ deactivation }` cleanup survives.
1700
+ */
1701
+ _execFn() {
1702
+ if (!this._fn) return;
1703
+ if (this._isTerminal && !this._resubscribable) return;
1704
+ if (this._isExecutingFn) {
1705
+ this._pendingRerun = true;
1706
+ return;
1707
+ }
1708
+ const prevCleanup = this._cleanup;
1709
+ if (typeof prevCleanup === "function") {
1710
+ this._cleanup = void 0;
1711
+ try {
1712
+ prevCleanup();
1713
+ } catch (err) {
1714
+ this._emit([[ERROR, this._wrapFnError("cleanup threw", err)]]);
1715
+ return;
1716
+ }
1717
+ }
1718
+ const batchData = this._deps.map(
1719
+ (d) => !d.involvedThisWave ? void 0 : d.dataBatch.length > 0 ? [...d.dataBatch] : []
1720
+ );
1721
+ const prevData = this._deps.map((d) => d.prevData);
1722
+ for (let i = 0; i < this._deps.length; i++) {
1723
+ const batch2 = batchData[i];
1724
+ if (batch2 != null && batch2.length > 0) {
1725
+ this._deps[i].prevData = batch2[batch2.length - 1];
1726
+ }
1727
+ }
1728
+ const terminalDeps = this._deps.map((d) => d.terminal);
1729
+ const ctx = { prevData, terminalDeps, store: this._store };
1730
+ this._hasCalledFnOnce = true;
1731
+ this._clearWaveFlags();
1732
+ if (this._inspectorHooks != null) {
1733
+ const ev = { kind: "run", batchData, prevData };
1734
+ for (const hook of this._inspectorHooks) hook(ev);
1735
+ }
1736
+ this._isExecutingFn = true;
1737
+ try {
1738
+ const result = this._fn(batchData, this._actions, ctx);
1739
+ if (typeof result === "function") {
1740
+ this._cleanup = result;
1741
+ } else if (result != null && typeof result === "object" && typeof result.deactivation === "function") {
1742
+ this._cleanup = result;
1743
+ }
1744
+ } catch (err) {
1745
+ this._emit([[ERROR, this._wrapFnError("fn threw", err)]]);
1746
+ } finally {
1747
+ this._isExecutingFn = false;
1748
+ if (this._pendingRerun) {
1749
+ this._pendingRerun = false;
1750
+ this._rerunDepth += 1;
1751
+ if (this._rerunDepth > MAX_RERUN_DEPTH) {
1752
+ this._rerunDepth = 0;
1753
+ this._emit([
1754
+ [
1755
+ ERROR,
1756
+ new Error(
1757
+ `Node "${this.name}": _pendingRerun depth exceeded ${MAX_RERUN_DEPTH} \u2014 likely a reactive cycle`
1758
+ )
1759
+ ]
1760
+ ]);
1761
+ } else {
1762
+ this._maybeRunFnOnSettlement();
1763
+ }
1764
+ } else {
1765
+ this._rerunDepth = 0;
1766
+ }
1767
+ this._clearWaveFlags();
1768
+ }
1769
+ }
1770
+ _clearWaveFlags() {
1771
+ this._waveHasNewData = false;
1772
+ this._hasNewTerminal = false;
1773
+ for (const d of this._deps) {
1774
+ d.involvedThisWave = false;
1775
+ d.dataBatch.length = 0;
1776
+ }
1777
+ }
1778
+ _wrapFnError(label, err) {
1779
+ const msg = err instanceof Error ? err.message : String(err);
1780
+ return new Error(`Node "${this.name}": ${label}: ${msg}`, { cause: err });
1781
+ }
1782
+ // --- Framing (tier sort + synthetic DIRTY prefix) ---
1783
+ /**
1784
+ * @internal Stable tier sort + synthetic DIRTY prefix for an outgoing
1785
+ * batch. Fast path: already-monotone single-tier batches (the common
1786
+ * case from interned singletons like `DIRTY_ONLY_BATCH`) return the
1787
+ * input unchanged. General path: decorate-sort-undecorate into a new
1788
+ * array, then prepend `[DIRTY]` after any tier-0 START entries when
1789
+ * a tier-3 payload is present and the node isn't already dirty.
1790
+ *
1791
+ * Single source of truth for the spec §1.3.1 framing invariant. Every
1792
+ * outgoing path hits `_frameBatch` exactly once via `_emit`.
1793
+ */
1794
+ _frameBatch(messages) {
1795
+ const tierOf = this._config.tierOf;
1796
+ if (messages.length === 1) {
1797
+ const t = tierOf(messages[0][0]);
1798
+ if (t === 3 && this._status !== "dirty") {
1799
+ return [DIRTY_MSG, messages[0]];
1800
+ }
1801
+ return messages;
1802
+ }
1803
+ let monotone = true;
1804
+ let hasTier3 = false;
1805
+ let hasDirty = false;
1806
+ let prevTier = -1;
1807
+ for (const m of messages) {
1808
+ const tier = tierOf(m[0]);
1809
+ if (tier < prevTier) monotone = false;
1810
+ if (tier === 3) hasTier3 = true;
1811
+ if (m[0] === DIRTY) hasDirty = true;
1812
+ prevTier = tier;
1813
+ }
1814
+ let sorted = messages;
1815
+ if (!monotone) {
1816
+ const indexed = messages.map((m, i) => ({ m, i, tier: tierOf(m[0]) }));
1817
+ indexed.sort((a, b) => a.tier - b.tier || a.i - b.i);
1818
+ sorted = indexed.map((x) => x.m);
1819
+ }
1820
+ if (hasTier3 && !hasDirty && this._status !== "dirty") {
1821
+ let insertAt = 0;
1822
+ while (insertAt < sorted.length && tierOf(sorted[insertAt][0]) === 0) insertAt++;
1823
+ if (insertAt === 0) return [DIRTY_MSG, ...sorted];
1824
+ return [...sorted.slice(0, insertAt), DIRTY_MSG, ...sorted.slice(insertAt)];
1825
+ }
1826
+ return sorted;
1827
+ }
1828
+ // --- Emit pipeline ---
1829
+ /**
1830
+ * @internal The unified dispatch waist — one call = one wave.
1831
+ *
1832
+ * Pipeline stages, in order:
1833
+ *
1834
+ * 1. Early-return on empty batch.
1835
+ * 2. Terminal filter — post-COMPLETE/ERROR only TEARDOWN/INVALIDATE
1836
+ * still propagate so graph teardown and cache-clear still work.
1837
+ * 3. Tier sort (stable) — the batch can be in any order when it
1838
+ * arrives; the walker downstream (`downWithBatch`) assumes
1839
+ * ascending tier monotone, and so does `_updateState`'s tier-3
1840
+ * slice walk. This is the single source of truth for ordering.
1841
+ * 4. Synthetic DIRTY prefix — if a tier-3 payload is present, no
1842
+ * DIRTY is already in the batch, and the node isn't already in
1843
+ * `"dirty"` status, prepend `[DIRTY]` after any tier-0 START
1844
+ * entries. Guarantees spec §1.3.1 (DIRTY precedes DATA within
1845
+ * the same batch) uniformly across every entry point.
1846
+ * 5. PAUSE/RESUME lock bookkeeping (C0) — update `_pauseLocks`,
1847
+ * derive `_paused`, filter unknown-lockId RESUME, replay
1848
+ * bufferAll buffer on final lock release.
1849
+ * 6. Meta TEARDOWN fan-out — notify meta children before
1850
+ * `_updateState`'s TEARDOWN branch calls `_deactivate`. Hoisted
1851
+ * out of the walk to keep `_updateState` re-entrance-free.
1852
+ * 7. `_updateState` — walk the batch in tier order, advancing
1853
+ * `_cached` / `_status` / `_versioning` and running equals
1854
+ * substitution on tier-3 DATA (§3.5.1). Returns
1855
+ * `{finalMessages, equalsError?}`.
1856
+ * 8. `downWithBatch` dispatch (or bufferAll capture if paused with
1857
+ * `pausable: "resumeAll"`).
1858
+ * 9. Recursive ERROR emission if equals threw mid-walk.
1859
+ *
1860
+ * `node.down` / `node.emit` / `actions.down` / `actions.emit` all
1861
+ * converge here — the unified `_emit` waist (spec §1.3.1).
1862
+ */
1863
+ _emit(messages) {
1864
+ if (messages.length === 0) return;
1865
+ let deliverable = messages;
1866
+ const terminal = this._isTerminal;
1867
+ if (terminal && !this._resubscribable) {
1868
+ const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
1869
+ if (pass.length === 0) return;
1870
+ deliverable = pass;
1871
+ }
1872
+ deliverable = this._frameBatch(deliverable);
1873
+ let filtered = null;
1874
+ for (let i = 0; i < deliverable.length; i++) {
1875
+ const m = deliverable[i];
1876
+ const t = m[0];
1877
+ if (t !== PAUSE && t !== RESUME) {
1878
+ if (filtered != null) filtered.push(m);
1879
+ continue;
1880
+ }
1881
+ if (m.length < 2) {
1882
+ throw new Error(
1883
+ `Node "${this.name}": [[${t === PAUSE ? "PAUSE" : "RESUME"}]] must carry a lockId payload \u2014 bare PAUSE/RESUME is a protocol violation (C0 rule). Use \`[[PAUSE, lockId]]\` / \`[[RESUME, lockId]]\`.`
1884
+ );
1885
+ }
1886
+ let forward = true;
1887
+ if (this._pausable !== false) {
1888
+ const lockId = m[1];
1889
+ if (t === PAUSE) {
1890
+ if (this._pauseLocks == null) this._pauseLocks = /* @__PURE__ */ new Set();
1891
+ this._pauseLocks.add(lockId);
1892
+ this._paused = true;
1893
+ if (this._pausable === "resumeAll" && this._pauseBuffer == null) {
1894
+ this._pauseBuffer = [];
1895
+ }
1896
+ } else {
1897
+ if (this._pauseLocks == null || !this._pauseLocks.has(lockId)) {
1898
+ forward = false;
1899
+ } else {
1900
+ this._pauseLocks.delete(lockId);
1901
+ if (this._pauseLocks.size === 0) {
1902
+ this._paused = false;
1903
+ if (this._pauseBuffer != null && this._pauseBuffer.length > 0) {
1904
+ const drain = this._pauseBuffer;
1905
+ this._pauseBuffer = [];
1906
+ this._emit(drain);
1907
+ }
1908
+ if (this._pendingWave) {
1909
+ this._pendingWave = false;
1910
+ this._maybeRunFnOnSettlement();
1911
+ }
1912
+ }
1913
+ }
1914
+ }
1915
+ }
1916
+ if (!forward) {
1917
+ if (filtered == null) filtered = deliverable.slice(0, i);
1918
+ } else if (filtered != null) {
1919
+ filtered.push(m);
1920
+ }
1921
+ }
1922
+ if (filtered != null) {
1923
+ if (filtered.length === 0) return;
1924
+ deliverable = filtered;
1925
+ }
1926
+ if (this._hasMeta && deliverable.some((m) => m[0] === TEARDOWN)) {
1927
+ for (const k of Object.keys(this.meta)) {
1928
+ try {
1929
+ this.meta[k]._emit(TEARDOWN_ONLY_BATCH);
1930
+ } catch {
1931
+ }
1932
+ }
1933
+ }
1934
+ const { finalMessages, equalsError } = this._updateState(deliverable);
1935
+ if (finalMessages.length > 0 && this._config.inspectorEnabled) {
1936
+ const inspector = this._config.globalInspector;
1937
+ if (inspector != null) {
1938
+ try {
1939
+ inspector({ kind: "emit", node: this, messages: finalMessages });
1940
+ } catch {
1941
+ }
1942
+ }
1943
+ }
1944
+ if (finalMessages.length > 0) {
1945
+ if (this._paused && this._pausable === "resumeAll" && this._pauseBuffer != null) {
1946
+ const tierOf = this._config.tierOf;
1947
+ const immediate = [];
1948
+ for (const m of finalMessages) {
1949
+ const tier = tierOf(m[0]);
1950
+ if (tier < 3 || tier === 5) {
1951
+ immediate.push(m);
1952
+ } else {
1953
+ this._pauseBuffer.push(m);
1954
+ }
1955
+ }
1956
+ if (immediate.length > 0) {
1957
+ this._dispatchOrAccumulate(immediate);
1958
+ }
1959
+ } else {
1960
+ this._dispatchOrAccumulate(finalMessages);
1961
+ }
1962
+ }
1963
+ if (equalsError != null) {
1964
+ this._emit([[ERROR, equalsError]]);
1965
+ }
1966
+ }
1967
+ /**
1968
+ * @internal Walk an outgoing (already-framed) batch, updating own
1969
+ * cache / status / versioning and running equals substitution on
1970
+ * every tier-3 DATA (§3.5.1). Framing — tier sort and synthetic
1971
+ * DIRTY prefix — has already happened upstream in `_frameBatch`.
1972
+ * This walk trusts the input is in monotone tier order and that the
1973
+ * spec §1.3.1 DIRTY/RESOLVED precedence invariant is already
1974
+ * satisfied by the frame.
1975
+ *
1976
+ * Equals substitution: every DATA payload is compared against the
1977
+ * live `_cached`; when equal, the tuple is rewritten to `[RESOLVED]`
1978
+ * in a per-call copy and cache is not re-advanced. `.cache` remains
1979
+ * coherent with "the last DATA payload this node actually sent
1980
+ * downstream".
1981
+ *
1982
+ * Returns `{ finalMessages, equalsError? }`:
1983
+ * - `finalMessages` — the array to deliver to sinks (may be
1984
+ * `messages` unchanged, a rewritten copy with DATA→RESOLVED
1985
+ * substitutions, or a truncated prefix when equals throws mid-walk).
1986
+ * - `equalsError` — present only when the configured `equals` function
1987
+ * threw on some DATA message. `_emit` delivers the prefix first,
1988
+ * then emits a fresh ERROR batch via a recursive `_emit` call so
1989
+ * subscribers observe `[...walked_prefix, ERROR]` in order.
1990
+ */
1991
+ _updateState(messages) {
1992
+ const tierOf = this._config.tierOf;
1993
+ let rewritten;
1994
+ let equalsError;
1995
+ let abortedAt = -1;
1996
+ let dataCount = 0;
1997
+ for (const m of messages) {
1998
+ if (tierOf(m[0]) === 3) dataCount++;
1999
+ }
2000
+ const checkEquals = dataCount <= 1;
2001
+ let lastDataIdx = -1;
2002
+ if (this._versioning != null && dataCount > 1) {
2003
+ for (let i = messages.length - 1; i >= 0; i--) {
2004
+ if (messages[i][0] === DATA) {
2005
+ lastDataIdx = i;
2006
+ break;
2007
+ }
2008
+ }
2009
+ }
2010
+ for (let i = 0; i < messages.length; i++) {
2011
+ const m = messages[i];
2012
+ const t = m[0];
2013
+ if (t === DATA) {
2014
+ if (m.length >= 2) {
2015
+ let unchanged = false;
2016
+ if (checkEquals && this._cached !== void 0) {
2017
+ try {
2018
+ unchanged = this._equals(this._cached, m[1]);
2019
+ } catch (err) {
2020
+ equalsError = this._wrapFnError("equals threw", err);
2021
+ abortedAt = i;
2022
+ break;
2023
+ }
2024
+ }
2025
+ if (unchanged) {
2026
+ if (rewritten == null) rewritten = messages.slice(0, i);
2027
+ rewritten.push(RESOLVED_MSG);
2028
+ this._status = "resolved";
2029
+ continue;
2030
+ }
2031
+ this._cached = m[1];
2032
+ if (this._versioning != null) {
2033
+ if (lastDataIdx < 0 || i === lastDataIdx) {
2034
+ advanceVersion(this._versioning, m[1], this._hashFn);
2035
+ }
2036
+ }
2037
+ }
2038
+ this._status = "settled";
2039
+ if (rewritten != null) rewritten.push(m);
2040
+ } else {
2041
+ if (rewritten != null) rewritten.push(m);
2042
+ if (t === DIRTY) {
2043
+ this._status = "dirty";
2044
+ } else if (t === RESOLVED) {
2045
+ this._status = "resolved";
2046
+ } else if (t === COMPLETE) {
2047
+ this._status = "completed";
2048
+ } else if (t === ERROR) {
2049
+ this._status = "errored";
2050
+ } else if (t === INVALIDATE) {
2051
+ this._cached = void 0;
2052
+ this._status = "dirty";
2053
+ const c = this._cleanup;
2054
+ if (typeof c === "function") {
2055
+ this._cleanup = void 0;
2056
+ try {
2057
+ c();
2058
+ } catch {
2059
+ }
2060
+ }
2061
+ } else if (t === TEARDOWN) {
2062
+ if (this._resetOnTeardown) this._cached = void 0;
2063
+ this._deactivate(
2064
+ /* skipStatusUpdate */
2065
+ true
2066
+ );
2067
+ this._status = "sentinel";
2068
+ }
2069
+ }
2070
+ }
2071
+ const base = abortedAt >= 0 ? rewritten ?? messages.slice(0, abortedAt) : rewritten ?? messages;
2072
+ return equalsError != null ? { finalMessages: base, equalsError } : { finalMessages: base };
2073
+ }
2074
+ _deliverToSinks = (messages) => {
2075
+ if (this._sinks == null) return;
2076
+ if (typeof this._sinks === "function") {
2077
+ this._sinks(messages);
2078
+ return;
2079
+ }
2080
+ const snapshot = [...this._sinks];
2081
+ for (const sink of snapshot) sink(messages);
2082
+ };
2083
+ /**
2084
+ * @internal Dispatch entry point that respects the per-batch emit
2085
+ * accumulator (Bug 2). Inside an explicit `batch()` scope, append to
2086
+ * `_batchPendingMessages` and register a flush hook on first append.
2087
+ * Outside batch — or during a drain (where `flushInProgress` is true
2088
+ * but `batchDepth` is 0) — dispatch synchronously through `downWithBatch`.
2089
+ *
2090
+ * Per-emit state updates (`_frameBatch`, `_updateState`) have already
2091
+ * happened by the time we reach here; only the **downstream delivery**
2092
+ * is coalesced. Cache, version, and status are visible mid-batch on
2093
+ * the emitting node itself.
2094
+ */
2095
+ _dispatchOrAccumulate(messages) {
2096
+ if (isExplicitlyBatching()) {
2097
+ if (this._batchPendingMessages === null) {
2098
+ this._batchPendingMessages = [];
2099
+ registerBatchFlushHook(() => this._flushBatchPending());
2100
+ }
2101
+ for (const m of messages) this._batchPendingMessages.push(m);
2102
+ return;
2103
+ }
2104
+ downWithBatch(this._deliverToSinks, messages, this._config.tierOf);
2105
+ }
2106
+ /**
2107
+ * @internal Flushes the accumulated batch through `downWithBatch` and
2108
+ * clears the pending state. Idempotent — safe to call when pending is
2109
+ * already null or empty (e.g. on a `batch()` throw, where the hook
2110
+ * fires for cleanup but the drainPhase queues are wiped after).
2111
+ *
2112
+ * Critical: the accumulated batch is interleaved per-emit framings like
2113
+ * `[DIRTY, DATA(1), DIRTY, DATA(2)]` — non-monotone tier order. We must
2114
+ * re-frame to sort by tier before handing to `downWithBatch`, which
2115
+ * assumes pre-sorted input. `_frameBatch` also handles the synthetic
2116
+ * DIRTY prepend rule (no-op here — `hasDirty` is true since each
2117
+ * accumulated emit already carries its own DIRTY prefix).
2118
+ */
2119
+ _flushBatchPending() {
2120
+ const pending = this._batchPendingMessages;
2121
+ if (pending === null) return;
2122
+ this._batchPendingMessages = null;
2123
+ if (pending.length === 0) return;
2124
+ const framed = this._frameBatch(pending);
2125
+ downWithBatch(this._deliverToSinks, framed, this._config.tierOf);
2126
+ }
2127
+ };
2128
+ var isNodeArray = (value) => Array.isArray(value);
2129
+ var isNodeOptionsObject = (value) => typeof value === "object" && value != null && !Array.isArray(value);
2130
+ function node(depsOrFn, fnOrOpts, optsArg) {
2131
+ const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
2132
+ const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
2133
+ let opts = {};
2134
+ if (isNodeArray(depsOrFn)) {
2135
+ opts = (isNodeOptionsObject(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
2136
+ } else if (isNodeOptionsObject(depsOrFn)) {
2137
+ opts = depsOrFn;
2138
+ } else {
2139
+ opts = (isNodeOptionsObject(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
2140
+ }
2141
+ return new NodeImpl(deps, fn, opts);
2142
+ }
2143
+
2144
+ // src/core/sugar.ts
2145
+ function sentinelGuard(batchData, ctx, allowPartial) {
2146
+ if (allowPartial) return false;
2147
+ return batchData.some(
2148
+ (batch2, i) => !(batch2 != null && batch2.length > 0) && ctx.prevData[i] === void 0
2149
+ );
2150
+ }
2151
+ function state(initial, opts) {
2152
+ return node([], { ...opts, initial });
2153
+ }
2154
+ function producer(fn, opts) {
2155
+ const wrapped = (_data, actions, ctx) => fn(actions, ctx) ?? void 0;
2156
+ return node(wrapped, { describeKind: "producer", ...opts });
2157
+ }
2158
+ function derived(deps, fn, opts) {
2159
+ const allowPartial = opts?.partial ?? false;
2160
+ const wrapped = (batchData, actions, ctx) => {
2161
+ if (sentinelGuard(batchData, ctx, allowPartial)) {
2162
+ actions.down([[RESOLVED]]);
2163
+ return void 0;
2164
+ }
2165
+ const data = batchData.map(
2166
+ (batch2, i) => batch2 != null && batch2.length > 0 ? batch2.at(-1) : ctx.prevData[i]
2167
+ );
2168
+ actions.emit(fn(data, ctx));
2169
+ return void 0;
2170
+ };
2171
+ return node(deps, wrapped, { describeKind: "derived", ...opts });
2172
+ }
2173
+ function effect(deps, fn, opts) {
2174
+ const allowPartial = opts?.partial ?? false;
2175
+ const wrapped = (batchData, actions, ctx) => {
2176
+ if (sentinelGuard(batchData, ctx, allowPartial)) {
2177
+ actions.down([[RESOLVED]]);
2178
+ return void 0;
2179
+ }
2180
+ const data = batchData.map(
2181
+ (batch2, i) => batch2 != null && batch2.length > 0 ? batch2.at(-1) : ctx.prevData[i]
2182
+ );
2183
+ return fn(data, actions, ctx) ?? void 0;
2184
+ };
2185
+ return node(deps, wrapped, { describeKind: "effect", ...opts });
2186
+ }
2187
+ function dynamicNode(allDeps, fn, opts) {
2188
+ const depIndex = /* @__PURE__ */ new Map();
2189
+ allDeps.forEach((d, i) => {
2190
+ depIndex.set(d, i);
2191
+ });
2192
+ return derived(
2193
+ allDeps,
2194
+ // data[i] is already sugar-unwrapped to a scalar by derived()'s wrapper.
2195
+ (data, ctx) => {
2196
+ const track = (dep) => {
2197
+ const i = depIndex.get(dep);
2198
+ if (i == null) {
2199
+ throw new Error(`dynamicNode: untracked dep "${dep.name ?? "<unnamed>"}"`);
2200
+ }
2201
+ return data[i];
2202
+ };
2203
+ return fn(track, ctx);
2204
+ },
2205
+ opts
2206
+ );
2207
+ }
2208
+ function autoTrackNode(fn, opts) {
2209
+ let implRef;
2210
+ const depIndexMap = /* @__PURE__ */ new Map();
2211
+ const allowPartial = opts?.partial ?? false;
2212
+ const wrappedFn = (batchData, actions, ctx) => {
2213
+ let foundNew = false;
2214
+ const track = (dep) => {
2215
+ const idx = depIndexMap.get(dep);
2216
+ if (idx !== void 0) {
2217
+ if (idx < batchData.length) {
2218
+ const batch2 = batchData[idx];
2219
+ if (batch2 != null && batch2.length > 0) return batch2.at(-1);
2220
+ return ctx.prevData[idx];
2221
+ }
2222
+ return dep.cache;
2223
+ }
2224
+ foundNew = true;
2225
+ const newIdx = implRef._addDep(dep);
2226
+ depIndexMap.set(dep, newIdx);
2227
+ return dep.cache;
2228
+ };
2229
+ if (!allowPartial && depIndexMap.size > 0) {
2230
+ for (const [, idx] of depIndexMap) {
2231
+ if (idx < batchData.length) {
2232
+ const batch2 = batchData[idx];
2233
+ if (!(batch2 != null && batch2.length > 0) && ctx.prevData[idx] === void 0) {
2234
+ actions.down([[RESOLVED]]);
2235
+ return void 0;
2236
+ }
2237
+ }
2238
+ }
2239
+ }
2240
+ try {
2241
+ const result = fn(track, ctx);
2242
+ if (!foundNew) {
2243
+ actions.emit(result);
2244
+ if (ctx.store.__autoTrackLastDiscoveryError != null) {
2245
+ delete ctx.store.__autoTrackLastDiscoveryError;
2246
+ }
2247
+ }
2248
+ } catch (err) {
2249
+ if (!foundNew) throw err;
2250
+ ctx.store.__autoTrackLastDiscoveryError = err;
2251
+ }
2252
+ return void 0;
2253
+ };
2254
+ implRef = new NodeImpl([], wrappedFn, {
2255
+ describeKind: "derived",
2256
+ ...opts
2257
+ });
2258
+ return implRef;
2259
+ }
2260
+ function pipe(source, ...ops) {
2261
+ let current = source;
2262
+ for (const op of ops) current = op(current);
2263
+ return current;
2264
+ }
2265
+
2266
+ export {
2267
+ __require,
2268
+ __export,
2269
+ __decoratorStart,
2270
+ __runInitializers,
2271
+ __decorateElement,
2272
+ START,
2273
+ DATA,
2274
+ DIRTY,
2275
+ RESOLVED,
2276
+ INVALIDATE,
2277
+ PAUSE,
2278
+ RESUME,
2279
+ TEARDOWN,
2280
+ COMPLETE,
2281
+ ERROR,
2282
+ DIRTY_MSG,
2283
+ RESOLVED_MSG,
2284
+ INVALIDATE_MSG,
2285
+ START_MSG,
2286
+ COMPLETE_MSG,
2287
+ TEARDOWN_MSG,
2288
+ DIRTY_ONLY_BATCH,
2289
+ RESOLVED_ONLY_BATCH,
2290
+ INVALIDATE_ONLY_BATCH,
2291
+ COMPLETE_ONLY_BATCH,
2292
+ TEARDOWN_ONLY_BATCH,
2293
+ JsonCodec,
2294
+ createDagCborCodec,
2295
+ createDagCborZstdCodec,
2296
+ ENVELOPE_VERSION,
2297
+ encodeEnvelope,
2298
+ decodeEnvelope,
2299
+ registerBuiltinCodecs,
2300
+ replayWAL,
2301
+ DEFAULT_ACTOR,
2302
+ normalizeActor,
2303
+ isBatching,
2304
+ batch,
2305
+ downWithBatch,
2306
+ monotonicNs,
2307
+ wallClockNs,
2308
+ GraphReFlyConfig,
2309
+ registerBuiltins,
2310
+ GuardDenied,
2311
+ policy,
2312
+ policyFromRules,
2313
+ accessHintForGuard,
2314
+ defaultHash,
2315
+ createVersioning,
2316
+ advanceVersion,
2317
+ isV1,
2318
+ defaultConfig,
2319
+ configure,
2320
+ NodeImpl,
2321
+ node,
2322
+ state,
2323
+ producer,
2324
+ derived,
2325
+ effect,
2326
+ dynamicNode,
2327
+ autoTrackNode,
2328
+ pipe
2329
+ };
2330
+ //# sourceMappingURL=chunk-NZMBRXQV.js.map