@graphrefly/graphrefly 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-R6OHUUYB.js → chunk-AHRKWMNI.js} +7 -7
- package/dist/chunk-AHRKWMNI.js.map +1 -0
- package/dist/{chunk-2PORF4RP.js → chunk-BER7UYLM.js} +27 -32
- package/dist/chunk-BER7UYLM.js.map +1 -0
- package/dist/{chunk-646OG3PO.js → chunk-IRZAGZUB.js} +51 -52
- package/dist/chunk-IRZAGZUB.js.map +1 -0
- package/dist/{chunk-IHJHBADD.js → chunk-JC2SN46B.js} +385 -197
- package/dist/chunk-JC2SN46B.js.map +1 -0
- package/dist/{chunk-XJ6EMQ22.js → chunk-OO5QOAXI.js} +4 -10
- package/dist/chunk-OO5QOAXI.js.map +1 -0
- package/dist/{chunk-YXROQFXZ.js → chunk-UW77D7SP.js} +3 -3
- package/dist/{chunk-F2ULI3Q3.js → chunk-XUOY3YKN.js} +7 -3
- package/dist/chunk-XUOY3YKN.js.map +1 -0
- package/dist/chunk-YLR5JUJZ.js +111 -0
- package/dist/chunk-YLR5JUJZ.js.map +1 -0
- package/dist/{chunk-BV3TPSBK.js → chunk-YXR3WW3Q.js} +740 -755
- package/dist/chunk-YXR3WW3Q.js.map +1 -0
- package/dist/compat/nestjs/index.cjs +1127 -983
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +4 -4
- package/dist/compat/nestjs/index.d.ts +4 -4
- package/dist/compat/nestjs/index.js +7 -13
- package/dist/core/index.cjs +653 -749
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +7 -7
- package/dist/extra/index.cjs +773 -795
- package/dist/extra/index.cjs.map +1 -1
- package/dist/extra/index.d.cts +4 -4
- package/dist/extra/index.d.ts +4 -4
- package/dist/extra/index.js +5 -11
- package/dist/graph/index.cjs +1036 -975
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +3 -3
- package/dist/graph/index.d.ts +3 -3
- package/dist/graph/index.js +8 -8
- package/dist/{graph-fCsaaVIa.d.cts → graph-KsTe57nI.d.cts} +127 -51
- package/dist/{graph-Dc-P9BVm.d.ts → graph-mILUUqW8.d.ts} +127 -51
- package/dist/{index-DhXznWyH.d.ts → index-8a605sg9.d.ts} +2 -2
- package/dist/{index-D7y9Q8W4.d.ts → index-B2SvPEbc.d.ts} +8 -69
- package/dist/{index-YlOH1Gw6.d.cts → index-BBUYZfJ1.d.cts} +122 -78
- package/dist/{index-ClaKZFPl.d.cts → index-Bjh5C1Tp.d.cts} +38 -35
- package/dist/{index-DWq0P9T6.d.ts → index-BjtlNirP.d.cts} +5 -7
- package/dist/{index-N704txAA.d.ts → index-BnkMgNNa.d.ts} +38 -35
- package/dist/{index-BBVBYPxr.d.cts → index-CgSiUouz.d.ts} +5 -7
- package/dist/{index-BmoUvOGN.d.ts → index-CvKzv0AW.d.ts} +122 -78
- package/dist/{index-4OIX-q0C.d.cts → index-UudxGnzc.d.cts} +8 -69
- package/dist/{index-DlGMf_Qe.d.cts → index-VHA43cGP.d.cts} +2 -2
- package/dist/index.cjs +6146 -5725
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +617 -383
- package/dist/index.d.ts +617 -383
- package/dist/index.js +4401 -4028
- package/dist/index.js.map +1 -1
- package/dist/{meta-BV4pj9ML.d.cts → meta-BnG7XAaE.d.cts} +395 -289
- package/dist/{meta-BV4pj9ML.d.ts → meta-BnG7XAaE.d.ts} +395 -289
- package/dist/observable-C8Kx_O6k.d.cts +36 -0
- package/dist/observable-DcBwQY7t.d.ts +36 -0
- package/dist/patterns/reactive-layout/index.cjs +1037 -857
- package/dist/patterns/reactive-layout/index.cjs.map +1 -1
- package/dist/patterns/reactive-layout/index.d.cts +3 -3
- package/dist/patterns/reactive-layout/index.d.ts +3 -3
- package/dist/patterns/reactive-layout/index.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-2PORF4RP.js.map +0 -1
- package/dist/chunk-646OG3PO.js.map +0 -1
- package/dist/chunk-BV3TPSBK.js.map +0 -1
- package/dist/chunk-EBNKJULL.js +0 -231
- package/dist/chunk-EBNKJULL.js.map +0 -1
- package/dist/chunk-F2ULI3Q3.js.map +0 -1
- package/dist/chunk-IHJHBADD.js.map +0 -1
- package/dist/chunk-R6OHUUYB.js.map +0 -1
- package/dist/chunk-XJ6EMQ22.js.map +0 -1
- package/dist/observable-Cz-AWhwR.d.cts +0 -42
- package/dist/observable-DCqlwGyl.d.ts +0 -42
- /package/dist/{chunk-YXROQFXZ.js.map → chunk-UW77D7SP.js.map} +0 -0
|
@@ -68,7 +68,106 @@ function normalizeActor(actor) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// src/core/guard.ts
|
|
72
|
+
var GuardDenied = class extends Error {
|
|
73
|
+
actor;
|
|
74
|
+
action;
|
|
75
|
+
nodeName;
|
|
76
|
+
/**
|
|
77
|
+
* @param details - Actor, action, and optional node name for the denial.
|
|
78
|
+
* @param message - Optional override for the default error message.
|
|
79
|
+
*/
|
|
80
|
+
constructor(details, message) {
|
|
81
|
+
super(
|
|
82
|
+
message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
|
|
83
|
+
);
|
|
84
|
+
this.name = "GuardDenied";
|
|
85
|
+
this.actor = details.actor;
|
|
86
|
+
this.action = details.action;
|
|
87
|
+
this.nodeName = details.nodeName;
|
|
88
|
+
}
|
|
89
|
+
/** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
|
|
90
|
+
get node() {
|
|
91
|
+
return this.nodeName;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
function normalizeActions(action) {
|
|
95
|
+
if (Array.isArray(action)) {
|
|
96
|
+
return [...action];
|
|
97
|
+
}
|
|
98
|
+
return [action];
|
|
99
|
+
}
|
|
100
|
+
function matchesActions(set, action) {
|
|
101
|
+
return set.has(action) || set.has("*");
|
|
102
|
+
}
|
|
103
|
+
function policy(build) {
|
|
104
|
+
const rules = [];
|
|
105
|
+
const allow = (action, opts) => {
|
|
106
|
+
rules.push({
|
|
107
|
+
kind: "allow",
|
|
108
|
+
actions: new Set(normalizeActions(action)),
|
|
109
|
+
where: opts?.where ?? (() => true)
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const deny = (action, opts) => {
|
|
113
|
+
rules.push({
|
|
114
|
+
kind: "deny",
|
|
115
|
+
actions: new Set(normalizeActions(action)),
|
|
116
|
+
where: opts?.where ?? (() => true)
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
build(allow, deny);
|
|
120
|
+
return (actor, action) => {
|
|
121
|
+
let denied = false;
|
|
122
|
+
let allowed = false;
|
|
123
|
+
for (const r of rules) {
|
|
124
|
+
if (!matchesActions(r.actions, action)) continue;
|
|
125
|
+
if (!r.where(actor)) continue;
|
|
126
|
+
if (r.kind === "deny") {
|
|
127
|
+
denied = true;
|
|
128
|
+
} else {
|
|
129
|
+
allowed = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (denied) return false;
|
|
133
|
+
return allowed;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function policyFromRules(rules) {
|
|
137
|
+
return policy((allow, deny) => {
|
|
138
|
+
for (const rule of rules) {
|
|
139
|
+
const actorTypes = rule.actorType == null ? null : new Set(Array.isArray(rule.actorType) ? rule.actorType : [rule.actorType]);
|
|
140
|
+
const actorIds = rule.actorId == null ? null : new Set(Array.isArray(rule.actorId) ? rule.actorId : [rule.actorId]);
|
|
141
|
+
const claimEntries = Object.entries(rule.claims ?? {});
|
|
142
|
+
const where = (actor) => {
|
|
143
|
+
if (actorTypes !== null && !actorTypes.has(String(actor.type))) return false;
|
|
144
|
+
if (actorIds !== null && !actorIds.has(String(actor.id ?? ""))) return false;
|
|
145
|
+
for (const [key, value] of claimEntries) {
|
|
146
|
+
if (actor[key] !== value) return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
};
|
|
150
|
+
if (rule.effect === "deny") {
|
|
151
|
+
deny(rule.action, { where });
|
|
152
|
+
} else {
|
|
153
|
+
allow(rule.action, { where });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
|
|
159
|
+
function accessHintForGuard(guard) {
|
|
160
|
+
const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
|
|
161
|
+
if (allowed.length === 0) return "restricted";
|
|
162
|
+
if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
|
|
163
|
+
return "both";
|
|
164
|
+
}
|
|
165
|
+
if (allowed.length === 1) return allowed[0];
|
|
166
|
+
return allowed.join("+");
|
|
167
|
+
}
|
|
168
|
+
|
|
71
169
|
// src/core/messages.ts
|
|
170
|
+
var START = /* @__PURE__ */ Symbol.for("graphrefly/START");
|
|
72
171
|
var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
|
|
73
172
|
var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
|
|
74
173
|
var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
|
|
@@ -79,6 +178,7 @@ var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
|
|
|
79
178
|
var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
|
|
80
179
|
var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
|
|
81
180
|
var knownMessageTypes = [
|
|
181
|
+
START,
|
|
82
182
|
DATA,
|
|
83
183
|
DIRTY,
|
|
84
184
|
RESOLVED,
|
|
@@ -89,16 +189,18 @@ var knownMessageTypes = [
|
|
|
89
189
|
COMPLETE,
|
|
90
190
|
ERROR
|
|
91
191
|
];
|
|
192
|
+
var knownMessageSet = new Set(knownMessageTypes);
|
|
92
193
|
function isKnownMessageType(t) {
|
|
93
|
-
return
|
|
194
|
+
return knownMessageSet.has(t);
|
|
94
195
|
}
|
|
95
196
|
function messageTier(t) {
|
|
96
|
-
if (t ===
|
|
97
|
-
if (t ===
|
|
98
|
-
if (t ===
|
|
99
|
-
if (t ===
|
|
100
|
-
if (t ===
|
|
101
|
-
return
|
|
197
|
+
if (t === START) return 0;
|
|
198
|
+
if (t === DIRTY || t === INVALIDATE) return 1;
|
|
199
|
+
if (t === PAUSE || t === RESUME) return 2;
|
|
200
|
+
if (t === DATA || t === RESOLVED) return 3;
|
|
201
|
+
if (t === COMPLETE || t === ERROR) return 4;
|
|
202
|
+
if (t === TEARDOWN) return 5;
|
|
203
|
+
return 1;
|
|
102
204
|
}
|
|
103
205
|
function isPhase2Message(msg) {
|
|
104
206
|
const t = msg[0];
|
|
@@ -107,6 +209,10 @@ function isPhase2Message(msg) {
|
|
|
107
209
|
function isTerminalMessage(t) {
|
|
108
210
|
return t === COMPLETE || t === ERROR;
|
|
109
211
|
}
|
|
212
|
+
function isLocalOnly(t) {
|
|
213
|
+
if (!knownMessageSet.has(t)) return false;
|
|
214
|
+
return messageTier(t) < 3;
|
|
215
|
+
}
|
|
110
216
|
function propagatesToMeta(t) {
|
|
111
217
|
return t === TEARDOWN;
|
|
112
218
|
}
|
|
@@ -267,14 +373,14 @@ function _downSequential(sink, messages, phase = 2) {
|
|
|
267
373
|
const dataQueue = phase === 3 ? pendingPhase3 : pendingPhase2;
|
|
268
374
|
for (const msg of messages) {
|
|
269
375
|
const tier = messageTier(msg[0]);
|
|
270
|
-
if (tier ===
|
|
376
|
+
if (tier === 3) {
|
|
271
377
|
if (isBatching()) {
|
|
272
378
|
const m = msg;
|
|
273
379
|
dataQueue.push(() => sink([m]));
|
|
274
380
|
} else {
|
|
275
381
|
sink([msg]);
|
|
276
382
|
}
|
|
277
|
-
} else if (tier >=
|
|
383
|
+
} else if (tier >= 4) {
|
|
278
384
|
if (isBatching()) {
|
|
279
385
|
const m = msg;
|
|
280
386
|
pendingPhase3.push(() => sink([m]));
|
|
@@ -295,104 +401,6 @@ function wallClockNs() {
|
|
|
295
401
|
return Date.now() * 1e6;
|
|
296
402
|
}
|
|
297
403
|
|
|
298
|
-
// src/core/guard.ts
|
|
299
|
-
var GuardDenied = class extends Error {
|
|
300
|
-
actor;
|
|
301
|
-
action;
|
|
302
|
-
nodeName;
|
|
303
|
-
/**
|
|
304
|
-
* @param details - Actor, action, and optional node name for the denial.
|
|
305
|
-
* @param message - Optional override for the default error message.
|
|
306
|
-
*/
|
|
307
|
-
constructor(details, message) {
|
|
308
|
-
super(
|
|
309
|
-
message ?? `GuardDenied: action "${String(details.action)}" denied for actor type "${String(details.actor.type)}"`
|
|
310
|
-
);
|
|
311
|
-
this.name = "GuardDenied";
|
|
312
|
-
this.actor = details.actor;
|
|
313
|
-
this.action = details.action;
|
|
314
|
-
this.nodeName = details.nodeName;
|
|
315
|
-
}
|
|
316
|
-
/** Qualified registry path when known (roadmap diagnostics: same as {@link nodeName}). */
|
|
317
|
-
get node() {
|
|
318
|
-
return this.nodeName;
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
function normalizeActions(action) {
|
|
322
|
-
if (Array.isArray(action)) {
|
|
323
|
-
return [...action];
|
|
324
|
-
}
|
|
325
|
-
return [action];
|
|
326
|
-
}
|
|
327
|
-
function matchesActions(set, action) {
|
|
328
|
-
return set.has(action) || set.has("*");
|
|
329
|
-
}
|
|
330
|
-
function policy(build) {
|
|
331
|
-
const rules = [];
|
|
332
|
-
const allow = (action, opts) => {
|
|
333
|
-
rules.push({
|
|
334
|
-
kind: "allow",
|
|
335
|
-
actions: new Set(normalizeActions(action)),
|
|
336
|
-
where: opts?.where ?? (() => true)
|
|
337
|
-
});
|
|
338
|
-
};
|
|
339
|
-
const deny = (action, opts) => {
|
|
340
|
-
rules.push({
|
|
341
|
-
kind: "deny",
|
|
342
|
-
actions: new Set(normalizeActions(action)),
|
|
343
|
-
where: opts?.where ?? (() => true)
|
|
344
|
-
});
|
|
345
|
-
};
|
|
346
|
-
build(allow, deny);
|
|
347
|
-
return (actor, action) => {
|
|
348
|
-
let denied = false;
|
|
349
|
-
let allowed = false;
|
|
350
|
-
for (const r of rules) {
|
|
351
|
-
if (!matchesActions(r.actions, action)) continue;
|
|
352
|
-
if (!r.where(actor)) continue;
|
|
353
|
-
if (r.kind === "deny") {
|
|
354
|
-
denied = true;
|
|
355
|
-
} else {
|
|
356
|
-
allowed = true;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (denied) return false;
|
|
360
|
-
return allowed;
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function policyFromRules(rules) {
|
|
364
|
-
return policy((allow, deny) => {
|
|
365
|
-
for (const rule of rules) {
|
|
366
|
-
const actorTypes = rule.actorType == null ? null : new Set(Array.isArray(rule.actorType) ? rule.actorType : [rule.actorType]);
|
|
367
|
-
const actorIds = rule.actorId == null ? null : new Set(Array.isArray(rule.actorId) ? rule.actorId : [rule.actorId]);
|
|
368
|
-
const claimEntries = Object.entries(rule.claims ?? {});
|
|
369
|
-
const where = (actor) => {
|
|
370
|
-
if (actorTypes !== null && !actorTypes.has(String(actor.type))) return false;
|
|
371
|
-
if (actorIds !== null && !actorIds.has(String(actor.id ?? ""))) return false;
|
|
372
|
-
for (const [key, value] of claimEntries) {
|
|
373
|
-
if (actor[key] !== value) return false;
|
|
374
|
-
}
|
|
375
|
-
return true;
|
|
376
|
-
};
|
|
377
|
-
if (rule.effect === "deny") {
|
|
378
|
-
deny(rule.action, { where });
|
|
379
|
-
} else {
|
|
380
|
-
allow(rule.action, { where });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
var STANDARD_WRITE_TYPES = ["human", "llm", "wallet", "system"];
|
|
386
|
-
function accessHintForGuard(guard) {
|
|
387
|
-
const allowed = STANDARD_WRITE_TYPES.filter((t) => guard({ type: t, id: "" }, "write"));
|
|
388
|
-
if (allowed.length === 0) return "restricted";
|
|
389
|
-
if (allowed.includes("human") && allowed.includes("llm") && allowed.every((t) => t === "human" || t === "llm" || t === "system")) {
|
|
390
|
-
return "both";
|
|
391
|
-
}
|
|
392
|
-
if (allowed.length === 1) return allowed[0];
|
|
393
|
-
return allowed.join("+");
|
|
394
|
-
}
|
|
395
|
-
|
|
396
404
|
// src/core/versioning.ts
|
|
397
405
|
import { createHash, randomUUID } from "crypto";
|
|
398
406
|
function canonicalizeForHash(value) {
|
|
@@ -448,10 +456,29 @@ function isV1(info) {
|
|
|
448
456
|
return "cid" in info;
|
|
449
457
|
}
|
|
450
458
|
|
|
451
|
-
// src/core/node.ts
|
|
459
|
+
// src/core/node-base.ts
|
|
452
460
|
var NO_VALUE = /* @__PURE__ */ Symbol.for("graphrefly/NO_VALUE");
|
|
453
461
|
var CLEANUP_RESULT = /* @__PURE__ */ Symbol.for("graphrefly/CLEANUP_RESULT");
|
|
454
|
-
function
|
|
462
|
+
function cleanupResult(cleanup, ...args) {
|
|
463
|
+
const r = { [CLEANUP_RESULT]: true, cleanup };
|
|
464
|
+
if (args.length > 0) r.value = args[0];
|
|
465
|
+
return r;
|
|
466
|
+
}
|
|
467
|
+
var isCleanupResult = (value) => typeof value === "object" && value !== null && CLEANUP_RESULT in value;
|
|
468
|
+
var isCleanupFn = (value) => typeof value === "function";
|
|
469
|
+
function statusAfterMessage(status, msg) {
|
|
470
|
+
const t = msg[0];
|
|
471
|
+
if (t === DIRTY) return "dirty";
|
|
472
|
+
if (t === DATA) return "settled";
|
|
473
|
+
if (t === RESOLVED) return "resolved";
|
|
474
|
+
if (t === COMPLETE) return "completed";
|
|
475
|
+
if (t === ERROR) return "errored";
|
|
476
|
+
if (t === INVALIDATE) return "dirty";
|
|
477
|
+
if (t === TEARDOWN) return "disconnected";
|
|
478
|
+
return status;
|
|
479
|
+
}
|
|
480
|
+
function createIntBitSet(size) {
|
|
481
|
+
const fullMask = size >= 32 ? -1 : ~(-1 << size);
|
|
455
482
|
let bits = 0;
|
|
456
483
|
return {
|
|
457
484
|
set(i) {
|
|
@@ -464,7 +491,8 @@ function createIntBitSet() {
|
|
|
464
491
|
return (bits & 1 << i) !== 0;
|
|
465
492
|
},
|
|
466
493
|
covers(other) {
|
|
467
|
-
|
|
494
|
+
const otherBits = other._bits();
|
|
495
|
+
return (bits & otherBits) === otherBits;
|
|
468
496
|
},
|
|
469
497
|
any() {
|
|
470
498
|
return bits !== 0;
|
|
@@ -472,6 +500,9 @@ function createIntBitSet() {
|
|
|
472
500
|
reset() {
|
|
473
501
|
bits = 0;
|
|
474
502
|
},
|
|
503
|
+
setAll() {
|
|
504
|
+
bits = fullMask;
|
|
505
|
+
},
|
|
475
506
|
_bits() {
|
|
476
507
|
return bits;
|
|
477
508
|
}
|
|
@@ -479,6 +510,8 @@ function createIntBitSet() {
|
|
|
479
510
|
}
|
|
480
511
|
function createArrayBitSet(size) {
|
|
481
512
|
const words = new Uint32Array(Math.ceil(size / 32));
|
|
513
|
+
const lastBits = size % 32;
|
|
514
|
+
const lastWordMask = lastBits === 0 ? 4294967295 : (1 << lastBits) - 1 >>> 0;
|
|
482
515
|
return {
|
|
483
516
|
set(i) {
|
|
484
517
|
words[i >>> 5] |= 1 << (i & 31);
|
|
@@ -505,135 +538,103 @@ function createArrayBitSet(size) {
|
|
|
505
538
|
reset() {
|
|
506
539
|
words.fill(0);
|
|
507
540
|
},
|
|
541
|
+
setAll() {
|
|
542
|
+
for (let w = 0; w < words.length - 1; w++) words[w] = 4294967295;
|
|
543
|
+
if (words.length > 0) words[words.length - 1] = lastWordMask;
|
|
544
|
+
},
|
|
508
545
|
_words: words
|
|
509
546
|
};
|
|
510
547
|
}
|
|
511
548
|
function createBitSet(size) {
|
|
512
|
-
return size <= 31 ? createIntBitSet() : createArrayBitSet(size);
|
|
513
|
-
}
|
|
514
|
-
var isNodeArray = (value) => Array.isArray(value);
|
|
515
|
-
var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
|
|
516
|
-
function cleanupResult(cleanup, ...args) {
|
|
517
|
-
const r = { [CLEANUP_RESULT]: true, cleanup };
|
|
518
|
-
if (args.length > 0) r.value = args[0];
|
|
519
|
-
return r;
|
|
549
|
+
return size <= 31 ? createIntBitSet(size) : createArrayBitSet(size);
|
|
520
550
|
}
|
|
521
|
-
var
|
|
522
|
-
|
|
523
|
-
var statusAfterMessage = (status, msg) => {
|
|
524
|
-
const t = msg[0];
|
|
525
|
-
if (t === DIRTY) return "dirty";
|
|
526
|
-
if (t === DATA) return "settled";
|
|
527
|
-
if (t === RESOLVED) return "resolved";
|
|
528
|
-
if (t === COMPLETE) return "completed";
|
|
529
|
-
if (t === ERROR) return "errored";
|
|
530
|
-
if (t === INVALIDATE) return "dirty";
|
|
531
|
-
if (t === TEARDOWN) return "disconnected";
|
|
532
|
-
return status;
|
|
533
|
-
};
|
|
534
|
-
var NodeImpl = class {
|
|
535
|
-
// --- Configuration (set once, never reassigned) ---
|
|
551
|
+
var NodeBase = class {
|
|
552
|
+
// --- Identity (set once) ---
|
|
536
553
|
_optsName;
|
|
537
554
|
_registryName;
|
|
538
|
-
/** @internal
|
|
555
|
+
/** @internal Read by `describeNode` before inference. */
|
|
539
556
|
_describeKind;
|
|
540
557
|
meta;
|
|
541
|
-
|
|
542
|
-
_fn;
|
|
543
|
-
_opts;
|
|
558
|
+
// --- Options ---
|
|
544
559
|
_equals;
|
|
560
|
+
_resubscribable;
|
|
561
|
+
_resetOnTeardown;
|
|
562
|
+
_onResubscribe;
|
|
545
563
|
_onMessage;
|
|
546
|
-
/** @internal
|
|
564
|
+
/** @internal Read by `describeNode` for `accessHintForGuard`. */
|
|
547
565
|
_guard;
|
|
566
|
+
/** @internal Subclasses update this through {@link _recordMutation}. */
|
|
548
567
|
_lastMutation;
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
// ---
|
|
568
|
+
// --- Versioning ---
|
|
569
|
+
_hashFn;
|
|
570
|
+
_versioning;
|
|
571
|
+
// --- Lifecycle state ---
|
|
572
|
+
/** @internal Read by `describeNode` and `graph.ts`. */
|
|
553
573
|
_cached;
|
|
574
|
+
/** @internal Read externally via `get status()`. */
|
|
554
575
|
_status;
|
|
555
576
|
_terminal = false;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
_manualEmitUsed = false;
|
|
577
|
+
_active = false;
|
|
578
|
+
// --- Sink storage ---
|
|
579
|
+
/** @internal Read by `graph/profile.ts` for subscriber counts. */
|
|
560
580
|
_sinkCount = 0;
|
|
561
581
|
_singleDepSinkCount = 0;
|
|
562
|
-
// --- Object/collection state ---
|
|
563
|
-
_depDirtyMask;
|
|
564
|
-
_depSettledMask;
|
|
565
|
-
_depCompleteMask;
|
|
566
|
-
_allDepsCompleteMask;
|
|
567
|
-
_lastDepValues;
|
|
568
|
-
_cleanup;
|
|
569
|
-
_sinks = null;
|
|
570
582
|
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
571
|
-
|
|
583
|
+
_sinks = null;
|
|
584
|
+
// --- Actions + bound helpers ---
|
|
572
585
|
_actions;
|
|
573
586
|
_boundDownToSinks;
|
|
587
|
+
// --- Inspector hook (Graph observability) ---
|
|
574
588
|
_inspectorHook;
|
|
575
|
-
|
|
576
|
-
_hashFn;
|
|
577
|
-
constructor(deps, fn, opts) {
|
|
578
|
-
this._deps = deps;
|
|
579
|
-
this._fn = fn;
|
|
580
|
-
this._opts = opts;
|
|
589
|
+
constructor(opts) {
|
|
581
590
|
this._optsName = opts.name;
|
|
582
591
|
this._describeKind = opts.describeKind;
|
|
583
592
|
this._equals = opts.equals ?? Object.is;
|
|
593
|
+
this._resubscribable = opts.resubscribable ?? false;
|
|
594
|
+
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
595
|
+
this._onResubscribe = opts.onResubscribe;
|
|
584
596
|
this._onMessage = opts.onMessage;
|
|
585
597
|
this._guard = opts.guard;
|
|
586
|
-
this._hasDeps = deps.length > 0;
|
|
587
|
-
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
588
|
-
this._isSingleDep = deps.length === 1 && fn != null;
|
|
589
598
|
this._cached = "initial" in opts ? opts.initial : NO_VALUE;
|
|
590
|
-
this._status =
|
|
599
|
+
this._status = "disconnected";
|
|
591
600
|
this._hashFn = opts.versioningHash ?? defaultHash;
|
|
592
601
|
this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached === NO_VALUE ? void 0 : this._cached, {
|
|
593
602
|
id: opts.versioningId,
|
|
594
603
|
hash: this._hashFn
|
|
595
604
|
}) : void 0;
|
|
596
|
-
this._depDirtyMask = createBitSet(deps.length);
|
|
597
|
-
this._depSettledMask = createBitSet(deps.length);
|
|
598
|
-
this._depCompleteMask = createBitSet(deps.length);
|
|
599
|
-
this._allDepsCompleteMask = createBitSet(deps.length);
|
|
600
|
-
for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
|
|
601
605
|
const meta = {};
|
|
602
606
|
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
603
|
-
meta[k] =
|
|
604
|
-
initial: v,
|
|
605
|
-
name: `${opts.name ?? "node"}:meta:${k}`,
|
|
606
|
-
describeKind: "state",
|
|
607
|
-
...opts.guard != null ? { guard: opts.guard } : {}
|
|
608
|
-
});
|
|
607
|
+
meta[k] = this._createMetaNode(k, v, opts);
|
|
609
608
|
}
|
|
610
609
|
Object.freeze(meta);
|
|
611
610
|
this.meta = meta;
|
|
612
611
|
const self = this;
|
|
613
612
|
this._actions = {
|
|
614
613
|
down(messages) {
|
|
615
|
-
self.
|
|
614
|
+
self._onManualEmit();
|
|
616
615
|
self._downInternal(messages);
|
|
617
616
|
},
|
|
618
617
|
emit(value) {
|
|
619
|
-
self.
|
|
618
|
+
self._onManualEmit();
|
|
620
619
|
self._downAutoValue(value);
|
|
621
620
|
},
|
|
622
621
|
up(messages) {
|
|
623
622
|
self._upInternal(messages);
|
|
624
623
|
}
|
|
625
624
|
};
|
|
626
|
-
this.down = this.down.bind(this);
|
|
627
|
-
this.up = this.up.bind(this);
|
|
628
625
|
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
629
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* Subclass hook invoked by `actions.down` / `actions.emit`. Default no-op;
|
|
629
|
+
* {@link NodeImpl} overrides to set `_manualEmitUsed`.
|
|
630
|
+
*/
|
|
631
|
+
_onManualEmit() {
|
|
632
|
+
}
|
|
633
|
+
// --- Identity getters ---
|
|
630
634
|
get name() {
|
|
631
635
|
return this._registryName ?? this._optsName;
|
|
632
636
|
}
|
|
633
|
-
/**
|
|
634
|
-
* When a node is registered with {@link Graph.add} without an options `name`,
|
|
635
|
-
* the graph assigns the registry local name for introspection (parity with graphrefly-py).
|
|
636
|
-
*/
|
|
637
|
+
/** @internal Assigned by `Graph.add` when registered without an options `name`. */
|
|
637
638
|
_assignRegistryName(localName) {
|
|
638
639
|
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
639
640
|
this._registryName = localName;
|
|
@@ -651,7 +652,10 @@ var NodeImpl = class {
|
|
|
651
652
|
}
|
|
652
653
|
};
|
|
653
654
|
}
|
|
654
|
-
|
|
655
|
+
/** @internal Used by subclasses to surface inspector events. */
|
|
656
|
+
_emitInspectorHook(event) {
|
|
657
|
+
this._inspectorHook?.(event);
|
|
658
|
+
}
|
|
655
659
|
get status() {
|
|
656
660
|
return this._status;
|
|
657
661
|
}
|
|
@@ -661,15 +665,7 @@ var NodeImpl = class {
|
|
|
661
665
|
get v() {
|
|
662
666
|
return this._versioning;
|
|
663
667
|
}
|
|
664
|
-
/**
|
|
665
|
-
* Retroactively apply versioning to a node that was created without it.
|
|
666
|
-
* No-op if versioning is already enabled.
|
|
667
|
-
*
|
|
668
|
-
* Version starts at 0 regardless of prior DATA emissions — it tracks
|
|
669
|
-
* changes from the moment versioning is enabled, not historical ones.
|
|
670
|
-
*
|
|
671
|
-
* @internal — used by {@link Graph.setVersioning}.
|
|
672
|
-
*/
|
|
668
|
+
/** @internal Used by `Graph.setVersioning` to retroactively apply versioning. */
|
|
673
669
|
_applyVersioning(level, opts) {
|
|
674
670
|
if (this._versioning != null) return;
|
|
675
671
|
this._hashFn = opts?.hash ?? this._hashFn;
|
|
@@ -689,6 +685,7 @@ var NodeImpl = class {
|
|
|
689
685
|
if (this._guard == null) return true;
|
|
690
686
|
return this._guard(normalizeActor(actor), "observe");
|
|
691
687
|
}
|
|
688
|
+
// --- Public transport ---
|
|
692
689
|
get() {
|
|
693
690
|
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
694
691
|
}
|
|
@@ -701,43 +698,25 @@ var NodeImpl = class {
|
|
|
701
698
|
if (!this._guard(actor, action)) {
|
|
702
699
|
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
703
700
|
}
|
|
704
|
-
this.
|
|
701
|
+
this._recordMutation(actor);
|
|
705
702
|
}
|
|
706
703
|
this._downInternal(messages);
|
|
707
704
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
this.
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
722
|
-
const t = sinkMessages[i][0];
|
|
723
|
-
if (t === DATA || t === RESOLVED) {
|
|
724
|
-
hasPhase2 = true;
|
|
725
|
-
break;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (hasPhase2) {
|
|
729
|
-
const filtered = [];
|
|
730
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
731
|
-
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
732
|
-
}
|
|
733
|
-
if (filtered.length > 0) {
|
|
734
|
-
downWithBatch(this._boundDownToSinks, filtered);
|
|
735
|
-
}
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
705
|
+
/** @internal Record a successful guarded mutation (called by `down` and subclass `up`). */
|
|
706
|
+
_recordMutation(actor) {
|
|
707
|
+
this._lastMutation = { actor, timestamp_ns: wallClockNs() };
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* At-most-once deactivation guard. Both TEARDOWN (eager) and
|
|
711
|
+
* unsubscribe-body (lazy) call this. The `_active` flag ensures
|
|
712
|
+
* `_doDeactivate` runs exactly once per activation cycle.
|
|
713
|
+
*/
|
|
714
|
+
_onDeactivate() {
|
|
715
|
+
if (!this._active) return;
|
|
716
|
+
this._active = false;
|
|
717
|
+
this._doDeactivate();
|
|
740
718
|
}
|
|
719
|
+
// --- Subscribe (uniform across node shapes) ---
|
|
741
720
|
subscribe(sink, hints) {
|
|
742
721
|
if (hints?.actor != null && this._guard != null) {
|
|
743
722
|
const actor = normalizeActor(hints.actor);
|
|
@@ -745,17 +724,21 @@ var NodeImpl = class {
|
|
|
745
724
|
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
746
725
|
}
|
|
747
726
|
}
|
|
748
|
-
if (this._terminal && this.
|
|
727
|
+
if (this._terminal && this._resubscribable) {
|
|
749
728
|
this._terminal = false;
|
|
750
729
|
this._cached = NO_VALUE;
|
|
751
|
-
this._status =
|
|
752
|
-
this.
|
|
730
|
+
this._status = "disconnected";
|
|
731
|
+
this._onResubscribe?.();
|
|
753
732
|
}
|
|
754
733
|
this._sinkCount += 1;
|
|
755
734
|
if (hints?.singleDep) {
|
|
756
735
|
this._singleDepSinkCount += 1;
|
|
757
736
|
this._singleDepSinks.add(sink);
|
|
758
737
|
}
|
|
738
|
+
if (!this._terminal) {
|
|
739
|
+
const startMessages = this._cached === NO_VALUE ? [[START]] : [[START], [DATA, this._cached]];
|
|
740
|
+
downWithBatch(sink, startMessages);
|
|
741
|
+
}
|
|
759
742
|
if (this._sinks == null) {
|
|
760
743
|
this._sinks = sink;
|
|
761
744
|
} else if (typeof this._sinks === "function") {
|
|
@@ -763,10 +746,12 @@ var NodeImpl = class {
|
|
|
763
746
|
} else {
|
|
764
747
|
this._sinks.add(sink);
|
|
765
748
|
}
|
|
766
|
-
if (this.
|
|
767
|
-
this.
|
|
768
|
-
|
|
769
|
-
|
|
749
|
+
if (this._sinkCount === 1 && !this._terminal) {
|
|
750
|
+
this._active = true;
|
|
751
|
+
this._onActivate();
|
|
752
|
+
}
|
|
753
|
+
if (!this._terminal && this._status === "disconnected" && this._cached === NO_VALUE) {
|
|
754
|
+
this._status = "pending";
|
|
770
755
|
}
|
|
771
756
|
let removed = false;
|
|
772
757
|
return () => {
|
|
@@ -790,39 +775,49 @@ var NodeImpl = class {
|
|
|
790
775
|
}
|
|
791
776
|
}
|
|
792
777
|
if (this._sinks == null) {
|
|
793
|
-
this.
|
|
794
|
-
this._stopProducer();
|
|
778
|
+
this._onDeactivate();
|
|
795
779
|
}
|
|
796
780
|
};
|
|
797
781
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
782
|
+
// --- Down pipeline ---
|
|
783
|
+
/**
|
|
784
|
+
* Core outgoing dispatch. Applies terminal filter + local lifecycle
|
|
785
|
+
* update, then hands messages to `downWithBatch` for tier-aware delivery.
|
|
786
|
+
*/
|
|
787
|
+
_downInternal(messages) {
|
|
788
|
+
if (messages.length === 0) return;
|
|
789
|
+
let sinkMessages = messages;
|
|
790
|
+
if (this._terminal && !this._resubscribable) {
|
|
791
|
+
const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
|
|
792
|
+
if (pass.length === 0) return;
|
|
793
|
+
sinkMessages = pass;
|
|
806
794
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
795
|
+
this._handleLocalLifecycle(sinkMessages);
|
|
796
|
+
if (this._canSkipDirty()) {
|
|
797
|
+
let hasPhase2 = false;
|
|
798
|
+
for (let i = 0; i < sinkMessages.length; i++) {
|
|
799
|
+
const t = sinkMessages[i][0];
|
|
800
|
+
if (t === DATA || t === RESOLVED) {
|
|
801
|
+
hasPhase2 = true;
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (hasPhase2) {
|
|
806
|
+
const filtered = [];
|
|
807
|
+
for (let i = 0; i < sinkMessages.length; i++) {
|
|
808
|
+
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
809
|
+
}
|
|
810
|
+
if (filtered.length > 0) {
|
|
811
|
+
downWithBatch(this._boundDownToSinks, filtered);
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
812
814
|
}
|
|
813
815
|
}
|
|
816
|
+
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
814
817
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
for (const dep of this._deps) {
|
|
818
|
-
dep.up?.(messages, { internal: true });
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
unsubscribe() {
|
|
822
|
-
if (!this._hasDeps) return;
|
|
823
|
-
this._disconnectUpstream();
|
|
818
|
+
_canSkipDirty() {
|
|
819
|
+
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
824
820
|
}
|
|
825
|
-
// --- Private methods (prototype, _ prefix) ---
|
|
826
821
|
_downToSinks(messages) {
|
|
827
822
|
if (this._sinks == null) return;
|
|
828
823
|
if (typeof this._sinks === "function") {
|
|
@@ -834,6 +829,11 @@ var NodeImpl = class {
|
|
|
834
829
|
sink(messages);
|
|
835
830
|
}
|
|
836
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Update `_cached`, `_status`, `_terminal` from message batch before
|
|
834
|
+
* delivery. Subclass hooks `_onInvalidate` / `_onTeardown` let
|
|
835
|
+
* {@link NodeImpl} clear `_lastDepValues` and invoke cleanup fns.
|
|
836
|
+
*/
|
|
837
837
|
_handleLocalLifecycle(messages) {
|
|
838
838
|
for (const m of messages) {
|
|
839
839
|
const t = m[0];
|
|
@@ -847,28 +847,22 @@ var NodeImpl = class {
|
|
|
847
847
|
}
|
|
848
848
|
}
|
|
849
849
|
if (t === INVALIDATE) {
|
|
850
|
-
|
|
851
|
-
this._cleanup = void 0;
|
|
852
|
-
cleanupFn?.();
|
|
850
|
+
this._onInvalidate();
|
|
853
851
|
this._cached = NO_VALUE;
|
|
854
|
-
this._lastDepValues = void 0;
|
|
855
852
|
}
|
|
856
853
|
this._status = statusAfterMessage(this._status, m);
|
|
857
854
|
if (t === COMPLETE || t === ERROR) {
|
|
858
855
|
this._terminal = true;
|
|
859
856
|
}
|
|
860
857
|
if (t === TEARDOWN) {
|
|
861
|
-
if (this.
|
|
858
|
+
if (this._resetOnTeardown) {
|
|
862
859
|
this._cached = NO_VALUE;
|
|
863
860
|
}
|
|
864
|
-
|
|
865
|
-
this._cleanup = void 0;
|
|
866
|
-
teardownCleanup?.();
|
|
861
|
+
this._onTeardown();
|
|
867
862
|
try {
|
|
868
863
|
this._propagateToMeta(t);
|
|
869
864
|
} finally {
|
|
870
|
-
this.
|
|
871
|
-
this._stopProducer();
|
|
865
|
+
this._onDeactivate();
|
|
872
866
|
}
|
|
873
867
|
}
|
|
874
868
|
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
@@ -876,7 +870,20 @@ var NodeImpl = class {
|
|
|
876
870
|
}
|
|
877
871
|
}
|
|
878
872
|
}
|
|
879
|
-
/**
|
|
873
|
+
/**
|
|
874
|
+
* Subclass hook: invoked when INVALIDATE arrives (before `_cached` is
|
|
875
|
+
* cleared). {@link NodeImpl} uses this to run the fn cleanup fn and
|
|
876
|
+
* drop `_lastDepValues` so the next wave re-runs fn.
|
|
877
|
+
*/
|
|
878
|
+
_onInvalidate() {
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Subclass hook: invoked when TEARDOWN arrives, before `_onDeactivate`.
|
|
882
|
+
* {@link NodeImpl} uses this to run any pending cleanup fn.
|
|
883
|
+
*/
|
|
884
|
+
_onTeardown() {
|
|
885
|
+
}
|
|
886
|
+
/** Forward a signal to all companion meta nodes (best-effort). */
|
|
880
887
|
_propagateToMeta(t) {
|
|
881
888
|
for (const metaNode of Object.values(this.meta)) {
|
|
882
889
|
try {
|
|
@@ -885,9 +892,10 @@ var NodeImpl = class {
|
|
|
885
892
|
}
|
|
886
893
|
}
|
|
887
894
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
895
|
+
/**
|
|
896
|
+
* Frame a computed value into the right protocol messages and dispatch
|
|
897
|
+
* via `_downInternal`. Used by `_runFn` and `actions.emit`.
|
|
898
|
+
*/
|
|
891
899
|
_downAutoValue(value) {
|
|
892
900
|
const wasDirty = this._status === "dirty";
|
|
893
901
|
let unchanged;
|
|
@@ -895,7 +903,9 @@ var NodeImpl = class {
|
|
|
895
903
|
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
896
904
|
} catch (eqErr) {
|
|
897
905
|
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
898
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
906
|
+
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
907
|
+
cause: eqErr
|
|
908
|
+
});
|
|
899
909
|
this._downInternal([[ERROR, wrapped]]);
|
|
900
910
|
return;
|
|
901
911
|
}
|
|
@@ -905,89 +915,173 @@ var NodeImpl = class {
|
|
|
905
915
|
}
|
|
906
916
|
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
907
917
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/core/node.ts
|
|
921
|
+
var NodeImpl = class extends NodeBase {
|
|
922
|
+
// --- Dep configuration (set once) ---
|
|
923
|
+
_deps;
|
|
924
|
+
_fn;
|
|
925
|
+
_opts;
|
|
926
|
+
_hasDeps;
|
|
927
|
+
_isSingleDep;
|
|
928
|
+
_autoComplete;
|
|
929
|
+
// --- Wave tracking masks ---
|
|
930
|
+
_depDirtyMask;
|
|
931
|
+
_depSettledMask;
|
|
932
|
+
_depCompleteMask;
|
|
933
|
+
_allDepsCompleteMask;
|
|
934
|
+
// --- Identity-skip optimization ---
|
|
935
|
+
_lastDepValues;
|
|
936
|
+
_cleanup;
|
|
937
|
+
// --- Upstream bookkeeping ---
|
|
938
|
+
_upstreamUnsubs = [];
|
|
939
|
+
// --- Fn behavior flag ---
|
|
940
|
+
/** @internal Read by `describeNode` to infer `"operator"` label. */
|
|
941
|
+
_manualEmitUsed = false;
|
|
942
|
+
constructor(deps, fn, opts) {
|
|
943
|
+
super(opts);
|
|
944
|
+
this._deps = deps;
|
|
945
|
+
this._fn = fn;
|
|
946
|
+
this._opts = opts;
|
|
947
|
+
this._hasDeps = deps.length > 0;
|
|
948
|
+
this._isSingleDep = deps.length === 1 && fn != null;
|
|
949
|
+
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
950
|
+
if (!this._hasDeps && fn == null && this._cached !== NO_VALUE) {
|
|
951
|
+
this._status = "settled";
|
|
952
|
+
}
|
|
953
|
+
this._depDirtyMask = createBitSet(deps.length);
|
|
954
|
+
this._depSettledMask = createBitSet(deps.length);
|
|
955
|
+
this._depCompleteMask = createBitSet(deps.length);
|
|
956
|
+
this._allDepsCompleteMask = createBitSet(deps.length);
|
|
957
|
+
for (let i = 0; i < deps.length; i++) this._allDepsCompleteMask.set(i);
|
|
958
|
+
this.down = this.down.bind(this);
|
|
959
|
+
this.up = this.up.bind(this);
|
|
960
|
+
}
|
|
961
|
+
// --- Meta node factory (called from base constructor) ---
|
|
962
|
+
_createMetaNode(key, initialValue, opts) {
|
|
963
|
+
return node({
|
|
964
|
+
initial: initialValue,
|
|
965
|
+
name: `${opts.name ?? "node"}:meta:${key}`,
|
|
966
|
+
describeKind: "state",
|
|
967
|
+
...opts.guard != null ? { guard: opts.guard } : {}
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
// --- Manual emit tracker (set by actions.down / actions.emit) ---
|
|
971
|
+
_onManualEmit() {
|
|
972
|
+
this._manualEmitUsed = true;
|
|
973
|
+
}
|
|
974
|
+
// --- Up / unsubscribe ---
|
|
975
|
+
up(messages, options) {
|
|
976
|
+
if (!this._hasDeps) return;
|
|
977
|
+
if (!options?.internal && this._guard != null) {
|
|
978
|
+
const actor = normalizeActor(options?.actor);
|
|
979
|
+
if (!this._guard(actor, "write")) {
|
|
980
|
+
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
946
981
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
982
|
+
this._recordMutation(actor);
|
|
983
|
+
}
|
|
984
|
+
for (const dep of this._deps) {
|
|
985
|
+
if (options === void 0) {
|
|
986
|
+
dep.up?.(messages);
|
|
987
|
+
} else {
|
|
988
|
+
dep.up?.(messages, options);
|
|
950
989
|
}
|
|
951
|
-
if (this._manualEmitUsed) return;
|
|
952
|
-
if (out === void 0) return;
|
|
953
|
-
this._downAutoValue(out);
|
|
954
|
-
} catch (err) {
|
|
955
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
956
|
-
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
|
|
957
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
958
990
|
}
|
|
959
991
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
this.
|
|
963
|
-
|
|
964
|
-
if (!wasDirty) {
|
|
965
|
-
this._downInternal([[DIRTY]]);
|
|
992
|
+
_upInternal(messages) {
|
|
993
|
+
if (!this._hasDeps) return;
|
|
994
|
+
for (const dep of this._deps) {
|
|
995
|
+
dep.up?.(messages, { internal: true });
|
|
966
996
|
}
|
|
967
997
|
}
|
|
968
|
-
|
|
969
|
-
if (!this.
|
|
970
|
-
|
|
998
|
+
unsubscribe() {
|
|
999
|
+
if (!this._hasDeps) return;
|
|
1000
|
+
this._disconnectUpstream();
|
|
1001
|
+
}
|
|
1002
|
+
// --- Activation (first-subscriber / last-subscriber hooks) ---
|
|
1003
|
+
_onActivate() {
|
|
1004
|
+
if (this._hasDeps) {
|
|
1005
|
+
this._connectUpstream();
|
|
1006
|
+
return;
|
|
971
1007
|
}
|
|
972
|
-
this.
|
|
973
|
-
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
974
|
-
this._depDirtyMask.reset();
|
|
975
|
-
this._depSettledMask.reset();
|
|
1008
|
+
if (this._fn) {
|
|
976
1009
|
this._runFn();
|
|
1010
|
+
return;
|
|
977
1011
|
}
|
|
978
1012
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1013
|
+
_doDeactivate() {
|
|
1014
|
+
this._disconnectUpstream();
|
|
1015
|
+
const cleanup = this._cleanup;
|
|
1016
|
+
this._cleanup = void 0;
|
|
1017
|
+
cleanup?.();
|
|
1018
|
+
if (this._fn != null) {
|
|
1019
|
+
this._cached = NO_VALUE;
|
|
1020
|
+
this._lastDepValues = void 0;
|
|
982
1021
|
}
|
|
1022
|
+
if (this._hasDeps || this._fn != null) {
|
|
1023
|
+
this._status = "disconnected";
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
// --- INVALIDATE / TEARDOWN hooks (clear fn state) ---
|
|
1027
|
+
_onInvalidate() {
|
|
1028
|
+
const cleanup = this._cleanup;
|
|
1029
|
+
this._cleanup = void 0;
|
|
1030
|
+
cleanup?.();
|
|
1031
|
+
this._lastDepValues = void 0;
|
|
983
1032
|
}
|
|
1033
|
+
_onTeardown() {
|
|
1034
|
+
const cleanup = this._cleanup;
|
|
1035
|
+
this._cleanup = void 0;
|
|
1036
|
+
cleanup?.();
|
|
1037
|
+
}
|
|
1038
|
+
// --- Upstream connect / disconnect ---
|
|
1039
|
+
_connectUpstream() {
|
|
1040
|
+
if (!this._hasDeps) return;
|
|
1041
|
+
if (this._upstreamUnsubs.length > 0) return;
|
|
1042
|
+
this._depDirtyMask.setAll();
|
|
1043
|
+
this._depSettledMask.reset();
|
|
1044
|
+
this._depCompleteMask.reset();
|
|
1045
|
+
const depValuesBefore = this._lastDepValues;
|
|
1046
|
+
const subHints = this._isSingleDep ? { singleDep: true } : void 0;
|
|
1047
|
+
for (let i = 0; i < this._deps.length; i += 1) {
|
|
1048
|
+
const dep = this._deps[i];
|
|
1049
|
+
this._upstreamUnsubs.push(
|
|
1050
|
+
dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
if (this._fn && this._onMessage && !this._terminal && this._lastDepValues === depValuesBefore) {
|
|
1054
|
+
this._runFn();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
_disconnectUpstream() {
|
|
1058
|
+
if (this._upstreamUnsubs.length === 0) return;
|
|
1059
|
+
for (const unsub of this._upstreamUnsubs.splice(0)) {
|
|
1060
|
+
unsub();
|
|
1061
|
+
}
|
|
1062
|
+
this._depDirtyMask.reset();
|
|
1063
|
+
this._depSettledMask.reset();
|
|
1064
|
+
this._depCompleteMask.reset();
|
|
1065
|
+
}
|
|
1066
|
+
// --- Wave handling ---
|
|
984
1067
|
_handleDepMessages(index, messages) {
|
|
985
1068
|
for (const msg of messages) {
|
|
986
|
-
this.
|
|
1069
|
+
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
987
1070
|
const t = msg[0];
|
|
988
1071
|
if (this._onMessage) {
|
|
989
1072
|
try {
|
|
990
|
-
|
|
1073
|
+
const consumed = this._onMessage(msg, index, this._actions);
|
|
1074
|
+
if (consumed) {
|
|
1075
|
+
if (t === START) {
|
|
1076
|
+
this._depDirtyMask.clear(index);
|
|
1077
|
+
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1078
|
+
this._depDirtyMask.reset();
|
|
1079
|
+
this._depSettledMask.reset();
|
|
1080
|
+
this._runFn();
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
991
1085
|
} catch (err) {
|
|
992
1086
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
993
1087
|
const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
|
|
@@ -997,6 +1091,7 @@ var NodeImpl = class {
|
|
|
997
1091
|
return;
|
|
998
1092
|
}
|
|
999
1093
|
}
|
|
1094
|
+
if (messageTier(t) < 1) continue;
|
|
1000
1095
|
if (!this._fn) {
|
|
1001
1096
|
if (t === COMPLETE && this._deps.length > 1) {
|
|
1002
1097
|
this._depCompleteMask.set(index);
|
|
@@ -1040,53 +1135,85 @@ var NodeImpl = class {
|
|
|
1040
1135
|
this._downInternal([msg]);
|
|
1041
1136
|
}
|
|
1042
1137
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
this.
|
|
1046
|
-
this.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
this._status = "settled";
|
|
1050
|
-
const subHints = this._isSingleDep ? { singleDep: true } : void 0;
|
|
1051
|
-
this._connecting = true;
|
|
1052
|
-
try {
|
|
1053
|
-
for (let i = 0; i < this._deps.length; i += 1) {
|
|
1054
|
-
const dep = this._deps[i];
|
|
1055
|
-
this._upstreamUnsubs.push(
|
|
1056
|
-
dep.subscribe((msgs) => this._handleDepMessages(i, msgs), subHints)
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
} finally {
|
|
1060
|
-
this._connecting = false;
|
|
1138
|
+
_onDepDirty(index) {
|
|
1139
|
+
const wasDirty = this._depDirtyMask.has(index);
|
|
1140
|
+
this._depDirtyMask.set(index);
|
|
1141
|
+
this._depSettledMask.clear(index);
|
|
1142
|
+
if (!wasDirty) {
|
|
1143
|
+
this._downInternal([[DIRTY]]);
|
|
1061
1144
|
}
|
|
1062
|
-
|
|
1145
|
+
}
|
|
1146
|
+
_onDepSettled(index) {
|
|
1147
|
+
if (!this._depDirtyMask.has(index)) {
|
|
1148
|
+
this._onDepDirty(index);
|
|
1149
|
+
}
|
|
1150
|
+
this._depSettledMask.set(index);
|
|
1151
|
+
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1152
|
+
this._depDirtyMask.reset();
|
|
1153
|
+
this._depSettledMask.reset();
|
|
1063
1154
|
this._runFn();
|
|
1064
1155
|
}
|
|
1065
1156
|
}
|
|
1066
|
-
|
|
1067
|
-
if (
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
this._cleanup = void 0;
|
|
1071
|
-
producerCleanup?.();
|
|
1072
|
-
}
|
|
1073
|
-
_startProducer() {
|
|
1074
|
-
if (this._deps.length !== 0 || !this._fn || this._producerStarted) return;
|
|
1075
|
-
this._producerStarted = true;
|
|
1076
|
-
this._runFn();
|
|
1157
|
+
_maybeCompleteFromDeps() {
|
|
1158
|
+
if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
|
|
1159
|
+
this._downInternal([[COMPLETE]]);
|
|
1160
|
+
}
|
|
1077
1161
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1162
|
+
// --- Fn execution ---
|
|
1163
|
+
_runFn() {
|
|
1164
|
+
if (!this._fn) return;
|
|
1165
|
+
if (this._terminal && !this._resubscribable) return;
|
|
1166
|
+
try {
|
|
1167
|
+
const n = this._deps.length;
|
|
1168
|
+
const depValues = new Array(n);
|
|
1169
|
+
for (let i = 0; i < n; i++) depValues[i] = this._deps[i].get();
|
|
1170
|
+
const prev = this._lastDepValues;
|
|
1171
|
+
if (n > 0 && prev != null && prev.length === n) {
|
|
1172
|
+
let allSame = true;
|
|
1173
|
+
for (let i = 0; i < n; i++) {
|
|
1174
|
+
if (!Object.is(depValues[i], prev[i])) {
|
|
1175
|
+
allSame = false;
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (allSame) {
|
|
1180
|
+
if (this._status === "dirty") {
|
|
1181
|
+
this._downInternal([[RESOLVED]]);
|
|
1182
|
+
}
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
const prevCleanup = this._cleanup;
|
|
1187
|
+
this._cleanup = void 0;
|
|
1188
|
+
prevCleanup?.();
|
|
1189
|
+
this._manualEmitUsed = false;
|
|
1190
|
+
this._lastDepValues = depValues;
|
|
1191
|
+
this._emitInspectorHook({ kind: "run", depValues });
|
|
1192
|
+
const out = this._fn(depValues, this._actions);
|
|
1193
|
+
if (isCleanupResult(out)) {
|
|
1194
|
+
this._cleanup = out.cleanup;
|
|
1195
|
+
if (this._manualEmitUsed) return;
|
|
1196
|
+
if ("value" in out) {
|
|
1197
|
+
this._downAutoValue(out.value);
|
|
1198
|
+
}
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (isCleanupFn(out)) {
|
|
1202
|
+
this._cleanup = out;
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (this._manualEmitUsed) return;
|
|
1206
|
+
if (out === void 0) return;
|
|
1207
|
+
this._downAutoValue(out);
|
|
1208
|
+
} catch (err) {
|
|
1209
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1210
|
+
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
|
|
1211
|
+
this._downInternal([[ERROR, wrapped]]);
|
|
1082
1212
|
}
|
|
1083
|
-
this._connected = false;
|
|
1084
|
-
this._depDirtyMask.reset();
|
|
1085
|
-
this._depSettledMask.reset();
|
|
1086
|
-
this._depCompleteMask.reset();
|
|
1087
|
-
this._status = "disconnected";
|
|
1088
1213
|
}
|
|
1089
1214
|
};
|
|
1215
|
+
var isNodeArray = (value) => Array.isArray(value);
|
|
1216
|
+
var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
|
|
1090
1217
|
function node(depsOrFn, fnOrOpts, optsArg) {
|
|
1091
1218
|
const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
|
|
1092
1219
|
const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
|
|
@@ -1102,230 +1229,47 @@ function node(depsOrFn, fnOrOpts, optsArg) {
|
|
|
1102
1229
|
}
|
|
1103
1230
|
|
|
1104
1231
|
// src/core/dynamic-node.ts
|
|
1232
|
+
var MAX_RERUN = 16;
|
|
1105
1233
|
function dynamicNode(fn, opts) {
|
|
1106
1234
|
return new DynamicNodeImpl(fn, opts ?? {});
|
|
1107
1235
|
}
|
|
1108
|
-
var DynamicNodeImpl = class {
|
|
1109
|
-
_optsName;
|
|
1110
|
-
_registryName;
|
|
1111
|
-
_describeKind;
|
|
1112
|
-
meta;
|
|
1236
|
+
var DynamicNodeImpl = class extends NodeBase {
|
|
1113
1237
|
_fn;
|
|
1114
|
-
_equals;
|
|
1115
|
-
_resubscribable;
|
|
1116
|
-
_resetOnTeardown;
|
|
1117
1238
|
_autoComplete;
|
|
1118
|
-
_onMessage;
|
|
1119
|
-
_onResubscribe;
|
|
1120
|
-
/** @internal — read by {@link describeNode} for `accessHintForGuard`. */
|
|
1121
|
-
_guard;
|
|
1122
|
-
_lastMutation;
|
|
1123
|
-
_inspectorHook;
|
|
1124
|
-
// Sink tracking
|
|
1125
|
-
_sinkCount = 0;
|
|
1126
|
-
_singleDepSinkCount = 0;
|
|
1127
|
-
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
1128
|
-
// Actions object (for onMessage handler)
|
|
1129
|
-
_actions;
|
|
1130
|
-
_boundDownToSinks;
|
|
1131
|
-
// Mutable state
|
|
1132
|
-
_cached = NO_VALUE;
|
|
1133
|
-
_status = "disconnected";
|
|
1134
|
-
_terminal = false;
|
|
1135
|
-
_connected = false;
|
|
1136
|
-
_rewiring = false;
|
|
1137
|
-
// re-entrancy guard
|
|
1138
1239
|
// Dynamic deps tracking
|
|
1240
|
+
/** @internal Read by `describeNode`. */
|
|
1139
1241
|
_deps = [];
|
|
1140
1242
|
_depUnsubs = [];
|
|
1141
1243
|
_depIndexMap = /* @__PURE__ */ new Map();
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1244
|
+
_depDirtyBits = /* @__PURE__ */ new Set();
|
|
1245
|
+
_depSettledBits = /* @__PURE__ */ new Set();
|
|
1246
|
+
_depCompleteBits = /* @__PURE__ */ new Set();
|
|
1247
|
+
// Execution state
|
|
1248
|
+
_running = false;
|
|
1249
|
+
_rewiring = false;
|
|
1250
|
+
_bufferedDepMessages = [];
|
|
1251
|
+
_trackedValues = /* @__PURE__ */ new Map();
|
|
1252
|
+
_rerunCount = 0;
|
|
1148
1253
|
constructor(fn, opts) {
|
|
1254
|
+
super(opts);
|
|
1149
1255
|
this._fn = fn;
|
|
1150
|
-
this._optsName = opts.name;
|
|
1151
|
-
this._describeKind = opts.describeKind;
|
|
1152
|
-
this._equals = opts.equals ?? Object.is;
|
|
1153
|
-
this._resubscribable = opts.resubscribable ?? false;
|
|
1154
|
-
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
1155
1256
|
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
1156
|
-
this.
|
|
1157
|
-
this.
|
|
1158
|
-
this._guard = opts.guard;
|
|
1159
|
-
this._inspectorHook = void 0;
|
|
1160
|
-
const meta = {};
|
|
1161
|
-
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
1162
|
-
meta[k] = node({
|
|
1163
|
-
initial: v,
|
|
1164
|
-
name: `${opts.name ?? "dynamicNode"}:meta:${k}`,
|
|
1165
|
-
describeKind: "state",
|
|
1166
|
-
...opts.guard != null ? { guard: opts.guard } : {}
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
Object.freeze(meta);
|
|
1170
|
-
this.meta = meta;
|
|
1171
|
-
const self = this;
|
|
1172
|
-
this._actions = {
|
|
1173
|
-
down(messages) {
|
|
1174
|
-
self._downInternal(messages);
|
|
1175
|
-
},
|
|
1176
|
-
emit(value) {
|
|
1177
|
-
self._downAutoValue(value);
|
|
1178
|
-
},
|
|
1179
|
-
up(messages) {
|
|
1180
|
-
for (const dep of self._deps) {
|
|
1181
|
-
dep.up?.(messages, { internal: true });
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
};
|
|
1185
|
-
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
1186
|
-
}
|
|
1187
|
-
get name() {
|
|
1188
|
-
return this._registryName ?? this._optsName;
|
|
1189
|
-
}
|
|
1190
|
-
/** @internal */
|
|
1191
|
-
_assignRegistryName(localName) {
|
|
1192
|
-
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
1193
|
-
this._registryName = localName;
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
* @internal Attach/remove inspector hook for graph-level observability.
|
|
1197
|
-
* Returns a disposer that restores the previous hook.
|
|
1198
|
-
*/
|
|
1199
|
-
_setInspectorHook(hook) {
|
|
1200
|
-
const prev = this._inspectorHook;
|
|
1201
|
-
this._inspectorHook = hook;
|
|
1202
|
-
return () => {
|
|
1203
|
-
if (this._inspectorHook === hook) {
|
|
1204
|
-
this._inspectorHook = prev;
|
|
1205
|
-
}
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
get status() {
|
|
1209
|
-
return this._status;
|
|
1257
|
+
this.down = this.down.bind(this);
|
|
1258
|
+
this.up = this.up.bind(this);
|
|
1210
1259
|
}
|
|
1211
|
-
|
|
1212
|
-
return
|
|
1260
|
+
_createMetaNode(key, initialValue, opts) {
|
|
1261
|
+
return node({
|
|
1262
|
+
initial: initialValue,
|
|
1263
|
+
name: `${opts.name ?? "dynamicNode"}:meta:${key}`,
|
|
1264
|
+
describeKind: "state",
|
|
1265
|
+
...opts.guard != null ? { guard: opts.guard } : {}
|
|
1266
|
+
});
|
|
1213
1267
|
}
|
|
1214
|
-
/** Versioning not
|
|
1268
|
+
/** Versioning not supported on DynamicNodeImpl (override base). */
|
|
1215
1269
|
get v() {
|
|
1216
1270
|
return void 0;
|
|
1217
1271
|
}
|
|
1218
|
-
|
|
1219
|
-
return this._guard != null;
|
|
1220
|
-
}
|
|
1221
|
-
allowsObserve(actor) {
|
|
1222
|
-
if (this._guard == null) return true;
|
|
1223
|
-
return this._guard(normalizeActor(actor), "observe");
|
|
1224
|
-
}
|
|
1225
|
-
get() {
|
|
1226
|
-
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
1227
|
-
}
|
|
1228
|
-
down(messages, options) {
|
|
1229
|
-
if (messages.length === 0) return;
|
|
1230
|
-
if (!options?.internal && this._guard != null) {
|
|
1231
|
-
const actor = normalizeActor(options?.actor);
|
|
1232
|
-
const delivery = options?.delivery ?? "write";
|
|
1233
|
-
const action = delivery === "signal" ? "signal" : "write";
|
|
1234
|
-
if (!this._guard(actor, action)) {
|
|
1235
|
-
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
1236
|
-
}
|
|
1237
|
-
this._lastMutation = { actor, timestamp_ns: wallClockNs() };
|
|
1238
|
-
}
|
|
1239
|
-
this._downInternal(messages);
|
|
1240
|
-
}
|
|
1241
|
-
_downInternal(messages) {
|
|
1242
|
-
if (messages.length === 0) return;
|
|
1243
|
-
let sinkMessages = messages;
|
|
1244
|
-
if (this._terminal && !this._resubscribable) {
|
|
1245
|
-
const pass = messages.filter((m) => m[0] === TEARDOWN || m[0] === INVALIDATE);
|
|
1246
|
-
if (pass.length === 0) return;
|
|
1247
|
-
sinkMessages = pass;
|
|
1248
|
-
}
|
|
1249
|
-
this._handleLocalLifecycle(sinkMessages);
|
|
1250
|
-
if (this._canSkipDirty()) {
|
|
1251
|
-
let hasPhase2 = false;
|
|
1252
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
1253
|
-
const t = sinkMessages[i][0];
|
|
1254
|
-
if (t === DATA || t === RESOLVED) {
|
|
1255
|
-
hasPhase2 = true;
|
|
1256
|
-
break;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
if (hasPhase2) {
|
|
1260
|
-
const filtered = [];
|
|
1261
|
-
for (let i = 0; i < sinkMessages.length; i++) {
|
|
1262
|
-
if (sinkMessages[i][0] !== DIRTY) filtered.push(sinkMessages[i]);
|
|
1263
|
-
}
|
|
1264
|
-
if (filtered.length > 0) {
|
|
1265
|
-
downWithBatch(this._boundDownToSinks, filtered);
|
|
1266
|
-
}
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
1271
|
-
}
|
|
1272
|
-
_canSkipDirty() {
|
|
1273
|
-
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
1274
|
-
}
|
|
1275
|
-
subscribe(sink, hints) {
|
|
1276
|
-
if (hints?.actor != null && this._guard != null) {
|
|
1277
|
-
const actor = normalizeActor(hints.actor);
|
|
1278
|
-
if (!this._guard(actor, "observe")) {
|
|
1279
|
-
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
if (this._terminal && this._resubscribable) {
|
|
1283
|
-
this._terminal = false;
|
|
1284
|
-
this._cached = NO_VALUE;
|
|
1285
|
-
this._status = "disconnected";
|
|
1286
|
-
this._onResubscribe?.();
|
|
1287
|
-
}
|
|
1288
|
-
this._sinkCount += 1;
|
|
1289
|
-
if (hints?.singleDep) {
|
|
1290
|
-
this._singleDepSinkCount += 1;
|
|
1291
|
-
this._singleDepSinks.add(sink);
|
|
1292
|
-
}
|
|
1293
|
-
if (this._sinks == null) {
|
|
1294
|
-
this._sinks = sink;
|
|
1295
|
-
} else if (typeof this._sinks === "function") {
|
|
1296
|
-
this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
|
|
1297
|
-
} else {
|
|
1298
|
-
this._sinks.add(sink);
|
|
1299
|
-
}
|
|
1300
|
-
if (!this._connected) {
|
|
1301
|
-
this._connect();
|
|
1302
|
-
}
|
|
1303
|
-
let removed = false;
|
|
1304
|
-
return () => {
|
|
1305
|
-
if (removed) return;
|
|
1306
|
-
removed = true;
|
|
1307
|
-
this._sinkCount -= 1;
|
|
1308
|
-
if (this._singleDepSinks.has(sink)) {
|
|
1309
|
-
this._singleDepSinkCount -= 1;
|
|
1310
|
-
this._singleDepSinks.delete(sink);
|
|
1311
|
-
}
|
|
1312
|
-
if (this._sinks == null) return;
|
|
1313
|
-
if (typeof this._sinks === "function") {
|
|
1314
|
-
if (this._sinks === sink) this._sinks = null;
|
|
1315
|
-
} else {
|
|
1316
|
-
this._sinks.delete(sink);
|
|
1317
|
-
if (this._sinks.size === 1) {
|
|
1318
|
-
const [only] = this._sinks;
|
|
1319
|
-
this._sinks = only;
|
|
1320
|
-
} else if (this._sinks.size === 0) {
|
|
1321
|
-
this._sinks = null;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
if (this._sinks == null) {
|
|
1325
|
-
this._disconnect();
|
|
1326
|
-
}
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1272
|
+
// --- Up / unsubscribe ---
|
|
1329
1273
|
up(messages, options) {
|
|
1330
1274
|
if (this._deps.length === 0) return;
|
|
1331
1275
|
if (!options?.internal && this._guard != null) {
|
|
@@ -1333,221 +1277,227 @@ var DynamicNodeImpl = class {
|
|
|
1333
1277
|
if (!this._guard(actor, "write")) {
|
|
1334
1278
|
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
1335
1279
|
}
|
|
1336
|
-
this.
|
|
1280
|
+
this._recordMutation(actor);
|
|
1337
1281
|
}
|
|
1338
1282
|
for (const dep of this._deps) {
|
|
1339
1283
|
dep.up?.(messages, options);
|
|
1340
1284
|
}
|
|
1341
1285
|
}
|
|
1342
|
-
|
|
1343
|
-
this.
|
|
1344
|
-
|
|
1345
|
-
// --- Private methods ---
|
|
1346
|
-
_downToSinks(messages) {
|
|
1347
|
-
if (this._sinks == null) return;
|
|
1348
|
-
if (typeof this._sinks === "function") {
|
|
1349
|
-
this._sinks(messages);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
const snapshot = [...this._sinks];
|
|
1353
|
-
for (const sink of snapshot) {
|
|
1354
|
-
sink(messages);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
_handleLocalLifecycle(messages) {
|
|
1358
|
-
for (const m of messages) {
|
|
1359
|
-
const t = m[0];
|
|
1360
|
-
if (t === DATA) this._cached = m[1];
|
|
1361
|
-
if (t === INVALIDATE) {
|
|
1362
|
-
this._cached = NO_VALUE;
|
|
1363
|
-
this._status = "dirty";
|
|
1364
|
-
}
|
|
1365
|
-
if (t === DATA) {
|
|
1366
|
-
this._status = "settled";
|
|
1367
|
-
} else if (t === RESOLVED) {
|
|
1368
|
-
this._status = "resolved";
|
|
1369
|
-
} else if (t === DIRTY) {
|
|
1370
|
-
this._status = "dirty";
|
|
1371
|
-
} else if (t === COMPLETE) {
|
|
1372
|
-
this._status = "completed";
|
|
1373
|
-
this._terminal = true;
|
|
1374
|
-
} else if (t === ERROR) {
|
|
1375
|
-
this._status = "errored";
|
|
1376
|
-
this._terminal = true;
|
|
1377
|
-
}
|
|
1378
|
-
if (t === TEARDOWN) {
|
|
1379
|
-
if (this._resetOnTeardown) this._cached = NO_VALUE;
|
|
1380
|
-
try {
|
|
1381
|
-
this._propagateToMeta(t);
|
|
1382
|
-
} finally {
|
|
1383
|
-
this._disconnect();
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
1387
|
-
this._propagateToMeta(t);
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
/** Propagate a signal to all companion meta nodes (best-effort). */
|
|
1392
|
-
_propagateToMeta(t) {
|
|
1393
|
-
for (const metaNode of Object.values(this.meta)) {
|
|
1394
|
-
try {
|
|
1395
|
-
metaNode.down([[t]], { internal: true });
|
|
1396
|
-
} catch {
|
|
1397
|
-
}
|
|
1286
|
+
_upInternal(messages) {
|
|
1287
|
+
for (const dep of this._deps) {
|
|
1288
|
+
dep.up?.(messages, { internal: true });
|
|
1398
1289
|
}
|
|
1399
1290
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
let unchanged;
|
|
1403
|
-
try {
|
|
1404
|
-
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
1405
|
-
} catch (eqErr) {
|
|
1406
|
-
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
1407
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
|
|
1408
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
if (unchanged) {
|
|
1412
|
-
this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
this._cached = value;
|
|
1416
|
-
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
1291
|
+
unsubscribe() {
|
|
1292
|
+
this._disconnect();
|
|
1417
1293
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
this._connected = true;
|
|
1421
|
-
this._status = "settled";
|
|
1422
|
-
this._dirtyBits.clear();
|
|
1423
|
-
this._settledBits.clear();
|
|
1424
|
-
this._completeBits.clear();
|
|
1294
|
+
// --- Activation hooks ---
|
|
1295
|
+
_onActivate() {
|
|
1425
1296
|
this._runFn();
|
|
1426
1297
|
}
|
|
1298
|
+
_doDeactivate() {
|
|
1299
|
+
this._disconnect();
|
|
1300
|
+
}
|
|
1427
1301
|
_disconnect() {
|
|
1428
|
-
if (!this._connected) return;
|
|
1429
1302
|
for (const unsub of this._depUnsubs) unsub();
|
|
1430
1303
|
this._depUnsubs = [];
|
|
1431
1304
|
this._deps = [];
|
|
1432
1305
|
this._depIndexMap.clear();
|
|
1433
|
-
this.
|
|
1434
|
-
this.
|
|
1435
|
-
this.
|
|
1436
|
-
this.
|
|
1306
|
+
this._depDirtyBits.clear();
|
|
1307
|
+
this._depSettledBits.clear();
|
|
1308
|
+
this._depCompleteBits.clear();
|
|
1309
|
+
this._cached = NO_VALUE;
|
|
1437
1310
|
this._status = "disconnected";
|
|
1438
1311
|
}
|
|
1312
|
+
// --- Fn execution with rewire buffer ---
|
|
1439
1313
|
_runFn() {
|
|
1440
1314
|
if (this._terminal && !this._resubscribable) return;
|
|
1441
|
-
if (this.
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
if (!trackedSet.has(dep)) {
|
|
1446
|
-
trackedSet.add(dep);
|
|
1447
|
-
trackedDeps.push(dep);
|
|
1448
|
-
}
|
|
1449
|
-
return dep.get();
|
|
1450
|
-
};
|
|
1315
|
+
if (this._running) return;
|
|
1316
|
+
this._running = true;
|
|
1317
|
+
this._rerunCount = 0;
|
|
1318
|
+
let result;
|
|
1451
1319
|
try {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1320
|
+
for (; ; ) {
|
|
1321
|
+
const trackedDeps = [];
|
|
1322
|
+
const trackedValuesMap = /* @__PURE__ */ new Map();
|
|
1323
|
+
const trackedSet = /* @__PURE__ */ new Set();
|
|
1324
|
+
const get = (dep) => {
|
|
1325
|
+
if (!trackedSet.has(dep)) {
|
|
1326
|
+
trackedSet.add(dep);
|
|
1327
|
+
trackedDeps.push(dep);
|
|
1328
|
+
trackedValuesMap.set(dep, dep.get());
|
|
1329
|
+
}
|
|
1330
|
+
return dep.get();
|
|
1331
|
+
};
|
|
1332
|
+
this._trackedValues = trackedValuesMap;
|
|
1333
|
+
const depValues = [];
|
|
1334
|
+
for (const dep of this._deps) depValues.push(dep.get());
|
|
1335
|
+
this._emitInspectorHook({ kind: "run", depValues });
|
|
1336
|
+
try {
|
|
1337
|
+
result = this._fn(get);
|
|
1338
|
+
} catch (err) {
|
|
1339
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1340
|
+
const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, {
|
|
1341
|
+
cause: err
|
|
1342
|
+
});
|
|
1343
|
+
this._downInternal([[ERROR, wrapped]]);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
this._rewiring = true;
|
|
1347
|
+
this._bufferedDepMessages = [];
|
|
1348
|
+
try {
|
|
1349
|
+
this._rewire(trackedDeps);
|
|
1350
|
+
} finally {
|
|
1351
|
+
this._rewiring = false;
|
|
1352
|
+
}
|
|
1353
|
+
let needsRerun = false;
|
|
1354
|
+
for (const entry of this._bufferedDepMessages) {
|
|
1355
|
+
for (const msg of entry.msgs) {
|
|
1356
|
+
if (msg[0] === DATA) {
|
|
1357
|
+
const dep = this._deps[entry.index];
|
|
1358
|
+
const trackedValue = dep != null ? this._trackedValues.get(dep) : void 0;
|
|
1359
|
+
const actualValue = msg[1];
|
|
1360
|
+
if (!this._equals(trackedValue, actualValue)) {
|
|
1361
|
+
needsRerun = true;
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (needsRerun) break;
|
|
1367
|
+
}
|
|
1368
|
+
if (needsRerun) {
|
|
1369
|
+
this._rerunCount += 1;
|
|
1370
|
+
if (this._rerunCount > MAX_RERUN) {
|
|
1371
|
+
this._bufferedDepMessages = [];
|
|
1372
|
+
this._downInternal([
|
|
1373
|
+
[
|
|
1374
|
+
ERROR,
|
|
1375
|
+
new Error(
|
|
1376
|
+
`dynamicNode "${this.name ?? "anonymous"}": rewire did not stabilize within ${MAX_RERUN} iterations`
|
|
1377
|
+
)
|
|
1378
|
+
]
|
|
1379
|
+
]);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
this._bufferedDepMessages = [];
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
const drain = this._bufferedDepMessages;
|
|
1386
|
+
this._bufferedDepMessages = [];
|
|
1387
|
+
for (const entry of drain) {
|
|
1388
|
+
for (const msg of entry.msgs) {
|
|
1389
|
+
this._updateMasksForMessage(entry.index, msg);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
this._depDirtyBits.clear();
|
|
1393
|
+
this._depSettledBits.clear();
|
|
1394
|
+
break;
|
|
1455
1395
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
this._rewire(trackedDeps);
|
|
1459
|
-
if (result === void 0) return;
|
|
1460
|
-
this._downAutoValue(result);
|
|
1461
|
-
} catch (err) {
|
|
1462
|
-
this._downInternal([[ERROR, err]]);
|
|
1396
|
+
} finally {
|
|
1397
|
+
this._running = false;
|
|
1463
1398
|
}
|
|
1399
|
+
this._downAutoValue(result);
|
|
1464
1400
|
}
|
|
1465
1401
|
_rewire(newDeps) {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
|
|
1482
|
-
newUnsubs.push(unsub);
|
|
1483
|
-
}
|
|
1402
|
+
const oldMap = this._depIndexMap;
|
|
1403
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
1404
|
+
const newUnsubs = [];
|
|
1405
|
+
for (let i = 0; i < newDeps.length; i++) {
|
|
1406
|
+
const dep = newDeps[i];
|
|
1407
|
+
newMap.set(dep, i);
|
|
1408
|
+
const oldIdx = oldMap.get(dep);
|
|
1409
|
+
if (oldIdx !== void 0) {
|
|
1410
|
+
newUnsubs.push(this._depUnsubs[oldIdx]);
|
|
1411
|
+
this._depUnsubs[oldIdx] = () => {
|
|
1412
|
+
};
|
|
1413
|
+
} else {
|
|
1414
|
+
const idx = i;
|
|
1415
|
+
const unsub = dep.subscribe((msgs) => this._handleDepMessages(idx, msgs));
|
|
1416
|
+
newUnsubs.push(unsub);
|
|
1484
1417
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1418
|
+
}
|
|
1419
|
+
for (const [dep, oldIdx] of oldMap) {
|
|
1420
|
+
if (!newMap.has(dep)) {
|
|
1421
|
+
this._depUnsubs[oldIdx]();
|
|
1489
1422
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1423
|
+
}
|
|
1424
|
+
this._deps = newDeps;
|
|
1425
|
+
this._depUnsubs = newUnsubs;
|
|
1426
|
+
this._depIndexMap = newMap;
|
|
1427
|
+
this._depDirtyBits.clear();
|
|
1428
|
+
this._depSettledBits.clear();
|
|
1429
|
+
const newCompleteBits = /* @__PURE__ */ new Set();
|
|
1430
|
+
for (const oldIdx of this._depCompleteBits) {
|
|
1431
|
+
for (const [dep, idx] of oldMap) {
|
|
1432
|
+
if (idx === oldIdx && newMap.has(dep)) {
|
|
1499
1433
|
newCompleteBits.add(newMap.get(dep));
|
|
1434
|
+
break;
|
|
1500
1435
|
}
|
|
1501
1436
|
}
|
|
1502
|
-
this._completeBits = newCompleteBits;
|
|
1503
|
-
} finally {
|
|
1504
|
-
this._rewiring = false;
|
|
1505
1437
|
}
|
|
1438
|
+
this._depCompleteBits = newCompleteBits;
|
|
1506
1439
|
}
|
|
1440
|
+
// --- Dep message handling ---
|
|
1507
1441
|
_handleDepMessages(index, messages) {
|
|
1508
|
-
if (this._rewiring)
|
|
1442
|
+
if (this._rewiring) {
|
|
1443
|
+
this._bufferedDepMessages.push({ index, msgs: messages });
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1509
1446
|
for (const msg of messages) {
|
|
1510
|
-
this.
|
|
1447
|
+
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
1511
1448
|
const t = msg[0];
|
|
1512
1449
|
if (this._onMessage) {
|
|
1513
1450
|
try {
|
|
1514
1451
|
if (this._onMessage(msg, index, this._actions)) continue;
|
|
1515
1452
|
} catch (err) {
|
|
1516
|
-
|
|
1453
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1454
|
+
const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
|
|
1455
|
+
cause: err
|
|
1456
|
+
});
|
|
1457
|
+
this._downInternal([[ERROR, wrapped]]);
|
|
1517
1458
|
return;
|
|
1518
1459
|
}
|
|
1519
1460
|
}
|
|
1461
|
+
if (messageTier(t) < 1) continue;
|
|
1520
1462
|
if (t === DIRTY) {
|
|
1521
|
-
this.
|
|
1522
|
-
this.
|
|
1523
|
-
|
|
1524
|
-
|
|
1463
|
+
const wasEmpty = this._depDirtyBits.size === 0;
|
|
1464
|
+
this._depDirtyBits.add(index);
|
|
1465
|
+
this._depSettledBits.delete(index);
|
|
1466
|
+
if (wasEmpty) {
|
|
1467
|
+
this._downInternal([[DIRTY]]);
|
|
1525
1468
|
}
|
|
1526
1469
|
continue;
|
|
1527
1470
|
}
|
|
1528
1471
|
if (t === DATA || t === RESOLVED) {
|
|
1529
|
-
if (!this.
|
|
1530
|
-
this.
|
|
1531
|
-
|
|
1472
|
+
if (!this._depDirtyBits.has(index)) {
|
|
1473
|
+
const wasEmpty = this._depDirtyBits.size === 0;
|
|
1474
|
+
this._depDirtyBits.add(index);
|
|
1475
|
+
if (wasEmpty) {
|
|
1476
|
+
this._downInternal([[DIRTY]]);
|
|
1477
|
+
}
|
|
1532
1478
|
}
|
|
1533
|
-
this.
|
|
1479
|
+
this._depSettledBits.add(index);
|
|
1534
1480
|
if (this._allDirtySettled()) {
|
|
1535
|
-
this.
|
|
1536
|
-
this.
|
|
1537
|
-
this.
|
|
1481
|
+
this._depDirtyBits.clear();
|
|
1482
|
+
this._depSettledBits.clear();
|
|
1483
|
+
if (!this._running) {
|
|
1484
|
+
if (this._depValuesDifferFromTracked()) {
|
|
1485
|
+
this._runFn();
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1538
1488
|
}
|
|
1539
1489
|
continue;
|
|
1540
1490
|
}
|
|
1541
1491
|
if (t === COMPLETE) {
|
|
1542
|
-
this.
|
|
1543
|
-
this.
|
|
1544
|
-
this.
|
|
1492
|
+
this._depCompleteBits.add(index);
|
|
1493
|
+
this._depDirtyBits.delete(index);
|
|
1494
|
+
this._depSettledBits.delete(index);
|
|
1545
1495
|
if (this._allDirtySettled()) {
|
|
1546
|
-
this.
|
|
1547
|
-
this.
|
|
1548
|
-
this._runFn();
|
|
1496
|
+
this._depDirtyBits.clear();
|
|
1497
|
+
this._depSettledBits.clear();
|
|
1498
|
+
if (!this._running) this._runFn();
|
|
1549
1499
|
}
|
|
1550
|
-
if (this._autoComplete && this.
|
|
1500
|
+
if (this._autoComplete && this._depCompleteBits.size >= this._deps.length && this._deps.length > 0) {
|
|
1551
1501
|
this._downInternal([[COMPLETE]]);
|
|
1552
1502
|
}
|
|
1553
1503
|
continue;
|
|
@@ -1563,13 +1513,46 @@ var DynamicNodeImpl = class {
|
|
|
1563
1513
|
this._downInternal([msg]);
|
|
1564
1514
|
}
|
|
1565
1515
|
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Update dep masks for a message without triggering `_runFn` — used
|
|
1518
|
+
* during post-rewire drain so the wave state is consistent with the
|
|
1519
|
+
* buffered activation cascade without recursing.
|
|
1520
|
+
*/
|
|
1521
|
+
_updateMasksForMessage(index, msg) {
|
|
1522
|
+
const t = msg[0];
|
|
1523
|
+
if (t === DIRTY) {
|
|
1524
|
+
this._depDirtyBits.add(index);
|
|
1525
|
+
this._depSettledBits.delete(index);
|
|
1526
|
+
} else if (t === DATA || t === RESOLVED) {
|
|
1527
|
+
this._depDirtyBits.add(index);
|
|
1528
|
+
this._depSettledBits.add(index);
|
|
1529
|
+
} else if (t === COMPLETE) {
|
|
1530
|
+
this._depCompleteBits.add(index);
|
|
1531
|
+
this._depDirtyBits.delete(index);
|
|
1532
|
+
this._depSettledBits.delete(index);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1566
1535
|
_allDirtySettled() {
|
|
1567
|
-
if (this.
|
|
1568
|
-
for (const idx of this.
|
|
1569
|
-
if (!this.
|
|
1536
|
+
if (this._depDirtyBits.size === 0) return false;
|
|
1537
|
+
for (const idx of this._depDirtyBits) {
|
|
1538
|
+
if (!this._depSettledBits.has(idx)) return false;
|
|
1570
1539
|
}
|
|
1571
1540
|
return true;
|
|
1572
1541
|
}
|
|
1542
|
+
/**
|
|
1543
|
+
* True if any current dep value differs from what the last `_runFn`
|
|
1544
|
+
* saw via `get()`. Used to suppress redundant re-runs when deferred
|
|
1545
|
+
* handshake messages arrive after `_rewire` for a dep whose value
|
|
1546
|
+
* already matches `_trackedValues`.
|
|
1547
|
+
*/
|
|
1548
|
+
_depValuesDifferFromTracked() {
|
|
1549
|
+
for (const dep of this._deps) {
|
|
1550
|
+
const current = dep.get();
|
|
1551
|
+
const tracked = this._trackedValues.get(dep);
|
|
1552
|
+
if (!this._equals(current, tracked)) return true;
|
|
1553
|
+
}
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1573
1556
|
};
|
|
1574
1557
|
|
|
1575
1558
|
// src/core/sugar.ts
|
|
@@ -1601,6 +1584,11 @@ export {
|
|
|
1601
1584
|
__decorateElement,
|
|
1602
1585
|
DEFAULT_ACTOR,
|
|
1603
1586
|
normalizeActor,
|
|
1587
|
+
GuardDenied,
|
|
1588
|
+
policy,
|
|
1589
|
+
policyFromRules,
|
|
1590
|
+
accessHintForGuard,
|
|
1591
|
+
START,
|
|
1604
1592
|
DATA,
|
|
1605
1593
|
DIRTY,
|
|
1606
1594
|
RESOLVED,
|
|
@@ -1615,6 +1603,7 @@ export {
|
|
|
1615
1603
|
messageTier,
|
|
1616
1604
|
isPhase2Message,
|
|
1617
1605
|
isTerminalMessage,
|
|
1606
|
+
isLocalOnly,
|
|
1618
1607
|
propagatesToMeta,
|
|
1619
1608
|
isBatching,
|
|
1620
1609
|
batch,
|
|
@@ -1622,10 +1611,6 @@ export {
|
|
|
1622
1611
|
downWithBatch,
|
|
1623
1612
|
monotonicNs,
|
|
1624
1613
|
wallClockNs,
|
|
1625
|
-
GuardDenied,
|
|
1626
|
-
policy,
|
|
1627
|
-
policyFromRules,
|
|
1628
|
-
accessHintForGuard,
|
|
1629
1614
|
defaultHash,
|
|
1630
1615
|
createVersioning,
|
|
1631
1616
|
advanceVersion,
|
|
@@ -1643,4 +1628,4 @@ export {
|
|
|
1643
1628
|
effect,
|
|
1644
1629
|
pipe
|
|
1645
1630
|
};
|
|
1646
|
-
//# sourceMappingURL=chunk-
|
|
1631
|
+
//# sourceMappingURL=chunk-YXR3WW3Q.js.map
|