doxum 0.1.6 → 0.1.7
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 +38 -13
- package/dist/{contract-I99DUMbN.d.ts → contract-DNZ4D53r.d.ts} +87 -76
- package/dist/{contract-uYJTmT8o.d.cts → contract-j3SLGAwh.d.cts} +87 -76
- package/dist/index.cjs +221 -349
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +209 -327
- package/dist/index.js.map +1 -1
- package/dist/{integration-xupBIKjU.js → integration-B56u1l9V.js} +2 -2
- package/dist/integration-B56u1l9V.js.map +1 -0
- package/dist/{integration-Bs3pOk65.cjs → integration-CZwCwFBS.cjs} +2 -2
- package/dist/integration-CZwCwFBS.cjs.map +1 -0
- package/dist/integration.cjs +10 -2
- package/dist/integration.d.cts +3 -3
- package/dist/integration.d.ts +3 -3
- package/dist/integration.js +3 -3
- package/dist/local-sync.cjs +56 -17
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +2 -2
- package/dist/local-sync.d.ts +2 -2
- package/dist/local-sync.js +56 -17
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +27 -19
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +27 -19
- package/dist/react.js.map +1 -1
- package/dist/{runtime-CRijXjYp.cjs → runtime-0mOFbe_H.cjs} +899 -451
- package/dist/runtime-0mOFbe_H.cjs.map +1 -0
- package/dist/{runtime-CUcvhFNP.d.ts → runtime-B22tuj9A.d.cts} +18 -20
- package/dist/{runtime-C_vKHmhy.d.cts → runtime-BFmhzPpZ.d.ts} +18 -20
- package/dist/{runtime-BJRXF0gq.js → runtime-DF1q9Gje.js} +823 -429
- package/dist/runtime-DF1q9Gje.js.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +2 -1
- package/skills/doxum-runtime/references/guide.en.md +18 -18
- package/skills/doxum-runtime/references/guide.zh-CN.md +12 -17
- package/skills/doxum-runtime/references/invariants.en.md +1 -1
- package/skills/doxum-runtime/references/invariants.zh-CN.md +1 -1
- package/skills/doxum-runtime/references/patterns.en.md +9 -20
- package/skills/doxum-runtime/references/patterns.zh-CN.md +5 -18
- package/dist/integration-Bs3pOk65.cjs.map +0 -1
- package/dist/integration-xupBIKjU.js.map +0 -1
- package/dist/runtime-BJRXF0gq.js.map +0 -1
- package/dist/runtime-CRijXjYp.cjs.map +0 -1
|
@@ -3,10 +3,19 @@ import { i as isRecord$1, t as cloneValue, u as profile } from "./ownership-CduR
|
|
|
3
3
|
//#region core/src/schema.ts
|
|
4
4
|
const node = (value) => Object.freeze(value);
|
|
5
5
|
const field = () => node({ kind: "field" });
|
|
6
|
-
const optional = (value) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const optional = (value) => {
|
|
7
|
+
if (![
|
|
8
|
+
"field",
|
|
9
|
+
"variant",
|
|
10
|
+
"dict",
|
|
11
|
+
"list",
|
|
12
|
+
"tree"
|
|
13
|
+
].includes(value.kind)) throw new TypeError("Only field, variant, dict, list and tree nodes support optional presence.");
|
|
14
|
+
return node({
|
|
15
|
+
...value,
|
|
16
|
+
optional: true
|
|
17
|
+
});
|
|
18
|
+
};
|
|
10
19
|
const object = (shape) => node({
|
|
11
20
|
kind: "object",
|
|
12
21
|
shape
|
|
@@ -16,10 +25,6 @@ const variant = (tag, variants) => node({
|
|
|
16
25
|
tag,
|
|
17
26
|
variants
|
|
18
27
|
});
|
|
19
|
-
const single = (value) => node({
|
|
20
|
-
kind: "single",
|
|
21
|
-
value
|
|
22
|
-
});
|
|
23
28
|
const table = (value) => node({
|
|
24
29
|
kind: "table",
|
|
25
30
|
value
|
|
@@ -28,26 +33,55 @@ const map = (value) => node({
|
|
|
28
33
|
kind: "map",
|
|
29
34
|
value
|
|
30
35
|
});
|
|
31
|
-
const record = () => node({ kind: "record" });
|
|
32
36
|
const dict = () => node({ kind: "dict" });
|
|
33
37
|
const list = (config) => ({
|
|
34
38
|
kind: "list",
|
|
35
39
|
keyOf: config.keyOf
|
|
36
40
|
});
|
|
37
41
|
const tree = () => node({ kind: "tree" });
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const paths = /* @__PURE__ */ new WeakMap();
|
|
43
|
+
const collectionSchemas = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
const collectionSchema = (selector) => {
|
|
45
|
+
const node = collectionSchemas.get(selector);
|
|
46
|
+
if (!node) throw new TypeError("Expected a schema-owned collection selector.");
|
|
47
|
+
return node;
|
|
48
|
+
};
|
|
49
|
+
const pathProxy = (nodes, address, owner) => {
|
|
50
|
+
const proxy = new Proxy({}, { get: (_target, property) => {
|
|
51
|
+
if (typeof property !== "string") return void 0;
|
|
52
|
+
if (nodes.every((node) => node.kind === "table" || node.kind === "map") && property === "item") return (id) => {
|
|
53
|
+
if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
|
|
54
|
+
return pathProxy(nodes.map((node) => node.value), [...address, id], owner);
|
|
55
|
+
};
|
|
56
|
+
const children = nodes.flatMap((node) => {
|
|
57
|
+
if (node.kind === "object") return node.shape[property] ? [node.shape[property]] : [];
|
|
58
|
+
if (node.kind !== "variant") return [];
|
|
59
|
+
if (property === node.tag) return [field()];
|
|
60
|
+
return Object.values(node.variants).flatMap((branch) => branch.shape[property] ? [branch.shape[property]] : []);
|
|
61
|
+
});
|
|
62
|
+
if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
|
|
63
|
+
return pathProxy(children, [...address, property], owner);
|
|
64
|
+
} });
|
|
65
|
+
paths.set(proxy, {
|
|
66
|
+
nodes,
|
|
67
|
+
address,
|
|
68
|
+
owner
|
|
69
|
+
});
|
|
70
|
+
return proxy;
|
|
71
|
+
};
|
|
44
72
|
const select = (owner, kind, pick) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
73
|
+
const scope = {};
|
|
74
|
+
const value = pick(pathProxy([object(owner.shape)], [], scope));
|
|
75
|
+
const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
|
|
76
|
+
if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
|
|
77
|
+
if (kind === "collection" && !selected.nodes.every((node) => node.kind === "table" || node.kind === "map")) throw new TypeError("Collection selectors require a table or map path.");
|
|
78
|
+
const result = Object.freeze({
|
|
47
79
|
kind,
|
|
48
80
|
schema: owner,
|
|
49
|
-
address: Object.freeze([...selected.address
|
|
81
|
+
address: Object.freeze([...selected.address])
|
|
50
82
|
});
|
|
83
|
+
if (kind === "collection") collectionSchemas.set(result, selected.nodes[0]);
|
|
84
|
+
return result;
|
|
51
85
|
};
|
|
52
86
|
const schema = (shape) => {
|
|
53
87
|
const result = {
|
|
@@ -79,11 +113,6 @@ const hashText = (value, seed) => {
|
|
|
79
113
|
};
|
|
80
114
|
const appendAddressHash = (hash, segment) => hashText(segment, hashText("/", hash));
|
|
81
115
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
82
|
-
const unwrap = (node) => {
|
|
83
|
-
let current = node;
|
|
84
|
-
while (current?.kind === "single") current = current.value;
|
|
85
|
-
return current;
|
|
86
|
-
};
|
|
87
116
|
const variantNode = (node, value) => {
|
|
88
117
|
const tag = isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
|
|
89
118
|
return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
|
|
@@ -93,12 +122,12 @@ const schemaRoot$1 = (schema) => ({
|
|
|
93
122
|
shape: schema.shape
|
|
94
123
|
});
|
|
95
124
|
const step = (nodeInput, value, segment) => {
|
|
96
|
-
let node =
|
|
125
|
+
let node = nodeInput;
|
|
97
126
|
if (!node) return {
|
|
98
127
|
node: void 0,
|
|
99
128
|
dynamic: false
|
|
100
129
|
};
|
|
101
|
-
if (node.kind === "variant") node =
|
|
130
|
+
if (node.kind === "variant") node = variantNode(node, value);
|
|
102
131
|
if (!node) return {
|
|
103
132
|
node: void 0,
|
|
104
133
|
dynamic: false
|
|
@@ -111,7 +140,7 @@ const step = (nodeInput, value, segment) => {
|
|
|
111
140
|
node: node.value,
|
|
112
141
|
dynamic: true
|
|
113
142
|
};
|
|
114
|
-
if (node.kind === "
|
|
143
|
+
if (node.kind === "dict" || node.kind === "list" || node.kind === "tree") return {
|
|
115
144
|
node,
|
|
116
145
|
dynamic: true
|
|
117
146
|
};
|
|
@@ -162,7 +191,7 @@ const nodeAt = (schema, address, document) => {
|
|
|
162
191
|
value = readSegment(value, segment);
|
|
163
192
|
if (!node) return void 0;
|
|
164
193
|
}
|
|
165
|
-
return
|
|
194
|
+
return node;
|
|
166
195
|
};
|
|
167
196
|
const readSegment = (value, segment) => {
|
|
168
197
|
if (!isRecord(value) && !Array.isArray(value)) return void 0;
|
|
@@ -276,25 +305,93 @@ const debugKey = (address) => {
|
|
|
276
305
|
//#endregion
|
|
277
306
|
//#region core/src/impact-target.ts
|
|
278
307
|
var impact_target_exports = /* @__PURE__ */ __exportAll({
|
|
279
|
-
address: () => address,
|
|
308
|
+
address: () => address$1,
|
|
280
309
|
belongs: () => belongs,
|
|
281
310
|
bucket: () => bucket,
|
|
282
311
|
id: () => id,
|
|
283
312
|
same: () => same
|
|
284
313
|
});
|
|
285
|
-
const address = (target) => "at" in target ? target.at : target.address;
|
|
314
|
+
const address$1 = (target) => "at" in target ? target.at : target.address;
|
|
286
315
|
const id = (target) => target.kind === "collection" && "id" in target ? target.id : void 0;
|
|
287
316
|
const belongs = (target, schema) => !("schema" in target) || target.schema === schema;
|
|
288
317
|
const same = (left, right) => {
|
|
289
318
|
if (left.kind !== right.kind) return false;
|
|
290
319
|
if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
|
|
291
|
-
const leftAddress = address(left);
|
|
292
|
-
const rightAddress = address(right);
|
|
320
|
+
const leftAddress = address$1(left);
|
|
321
|
+
const rightAddress = address$1(right);
|
|
293
322
|
if (leftAddress.length !== rightAddress.length) return false;
|
|
294
323
|
for (let index = 0; index < leftAddress.length; index += 1) if (leftAddress[index] !== rightAddress[index]) return false;
|
|
295
324
|
return left.kind !== "collection" || id(left) === id(right);
|
|
296
325
|
};
|
|
297
|
-
const bucket = (target) => address(target)[0];
|
|
326
|
+
const bucket = (target) => address$1(target)[0];
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region core/src/runtime/contract.ts
|
|
329
|
+
var DocumentReentrancyError = class extends Error {
|
|
330
|
+
constructor() {
|
|
331
|
+
super("Document writes cannot be re-entered during an update or notification.");
|
|
332
|
+
this.name = "DocumentReentrancyError";
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
var DocumentDisposedError = class extends Error {
|
|
336
|
+
constructor() {
|
|
337
|
+
super("Doxum runtime has been disposed.");
|
|
338
|
+
this.name = "DocumentDisposedError";
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region core/src/mutation/anchor.ts
|
|
343
|
+
const length = (keys) => keys.length;
|
|
344
|
+
const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
|
|
345
|
+
const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
|
|
346
|
+
const keys = (values, keyOf) => ({
|
|
347
|
+
length: values.length,
|
|
348
|
+
at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
|
|
349
|
+
index: (key) => {
|
|
350
|
+
for (let index = 0; index < values.length; index += 1) if (keyOf(values[index]) === key) return index;
|
|
351
|
+
return -1;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
const index = (keys, anchor) => {
|
|
355
|
+
if (!anchor) return length(keys);
|
|
356
|
+
if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
|
|
357
|
+
if ("before" in anchor) return keyIndex(keys, anchor.before);
|
|
358
|
+
return keyIndex(keys, anchor.after) + 1;
|
|
359
|
+
};
|
|
360
|
+
const at = (keys, position) => position <= 0 ? { at: "start" } : { after: keyAt(keys, position - 1) };
|
|
361
|
+
const valid = (keys, anchor) => {
|
|
362
|
+
if (!anchor) return true;
|
|
363
|
+
if (typeof anchor !== "object" || anchor === null) return false;
|
|
364
|
+
if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
|
|
365
|
+
if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
|
|
366
|
+
return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
|
|
367
|
+
};
|
|
368
|
+
const afterRemove = (keys, removedIndex, anchor) => {
|
|
369
|
+
const position = index(keys, anchor);
|
|
370
|
+
return position > removedIndex ? position - 1 : position;
|
|
371
|
+
};
|
|
372
|
+
const validPositions = (length, positions) => {
|
|
373
|
+
let previous = -1;
|
|
374
|
+
for (let index = 0; index < positions.length; index += 1) {
|
|
375
|
+
const position = positions[index];
|
|
376
|
+
if (!Number.isInteger(position) || position < 0 || position <= previous || position > length + index) return false;
|
|
377
|
+
previous = position;
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
};
|
|
381
|
+
const restore = (keys, inserted, positions) => {
|
|
382
|
+
const currentLength = keys.length;
|
|
383
|
+
if (inserted.length !== positions.length || !validPositions(currentLength, positions)) throw new Error("Ordered restore positions are invalid.");
|
|
384
|
+
let readIndex = currentLength - 1;
|
|
385
|
+
let insertedIndex = inserted.length - 1;
|
|
386
|
+
keys.length = currentLength + inserted.length;
|
|
387
|
+
for (let writeIndex = keys.length - 1; writeIndex >= 0; writeIndex -= 1) if (insertedIndex >= 0 && positions[insertedIndex] === writeIndex) {
|
|
388
|
+
keys[writeIndex] = inserted[insertedIndex];
|
|
389
|
+
insertedIndex -= 1;
|
|
390
|
+
} else {
|
|
391
|
+
keys[writeIndex] = keys[readIndex];
|
|
392
|
+
readIndex -= 1;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
298
395
|
//#endregion
|
|
299
396
|
//#region core/src/access/reader.ts
|
|
300
397
|
const schemaRoot = (schema) => object(schema.shape);
|
|
@@ -325,12 +422,40 @@ const readerFor = (node, context, address = []) => {
|
|
|
325
422
|
collectValue(context, address);
|
|
326
423
|
return readAt(context, address);
|
|
327
424
|
} };
|
|
328
|
-
if (node.kind === "dict"
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
425
|
+
if (node.kind === "dict") return {
|
|
426
|
+
get: (key) => {
|
|
427
|
+
collectValue(context, address);
|
|
428
|
+
const value = readAt(context, address);
|
|
429
|
+
return cloneValue(isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, key) ? value[key] : void 0, "reader");
|
|
430
|
+
},
|
|
431
|
+
has: (key) => {
|
|
432
|
+
collectValue(context, address);
|
|
433
|
+
const value = readAt(context, address);
|
|
434
|
+
return isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, key);
|
|
435
|
+
},
|
|
436
|
+
keys: () => {
|
|
437
|
+
collectValue(context, address);
|
|
438
|
+
const value = readAt(context, address);
|
|
439
|
+
return isRecord$1(value) ? Object.keys(value) : [];
|
|
440
|
+
},
|
|
441
|
+
values: () => {
|
|
442
|
+
collectValue(context, address);
|
|
443
|
+
profile.reader.structuralSnapshot();
|
|
444
|
+
return cloneValue(readAt(context, address) ?? {}, "reader");
|
|
445
|
+
}
|
|
446
|
+
};
|
|
333
447
|
if (node.kind === "list") return {
|
|
448
|
+
get: (key) => {
|
|
449
|
+
collectValue(context, address);
|
|
450
|
+
const value = readAt(context, address);
|
|
451
|
+
if (!Array.isArray(value)) return void 0;
|
|
452
|
+
return cloneValue(value[keys(value, node.keyOf).index(key)], "reader");
|
|
453
|
+
},
|
|
454
|
+
has: (key) => {
|
|
455
|
+
collectValue(context, address);
|
|
456
|
+
const value = readAt(context, address);
|
|
457
|
+
return Array.isArray(value) && keys(value, node.keyOf).index(key) >= 0;
|
|
458
|
+
},
|
|
334
459
|
values: () => {
|
|
335
460
|
collectValue(context, address);
|
|
336
461
|
const value = readAt(context, address);
|
|
@@ -408,14 +533,10 @@ const readerFor = (node, context, address = []) => {
|
|
|
408
533
|
}
|
|
409
534
|
};
|
|
410
535
|
}
|
|
411
|
-
if (node.kind === "
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const tag = isRecord$1(value) && typeof value[node.tag] === "string" ? String(value[node.tag]) : void 0;
|
|
416
|
-
const child = (tag ? node.variants[tag] : void 0)?.shape[property];
|
|
417
|
-
return child ? readerFor(child, context, [...address, property]) : void 0;
|
|
418
|
-
} });
|
|
536
|
+
if (node.kind === "variant") return { get: () => {
|
|
537
|
+
collectValue(context, address);
|
|
538
|
+
return cloneValue(readAt(context, address), "reader");
|
|
539
|
+
} };
|
|
419
540
|
let children;
|
|
420
541
|
return new Proxy({}, { get: (_target, property) => {
|
|
421
542
|
if (typeof property === "symbol") return void 0;
|
|
@@ -437,19 +558,191 @@ const documentReader = (schema, root, active, dependencies) => {
|
|
|
437
558
|
});
|
|
438
559
|
};
|
|
439
560
|
//#endregion
|
|
440
|
-
//#region core/src/
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
561
|
+
//#region core/src/mutation/footprint.ts
|
|
562
|
+
const sameAddress = (left, right) => left.length === right.length && left.every((segment, index) => segment === right[index]);
|
|
563
|
+
const containsAddress = (parent, child) => parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
|
|
564
|
+
const address = (value) => Array.isArray(value) && value.every((segment) => typeof segment === "string") ? Object.freeze(value.slice()) : void 0;
|
|
565
|
+
const targetKey = (target) => {
|
|
566
|
+
const at = target.at.join("\0");
|
|
567
|
+
if (target.kind === "dictionary-key" || target.kind === "list-item") return `${target.kind}:${at}:${target.key}`;
|
|
568
|
+
if (target.kind === "entity" || target.kind === "tree-node") return `${target.kind}:${at}:${target.id}`;
|
|
569
|
+
return `${target.kind}:${at}`;
|
|
570
|
+
};
|
|
571
|
+
const freezeTarget = (target) => Object.freeze({
|
|
572
|
+
...target,
|
|
573
|
+
at: Object.freeze(target.at.slice())
|
|
574
|
+
});
|
|
575
|
+
const add = (targets, target) => {
|
|
576
|
+
const stable = freezeTarget(target);
|
|
577
|
+
targets.set(targetKey(stable), stable);
|
|
578
|
+
};
|
|
579
|
+
const commandFootprint = (operations) => {
|
|
580
|
+
const targets = /* @__PURE__ */ new Map();
|
|
581
|
+
for (const operation of operations) switch (operation.type) {
|
|
582
|
+
case "field.set":
|
|
583
|
+
case "field.clear":
|
|
584
|
+
case "value.clear":
|
|
585
|
+
case "variant.replace":
|
|
586
|
+
add(targets, {
|
|
587
|
+
kind: "value",
|
|
588
|
+
at: operation.at
|
|
589
|
+
});
|
|
590
|
+
break;
|
|
591
|
+
case "dict.replace":
|
|
592
|
+
add(targets, {
|
|
593
|
+
kind: "dictionary",
|
|
594
|
+
at: operation.at
|
|
595
|
+
});
|
|
596
|
+
break;
|
|
597
|
+
case "dict.set":
|
|
598
|
+
case "dict.delete":
|
|
599
|
+
add(targets, {
|
|
600
|
+
kind: "dictionary-key",
|
|
601
|
+
at: operation.at,
|
|
602
|
+
key: operation.key
|
|
603
|
+
});
|
|
604
|
+
break;
|
|
605
|
+
case "entity.create":
|
|
606
|
+
add(targets, {
|
|
607
|
+
kind: "collection",
|
|
608
|
+
at: operation.at
|
|
609
|
+
});
|
|
610
|
+
for (const entry of operation.entries) add(targets, {
|
|
611
|
+
kind: "entity",
|
|
612
|
+
at: operation.at,
|
|
613
|
+
id: entry.id
|
|
614
|
+
});
|
|
615
|
+
break;
|
|
616
|
+
case "entity.remove":
|
|
617
|
+
add(targets, {
|
|
618
|
+
kind: "collection",
|
|
619
|
+
at: operation.at
|
|
620
|
+
});
|
|
621
|
+
for (const id of operation.ids) add(targets, {
|
|
622
|
+
kind: "entity",
|
|
623
|
+
at: operation.at,
|
|
624
|
+
id
|
|
625
|
+
});
|
|
626
|
+
break;
|
|
627
|
+
case "entity.move":
|
|
628
|
+
add(targets, {
|
|
629
|
+
kind: "collection",
|
|
630
|
+
at: operation.at
|
|
631
|
+
});
|
|
632
|
+
add(targets, {
|
|
633
|
+
kind: "entity",
|
|
634
|
+
at: operation.at,
|
|
635
|
+
id: operation.id
|
|
636
|
+
});
|
|
637
|
+
break;
|
|
638
|
+
case "list.insert":
|
|
639
|
+
case "list.move":
|
|
640
|
+
case "list.remove":
|
|
641
|
+
add(targets, {
|
|
642
|
+
kind: "list",
|
|
643
|
+
at: operation.at
|
|
644
|
+
});
|
|
645
|
+
add(targets, {
|
|
646
|
+
kind: "list-item",
|
|
647
|
+
at: operation.at,
|
|
648
|
+
key: operation.key
|
|
649
|
+
});
|
|
650
|
+
break;
|
|
651
|
+
case "list.replace":
|
|
652
|
+
add(targets, {
|
|
653
|
+
kind: "list",
|
|
654
|
+
at: operation.at
|
|
655
|
+
});
|
|
656
|
+
for (const key of operation.keys) add(targets, {
|
|
657
|
+
kind: "list-item",
|
|
658
|
+
at: operation.at,
|
|
659
|
+
key
|
|
660
|
+
});
|
|
661
|
+
break;
|
|
662
|
+
case "tree.insert":
|
|
663
|
+
case "tree.move":
|
|
664
|
+
case "tree.remove":
|
|
665
|
+
case "tree.set":
|
|
666
|
+
add(targets, {
|
|
667
|
+
kind: "tree",
|
|
668
|
+
at: operation.at
|
|
669
|
+
});
|
|
670
|
+
add(targets, {
|
|
671
|
+
kind: "tree-node",
|
|
672
|
+
at: operation.at,
|
|
673
|
+
id: operation.treeNodeId
|
|
674
|
+
});
|
|
675
|
+
break;
|
|
676
|
+
case "tree.replace":
|
|
677
|
+
add(targets, {
|
|
678
|
+
kind: "tree",
|
|
679
|
+
at: operation.at
|
|
680
|
+
});
|
|
681
|
+
break;
|
|
445
682
|
}
|
|
683
|
+
return Object.freeze(Array.from(targets.values()));
|
|
446
684
|
};
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
685
|
+
const overlapsValueAndEntity = (value, entity) => containsAddress([...entity.at, entity.id], value.at) || containsAddress(value.at, [...entity.at, entity.id]);
|
|
686
|
+
const targetsOverlap = (left, right) => {
|
|
687
|
+
if (left.kind === "value" && right.kind === "value") return containsAddress(left.at, right.at) || containsAddress(right.at, left.at);
|
|
688
|
+
if (left.kind === "value" && right.kind === "entity") return overlapsValueAndEntity(left, right);
|
|
689
|
+
if (left.kind === "entity" && right.kind === "value") return overlapsValueAndEntity(right, left);
|
|
690
|
+
if (left.kind === "dictionary" && right.kind === "dictionary") return sameAddress(left.at, right.at);
|
|
691
|
+
if (left.kind === "dictionary" && right.kind === "dictionary-key") return sameAddress(left.at, right.at);
|
|
692
|
+
if (left.kind === "dictionary-key" && right.kind === "dictionary") return sameAddress(left.at, right.at);
|
|
693
|
+
if (left.kind === "dictionary-key" && right.kind === "dictionary-key") return sameAddress(left.at, right.at) && left.key === right.key;
|
|
694
|
+
if (left.kind === "collection" && right.kind === "collection") return sameAddress(left.at, right.at);
|
|
695
|
+
if (left.kind === "collection" && right.kind === "entity") return sameAddress(left.at, right.at);
|
|
696
|
+
if (left.kind === "entity" && right.kind === "collection") return sameAddress(left.at, right.at);
|
|
697
|
+
if (left.kind === "entity" && right.kind === "entity") return sameAddress(left.at, right.at) && left.id === right.id;
|
|
698
|
+
if (left.kind === "list" && (right.kind === "list" || right.kind === "list-item")) return sameAddress(left.at, right.at);
|
|
699
|
+
if (right.kind === "list" && left.kind === "list-item") return sameAddress(left.at, right.at);
|
|
700
|
+
if (left.kind === "list-item" && right.kind === "list-item") return sameAddress(left.at, right.at) && left.key === right.key;
|
|
701
|
+
if (left.kind === "tree" && (right.kind === "tree" || right.kind === "tree-node")) return sameAddress(left.at, right.at);
|
|
702
|
+
if (right.kind === "tree" && left.kind === "tree-node") return sameAddress(left.at, right.at);
|
|
703
|
+
if (left.kind === "tree-node" && right.kind === "tree-node") return sameAddress(left.at, right.at) && left.id === right.id;
|
|
704
|
+
return false;
|
|
705
|
+
};
|
|
706
|
+
const footprintsOverlap = (left, right) => left.some((leftTarget) => right.some((rightTarget) => targetsOverlap(leftTarget, rightTarget)));
|
|
707
|
+
const decodedTarget = (value) => {
|
|
708
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
709
|
+
const record = value;
|
|
710
|
+
const at = address(record.at);
|
|
711
|
+
if (!at || typeof record.kind !== "string") return void 0;
|
|
712
|
+
switch (record.kind) {
|
|
713
|
+
case "value":
|
|
714
|
+
case "dictionary":
|
|
715
|
+
case "collection":
|
|
716
|
+
case "list":
|
|
717
|
+
case "tree": return freezeTarget({
|
|
718
|
+
kind: record.kind,
|
|
719
|
+
at
|
|
720
|
+
});
|
|
721
|
+
case "dictionary-key":
|
|
722
|
+
case "list-item": return typeof record.key === "string" ? freezeTarget({
|
|
723
|
+
kind: record.kind,
|
|
724
|
+
at,
|
|
725
|
+
key: record.key
|
|
726
|
+
}) : void 0;
|
|
727
|
+
case "entity":
|
|
728
|
+
case "tree-node": return typeof record.id === "string" ? freezeTarget({
|
|
729
|
+
kind: record.kind,
|
|
730
|
+
at,
|
|
731
|
+
id: record.id
|
|
732
|
+
}) : void 0;
|
|
733
|
+
default: return;
|
|
451
734
|
}
|
|
452
735
|
};
|
|
736
|
+
const decodeCommandFootprint = (value) => {
|
|
737
|
+
if (!Array.isArray(value)) return void 0;
|
|
738
|
+
const targets = /* @__PURE__ */ new Map();
|
|
739
|
+
for (const entry of value) {
|
|
740
|
+
const target = decodedTarget(entry);
|
|
741
|
+
if (!target) return void 0;
|
|
742
|
+
targets.set(targetKey(target), target);
|
|
743
|
+
}
|
|
744
|
+
return Object.freeze(Array.from(targets.values()));
|
|
745
|
+
};
|
|
453
746
|
//#endregion
|
|
454
747
|
//#region core/src/runtime/access.ts
|
|
455
748
|
const states = /* @__PURE__ */ new WeakMap();
|
|
@@ -476,6 +769,11 @@ const readWith = (runtime, run, dependencies) => {
|
|
|
476
769
|
//#region core/src/runtime/notification.ts
|
|
477
770
|
const ROOT_BUCKET = Symbol("document-root");
|
|
478
771
|
const notifications = /* @__PURE__ */ new WeakMap();
|
|
772
|
+
const readableOwners = /* @__PURE__ */ new WeakMap();
|
|
773
|
+
const bindDocumentReadable = (readable, runtime) => {
|
|
774
|
+
readableOwners.set(readable, runtime);
|
|
775
|
+
};
|
|
776
|
+
const documentReadableOwner = (readable) => readableOwners.get(readable);
|
|
479
777
|
const key = (value) => bucket(value) ?? ROOT_BUCKET;
|
|
480
778
|
const createNotification = (runtime) => {
|
|
481
779
|
const notification = {
|
|
@@ -550,7 +848,7 @@ const subscribeTargets = (notification, targets, listener) => {
|
|
|
550
848
|
});
|
|
551
849
|
};
|
|
552
850
|
};
|
|
553
|
-
const notify = (notification, commit) => {
|
|
851
|
+
const notify = (notification, commit, afterSettle) => {
|
|
554
852
|
notification.notifying = true;
|
|
555
853
|
const errors = [];
|
|
556
854
|
const call = (listener) => {
|
|
@@ -601,6 +899,7 @@ const notify = (notification, commit) => {
|
|
|
601
899
|
}));
|
|
602
900
|
}
|
|
603
901
|
}
|
|
902
|
+
if (afterSettle) errors.push(...afterSettle());
|
|
604
903
|
const candidates = notification.candidates;
|
|
605
904
|
candidates.clear();
|
|
606
905
|
if (commit.impact.kind === "reset") notification.filtered.forEach((entry) => candidates.add(entry));
|
|
@@ -974,239 +1273,34 @@ const assertScope = (active) => {
|
|
|
974
1273
|
if (!active()) throw new Error("Projection reader or writer is no longer active.");
|
|
975
1274
|
};
|
|
976
1275
|
//#endregion
|
|
977
|
-
//#region core/src/projection/
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
const
|
|
981
|
-
const
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
clear: () => {
|
|
1004
|
-
commits = [];
|
|
1005
|
-
},
|
|
1006
|
-
context: (active) => {
|
|
1007
|
-
assertScope(active);
|
|
1008
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1009
|
-
const read = collection ? readerFor(nodeAt(state.schema, collection.address), {
|
|
1010
|
-
root: () => state.document,
|
|
1011
|
-
active
|
|
1012
|
-
}, collection.address) : documentReader(state.schema, () => state.document, active);
|
|
1013
|
-
return Object.freeze({
|
|
1014
|
-
read,
|
|
1015
|
-
target: collection,
|
|
1016
|
-
revision: runtime.revision(),
|
|
1017
|
-
commits: Object.freeze(commits.slice()),
|
|
1018
|
-
reset: record.reset()
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
const handle = collection ? {} : {
|
|
1023
|
-
collection: (pick) => {
|
|
1024
|
-
scheduler.assertIdle();
|
|
1025
|
-
const selector = state.schema.collection(pick);
|
|
1026
|
-
const node = nodeAt(state.schema, selector.address);
|
|
1027
|
-
if (node?.kind !== "table" && node?.kind !== "map") throw new TypeError("Expected a table or map collection path.");
|
|
1028
|
-
return make([selector], selector);
|
|
1029
|
-
},
|
|
1030
|
-
targets: (...selected) => {
|
|
1031
|
-
scheduler.assertIdle();
|
|
1032
|
-
if (!selected.length) throw new TypeError("Expected at least one target.");
|
|
1033
|
-
return make(selected);
|
|
1034
|
-
}
|
|
1035
|
-
};
|
|
1036
|
-
Object.freeze(handle);
|
|
1037
|
-
bindings.push({
|
|
1038
|
-
handle,
|
|
1039
|
-
targets,
|
|
1040
|
-
record
|
|
1041
|
-
});
|
|
1042
|
-
if (collection) collections.set(handle, {
|
|
1043
|
-
source: handle,
|
|
1044
|
-
target: collection
|
|
1045
|
-
});
|
|
1046
|
-
scheduler.register(handle, record);
|
|
1047
|
-
receivers.set(record, (commit) => {
|
|
1048
|
-
commits.push(commit);
|
|
1049
|
-
});
|
|
1050
|
-
return handle;
|
|
1051
|
-
};
|
|
1052
|
-
const receivers = /* @__PURE__ */ new Map();
|
|
1053
|
-
const handle = make([]);
|
|
1054
|
-
const guard = (locked) => {
|
|
1055
|
-
state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
|
|
1056
|
-
};
|
|
1057
|
-
scheduler.guards.add(guard);
|
|
1058
|
-
const unsubscribe = attachProjection(runtime, {
|
|
1059
|
-
capture: (commit) => {
|
|
1060
|
-
for (const binding of bindings) if (!binding.targets.length || binding.targets.some(commit.impact.affects)) {
|
|
1061
|
-
receivers.get(binding.record)(commit);
|
|
1062
|
-
scheduler.capture(binding.record);
|
|
1063
|
-
}
|
|
1064
|
-
},
|
|
1065
|
-
settle: scheduler.settle,
|
|
1066
|
-
flush: scheduler.flush,
|
|
1067
|
-
dispose: () => {
|
|
1068
|
-
bindings.forEach(({ record }) => {
|
|
1069
|
-
record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1070
|
-
scheduler.capture(record);
|
|
1071
|
-
});
|
|
1072
|
-
scheduler.run();
|
|
1276
|
+
//#region core/src/projection/node.ts
|
|
1277
|
+
const createNode = (scheduler, spec, behavior) => {
|
|
1278
|
+
scheduler.assertIdle();
|
|
1279
|
+
const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
1280
|
+
const order = scheduler.order();
|
|
1281
|
+
const node = {
|
|
1282
|
+
...behavior,
|
|
1283
|
+
publish: () => {
|
|
1284
|
+
if (node.fault) behavior.clear();
|
|
1285
|
+
else behavior.publish();
|
|
1286
|
+
},
|
|
1287
|
+
order,
|
|
1288
|
+
name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
|
|
1289
|
+
sources: entries.map(([, source]) => source),
|
|
1290
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1291
|
+
disposed: false,
|
|
1292
|
+
fault: void 0,
|
|
1293
|
+
forced: false,
|
|
1294
|
+
statusChanged: false,
|
|
1295
|
+
evaluate: (build) => {
|
|
1296
|
+
let active = true;
|
|
1297
|
+
try {
|
|
1298
|
+
const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(() => active)]));
|
|
1299
|
+
return behavior.evaluate(inputs, build, () => active);
|
|
1300
|
+
} finally {
|
|
1301
|
+
active = false;
|
|
1073
1302
|
}
|
|
1074
|
-
}
|
|
1075
|
-
scheduler.cleanups.add(() => {
|
|
1076
|
-
unsubscribe();
|
|
1077
|
-
scheduler.guards.delete(guard);
|
|
1078
|
-
});
|
|
1079
|
-
documents.set(state, handle);
|
|
1080
|
-
return handle;
|
|
1081
|
-
};
|
|
1082
|
-
const valueSource = (initial, equal) => {
|
|
1083
|
-
let value = initial;
|
|
1084
|
-
let previous = initial;
|
|
1085
|
-
let revision = 0;
|
|
1086
|
-
const handle = Object.freeze({});
|
|
1087
|
-
const record = {
|
|
1088
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1089
|
-
disposed: false,
|
|
1090
|
-
fault: void 0,
|
|
1091
|
-
revision: () => revision,
|
|
1092
|
-
reset: () => false,
|
|
1093
|
-
clear: () => {
|
|
1094
|
-
previous = value;
|
|
1095
|
-
},
|
|
1096
|
-
context: (active) => {
|
|
1097
|
-
assertScope(active);
|
|
1098
|
-
return Object.freeze({
|
|
1099
|
-
value,
|
|
1100
|
-
previous,
|
|
1101
|
-
revision,
|
|
1102
|
-
changed: !equal(previous, value),
|
|
1103
|
-
reset: false
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
scheduler.register(handle, record);
|
|
1108
|
-
const accept = (next) => {
|
|
1109
|
-
scheduler.assertIdle();
|
|
1110
|
-
const recovering = record.fault !== void 0;
|
|
1111
|
-
if (equal(value, next) && !recovering) return;
|
|
1112
|
-
record.fault = void 0;
|
|
1113
|
-
value = next;
|
|
1114
|
-
revision++;
|
|
1115
|
-
scheduler.capture(record);
|
|
1116
|
-
};
|
|
1117
|
-
return {
|
|
1118
|
-
source: handle,
|
|
1119
|
-
accept,
|
|
1120
|
-
set(next) {
|
|
1121
|
-
accept(next);
|
|
1122
|
-
scheduler.run();
|
|
1123
|
-
}
|
|
1124
|
-
};
|
|
1125
|
-
};
|
|
1126
|
-
return {
|
|
1127
|
-
dispose: () => {
|
|
1128
|
-
documents.clear();
|
|
1129
|
-
readables.clear();
|
|
1130
|
-
},
|
|
1131
|
-
document,
|
|
1132
|
-
input: (initial, options) => {
|
|
1133
|
-
const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
|
|
1134
|
-
return Object.freeze({
|
|
1135
|
-
source,
|
|
1136
|
-
set
|
|
1137
|
-
});
|
|
1138
|
-
},
|
|
1139
|
-
fromReadable: (readable, options) => {
|
|
1140
|
-
scheduler.assertIdle();
|
|
1141
|
-
if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
|
|
1142
|
-
const old = readables.get(readable);
|
|
1143
|
-
if (old) return old;
|
|
1144
|
-
const input = valueSource(readable.current(), options?.isEqual ?? Object.is);
|
|
1145
|
-
const receive = () => {
|
|
1146
|
-
if (!scheduler.active) return;
|
|
1147
|
-
try {
|
|
1148
|
-
input.accept(readable.current());
|
|
1149
|
-
} catch (cause) {
|
|
1150
|
-
const record = scheduler.source(input.source);
|
|
1151
|
-
record.fault = new ProjectionError("source", "external readable", [readable.revision()], cause);
|
|
1152
|
-
scheduler.capture(record);
|
|
1153
|
-
}
|
|
1154
|
-
scheduler.run();
|
|
1155
|
-
};
|
|
1156
|
-
let unsubscribe;
|
|
1157
|
-
try {
|
|
1158
|
-
unsubscribe = readable.subscribe(receive);
|
|
1159
|
-
input.accept(readable.current());
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
try {
|
|
1162
|
-
unsubscribe?.();
|
|
1163
|
-
} finally {
|
|
1164
|
-
scheduler.unregisterSource(input.source);
|
|
1165
|
-
}
|
|
1166
|
-
throw error;
|
|
1167
|
-
}
|
|
1168
|
-
scheduler.cleanups.add(unsubscribe);
|
|
1169
|
-
readables.set(readable, input.source);
|
|
1170
|
-
scheduler.run();
|
|
1171
|
-
return input.source;
|
|
1172
|
-
},
|
|
1173
|
-
collectionTarget: (handle) => {
|
|
1174
|
-
scheduler.source(handle);
|
|
1175
|
-
const value = collections.get(handle);
|
|
1176
|
-
if (!value) throw new Error("map requires a document collection source.");
|
|
1177
|
-
return value.target;
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
};
|
|
1181
|
-
//#endregion
|
|
1182
|
-
//#region core/src/projection/node.ts
|
|
1183
|
-
const createNode = (scheduler, spec, behavior) => {
|
|
1184
|
-
scheduler.assertIdle();
|
|
1185
|
-
const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
1186
|
-
const order = scheduler.order();
|
|
1187
|
-
const node = {
|
|
1188
|
-
...behavior,
|
|
1189
|
-
publish: () => {
|
|
1190
|
-
if (node.fault) behavior.clear();
|
|
1191
|
-
else behavior.publish();
|
|
1192
|
-
},
|
|
1193
|
-
order,
|
|
1194
|
-
name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
|
|
1195
|
-
sources: entries.map(([, source]) => source),
|
|
1196
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1197
|
-
disposed: false,
|
|
1198
|
-
fault: void 0,
|
|
1199
|
-
forced: false,
|
|
1200
|
-
statusChanged: false,
|
|
1201
|
-
evaluate: (build) => {
|
|
1202
|
-
let active = true;
|
|
1203
|
-
try {
|
|
1204
|
-
const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(() => active)]));
|
|
1205
|
-
return behavior.evaluate(inputs, build, () => active);
|
|
1206
|
-
} finally {
|
|
1207
|
-
active = false;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1303
|
+
}
|
|
1210
1304
|
};
|
|
1211
1305
|
scheduler.initialize(() => {
|
|
1212
1306
|
const invalid = node.sources.find((source) => source.fault || source.disposed);
|
|
@@ -1231,103 +1325,8 @@ const createNode = (scheduler, spec, behavior) => {
|
|
|
1231
1325
|
};
|
|
1232
1326
|
};
|
|
1233
1327
|
//#endregion
|
|
1234
|
-
//#region core/src/projection/value.ts
|
|
1235
|
-
const createValue = (scheduler, spec) => {
|
|
1236
|
-
let instance;
|
|
1237
|
-
let value;
|
|
1238
|
-
let next;
|
|
1239
|
-
let previous;
|
|
1240
|
-
let initialized = false;
|
|
1241
|
-
let revision = 0;
|
|
1242
|
-
let changed = false;
|
|
1243
|
-
let reset = false;
|
|
1244
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1245
|
-
const owner = createNode(scheduler, spec, {
|
|
1246
|
-
evaluate: (sources, build) => {
|
|
1247
|
-
let candidate;
|
|
1248
|
-
if (build || !instance) {
|
|
1249
|
-
profile.materialized.rebuilt();
|
|
1250
|
-
instance = void 0;
|
|
1251
|
-
const built = spec.build(sources);
|
|
1252
|
-
assertSynchronous(built);
|
|
1253
|
-
instance = built;
|
|
1254
|
-
candidate = built.value;
|
|
1255
|
-
} else {
|
|
1256
|
-
profile.materialized.updated();
|
|
1257
|
-
const result = instance.update(sources);
|
|
1258
|
-
assertSynchronous(result);
|
|
1259
|
-
if (result.kind === "rebuild") {
|
|
1260
|
-
instance = void 0;
|
|
1261
|
-
const built = spec.build(sources);
|
|
1262
|
-
assertSynchronous(built);
|
|
1263
|
-
instance = built;
|
|
1264
|
-
candidate = built.value;
|
|
1265
|
-
build = true;
|
|
1266
|
-
} else candidate = result.kind === "changed" ? result.value : value;
|
|
1267
|
-
}
|
|
1268
|
-
previous = value;
|
|
1269
|
-
changed = !initialized || !(spec.isEqual ?? Object.is)(value, candidate);
|
|
1270
|
-
next = changed ? candidate : value;
|
|
1271
|
-
reset = build;
|
|
1272
|
-
return changed;
|
|
1273
|
-
},
|
|
1274
|
-
context: (active) => {
|
|
1275
|
-
assertScope(active);
|
|
1276
|
-
return Object.freeze({
|
|
1277
|
-
value: next,
|
|
1278
|
-
previous,
|
|
1279
|
-
changed,
|
|
1280
|
-
revision: revision + (changed ? 1 : 0),
|
|
1281
|
-
reset
|
|
1282
|
-
});
|
|
1283
|
-
},
|
|
1284
|
-
revision: () => revision,
|
|
1285
|
-
reset: () => reset,
|
|
1286
|
-
publish: () => {
|
|
1287
|
-
if (changed && initialized) revision++;
|
|
1288
|
-
value = next;
|
|
1289
|
-
initialized = true;
|
|
1290
|
-
},
|
|
1291
|
-
emit: (call) => {
|
|
1292
|
-
profile.materialized.notification();
|
|
1293
|
-
Array.from(listeners).forEach((listener) => call(listener));
|
|
1294
|
-
},
|
|
1295
|
-
clear: () => {
|
|
1296
|
-
next = value;
|
|
1297
|
-
previous = value;
|
|
1298
|
-
changed = false;
|
|
1299
|
-
reset = false;
|
|
1300
|
-
},
|
|
1301
|
-
release: () => {
|
|
1302
|
-
listeners.clear();
|
|
1303
|
-
instance = void 0;
|
|
1304
|
-
value = next = previous = void 0;
|
|
1305
|
-
}
|
|
1306
|
-
});
|
|
1307
|
-
const handle = Object.freeze({
|
|
1308
|
-
current: () => {
|
|
1309
|
-
owner.check();
|
|
1310
|
-
return value;
|
|
1311
|
-
},
|
|
1312
|
-
revision: () => {
|
|
1313
|
-
owner.check();
|
|
1314
|
-
return revision;
|
|
1315
|
-
},
|
|
1316
|
-
subscribe: (listener) => {
|
|
1317
|
-
owner.check();
|
|
1318
|
-
listeners.add(listener);
|
|
1319
|
-
return () => {
|
|
1320
|
-
listeners.delete(listener);
|
|
1321
|
-
};
|
|
1322
|
-
},
|
|
1323
|
-
rebuild: owner.rebuild,
|
|
1324
|
-
dispose: owner.dispose
|
|
1325
|
-
});
|
|
1326
|
-
owner.install(handle);
|
|
1327
|
-
return handle;
|
|
1328
|
-
};
|
|
1329
|
-
//#endregion
|
|
1330
1328
|
//#region core/src/projection/collection.ts
|
|
1329
|
+
const collectionHandles = /* @__PURE__ */ new WeakSet();
|
|
1331
1330
|
const readonlySet = (values) => {
|
|
1332
1331
|
const set = new Set(values);
|
|
1333
1332
|
const view = Object.freeze({
|
|
@@ -1655,10 +1654,374 @@ const createCollection = (scheduler, spec) => {
|
|
|
1655
1654
|
};
|
|
1656
1655
|
projectionHandles.add(handle.ids);
|
|
1657
1656
|
projectionHandles.add(handle.all);
|
|
1657
|
+
collectionHandles.add(handle);
|
|
1658
1658
|
owner.install(handle);
|
|
1659
1659
|
return Object.freeze(handle);
|
|
1660
1660
|
};
|
|
1661
1661
|
//#endregion
|
|
1662
|
+
//#region core/src/projection/source.ts
|
|
1663
|
+
const createSources = (scheduler) => {
|
|
1664
|
+
const documents = /* @__PURE__ */ new Map();
|
|
1665
|
+
const readables = /* @__PURE__ */ new Map();
|
|
1666
|
+
const collections = /* @__PURE__ */ new WeakSet();
|
|
1667
|
+
const document = (runtime) => {
|
|
1668
|
+
scheduler.assertIdle();
|
|
1669
|
+
const state = accessOf(runtime);
|
|
1670
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1671
|
+
const existing = documents.get(state);
|
|
1672
|
+
if (existing) return existing;
|
|
1673
|
+
const bindings = [];
|
|
1674
|
+
const make = (targets, collection) => {
|
|
1675
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1676
|
+
targets.forEach((value) => {
|
|
1677
|
+
if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
|
|
1678
|
+
});
|
|
1679
|
+
const old = bindings.find((entry) => Boolean(collection) === collections.has(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
|
|
1680
|
+
if (old) return old.handle;
|
|
1681
|
+
let commits = [];
|
|
1682
|
+
let reset = false;
|
|
1683
|
+
let orderDirty = false;
|
|
1684
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1685
|
+
let candidates;
|
|
1686
|
+
const record = {
|
|
1687
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1688
|
+
disposed: false,
|
|
1689
|
+
fault: void 0,
|
|
1690
|
+
revision: runtime.revision,
|
|
1691
|
+
reset: () => reset,
|
|
1692
|
+
clear: () => {
|
|
1693
|
+
commits = [];
|
|
1694
|
+
reset = orderDirty = false;
|
|
1695
|
+
keys.clear();
|
|
1696
|
+
candidates = void 0;
|
|
1697
|
+
},
|
|
1698
|
+
context: (active) => {
|
|
1699
|
+
assertScope(active);
|
|
1700
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1701
|
+
const read = collection ? readerFor(nodeAt(state.schema, collection.address, state.document) ?? collectionSchema(collection), {
|
|
1702
|
+
root: () => state.document,
|
|
1703
|
+
active
|
|
1704
|
+
}, collection.address) : documentReader(state.schema, () => state.document, active);
|
|
1705
|
+
return Object.freeze({
|
|
1706
|
+
read,
|
|
1707
|
+
target: collection,
|
|
1708
|
+
revision: runtime.revision(),
|
|
1709
|
+
commits: Object.freeze(commits.slice()),
|
|
1710
|
+
reset: record.reset(),
|
|
1711
|
+
...collection ? { candidates: candidates ??= Object.freeze({
|
|
1712
|
+
keys: Object.freeze([...keys]),
|
|
1713
|
+
orderDirty
|
|
1714
|
+
}) } : {}
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
const handle = collection ? {} : {
|
|
1719
|
+
collection: (pick) => {
|
|
1720
|
+
scheduler.assertIdle();
|
|
1721
|
+
const selector = state.schema.collection(pick);
|
|
1722
|
+
return make([selector], selector);
|
|
1723
|
+
},
|
|
1724
|
+
targets: (...selected) => {
|
|
1725
|
+
scheduler.assertIdle();
|
|
1726
|
+
if (!selected.length) throw new TypeError("Expected at least one target.");
|
|
1727
|
+
return make(selected);
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
Object.freeze(handle);
|
|
1731
|
+
bindings.push({
|
|
1732
|
+
handle,
|
|
1733
|
+
targets,
|
|
1734
|
+
record
|
|
1735
|
+
});
|
|
1736
|
+
if (collection) collections.add(handle);
|
|
1737
|
+
scheduler.register(handle, record);
|
|
1738
|
+
receivers.set(record, (commit) => {
|
|
1739
|
+
commits.push(commit);
|
|
1740
|
+
reset ||= commit.kind === "replace";
|
|
1741
|
+
if (collection) {
|
|
1742
|
+
const change = commit.impact.collection(collection);
|
|
1743
|
+
if (change.kind === "reset") reset = true;
|
|
1744
|
+
else {
|
|
1745
|
+
change.added.forEach((key) => keys.add(key));
|
|
1746
|
+
change.removed.forEach((key) => keys.add(key));
|
|
1747
|
+
change.updated.forEach((key) => keys.add(key));
|
|
1748
|
+
orderDirty ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
1749
|
+
}
|
|
1750
|
+
candidates = void 0;
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
return handle;
|
|
1754
|
+
};
|
|
1755
|
+
const receivers = /* @__PURE__ */ new Map();
|
|
1756
|
+
const handle = make([]);
|
|
1757
|
+
const guard = (locked) => {
|
|
1758
|
+
state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
|
|
1759
|
+
};
|
|
1760
|
+
scheduler.guards.add(guard);
|
|
1761
|
+
const unsubscribe = attachProjection(runtime, {
|
|
1762
|
+
capture: (commit) => {
|
|
1763
|
+
for (const binding of bindings) if (!binding.targets.length || binding.targets.some(commit.impact.affects)) {
|
|
1764
|
+
receivers.get(binding.record)(commit);
|
|
1765
|
+
scheduler.capture(binding.record);
|
|
1766
|
+
}
|
|
1767
|
+
},
|
|
1768
|
+
settle: scheduler.settle,
|
|
1769
|
+
flush: scheduler.flush,
|
|
1770
|
+
dispose: () => {
|
|
1771
|
+
bindings.forEach(({ record }) => {
|
|
1772
|
+
record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1773
|
+
scheduler.capture(record);
|
|
1774
|
+
});
|
|
1775
|
+
scheduler.run();
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
scheduler.cleanups.add(() => {
|
|
1779
|
+
unsubscribe();
|
|
1780
|
+
scheduler.guards.delete(guard);
|
|
1781
|
+
});
|
|
1782
|
+
documents.set(state, handle);
|
|
1783
|
+
return handle;
|
|
1784
|
+
};
|
|
1785
|
+
const valueSource = (initial, equal) => {
|
|
1786
|
+
let value = initial;
|
|
1787
|
+
let previous = initial;
|
|
1788
|
+
let revision = 0;
|
|
1789
|
+
const handle = Object.freeze({});
|
|
1790
|
+
const record = {
|
|
1791
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1792
|
+
disposed: false,
|
|
1793
|
+
fault: void 0,
|
|
1794
|
+
revision: () => revision,
|
|
1795
|
+
reset: () => false,
|
|
1796
|
+
clear: () => {
|
|
1797
|
+
previous = value;
|
|
1798
|
+
},
|
|
1799
|
+
context: (active) => {
|
|
1800
|
+
assertScope(active);
|
|
1801
|
+
return Object.freeze({
|
|
1802
|
+
value,
|
|
1803
|
+
previous,
|
|
1804
|
+
revision,
|
|
1805
|
+
changed: !equal(previous, value),
|
|
1806
|
+
reset: false
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
scheduler.register(handle, record);
|
|
1811
|
+
const accept = (next) => {
|
|
1812
|
+
scheduler.assertIdle();
|
|
1813
|
+
const recovering = record.fault !== void 0;
|
|
1814
|
+
if (equal(value, next) && !recovering) return;
|
|
1815
|
+
record.fault = void 0;
|
|
1816
|
+
value = next;
|
|
1817
|
+
revision++;
|
|
1818
|
+
scheduler.capture(record);
|
|
1819
|
+
};
|
|
1820
|
+
return {
|
|
1821
|
+
source: handle,
|
|
1822
|
+
accept,
|
|
1823
|
+
set(next) {
|
|
1824
|
+
accept(next);
|
|
1825
|
+
scheduler.run();
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
};
|
|
1829
|
+
return {
|
|
1830
|
+
dispose: () => {
|
|
1831
|
+
documents.clear();
|
|
1832
|
+
readables.clear();
|
|
1833
|
+
},
|
|
1834
|
+
document,
|
|
1835
|
+
input: (initial, options) => {
|
|
1836
|
+
const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
|
|
1837
|
+
return Object.freeze({
|
|
1838
|
+
source,
|
|
1839
|
+
set
|
|
1840
|
+
});
|
|
1841
|
+
},
|
|
1842
|
+
fromReadable: (readable, options) => {
|
|
1843
|
+
scheduler.assertIdle();
|
|
1844
|
+
if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
|
|
1845
|
+
const equal = options?.isEqual ?? Object.is;
|
|
1846
|
+
let bindings = readables.get(readable);
|
|
1847
|
+
const old = bindings?.get(equal);
|
|
1848
|
+
if (old) return old.source;
|
|
1849
|
+
const input = valueSource(readable.current(), equal);
|
|
1850
|
+
if (bindings) {
|
|
1851
|
+
bindings.set(equal, {
|
|
1852
|
+
source: input.source,
|
|
1853
|
+
accept: (value) => input.accept(value)
|
|
1854
|
+
});
|
|
1855
|
+
return input.source;
|
|
1856
|
+
}
|
|
1857
|
+
bindings = new Map([[equal, {
|
|
1858
|
+
source: input.source,
|
|
1859
|
+
accept: (value) => input.accept(value)
|
|
1860
|
+
}]]);
|
|
1861
|
+
const members = bindings;
|
|
1862
|
+
const receive = (settle = true) => {
|
|
1863
|
+
if (!scheduler.active) return;
|
|
1864
|
+
let value;
|
|
1865
|
+
try {
|
|
1866
|
+
value = readable.current();
|
|
1867
|
+
} catch (cause) {
|
|
1868
|
+
for (const member of members.values()) {
|
|
1869
|
+
const record = scheduler.source(member.source);
|
|
1870
|
+
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1871
|
+
scheduler.capture(record);
|
|
1872
|
+
}
|
|
1873
|
+
if (settle) scheduler.run();
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
for (const member of members.values()) try {
|
|
1877
|
+
member.accept(value);
|
|
1878
|
+
} catch (cause) {
|
|
1879
|
+
const record = scheduler.source(member.source);
|
|
1880
|
+
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1881
|
+
scheduler.capture(record);
|
|
1882
|
+
}
|
|
1883
|
+
if (settle) scheduler.run();
|
|
1884
|
+
};
|
|
1885
|
+
let unsubscribe;
|
|
1886
|
+
let detach;
|
|
1887
|
+
try {
|
|
1888
|
+
const owner = documentReadableOwner(readable);
|
|
1889
|
+
if (owner) {
|
|
1890
|
+
document(owner);
|
|
1891
|
+
detach = attachProjection(owner, {
|
|
1892
|
+
capture: () => receive(false),
|
|
1893
|
+
settle: scheduler.settle,
|
|
1894
|
+
flush: scheduler.flush,
|
|
1895
|
+
dispose: () => {
|
|
1896
|
+
for (const member of members.values()) {
|
|
1897
|
+
const record = scheduler.source(member.source);
|
|
1898
|
+
record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1899
|
+
scheduler.capture(record);
|
|
1900
|
+
}
|
|
1901
|
+
scheduler.run();
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
unsubscribe = readable.subscribe(() => receive());
|
|
1906
|
+
input.accept(readable.current());
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
try {
|
|
1909
|
+
unsubscribe?.();
|
|
1910
|
+
detach?.();
|
|
1911
|
+
} finally {
|
|
1912
|
+
scheduler.unregisterSource(input.source);
|
|
1913
|
+
}
|
|
1914
|
+
throw error;
|
|
1915
|
+
}
|
|
1916
|
+
scheduler.cleanups.add(unsubscribe);
|
|
1917
|
+
if (detach) scheduler.cleanups.add(detach);
|
|
1918
|
+
readables.set(readable, bindings);
|
|
1919
|
+
scheduler.run();
|
|
1920
|
+
return input.source;
|
|
1921
|
+
},
|
|
1922
|
+
assertCollection: (handle) => {
|
|
1923
|
+
scheduler.source(handle);
|
|
1924
|
+
if (!collections.has(handle) && !collectionHandles.has(handle)) throw new Error("map requires a collection source.");
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
};
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region core/src/projection/value.ts
|
|
1930
|
+
const createValue = (scheduler, spec, options) => {
|
|
1931
|
+
let instance;
|
|
1932
|
+
let value;
|
|
1933
|
+
let next;
|
|
1934
|
+
let previous;
|
|
1935
|
+
let initialized = false;
|
|
1936
|
+
let revision = 0;
|
|
1937
|
+
let changed = false;
|
|
1938
|
+
let reset = false;
|
|
1939
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1940
|
+
const owner = createNode(scheduler, spec, {
|
|
1941
|
+
evaluate: (sources, build) => {
|
|
1942
|
+
let candidate;
|
|
1943
|
+
if (build || !instance) {
|
|
1944
|
+
profile.materialized.rebuilt();
|
|
1945
|
+
instance = void 0;
|
|
1946
|
+
const built = spec.build(sources);
|
|
1947
|
+
assertSynchronous(built);
|
|
1948
|
+
instance = built;
|
|
1949
|
+
candidate = built.value;
|
|
1950
|
+
} else {
|
|
1951
|
+
profile.materialized.updated();
|
|
1952
|
+
const result = instance.update(sources);
|
|
1953
|
+
assertSynchronous(result);
|
|
1954
|
+
if (result.kind === "rebuild") {
|
|
1955
|
+
instance = void 0;
|
|
1956
|
+
const built = spec.build(sources);
|
|
1957
|
+
assertSynchronous(built);
|
|
1958
|
+
instance = built;
|
|
1959
|
+
candidate = built.value;
|
|
1960
|
+
build = true;
|
|
1961
|
+
} else candidate = result.kind === "changed" ? result.value : value;
|
|
1962
|
+
}
|
|
1963
|
+
previous = value;
|
|
1964
|
+
changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
|
|
1965
|
+
next = changed ? candidate : value;
|
|
1966
|
+
reset = build;
|
|
1967
|
+
return changed;
|
|
1968
|
+
},
|
|
1969
|
+
context: (active) => {
|
|
1970
|
+
assertScope(active);
|
|
1971
|
+
return Object.freeze({
|
|
1972
|
+
value: next,
|
|
1973
|
+
previous,
|
|
1974
|
+
changed,
|
|
1975
|
+
revision: revision + (changed ? 1 : 0),
|
|
1976
|
+
reset
|
|
1977
|
+
});
|
|
1978
|
+
},
|
|
1979
|
+
revision: () => revision,
|
|
1980
|
+
reset: () => reset,
|
|
1981
|
+
publish: () => {
|
|
1982
|
+
if (changed && initialized) revision++;
|
|
1983
|
+
value = next;
|
|
1984
|
+
initialized = true;
|
|
1985
|
+
},
|
|
1986
|
+
emit: (call) => {
|
|
1987
|
+
profile.materialized.notification();
|
|
1988
|
+
Array.from(listeners).forEach((listener) => call(listener));
|
|
1989
|
+
},
|
|
1990
|
+
clear: () => {
|
|
1991
|
+
next = value;
|
|
1992
|
+
previous = value;
|
|
1993
|
+
changed = false;
|
|
1994
|
+
reset = false;
|
|
1995
|
+
},
|
|
1996
|
+
release: () => {
|
|
1997
|
+
listeners.clear();
|
|
1998
|
+
instance = void 0;
|
|
1999
|
+
value = next = previous = void 0;
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
const handle = Object.freeze({
|
|
2003
|
+
current: () => {
|
|
2004
|
+
owner.check();
|
|
2005
|
+
return value;
|
|
2006
|
+
},
|
|
2007
|
+
revision: () => {
|
|
2008
|
+
owner.check();
|
|
2009
|
+
return revision;
|
|
2010
|
+
},
|
|
2011
|
+
subscribe: (listener) => {
|
|
2012
|
+
owner.check();
|
|
2013
|
+
listeners.add(listener);
|
|
2014
|
+
return () => {
|
|
2015
|
+
listeners.delete(listener);
|
|
2016
|
+
};
|
|
2017
|
+
},
|
|
2018
|
+
rebuild: owner.rebuild,
|
|
2019
|
+
dispose: owner.dispose
|
|
2020
|
+
});
|
|
2021
|
+
owner.install(handle);
|
|
2022
|
+
return handle;
|
|
2023
|
+
};
|
|
2024
|
+
//#endregion
|
|
1662
2025
|
//#region core/src/projection/runtime.ts
|
|
1663
2026
|
const debug = /* @__PURE__ */ new WeakMap();
|
|
1664
2027
|
const projectionDebug = (runtime) => {
|
|
@@ -1669,54 +2032,85 @@ const projectionDebug = (runtime) => {
|
|
|
1669
2032
|
const createProjectionRuntime = (options) => {
|
|
1670
2033
|
const scheduler = createScheduler(options.onError);
|
|
1671
2034
|
const sources = createSources(scheduler);
|
|
2035
|
+
function value(input, computeOrOptions, options) {
|
|
2036
|
+
if (typeof computeOrOptions !== "function") return createValue(scheduler, input, computeOrOptions);
|
|
2037
|
+
const compute = (input) => {
|
|
2038
|
+
const result = computeOrOptions(input);
|
|
2039
|
+
assertSynchronous(result);
|
|
2040
|
+
return result;
|
|
2041
|
+
};
|
|
2042
|
+
return createValue(scheduler, {
|
|
2043
|
+
sources: input,
|
|
2044
|
+
build: (input) => ({
|
|
2045
|
+
value: compute(input),
|
|
2046
|
+
update: (input) => ({
|
|
2047
|
+
kind: "changed",
|
|
2048
|
+
value: compute(input)
|
|
2049
|
+
})
|
|
2050
|
+
})
|
|
2051
|
+
}, options);
|
|
2052
|
+
}
|
|
2053
|
+
const map = (source, mapper, options) => {
|
|
2054
|
+
sources.assertCollection(source);
|
|
2055
|
+
const calculate = (id, entry) => {
|
|
2056
|
+
const value = mapper(id, entry);
|
|
2057
|
+
assertSynchronous(value);
|
|
2058
|
+
return value;
|
|
2059
|
+
};
|
|
2060
|
+
return createCollection(scheduler, {
|
|
2061
|
+
sources: { source },
|
|
2062
|
+
isEqual: options?.isEqual,
|
|
2063
|
+
build: ({ sources, writer }) => {
|
|
2064
|
+
const input = sources.source;
|
|
2065
|
+
const read = "read" in input ? input.read : input;
|
|
2066
|
+
const ids = read.ids();
|
|
2067
|
+
profile.collectionView.idsScanned(ids.length);
|
|
2068
|
+
for (const id of ids) {
|
|
2069
|
+
profile.collectionView.mapped();
|
|
2070
|
+
writer.set(id, calculate(id, read.get(id)));
|
|
2071
|
+
}
|
|
2072
|
+
writer.order(ids);
|
|
2073
|
+
return { update: ({ sources, writer }) => {
|
|
2074
|
+
const input = sources.source;
|
|
2075
|
+
if (input.reset) return { kind: "rebuild" };
|
|
2076
|
+
const read = "read" in input ? input.read : input;
|
|
2077
|
+
let keys;
|
|
2078
|
+
let structural;
|
|
2079
|
+
if ("candidates" in input) {
|
|
2080
|
+
keys = input.candidates.keys;
|
|
2081
|
+
structural = input.candidates.orderDirty;
|
|
2082
|
+
} else {
|
|
2083
|
+
const change = input.change;
|
|
2084
|
+
if (!change) return;
|
|
2085
|
+
if (change.kind === "reset") return { kind: "rebuild" };
|
|
2086
|
+
keys = new Set([
|
|
2087
|
+
...change.added,
|
|
2088
|
+
...change.removed,
|
|
2089
|
+
...change.updated
|
|
2090
|
+
]);
|
|
2091
|
+
structural = Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
2092
|
+
}
|
|
2093
|
+
for (const key of keys) if (!read.has(key)) writer.remove(key);
|
|
2094
|
+
else {
|
|
2095
|
+
profile.collectionView.mapped();
|
|
2096
|
+
writer.set(key, calculate(key, read.get(key)));
|
|
2097
|
+
}
|
|
2098
|
+
if (structural) {
|
|
2099
|
+
const ids = read.ids();
|
|
2100
|
+
profile.collectionView.idsScanned(ids.length);
|
|
2101
|
+
writer.order(ids);
|
|
2102
|
+
}
|
|
2103
|
+
} };
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
};
|
|
1672
2107
|
const runtime = {
|
|
1673
2108
|
document: sources.document,
|
|
1674
2109
|
input: sources.input,
|
|
1675
2110
|
fromReadable: sources.fromReadable,
|
|
1676
|
-
value
|
|
1677
|
-
collection: (spec) => createCollection(scheduler, spec),
|
|
1678
|
-
map
|
|
1679
|
-
sources.collectionTarget(source);
|
|
1680
|
-
return createCollection(scheduler, {
|
|
1681
|
-
sources: { source },
|
|
1682
|
-
isEqual: options?.isEqual,
|
|
1683
|
-
build: ({ sources, writer }) => {
|
|
1684
|
-
const ids = sources.source.read.ids();
|
|
1685
|
-
profile.collectionView.idsScanned(ids.length);
|
|
1686
|
-
for (const id of ids) {
|
|
1687
|
-
profile.collectionView.mapped();
|
|
1688
|
-
writer.set(id, mapper(id, sources.source.read.get(id)));
|
|
1689
|
-
}
|
|
1690
|
-
writer.order(ids);
|
|
1691
|
-
return { update: ({ sources, writer }) => {
|
|
1692
|
-
const input = sources.source;
|
|
1693
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1694
|
-
let structural = false;
|
|
1695
|
-
for (const commit of input.commits) {
|
|
1696
|
-
const impact = commit.impact.collection(input.target);
|
|
1697
|
-
if (impact.kind === "reset") return { kind: "rebuild" };
|
|
1698
|
-
impact.added.forEach((key) => keys.add(key));
|
|
1699
|
-
impact.removed.forEach((key) => keys.add(key));
|
|
1700
|
-
impact.updated.forEach((key) => keys.add(key));
|
|
1701
|
-
structural ||= Boolean(impact.added.size || impact.removed.size || impact.orderChanged);
|
|
1702
|
-
}
|
|
1703
|
-
for (const key of keys) {
|
|
1704
|
-
const entry = input.read.get(key);
|
|
1705
|
-
if (!entry) writer.remove(key);
|
|
1706
|
-
else {
|
|
1707
|
-
profile.collectionView.mapped();
|
|
1708
|
-
writer.set(key, mapper(key, entry));
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
if (structural) {
|
|
1712
|
-
const ids = input.read.ids();
|
|
1713
|
-
profile.collectionView.idsScanned(ids.length);
|
|
1714
|
-
writer.order(ids);
|
|
1715
|
-
}
|
|
1716
|
-
} };
|
|
1717
|
-
}
|
|
1718
|
-
});
|
|
1719
|
-
},
|
|
2111
|
+
value,
|
|
2112
|
+
collection: () => (spec) => createCollection(scheduler, spec),
|
|
2113
|
+
map,
|
|
1720
2114
|
batch: scheduler.batch,
|
|
1721
2115
|
dispose: () => {
|
|
1722
2116
|
try {
|
|
@@ -1730,6 +2124,6 @@ const createProjectionRuntime = (options) => {
|
|
|
1730
2124
|
return Object.freeze(runtime);
|
|
1731
2125
|
};
|
|
1732
2126
|
//#endregion
|
|
1733
|
-
export {
|
|
2127
|
+
export { id as A, set as B, restore as C, DocumentReentrancyError as D, DocumentDisposedError as E, locate as F, object as G, field as H, overlaps as I, table as J, optional as K, read as L, same as M, contains as N, address$1 as O, debugKey as P, resolveAddress as R, keys as S, validPositions as T, list as U, dict as V, map as W, variant as X, tree as Y, footprintsOverlap as _, bindDocumentReadable as a, at as b, notify as c, subscribeTargets as d, accessOf as f, decodeCommandFootprint as g, commandFootprint as h, ProjectionError as i, impact_target_exports as j, belongs as k, shareNotification as l, readWith as m, projectionDebug as n, createNotification as o, bindRuntimeAccess as p, schema as q, ProjectionDisposedError as r, disposeNotification as s, createProjectionRuntime as t, subscribeRoot as u, documentReader as v, valid as w, index as x, afterRemove as y, resolveLocated as z };
|
|
1734
2128
|
|
|
1735
|
-
//# sourceMappingURL=runtime-
|
|
2129
|
+
//# sourceMappingURL=runtime-DF1q9Gje.js.map
|