@markless/runtime 0.3.2 → 0.3.3
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/graph-B77v_FUC.js +441 -0
- package/dist/graph-async-Db6SqB5x.js +197 -0
- package/dist/{graph-C1ACA8O1.d.ts → graph-reconcile-DoBBfYsT.d.ts} +128 -68
- package/dist/graph-reconcile.d.ts +2 -0
- package/dist/graph-reconcile.js +324 -0
- package/dist/graph.d.ts +2 -2
- package/dist/graph.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +6 -2
- package/dist/graph-DAwgoMQ1.js +0 -941
package/dist/graph-DAwgoMQ1.js
DELETED
|
@@ -1,941 +0,0 @@
|
|
|
1
|
-
//#region src/graph-core.ts
|
|
2
|
-
function findLastSharedReturnProperty(properties, propertyName) {
|
|
3
|
-
if (!properties) return void 0;
|
|
4
|
-
for (let index = properties.length - 1; index >= 0; index--) {
|
|
5
|
-
const property = properties[index];
|
|
6
|
-
if (property?.name === propertyName) return property;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
function readPath(value, path) {
|
|
10
|
-
let current = value;
|
|
11
|
-
for (const segment of path) {
|
|
12
|
-
if (current == null) return void 0;
|
|
13
|
-
current = current[segment];
|
|
14
|
-
}
|
|
15
|
-
return current;
|
|
16
|
-
}
|
|
17
|
-
function writePath(value, path, nextValue) {
|
|
18
|
-
if (path.length === 0) return nextValue;
|
|
19
|
-
const root = isObject(value) ? value : {};
|
|
20
|
-
let current = root;
|
|
21
|
-
for (const segment of path.slice(0, -1)) {
|
|
22
|
-
const child = current[segment];
|
|
23
|
-
if (!isObject(child)) current[segment] = {};
|
|
24
|
-
current = current[segment];
|
|
25
|
-
}
|
|
26
|
-
current[path[path.length - 1]] = nextValue;
|
|
27
|
-
return root;
|
|
28
|
-
}
|
|
29
|
-
function dirtyPathForGraphWrite(value, path) {
|
|
30
|
-
if (path[path.length - 1] !== "length") return path;
|
|
31
|
-
const parentPath = path.slice(0, -1);
|
|
32
|
-
const parent = readPath(value, parentPath);
|
|
33
|
-
if (!Array.isArray(parent)) return path;
|
|
34
|
-
return parentPath;
|
|
35
|
-
}
|
|
36
|
-
function deletePath(value, path) {
|
|
37
|
-
if (path.length === 0) throw new TypeError("Cannot delete a graph node root. Delete a property path instead.");
|
|
38
|
-
let current = value;
|
|
39
|
-
for (const segment of path.slice(0, -1)) {
|
|
40
|
-
if (current == null) throw new TypeError(`Cannot delete graph path "${path.join(".")}".`);
|
|
41
|
-
current = current[segment];
|
|
42
|
-
}
|
|
43
|
-
if (current == null) throw new TypeError(`Cannot delete graph path "${path.join(".")}".`);
|
|
44
|
-
if (!isObject(current)) return {
|
|
45
|
-
result: true,
|
|
46
|
-
mutated: false
|
|
47
|
-
};
|
|
48
|
-
const key = path[path.length - 1];
|
|
49
|
-
const hadProperty = Object.prototype.hasOwnProperty.call(current, key);
|
|
50
|
-
const result = delete current[key];
|
|
51
|
-
return {
|
|
52
|
-
result,
|
|
53
|
-
mutated: hadProperty && result
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function pathsIntersect(a, b) {
|
|
57
|
-
return isPrefix(a, b) || isPrefix(b, a);
|
|
58
|
-
}
|
|
59
|
-
function isPrefix(prefix, value) {
|
|
60
|
-
if (prefix.length > value.length) return false;
|
|
61
|
-
return prefix.every((segment, index) => value[index] === segment);
|
|
62
|
-
}
|
|
63
|
-
function isObject(value) {
|
|
64
|
-
return typeof value === "object" && value !== null;
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/graph-collections.ts
|
|
68
|
-
function applyCollectionCall(target, method, args) {
|
|
69
|
-
if (!isSupportedCollectionTarget(target)) throw new TypeError(`Cannot call collection method "${method}" because the graph path is not an Array, Map, Set, or Date.`);
|
|
70
|
-
if (!isSupportedCollectionMethod(method)) throw new TypeError(`Unsupported graph collection method "${method}".`);
|
|
71
|
-
const callable = target[method];
|
|
72
|
-
if (typeof callable !== "function") throw new TypeError(`Unsupported graph collection method "${method}".`);
|
|
73
|
-
return Reflect.apply(callable, target, [...args]);
|
|
74
|
-
}
|
|
75
|
-
function collectionCallMutated(method, result, beforeMutation) {
|
|
76
|
-
if (method === "delete") return result === true;
|
|
77
|
-
if (method === "clear") return beforeMutation?.type === "size" && beforeMutation.value > 0;
|
|
78
|
-
if (method === "pop") return beforeMutation?.type === "size" && beforeMutation.value > 0;
|
|
79
|
-
if (method === "shift") return beforeMutation?.type === "size" && beforeMutation.value > 0;
|
|
80
|
-
if (method === "push" || method === "unshift") return beforeMutation?.type !== "size" || result !== beforeMutation.value;
|
|
81
|
-
if (arrayContentMutationMethod(method)) return beforeMutation?.type !== "array" || !arraySlotsEqual(beforeMutation.slots, beforeMutation.target);
|
|
82
|
-
if (method === "add") return beforeMutation?.type !== "set-add" || !beforeMutation.hadValue;
|
|
83
|
-
if (method === "set") return beforeMutation?.type !== "map-set" || !beforeMutation.hadKey || beforeMutation.valueChanged;
|
|
84
|
-
if (dateMutationMethod(method)) return beforeMutation?.type !== "date" || !Object.is(beforeMutation.time, beforeMutation.target.getTime());
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
function collectionMutationSnapshot(target, method, args) {
|
|
88
|
-
if (method === "add" && target instanceof Set) return {
|
|
89
|
-
type: "set-add",
|
|
90
|
-
hadValue: target.has(args[0])
|
|
91
|
-
};
|
|
92
|
-
if (method === "set" && target instanceof Map) {
|
|
93
|
-
const hadKey = target.has(args[0]);
|
|
94
|
-
return {
|
|
95
|
-
type: "map-set",
|
|
96
|
-
hadKey,
|
|
97
|
-
valueChanged: !hadKey || !Object.is(target.get(args[0]), args[1])
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
if (target instanceof Date && dateMutationMethod(method)) return {
|
|
101
|
-
type: "date",
|
|
102
|
-
time: target.getTime(),
|
|
103
|
-
target
|
|
104
|
-
};
|
|
105
|
-
if (arrayContentMutationMethod(method) && Array.isArray(target)) return {
|
|
106
|
-
type: "array",
|
|
107
|
-
slots: arraySlotSnapshot(target),
|
|
108
|
-
target
|
|
109
|
-
};
|
|
110
|
-
if (Array.isArray(target)) return {
|
|
111
|
-
type: "size",
|
|
112
|
-
value: target.length
|
|
113
|
-
};
|
|
114
|
-
if (target instanceof Map || target instanceof Set) return {
|
|
115
|
-
type: "size",
|
|
116
|
-
value: target.size
|
|
117
|
-
};
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
function isSupportedCollectionMethod(name) {
|
|
121
|
-
return name === "add" || name === "clear" || name === "copyWithin" || name === "delete" || name === "fill" || name === "pop" || name === "push" || name === "reverse" || name === "set" || name === "shift" || name === "sort" || name === "splice" || name === "unshift" || dateMutationMethod(name);
|
|
122
|
-
}
|
|
123
|
-
function isSupportedCollectionTarget(target) {
|
|
124
|
-
return Array.isArray(target) || target instanceof Map || target instanceof Set || target instanceof Date;
|
|
125
|
-
}
|
|
126
|
-
function arrayContentMutationMethod(method) {
|
|
127
|
-
return method === "copyWithin" || method === "fill" || method === "reverse" || method === "sort" || method === "splice";
|
|
128
|
-
}
|
|
129
|
-
function dateMutationMethod(method) {
|
|
130
|
-
return method === "setDate" || method === "setFullYear" || method === "setHours" || method === "setMilliseconds" || method === "setMinutes" || method === "setMonth" || method === "setSeconds" || method === "setTime" || method === "setUTCDate" || method === "setUTCFullYear" || method === "setUTCHours" || method === "setUTCMilliseconds" || method === "setUTCMinutes" || method === "setUTCMonth" || method === "setUTCSeconds" || method === "setYear";
|
|
131
|
-
}
|
|
132
|
-
function arraySlotSnapshot(target) {
|
|
133
|
-
return Array.from({ length: target.length }, (_, index) => ({
|
|
134
|
-
exists: Object.prototype.hasOwnProperty.call(target, index),
|
|
135
|
-
value: target[index]
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
function arraySlotsEqual(before, after) {
|
|
139
|
-
if (!Array.isArray(after)) return false;
|
|
140
|
-
if (before.length !== after.length) return false;
|
|
141
|
-
return before.every((slot, index) => {
|
|
142
|
-
const exists = Object.prototype.hasOwnProperty.call(after, index);
|
|
143
|
-
return slot.exists === exists && (!slot.exists || Object.is(slot.value, after[index]));
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
//#endregion
|
|
147
|
-
//#region src/graph-reconcile.ts
|
|
148
|
-
const WHOLE_NODE = Object.freeze([Object.freeze([])]);
|
|
149
|
-
/**
|
|
150
|
-
* Compares a derived value with the previous value of the same graph node and
|
|
151
|
-
* reports the graph paths that changed. It never mutates either value: the
|
|
152
|
-
* caller replaces the node's value pointer and invalidates the reported paths.
|
|
153
|
-
*
|
|
154
|
-
* Rules (specs/framework/03-state-graph.md "Derived reconciliation"):
|
|
155
|
-
* - identical references are unchanged, unless a write touched that object in
|
|
156
|
-
* this flush, in which case the touched remainders are reported; the same
|
|
157
|
-
* holds when only the previous value held the written object, because an
|
|
158
|
-
* in-place write left that baseline already carrying the new field;
|
|
159
|
-
* - primitives, functions, `Map`, `Set`, `Date` and class instances are leaves;
|
|
160
|
-
* - plain objects reconcile field by field;
|
|
161
|
-
* - arrays reconcile by declared key, otherwise by element identity, and a
|
|
162
|
-
* length or structural mismatch reports the array's own path;
|
|
163
|
-
* - a declared key that is missing or duplicated on either side falls back to
|
|
164
|
-
* structural reconciliation for that array, which reports the array's own
|
|
165
|
-
* path: keys are never matched partially and an index is never identity;
|
|
166
|
-
* - a missing baseline (first value, or a value whose write-touched record was
|
|
167
|
-
* already cleared) reports the whole node.
|
|
168
|
-
*
|
|
169
|
-
* `identicalContainers: 'unknown'` is for a caller that has no write-touched
|
|
170
|
-
* record for the flush that produced the new value, so identity below the root
|
|
171
|
-
* proves nothing: every `Object.is`-identical plain object or array met below
|
|
172
|
-
* the root is reported at its own path and is not walked into. An identical
|
|
173
|
-
* root still reports nothing — nothing narrower is known about it, and the
|
|
174
|
-
* caller decides what a wholly identical value means — and identical
|
|
175
|
-
* primitives and other leaves stay unchanged. The default, `'unchanged'`,
|
|
176
|
-
* trusts identity as above.
|
|
177
|
-
*/
|
|
178
|
-
function diffDerivedValue(input) {
|
|
179
|
-
if (input.baselineStale) return WHOLE_NODE;
|
|
180
|
-
if (input.previous === void 0 && input.next !== void 0) return WHOLE_NODE;
|
|
181
|
-
const changed = [];
|
|
182
|
-
const active = /* @__PURE__ */ new Map();
|
|
183
|
-
function emitTouched(value, path) {
|
|
184
|
-
if (!input.touched || typeof value !== "object" || value === null) return;
|
|
185
|
-
const remainders = input.touched.get(value);
|
|
186
|
-
if (!remainders) return;
|
|
187
|
-
for (const remaining of remainders) {
|
|
188
|
-
const touchedPath = [...path, ...remaining];
|
|
189
|
-
if (changed.some((entry) => samePath(entry, touchedPath))) continue;
|
|
190
|
-
changed.push(touchedPath);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* What an identical reference met at `path` reports. With a write-touched
|
|
195
|
-
* record identity means unchanged apart from the recorded remainders; in
|
|
196
|
-
* `'unknown'` mode there is no such record, so an identical container below
|
|
197
|
-
* the root is reported whole rather than trusted or walked into.
|
|
198
|
-
*/
|
|
199
|
-
function reportIdentical(value, path) {
|
|
200
|
-
if (input.identicalContainers === "unknown" && path.length > 0 && isDiffableContainer(value)) {
|
|
201
|
-
changed.push(path);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
emitTouched(value, path);
|
|
205
|
-
}
|
|
206
|
-
function keyedFor(path) {
|
|
207
|
-
return input.keyed?.find((entry) => samePath(entry.path, path));
|
|
208
|
-
}
|
|
209
|
-
function walkArray(previous, next, path) {
|
|
210
|
-
const mark = changed.length;
|
|
211
|
-
const fallBackToStructural = () => {
|
|
212
|
-
changed.length = mark;
|
|
213
|
-
changed.push(path);
|
|
214
|
-
};
|
|
215
|
-
if (previous.length !== next.length) {
|
|
216
|
-
fallBackToStructural();
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const keyed = keyedFor(path);
|
|
220
|
-
if (keyed) {
|
|
221
|
-
const fault = keyFault(previous, keyed) ?? keyFault(next, keyed);
|
|
222
|
-
if (fault) {
|
|
223
|
-
reportKeyFault(path, keyed, fault);
|
|
224
|
-
fallBackToStructural();
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
for (let index = 0; index < next.length; index++) {
|
|
229
|
-
const previousItem = previous[index];
|
|
230
|
-
const nextItem = next[index];
|
|
231
|
-
const itemPath = [...path, String(index)];
|
|
232
|
-
if (Object.is(previousItem, nextItem)) {
|
|
233
|
-
reportIdentical(nextItem, itemPath);
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
if (keyed && isDiffableContainer(previousItem) && isDiffableContainer(nextItem)) {
|
|
237
|
-
const previousKey = readPath(previousItem, keyed.keyPath);
|
|
238
|
-
const nextKey = readPath(nextItem, keyed.keyPath);
|
|
239
|
-
if (!Object.is(previousKey, nextKey)) {
|
|
240
|
-
fallBackToStructural();
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
walk(previousItem, nextItem, itemPath);
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
changed.push(itemPath);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function walkObject(previous, next, path) {
|
|
250
|
-
for (const key of new Set([...Object.keys(previous), ...Object.keys(next)])) {
|
|
251
|
-
const keyPath = [...path, key];
|
|
252
|
-
const inPrevious = Object.prototype.hasOwnProperty.call(previous, key);
|
|
253
|
-
const inNext = Object.prototype.hasOwnProperty.call(next, key);
|
|
254
|
-
if (!inPrevious || !inNext) {
|
|
255
|
-
changed.push(keyPath);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
walk(previous[key], next[key], keyPath);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function walk(previous, next, path) {
|
|
262
|
-
if (Object.is(previous, next)) {
|
|
263
|
-
reportIdentical(next, path);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (!isDiffableContainer(previous) || !isDiffableContainer(next)) {
|
|
267
|
-
changed.push(path);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const previousIsArray = Array.isArray(previous);
|
|
271
|
-
if (previousIsArray !== Array.isArray(next)) {
|
|
272
|
-
changed.push(path);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
let pairedWithPrevious = active.get(previous);
|
|
276
|
-
if (pairedWithPrevious?.has(next)) return;
|
|
277
|
-
if (!pairedWithPrevious) {
|
|
278
|
-
pairedWithPrevious = /* @__PURE__ */ new Set();
|
|
279
|
-
active.set(previous, pairedWithPrevious);
|
|
280
|
-
}
|
|
281
|
-
pairedWithPrevious.add(next);
|
|
282
|
-
try {
|
|
283
|
-
if (previousIsArray) walkArray(previous, next, path);
|
|
284
|
-
else walkObject(previous, next, path);
|
|
285
|
-
} finally {
|
|
286
|
-
pairedWithPrevious.delete(next);
|
|
287
|
-
if (pairedWithPrevious.size === 0) active.delete(previous);
|
|
288
|
-
}
|
|
289
|
-
emitTouched(previous, path);
|
|
290
|
-
}
|
|
291
|
-
walk(input.previous, input.next, []);
|
|
292
|
-
return changed;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Reads the declared key of every element and reports the first element that
|
|
296
|
-
* has no key or repeats one already used in the same array. Reads only; it
|
|
297
|
-
* never touches the elements it inspects.
|
|
298
|
-
*/
|
|
299
|
-
function keyFault(items, keyed) {
|
|
300
|
-
const seen = /* @__PURE__ */ new Set();
|
|
301
|
-
for (let index = 0; index < items.length; index++) {
|
|
302
|
-
const key = readPath(items[index], keyed.keyPath);
|
|
303
|
-
if (key === void 0) return {
|
|
304
|
-
reason: "missing",
|
|
305
|
-
index,
|
|
306
|
-
key
|
|
307
|
-
};
|
|
308
|
-
if (seen.has(key)) return {
|
|
309
|
-
reason: "duplicate",
|
|
310
|
-
index,
|
|
311
|
-
key
|
|
312
|
-
};
|
|
313
|
-
seen.add(key);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Names the array and the offending key in debug-enabled builds. Keyed
|
|
318
|
-
* reconciliation degrading to structural reconciliation is silent in
|
|
319
|
-
* production — the result is still correct, only coarser.
|
|
320
|
-
*/
|
|
321
|
-
function reportKeyFault(path, keyed, fault) {
|
|
322
|
-
if (typeof __MARKLESS_DEBUG_ENABLED__ === "undefined" || !__MARKLESS_DEBUG_ENABLED__) return;
|
|
323
|
-
const array = path.length === 0 ? "the derived value" : `derived path ${path.join(".")}`;
|
|
324
|
-
const keyPath = keyed.keyPath.length === 0 ? "the element itself" : keyed.keyPath.join(".");
|
|
325
|
-
const cause = fault.reason === "missing" ? `element ${fault.index} has no key at ${keyPath}` : `key ${describeKey(fault.key)} at ${keyPath} is used by more than one element (element ${fault.index})`;
|
|
326
|
-
console.warn(`markless: reconciling ${array} by key is not possible because ${cause}; falling back to structural reconciliation for that array.`);
|
|
327
|
-
}
|
|
328
|
-
function describeKey(key) {
|
|
329
|
-
return typeof key === "string" ? JSON.stringify(key) : String(key);
|
|
330
|
-
}
|
|
331
|
-
/** Plain objects and arrays are the only values reconciliation walks into. */
|
|
332
|
-
function isDiffableContainer(value) {
|
|
333
|
-
if (typeof value !== "object" || value === null) return false;
|
|
334
|
-
if (Array.isArray(value)) return true;
|
|
335
|
-
const prototype = Object.getPrototypeOf(value);
|
|
336
|
-
return prototype === Object.prototype || prototype === null;
|
|
337
|
-
}
|
|
338
|
-
function samePath(a, b) {
|
|
339
|
-
return a.length === b.length && a.every((segment, index) => b[index] === segment);
|
|
340
|
-
}
|
|
341
|
-
//#endregion
|
|
342
|
-
//#region src/graph-async.ts
|
|
343
|
-
const ASYNC_SNAPSHOT_META_KEYS = new Set([
|
|
344
|
-
"status",
|
|
345
|
-
"version",
|
|
346
|
-
"key",
|
|
347
|
-
"error",
|
|
348
|
-
"value"
|
|
349
|
-
]);
|
|
350
|
-
function createRuntimeAsyncComputedNodes(asyncComputedInput) {
|
|
351
|
-
const asyncComputedNodes = /* @__PURE__ */ new Map();
|
|
352
|
-
for (const asyncComputed of asyncComputedInput ?? []) {
|
|
353
|
-
const initialSnapshot = asyncComputed.initialSnapshot ?? {
|
|
354
|
-
status: "idle",
|
|
355
|
-
version: 0
|
|
356
|
-
};
|
|
357
|
-
asyncComputedNodes.set(asyncComputed.graphNodeId, {
|
|
358
|
-
...asyncComputed,
|
|
359
|
-
gate: initialSnapshot.status === "pending" ? 1 : 0,
|
|
360
|
-
snapshot: initialSnapshot,
|
|
361
|
-
version: initialSnapshot.version
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
return asyncComputedNodes;
|
|
365
|
-
}
|
|
366
|
-
function readAsyncComputedNode(node, path, demand) {
|
|
367
|
-
demand();
|
|
368
|
-
const head = path[0];
|
|
369
|
-
if (head === void 0 || ASYNC_SNAPSHOT_META_KEYS.has(head)) return readPath(node.snapshot, path);
|
|
370
|
-
return readPath(node.snapshot.value, path);
|
|
371
|
-
}
|
|
372
|
-
function demandAsyncComputed(input) {
|
|
373
|
-
if (input.node.gate !== 1 && input.node.snapshot.status !== "idle") return;
|
|
374
|
-
advanceAsyncComputed(input);
|
|
375
|
-
}
|
|
376
|
-
function invalidateAsyncComputed(input) {
|
|
377
|
-
if (input.node.snapshot.status === "idle") return;
|
|
378
|
-
advanceAsyncComputed(input);
|
|
379
|
-
}
|
|
380
|
-
function advanceAsyncComputed(input) {
|
|
381
|
-
const visited = new Set([input.node.graphNodeId]);
|
|
382
|
-
let blocked;
|
|
383
|
-
const visit = (graphNodeId) => {
|
|
384
|
-
if (visited.has(graphNodeId)) return;
|
|
385
|
-
visited.add(graphNodeId);
|
|
386
|
-
const dependency = input.asyncComputedNodes.get(graphNodeId);
|
|
387
|
-
if (dependency) {
|
|
388
|
-
if (dependency.snapshot.status === "idle" || dependency.snapshot.status === "pending") {
|
|
389
|
-
blocked = dependency;
|
|
390
|
-
input.demandAsyncComputed(dependency);
|
|
391
|
-
} else if (!blocked && dependency.snapshot.status === "rejected") blocked = dependency;
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
for (const dependency of input.computedNodes.get(graphNodeId)?.dependencies ?? []) visit(dependency.graphNodeId);
|
|
395
|
-
};
|
|
396
|
-
for (const dependency of input.node.dependencies) visit(dependency.graphNodeId);
|
|
397
|
-
if (blocked) {
|
|
398
|
-
commitDependencyBlock(input, blocked.snapshot.status === "rejected" ? "rejected" : "pending", blocked.snapshot.error);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
const nextKey = input.node.key(input.readGraph);
|
|
402
|
-
if (!input.node.gate && input.node.snapshot.status !== "idle" && Object.is(input.node.snapshot.key, nextKey)) return;
|
|
403
|
-
startAsyncComputed({
|
|
404
|
-
...input,
|
|
405
|
-
key: nextKey
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
function commitDependencyBlock(input, status, error) {
|
|
409
|
-
if (input.node.gate === 2 && (status === "pending" || input.node.snapshot.status === "rejected" && Object.is(input.node.snapshot.error, error))) return;
|
|
410
|
-
input.node.controller?.abort();
|
|
411
|
-
input.node.controller = void 0;
|
|
412
|
-
input.node.gate = 2;
|
|
413
|
-
const version = ++input.node.version;
|
|
414
|
-
const key = input.node.snapshot.key;
|
|
415
|
-
publish(input, status === "pending" ? {
|
|
416
|
-
status,
|
|
417
|
-
version,
|
|
418
|
-
key,
|
|
419
|
-
value: input.node.snapshot.value
|
|
420
|
-
} : {
|
|
421
|
-
status,
|
|
422
|
-
version,
|
|
423
|
-
key,
|
|
424
|
-
error
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
function startAsyncComputed(input) {
|
|
428
|
-
input.node.controller?.abort();
|
|
429
|
-
input.node.gate = 0;
|
|
430
|
-
const controller = new AbortController();
|
|
431
|
-
const version = ++input.node.version;
|
|
432
|
-
input.node.controller = controller;
|
|
433
|
-
publish(input, {
|
|
434
|
-
status: "pending",
|
|
435
|
-
version,
|
|
436
|
-
key: input.key,
|
|
437
|
-
value: input.node.snapshot.value
|
|
438
|
-
});
|
|
439
|
-
const commit = (snapshot) => {
|
|
440
|
-
if (input.node.version !== version) return;
|
|
441
|
-
publish(input, snapshot);
|
|
442
|
-
};
|
|
443
|
-
const commitRejected = (error) => commit({
|
|
444
|
-
status: "rejected",
|
|
445
|
-
version,
|
|
446
|
-
key: input.key,
|
|
447
|
-
error
|
|
448
|
-
});
|
|
449
|
-
try {
|
|
450
|
-
Promise.resolve(input.node.run({
|
|
451
|
-
key: input.key,
|
|
452
|
-
signal: controller.signal,
|
|
453
|
-
read: input.readGraph
|
|
454
|
-
})).then((value) => commit({
|
|
455
|
-
status: "fulfilled",
|
|
456
|
-
version,
|
|
457
|
-
key: input.key,
|
|
458
|
-
value
|
|
459
|
-
}), commitRejected);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
commitRejected(error);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
function publish(input, snapshot) {
|
|
465
|
-
const previous = input.node.snapshot;
|
|
466
|
-
input.node.snapshot = snapshot;
|
|
467
|
-
for (const path of asyncCommitPaths(input.node, previous, snapshot)) input.markDirtyPath(input.node.graphNodeId, path);
|
|
468
|
-
input.scheduleFlush();
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* The graph paths a snapshot commit changed. Snapshot metadata is addressed as
|
|
472
|
-
* `status`/`version`/`key`/`error`; the fulfilled value is addressed both as
|
|
473
|
-
* `value.<path>` and, for compiled reads, as the bare `<path>` (see
|
|
474
|
-
* `readAsyncComputedNode`), so a changed value path is reported in both
|
|
475
|
-
* coordinate systems.
|
|
476
|
-
*
|
|
477
|
-
* A value commit compares structurally only. Unlike a sync computed, an async
|
|
478
|
-
* snapshot can land long after the flush that wrote the state it embeds, so the
|
|
479
|
-
* per-flush write-touched record is already cleared by the time it publishes.
|
|
480
|
-
* Identity below the root therefore proves nothing here: a runner that returns
|
|
481
|
-
* live state objects publishes the very rows a later write mutated in place,
|
|
482
|
-
* and treating them as unchanged would silently withhold that write from the
|
|
483
|
-
* subscribers of those paths. The diff runs in `identicalContainers: 'unknown'`
|
|
484
|
-
* mode, which reports each identical nested container at its path; a value
|
|
485
|
-
* rebuilt from fresh objects still reconciles field by field, and an identical
|
|
486
|
-
* root is handled by the whole-node rule below.
|
|
487
|
-
*/
|
|
488
|
-
function asyncCommitPaths(node, previous, next) {
|
|
489
|
-
const paths = [];
|
|
490
|
-
if (previous.status !== next.status) paths.push(["status"]);
|
|
491
|
-
if (previous.version !== next.version) paths.push(["version"]);
|
|
492
|
-
if (!Object.is(snapshotField(previous, "key"), snapshotField(next, "key"))) paths.push(["key"]);
|
|
493
|
-
if (!Object.is(snapshotField(previous, "error"), snapshotField(next, "error"))) paths.push(["error"]);
|
|
494
|
-
if (next.status === "pending") return paths;
|
|
495
|
-
const previousValue = snapshotField(previous, "value");
|
|
496
|
-
const nextValue = snapshotField(next, "value");
|
|
497
|
-
if (!Object.is(previousValue, nextValue) && isDiffableContainer(previousValue) && isDiffableContainer(nextValue)) {
|
|
498
|
-
const changed = diffDerivedValue({
|
|
499
|
-
previous: previousValue,
|
|
500
|
-
next: nextValue,
|
|
501
|
-
keyed: node.reconcile?.keyed,
|
|
502
|
-
identicalContainers: "unknown"
|
|
503
|
-
});
|
|
504
|
-
if (changed.every((path) => path.length > 0)) {
|
|
505
|
-
for (const path of changed) {
|
|
506
|
-
paths.push(["value", ...path]);
|
|
507
|
-
if (!ASYNC_SNAPSHOT_META_KEYS.has(path[0])) paths.push([...path]);
|
|
508
|
-
}
|
|
509
|
-
return paths;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
paths.push([]);
|
|
513
|
-
return paths;
|
|
514
|
-
}
|
|
515
|
-
function snapshotField(snapshot, field) {
|
|
516
|
-
return snapshot[field];
|
|
517
|
-
}
|
|
518
|
-
//#endregion
|
|
519
|
-
//#region src/graph-computed.ts
|
|
520
|
-
function createRuntimeComputedNodes(computedInput) {
|
|
521
|
-
const computedNodes = /* @__PURE__ */ new Map();
|
|
522
|
-
for (const computed of computedInput ?? []) computedNodes.set(computed.graphNodeId, {
|
|
523
|
-
...computed,
|
|
524
|
-
dirty: true,
|
|
525
|
-
invalidated: false,
|
|
526
|
-
baselineStale: false,
|
|
527
|
-
value: void 0
|
|
528
|
-
});
|
|
529
|
-
return computedNodes;
|
|
530
|
-
}
|
|
531
|
-
function readComputedNode(computed, readGraph, path, reconcile) {
|
|
532
|
-
const { compute } = computed;
|
|
533
|
-
if (computed.dirty && compute) {
|
|
534
|
-
const previous = computed.value;
|
|
535
|
-
const next = compute(readGraph);
|
|
536
|
-
const changed = computed.invalidated && reconcile ? diffDerivedValue({
|
|
537
|
-
previous,
|
|
538
|
-
next,
|
|
539
|
-
keyed: computed.reconcile?.keyed,
|
|
540
|
-
touched: reconcile.touched,
|
|
541
|
-
baselineStale: computed.baselineStale
|
|
542
|
-
}) : [];
|
|
543
|
-
computed.value = next;
|
|
544
|
-
computed.dirty = false;
|
|
545
|
-
computed.invalidated = false;
|
|
546
|
-
computed.baselineStale = false;
|
|
547
|
-
if (reconcile && changed.length > 0) {
|
|
548
|
-
for (const changedPath of changed) reconcile.dirtyPaths.push({
|
|
549
|
-
graphNodeId: computed.graphNodeId,
|
|
550
|
-
path: changedPath
|
|
551
|
-
});
|
|
552
|
-
reconcile.scheduleFlush();
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return readPath(computed.value, path);
|
|
556
|
-
}
|
|
557
|
-
function markComputedDirty(input) {
|
|
558
|
-
if (input.visited.has(input.graphNodeId)) return;
|
|
559
|
-
input.visited.add(input.graphNodeId);
|
|
560
|
-
const computed = input.computedNodes.get(input.graphNodeId);
|
|
561
|
-
if (!computed?.compute) return;
|
|
562
|
-
computed.dirty = true;
|
|
563
|
-
computed.invalidated = true;
|
|
564
|
-
input.dirtyComputedIds.add(input.graphNodeId);
|
|
565
|
-
for (const dependent of input.computedNodes.values()) if (dependent.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId) && dependent.compute) markComputedDirty({
|
|
566
|
-
...input,
|
|
567
|
-
graphNodeId: dependent.graphNodeId
|
|
568
|
-
});
|
|
569
|
-
for (const dependent of input.asyncComputedNodes.values()) if (dependent.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId)) input.invalidateAsyncComputed(dependent);
|
|
570
|
-
}
|
|
571
|
-
function markDirtyComputedDependencies(input) {
|
|
572
|
-
for (const computed of input.computedNodes.values()) if (computed.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId && pathsIntersect(input.path, dependency.path ?? [])) && computed.compute) markComputedDirty({
|
|
573
|
-
...input,
|
|
574
|
-
graphNodeId: computed.graphNodeId,
|
|
575
|
-
visited: /* @__PURE__ */ new Set()
|
|
576
|
-
});
|
|
577
|
-
for (const asyncComputed of input.asyncComputedNodes.values()) if (asyncComputed.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId && pathsIntersect(input.path, dependency.path ?? []))) input.invalidateAsyncComputed(asyncComputed);
|
|
578
|
-
}
|
|
579
|
-
//#endregion
|
|
580
|
-
//#region src/graph-scheduler.ts
|
|
581
|
-
function appendJournalResult(journal, result) {
|
|
582
|
-
if (!result) return;
|
|
583
|
-
if (isDomJournalEntryArray(result)) {
|
|
584
|
-
journal.push(...result);
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
journal.push(result);
|
|
588
|
-
}
|
|
589
|
-
function scheduleMicrotask(callback) {
|
|
590
|
-
if (typeof queueMicrotask === "function") {
|
|
591
|
-
queueMicrotask(callback);
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
Promise.resolve().then(callback);
|
|
595
|
-
}
|
|
596
|
-
function isDomJournalEntryArray(result) {
|
|
597
|
-
return Array.isArray(result);
|
|
598
|
-
}
|
|
599
|
-
//#endregion
|
|
600
|
-
//#region src/graph-shared.ts
|
|
601
|
-
function createSharedGraphPlane(input) {
|
|
602
|
-
const sharedDefinitions = /* @__PURE__ */ new Map();
|
|
603
|
-
const sharedPatches = [];
|
|
604
|
-
for (const definition of input.sharedDefinitionsInput ?? []) sharedDefinitions.set(definition.id, definition);
|
|
605
|
-
const resolveSharedGraphPath = (definitionId, propertyName, path) => {
|
|
606
|
-
const definition = sharedDefinitions.get(definitionId);
|
|
607
|
-
if (!definition) return void 0;
|
|
608
|
-
const property = findLastSharedReturnProperty(definition.returnProperties, propertyName);
|
|
609
|
-
if (!property || property.kind !== "graph") return void 0;
|
|
610
|
-
return {
|
|
611
|
-
definition,
|
|
612
|
-
graphNodeId: property.graphNodeId,
|
|
613
|
-
graphPath: [...property.path, ...path],
|
|
614
|
-
exposedPath: [property.name, ...path]
|
|
615
|
-
};
|
|
616
|
-
};
|
|
617
|
-
const setSharedDefinitionVersion = (definition, version) => {
|
|
618
|
-
const nextDefinition = {
|
|
619
|
-
...definition,
|
|
620
|
-
version
|
|
621
|
-
};
|
|
622
|
-
sharedDefinitions.set(definition.id, nextDefinition);
|
|
623
|
-
return nextDefinition;
|
|
624
|
-
};
|
|
625
|
-
const nextSharedDefinitionVersion = (definition) => setSharedDefinitionVersion(definition, definition.version + 1);
|
|
626
|
-
const applySharedPatch = (patch) => {
|
|
627
|
-
const definition = sharedDefinitions.get(patch.id);
|
|
628
|
-
if (!definition) return false;
|
|
629
|
-
if (patch.version <= definition.version) return false;
|
|
630
|
-
if (patch.scope && definition.scope && patch.scope !== definition.scope) return false;
|
|
631
|
-
if (patch.patch.length === 0) return false;
|
|
632
|
-
const resolvedPatches = [];
|
|
633
|
-
for (const [operation, exposedPath, value] of patch.patch) {
|
|
634
|
-
if (operation !== "set") return false;
|
|
635
|
-
const [propertyName, ...path] = exposedPath;
|
|
636
|
-
if (!propertyName) return false;
|
|
637
|
-
const resolved = resolveSharedGraphPath(patch.id, propertyName, path);
|
|
638
|
-
if (!resolved) return false;
|
|
639
|
-
resolvedPatches.push({
|
|
640
|
-
graphNodeId: resolved.graphNodeId,
|
|
641
|
-
graphPath: resolved.graphPath,
|
|
642
|
-
value
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
for (const resolved of resolvedPatches) {
|
|
646
|
-
const current = input.cells.get(resolved.graphNodeId);
|
|
647
|
-
input.cells.set(resolved.graphNodeId, writePath(current, resolved.graphPath, resolved.value));
|
|
648
|
-
input.markDirtyPath(resolved.graphNodeId, dirtyPathForGraphWrite(current, resolved.graphPath));
|
|
649
|
-
}
|
|
650
|
-
setSharedDefinitionVersion(definition, patch.version);
|
|
651
|
-
input.scheduleFlush();
|
|
652
|
-
return true;
|
|
653
|
-
};
|
|
654
|
-
return {
|
|
655
|
-
readShared(definitionId, propertyName, path = []) {
|
|
656
|
-
const resolved = resolveSharedGraphPath(definitionId, propertyName, path);
|
|
657
|
-
if (!resolved) return void 0;
|
|
658
|
-
return input.readGraph(resolved.graphNodeId, resolved.graphPath);
|
|
659
|
-
},
|
|
660
|
-
writeShared(write) {
|
|
661
|
-
const target = resolveSharedGraphPath(write.definitionId, write.propertyName, write.path ?? []);
|
|
662
|
-
if (!target) return false;
|
|
663
|
-
const current = input.cells.get(target.graphNodeId);
|
|
664
|
-
input.cells.set(target.graphNodeId, writePath(current, target.graphPath, write.value));
|
|
665
|
-
const nextDefinition = nextSharedDefinitionVersion(target.definition);
|
|
666
|
-
sharedPatches.push({
|
|
667
|
-
id: nextDefinition.id,
|
|
668
|
-
...nextDefinition.scope ? { scope: nextDefinition.scope } : {},
|
|
669
|
-
version: nextDefinition.version,
|
|
670
|
-
patch: [[
|
|
671
|
-
"set",
|
|
672
|
-
target.exposedPath,
|
|
673
|
-
write.value
|
|
674
|
-
]]
|
|
675
|
-
});
|
|
676
|
-
input.markDirtyPath(target.graphNodeId, dirtyPathForGraphWrite(current, target.graphPath));
|
|
677
|
-
input.scheduleFlush();
|
|
678
|
-
return true;
|
|
679
|
-
},
|
|
680
|
-
getSharedDefinition(definitionId) {
|
|
681
|
-
return sharedDefinitions.get(definitionId);
|
|
682
|
-
},
|
|
683
|
-
listSharedDefinitions() {
|
|
684
|
-
return [...sharedDefinitions.values()];
|
|
685
|
-
},
|
|
686
|
-
takeSharedPatches() {
|
|
687
|
-
return sharedPatches.splice(0);
|
|
688
|
-
},
|
|
689
|
-
applySharedPatch
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
//#endregion
|
|
693
|
-
//#region src/graph.ts
|
|
694
|
-
function createRuntimeGraph(input) {
|
|
695
|
-
const cells = /* @__PURE__ */ new Map();
|
|
696
|
-
const readInitializers = /* @__PURE__ */ new Map();
|
|
697
|
-
const computedNodes = createRuntimeComputedNodes(input.computed);
|
|
698
|
-
const asyncComputedNodes = createRuntimeAsyncComputedNodes(input.asyncComputed);
|
|
699
|
-
const subscriptions = [];
|
|
700
|
-
const journalListeners = [];
|
|
701
|
-
const dirtyPaths = [];
|
|
702
|
-
const dirtyComputedIds = /* @__PURE__ */ new Set();
|
|
703
|
-
const writeTouched = /* @__PURE__ */ new Map();
|
|
704
|
-
const journal = [];
|
|
705
|
-
let flushScheduled = false;
|
|
706
|
-
let flushing = false;
|
|
707
|
-
let activeFlush;
|
|
708
|
-
for (const cell of input.cells) {
|
|
709
|
-
cells.set(cell.graphNodeId, cell.value);
|
|
710
|
-
if (cell.readInitializer) readInitializers.set(cell.graphNodeId, cell.readInitializer);
|
|
711
|
-
}
|
|
712
|
-
const reconcileContext = {
|
|
713
|
-
dirtyPaths,
|
|
714
|
-
touched: writeTouched,
|
|
715
|
-
scheduleFlush: () => {
|
|
716
|
-
scheduleFlush();
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
const readGraph = (graphNodeId, path = []) => {
|
|
720
|
-
const computed = computedNodes.get(graphNodeId);
|
|
721
|
-
if (computed?.compute) return readComputedNode(computed, readGraph, path, reconcileContext);
|
|
722
|
-
const asyncComputed = asyncComputedNodes.get(graphNodeId);
|
|
723
|
-
if (asyncComputed) return readAsyncComputedNode(asyncComputed, path, () => demandAsyncComputed$1(asyncComputed));
|
|
724
|
-
const initialize = readInitializers.get(graphNodeId);
|
|
725
|
-
if (initialize) {
|
|
726
|
-
readInitializers.delete(graphNodeId);
|
|
727
|
-
const current = cells.get(graphNodeId);
|
|
728
|
-
try {
|
|
729
|
-
const value = initialize();
|
|
730
|
-
if (!Object.is(current, value)) {
|
|
731
|
-
cells.set(graphNodeId, value);
|
|
732
|
-
markDirtyPath(graphNodeId, dirtyPathForGraphWrite(current, []));
|
|
733
|
-
scheduleFlush();
|
|
734
|
-
}
|
|
735
|
-
} catch {}
|
|
736
|
-
}
|
|
737
|
-
return readPath(cells.get(graphNodeId), path);
|
|
738
|
-
};
|
|
739
|
-
const recordWriteTouched = (graphNodeId, path) => {
|
|
740
|
-
if (computedNodes.has(graphNodeId)) return;
|
|
741
|
-
let container = cells.get(graphNodeId);
|
|
742
|
-
for (let depth = 0; depth <= path.length; depth++) {
|
|
743
|
-
if (typeof container === "object" && container !== null) {
|
|
744
|
-
const remainders = writeTouched.get(container) ?? [];
|
|
745
|
-
const remaining = path.slice(depth);
|
|
746
|
-
if (!remainders.some((entry) => samePath(entry, remaining))) {
|
|
747
|
-
remainders.push(remaining);
|
|
748
|
-
writeTouched.set(container, remainders);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
if (depth === path.length) break;
|
|
752
|
-
container = readPath(container, [path[depth]]);
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
const markDirtyPath = (graphNodeId, path) => {
|
|
756
|
-
dirtyPaths.push({
|
|
757
|
-
graphNodeId,
|
|
758
|
-
path
|
|
759
|
-
});
|
|
760
|
-
recordWriteTouched(graphNodeId, path);
|
|
761
|
-
markDirtyComputedDependencies({
|
|
762
|
-
graphNodeId,
|
|
763
|
-
path,
|
|
764
|
-
computedNodes,
|
|
765
|
-
asyncComputedNodes,
|
|
766
|
-
dirtyComputedIds,
|
|
767
|
-
invalidateAsyncComputed: invalidateAsyncComputed$1
|
|
768
|
-
});
|
|
769
|
-
};
|
|
770
|
-
const scheduleFlush = () => {
|
|
771
|
-
if (flushScheduled || flushing) return;
|
|
772
|
-
flushScheduled = true;
|
|
773
|
-
scheduleMicrotask(() => {
|
|
774
|
-
flush();
|
|
775
|
-
});
|
|
776
|
-
};
|
|
777
|
-
const asyncComputedInput = () => ({
|
|
778
|
-
computedNodes,
|
|
779
|
-
asyncComputedNodes,
|
|
780
|
-
demandAsyncComputed: demandAsyncComputed$1,
|
|
781
|
-
readGraph,
|
|
782
|
-
markDirtyPath,
|
|
783
|
-
scheduleFlush
|
|
784
|
-
});
|
|
785
|
-
const demandAsyncComputed$1 = (node) => demandAsyncComputed({
|
|
786
|
-
node,
|
|
787
|
-
...asyncComputedInput()
|
|
788
|
-
});
|
|
789
|
-
const invalidateAsyncComputed$1 = (node) => invalidateAsyncComputed({
|
|
790
|
-
node,
|
|
791
|
-
...asyncComputedInput()
|
|
792
|
-
});
|
|
793
|
-
const sharedGraph = createSharedGraphPlane({
|
|
794
|
-
cells,
|
|
795
|
-
sharedDefinitionsInput: input.sharedDefinitions,
|
|
796
|
-
readGraph,
|
|
797
|
-
markDirtyPath,
|
|
798
|
-
scheduleFlush
|
|
799
|
-
});
|
|
800
|
-
const flush = () => {
|
|
801
|
-
if (activeFlush) return activeFlush;
|
|
802
|
-
activeFlush = runFlush();
|
|
803
|
-
return activeFlush;
|
|
804
|
-
};
|
|
805
|
-
const recomputeSubscribedComputeds = () => {
|
|
806
|
-
for (const graphNodeId of Array.from(dirtyComputedIds)) {
|
|
807
|
-
if (!computedNodes.get(graphNodeId)?.dirty) {
|
|
808
|
-
dirtyComputedIds.delete(graphNodeId);
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
if (!subscriptions.some((subscription) => subscription.graphNodeId === graphNodeId)) continue;
|
|
812
|
-
dirtyComputedIds.delete(graphNodeId);
|
|
813
|
-
readGraph(graphNodeId);
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
const settleReconcileBaselines = () => {
|
|
817
|
-
for (const graphNodeId of dirtyComputedIds) {
|
|
818
|
-
const computed = computedNodes.get(graphNodeId);
|
|
819
|
-
if (computed?.dirty) computed.baselineStale = true;
|
|
820
|
-
}
|
|
821
|
-
dirtyComputedIds.clear();
|
|
822
|
-
writeTouched.clear();
|
|
823
|
-
};
|
|
824
|
-
const runFlush = async () => {
|
|
825
|
-
flushScheduled = false;
|
|
826
|
-
flushing = true;
|
|
827
|
-
try {
|
|
828
|
-
try {
|
|
829
|
-
while (dirtyPaths.length > 0) {
|
|
830
|
-
recomputeSubscribedComputeds();
|
|
831
|
-
const pending = dirtyPaths.splice(0);
|
|
832
|
-
const ranSubscriptions = /* @__PURE__ */ new Set();
|
|
833
|
-
for (const subscription of subscriptions) {
|
|
834
|
-
const subscriptionPath = subscription.path ?? [];
|
|
835
|
-
if (!pending.some((path) => path.graphNodeId === subscription.graphNodeId && pathsIntersect(path.path, subscriptionPath)) || ranSubscriptions.has(subscription.id)) continue;
|
|
836
|
-
ranSubscriptions.add(subscription.id);
|
|
837
|
-
appendJournalResult(journal, await subscription.run(readGraph(subscription.graphNodeId, subscriptionPath)));
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
} finally {
|
|
841
|
-
settleReconcileBaselines();
|
|
842
|
-
flushing = false;
|
|
843
|
-
}
|
|
844
|
-
await notifyJournalListeners();
|
|
845
|
-
} finally {
|
|
846
|
-
activeFlush = void 0;
|
|
847
|
-
if (dirtyPaths.length > 0) scheduleFlush();
|
|
848
|
-
}
|
|
849
|
-
};
|
|
850
|
-
const notifyJournalListeners = async () => {
|
|
851
|
-
if (journalListeners.length === 0 || journal.length === 0) return;
|
|
852
|
-
const entries = journal.splice(0);
|
|
853
|
-
for (const listener of journalListeners) await listener(entries);
|
|
854
|
-
};
|
|
855
|
-
return {
|
|
856
|
-
read: readGraph,
|
|
857
|
-
...typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__ ? { peekAsyncSnapshot: (graphNodeId) => asyncComputedNodes.get(graphNodeId)?.snapshot } : {},
|
|
858
|
-
readShared: sharedGraph.readShared,
|
|
859
|
-
writeShared: sharedGraph.writeShared,
|
|
860
|
-
getSharedDefinition: sharedGraph.getSharedDefinition,
|
|
861
|
-
listSharedDefinitions: sharedGraph.listSharedDefinitions,
|
|
862
|
-
takeSharedPatches: sharedGraph.takeSharedPatches,
|
|
863
|
-
applySharedPatch: sharedGraph.applySharedPatch,
|
|
864
|
-
write(write) {
|
|
865
|
-
const path = write.path ?? [];
|
|
866
|
-
const current = cells.get(write.graphNodeId);
|
|
867
|
-
const computed = computedNodes.get(write.graphNodeId);
|
|
868
|
-
if (computed && !computed.compute && path.length === 0) {
|
|
869
|
-
const changed = diffDerivedValue({
|
|
870
|
-
previous: current,
|
|
871
|
-
next: write.value,
|
|
872
|
-
keyed: computed.reconcile?.keyed,
|
|
873
|
-
touched: writeTouched,
|
|
874
|
-
baselineStale: computed.baselineStale
|
|
875
|
-
});
|
|
876
|
-
cells.set(write.graphNodeId, write.value);
|
|
877
|
-
computed.baselineStale = false;
|
|
878
|
-
if (changed.length === 0) return;
|
|
879
|
-
for (const changedPath of changed) markDirtyPath(write.graphNodeId, changedPath);
|
|
880
|
-
scheduleFlush();
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
if (Object.is(readPath(current, path), write.value)) return;
|
|
884
|
-
cells.set(write.graphNodeId, writePath(current, path, write.value));
|
|
885
|
-
markDirtyPath(write.graphNodeId, dirtyPathForGraphWrite(current, path));
|
|
886
|
-
scheduleFlush();
|
|
887
|
-
},
|
|
888
|
-
update(update) {
|
|
889
|
-
const path = update.path ?? [];
|
|
890
|
-
const currentValue = readPath(cells.get(update.graphNodeId), path);
|
|
891
|
-
const nextValue = update.update(currentValue);
|
|
892
|
-
if (!Object.is(currentValue, nextValue)) {
|
|
893
|
-
const current = cells.get(update.graphNodeId);
|
|
894
|
-
cells.set(update.graphNodeId, writePath(current, path, nextValue));
|
|
895
|
-
markDirtyPath(update.graphNodeId, dirtyPathForGraphWrite(current, path));
|
|
896
|
-
scheduleFlush();
|
|
897
|
-
}
|
|
898
|
-
if (update.returnValue === "previous") return currentValue;
|
|
899
|
-
if (update.returnValue === "next") return nextValue;
|
|
900
|
-
},
|
|
901
|
-
call(call) {
|
|
902
|
-
const path = call.path ?? [];
|
|
903
|
-
const target = readPath(cells.get(call.graphNodeId), path);
|
|
904
|
-
const beforeMutation = collectionMutationSnapshot(target, call.method, call.args ?? []);
|
|
905
|
-
const result = applyCollectionCall(target, call.method, call.args ?? []);
|
|
906
|
-
if (collectionCallMutated(call.method, result, beforeMutation)) {
|
|
907
|
-
markDirtyPath(call.graphNodeId, path);
|
|
908
|
-
scheduleFlush();
|
|
909
|
-
}
|
|
910
|
-
return result;
|
|
911
|
-
},
|
|
912
|
-
delete(deletion) {
|
|
913
|
-
const outcome = deletePath(cells.get(deletion.graphNodeId), deletion.path);
|
|
914
|
-
if (outcome.mutated) {
|
|
915
|
-
markDirtyPath(deletion.graphNodeId, deletion.path);
|
|
916
|
-
scheduleFlush();
|
|
917
|
-
}
|
|
918
|
-
return outcome.result;
|
|
919
|
-
},
|
|
920
|
-
subscribe(subscription) {
|
|
921
|
-
subscriptions.push(subscription);
|
|
922
|
-
return () => {
|
|
923
|
-
const index = subscriptions.indexOf(subscription);
|
|
924
|
-
if (index >= 0) subscriptions.splice(index, 1);
|
|
925
|
-
};
|
|
926
|
-
},
|
|
927
|
-
subscribeJournal(listener) {
|
|
928
|
-
journalListeners.push(listener);
|
|
929
|
-
return () => {
|
|
930
|
-
const index = journalListeners.indexOf(listener);
|
|
931
|
-
if (index >= 0) journalListeners.splice(index, 1);
|
|
932
|
-
};
|
|
933
|
-
},
|
|
934
|
-
flush,
|
|
935
|
-
takeJournal() {
|
|
936
|
-
return journal.splice(0);
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
//#endregion
|
|
941
|
-
export { diffDerivedValue as n, createRuntimeGraph as t };
|