@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.
- package/README.md +7 -5
- package/dist/chunk-263BEJJO.js +115 -0
- package/dist/chunk-263BEJJO.js.map +1 -0
- package/dist/chunk-2GQLMQVJ.js +47 -0
- package/dist/chunk-2GQLMQVJ.js.map +1 -0
- package/dist/chunk-32N5A454.js +36 -0
- package/dist/chunk-32N5A454.js.map +1 -0
- package/dist/chunk-7TAQJHQV.js +103 -0
- package/dist/chunk-7TAQJHQV.js.map +1 -0
- package/dist/{chunk-VOQFK7YN.js → chunk-CWYPA63G.js} +109 -259
- package/dist/chunk-CWYPA63G.js.map +1 -0
- package/dist/{chunk-7IGHIFTT.js → chunk-HVBX5KIW.js} +15 -26
- package/dist/chunk-HVBX5KIW.js.map +1 -0
- package/dist/chunk-JFONSPNF.js +391 -0
- package/dist/chunk-JFONSPNF.js.map +1 -0
- package/dist/chunk-NZMBRXQV.js +2330 -0
- package/dist/chunk-NZMBRXQV.js.map +1 -0
- package/dist/{chunk-XWBVAO2R.js → chunk-PNUZM7PC.js} +20 -30
- package/dist/chunk-PNUZM7PC.js.map +1 -0
- package/dist/{chunk-ZTCDY5NQ.js → chunk-PX6PDUJ5.js} +34 -50
- package/dist/chunk-PX6PDUJ5.js.map +1 -0
- package/dist/chunk-XRFJJ2IU.js +2417 -0
- package/dist/chunk-XRFJJ2IU.js.map +1 -0
- package/dist/chunk-XTLYW4FR.js +6829 -0
- package/dist/chunk-XTLYW4FR.js.map +1 -0
- package/dist/compat/nestjs/index.cjs +3489 -2286
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +6 -4
- package/dist/compat/nestjs/index.d.ts +6 -4
- package/dist/compat/nestjs/index.js +10 -8
- package/dist/core/index.cjs +1706 -1217
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +3 -2
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +37 -34
- package/dist/extra/index.cjs +7519 -6125
- 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 +63 -34
- package/dist/graph/index.cjs +3199 -2212
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +5 -3
- package/dist/graph/index.d.ts +5 -3
- package/dist/graph/index.js +24 -11
- package/dist/graph-BtdSRHUc.d.cts +1128 -0
- package/dist/graph-CEO2FkLY.d.ts +1128 -0
- package/dist/{index-DuN3bhtm.d.ts → index-B0tfuXwV.d.cts} +1697 -586
- package/dist/index-BFGjXbiP.d.cts +315 -0
- package/dist/{index-CgSiUouz.d.ts → index-BPlWVAKY.d.cts} +4 -4
- package/dist/index-BUj3ASVe.d.cts +406 -0
- package/dist/{index-VHA43cGP.d.cts → index-C59uSJAH.d.cts} +2 -2
- package/dist/index-CkElcUY6.d.ts +315 -0
- package/dist/index-DSPc5rkv.d.ts +406 -0
- package/dist/{index-BjtlNirP.d.cts → index-DgscL7v0.d.ts} +4 -4
- package/dist/{index-SFzE_KTa.d.cts → index-RXN94sHK.d.ts} +1697 -586
- package/dist/{index-8a605sg9.d.ts → index-jEtF4N7L.d.ts} +2 -2
- package/dist/index.cjs +9947 -7949
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -37
- package/dist/index.d.ts +214 -37
- package/dist/index.js +919 -648
- package/dist/index.js.map +1 -1
- package/dist/meta-3QjzotRv.d.ts +41 -0
- package/dist/meta-B-Lbs4-O.d.cts +41 -0
- package/dist/node-C7PD3sn9.d.cts +1188 -0
- package/dist/node-C7PD3sn9.d.ts +1188 -0
- package/dist/{observable-DcBwQY7t.d.ts → observable-EyO-moQY.d.ts} +1 -1
- package/dist/{observable-C8Kx_O6k.d.cts → observable-axpzv1K2.d.cts} +1 -1
- package/dist/patterns/reactive-layout/index.cjs +3205 -2138
- package/dist/patterns/reactive-layout/index.cjs.map +1 -1
- package/dist/patterns/reactive-layout/index.d.cts +5 -3
- package/dist/patterns/reactive-layout/index.d.ts +5 -3
- package/dist/patterns/reactive-layout/index.js +7 -4
- package/dist/storage-CHT5WE9m.d.ts +182 -0
- package/dist/storage-DIgAr7M_.d.cts +182 -0
- package/package.json +2 -1
- package/dist/chunk-2UDLYZHT.js +0 -2117
- package/dist/chunk-2UDLYZHT.js.map +0 -1
- package/dist/chunk-4MQ2J6IG.js +0 -1631
- package/dist/chunk-4MQ2J6IG.js.map +0 -1
- package/dist/chunk-7IGHIFTT.js.map +0 -1
- package/dist/chunk-DOSLSFKL.js +0 -162
- package/dist/chunk-DOSLSFKL.js.map +0 -1
- package/dist/chunk-ECN37NVS.js +0 -6227
- package/dist/chunk-ECN37NVS.js.map +0 -1
- package/dist/chunk-G66H6ZRK.js +0 -111
- package/dist/chunk-G66H6ZRK.js.map +0 -1
- package/dist/chunk-VOQFK7YN.js.map +0 -1
- package/dist/chunk-WZ2Z2CRV.js +0 -32
- package/dist/chunk-WZ2Z2CRV.js.map +0 -1
- package/dist/chunk-XWBVAO2R.js.map +0 -1
- package/dist/chunk-ZTCDY5NQ.js.map +0 -1
- package/dist/graph-KsTe57nI.d.cts +0 -750
- package/dist/graph-mILUUqW8.d.ts +0 -750
- package/dist/index-B2SvPEbc.d.ts +0 -257
- package/dist/index-BHfg_Ez3.d.ts +0 -629
- package/dist/index-Bc_diYYJ.d.cts +0 -629
- package/dist/index-UudxGnzc.d.cts +0 -257
- package/dist/meta-BnG7XAaE.d.cts +0 -778
- package/dist/meta-BnG7XAaE.d.ts +0 -778
package/dist/chunk-4MQ2J6IG.js
DELETED
|
@@ -1,1631 +0,0 @@
|
|
|
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/actor.ts
|
|
60
|
-
var DEFAULT_ACTOR = { type: "system", id: "" };
|
|
61
|
-
function normalizeActor(actor) {
|
|
62
|
-
if (actor == null) return DEFAULT_ACTOR;
|
|
63
|
-
const { type, id, ...rest } = actor;
|
|
64
|
-
return {
|
|
65
|
-
type: type ?? "system",
|
|
66
|
-
id: id ?? "",
|
|
67
|
-
...rest
|
|
68
|
-
};
|
|
69
|
-
}
|
|
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
|
-
|
|
169
|
-
// src/core/messages.ts
|
|
170
|
-
var START = /* @__PURE__ */ Symbol.for("graphrefly/START");
|
|
171
|
-
var DATA = /* @__PURE__ */ Symbol.for("graphrefly/DATA");
|
|
172
|
-
var DIRTY = /* @__PURE__ */ Symbol.for("graphrefly/DIRTY");
|
|
173
|
-
var RESOLVED = /* @__PURE__ */ Symbol.for("graphrefly/RESOLVED");
|
|
174
|
-
var INVALIDATE = /* @__PURE__ */ Symbol.for("graphrefly/INVALIDATE");
|
|
175
|
-
var PAUSE = /* @__PURE__ */ Symbol.for("graphrefly/PAUSE");
|
|
176
|
-
var RESUME = /* @__PURE__ */ Symbol.for("graphrefly/RESUME");
|
|
177
|
-
var TEARDOWN = /* @__PURE__ */ Symbol.for("graphrefly/TEARDOWN");
|
|
178
|
-
var COMPLETE = /* @__PURE__ */ Symbol.for("graphrefly/COMPLETE");
|
|
179
|
-
var ERROR = /* @__PURE__ */ Symbol.for("graphrefly/ERROR");
|
|
180
|
-
var knownMessageTypes = [
|
|
181
|
-
START,
|
|
182
|
-
DATA,
|
|
183
|
-
DIRTY,
|
|
184
|
-
RESOLVED,
|
|
185
|
-
INVALIDATE,
|
|
186
|
-
PAUSE,
|
|
187
|
-
RESUME,
|
|
188
|
-
TEARDOWN,
|
|
189
|
-
COMPLETE,
|
|
190
|
-
ERROR
|
|
191
|
-
];
|
|
192
|
-
var knownMessageSet = new Set(knownMessageTypes);
|
|
193
|
-
function isKnownMessageType(t) {
|
|
194
|
-
return knownMessageSet.has(t);
|
|
195
|
-
}
|
|
196
|
-
function messageTier(t) {
|
|
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;
|
|
204
|
-
}
|
|
205
|
-
function isPhase2Message(msg) {
|
|
206
|
-
const t = msg[0];
|
|
207
|
-
return t === DATA || t === RESOLVED;
|
|
208
|
-
}
|
|
209
|
-
function isTerminalMessage(t) {
|
|
210
|
-
return t === COMPLETE || t === ERROR;
|
|
211
|
-
}
|
|
212
|
-
function isLocalOnly(t) {
|
|
213
|
-
if (!knownMessageSet.has(t)) return false;
|
|
214
|
-
return messageTier(t) < 3;
|
|
215
|
-
}
|
|
216
|
-
function propagatesToMeta(t) {
|
|
217
|
-
return t === TEARDOWN;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// src/core/batch.ts
|
|
221
|
-
var MAX_DRAIN_ITERATIONS = 1e3;
|
|
222
|
-
var batchDepth = 0;
|
|
223
|
-
var flushInProgress = false;
|
|
224
|
-
var pendingPhase2 = [];
|
|
225
|
-
var pendingPhase3 = [];
|
|
226
|
-
function isBatching() {
|
|
227
|
-
return batchDepth > 0 || flushInProgress;
|
|
228
|
-
}
|
|
229
|
-
function batch(fn) {
|
|
230
|
-
batchDepth += 1;
|
|
231
|
-
let threw = false;
|
|
232
|
-
try {
|
|
233
|
-
fn();
|
|
234
|
-
} catch (e) {
|
|
235
|
-
threw = true;
|
|
236
|
-
throw e;
|
|
237
|
-
} finally {
|
|
238
|
-
batchDepth -= 1;
|
|
239
|
-
if (batchDepth === 0) {
|
|
240
|
-
if (threw) {
|
|
241
|
-
if (!flushInProgress) {
|
|
242
|
-
pendingPhase2.length = 0;
|
|
243
|
-
pendingPhase3.length = 0;
|
|
244
|
-
}
|
|
245
|
-
} else {
|
|
246
|
-
drainPending();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function drainPending() {
|
|
252
|
-
const ownsFlush = !flushInProgress;
|
|
253
|
-
if (ownsFlush) {
|
|
254
|
-
flushInProgress = true;
|
|
255
|
-
}
|
|
256
|
-
const errors = [];
|
|
257
|
-
try {
|
|
258
|
-
let iterations = 0;
|
|
259
|
-
while (pendingPhase2.length > 0 || pendingPhase3.length > 0) {
|
|
260
|
-
while (pendingPhase2.length > 0) {
|
|
261
|
-
iterations += 1;
|
|
262
|
-
if (iterations > MAX_DRAIN_ITERATIONS) {
|
|
263
|
-
pendingPhase2.length = 0;
|
|
264
|
-
pendingPhase3.length = 0;
|
|
265
|
-
throw new Error(
|
|
266
|
-
`batch drain exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely a reactive cycle`
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
const ops = pendingPhase2.splice(0);
|
|
270
|
-
for (const run of ops) {
|
|
271
|
-
try {
|
|
272
|
-
run();
|
|
273
|
-
} catch (e) {
|
|
274
|
-
errors.push(e);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (pendingPhase3.length > 0) {
|
|
279
|
-
iterations += 1;
|
|
280
|
-
if (iterations > MAX_DRAIN_ITERATIONS) {
|
|
281
|
-
pendingPhase2.length = 0;
|
|
282
|
-
pendingPhase3.length = 0;
|
|
283
|
-
throw new Error(
|
|
284
|
-
`batch drain exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely a reactive cycle`
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
const ops = pendingPhase3.splice(0);
|
|
288
|
-
for (const run of ops) {
|
|
289
|
-
try {
|
|
290
|
-
run();
|
|
291
|
-
} catch (e) {
|
|
292
|
-
errors.push(e);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
} finally {
|
|
298
|
-
if (ownsFlush) {
|
|
299
|
-
flushInProgress = false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (errors.length === 1) {
|
|
303
|
-
throw errors[0];
|
|
304
|
-
}
|
|
305
|
-
if (errors.length > 1) {
|
|
306
|
-
throw new AggregateError(errors, "batch drain: multiple callbacks threw");
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
function partitionForBatch(messages) {
|
|
310
|
-
const immediate = [];
|
|
311
|
-
const deferred = [];
|
|
312
|
-
const terminal = [];
|
|
313
|
-
for (const m of messages) {
|
|
314
|
-
if (isPhase2Message(m)) {
|
|
315
|
-
deferred.push(m);
|
|
316
|
-
} else if (isTerminalMessage(m[0])) {
|
|
317
|
-
terminal.push(m);
|
|
318
|
-
} else {
|
|
319
|
-
immediate.push(m);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return { immediate, deferred, terminal };
|
|
323
|
-
}
|
|
324
|
-
function downWithBatch(sink, messages, phase = 2, options) {
|
|
325
|
-
if (messages.length === 0) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (options?.strategy === "sequential") {
|
|
329
|
-
_downSequential(sink, messages, phase);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const queue = phase === 3 ? pendingPhase3 : pendingPhase2;
|
|
333
|
-
if (messages.length === 1) {
|
|
334
|
-
const t = messages[0][0];
|
|
335
|
-
if (t === DATA || t === RESOLVED) {
|
|
336
|
-
if (isBatching()) {
|
|
337
|
-
queue.push(() => sink(messages));
|
|
338
|
-
} else {
|
|
339
|
-
sink(messages);
|
|
340
|
-
}
|
|
341
|
-
} else if (isTerminalMessage(t)) {
|
|
342
|
-
if (isBatching()) {
|
|
343
|
-
queue.push(() => sink(messages));
|
|
344
|
-
} else {
|
|
345
|
-
sink(messages);
|
|
346
|
-
}
|
|
347
|
-
} else {
|
|
348
|
-
sink(messages);
|
|
349
|
-
}
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
const { immediate, deferred, terminal } = partitionForBatch(messages);
|
|
353
|
-
if (immediate.length > 0) {
|
|
354
|
-
sink(immediate);
|
|
355
|
-
}
|
|
356
|
-
if (isBatching()) {
|
|
357
|
-
if (deferred.length > 0) {
|
|
358
|
-
queue.push(() => sink(deferred));
|
|
359
|
-
}
|
|
360
|
-
if (terminal.length > 0) {
|
|
361
|
-
queue.push(() => sink(terminal));
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
if (deferred.length > 0) {
|
|
365
|
-
sink(deferred);
|
|
366
|
-
}
|
|
367
|
-
if (terminal.length > 0) {
|
|
368
|
-
sink(terminal);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function _downSequential(sink, messages, phase = 2) {
|
|
373
|
-
const dataQueue = phase === 3 ? pendingPhase3 : pendingPhase2;
|
|
374
|
-
for (const msg of messages) {
|
|
375
|
-
const tier = messageTier(msg[0]);
|
|
376
|
-
if (tier === 3) {
|
|
377
|
-
if (isBatching()) {
|
|
378
|
-
const m = msg;
|
|
379
|
-
dataQueue.push(() => sink([m]));
|
|
380
|
-
} else {
|
|
381
|
-
sink([msg]);
|
|
382
|
-
}
|
|
383
|
-
} else if (tier >= 4) {
|
|
384
|
-
if (isBatching()) {
|
|
385
|
-
const m = msg;
|
|
386
|
-
pendingPhase3.push(() => sink([m]));
|
|
387
|
-
} else {
|
|
388
|
-
sink([msg]);
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
sink([msg]);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// src/core/clock.ts
|
|
397
|
-
function monotonicNs() {
|
|
398
|
-
return Math.trunc(performance.now() * 1e6);
|
|
399
|
-
}
|
|
400
|
-
function wallClockNs() {
|
|
401
|
-
return Date.now() * 1e6;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/core/versioning.ts
|
|
405
|
-
import { createHash, randomUUID } from "crypto";
|
|
406
|
-
function canonicalizeForHash(value) {
|
|
407
|
-
if (value === void 0) return null;
|
|
408
|
-
if (typeof value === "number") {
|
|
409
|
-
if (!Number.isFinite(value)) {
|
|
410
|
-
throw new TypeError(`Cannot hash non-finite number: ${value}`);
|
|
411
|
-
}
|
|
412
|
-
if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
413
|
-
throw new TypeError(
|
|
414
|
-
`Cannot hash integer outside safe range (|n| > 2^53-1): ${value}. Cross-language cid parity is not guaranteed for unsafe integers.`
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
return value;
|
|
418
|
-
}
|
|
419
|
-
if (typeof value === "string" || typeof value === "boolean" || value === null) {
|
|
420
|
-
return value;
|
|
421
|
-
}
|
|
422
|
-
if (Array.isArray(value)) {
|
|
423
|
-
return value.map(canonicalizeForHash);
|
|
424
|
-
}
|
|
425
|
-
if (typeof value === "object" && value !== null) {
|
|
426
|
-
const sorted = {};
|
|
427
|
-
for (const k of Object.keys(value).sort()) {
|
|
428
|
-
sorted[k] = canonicalizeForHash(value[k]);
|
|
429
|
-
}
|
|
430
|
-
return sorted;
|
|
431
|
-
}
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
434
|
-
function defaultHash(value) {
|
|
435
|
-
const canonical = canonicalizeForHash(value ?? null);
|
|
436
|
-
const json = JSON.stringify(canonical);
|
|
437
|
-
return createHash("sha256").update(json).digest("hex").slice(0, 16);
|
|
438
|
-
}
|
|
439
|
-
function createVersioning(level, initialValue, opts) {
|
|
440
|
-
const id = opts?.id ?? randomUUID();
|
|
441
|
-
if (level === 0) {
|
|
442
|
-
return { id, version: 0 };
|
|
443
|
-
}
|
|
444
|
-
const hash = opts?.hash ?? defaultHash;
|
|
445
|
-
const cid = hash(initialValue);
|
|
446
|
-
return { id, version: 0, cid, prev: null };
|
|
447
|
-
}
|
|
448
|
-
function advanceVersion(info, newValue, hashFn) {
|
|
449
|
-
info.version += 1;
|
|
450
|
-
if ("cid" in info) {
|
|
451
|
-
info.prev = info.cid;
|
|
452
|
-
info.cid = hashFn(newValue);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
function isV1(info) {
|
|
456
|
-
return "cid" in info;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// src/core/node-base.ts
|
|
460
|
-
var NO_VALUE = /* @__PURE__ */ Symbol.for("graphrefly/NO_VALUE");
|
|
461
|
-
var CLEANUP_RESULT = /* @__PURE__ */ Symbol.for("graphrefly/CLEANUP_RESULT");
|
|
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);
|
|
482
|
-
let bits = 0;
|
|
483
|
-
return {
|
|
484
|
-
set(i) {
|
|
485
|
-
bits |= 1 << i;
|
|
486
|
-
},
|
|
487
|
-
clear(i) {
|
|
488
|
-
bits &= ~(1 << i);
|
|
489
|
-
},
|
|
490
|
-
has(i) {
|
|
491
|
-
return (bits & 1 << i) !== 0;
|
|
492
|
-
},
|
|
493
|
-
covers(other) {
|
|
494
|
-
const otherBits = other._bits();
|
|
495
|
-
return (bits & otherBits) === otherBits;
|
|
496
|
-
},
|
|
497
|
-
any() {
|
|
498
|
-
return bits !== 0;
|
|
499
|
-
},
|
|
500
|
-
reset() {
|
|
501
|
-
bits = 0;
|
|
502
|
-
},
|
|
503
|
-
setAll() {
|
|
504
|
-
bits = fullMask;
|
|
505
|
-
},
|
|
506
|
-
_bits() {
|
|
507
|
-
return bits;
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
function createArrayBitSet(size) {
|
|
512
|
-
const words = new Uint32Array(Math.ceil(size / 32));
|
|
513
|
-
const lastBits = size % 32;
|
|
514
|
-
const lastWordMask = lastBits === 0 ? 4294967295 : (1 << lastBits) - 1 >>> 0;
|
|
515
|
-
return {
|
|
516
|
-
set(i) {
|
|
517
|
-
words[i >>> 5] |= 1 << (i & 31);
|
|
518
|
-
},
|
|
519
|
-
clear(i) {
|
|
520
|
-
words[i >>> 5] &= ~(1 << (i & 31));
|
|
521
|
-
},
|
|
522
|
-
has(i) {
|
|
523
|
-
return (words[i >>> 5] & 1 << (i & 31)) !== 0;
|
|
524
|
-
},
|
|
525
|
-
covers(other) {
|
|
526
|
-
const ow = other._words;
|
|
527
|
-
for (let w = 0; w < words.length; w++) {
|
|
528
|
-
if ((words[w] & ow[w]) >>> 0 !== ow[w]) return false;
|
|
529
|
-
}
|
|
530
|
-
return true;
|
|
531
|
-
},
|
|
532
|
-
any() {
|
|
533
|
-
for (let w = 0; w < words.length; w++) {
|
|
534
|
-
if (words[w] !== 0) return true;
|
|
535
|
-
}
|
|
536
|
-
return false;
|
|
537
|
-
},
|
|
538
|
-
reset() {
|
|
539
|
-
words.fill(0);
|
|
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
|
-
},
|
|
545
|
-
_words: words
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
function createBitSet(size) {
|
|
549
|
-
return size <= 31 ? createIntBitSet(size) : createArrayBitSet(size);
|
|
550
|
-
}
|
|
551
|
-
var NodeBase = class {
|
|
552
|
-
// --- Identity (set once) ---
|
|
553
|
-
_optsName;
|
|
554
|
-
_registryName;
|
|
555
|
-
/** @internal Read by `describeNode` before inference. */
|
|
556
|
-
_describeKind;
|
|
557
|
-
meta;
|
|
558
|
-
// --- Options ---
|
|
559
|
-
_equals;
|
|
560
|
-
_resubscribable;
|
|
561
|
-
_resetOnTeardown;
|
|
562
|
-
_onResubscribe;
|
|
563
|
-
_onMessage;
|
|
564
|
-
/** @internal Read by `describeNode` for `accessHintForGuard`. */
|
|
565
|
-
_guard;
|
|
566
|
-
/** @internal Subclasses update this through {@link _recordMutation}. */
|
|
567
|
-
_lastMutation;
|
|
568
|
-
// --- Versioning ---
|
|
569
|
-
_hashFn;
|
|
570
|
-
_versioning;
|
|
571
|
-
// --- Lifecycle state ---
|
|
572
|
-
/** @internal Read by `describeNode` and `graph.ts`. */
|
|
573
|
-
_cached;
|
|
574
|
-
/** @internal Read externally via `get status()`. */
|
|
575
|
-
_status;
|
|
576
|
-
_terminal = false;
|
|
577
|
-
_active = false;
|
|
578
|
-
// --- Sink storage ---
|
|
579
|
-
/** @internal Read by `graph/profile.ts` for subscriber counts. */
|
|
580
|
-
_sinkCount = 0;
|
|
581
|
-
_singleDepSinkCount = 0;
|
|
582
|
-
_singleDepSinks = /* @__PURE__ */ new WeakSet();
|
|
583
|
-
_sinks = null;
|
|
584
|
-
// --- Actions + bound helpers ---
|
|
585
|
-
_actions;
|
|
586
|
-
_boundDownToSinks;
|
|
587
|
-
// --- Inspector hook (Graph observability) ---
|
|
588
|
-
_inspectorHook;
|
|
589
|
-
constructor(opts) {
|
|
590
|
-
this._optsName = opts.name;
|
|
591
|
-
this._describeKind = opts.describeKind;
|
|
592
|
-
this._equals = opts.equals ?? Object.is;
|
|
593
|
-
this._resubscribable = opts.resubscribable ?? false;
|
|
594
|
-
this._resetOnTeardown = opts.resetOnTeardown ?? false;
|
|
595
|
-
this._onResubscribe = opts.onResubscribe;
|
|
596
|
-
this._onMessage = opts.onMessage;
|
|
597
|
-
this._guard = opts.guard;
|
|
598
|
-
this._cached = "initial" in opts ? opts.initial : NO_VALUE;
|
|
599
|
-
this._status = "disconnected";
|
|
600
|
-
this._hashFn = opts.versioningHash ?? defaultHash;
|
|
601
|
-
this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached === NO_VALUE ? void 0 : this._cached, {
|
|
602
|
-
id: opts.versioningId,
|
|
603
|
-
hash: this._hashFn
|
|
604
|
-
}) : void 0;
|
|
605
|
-
const meta = {};
|
|
606
|
-
for (const [k, v] of Object.entries(opts.meta ?? {})) {
|
|
607
|
-
meta[k] = this._createMetaNode(k, v, opts);
|
|
608
|
-
}
|
|
609
|
-
Object.freeze(meta);
|
|
610
|
-
this.meta = meta;
|
|
611
|
-
const self = this;
|
|
612
|
-
this._actions = {
|
|
613
|
-
down(messages) {
|
|
614
|
-
self._onManualEmit();
|
|
615
|
-
self._downInternal(messages);
|
|
616
|
-
},
|
|
617
|
-
emit(value) {
|
|
618
|
-
self._onManualEmit();
|
|
619
|
-
self._downAutoValue(value);
|
|
620
|
-
},
|
|
621
|
-
up(messages) {
|
|
622
|
-
self._upInternal(messages);
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
this._boundDownToSinks = this._downToSinks.bind(this);
|
|
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 ---
|
|
634
|
-
get name() {
|
|
635
|
-
return this._registryName ?? this._optsName;
|
|
636
|
-
}
|
|
637
|
-
/** @internal Assigned by `Graph.add` when registered without an options `name`. */
|
|
638
|
-
_assignRegistryName(localName) {
|
|
639
|
-
if (this._optsName !== void 0 || this._registryName !== void 0) return;
|
|
640
|
-
this._registryName = localName;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* @internal Attach/remove inspector hook for graph-level observability.
|
|
644
|
-
* Returns a disposer that restores the previous hook.
|
|
645
|
-
*/
|
|
646
|
-
_setInspectorHook(hook) {
|
|
647
|
-
const prev = this._inspectorHook;
|
|
648
|
-
this._inspectorHook = hook;
|
|
649
|
-
return () => {
|
|
650
|
-
if (this._inspectorHook === hook) {
|
|
651
|
-
this._inspectorHook = prev;
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
/** @internal Used by subclasses to surface inspector events. */
|
|
656
|
-
_emitInspectorHook(event) {
|
|
657
|
-
this._inspectorHook?.(event);
|
|
658
|
-
}
|
|
659
|
-
get status() {
|
|
660
|
-
return this._status;
|
|
661
|
-
}
|
|
662
|
-
get lastMutation() {
|
|
663
|
-
return this._lastMutation;
|
|
664
|
-
}
|
|
665
|
-
get v() {
|
|
666
|
-
return this._versioning;
|
|
667
|
-
}
|
|
668
|
-
/** @internal Used by `Graph.setVersioning` to retroactively apply versioning. */
|
|
669
|
-
_applyVersioning(level, opts) {
|
|
670
|
-
if (this._versioning != null) return;
|
|
671
|
-
this._hashFn = opts?.hash ?? this._hashFn;
|
|
672
|
-
this._versioning = createVersioning(
|
|
673
|
-
level,
|
|
674
|
-
this._cached === NO_VALUE ? void 0 : this._cached,
|
|
675
|
-
{
|
|
676
|
-
id: opts?.id,
|
|
677
|
-
hash: this._hashFn
|
|
678
|
-
}
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
hasGuard() {
|
|
682
|
-
return this._guard != null;
|
|
683
|
-
}
|
|
684
|
-
allowsObserve(actor) {
|
|
685
|
-
if (this._guard == null) return true;
|
|
686
|
-
return this._guard(normalizeActor(actor), "observe");
|
|
687
|
-
}
|
|
688
|
-
// --- Public transport ---
|
|
689
|
-
get() {
|
|
690
|
-
return this._cached === NO_VALUE ? void 0 : this._cached;
|
|
691
|
-
}
|
|
692
|
-
down(messages, options) {
|
|
693
|
-
if (messages.length === 0) return;
|
|
694
|
-
if (!options?.internal && this._guard != null) {
|
|
695
|
-
const actor = normalizeActor(options?.actor);
|
|
696
|
-
const delivery = options?.delivery ?? "write";
|
|
697
|
-
const action = delivery === "signal" ? "signal" : "write";
|
|
698
|
-
if (!this._guard(actor, action)) {
|
|
699
|
-
throw new GuardDenied({ actor, action, nodeName: this.name });
|
|
700
|
-
}
|
|
701
|
-
this._recordMutation(actor);
|
|
702
|
-
}
|
|
703
|
-
this._downInternal(messages);
|
|
704
|
-
}
|
|
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();
|
|
718
|
-
}
|
|
719
|
-
// --- Subscribe (uniform across node shapes) ---
|
|
720
|
-
subscribe(sink, hints) {
|
|
721
|
-
if (hints?.actor != null && this._guard != null) {
|
|
722
|
-
const actor = normalizeActor(hints.actor);
|
|
723
|
-
if (!this._guard(actor, "observe")) {
|
|
724
|
-
throw new GuardDenied({ actor, action: "observe", nodeName: this.name });
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (this._terminal && this._resubscribable) {
|
|
728
|
-
this._terminal = false;
|
|
729
|
-
this._cached = NO_VALUE;
|
|
730
|
-
this._status = "disconnected";
|
|
731
|
-
this._onResubscribe?.();
|
|
732
|
-
}
|
|
733
|
-
this._sinkCount += 1;
|
|
734
|
-
if (hints?.singleDep) {
|
|
735
|
-
this._singleDepSinkCount += 1;
|
|
736
|
-
this._singleDepSinks.add(sink);
|
|
737
|
-
}
|
|
738
|
-
if (!this._terminal) {
|
|
739
|
-
const startMessages = this._cached === NO_VALUE ? [[START]] : [[START], [DATA, this._cached]];
|
|
740
|
-
downWithBatch(sink, startMessages);
|
|
741
|
-
}
|
|
742
|
-
if (this._sinks == null) {
|
|
743
|
-
this._sinks = sink;
|
|
744
|
-
} else if (typeof this._sinks === "function") {
|
|
745
|
-
this._sinks = /* @__PURE__ */ new Set([this._sinks, sink]);
|
|
746
|
-
} else {
|
|
747
|
-
this._sinks.add(sink);
|
|
748
|
-
}
|
|
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";
|
|
755
|
-
}
|
|
756
|
-
let removed = false;
|
|
757
|
-
return () => {
|
|
758
|
-
if (removed) return;
|
|
759
|
-
removed = true;
|
|
760
|
-
this._sinkCount -= 1;
|
|
761
|
-
if (this._singleDepSinks.has(sink)) {
|
|
762
|
-
this._singleDepSinkCount -= 1;
|
|
763
|
-
this._singleDepSinks.delete(sink);
|
|
764
|
-
}
|
|
765
|
-
if (this._sinks == null) return;
|
|
766
|
-
if (typeof this._sinks === "function") {
|
|
767
|
-
if (this._sinks === sink) this._sinks = null;
|
|
768
|
-
} else {
|
|
769
|
-
this._sinks.delete(sink);
|
|
770
|
-
if (this._sinks.size === 1) {
|
|
771
|
-
const [only] = this._sinks;
|
|
772
|
-
this._sinks = only;
|
|
773
|
-
} else if (this._sinks.size === 0) {
|
|
774
|
-
this._sinks = null;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
if (this._sinks == null) {
|
|
778
|
-
this._onDeactivate();
|
|
779
|
-
}
|
|
780
|
-
};
|
|
781
|
-
}
|
|
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;
|
|
794
|
-
}
|
|
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;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
downWithBatch(this._boundDownToSinks, sinkMessages);
|
|
817
|
-
}
|
|
818
|
-
_canSkipDirty() {
|
|
819
|
-
return this._sinkCount === 1 && this._singleDepSinkCount === 1;
|
|
820
|
-
}
|
|
821
|
-
_downToSinks(messages) {
|
|
822
|
-
if (this._sinks == null) return;
|
|
823
|
-
if (typeof this._sinks === "function") {
|
|
824
|
-
this._sinks(messages);
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
827
|
-
const snapshot = [...this._sinks];
|
|
828
|
-
for (const sink of snapshot) {
|
|
829
|
-
sink(messages);
|
|
830
|
-
}
|
|
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
|
-
_handleLocalLifecycle(messages) {
|
|
838
|
-
for (const m of messages) {
|
|
839
|
-
const t = m[0];
|
|
840
|
-
if (t === DATA) {
|
|
841
|
-
if (m.length < 2) {
|
|
842
|
-
continue;
|
|
843
|
-
}
|
|
844
|
-
this._cached = m[1];
|
|
845
|
-
if (this._versioning != null) {
|
|
846
|
-
advanceVersion(this._versioning, m[1], this._hashFn);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
if (t === INVALIDATE) {
|
|
850
|
-
this._onInvalidate();
|
|
851
|
-
this._cached = NO_VALUE;
|
|
852
|
-
}
|
|
853
|
-
this._status = statusAfterMessage(this._status, m);
|
|
854
|
-
if (t === COMPLETE || t === ERROR) {
|
|
855
|
-
this._terminal = true;
|
|
856
|
-
}
|
|
857
|
-
if (t === TEARDOWN) {
|
|
858
|
-
if (this._resetOnTeardown) {
|
|
859
|
-
this._cached = NO_VALUE;
|
|
860
|
-
}
|
|
861
|
-
this._onTeardown();
|
|
862
|
-
try {
|
|
863
|
-
this._propagateToMeta(t);
|
|
864
|
-
} finally {
|
|
865
|
-
this._onDeactivate();
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
if (t !== TEARDOWN && propagatesToMeta(t)) {
|
|
869
|
-
this._propagateToMeta(t);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
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). */
|
|
887
|
-
_propagateToMeta(t) {
|
|
888
|
-
for (const metaNode of Object.values(this.meta)) {
|
|
889
|
-
try {
|
|
890
|
-
metaNode._downInternal([[t]]);
|
|
891
|
-
} catch {
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Frame a computed value into the right protocol messages and dispatch
|
|
897
|
-
* via `_downInternal`. Used by `_runFn` and `actions.emit`.
|
|
898
|
-
*/
|
|
899
|
-
_downAutoValue(value) {
|
|
900
|
-
const wasDirty = this._status === "dirty";
|
|
901
|
-
let unchanged;
|
|
902
|
-
try {
|
|
903
|
-
unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
|
|
904
|
-
} catch (eqErr) {
|
|
905
|
-
const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
|
|
906
|
-
const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, {
|
|
907
|
-
cause: eqErr
|
|
908
|
-
});
|
|
909
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
if (unchanged) {
|
|
913
|
-
this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
this._downInternal(wasDirty ? [[DATA, value]] : [[DIRTY], [DATA, value]]);
|
|
917
|
-
}
|
|
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 });
|
|
981
|
-
}
|
|
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);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
_upInternal(messages) {
|
|
993
|
-
if (!this._hasDeps) return;
|
|
994
|
-
for (const dep of this._deps) {
|
|
995
|
-
dep.up?.(messages, { internal: true });
|
|
996
|
-
}
|
|
997
|
-
}
|
|
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;
|
|
1007
|
-
}
|
|
1008
|
-
if (this._fn) {
|
|
1009
|
-
this._runFn();
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
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;
|
|
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;
|
|
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 ---
|
|
1067
|
-
_handleDepMessages(index, messages) {
|
|
1068
|
-
for (const msg of messages) {
|
|
1069
|
-
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
1070
|
-
const t = msg[0];
|
|
1071
|
-
if (this._onMessage) {
|
|
1072
|
-
try {
|
|
1073
|
-
const consumed = this._onMessage(msg, index, this._actions);
|
|
1074
|
-
if (consumed) {
|
|
1075
|
-
if (t === START || t === DATA || t === RESOLVED) {
|
|
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
|
-
}
|
|
1085
|
-
} catch (err) {
|
|
1086
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1087
|
-
const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
|
|
1088
|
-
cause: err
|
|
1089
|
-
});
|
|
1090
|
-
this._downInternal([[ERROR, wrapped]]);
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
if (messageTier(t) < 1) continue;
|
|
1095
|
-
if (!this._fn) {
|
|
1096
|
-
if (t === COMPLETE && this._deps.length > 1) {
|
|
1097
|
-
this._depCompleteMask.set(index);
|
|
1098
|
-
this._maybeCompleteFromDeps();
|
|
1099
|
-
continue;
|
|
1100
|
-
}
|
|
1101
|
-
this._downInternal([msg]);
|
|
1102
|
-
continue;
|
|
1103
|
-
}
|
|
1104
|
-
if (t === DIRTY) {
|
|
1105
|
-
this._onDepDirty(index);
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
if (t === DATA || t === RESOLVED) {
|
|
1109
|
-
this._onDepSettled(index);
|
|
1110
|
-
continue;
|
|
1111
|
-
}
|
|
1112
|
-
if (t === COMPLETE) {
|
|
1113
|
-
this._depCompleteMask.set(index);
|
|
1114
|
-
this._depDirtyMask.clear(index);
|
|
1115
|
-
this._depSettledMask.clear(index);
|
|
1116
|
-
if (this._depDirtyMask.any() && this._depSettledMask.covers(this._depDirtyMask)) {
|
|
1117
|
-
this._depDirtyMask.reset();
|
|
1118
|
-
this._depSettledMask.reset();
|
|
1119
|
-
this._runFn();
|
|
1120
|
-
} else if (!this._depDirtyMask.any() && this._status === "dirty") {
|
|
1121
|
-
this._depSettledMask.reset();
|
|
1122
|
-
this._runFn();
|
|
1123
|
-
}
|
|
1124
|
-
this._maybeCompleteFromDeps();
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
if (t === ERROR) {
|
|
1128
|
-
this._downInternal([msg]);
|
|
1129
|
-
continue;
|
|
1130
|
-
}
|
|
1131
|
-
if (t === INVALIDATE || t === TEARDOWN || t === PAUSE || t === RESUME) {
|
|
1132
|
-
this._downInternal([msg]);
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1135
|
-
this._downInternal([msg]);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
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]]);
|
|
1144
|
-
}
|
|
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();
|
|
1154
|
-
this._runFn();
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
_maybeCompleteFromDeps() {
|
|
1158
|
-
if (this._autoComplete && this._deps.length > 0 && this._depCompleteMask.covers(this._allDepsCompleteMask)) {
|
|
1159
|
-
this._downInternal([[COMPLETE]]);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
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]]);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
};
|
|
1215
|
-
var isNodeArray = (value) => Array.isArray(value);
|
|
1216
|
-
var isNodeOptions = (value) => typeof value === "object" && value != null && !Array.isArray(value);
|
|
1217
|
-
function node(depsOrFn, fnOrOpts, optsArg) {
|
|
1218
|
-
const deps = isNodeArray(depsOrFn) ? depsOrFn : [];
|
|
1219
|
-
const fn = typeof depsOrFn === "function" ? depsOrFn : typeof fnOrOpts === "function" ? fnOrOpts : void 0;
|
|
1220
|
-
let opts = {};
|
|
1221
|
-
if (isNodeArray(depsOrFn)) {
|
|
1222
|
-
opts = (isNodeOptions(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
|
|
1223
|
-
} else if (isNodeOptions(depsOrFn)) {
|
|
1224
|
-
opts = depsOrFn;
|
|
1225
|
-
} else {
|
|
1226
|
-
opts = (isNodeOptions(fnOrOpts) ? fnOrOpts : optsArg) ?? {};
|
|
1227
|
-
}
|
|
1228
|
-
return new NodeImpl(deps, fn, opts);
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
// src/core/dynamic-node.ts
|
|
1232
|
-
var MAX_RERUN = 16;
|
|
1233
|
-
function dynamicNode(fn, opts) {
|
|
1234
|
-
return new DynamicNodeImpl(fn, opts ?? {});
|
|
1235
|
-
}
|
|
1236
|
-
var DynamicNodeImpl = class extends NodeBase {
|
|
1237
|
-
_fn;
|
|
1238
|
-
_autoComplete;
|
|
1239
|
-
// Dynamic deps tracking
|
|
1240
|
-
/** @internal Read by `describeNode`. */
|
|
1241
|
-
_deps = [];
|
|
1242
|
-
_depUnsubs = [];
|
|
1243
|
-
_depIndexMap = /* @__PURE__ */ new Map();
|
|
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;
|
|
1253
|
-
constructor(fn, opts) {
|
|
1254
|
-
super(opts);
|
|
1255
|
-
this._fn = fn;
|
|
1256
|
-
this._autoComplete = opts.completeWhenDepsComplete ?? true;
|
|
1257
|
-
this.down = this.down.bind(this);
|
|
1258
|
-
this.up = this.up.bind(this);
|
|
1259
|
-
}
|
|
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
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
/** Versioning not supported on DynamicNodeImpl (override base). */
|
|
1269
|
-
get v() {
|
|
1270
|
-
return void 0;
|
|
1271
|
-
}
|
|
1272
|
-
// --- Up / unsubscribe ---
|
|
1273
|
-
up(messages, options) {
|
|
1274
|
-
if (this._deps.length === 0) return;
|
|
1275
|
-
if (!options?.internal && this._guard != null) {
|
|
1276
|
-
const actor = normalizeActor(options?.actor);
|
|
1277
|
-
if (!this._guard(actor, "write")) {
|
|
1278
|
-
throw new GuardDenied({ actor, action: "write", nodeName: this.name });
|
|
1279
|
-
}
|
|
1280
|
-
this._recordMutation(actor);
|
|
1281
|
-
}
|
|
1282
|
-
for (const dep of this._deps) {
|
|
1283
|
-
dep.up?.(messages, options);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
_upInternal(messages) {
|
|
1287
|
-
for (const dep of this._deps) {
|
|
1288
|
-
dep.up?.(messages, { internal: true });
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
unsubscribe() {
|
|
1292
|
-
this._disconnect();
|
|
1293
|
-
}
|
|
1294
|
-
// --- Activation hooks ---
|
|
1295
|
-
_onActivate() {
|
|
1296
|
-
this._runFn();
|
|
1297
|
-
}
|
|
1298
|
-
_doDeactivate() {
|
|
1299
|
-
this._disconnect();
|
|
1300
|
-
}
|
|
1301
|
-
_disconnect() {
|
|
1302
|
-
for (const unsub of this._depUnsubs) unsub();
|
|
1303
|
-
this._depUnsubs = [];
|
|
1304
|
-
this._deps = [];
|
|
1305
|
-
this._depIndexMap.clear();
|
|
1306
|
-
this._depDirtyBits.clear();
|
|
1307
|
-
this._depSettledBits.clear();
|
|
1308
|
-
this._depCompleteBits.clear();
|
|
1309
|
-
this._cached = NO_VALUE;
|
|
1310
|
-
this._status = "disconnected";
|
|
1311
|
-
}
|
|
1312
|
-
// --- Fn execution with rewire buffer ---
|
|
1313
|
-
_runFn() {
|
|
1314
|
-
if (this._terminal && !this._resubscribable) return;
|
|
1315
|
-
if (this._running) return;
|
|
1316
|
-
this._running = true;
|
|
1317
|
-
this._rerunCount = 0;
|
|
1318
|
-
let result;
|
|
1319
|
-
try {
|
|
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;
|
|
1395
|
-
}
|
|
1396
|
-
} finally {
|
|
1397
|
-
this._running = false;
|
|
1398
|
-
}
|
|
1399
|
-
this._downAutoValue(result);
|
|
1400
|
-
}
|
|
1401
|
-
_rewire(newDeps) {
|
|
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);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
for (const [dep, oldIdx] of oldMap) {
|
|
1420
|
-
if (!newMap.has(dep)) {
|
|
1421
|
-
this._depUnsubs[oldIdx]();
|
|
1422
|
-
}
|
|
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)) {
|
|
1433
|
-
newCompleteBits.add(newMap.get(dep));
|
|
1434
|
-
break;
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
this._depCompleteBits = newCompleteBits;
|
|
1439
|
-
}
|
|
1440
|
-
// --- Dep message handling ---
|
|
1441
|
-
_handleDepMessages(index, messages) {
|
|
1442
|
-
if (this._rewiring) {
|
|
1443
|
-
this._bufferedDepMessages.push({ index, msgs: messages });
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
for (const msg of messages) {
|
|
1447
|
-
this._emitInspectorHook({ kind: "dep_message", depIndex: index, message: msg });
|
|
1448
|
-
const t = msg[0];
|
|
1449
|
-
if (this._onMessage) {
|
|
1450
|
-
try {
|
|
1451
|
-
if (this._onMessage(msg, index, this._actions)) continue;
|
|
1452
|
-
} catch (err) {
|
|
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]]);
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
if (messageTier(t) < 1) continue;
|
|
1462
|
-
if (t === DIRTY) {
|
|
1463
|
-
const wasEmpty = this._depDirtyBits.size === 0;
|
|
1464
|
-
this._depDirtyBits.add(index);
|
|
1465
|
-
this._depSettledBits.delete(index);
|
|
1466
|
-
if (wasEmpty) {
|
|
1467
|
-
this._downInternal([[DIRTY]]);
|
|
1468
|
-
}
|
|
1469
|
-
continue;
|
|
1470
|
-
}
|
|
1471
|
-
if (t === DATA || t === RESOLVED) {
|
|
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
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
this._depSettledBits.add(index);
|
|
1480
|
-
if (this._allDirtySettled()) {
|
|
1481
|
-
this._depDirtyBits.clear();
|
|
1482
|
-
this._depSettledBits.clear();
|
|
1483
|
-
if (!this._running) {
|
|
1484
|
-
if (this._depValuesDifferFromTracked()) {
|
|
1485
|
-
this._runFn();
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
continue;
|
|
1490
|
-
}
|
|
1491
|
-
if (t === COMPLETE) {
|
|
1492
|
-
this._depCompleteBits.add(index);
|
|
1493
|
-
this._depDirtyBits.delete(index);
|
|
1494
|
-
this._depSettledBits.delete(index);
|
|
1495
|
-
if (this._allDirtySettled()) {
|
|
1496
|
-
this._depDirtyBits.clear();
|
|
1497
|
-
this._depSettledBits.clear();
|
|
1498
|
-
if (!this._running) this._runFn();
|
|
1499
|
-
}
|
|
1500
|
-
if (this._autoComplete && this._depCompleteBits.size >= this._deps.length && this._deps.length > 0) {
|
|
1501
|
-
this._downInternal([[COMPLETE]]);
|
|
1502
|
-
}
|
|
1503
|
-
continue;
|
|
1504
|
-
}
|
|
1505
|
-
if (t === ERROR) {
|
|
1506
|
-
this._downInternal([msg]);
|
|
1507
|
-
continue;
|
|
1508
|
-
}
|
|
1509
|
-
if (t === INVALIDATE || t === TEARDOWN || t === PAUSE || t === RESUME) {
|
|
1510
|
-
this._downInternal([msg]);
|
|
1511
|
-
continue;
|
|
1512
|
-
}
|
|
1513
|
-
this._downInternal([msg]);
|
|
1514
|
-
}
|
|
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
|
-
}
|
|
1535
|
-
_allDirtySettled() {
|
|
1536
|
-
if (this._depDirtyBits.size === 0) return false;
|
|
1537
|
-
for (const idx of this._depDirtyBits) {
|
|
1538
|
-
if (!this._depSettledBits.has(idx)) return false;
|
|
1539
|
-
}
|
|
1540
|
-
return true;
|
|
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
|
-
}
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
// src/core/sugar.ts
|
|
1559
|
-
function state(initial, opts) {
|
|
1560
|
-
return node([], { ...opts, initial });
|
|
1561
|
-
}
|
|
1562
|
-
function producer(fn, opts) {
|
|
1563
|
-
return node(fn, { describeKind: "producer", ...opts });
|
|
1564
|
-
}
|
|
1565
|
-
function derived(deps, fn, opts) {
|
|
1566
|
-
return node(deps, fn, { describeKind: "derived", ...opts });
|
|
1567
|
-
}
|
|
1568
|
-
function effect(deps, fn, opts) {
|
|
1569
|
-
return node(deps, fn, { describeKind: "effect", ...opts });
|
|
1570
|
-
}
|
|
1571
|
-
function pipe(source, ...ops) {
|
|
1572
|
-
let current = source;
|
|
1573
|
-
for (const op of ops) {
|
|
1574
|
-
current = op(current);
|
|
1575
|
-
}
|
|
1576
|
-
return current;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
export {
|
|
1580
|
-
__require,
|
|
1581
|
-
__export,
|
|
1582
|
-
__decoratorStart,
|
|
1583
|
-
__runInitializers,
|
|
1584
|
-
__decorateElement,
|
|
1585
|
-
DEFAULT_ACTOR,
|
|
1586
|
-
normalizeActor,
|
|
1587
|
-
GuardDenied,
|
|
1588
|
-
policy,
|
|
1589
|
-
policyFromRules,
|
|
1590
|
-
accessHintForGuard,
|
|
1591
|
-
START,
|
|
1592
|
-
DATA,
|
|
1593
|
-
DIRTY,
|
|
1594
|
-
RESOLVED,
|
|
1595
|
-
INVALIDATE,
|
|
1596
|
-
PAUSE,
|
|
1597
|
-
RESUME,
|
|
1598
|
-
TEARDOWN,
|
|
1599
|
-
COMPLETE,
|
|
1600
|
-
ERROR,
|
|
1601
|
-
knownMessageTypes,
|
|
1602
|
-
isKnownMessageType,
|
|
1603
|
-
messageTier,
|
|
1604
|
-
isPhase2Message,
|
|
1605
|
-
isTerminalMessage,
|
|
1606
|
-
isLocalOnly,
|
|
1607
|
-
propagatesToMeta,
|
|
1608
|
-
isBatching,
|
|
1609
|
-
batch,
|
|
1610
|
-
partitionForBatch,
|
|
1611
|
-
downWithBatch,
|
|
1612
|
-
monotonicNs,
|
|
1613
|
-
wallClockNs,
|
|
1614
|
-
defaultHash,
|
|
1615
|
-
createVersioning,
|
|
1616
|
-
advanceVersion,
|
|
1617
|
-
isV1,
|
|
1618
|
-
NO_VALUE,
|
|
1619
|
-
CLEANUP_RESULT,
|
|
1620
|
-
cleanupResult,
|
|
1621
|
-
NodeImpl,
|
|
1622
|
-
node,
|
|
1623
|
-
dynamicNode,
|
|
1624
|
-
DynamicNodeImpl,
|
|
1625
|
-
state,
|
|
1626
|
-
producer,
|
|
1627
|
-
derived,
|
|
1628
|
-
effect,
|
|
1629
|
-
pipe
|
|
1630
|
-
};
|
|
1631
|
-
//# sourceMappingURL=chunk-4MQ2J6IG.js.map
|