doxum 0.1.21 → 0.1.23
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 +35 -7
- package/dist/{advanced-FgdTwu5b.d.ts → advanced-CLJCWkHa.d.cts} +3 -3
- package/dist/{advanced-COFJtWMT.js → advanced-CRNHfJBu.js} +482 -446
- package/dist/advanced-CRNHfJBu.js.map +1 -0
- package/dist/{advanced-BRL5pN0y.cjs → advanced-Cle5g3B4.cjs} +615 -459
- package/dist/advanced-Cle5g3B4.cjs.map +1 -0
- package/dist/{advanced-DfKc7TZR.d.cts → advanced-d6J61er9.d.ts} +3 -3
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +1 -1
- package/dist/context-BcLKGfq8.cjs +149 -0
- package/dist/context-BcLKGfq8.cjs.map +1 -0
- package/dist/context-DFEaDUv5.js +84 -0
- package/dist/context-DFEaDUv5.js.map +1 -0
- package/dist/{contract-CdmDHRUq.d.cts → contract-SYJFP0lM.d.cts} +62 -88
- package/dist/{contract-KG5XnQZt.d.ts → contract-ULPQmiEs.d.ts} +62 -88
- package/dist/driver-BD3DQOW6.cjs +382 -0
- package/dist/driver-BD3DQOW6.cjs.map +1 -0
- package/dist/{driver-CLYJOGDQ.js → driver-yFfvDMX5.js} +155 -26
- package/dist/driver-yFfvDMX5.js.map +1 -0
- package/dist/index.cjs +2370 -406
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2293 -330
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +5 -5
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +1 -1
- package/dist/local-sync.d.ts +1 -1
- package/dist/local-sync.js +2 -2
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +2 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +0 -1
- package/dist/react.d.ts +1 -2
- package/dist/react.js +3 -70
- package/dist/react.js.map +1 -1
- package/package.json +1 -6
- package/skills/doxum-runtime/SKILL.md +7 -1
- package/skills/doxum-runtime/references/guide.en.md +17 -6
- package/skills/doxum-runtime/references/guide.zh-CN.md +15 -5
- package/skills/doxum-runtime/references/invariants.en.md +5 -3
- package/skills/doxum-runtime/references/invariants.zh-CN.md +4 -3
- package/skills/doxum-runtime/references/projections.en.md +5 -0
- package/skills/doxum-runtime/references/projections.zh-CN.md +5 -0
- package/dist/access-BeSV5ve-.js +0 -816
- package/dist/access-BeSV5ve-.js.map +0 -1
- package/dist/access-Bienf28h.cjs +0 -911
- package/dist/access-Bienf28h.cjs.map +0 -1
- package/dist/advanced-BRL5pN0y.cjs.map +0 -1
- package/dist/advanced-COFJtWMT.js.map +0 -1
- package/dist/driver-CLYJOGDQ.js.map +0 -1
- package/dist/driver-Djr3BVvo.cjs +0 -241
- package/dist/driver-Djr3BVvo.cjs.map +0 -1
- package/dist/integration-C4Isp8lJ.js +0 -26
- package/dist/integration-C4Isp8lJ.js.map +0 -1
- package/dist/integration-Z6MsfFI7.cjs +0 -31
- package/dist/integration-Z6MsfFI7.cjs.map +0 -1
- package/dist/integration.cjs +0 -12
- package/dist/integration.d.cts +0 -17
- package/dist/integration.d.ts +0 -17
- package/dist/integration.js +0 -4
- package/dist/issue-DjK1xp5K.cjs +0 -881
- package/dist/issue-DjK1xp5K.cjs.map +0 -1
- package/dist/issue-W0hT8u8D.js +0 -642
- package/dist/issue-W0hT8u8D.js.map +0 -1
- package/dist/notification-Bkjau6B6.js +0 -232
- package/dist/notification-Bkjau6B6.js.map +0 -1
- package/dist/notification-DJfouMEb.cjs +0 -309
- package/dist/notification-DJfouMEb.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,905 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { A as checkValue, B as insert$2, C as optional, D as ParseError, E as variant, F as parse, G as listSequence, H as installKeys, I as compiledLayout, J as toArray, K as matches, L as compiledShape, M as copyValue, N as equalTreeNode, O as checkKey, P as equalValue, R as equal, S as object, T as tree, U as installList, V as install, W as keys, Y as profile, _ as collectionEntryNode, a as observe, b as list, c as projectionRef, d as createScheduler, f as ProjectionDisposedError, g as snapshotCollectionView, h as mapRead, i as isProjection, j as copyTreeNode, k as checkTreePayload, l as assertScope, m as collectionView, n as derive, o as ownProjection, p as ProjectionError, q as remove$4, r as input, s as producerOf, t as incremental, u as assertSynchronous, v as compilePath, w as table, x as map, y as field, z as indexedKeys } from "./advanced-CRNHfJBu.js";
|
|
2
|
+
import { a as is, c as validate, d as isRecord, i as contains$1, l as installOwn, n as contextOf, o as parent, r as children, t as bindContext } from "./context-DFEaDUv5.js";
|
|
3
|
+
import { a as sealChanges, c as fail, i as decodeChanges, l as invalidValue, o as AddressIndex, s as MutationRejected, t as assertRuntimeWritable } from "./driver-yFfvDMX5.js";
|
|
4
|
+
//#region core/src/address/resolve.ts
|
|
5
|
+
const variantNode = (node, value) => {
|
|
6
|
+
const tag = isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
|
|
7
|
+
return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
|
|
8
|
+
};
|
|
9
|
+
const step = (nodeInput, value, segment) => {
|
|
10
|
+
let node = nodeInput;
|
|
11
|
+
if (!node) return void 0;
|
|
12
|
+
if (node.kind === "variant") node = variantNode(node, value);
|
|
13
|
+
if (!node) return void 0;
|
|
14
|
+
if (node.kind === "object") return Object.hasOwn(node.shape, segment) ? node.shape[segment] : void 0;
|
|
15
|
+
if (node.kind === "table" || node.kind === "map") return node.value;
|
|
16
|
+
if (node.kind === "list") return node.value;
|
|
17
|
+
};
|
|
18
|
+
const nodeAt = (schema, address, document) => {
|
|
19
|
+
let node = schema;
|
|
20
|
+
let value = document;
|
|
21
|
+
for (const segment of address) {
|
|
22
|
+
const resolved = step(node, value, segment);
|
|
23
|
+
value = readSegment(value, segment, node);
|
|
24
|
+
node = resolved;
|
|
25
|
+
if (!node) return void 0;
|
|
26
|
+
}
|
|
27
|
+
return node;
|
|
28
|
+
};
|
|
29
|
+
const readSegment = (value, segment, node) => {
|
|
30
|
+
if (!isRecord(value) && !Array.isArray(value)) return void 0;
|
|
31
|
+
if (node?.kind === "list" && Array.isArray(value)) return value[indexedKeys(value, node.keyOf).index(segment)];
|
|
32
|
+
if (node?.kind === "table" && isRecord(value) && isRecord(value.byId)) return Object.hasOwn(value.byId, segment) ? value.byId[segment] : void 0;
|
|
33
|
+
return Object.prototype.hasOwnProperty.call(value, segment) ? value[segment] : void 0;
|
|
34
|
+
};
|
|
35
|
+
const resolveContainer = (owner, generation, at, node, value) => {
|
|
36
|
+
if (!node || !isRecord(value) && !Array.isArray(value)) return void 0;
|
|
37
|
+
const parent = node.kind === "table" && isRecord(value) ? value.byId : value;
|
|
38
|
+
if (!isRecord(parent) && !Array.isArray(parent)) return void 0;
|
|
39
|
+
if (node.kind === "tree") return void 0;
|
|
40
|
+
const layout = compiledLayout(node, value);
|
|
41
|
+
if (!layout) return void 0;
|
|
42
|
+
return {
|
|
43
|
+
owner,
|
|
44
|
+
generation,
|
|
45
|
+
at,
|
|
46
|
+
node,
|
|
47
|
+
parent,
|
|
48
|
+
value,
|
|
49
|
+
layout
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const resolveTreeContainer = (owner, generation, at, node, value) => node?.kind === "tree" && is(value) ? {
|
|
53
|
+
owner,
|
|
54
|
+
generation,
|
|
55
|
+
at,
|
|
56
|
+
node,
|
|
57
|
+
value
|
|
58
|
+
} : void 0;
|
|
59
|
+
const memberKey = (container, key) => container.node.kind === "list" ? indexedKeys(container.parent, container.node.keyOf).index(key) : key;
|
|
60
|
+
/** One recent path per transaction; structural writes invalidate all retained locations. */
|
|
61
|
+
const createAddressResolver = (schema, root) => {
|
|
62
|
+
const prefix = [];
|
|
63
|
+
return {
|
|
64
|
+
container: (owner, generation, at) => {
|
|
65
|
+
if (!at.length) return resolveContainer(owner, generation, at, schema, root);
|
|
66
|
+
const location = resolveWithPrefix(schema, root, at, prefix);
|
|
67
|
+
if (!location || !Object.hasOwn(location.parent, location.key)) return void 0;
|
|
68
|
+
return resolveContainer(owner, generation, at, location.node, location.parent[location.key]);
|
|
69
|
+
},
|
|
70
|
+
invalidate: () => {
|
|
71
|
+
prefix.length = 0;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/** A subtree root is a value, not a fabricated member of an ObjectNode. */
|
|
76
|
+
const resolveValue = (schema, root, address, prefix) => {
|
|
77
|
+
if (!address.length) return {
|
|
78
|
+
node: schema,
|
|
79
|
+
value: root
|
|
80
|
+
};
|
|
81
|
+
const location = resolveWithPrefix(schema, root, address, prefix);
|
|
82
|
+
return location && {
|
|
83
|
+
node: location.node,
|
|
84
|
+
value: Object.hasOwn(location.parent, location.key) ? location.parent[location.key] : void 0
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
/** Resolves schema and document location in one address traversal. */
|
|
88
|
+
const resolveLocated = (schema, root, address) => resolveWithPrefix(schema, root, address);
|
|
89
|
+
const resolveWithPrefix = (schema, root, address, prefix) => {
|
|
90
|
+
if (address.length === 0) return void 0;
|
|
91
|
+
let node = schema;
|
|
92
|
+
let current = root;
|
|
93
|
+
let start = 0;
|
|
94
|
+
if (prefix) {
|
|
95
|
+
while (start < prefix.length && start < address.length - 1 && prefix[start].segment === address[start]) start++;
|
|
96
|
+
prefix.length = start;
|
|
97
|
+
if (start) {
|
|
98
|
+
const retained = prefix[start - 1];
|
|
99
|
+
node = retained.node;
|
|
100
|
+
current = retained.value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let cacheable = true;
|
|
104
|
+
for (let index = start; index < address.length - 1; index += 1) {
|
|
105
|
+
profile.address.schemaStep();
|
|
106
|
+
profile.address.documentStep();
|
|
107
|
+
const resolved = step(node, current, address[index]);
|
|
108
|
+
if (!resolved) return void 0;
|
|
109
|
+
if (node?.kind !== "object" && node?.kind !== "table" && node?.kind !== "map") cacheable = false;
|
|
110
|
+
current = readSegment(current, address[index], node);
|
|
111
|
+
node = resolved;
|
|
112
|
+
if (prefix && cacheable) prefix.push({
|
|
113
|
+
segment: address[index],
|
|
114
|
+
node: resolved,
|
|
115
|
+
value: current
|
|
116
|
+
});
|
|
117
|
+
if (!node) return void 0;
|
|
118
|
+
}
|
|
119
|
+
const last = address[address.length - 1];
|
|
120
|
+
return resolveChild(node, current, last);
|
|
121
|
+
};
|
|
122
|
+
/** Resolves one member from an already current schema/container pair. */
|
|
123
|
+
const resolveChild = (node, current, last) => {
|
|
124
|
+
profile.address.schemaStep();
|
|
125
|
+
profile.address.documentStep();
|
|
126
|
+
const resolved = step(node, current, last);
|
|
127
|
+
if (!resolved || !isRecord(current) && !Array.isArray(current)) return void 0;
|
|
128
|
+
if (node?.kind === "table" && isRecord(current) && isRecord(current.byId)) return {
|
|
129
|
+
node: resolved,
|
|
130
|
+
parent: current.byId,
|
|
131
|
+
key: last
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
node: resolved,
|
|
135
|
+
parent: current,
|
|
136
|
+
key: node?.kind === "list" && Array.isArray(current) ? indexedKeys(current, node.keyOf).index(last) : last
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region core/src/mutation/operations/map.ts
|
|
141
|
+
function put(session, container, id, value) {
|
|
142
|
+
if (container.node.kind !== "map") return fail(container.at, "invalid-collection", "Expected a map.");
|
|
143
|
+
session.writeMember(container, id, value, "set");
|
|
144
|
+
}
|
|
145
|
+
function remove$3(session, container, id) {
|
|
146
|
+
if (container.node.kind !== "map") return fail(container.at, "invalid-collection", "Expected a map.");
|
|
147
|
+
session.writeMember(container, id, void 0, "remove");
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region core/src/order/anchor.ts
|
|
151
|
+
const length = (keys) => keys.length;
|
|
152
|
+
const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
|
|
153
|
+
/** Plan one relative move against the order that remains after removing the selection. */
|
|
154
|
+
const moveSelection = (current, selection, position) => {
|
|
155
|
+
if (!selection.length) return {
|
|
156
|
+
kind: "ok",
|
|
157
|
+
order: current
|
|
158
|
+
};
|
|
159
|
+
const selected = /* @__PURE__ */ new Set();
|
|
160
|
+
for (const key of selection) {
|
|
161
|
+
if (selected.has(key)) return { kind: "duplicate" };
|
|
162
|
+
selected.add(key);
|
|
163
|
+
}
|
|
164
|
+
const moved = [];
|
|
165
|
+
const remaining = [];
|
|
166
|
+
for (const key of current) (selected.has(key) ? moved : remaining).push(key);
|
|
167
|
+
if (moved.length !== selected.size) return { kind: "missing" };
|
|
168
|
+
if (!valid(remaining, position)) return { kind: "invalid-anchor" };
|
|
169
|
+
const insertion = index(remaining, position);
|
|
170
|
+
return {
|
|
171
|
+
kind: "ok",
|
|
172
|
+
order: [
|
|
173
|
+
...remaining.slice(0, insertion),
|
|
174
|
+
...moved,
|
|
175
|
+
...remaining.slice(insertion)
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
const index = (keys, anchor) => {
|
|
180
|
+
if (!anchor) return length(keys);
|
|
181
|
+
if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
|
|
182
|
+
if ("before" in anchor) return keyIndex(keys, anchor.before);
|
|
183
|
+
return keyIndex(keys, anchor.after) + 1;
|
|
184
|
+
};
|
|
185
|
+
const valid = (keys, anchor) => {
|
|
186
|
+
if (!anchor) return true;
|
|
187
|
+
if (typeof anchor !== "object" || anchor === null) return false;
|
|
188
|
+
if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
|
|
189
|
+
if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
|
|
190
|
+
return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
|
|
191
|
+
};
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region core/src/mutation/operations/table.ts
|
|
194
|
+
function create(session, container, entries, position) {
|
|
195
|
+
const { at, node, value } = container;
|
|
196
|
+
if (node.kind !== "table") return fail(at, "invalid-collection", "Expected a table.");
|
|
197
|
+
const table = value;
|
|
198
|
+
if (!valid(table.ids, position)) return fail(at, "invalid-anchor", "Unknown table anchor.");
|
|
199
|
+
const ids = /* @__PURE__ */ new Set();
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
if (Object.hasOwn(table.byId, entry.id) || ids.has(entry.id)) return fail(at, "duplicate-entity", "Table key already exists.");
|
|
202
|
+
ids.add(entry.id);
|
|
203
|
+
}
|
|
204
|
+
if (!entries.length) return;
|
|
205
|
+
const index$1 = index(table.ids, position);
|
|
206
|
+
for (const entry of entries) session.writeMember(container, entry.id, entry.value, "set");
|
|
207
|
+
const length = table.ids.length;
|
|
208
|
+
table.ids.length += entries.length;
|
|
209
|
+
table.ids.copyWithin(index$1 + entries.length, index$1, length);
|
|
210
|
+
for (let i = 0; i < entries.length; i++) table.ids[index$1 + i] = entries[i].id;
|
|
211
|
+
}
|
|
212
|
+
function remove$2(session, container, ids) {
|
|
213
|
+
const { at, node, value } = container;
|
|
214
|
+
if (node.kind !== "table") return fail(at, "invalid-collection", "Expected a table.");
|
|
215
|
+
const table = value;
|
|
216
|
+
for (const id of ids) if (!Object.hasOwn(table.byId, id)) return fail(at, "missing-entity", "Table key does not exist.");
|
|
217
|
+
if (!ids.length) return;
|
|
218
|
+
const removed = new Set(ids);
|
|
219
|
+
for (const id of removed) session.writeMember(container, id, void 0, "remove");
|
|
220
|
+
table.ids = table.ids.filter((id) => !removed.has(id));
|
|
221
|
+
}
|
|
222
|
+
function replace$3(session, container, id, value) {
|
|
223
|
+
const { at, node, value: tableValue } = container;
|
|
224
|
+
if (node.kind !== "table") return fail(at, "invalid-collection", "Expected a table.");
|
|
225
|
+
const table = tableValue;
|
|
226
|
+
if (!Object.hasOwn(table.byId, id)) return fail(at.concat(id), "missing-entity", "Table key does not exist.");
|
|
227
|
+
session.writeMember(container, id, value, "set");
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region core/src/mutation/operations/list.ts
|
|
231
|
+
function insert$1(session, container, value, position) {
|
|
232
|
+
const { at, node, value: current } = container;
|
|
233
|
+
if (node.kind !== "list") return fail(at, "invalid-list-key", "Expected a list.");
|
|
234
|
+
session.validate(node.value, value, at);
|
|
235
|
+
const id = node.keyOf(value);
|
|
236
|
+
if (typeof id !== "string") return fail(at, "invalid-list-key", "List key must be a string.");
|
|
237
|
+
const items = current, keys$1 = keys(items, node.keyOf);
|
|
238
|
+
if (keys$1.index(id) >= 0) return fail(at, "duplicate-list-item", "List key already exists.");
|
|
239
|
+
if (!valid(keys$1, position)) return fail(at, "invalid-anchor", "Unknown list anchor.");
|
|
240
|
+
session.recorder.order(container);
|
|
241
|
+
session.recorder.member(container, id, session.definition(container, id), -1);
|
|
242
|
+
insert$2(items, index(keys$1, position), value);
|
|
243
|
+
session.invalidate();
|
|
244
|
+
}
|
|
245
|
+
function replace$2(session, container, id, value) {
|
|
246
|
+
const { at, node, value: items } = container;
|
|
247
|
+
if (node.kind !== "list") return fail(at, "invalid-list-key", "Expected a list.");
|
|
248
|
+
const index = indexedKeys(items, node.keyOf).index(id);
|
|
249
|
+
if (index < 0) return fail(at, "missing-list-item", "List key does not exist.");
|
|
250
|
+
session.writeMember(container, id, value, "set", index);
|
|
251
|
+
}
|
|
252
|
+
function remove$1(session, container, id) {
|
|
253
|
+
const { at, node, value } = container;
|
|
254
|
+
if (node.kind !== "list") return fail(at, "missing-list-item", "List key does not exist.");
|
|
255
|
+
const index = keys(value, node.keyOf).index(id);
|
|
256
|
+
if (index < 0) return fail(at, "missing-list-item", "List key does not exist.");
|
|
257
|
+
session.writeMember(container, id, void 0, "remove", index);
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region core/src/mutation/operations/tree.ts
|
|
261
|
+
const node$1 = (tree, id) => Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
262
|
+
const positionIndex = (index, length, at) => {
|
|
263
|
+
if (index !== void 0 && (!Number.isInteger(index) || index < 0)) return fail(at, "invalid-tree-index", "Tree index must be nonnegative.");
|
|
264
|
+
return Math.min(index ?? length, length);
|
|
265
|
+
};
|
|
266
|
+
const insert = (session, container, id, value, position) => {
|
|
267
|
+
const { at, node: schema, value: tree } = container;
|
|
268
|
+
if (typeof id !== "string") return fail(at, "invalid-key", "Tree keys must be strings.");
|
|
269
|
+
if (contains$1(tree, id)) return fail(at, "duplicate-tree-node", "Tree node already exists.");
|
|
270
|
+
const parentId = position?.parentId;
|
|
271
|
+
const parent = parentId === void 0 ? void 0 : node$1(tree, parentId);
|
|
272
|
+
if (tree.rootId !== void 0 && !parent || tree.rootId === void 0 && parentId !== void 0) return fail(at, "missing-tree-parent", "A nonempty tree requires an existing parent.");
|
|
273
|
+
const index = positionIndex(position?.index, parent?.children.length ?? 0, at);
|
|
274
|
+
session.validate(schema.value, value, at.concat(id));
|
|
275
|
+
session.recorder.tree(container, id);
|
|
276
|
+
if (parentId !== void 0) session.recorder.tree(container, parentId);
|
|
277
|
+
Object.defineProperty(tree.nodes, id, {
|
|
278
|
+
value: {
|
|
279
|
+
...parentId === void 0 ? {} : { parentId },
|
|
280
|
+
children: [],
|
|
281
|
+
value
|
|
282
|
+
},
|
|
283
|
+
writable: true,
|
|
284
|
+
configurable: true,
|
|
285
|
+
enumerable: true
|
|
286
|
+
});
|
|
287
|
+
if (parent) parent.children.splice(index, 0, id);
|
|
288
|
+
else tree.rootId = id;
|
|
289
|
+
session.invalidate();
|
|
290
|
+
};
|
|
291
|
+
const replace$1 = (session, container, id, value) => {
|
|
292
|
+
const { at, node: schema, value: tree } = container;
|
|
293
|
+
const entry = node$1(tree, id);
|
|
294
|
+
if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
295
|
+
if (Object.is(entry.value, value) && Object.hasOwn(entry, "value")) return;
|
|
296
|
+
session.validate(schema.value, value, at.concat(id));
|
|
297
|
+
session.recorder.tree(container, id);
|
|
298
|
+
entry.value = value;
|
|
299
|
+
};
|
|
300
|
+
const remove = (session, container, id) => {
|
|
301
|
+
const { at, value: tree } = container;
|
|
302
|
+
const entry = node$1(tree, id);
|
|
303
|
+
if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
304
|
+
const ids = [], stack = [id];
|
|
305
|
+
while (stack.length) {
|
|
306
|
+
const current = stack.pop();
|
|
307
|
+
ids.push(current);
|
|
308
|
+
for (const child of tree.nodes[current].children) stack.push(child);
|
|
309
|
+
}
|
|
310
|
+
for (const key of ids) session.recorder.tree(container, key);
|
|
311
|
+
if (entry.parentId !== void 0) session.recorder.tree(container, entry.parentId);
|
|
312
|
+
if (entry.parentId === void 0) delete tree.rootId;
|
|
313
|
+
else {
|
|
314
|
+
const children = tree.nodes[entry.parentId].children;
|
|
315
|
+
children.splice(children.indexOf(id), 1);
|
|
316
|
+
}
|
|
317
|
+
for (const key of ids) delete tree.nodes[key];
|
|
318
|
+
session.invalidate();
|
|
319
|
+
};
|
|
320
|
+
const move$1 = (session, container, id, position) => {
|
|
321
|
+
const { at, value: tree } = container;
|
|
322
|
+
const entry = node$1(tree, id), parentId = position?.parentId;
|
|
323
|
+
if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
324
|
+
if (entry.parentId === void 0 || parentId === void 0 || !contains$1(tree, parentId)) return fail(at, "missing-tree-parent", "Moving requires a non-root node and an existing parent.");
|
|
325
|
+
for (let ancestor = parentId; ancestor !== void 0; ancestor = tree.nodes[ancestor].parentId) if (ancestor === id) return fail(at, "tree-cycle", "Cannot move a node under its descendant.");
|
|
326
|
+
const old = tree.nodes[entry.parentId], next = tree.nodes[parentId];
|
|
327
|
+
const index = positionIndex(position?.index, next.children.length - (old === next ? 1 : 0), at);
|
|
328
|
+
const previous = old.children.indexOf(id);
|
|
329
|
+
if (old === next && previous === index) return;
|
|
330
|
+
session.recorder.tree(container, id);
|
|
331
|
+
session.recorder.tree(container, entry.parentId);
|
|
332
|
+
session.recorder.tree(container, parentId);
|
|
333
|
+
old.children.splice(previous, 1);
|
|
334
|
+
next.children.splice(index, 0, id);
|
|
335
|
+
entry.parentId = parentId;
|
|
336
|
+
session.invalidate();
|
|
337
|
+
};
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region core/src/mutation/operations/order.ts
|
|
340
|
+
const assertOrdered = (container) => {
|
|
341
|
+
const { at, node } = container;
|
|
342
|
+
if (node.kind !== "table" && node.kind !== "list") return fail(at, "invalid-collection", "Expected an ordered container.");
|
|
343
|
+
return node;
|
|
344
|
+
};
|
|
345
|
+
const plannedOrder = (at, current, selection, position) => {
|
|
346
|
+
const plan = moveSelection(current, selection, position);
|
|
347
|
+
switch (plan.kind) {
|
|
348
|
+
case "ok": return plan.order;
|
|
349
|
+
case "duplicate": return fail(at, "invalid-key", "Moved keys must be unique.");
|
|
350
|
+
case "missing": return fail(at, "missing-entity", "Ordered key does not exist.");
|
|
351
|
+
case "invalid-anchor": return fail(at, "invalid-anchor", "Unknown order anchor.");
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function move(session, container, ids, position) {
|
|
355
|
+
const node = assertOrdered(container);
|
|
356
|
+
const selection = typeof ids === "string" ? [ids] : ids;
|
|
357
|
+
if (!selection.length) return;
|
|
358
|
+
if (node.kind === "table") {
|
|
359
|
+
const order = container.value.ids;
|
|
360
|
+
const next = plannedOrder(container.at, order, selection, position);
|
|
361
|
+
if (equal(order, next)) return;
|
|
362
|
+
session.recorder.order(container);
|
|
363
|
+
installKeys(order, next);
|
|
364
|
+
} else {
|
|
365
|
+
const items = container.value;
|
|
366
|
+
const current = listSequence(items, node.keyOf);
|
|
367
|
+
const next = plannedOrder(container.at, current.order, selection, position);
|
|
368
|
+
if (equal(current.order, next)) return;
|
|
369
|
+
session.recorder.order(container);
|
|
370
|
+
installList(items, node.keyOf, current, next);
|
|
371
|
+
}
|
|
372
|
+
session.invalidate();
|
|
373
|
+
}
|
|
374
|
+
function reorder(session, container, next) {
|
|
375
|
+
const node = assertOrdered(container);
|
|
376
|
+
if (node.kind === "table") {
|
|
377
|
+
const current = container.value.ids;
|
|
378
|
+
if (!matches(next, current)) return fail(container.at, "invalid-collection", "Order must contain every key exactly once.");
|
|
379
|
+
if (equal(current, next)) return;
|
|
380
|
+
session.recorder.order(container);
|
|
381
|
+
installKeys(current, next);
|
|
382
|
+
} else {
|
|
383
|
+
const items = container.value;
|
|
384
|
+
const current = listSequence(items, node.keyOf);
|
|
385
|
+
if (!matches(next, current.order)) return fail(container.at, "invalid-collection", "Order must contain every key exactly once.");
|
|
386
|
+
if (equal(current.order, next)) return;
|
|
387
|
+
session.recorder.order(container);
|
|
388
|
+
installList(items, node.keyOf, current, next);
|
|
389
|
+
}
|
|
390
|
+
session.invalidate();
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region core/src/impact/target.ts
|
|
394
|
+
const treeRootSegment = "rootId";
|
|
395
|
+
const treeNodesSegment = "nodes";
|
|
396
|
+
const ordinaryNamespace = "\0value";
|
|
397
|
+
const treeNamespace = "\0tree";
|
|
398
|
+
const tree$1 = (at, selection) => ({
|
|
399
|
+
kind: "value",
|
|
400
|
+
at,
|
|
401
|
+
tree: selection
|
|
402
|
+
});
|
|
403
|
+
const address = (target) => "at" in target ? target.at : target.address;
|
|
404
|
+
const id = (target) => target.kind === "collection" && "id" in target ? target.id : void 0;
|
|
405
|
+
const belongs = (target, schema) => !("schema" in target) || target.schema === schema;
|
|
406
|
+
const treeTarget = (target) => "tree" in target ? target.tree : void 0;
|
|
407
|
+
const sameTreeTarget = (left, right) => {
|
|
408
|
+
if (!left || !right) return left === right;
|
|
409
|
+
if (left.kind !== right.kind) return false;
|
|
410
|
+
return left.kind !== "node" || right.kind === "node" && left.id === right.id;
|
|
411
|
+
};
|
|
412
|
+
const same = (left, right) => {
|
|
413
|
+
if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
|
|
414
|
+
if (!sameTreeTarget(treeTarget(left), treeTarget(right))) return false;
|
|
415
|
+
if (left.kind !== right.kind) return false;
|
|
416
|
+
const leftAddress = address(left);
|
|
417
|
+
const rightAddress = address(right);
|
|
418
|
+
if (leftAddress.length !== rightAddress.length) return false;
|
|
419
|
+
for (let index = 0; index < leftAddress.length; index += 1) if (leftAddress[index] !== rightAddress[index]) return false;
|
|
420
|
+
return left.kind !== "collection" || id(left) === id(right);
|
|
421
|
+
};
|
|
422
|
+
const indexedAddress = (target) => {
|
|
423
|
+
const tree = treeTarget(target);
|
|
424
|
+
if (tree?.kind === "root") return [
|
|
425
|
+
treeNamespace,
|
|
426
|
+
...address(target),
|
|
427
|
+
treeRootSegment
|
|
428
|
+
];
|
|
429
|
+
if (tree?.kind === "nodes") return [
|
|
430
|
+
treeNamespace,
|
|
431
|
+
...address(target),
|
|
432
|
+
treeNodesSegment
|
|
433
|
+
];
|
|
434
|
+
if (tree?.kind === "node") return [
|
|
435
|
+
treeNamespace,
|
|
436
|
+
...address(target),
|
|
437
|
+
treeNodesSegment,
|
|
438
|
+
tree.id
|
|
439
|
+
];
|
|
440
|
+
const key = id(target);
|
|
441
|
+
return key === void 0 ? [ordinaryNamespace, ...address(target)] : [
|
|
442
|
+
ordinaryNamespace,
|
|
443
|
+
...address(target),
|
|
444
|
+
key
|
|
445
|
+
];
|
|
446
|
+
};
|
|
447
|
+
const visitChangedLocations = (changes, value, order) => {
|
|
448
|
+
for (const change of changes.changes) {
|
|
449
|
+
if (change.kind === "reset") continue;
|
|
450
|
+
if (change.kind === "members") {
|
|
451
|
+
for (const member of change.members) {
|
|
452
|
+
value([
|
|
453
|
+
ordinaryNamespace,
|
|
454
|
+
...change.at,
|
|
455
|
+
member.key
|
|
456
|
+
]);
|
|
457
|
+
value([
|
|
458
|
+
treeNamespace,
|
|
459
|
+
...change.at,
|
|
460
|
+
member.key
|
|
461
|
+
]);
|
|
462
|
+
}
|
|
463
|
+
if (change.order) order([ordinaryNamespace, ...change.at]);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
value([ordinaryNamespace, ...change.at]);
|
|
467
|
+
if (change.before !== change.after) value([
|
|
468
|
+
treeNamespace,
|
|
469
|
+
...change.at,
|
|
470
|
+
treeRootSegment
|
|
471
|
+
]);
|
|
472
|
+
for (const node of change.nodes) value([
|
|
473
|
+
treeNamespace,
|
|
474
|
+
...change.at,
|
|
475
|
+
treeNodesSegment,
|
|
476
|
+
node.id
|
|
477
|
+
]);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const affected = (target, values, orders) => {
|
|
481
|
+
const at = indexedAddress(target);
|
|
482
|
+
return id(target) !== void 0 ? values.hasAncestor(at) : values.overlaps(at) || orders.hasDescendant(at);
|
|
483
|
+
};
|
|
484
|
+
/** Targets are classified once; commit matching never builds a reverse impact index. */
|
|
485
|
+
var SubscriptionIndex = class {
|
|
486
|
+
ordinary = new AddressIndex();
|
|
487
|
+
membership = new AddressIndex();
|
|
488
|
+
constructor(schema) {
|
|
489
|
+
this.schema = schema;
|
|
490
|
+
}
|
|
491
|
+
add(target, value) {
|
|
492
|
+
if (!belongs(target, this.schema)) return;
|
|
493
|
+
(id(target) === void 0 ? this.ordinary : this.membership).add(indexedAddress(target), value);
|
|
494
|
+
}
|
|
495
|
+
delete(target, value) {
|
|
496
|
+
if (!belongs(target, this.schema)) return;
|
|
497
|
+
(id(target) === void 0 ? this.ordinary : this.membership).delete(indexedAddress(target), value);
|
|
498
|
+
}
|
|
499
|
+
collect(changes, visit) {
|
|
500
|
+
const values = this.ordinary.query(visit);
|
|
501
|
+
const members = this.membership.query(visit, "descendants");
|
|
502
|
+
const orders = this.ordinary.query(visit, "ancestors");
|
|
503
|
+
if (changes.changes.some((change) => change.kind === "reset")) {
|
|
504
|
+
values([]);
|
|
505
|
+
members([]);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
visitChangedLocations(changes, (at) => {
|
|
509
|
+
values(at);
|
|
510
|
+
members(at);
|
|
511
|
+
}, orders);
|
|
512
|
+
}
|
|
513
|
+
clear() {
|
|
514
|
+
this.ordinary.clear();
|
|
515
|
+
this.membership.clear();
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region core/src/access/scope.ts
|
|
520
|
+
const locations = /* @__PURE__ */ new WeakMap();
|
|
521
|
+
const locationOf = (value) => locations.get(value);
|
|
522
|
+
const addressOf = (target) => {
|
|
523
|
+
if (target.at) return target.at;
|
|
524
|
+
profile.access("addresses");
|
|
525
|
+
return target.at = addressOf(target.parent).concat(target.key);
|
|
526
|
+
};
|
|
527
|
+
const assertActive = (context) => {
|
|
528
|
+
if (context.active && !context.active()) throw new Error("Document access scope has expired.");
|
|
529
|
+
};
|
|
530
|
+
const collect = (context, at, kind = "value", id) => {
|
|
531
|
+
context.dependencies?.record(kind === "value" ? {
|
|
532
|
+
kind,
|
|
533
|
+
at
|
|
534
|
+
} : {
|
|
535
|
+
kind,
|
|
536
|
+
at,
|
|
537
|
+
...id === void 0 ? {} : { id }
|
|
538
|
+
});
|
|
539
|
+
};
|
|
540
|
+
const collectTree = (context, at, selection) => context.dependencies?.record(tree$1(at, selection));
|
|
541
|
+
const snapshot = (value) => {
|
|
542
|
+
const location = value && typeof value === "object" ? locationOf(value) : void 0;
|
|
543
|
+
if (!location) return value;
|
|
544
|
+
const { context } = location;
|
|
545
|
+
const at = addressOf(location.target);
|
|
546
|
+
profile.access("snapshots");
|
|
547
|
+
collect(context, at);
|
|
548
|
+
const resolved = resolve(context, location.target);
|
|
549
|
+
if (!resolved.node) throw new Error("The selected schema address no longer exists.");
|
|
550
|
+
return copyValue(resolved.node, resolved.value);
|
|
551
|
+
};
|
|
552
|
+
const replace = (container, key, value) => {
|
|
553
|
+
if (!locationOf(container)) throw new TypeError("replace requires scoped document access.");
|
|
554
|
+
if (typeof key !== "string") throw new TypeError("Document keys must be strings.");
|
|
555
|
+
Reflect.set(container, key, value);
|
|
556
|
+
};
|
|
557
|
+
const resolve = (context, target) => {
|
|
558
|
+
assertActive(context);
|
|
559
|
+
const generation = context.session?.generation ?? 0;
|
|
560
|
+
if (target.generation !== generation) {
|
|
561
|
+
profile.access("resolutions");
|
|
562
|
+
if (!target.parent && !target.at.length) {
|
|
563
|
+
target.node = context.state.schema;
|
|
564
|
+
target.value = context.state.document;
|
|
565
|
+
} else {
|
|
566
|
+
const parent = target.parent && resolve(context, target.parent);
|
|
567
|
+
const location = parent ? resolveChild(parent.node, parent.value, target.key) : resolveLocated(context.state.schema, context.state.document, target.at);
|
|
568
|
+
target.node = location?.node ?? nodeAt(context.state.schema, addressOf(target), context.state.document);
|
|
569
|
+
target.value = location && Object.hasOwn(location.parent, location.key) ? location.parent[location.key] : void 0;
|
|
570
|
+
}
|
|
571
|
+
target.generation = generation;
|
|
572
|
+
}
|
|
573
|
+
return target;
|
|
574
|
+
};
|
|
575
|
+
const createAccess = (context, initial = []) => {
|
|
576
|
+
profile.access("scopes");
|
|
577
|
+
const mutable = () => {
|
|
578
|
+
assertActive(context);
|
|
579
|
+
if (!context.session) throw new TypeError("Cannot modify read-only document access.");
|
|
580
|
+
return context.session;
|
|
581
|
+
};
|
|
582
|
+
const writableContainer = (target, session) => {
|
|
583
|
+
if (target.container?.generation === session.generation && "layout" in target.container) return target.container;
|
|
584
|
+
return target.container = session.bind(addressOf(target), target.node, target.value);
|
|
585
|
+
};
|
|
586
|
+
const childAt = (target, key) => {
|
|
587
|
+
profile.access("addresses");
|
|
588
|
+
return addressOf(target).concat(key);
|
|
589
|
+
};
|
|
590
|
+
const access = (target) => {
|
|
591
|
+
if (target.proxy) return target.proxy;
|
|
592
|
+
const proxy = new Proxy(target, handler);
|
|
593
|
+
profile.access("proxies");
|
|
594
|
+
locations.set(proxy, {
|
|
595
|
+
context,
|
|
596
|
+
target
|
|
597
|
+
});
|
|
598
|
+
target.proxy = proxy;
|
|
599
|
+
return proxy;
|
|
600
|
+
};
|
|
601
|
+
const createTarget = (at, parent, key) => ({
|
|
602
|
+
at,
|
|
603
|
+
parent,
|
|
604
|
+
key,
|
|
605
|
+
node: void 0,
|
|
606
|
+
value: void 0,
|
|
607
|
+
generation: -1,
|
|
608
|
+
children: void 0,
|
|
609
|
+
proxy: void 0
|
|
610
|
+
});
|
|
611
|
+
const childAccess = (target, member, node, value) => {
|
|
612
|
+
const key = typeof member === "string" ? member : member.key;
|
|
613
|
+
if (node.kind === "field" || value === void 0) {
|
|
614
|
+
if (context.dependencies) collect(context, childAt(target, key));
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
let children = target.children;
|
|
618
|
+
let child;
|
|
619
|
+
if (typeof member !== "string") {
|
|
620
|
+
if (children?.kind !== "fixed" || children.schema !== target.node) children = {
|
|
621
|
+
kind: "fixed",
|
|
622
|
+
schema: target.node,
|
|
623
|
+
slots: new Array(compiledShape(target.node, target.value).members.size)
|
|
624
|
+
};
|
|
625
|
+
child = children.slots[member.slot] ??= createTarget(void 0, target, key);
|
|
626
|
+
} else {
|
|
627
|
+
if (children?.kind !== "dynamic") children = {
|
|
628
|
+
kind: "dynamic",
|
|
629
|
+
keys: /* @__PURE__ */ new Map()
|
|
630
|
+
};
|
|
631
|
+
const retained = children.keys.get(key);
|
|
632
|
+
child = retained ?? createTarget(void 0, target, key);
|
|
633
|
+
if (!retained) children.keys.set(key, child);
|
|
634
|
+
}
|
|
635
|
+
target.children = children;
|
|
636
|
+
if (child.generation === target.generation) return child.proxy;
|
|
637
|
+
child.node = node;
|
|
638
|
+
child.value = value;
|
|
639
|
+
child.generation = target.generation;
|
|
640
|
+
return access(child);
|
|
641
|
+
};
|
|
642
|
+
const collectionValue = (target, node) => {
|
|
643
|
+
const current = resolve(context, target);
|
|
644
|
+
if (current.node !== node || current.value === void 0) throw new TypeError("The collection method belongs to a replaced schema branch.");
|
|
645
|
+
return current.value;
|
|
646
|
+
};
|
|
647
|
+
const writableCollection = (target, node) => {
|
|
648
|
+
const session = mutable();
|
|
649
|
+
collectionValue(target, node);
|
|
650
|
+
return writableContainer(target, session);
|
|
651
|
+
};
|
|
652
|
+
const writableTree = (target, node) => {
|
|
653
|
+
const session = mutable();
|
|
654
|
+
collectionValue(target, node);
|
|
655
|
+
if (target.container?.generation === session.generation && !("layout" in target.container)) return target.container;
|
|
656
|
+
return target.container = session.bindTree(addressOf(target), target.node, target.value);
|
|
657
|
+
};
|
|
658
|
+
const replaceCollection = (target, node, value) => {
|
|
659
|
+
const session = mutable();
|
|
660
|
+
collectionValue(target, node);
|
|
661
|
+
session.replace(addressOf(target), value);
|
|
662
|
+
};
|
|
663
|
+
const mapMethod = (target, property, node) => {
|
|
664
|
+
if (property === "ids") return () => {
|
|
665
|
+
const current = collectionValue(target, node);
|
|
666
|
+
if (context.dependencies) collect(context, addressOf(target), "collection");
|
|
667
|
+
return Object.keys(current);
|
|
668
|
+
};
|
|
669
|
+
if (property === "has") return (id) => {
|
|
670
|
+
const current = collectionValue(target, node);
|
|
671
|
+
if (context.dependencies) collect(context, addressOf(target), "collection", id);
|
|
672
|
+
return Object.hasOwn(current, id);
|
|
673
|
+
};
|
|
674
|
+
if (property === "get") return (id) => {
|
|
675
|
+
const current = collectionValue(target, node);
|
|
676
|
+
if (context.dependencies) collect(context, addressOf(target), "collection", id);
|
|
677
|
+
if (!Object.hasOwn(current, id)) return void 0;
|
|
678
|
+
if (node.value.kind === "field") {
|
|
679
|
+
if (context.dependencies) collect(context, childAt(target, id));
|
|
680
|
+
return current[id];
|
|
681
|
+
}
|
|
682
|
+
return childAccess(target, id, node.value, current[id]);
|
|
683
|
+
};
|
|
684
|
+
if (property === "put") return (id, value) => {
|
|
685
|
+
const writable = writableCollection(target, node);
|
|
686
|
+
put(context.session, writable, id, value);
|
|
687
|
+
};
|
|
688
|
+
if (property === "remove") return (id) => {
|
|
689
|
+
const writable = writableCollection(target, node);
|
|
690
|
+
remove$3(context.session, writable, id);
|
|
691
|
+
};
|
|
692
|
+
if (property === "replace") return (value) => replaceCollection(target, node, value);
|
|
693
|
+
};
|
|
694
|
+
const orderedMethod = (target, property, node) => {
|
|
695
|
+
const at = addressOf(target);
|
|
696
|
+
if (property === "ids") return () => {
|
|
697
|
+
const current = collectionValue(target, node);
|
|
698
|
+
collect(context, at, "collection");
|
|
699
|
+
return node.kind === "table" ? [...current.ids] : current.map(node.keyOf);
|
|
700
|
+
};
|
|
701
|
+
if (property === "has") return (id) => {
|
|
702
|
+
const current = collectionValue(target, node);
|
|
703
|
+
collect(context, at, "collection", id);
|
|
704
|
+
return node.kind === "table" ? Object.hasOwn(current.byId, id) : indexedKeys(current, node.keyOf).index(id) >= 0;
|
|
705
|
+
};
|
|
706
|
+
if (property === "get") return (id) => {
|
|
707
|
+
const current = collectionValue(target, node);
|
|
708
|
+
collect(context, at, "collection", id);
|
|
709
|
+
const location = resolveChild(node, current, id);
|
|
710
|
+
if (!location || !Object.hasOwn(location.parent, location.key)) return void 0;
|
|
711
|
+
return childAccess(target, id, location.node, location.parent[location.key]);
|
|
712
|
+
};
|
|
713
|
+
if (property === "move") return (ids, position) => {
|
|
714
|
+
const writable = writableCollection(target, node);
|
|
715
|
+
move(context.session, writable, ids, position);
|
|
716
|
+
};
|
|
717
|
+
if (property === "reorder") return (ids) => {
|
|
718
|
+
const writable = writableCollection(target, node);
|
|
719
|
+
reorder(context.session, writable, ids);
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
const tableMethod = (target, property, node) => {
|
|
723
|
+
if (property === "create") return (input, valueOrPosition, position) => {
|
|
724
|
+
const writable = writableCollection(target, node);
|
|
725
|
+
create(context.session, writable, Array.isArray(input) ? input : [{
|
|
726
|
+
id: input,
|
|
727
|
+
value: valueOrPosition
|
|
728
|
+
}], Array.isArray(input) ? valueOrPosition : position);
|
|
729
|
+
};
|
|
730
|
+
if (property === "remove") return (ids) => {
|
|
731
|
+
const writable = writableCollection(target, node);
|
|
732
|
+
remove$2(context.session, writable, typeof ids === "string" ? [ids] : ids);
|
|
733
|
+
};
|
|
734
|
+
if (property === "replace") return (...args) => {
|
|
735
|
+
const [valueOrId, value] = args;
|
|
736
|
+
if (args.length === 1) return replaceCollection(target, node, valueOrId);
|
|
737
|
+
const writable = writableCollection(target, node);
|
|
738
|
+
replace$3(context.session, writable, valueOrId, value);
|
|
739
|
+
};
|
|
740
|
+
return orderedMethod(target, property, node);
|
|
741
|
+
};
|
|
742
|
+
const listMethod = (target, property, node) => {
|
|
743
|
+
if (property === "insert") return (value, position) => {
|
|
744
|
+
const writable = writableCollection(target, node);
|
|
745
|
+
insert$1(context.session, writable, value, position);
|
|
746
|
+
};
|
|
747
|
+
if (property === "remove") return (id) => {
|
|
748
|
+
const writable = writableCollection(target, node);
|
|
749
|
+
remove$1(context.session, writable, id);
|
|
750
|
+
};
|
|
751
|
+
if (property === "replace") return (...args) => {
|
|
752
|
+
const [valueOrId, value] = args;
|
|
753
|
+
if (args.length === 1) return replaceCollection(target, node, valueOrId);
|
|
754
|
+
const writable = writableCollection(target, node);
|
|
755
|
+
replace$2(context.session, writable, valueOrId, value);
|
|
756
|
+
};
|
|
757
|
+
return orderedMethod(target, property, node);
|
|
758
|
+
};
|
|
759
|
+
const treeMethod = (target, property, node) => {
|
|
760
|
+
const at = addressOf(target);
|
|
761
|
+
if (property === "insert") return (id, value, position) => {
|
|
762
|
+
const writable = writableTree(target, node);
|
|
763
|
+
insert(context.session, writable, id, value, position);
|
|
764
|
+
};
|
|
765
|
+
if (property === "remove") return (id) => {
|
|
766
|
+
const writable = writableTree(target, node);
|
|
767
|
+
remove(context.session, writable, id);
|
|
768
|
+
};
|
|
769
|
+
if (property === "move") return (id, position) => {
|
|
770
|
+
const writable = writableTree(target, node);
|
|
771
|
+
move$1(context.session, writable, id, position);
|
|
772
|
+
};
|
|
773
|
+
if (property === "replace") return (...args) => {
|
|
774
|
+
const [valueOrId, value] = args;
|
|
775
|
+
if (args.length === 1) return replaceCollection(target, node, valueOrId);
|
|
776
|
+
const writable = writableTree(target, node);
|
|
777
|
+
replace$1(context.session, writable, valueOrId, value);
|
|
778
|
+
};
|
|
779
|
+
const current = () => collectionValue(target, node);
|
|
780
|
+
if (property === "rootId") return () => {
|
|
781
|
+
collectTree(context, at, { kind: "root" });
|
|
782
|
+
return current().rootId;
|
|
783
|
+
};
|
|
784
|
+
if (property === "get") return (id) => {
|
|
785
|
+
collectTree(context, at, {
|
|
786
|
+
kind: "node",
|
|
787
|
+
id
|
|
788
|
+
});
|
|
789
|
+
const value = current();
|
|
790
|
+
return contains$1(value, id) ? value.nodes[id].value : void 0;
|
|
791
|
+
};
|
|
792
|
+
if (property === "has") return (id) => {
|
|
793
|
+
collectTree(context, at, {
|
|
794
|
+
kind: "node",
|
|
795
|
+
id
|
|
796
|
+
});
|
|
797
|
+
return contains$1(current(), id);
|
|
798
|
+
};
|
|
799
|
+
if (property === "parent") return (id) => {
|
|
800
|
+
collectTree(context, at, {
|
|
801
|
+
kind: "node",
|
|
802
|
+
id
|
|
803
|
+
});
|
|
804
|
+
return parent(current(), id);
|
|
805
|
+
};
|
|
806
|
+
if (property === "children") return (id) => {
|
|
807
|
+
collectTree(context, at, {
|
|
808
|
+
kind: "node",
|
|
809
|
+
id
|
|
810
|
+
});
|
|
811
|
+
const children$1 = children(current(), id);
|
|
812
|
+
return children$1 ? [...children$1] : void 0;
|
|
813
|
+
};
|
|
814
|
+
};
|
|
815
|
+
const handler = {
|
|
816
|
+
get: (target, property) => {
|
|
817
|
+
const { node, value } = resolve(context, target);
|
|
818
|
+
if (!node || value === void 0) return void 0;
|
|
819
|
+
if (typeof property !== "string") return void 0;
|
|
820
|
+
if (node.kind === "map") return mapMethod(target, property, node);
|
|
821
|
+
if (node.kind === "table") return tableMethod(target, property, node);
|
|
822
|
+
if (node.kind === "list") return listMethod(target, property, node);
|
|
823
|
+
if (node.kind === "tree") return treeMethod(target, property, node);
|
|
824
|
+
if (node.kind === "variant" && property === node.tag) {
|
|
825
|
+
collect(context, childAt(target, property));
|
|
826
|
+
return value[property];
|
|
827
|
+
}
|
|
828
|
+
const member = compiledShape(node, value)?.members.get(property);
|
|
829
|
+
if (!member) return void 0;
|
|
830
|
+
if (member.field) {
|
|
831
|
+
if (context.dependencies) collect(context, childAt(target, property));
|
|
832
|
+
return value[property];
|
|
833
|
+
}
|
|
834
|
+
return childAccess(target, node.kind === "object" ? member : property, member.node, value[property]);
|
|
835
|
+
},
|
|
836
|
+
set: (target, property, value) => {
|
|
837
|
+
const session = mutable(), { node } = resolve(context, target);
|
|
838
|
+
if (typeof property !== "string" || !node || node.kind !== "object" && node.kind !== "variant") throw new TypeError("Invalid structural assignment.");
|
|
839
|
+
if (node.kind === "variant" && node.tag === property) throw new TypeError("Variant discriminants are read-only.");
|
|
840
|
+
session.assignMember(writableContainer(target, session), property, value);
|
|
841
|
+
return true;
|
|
842
|
+
},
|
|
843
|
+
deleteProperty: (target, property) => {
|
|
844
|
+
const session = mutable(), { node } = resolve(context, target);
|
|
845
|
+
if (typeof property !== "string" || node?.kind === "map" || node?.kind === "variant" && node.tag === property) throw new TypeError("Invalid structural deletion.");
|
|
846
|
+
session.removeMember(writableContainer(target, session), property);
|
|
847
|
+
return true;
|
|
848
|
+
},
|
|
849
|
+
has: (target, property) => {
|
|
850
|
+
const { node, value } = resolve(context, target);
|
|
851
|
+
if (typeof property !== "string") return false;
|
|
852
|
+
if (node?.kind === "map") return false;
|
|
853
|
+
collect(context, addressOf(target));
|
|
854
|
+
return value !== void 0 && Object.hasOwn(value, property);
|
|
855
|
+
},
|
|
856
|
+
ownKeys: (target) => {
|
|
857
|
+
const { node, value } = resolve(context, target);
|
|
858
|
+
if (node?.kind === "map") return [];
|
|
859
|
+
if (context.dependencies) collect(context, addressOf(target));
|
|
860
|
+
return value && typeof value === "object" ? Object.keys(value) : [];
|
|
861
|
+
},
|
|
862
|
+
getOwnPropertyDescriptor: (target, property) => {
|
|
863
|
+
const { node, value } = resolve(context, target);
|
|
864
|
+
if (node?.kind === "map") return void 0;
|
|
865
|
+
return value && Object.hasOwn(value, property) ? {
|
|
866
|
+
enumerable: true,
|
|
867
|
+
configurable: true
|
|
868
|
+
} : void 0;
|
|
869
|
+
},
|
|
870
|
+
defineProperty: () => {
|
|
871
|
+
assertActive(context);
|
|
872
|
+
throw new TypeError("Use schema assignments.");
|
|
873
|
+
},
|
|
874
|
+
setPrototypeOf: () => {
|
|
875
|
+
assertActive(context);
|
|
876
|
+
throw new TypeError("Document prototypes are immutable.");
|
|
877
|
+
},
|
|
878
|
+
preventExtensions: () => {
|
|
879
|
+
assertActive(context);
|
|
880
|
+
throw new TypeError("Document access cannot be frozen.");
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
const root = resolve(context, createTarget(initial, void 0, ""));
|
|
884
|
+
if (!root.node || root.node.kind === "field" || root.value === void 0) {
|
|
885
|
+
if (context.dependencies) collect(context, initial);
|
|
886
|
+
return root.value;
|
|
887
|
+
}
|
|
888
|
+
return access(root);
|
|
889
|
+
};
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region core/src/address/relation.ts
|
|
892
|
+
const contains = (parent, child) => {
|
|
893
|
+
profile.address.prefixComparison();
|
|
894
|
+
if (parent.length > child.length) return false;
|
|
895
|
+
for (let index = 0; index < parent.length; index += 1) {
|
|
896
|
+
profile.address.segmentCompared();
|
|
897
|
+
if (parent[index] !== child[index]) return false;
|
|
898
|
+
}
|
|
899
|
+
return true;
|
|
900
|
+
};
|
|
901
|
+
//#endregion
|
|
6
902
|
//#region core/src/impact.ts
|
|
7
|
-
const queries = /* @__PURE__ */ new WeakMap();
|
|
8
|
-
const affectsTarget = (impact, value) => queries.get(impact).affects(value);
|
|
9
|
-
const collectionImpact = (impact, selector) => queries.get(impact).collection(selector);
|
|
10
903
|
const createImpact = (schema, changes) => {
|
|
11
904
|
const reset = changes.changes.some((change) => change.kind === "reset");
|
|
12
905
|
let values;
|
|
@@ -16,89 +909,117 @@ const createImpact = (schema, changes) => {
|
|
|
16
909
|
profile.impact.index();
|
|
17
910
|
values = new AddressIndex();
|
|
18
911
|
orders = new AddressIndex();
|
|
19
|
-
|
|
20
|
-
for (const member of change.members) values.add(change.at, true, member.key);
|
|
21
|
-
if (change.order) orders.add(change.at, true);
|
|
22
|
-
} else if (change.kind === "tree") values.add(change.at, true);
|
|
912
|
+
visitChangedLocations(changes, (at) => values.add(at, true), (at) => orders.add(at, true));
|
|
23
913
|
}
|
|
24
914
|
return values;
|
|
25
915
|
};
|
|
26
916
|
const cache = /* @__PURE__ */ new Map();
|
|
27
|
-
const
|
|
28
|
-
affects(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
917
|
+
const affects = (value) => {
|
|
918
|
+
profile.impact.affects();
|
|
919
|
+
if (!belongs(value, schema)) return false;
|
|
920
|
+
if (reset) return true;
|
|
921
|
+
return affected(value, index(), orders);
|
|
922
|
+
};
|
|
923
|
+
const collection = (selector) => {
|
|
924
|
+
if (selector.schema !== schema) throw new TypeError("Collection belongs to another root model.");
|
|
925
|
+
const key = JSON.stringify(indexedAddress(selector)), cached = cache.get(key);
|
|
926
|
+
if (cached) return cached;
|
|
927
|
+
if (reset) {
|
|
928
|
+
const result = { kind: "reset" };
|
|
929
|
+
cache.set(key, result);
|
|
930
|
+
return result;
|
|
931
|
+
}
|
|
932
|
+
const at = selector.address;
|
|
933
|
+
const tree = selector.tree;
|
|
934
|
+
const added = /* @__PURE__ */ new Set(), removed = /* @__PURE__ */ new Set(), updated = /* @__PURE__ */ new Set();
|
|
935
|
+
let orderChanged = false;
|
|
936
|
+
for (const change of changes.changes) {
|
|
937
|
+
if (change.kind === "reset") continue;
|
|
938
|
+
if (change.kind === "members") {
|
|
939
|
+
if (change.at.length < at.length && contains(change.at, at)) {
|
|
940
|
+
if (change.members.some((member) => member.key === at[change.at.length])) {
|
|
941
|
+
const result = { kind: "reset" };
|
|
942
|
+
cache.set(key, result);
|
|
943
|
+
return result;
|
|
944
|
+
}
|
|
945
|
+
} else if (contains(at, change.at)) if (change.at.length === at.length) {
|
|
946
|
+
if (change.order) {
|
|
947
|
+
const positions = new Map(change.order.after.map((id, i) => [id, i]));
|
|
948
|
+
let previous = -1;
|
|
949
|
+
for (const id of change.order.before) {
|
|
950
|
+
const position = positions.get(id);
|
|
951
|
+
if (position === void 0) continue;
|
|
952
|
+
if (position < previous) orderChanged = true;
|
|
953
|
+
previous = position;
|
|
65
954
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
955
|
+
}
|
|
956
|
+
for (const member of change.members) if (member.kind === "added") added.add(member.key);
|
|
957
|
+
else if (member.kind === "removed") removed.add(member.key);
|
|
958
|
+
else updated.add(member.key);
|
|
959
|
+
} else updated.add(change.at[at.length]);
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (change.kind === "tree" && contains(change.at, at)) {
|
|
963
|
+
if (tree?.kind === "nodes" && change.at.length === at.length && contains(at, change.at)) {
|
|
964
|
+
for (const node of change.nodes) if (node.kind === "added") added.add(node.id);
|
|
965
|
+
else if (node.kind === "removed") removed.add(node.id);
|
|
966
|
+
else updated.add(node.id);
|
|
70
967
|
continue;
|
|
71
968
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return result;
|
|
76
|
-
}
|
|
77
|
-
if (!contains(at, change.at)) continue;
|
|
78
|
-
if (change.at.length === at.length) continue;
|
|
79
|
-
const id = change.at[at.length];
|
|
80
|
-
updated.add(id);
|
|
969
|
+
const result = { kind: "reset" };
|
|
970
|
+
cache.set(key, result);
|
|
971
|
+
return result;
|
|
81
972
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
added,
|
|
87
|
-
removed,
|
|
88
|
-
updated,
|
|
89
|
-
orderChanged
|
|
90
|
-
};
|
|
91
|
-
cache.set(key, result);
|
|
92
|
-
return result;
|
|
973
|
+
if (!contains(at, change.at)) continue;
|
|
974
|
+
if (change.at.length === at.length) continue;
|
|
975
|
+
const id = change.at[at.length];
|
|
976
|
+
updated.add(id);
|
|
93
977
|
}
|
|
978
|
+
added.forEach((id) => updated.delete(id));
|
|
979
|
+
removed.forEach((id) => updated.delete(id));
|
|
980
|
+
const result = {
|
|
981
|
+
kind: "incremental",
|
|
982
|
+
added,
|
|
983
|
+
removed,
|
|
984
|
+
updated,
|
|
985
|
+
orderChanged
|
|
986
|
+
};
|
|
987
|
+
cache.set(key, result);
|
|
988
|
+
return result;
|
|
94
989
|
};
|
|
95
|
-
|
|
990
|
+
return Object.freeze({
|
|
96
991
|
kind: reset ? "reset" : "incremental",
|
|
97
|
-
affects: (pick) =>
|
|
98
|
-
collection: (pick) =>
|
|
992
|
+
affects: (pick) => affects(compilePath(schema, "value", pick)),
|
|
993
|
+
collection: (pick) => collection(compilePath(schema, "collection", pick))
|
|
99
994
|
});
|
|
100
|
-
|
|
101
|
-
|
|
995
|
+
};
|
|
996
|
+
//#endregion
|
|
997
|
+
//#region core/src/runtime/contract.ts
|
|
998
|
+
var DocumentReentrancyError = class extends Error {
|
|
999
|
+
constructor() {
|
|
1000
|
+
super("Document writes cannot be re-entered during an update or notification.");
|
|
1001
|
+
this.name = "DocumentReentrancyError";
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
var DocumentDisposedError = class extends Error {
|
|
1005
|
+
constructor() {
|
|
1006
|
+
super("Doxum runtime has been disposed.");
|
|
1007
|
+
this.name = "DocumentDisposedError";
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
var TransactionRejected = class extends Error {
|
|
1011
|
+
issues;
|
|
1012
|
+
constructor(input) {
|
|
1013
|
+
super("Document update rejected.");
|
|
1014
|
+
this.name = "TransactionRejected";
|
|
1015
|
+
const issues = Array.isArray(input) ? input : [input];
|
|
1016
|
+
this.issues = Object.freeze(issues.map((issue) => Object.freeze({
|
|
1017
|
+
code: issue.code,
|
|
1018
|
+
message: issue.message,
|
|
1019
|
+
source: "application",
|
|
1020
|
+
...issue.address === void 0 ? {} : { address: Object.freeze([...issue.address]) }
|
|
1021
|
+
})));
|
|
1022
|
+
}
|
|
102
1023
|
};
|
|
103
1024
|
//#endregion
|
|
104
1025
|
//#region core/src/history.ts
|
|
@@ -271,17 +1192,10 @@ const createHistory = (input) => {
|
|
|
271
1192
|
const installMember = (parent, key, present, value) => {
|
|
272
1193
|
if (Array.isArray(parent)) {
|
|
273
1194
|
const index = Number(key);
|
|
274
|
-
if (present) if (index < 0) insert(parent, parent.length, value);
|
|
1195
|
+
if (present) if (index < 0) insert$2(parent, parent.length, value);
|
|
275
1196
|
else parent[index] = value;
|
|
276
|
-
else if (index >= 0) remove(parent, index);
|
|
277
|
-
} else
|
|
278
|
-
else if (Object.hasOwn(parent, key)) parent[key] = value;
|
|
279
|
-
else Object.defineProperty(parent, key, {
|
|
280
|
-
value,
|
|
281
|
-
writable: true,
|
|
282
|
-
configurable: true,
|
|
283
|
-
enumerable: true
|
|
284
|
-
});
|
|
1197
|
+
else if (index >= 0) remove$4(parent, index);
|
|
1198
|
+
} else installOwn(parent, key, present, value);
|
|
285
1199
|
};
|
|
286
1200
|
const orderOf = (node, value) => {
|
|
287
1201
|
if (node.kind === "list") return toArray(indexedKeys(value, node.keyOf));
|
|
@@ -308,10 +1222,6 @@ const forgetMember = (group, key) => {
|
|
|
308
1222
|
}
|
|
309
1223
|
};
|
|
310
1224
|
const emptyGroup = (group) => !group.order && (group.layout === "fixed" ? !group.members.some(Boolean) : !group.members?.size);
|
|
311
|
-
const copyNode = (node) => ({
|
|
312
|
-
...node,
|
|
313
|
-
children: [...node.children]
|
|
314
|
-
});
|
|
315
1225
|
const transition = (node, key, beforePresent, before, afterPresent, after) => {
|
|
316
1226
|
if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
|
|
317
1227
|
if (node.kind !== "field" && beforePresent === afterPresent && equalValue(node, before, after)) return;
|
|
@@ -402,7 +1312,7 @@ const restore = (fact, schema, root, skip = 0) => {
|
|
|
402
1312
|
const tree = location.value;
|
|
403
1313
|
if (fact.before === null) delete tree.rootId;
|
|
404
1314
|
else tree.rootId = fact.before;
|
|
405
|
-
for (const [id, node] of fact.nodes) installMember(tree.nodes, id, node !== void 0, node &&
|
|
1315
|
+
for (const [id, node] of fact.nodes) installMember(tree.nodes, id, node !== void 0, node && copyTreeNode(node));
|
|
406
1316
|
}
|
|
407
1317
|
return root;
|
|
408
1318
|
};
|
|
@@ -438,20 +1348,19 @@ const sealMembers = (fact, changes) => {
|
|
|
438
1348
|
}
|
|
439
1349
|
publishMembers(changes, fact.at, members, order);
|
|
440
1350
|
};
|
|
441
|
-
const sealTree = (fact,
|
|
442
|
-
const location = resolveValue(
|
|
1351
|
+
const sealTree = (fact, canonical, changes) => {
|
|
1352
|
+
const location = resolveValue(canonical.schema, canonical.document, fact.at);
|
|
443
1353
|
const tree = location?.value;
|
|
444
|
-
|
|
445
|
-
if (node?.kind !== "tree") throw new Error("Tree schema disappeared before sealing.");
|
|
1354
|
+
if ((location?.node)?.kind !== "tree") throw new Error("Tree schema disappeared before sealing.");
|
|
446
1355
|
const nodes = [];
|
|
447
1356
|
for (const [id, before] of fact.nodes) {
|
|
448
1357
|
const after = Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
449
1358
|
if (!before && !after) continue;
|
|
450
|
-
if (before && after &&
|
|
1359
|
+
if (before && after && equalTreeNode(before, after)) continue;
|
|
451
1360
|
nodes.push(!before ? {
|
|
452
1361
|
id,
|
|
453
1362
|
kind: "added",
|
|
454
|
-
after:
|
|
1363
|
+
after: copyTreeNode(after)
|
|
455
1364
|
} : !after ? {
|
|
456
1365
|
id,
|
|
457
1366
|
kind: "removed",
|
|
@@ -460,7 +1369,7 @@ const sealTree = (fact, state, changes) => {
|
|
|
460
1369
|
id,
|
|
461
1370
|
kind: "updated",
|
|
462
1371
|
before,
|
|
463
|
-
after:
|
|
1372
|
+
after: copyTreeNode(after)
|
|
464
1373
|
});
|
|
465
1374
|
}
|
|
466
1375
|
const after = tree.rootId ?? null;
|
|
@@ -642,7 +1551,7 @@ var ChangeRecorder = class {
|
|
|
642
1551
|
}
|
|
643
1552
|
if (id !== void 0 && !fact.nodes.has(id)) {
|
|
644
1553
|
profile.recorder("treeNodes");
|
|
645
|
-
fact.nodes.set(id, Object.hasOwn(tree.nodes, id) ?
|
|
1554
|
+
fact.nodes.set(id, Object.hasOwn(tree.nodes, id) ? copyTreeNode(tree.nodes[id]) : void 0);
|
|
646
1555
|
}
|
|
647
1556
|
}
|
|
648
1557
|
rollback() {
|
|
@@ -695,8 +1604,8 @@ var MutationSession = class {
|
|
|
695
1604
|
this.resolvedRoot = state.document;
|
|
696
1605
|
}
|
|
697
1606
|
validate(node, value, at) {
|
|
698
|
-
const
|
|
699
|
-
if (
|
|
1607
|
+
const failure = checkValue(node, value, at);
|
|
1608
|
+
if (failure) invalidValue(failure);
|
|
700
1609
|
}
|
|
701
1610
|
resolveContainer(at) {
|
|
702
1611
|
return this.resolver.container(this.identity, this.generation, at) ?? fail(at, "invalid-address", "Container does not exist.");
|
|
@@ -759,13 +1668,13 @@ var MutationSession = class {
|
|
|
759
1668
|
const previous = container.parent[physicalKey];
|
|
760
1669
|
if (existed === present && (!present || Object.is(previous, value))) return false;
|
|
761
1670
|
if (parentNode.kind === "map" || parentNode.kind === "table") {
|
|
762
|
-
const
|
|
763
|
-
if (
|
|
1671
|
+
const failure = checkKey(parentNode.key, key, []);
|
|
1672
|
+
if (failure) return fail(container.at.concat(key, ...failure.address), "invalid-key", failure.message);
|
|
764
1673
|
}
|
|
765
1674
|
if (present) {
|
|
766
1675
|
if (node.kind === "field") {
|
|
767
|
-
const
|
|
768
|
-
if (
|
|
1676
|
+
const failure = checkValue(node, value, []);
|
|
1677
|
+
if (failure) return invalidValue(failure, container.at.concat(key, ...failure.address));
|
|
769
1678
|
} else this.validate(node, value, container.at.concat(key));
|
|
770
1679
|
if (parentNode.kind === "list" && parentNode.keyOf(value) !== key) return fail(container.at.concat(key), "invalid-list-key", "Replacing an item must retain its addressed key.");
|
|
771
1680
|
}
|
|
@@ -829,16 +1738,178 @@ function apply(session, changes, direction) {
|
|
|
829
1738
|
else current.rootId = root;
|
|
830
1739
|
for (const item of change.nodes) {
|
|
831
1740
|
const next = side === "after" ? item.kind !== "removed" ? item.after : void 0 : item.kind !== "added" ? item.before : void 0;
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
1741
|
+
if (next !== void 0) {
|
|
1742
|
+
const diagnostic = checkTreePayload(node.value, next, change.at.concat(item.id));
|
|
1743
|
+
if (diagnostic) invalidValue(diagnostic);
|
|
1744
|
+
}
|
|
1745
|
+
installMember(current.nodes, item.id, next !== void 0, next && copyTreeNode(next));
|
|
836
1746
|
}
|
|
837
|
-
|
|
1747
|
+
if (!validate(current)) return fail(change.at, "invalid-tree", "Invalid tree structure.");
|
|
838
1748
|
session.invalidate();
|
|
839
1749
|
}
|
|
840
1750
|
}
|
|
841
1751
|
//#endregion
|
|
1752
|
+
//#region core/src/runtime/notification.ts
|
|
1753
|
+
const attachProjection = (notification, processor) => {
|
|
1754
|
+
const entry = {
|
|
1755
|
+
processor,
|
|
1756
|
+
active: true
|
|
1757
|
+
};
|
|
1758
|
+
notification.processors.push(entry);
|
|
1759
|
+
return () => {
|
|
1760
|
+
if (!entry.active) return;
|
|
1761
|
+
entry.active = false;
|
|
1762
|
+
if (!notification.notifying) {
|
|
1763
|
+
const index = notification.processors.indexOf(entry);
|
|
1764
|
+
if (index >= 0) notification.processors.splice(index, 1);
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
};
|
|
1768
|
+
const addRoot = (notification, listener) => {
|
|
1769
|
+
const entry = {
|
|
1770
|
+
listener,
|
|
1771
|
+
active: true
|
|
1772
|
+
};
|
|
1773
|
+
notification.root.add(entry);
|
|
1774
|
+
return () => {
|
|
1775
|
+
entry.active = false;
|
|
1776
|
+
if (!notification.notifying) notification.root.delete(entry);
|
|
1777
|
+
else notification.cleanups.add(() => {
|
|
1778
|
+
notification.root.delete(entry);
|
|
1779
|
+
});
|
|
1780
|
+
};
|
|
1781
|
+
};
|
|
1782
|
+
const addFiltered = (notification, targets, listener) => {
|
|
1783
|
+
const entry = {
|
|
1784
|
+
targets: Object.freeze(targets.slice()),
|
|
1785
|
+
listener,
|
|
1786
|
+
active: true
|
|
1787
|
+
};
|
|
1788
|
+
notification.filtered.add(entry);
|
|
1789
|
+
entry.targets.forEach((value) => notification.index.add(value, entry));
|
|
1790
|
+
const remove = () => {
|
|
1791
|
+
notification.filtered.delete(entry);
|
|
1792
|
+
entry.targets.forEach((value) => notification.index.delete(value, entry));
|
|
1793
|
+
};
|
|
1794
|
+
return () => {
|
|
1795
|
+
if (!entry.active) return;
|
|
1796
|
+
entry.active = false;
|
|
1797
|
+
if (notification.notifying) notification.cleanups.add(remove);
|
|
1798
|
+
else remove();
|
|
1799
|
+
};
|
|
1800
|
+
};
|
|
1801
|
+
const publishState = (notification, commit, afterSettle) => {
|
|
1802
|
+
notification.notifying = true;
|
|
1803
|
+
const errors = [];
|
|
1804
|
+
const call = (listener) => {
|
|
1805
|
+
try {
|
|
1806
|
+
listener(commit);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
errors.push(Object.freeze({
|
|
1809
|
+
phase: "listener",
|
|
1810
|
+
error
|
|
1811
|
+
}));
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
try {
|
|
1815
|
+
const processorCount = notification.processors.length;
|
|
1816
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1817
|
+
const entry = notification.processors[index];
|
|
1818
|
+
if (!entry?.active) continue;
|
|
1819
|
+
try {
|
|
1820
|
+
entry.processor.capture(commit);
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
errors.push(Object.freeze({
|
|
1823
|
+
phase: "processor",
|
|
1824
|
+
error
|
|
1825
|
+
}));
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1829
|
+
const entry = notification.processors[index];
|
|
1830
|
+
if (!entry?.active) continue;
|
|
1831
|
+
try {
|
|
1832
|
+
entry.processor.settle();
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
errors.push(Object.freeze({
|
|
1835
|
+
phase: "processor",
|
|
1836
|
+
error
|
|
1837
|
+
}));
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1841
|
+
const entry = notification.processors[index];
|
|
1842
|
+
if (!entry?.active) continue;
|
|
1843
|
+
try {
|
|
1844
|
+
errors.push(...entry.processor.flush());
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
errors.push(Object.freeze({
|
|
1847
|
+
phase: "flush",
|
|
1848
|
+
error
|
|
1849
|
+
}));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
if (afterSettle) errors.push(...afterSettle());
|
|
1853
|
+
const candidates = notification.candidates;
|
|
1854
|
+
candidates.clear();
|
|
1855
|
+
if (notification.filtered.size) notification.index.collect(commit.changes, (entry) => candidates.add(entry));
|
|
1856
|
+
candidates.forEach((entry) => {
|
|
1857
|
+
if (entry.active) call(entry.listener);
|
|
1858
|
+
});
|
|
1859
|
+
const rootSnapshot = notification.rootSnapshot;
|
|
1860
|
+
rootSnapshot.length = 0;
|
|
1861
|
+
notification.root.forEach((entry) => rootSnapshot.push(entry));
|
|
1862
|
+
for (const entry of rootSnapshot) if (entry.active) call(entry.listener);
|
|
1863
|
+
} finally {
|
|
1864
|
+
notification.notifying = false;
|
|
1865
|
+
let writeIndex = 0;
|
|
1866
|
+
for (const entry of notification.processors) if (entry.active) notification.processors[writeIndex++] = entry;
|
|
1867
|
+
notification.processors.length = writeIndex;
|
|
1868
|
+
notification.cleanups.forEach((cleanup) => cleanup());
|
|
1869
|
+
notification.cleanups.clear();
|
|
1870
|
+
}
|
|
1871
|
+
return Object.freeze(errors);
|
|
1872
|
+
};
|
|
1873
|
+
const disposeState = (notification) => {
|
|
1874
|
+
const attachments = notification.processors.slice();
|
|
1875
|
+
notification.root.clear();
|
|
1876
|
+
notification.filtered.clear();
|
|
1877
|
+
notification.index.clear();
|
|
1878
|
+
notification.cleanups.clear();
|
|
1879
|
+
notification.processors.length = 0;
|
|
1880
|
+
notification.candidates.clear();
|
|
1881
|
+
notification.rootSnapshot.length = 0;
|
|
1882
|
+
const errors = [];
|
|
1883
|
+
attachments.forEach((entry) => {
|
|
1884
|
+
if (!entry.active) return;
|
|
1885
|
+
try {
|
|
1886
|
+
entry.processor.dispose();
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
errors.push(error);
|
|
1889
|
+
}
|
|
1890
|
+
});
|
|
1891
|
+
if (errors.length) throw new AggregateError(errors, "Projection disposal notification failed.");
|
|
1892
|
+
};
|
|
1893
|
+
const createNotificationCenter = (schema) => {
|
|
1894
|
+
const state = {
|
|
1895
|
+
root: /* @__PURE__ */ new Set(),
|
|
1896
|
+
filtered: /* @__PURE__ */ new Set(),
|
|
1897
|
+
index: new SubscriptionIndex(schema),
|
|
1898
|
+
cleanups: /* @__PURE__ */ new Set(),
|
|
1899
|
+
processors: [],
|
|
1900
|
+
candidates: /* @__PURE__ */ new Set(),
|
|
1901
|
+
rootSnapshot: [],
|
|
1902
|
+
notifying: false
|
|
1903
|
+
};
|
|
1904
|
+
return Object.freeze({
|
|
1905
|
+
subscribe: (listener) => addRoot(state, listener),
|
|
1906
|
+
subscribeTargets: (targets, listener) => addFiltered(state, targets, listener),
|
|
1907
|
+
attachProjection: (processor) => attachProjection(state, processor),
|
|
1908
|
+
publish: (commit, afterSettle) => publishState(state, commit, afterSettle),
|
|
1909
|
+
dispose: () => disposeState(state)
|
|
1910
|
+
});
|
|
1911
|
+
};
|
|
1912
|
+
//#endregion
|
|
842
1913
|
//#region core/src/runtime.ts
|
|
843
1914
|
const createDocument = (input) => {
|
|
844
1915
|
if (input.schema.kind !== "object") throw new TypeError("Document schema must be a root object node.");
|
|
@@ -847,13 +1918,15 @@ const createDocument = (input) => {
|
|
|
847
1918
|
const state = {
|
|
848
1919
|
schema: input.schema,
|
|
849
1920
|
document: copyValue(input.schema, input.initial),
|
|
850
|
-
disposed: false
|
|
1921
|
+
disposed: false,
|
|
1922
|
+
projectionLocks: 0
|
|
851
1923
|
};
|
|
852
1924
|
let revision = 0, busy = false;
|
|
853
|
-
|
|
1925
|
+
const notifications = createNotificationCenter(input.schema);
|
|
1926
|
+
let runtime, context;
|
|
854
1927
|
const idle = () => {
|
|
855
1928
|
if (state.disposed) throw new DocumentDisposedError();
|
|
856
|
-
if (busy ||
|
|
1929
|
+
if (busy || state.projectionLocks) throw new DocumentReentrancyError();
|
|
857
1930
|
};
|
|
858
1931
|
const writable = (intent) => {
|
|
859
1932
|
idle();
|
|
@@ -892,7 +1965,7 @@ const createDocument = (input) => {
|
|
|
892
1965
|
return {
|
|
893
1966
|
status: "committed",
|
|
894
1967
|
commit,
|
|
895
|
-
observerErrors:
|
|
1968
|
+
observerErrors: notifications.publish(commit, history.flush)
|
|
896
1969
|
};
|
|
897
1970
|
};
|
|
898
1971
|
function mutate(intent, recordHistory, execute) {
|
|
@@ -929,93 +2002,567 @@ const createDocument = (input) => {
|
|
|
929
2002
|
}
|
|
930
2003
|
runtime = {
|
|
931
2004
|
schema: input.schema,
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
2005
|
+
revision: () => revision,
|
|
2006
|
+
update: (run, options) => {
|
|
2007
|
+
const source = options?.source ?? "local";
|
|
2008
|
+
let value;
|
|
2009
|
+
const result = mutate({
|
|
2010
|
+
kind: "update",
|
|
2011
|
+
source
|
|
2012
|
+
}, options?.history ?? true, (session) => {
|
|
2013
|
+
value = run(createAccess({
|
|
2014
|
+
state,
|
|
2015
|
+
session
|
|
2016
|
+
}));
|
|
2017
|
+
if (value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") throw new TypeError("Document updates must be synchronous.");
|
|
2018
|
+
});
|
|
2019
|
+
return result.status === "rejected" ? result : {
|
|
2020
|
+
...result,
|
|
2021
|
+
value
|
|
2022
|
+
};
|
|
2023
|
+
},
|
|
2024
|
+
apply: (inputChanges, options) => mutate({
|
|
2025
|
+
kind: "apply",
|
|
2026
|
+
source: options?.source ?? "local"
|
|
2027
|
+
}, options?.history ?? true, (session) => {
|
|
2028
|
+
if (!options || options.expectedRevision !== revision) fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
|
|
2029
|
+
apply(session, decodeChanges(inputChanges), "forward");
|
|
2030
|
+
}),
|
|
2031
|
+
replace: (value, options) => {
|
|
2032
|
+
const source = options?.source ?? "system";
|
|
2033
|
+
return mutate({
|
|
2034
|
+
kind: "replace",
|
|
2035
|
+
source
|
|
2036
|
+
}, source !== "remote", (session) => session.replace([], value));
|
|
2037
|
+
},
|
|
2038
|
+
snapshot: () => {
|
|
2039
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2040
|
+
return copyValue(input.schema, state.document);
|
|
2041
|
+
},
|
|
2042
|
+
subscribe: ((pick, listener) => {
|
|
2043
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2044
|
+
if (!listener) return notifications.subscribe(pick);
|
|
2045
|
+
const picks = Array.isArray(pick) ? pick : [pick];
|
|
2046
|
+
if (!picks.length) throw new TypeError("Expected at least one subscription path.");
|
|
2047
|
+
return notifications.subscribeTargets(picks.map((p) => compilePath(input.schema, "value", p)), listener);
|
|
2048
|
+
}),
|
|
2049
|
+
history: history.api,
|
|
2050
|
+
dispose: () => {
|
|
2051
|
+
if (state.disposed) return;
|
|
2052
|
+
idle();
|
|
2053
|
+
state.disposed = true;
|
|
2054
|
+
try {
|
|
2055
|
+
notifications.dispose();
|
|
2056
|
+
} finally {
|
|
2057
|
+
history.dispose();
|
|
2058
|
+
context.driver = void 0;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
context = {
|
|
2063
|
+
state,
|
|
2064
|
+
owner: runtime,
|
|
2065
|
+
notifications,
|
|
2066
|
+
driver: void 0,
|
|
2067
|
+
bypassDepth: 0
|
|
2068
|
+
};
|
|
2069
|
+
bindContext(runtime, context);
|
|
2070
|
+
bindContext(history.api, context);
|
|
2071
|
+
return runtime;
|
|
2072
|
+
};
|
|
2073
|
+
//#endregion
|
|
2074
|
+
//#region core/src/runtime/readable.ts
|
|
2075
|
+
const asReadable = (runtime) => {
|
|
2076
|
+
const readable = {
|
|
2077
|
+
revision: runtime.revision,
|
|
2078
|
+
subscribe: runtime.subscribe
|
|
2079
|
+
};
|
|
2080
|
+
bindContext(readable, contextOf(runtime));
|
|
2081
|
+
return Object.freeze(readable);
|
|
2082
|
+
};
|
|
2083
|
+
//#endregion
|
|
2084
|
+
//#region core/src/access/dependency.ts
|
|
2085
|
+
const createDependencyTracker = () => {
|
|
2086
|
+
const targets = [];
|
|
2087
|
+
return {
|
|
2088
|
+
record: (value) => {
|
|
2089
|
+
if (!targets.some((entry) => same(entry, value))) targets.push(value);
|
|
2090
|
+
},
|
|
2091
|
+
snapshot: () => Object.freeze(targets.slice())
|
|
2092
|
+
};
|
|
2093
|
+
};
|
|
2094
|
+
//#endregion
|
|
2095
|
+
//#region core/src/runtime/access.ts
|
|
2096
|
+
const readWith = (runtime, run, dependencies) => {
|
|
2097
|
+
const state = contextOf(runtime).state;
|
|
2098
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2099
|
+
state.projectionLocks += 1;
|
|
2100
|
+
try {
|
|
2101
|
+
return run(createAccess({
|
|
2102
|
+
state,
|
|
2103
|
+
dependencies
|
|
2104
|
+
}));
|
|
2105
|
+
} finally {
|
|
2106
|
+
state.projectionLocks--;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
//#endregion
|
|
2110
|
+
//#region core/src/runtime/select.ts
|
|
2111
|
+
const read = (document, selector) => readWith(document, selector);
|
|
2112
|
+
const sameDependencySet = (left, right) => {
|
|
2113
|
+
if (left.length !== right.length) return false;
|
|
2114
|
+
for (let index = 0; index < left.length; index += 1) if (!same(left[index], right[index])) return false;
|
|
2115
|
+
return true;
|
|
2116
|
+
};
|
|
2117
|
+
const select = (document, selector, equality = Object.is) => {
|
|
2118
|
+
const context = contextOf(document);
|
|
2119
|
+
let initialized = false;
|
|
2120
|
+
let value;
|
|
2121
|
+
let selectedRevision = 0;
|
|
2122
|
+
let documentRevision = -1;
|
|
2123
|
+
let dependencies = Object.freeze([]);
|
|
2124
|
+
let unsubscribeDocument;
|
|
2125
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2126
|
+
const evaluate = () => {
|
|
2127
|
+
const tracker = createDependencyTracker();
|
|
2128
|
+
const next = readWith(document, selector, tracker);
|
|
2129
|
+
const nextDependencies = tracker.snapshot();
|
|
2130
|
+
const changed = !initialized || !equality(value, next);
|
|
2131
|
+
const rebound = !sameDependencySet(dependencies, nextDependencies);
|
|
2132
|
+
if (changed) {
|
|
2133
|
+
if (initialized) selectedRevision += 1;
|
|
2134
|
+
value = next;
|
|
2135
|
+
}
|
|
2136
|
+
dependencies = nextDependencies;
|
|
2137
|
+
initialized = true;
|
|
2138
|
+
documentRevision = document.revision();
|
|
2139
|
+
return {
|
|
2140
|
+
changed,
|
|
2141
|
+
rebound
|
|
2142
|
+
};
|
|
2143
|
+
};
|
|
2144
|
+
const installSubscription = () => {
|
|
2145
|
+
unsubscribeDocument?.();
|
|
2146
|
+
unsubscribeDocument = dependencies.length > 0 ? context.notifications.subscribeTargets(dependencies, () => {
|
|
2147
|
+
const result = evaluate();
|
|
2148
|
+
if (result.rebound) installSubscription();
|
|
2149
|
+
if (result.changed) for (const listener of [...listeners]) listener();
|
|
2150
|
+
}) : void 0;
|
|
2151
|
+
};
|
|
2152
|
+
const ensureCurrent = () => {
|
|
2153
|
+
if (context.state.disposed) throw new DocumentDisposedError();
|
|
2154
|
+
if (!initialized || !unsubscribeDocument && documentRevision !== document.revision()) evaluate();
|
|
2155
|
+
return value;
|
|
2156
|
+
};
|
|
2157
|
+
return Object.freeze({
|
|
2158
|
+
current: ensureCurrent,
|
|
2159
|
+
revision: () => {
|
|
2160
|
+
ensureCurrent();
|
|
2161
|
+
return selectedRevision;
|
|
938
2162
|
},
|
|
2163
|
+
subscribe: (listener) => {
|
|
2164
|
+
ensureCurrent();
|
|
2165
|
+
listeners.add(listener);
|
|
2166
|
+
if (listeners.size === 1) installSubscription();
|
|
2167
|
+
return () => {
|
|
2168
|
+
listeners.delete(listener);
|
|
2169
|
+
if (!listeners.size) {
|
|
2170
|
+
unsubscribeDocument?.();
|
|
2171
|
+
unsubscribeDocument = void 0;
|
|
2172
|
+
}
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
};
|
|
2177
|
+
//#endregion
|
|
2178
|
+
//#region core/src/projection/collection/change.ts
|
|
2179
|
+
const commonOrderChanged = (before, after, added, removed) => {
|
|
2180
|
+
const beforeCommon = before.filter((key) => !removed.has(key));
|
|
2181
|
+
const afterCommon = after.filter((key) => !added.has(key));
|
|
2182
|
+
return beforeCommon.some((key, index) => afterCommon[index] !== key);
|
|
2183
|
+
};
|
|
2184
|
+
/** Builds the canonical incremental CollectionChange from already-known net transitions. */
|
|
2185
|
+
const createCollectionChange = (input) => {
|
|
2186
|
+
const addedKeys = new Set(input.added.map((entry) => entry.key));
|
|
2187
|
+
const removedKeys = new Set(input.removed.map((entry) => entry.key));
|
|
2188
|
+
const orderChanged = input.beforeOrder !== input.afterOrder && commonOrderChanged(input.beforeOrder, input.afterOrder, addedKeys, removedKeys);
|
|
2189
|
+
if (!input.added.length && !input.updated.length && !input.removed.length && !orderChanged) return void 0;
|
|
2190
|
+
return Object.freeze({
|
|
2191
|
+
kind: "incremental",
|
|
2192
|
+
added: Object.freeze([...input.added]),
|
|
2193
|
+
updated: Object.freeze([...input.updated]),
|
|
2194
|
+
removed: Object.freeze([...input.removed]),
|
|
2195
|
+
...orderChanged ? { order: Object.freeze({
|
|
2196
|
+
before: Object.freeze([...input.beforeOrder]),
|
|
2197
|
+
after: Object.freeze([...input.afterOrder])
|
|
2198
|
+
}) } : {}
|
|
2199
|
+
});
|
|
2200
|
+
};
|
|
2201
|
+
/** Exact fallback diff between two published map-like snapshots. */
|
|
2202
|
+
const diffCollection = (previous, current, isEqual = Object.is) => {
|
|
2203
|
+
const added = [];
|
|
2204
|
+
const updated = [];
|
|
2205
|
+
const removed = [];
|
|
2206
|
+
for (const [key, before] of previous) if (!current.has(key)) removed.push({
|
|
2207
|
+
key,
|
|
2208
|
+
before
|
|
2209
|
+
});
|
|
2210
|
+
else {
|
|
2211
|
+
const after = current.get(key);
|
|
2212
|
+
if (!isEqual(before, after)) updated.push({
|
|
2213
|
+
key,
|
|
2214
|
+
before,
|
|
2215
|
+
after
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
for (const [key, after] of current) if (!previous.has(key)) added.push({
|
|
2219
|
+
key,
|
|
2220
|
+
after
|
|
2221
|
+
});
|
|
2222
|
+
return createCollectionChange({
|
|
2223
|
+
added,
|
|
2224
|
+
updated,
|
|
2225
|
+
removed,
|
|
2226
|
+
beforeOrder: [...previous.keys()],
|
|
2227
|
+
afterOrder: [...current.keys()]
|
|
2228
|
+
});
|
|
2229
|
+
};
|
|
2230
|
+
const collectionChangedKeys = (change) => change.kind === "reset" ? /* @__PURE__ */ new Set() : new Set([
|
|
2231
|
+
...change.added.map((entry) => entry.key),
|
|
2232
|
+
...change.updated.map((entry) => entry.key),
|
|
2233
|
+
...change.removed.map((entry) => entry.key)
|
|
2234
|
+
]);
|
|
2235
|
+
const collectionHasStructuralChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.removed.length || change.order);
|
|
2236
|
+
const collectionHasAnyChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.updated.length || change.removed.length || change.order);
|
|
2237
|
+
//#endregion
|
|
2238
|
+
//#region core/src/projection/collection/index.ts
|
|
2239
|
+
const height = (node) => node?.height ?? 0;
|
|
2240
|
+
const node = (key, value, left, right) => {
|
|
2241
|
+
profile.collectionIndex.node();
|
|
2242
|
+
return Object.freeze({
|
|
2243
|
+
key,
|
|
2244
|
+
value,
|
|
2245
|
+
left,
|
|
2246
|
+
right,
|
|
2247
|
+
height: Math.max(height(left), height(right)) + 1
|
|
2248
|
+
});
|
|
2249
|
+
};
|
|
2250
|
+
const rotateLeft = (current) => {
|
|
2251
|
+
const right = current.right;
|
|
2252
|
+
return node(right.key, right.value, node(current.key, current.value, current.left, right.left), right.right);
|
|
2253
|
+
};
|
|
2254
|
+
const rotateRight = (current) => {
|
|
2255
|
+
const left = current.left;
|
|
2256
|
+
return node(left.key, left.value, left.left, node(current.key, current.value, left.right, current.right));
|
|
2257
|
+
};
|
|
2258
|
+
const rebalance = (current) => {
|
|
2259
|
+
const balance = height(current.left) - height(current.right);
|
|
2260
|
+
if (balance > 1) {
|
|
2261
|
+
if (height(current.left.left) < height(current.left.right)) return rotateRight(node(current.key, current.value, rotateLeft(current.left), current.right));
|
|
2262
|
+
return rotateRight(current);
|
|
2263
|
+
}
|
|
2264
|
+
if (balance < -1) {
|
|
2265
|
+
if (height(current.right.right) < height(current.right.left)) return rotateLeft(node(current.key, current.value, current.left, rotateRight(current.right)));
|
|
2266
|
+
return rotateLeft(current);
|
|
2267
|
+
}
|
|
2268
|
+
return current;
|
|
2269
|
+
};
|
|
2270
|
+
const setNode = (current, key, value) => {
|
|
2271
|
+
if (!current) return node(key, value);
|
|
2272
|
+
if (key === current.key) return node(key, value, current.left, current.right);
|
|
2273
|
+
if (key < current.key) return rebalance(node(current.key, current.value, setNode(current.left, key, value), current.right));
|
|
2274
|
+
return rebalance(node(current.key, current.value, current.left, setNode(current.right, key, value)));
|
|
2275
|
+
};
|
|
2276
|
+
const minNode = (current) => current.left ? minNode(current.left) : current;
|
|
2277
|
+
const removeNode = (current, key) => {
|
|
2278
|
+
if (!current) return void 0;
|
|
2279
|
+
if (key < current.key) return rebalance(node(current.key, current.value, removeNode(current.left, key), current.right));
|
|
2280
|
+
if (key > current.key) return rebalance(node(current.key, current.value, current.left, removeNode(current.right, key)));
|
|
2281
|
+
if (!current.left) return current.right;
|
|
2282
|
+
if (!current.right) return current.left;
|
|
2283
|
+
const next = minNode(current.right);
|
|
2284
|
+
return rebalance(node(next.key, next.value, current.left, removeNode(current.right, next.key)));
|
|
2285
|
+
};
|
|
2286
|
+
const fromSorted = (entries, start = 0, end = entries.length) => {
|
|
2287
|
+
if (start >= end) return void 0;
|
|
2288
|
+
const middle = start + end >> 1;
|
|
2289
|
+
const [key, value] = entries[middle];
|
|
2290
|
+
return node(key, value, fromSorted(entries, start, middle), fromSorted(entries, middle + 1, end));
|
|
2291
|
+
};
|
|
2292
|
+
/** Immutable keyed lookup used to keep published collection snapshots durable across revisions. */
|
|
2293
|
+
var PersistentKeyedIndex = class PersistentKeyedIndex {
|
|
2294
|
+
constructor(root) {
|
|
2295
|
+
this.root = root;
|
|
2296
|
+
}
|
|
2297
|
+
static empty() {
|
|
2298
|
+
return new PersistentKeyedIndex();
|
|
2299
|
+
}
|
|
2300
|
+
static from(entries) {
|
|
2301
|
+
const sorted = [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
2302
|
+
profile.collectionIndex.build(sorted.length);
|
|
2303
|
+
return new PersistentKeyedIndex(fromSorted(sorted));
|
|
2304
|
+
}
|
|
2305
|
+
get(key) {
|
|
2306
|
+
let current = this.root;
|
|
2307
|
+
while (current) {
|
|
2308
|
+
if (key === current.key) return current.value;
|
|
2309
|
+
current = key < current.key ? current.left : current.right;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
has(key) {
|
|
2313
|
+
let current = this.root;
|
|
2314
|
+
while (current) {
|
|
2315
|
+
if (key === current.key) return true;
|
|
2316
|
+
current = key < current.key ? current.left : current.right;
|
|
2317
|
+
}
|
|
2318
|
+
return false;
|
|
2319
|
+
}
|
|
2320
|
+
set(key, value) {
|
|
2321
|
+
return new PersistentKeyedIndex(setNode(this.root, key, value));
|
|
2322
|
+
}
|
|
2323
|
+
remove(key) {
|
|
2324
|
+
return new PersistentKeyedIndex(removeNode(this.root, key));
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
//#endregion
|
|
2328
|
+
//#region core/src/projection/output/collection.ts
|
|
2329
|
+
const createPublishedCollection = () => {
|
|
2330
|
+
const values = /* @__PURE__ */ new Map();
|
|
2331
|
+
let ids = Object.freeze([]);
|
|
2332
|
+
let index = PersistentKeyedIndex.empty();
|
|
2333
|
+
return {
|
|
2334
|
+
get: (key) => values.get(key),
|
|
2335
|
+
has: (key) => values.has(key),
|
|
2336
|
+
keys: () => values.keys(),
|
|
2337
|
+
size: () => values.size,
|
|
2338
|
+
ids: () => ids,
|
|
2339
|
+
read: (check) => {
|
|
2340
|
+
const snapshotIndex = index;
|
|
2341
|
+
const snapshotIds = ids;
|
|
2342
|
+
return Object.freeze({
|
|
2343
|
+
get: (key) => {
|
|
2344
|
+
check();
|
|
2345
|
+
return snapshotIndex.get(key);
|
|
2346
|
+
},
|
|
2347
|
+
has: (key) => {
|
|
2348
|
+
check();
|
|
2349
|
+
return snapshotIndex.has(key);
|
|
2350
|
+
},
|
|
2351
|
+
ids: () => {
|
|
2352
|
+
check();
|
|
2353
|
+
return snapshotIds;
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
},
|
|
2357
|
+
publish: (staged, nextIds, reset) => {
|
|
2358
|
+
if (reset) {
|
|
2359
|
+
const entries = [];
|
|
2360
|
+
for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
|
|
2361
|
+
index = PersistentKeyedIndex.from(entries);
|
|
2362
|
+
values.clear();
|
|
2363
|
+
} else for (const [key, entry] of staged) index = entry.present ? index.set(key, entry.value) : index.remove(key);
|
|
2364
|
+
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
2365
|
+
else values.delete(key);
|
|
2366
|
+
ids = nextIds;
|
|
2367
|
+
},
|
|
2368
|
+
release: () => {
|
|
2369
|
+
values.clear();
|
|
2370
|
+
ids = Object.freeze([]);
|
|
2371
|
+
index = PersistentKeyedIndex.empty();
|
|
2372
|
+
}
|
|
2373
|
+
};
|
|
2374
|
+
};
|
|
2375
|
+
/** Owns staged keyed intent, exact transitions and published collection state. */
|
|
2376
|
+
const createCollectionOutput = () => {
|
|
2377
|
+
const published = createPublishedCollection();
|
|
2378
|
+
let staged = /* @__PURE__ */ new Map();
|
|
2379
|
+
let nextIds = published.ids();
|
|
2380
|
+
let change;
|
|
2381
|
+
let initialized = false;
|
|
2382
|
+
let reset = false;
|
|
2383
|
+
let revision = 0;
|
|
2384
|
+
let explicitOrder;
|
|
2385
|
+
let cleared = false;
|
|
2386
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2387
|
+
const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && published.has(key);
|
|
2388
|
+
const getNext = (key) => {
|
|
2389
|
+
const entry = staged.get(key);
|
|
2390
|
+
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : published.get(key);
|
|
2391
|
+
};
|
|
2392
|
+
const deriveIds = () => {
|
|
2393
|
+
const result = cleared ? [] : published.ids().filter(hasNext);
|
|
2394
|
+
for (const [key, entry] of staged) if (entry.present && (cleared || !published.has(key))) result.push(key);
|
|
2395
|
+
return result;
|
|
2396
|
+
};
|
|
2397
|
+
const begin = (active, initialize) => {
|
|
2398
|
+
staged = /* @__PURE__ */ new Map();
|
|
2399
|
+
nextIds = published.ids();
|
|
2400
|
+
change = void 0;
|
|
2401
|
+
explicitOrder = void 0;
|
|
2402
|
+
cleared = initialize;
|
|
2403
|
+
reset = initialize;
|
|
2404
|
+
const read = (next) => ({
|
|
2405
|
+
get: (key) => {
|
|
2406
|
+
assertScope(active);
|
|
2407
|
+
return next ? getNext(key) : published.get(key);
|
|
2408
|
+
},
|
|
2409
|
+
has: (key) => {
|
|
2410
|
+
assertScope(active);
|
|
2411
|
+
return next ? hasNext(key) : published.has(key);
|
|
2412
|
+
},
|
|
2413
|
+
ids: () => {
|
|
2414
|
+
assertScope(active);
|
|
2415
|
+
return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
|
|
2416
|
+
}
|
|
2417
|
+
});
|
|
2418
|
+
return {
|
|
2419
|
+
previous: read(false),
|
|
2420
|
+
next: read(true),
|
|
2421
|
+
output: {
|
|
2422
|
+
set: (key, value) => {
|
|
2423
|
+
assertScope(active);
|
|
2424
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2425
|
+
staged.set(key, {
|
|
2426
|
+
present: true,
|
|
2427
|
+
value
|
|
2428
|
+
});
|
|
2429
|
+
},
|
|
2430
|
+
remove: (key) => {
|
|
2431
|
+
assertScope(active);
|
|
2432
|
+
staged.set(key, { present: false });
|
|
2433
|
+
},
|
|
2434
|
+
order: (order) => {
|
|
2435
|
+
assertScope(active);
|
|
2436
|
+
explicitOrder = Object.freeze([...order]);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
};
|
|
2441
|
+
const seal = (nextReset, isEqual) => {
|
|
2442
|
+
if (cleared) {
|
|
2443
|
+
for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
2444
|
+
}
|
|
2445
|
+
profile.projection("touchedKeys", staged.size);
|
|
2446
|
+
const added = /* @__PURE__ */ new Set();
|
|
2447
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2448
|
+
const updated = /* @__PURE__ */ new Set();
|
|
2449
|
+
for (const [key, entry] of staged) {
|
|
2450
|
+
const existed = published.has(key);
|
|
2451
|
+
if (entry.present) {
|
|
2452
|
+
if (!existed) added.add(key);
|
|
2453
|
+
else if (!isEqual(published.get(key), entry.value)) updated.add(key);
|
|
2454
|
+
else if (!nextReset) staged.delete(key);
|
|
2455
|
+
} else if (existed) removed.add(key);
|
|
2456
|
+
else staged.delete(key);
|
|
2457
|
+
}
|
|
2458
|
+
const ids = published.ids();
|
|
2459
|
+
if (explicitOrder || added.size || removed.size) {
|
|
2460
|
+
const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
|
|
2461
|
+
if (new Set(order).size !== order.length || order.length !== published.size() + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
|
|
2462
|
+
nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
|
|
2463
|
+
} else nextIds = ids;
|
|
2464
|
+
const changedKeys = new Set([
|
|
2465
|
+
...added,
|
|
2466
|
+
...removed,
|
|
2467
|
+
...updated
|
|
2468
|
+
]);
|
|
2469
|
+
profile.projection("changedKeys", changedKeys.size);
|
|
2470
|
+
const addedEntries = [];
|
|
2471
|
+
const updatedEntries = [];
|
|
2472
|
+
const removedEntries = [];
|
|
2473
|
+
for (const key of changedKeys) {
|
|
2474
|
+
const beforePresent = published.has(key);
|
|
2475
|
+
const afterPresent = hasNext(key);
|
|
2476
|
+
if (beforePresent === afterPresent) {
|
|
2477
|
+
if (!beforePresent || isEqual(published.get(key), getNext(key))) continue;
|
|
2478
|
+
}
|
|
2479
|
+
if (!beforePresent) addedEntries.push({
|
|
2480
|
+
key,
|
|
2481
|
+
after: getNext(key)
|
|
2482
|
+
});
|
|
2483
|
+
else if (!afterPresent) removedEntries.push({
|
|
2484
|
+
key,
|
|
2485
|
+
before: published.get(key)
|
|
2486
|
+
});
|
|
2487
|
+
else updatedEntries.push({
|
|
2488
|
+
key,
|
|
2489
|
+
before: published.get(key),
|
|
2490
|
+
after: getNext(key)
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
change = nextReset ? Object.freeze({ kind: "reset" }) : createCollectionChange({
|
|
2494
|
+
added: addedEntries,
|
|
2495
|
+
updated: updatedEntries,
|
|
2496
|
+
removed: removedEntries,
|
|
2497
|
+
beforeOrder: ids,
|
|
2498
|
+
afterOrder: nextIds
|
|
2499
|
+
});
|
|
2500
|
+
reset = nextReset;
|
|
2501
|
+
return change !== void 0;
|
|
2502
|
+
};
|
|
2503
|
+
const context = (active, cause) => Object.freeze({
|
|
2504
|
+
kind: "collection",
|
|
2505
|
+
read: {
|
|
2506
|
+
get: (key) => {
|
|
2507
|
+
assertScope(active);
|
|
2508
|
+
return getNext(key);
|
|
2509
|
+
},
|
|
2510
|
+
has: (key) => {
|
|
2511
|
+
assertScope(active);
|
|
2512
|
+
return hasNext(key);
|
|
2513
|
+
},
|
|
2514
|
+
ids: () => {
|
|
2515
|
+
assertScope(active);
|
|
2516
|
+
return nextIds;
|
|
2517
|
+
}
|
|
2518
|
+
},
|
|
2519
|
+
change,
|
|
2520
|
+
revision,
|
|
2521
|
+
reset,
|
|
2522
|
+
cause
|
|
2523
|
+
});
|
|
2524
|
+
return {
|
|
2525
|
+
begin,
|
|
2526
|
+
seal,
|
|
2527
|
+
context,
|
|
2528
|
+
current: (check) => published.read(check),
|
|
939
2529
|
revision: () => revision,
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
source
|
|
946
|
-
}, options?.history ?? true, (session) => {
|
|
947
|
-
value = run(createAccess({
|
|
948
|
-
state,
|
|
949
|
-
session
|
|
950
|
-
}));
|
|
951
|
-
if (value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") throw new TypeError("Document updates must be synchronous.");
|
|
952
|
-
});
|
|
953
|
-
return result.status === "rejected" ? result : {
|
|
954
|
-
...result,
|
|
955
|
-
value
|
|
956
|
-
};
|
|
2530
|
+
reset: () => reset,
|
|
2531
|
+
publish: () => {
|
|
2532
|
+
if (change) published.publish(staged, nextIds, change.kind === "reset");
|
|
2533
|
+
if (change && initialized) revision++;
|
|
2534
|
+
initialized = true;
|
|
957
2535
|
},
|
|
958
|
-
|
|
959
|
-
kind: "
|
|
960
|
-
|
|
961
|
-
}, options?.history ?? true, (session) => {
|
|
962
|
-
if (!options || options.expectedRevision !== revision) fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
|
|
963
|
-
apply(session, decodeChanges(inputChanges), "forward");
|
|
964
|
-
}),
|
|
965
|
-
replace: (value, options) => {
|
|
966
|
-
const source = options?.source ?? "system";
|
|
967
|
-
return mutate({
|
|
968
|
-
kind: "replace",
|
|
969
|
-
source
|
|
970
|
-
}, source !== "remote", (session) => session.replace([], value));
|
|
2536
|
+
emit: (call) => {
|
|
2537
|
+
const resetEvent = Object.freeze({ kind: "reset" });
|
|
2538
|
+
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
971
2539
|
},
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
2540
|
+
clear: () => {
|
|
2541
|
+
staged.clear();
|
|
2542
|
+
nextIds = published.ids();
|
|
2543
|
+
change = void 0;
|
|
2544
|
+
reset = false;
|
|
2545
|
+
explicitOrder = void 0;
|
|
2546
|
+
cleared = false;
|
|
975
2547
|
},
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
} finally {
|
|
991
|
-
history.dispose();
|
|
992
|
-
disposeRuntimeDriver(runtime);
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
bindRuntimeAccess(runtime, state);
|
|
997
|
-
bindRuntimeDriver(runtime);
|
|
998
|
-
notification = createNotification(runtime);
|
|
999
|
-
bindDocumentReadable(history.api, runtime);
|
|
1000
|
-
return runtime;
|
|
1001
|
-
};
|
|
1002
|
-
//#endregion
|
|
1003
|
-
//#region core/src/runtime/readable.ts
|
|
1004
|
-
const asReadable = (runtime) => {
|
|
1005
|
-
const readable = {
|
|
1006
|
-
address: runtime.address,
|
|
1007
|
-
revision: runtime.revision,
|
|
1008
|
-
subscribe: runtime.subscribe
|
|
2548
|
+
release: () => {
|
|
2549
|
+
published.release();
|
|
2550
|
+
staged.clear();
|
|
2551
|
+
listeners.clear();
|
|
2552
|
+
nextIds = Object.freeze([]);
|
|
2553
|
+
change = void 0;
|
|
2554
|
+
initialized = false;
|
|
2555
|
+
revision = 0;
|
|
2556
|
+
reset = false;
|
|
2557
|
+
explicitOrder = void 0;
|
|
2558
|
+
cleared = false;
|
|
2559
|
+
},
|
|
2560
|
+
subscribe: (listener) => listeners.add(listener),
|
|
2561
|
+
unsubscribe: (listener) => listeners.delete(listener)
|
|
1009
2562
|
};
|
|
1010
|
-
bindRuntimeAccess(readable, accessOf(runtime));
|
|
1011
|
-
shareNotification(runtime, readable);
|
|
1012
|
-
return Object.freeze(readable);
|
|
1013
2563
|
};
|
|
1014
2564
|
//#endregion
|
|
1015
|
-
//#region core/src/projection/
|
|
1016
|
-
const read = (runtime, selector) => readWith(runtime, selector);
|
|
1017
|
-
//#endregion
|
|
1018
|
-
//#region core/src/projection/value-output.ts
|
|
2565
|
+
//#region core/src/projection/output/value.ts
|
|
1019
2566
|
const unset = Symbol("projection-value-unset");
|
|
1020
2567
|
/** Owns staged and published state for one scalar Projection output. */
|
|
1021
2568
|
const createValueOutput = () => {
|
|
@@ -1107,7 +2654,7 @@ const createValueOutput = () => {
|
|
|
1107
2654
|
};
|
|
1108
2655
|
};
|
|
1109
2656
|
//#endregion
|
|
1110
|
-
//#region core/src/projection/processor.ts
|
|
2657
|
+
//#region core/src/projection/graph/processor.ts
|
|
1111
2658
|
/** Materializes every derive/incremental/group definition through one processor lifecycle. */
|
|
1112
2659
|
const createProcessor = (scheduler, definition, dependencies) => {
|
|
1113
2660
|
scheduler.assertIdle();
|
|
@@ -1269,7 +2816,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1269
2816
|
return processor;
|
|
1270
2817
|
};
|
|
1271
2818
|
//#endregion
|
|
1272
|
-
//#region core/src/projection/
|
|
2819
|
+
//#region core/src/projection/readable/selection.ts
|
|
1273
2820
|
let activeTracker;
|
|
1274
2821
|
const trackKey = (key) => {
|
|
1275
2822
|
activeTracker?.keys.add(key);
|
|
@@ -1320,9 +2867,10 @@ const trackedMap = (source) => {
|
|
|
1320
2867
|
};
|
|
1321
2868
|
const selectionAffects = (selection, change) => {
|
|
1322
2869
|
if (!change || change.kind === "reset") return true;
|
|
1323
|
-
if (selection.all && (change
|
|
1324
|
-
if (selection.structure && (change
|
|
1325
|
-
|
|
2870
|
+
if (selection.all && collectionHasAnyChange(change)) return true;
|
|
2871
|
+
if (selection.structure && collectionHasStructuralChange(change)) return true;
|
|
2872
|
+
const changedKeys = collectionChangedKeys(change);
|
|
2873
|
+
for (const key of selection.keys) if (changedKeys.has(key)) return true;
|
|
1326
2874
|
return false;
|
|
1327
2875
|
};
|
|
1328
2876
|
const createDirectReadable = (source) => Object.freeze({
|
|
@@ -1390,7 +2938,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
1390
2938
|
if (!change && previousCollection) {
|
|
1391
2939
|
const current = source.current();
|
|
1392
2940
|
if (isMapLike(current)) {
|
|
1393
|
-
change =
|
|
2941
|
+
change = diffCollection(previousCollection, current);
|
|
1394
2942
|
previousCollection = current;
|
|
1395
2943
|
if (!change) {
|
|
1396
2944
|
sourceRevision = source.revision();
|
|
@@ -1429,12 +2977,321 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
1429
2977
|
});
|
|
1430
2978
|
};
|
|
1431
2979
|
//#endregion
|
|
1432
|
-
//#region core/src/projection/source.ts
|
|
2980
|
+
//#region core/src/projection/source/materialization.ts
|
|
2981
|
+
const createDocumentDirty = () => ({
|
|
2982
|
+
replace: false,
|
|
2983
|
+
order: false,
|
|
2984
|
+
treeRoot: false,
|
|
2985
|
+
treeNodes: /* @__PURE__ */ new Set(),
|
|
2986
|
+
children: /* @__PURE__ */ new Map()
|
|
2987
|
+
});
|
|
2988
|
+
const clearDocumentDirty = (dirty) => {
|
|
2989
|
+
dirty.replace = false;
|
|
2990
|
+
dirty.order = false;
|
|
2991
|
+
dirty.treeRoot = false;
|
|
2992
|
+
dirty.treeNodes.clear();
|
|
2993
|
+
dirty.children.clear();
|
|
2994
|
+
};
|
|
2995
|
+
const hasDocumentDirty = (dirty) => dirty.replace || dirty.order || dirty.treeRoot || dirty.treeNodes.size > 0 || dirty.children.size > 0;
|
|
2996
|
+
const locate = (dirty, path) => {
|
|
2997
|
+
let current = dirty;
|
|
2998
|
+
for (const key of path) {
|
|
2999
|
+
if (current.replace) return void 0;
|
|
3000
|
+
let child = current.children.get(key);
|
|
3001
|
+
if (!child) {
|
|
3002
|
+
child = createDocumentDirty();
|
|
3003
|
+
current.children.set(key, child);
|
|
3004
|
+
}
|
|
3005
|
+
current = child;
|
|
3006
|
+
}
|
|
3007
|
+
return current;
|
|
3008
|
+
};
|
|
3009
|
+
const markDocumentReplace = (dirty, path) => {
|
|
3010
|
+
const current = locate(dirty, path);
|
|
3011
|
+
if (!current) return;
|
|
3012
|
+
current.replace = true;
|
|
3013
|
+
current.order = false;
|
|
3014
|
+
current.treeRoot = false;
|
|
3015
|
+
current.treeNodes.clear();
|
|
3016
|
+
current.children.clear();
|
|
3017
|
+
};
|
|
3018
|
+
const markDocumentOrder = (dirty, path) => {
|
|
3019
|
+
const current = locate(dirty, path);
|
|
3020
|
+
if (current && !current.replace) current.order = true;
|
|
3021
|
+
};
|
|
3022
|
+
const markDocumentTree = (dirty, path, root, nodes) => {
|
|
3023
|
+
const current = locate(dirty, path);
|
|
3024
|
+
if (!current || current.replace) return;
|
|
3025
|
+
current.treeRoot ||= root;
|
|
3026
|
+
for (const id of nodes) current.treeNodes.add(id);
|
|
3027
|
+
};
|
|
3028
|
+
const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) : equalValue(node, left, right);
|
|
3029
|
+
const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous : copyValue(node, current);
|
|
3030
|
+
const cloneRecord = (value) => {
|
|
3031
|
+
profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
|
|
3032
|
+
return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
|
|
3033
|
+
};
|
|
3034
|
+
const materializeRecordChild = (node, previous, current, key, dirty) => {
|
|
3035
|
+
const before = Object.hasOwn(previous, key);
|
|
3036
|
+
const after = Object.hasOwn(current, key);
|
|
3037
|
+
if (!before && !after) return { changed: false };
|
|
3038
|
+
if (!after) return {
|
|
3039
|
+
changed: true,
|
|
3040
|
+
present: false,
|
|
3041
|
+
value: void 0
|
|
3042
|
+
};
|
|
3043
|
+
const next = before ? materializeDocumentValue(node, previous[key], current[key], dirty) : copyValue(node, current[key]);
|
|
3044
|
+
if (before && next === previous[key]) return { changed: false };
|
|
3045
|
+
return {
|
|
3046
|
+
changed: true,
|
|
3047
|
+
present: true,
|
|
3048
|
+
value: next
|
|
3049
|
+
};
|
|
3050
|
+
};
|
|
3051
|
+
const materializeObject = (node, previous, current, dirty) => {
|
|
3052
|
+
if (node.kind === "variant" && previous[node.tag] !== current[node.tag]) return replaceValue(node, previous, current);
|
|
3053
|
+
const layout = compiledShape(node, current);
|
|
3054
|
+
if (!layout) return replaceValue(node, previous, current);
|
|
3055
|
+
let result;
|
|
3056
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3057
|
+
const member = layout.members.get(key);
|
|
3058
|
+
if (!member) return replaceValue(node, previous, current);
|
|
3059
|
+
const child = materializeRecordChild(member.node, previous, current, key, childDirty);
|
|
3060
|
+
if (!child.changed) continue;
|
|
3061
|
+
result ??= cloneRecord(previous);
|
|
3062
|
+
installOwn(result, key, child.present, child.value);
|
|
3063
|
+
}
|
|
3064
|
+
return result ?? previous;
|
|
3065
|
+
};
|
|
3066
|
+
const materializeMap = (node, previous, current, dirty) => {
|
|
3067
|
+
let result;
|
|
3068
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3069
|
+
const child = materializeRecordChild(node.value, previous, current, key, childDirty);
|
|
3070
|
+
if (!child.changed) continue;
|
|
3071
|
+
result ??= cloneRecord(previous);
|
|
3072
|
+
installOwn(result, key, child.present, child.value);
|
|
3073
|
+
}
|
|
3074
|
+
return result ?? previous;
|
|
3075
|
+
};
|
|
3076
|
+
const materializeTable = (node, previous, current, dirty) => {
|
|
3077
|
+
if (!isRecord(previous.byId) || !isRecord(current.byId)) return replaceValue(node, previous, current);
|
|
3078
|
+
const beforeById = previous.byId;
|
|
3079
|
+
const afterById = current.byId;
|
|
3080
|
+
let result;
|
|
3081
|
+
let nextById;
|
|
3082
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3083
|
+
const child = materializeRecordChild(node.value, beforeById, afterById, key, childDirty);
|
|
3084
|
+
if (!child.changed) continue;
|
|
3085
|
+
nextById ??= cloneRecord(beforeById);
|
|
3086
|
+
installOwn(nextById, key, child.present, child.value);
|
|
3087
|
+
}
|
|
3088
|
+
if (nextById) {
|
|
3089
|
+
result = cloneRecord(previous);
|
|
3090
|
+
installOwn(result, "byId", true, nextById);
|
|
3091
|
+
}
|
|
3092
|
+
if (dirty.order) {
|
|
3093
|
+
const beforeIds = previous.ids;
|
|
3094
|
+
const afterIds = current.ids;
|
|
3095
|
+
if (!Array.isArray(beforeIds) || !Array.isArray(afterIds)) return replaceValue(node, previous, current);
|
|
3096
|
+
if (!equal(beforeIds, afterIds)) {
|
|
3097
|
+
result ??= cloneRecord(previous);
|
|
3098
|
+
installOwn(result, "ids", true, [...afterIds]);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
return result ?? previous;
|
|
3102
|
+
};
|
|
3103
|
+
const materializeList = (node, previous, current, dirty) => {
|
|
3104
|
+
if (dirty.order) {
|
|
3105
|
+
const before = listSequence(previous, node.keyOf);
|
|
3106
|
+
const after = listSequence(current, node.keyOf);
|
|
3107
|
+
let changed = !equal(before.order, after.order);
|
|
3108
|
+
const result = new Array(after.order.length);
|
|
3109
|
+
for (let index = 0; index < after.order.length; index++) {
|
|
3110
|
+
const key = after.order[index];
|
|
3111
|
+
const currentValue = after.values.get(key);
|
|
3112
|
+
if (!before.values.has(key)) {
|
|
3113
|
+
changed = true;
|
|
3114
|
+
result[index] = currentValue;
|
|
3115
|
+
continue;
|
|
3116
|
+
}
|
|
3117
|
+
const previousValue = before.values.get(key);
|
|
3118
|
+
const childDirty = dirty.children.get(key);
|
|
3119
|
+
const next = childDirty ? materializeDocumentValue(node.value, previousValue, currentValue, childDirty) : previousValue;
|
|
3120
|
+
changed ||= next !== previousValue;
|
|
3121
|
+
result[index] = next;
|
|
3122
|
+
}
|
|
3123
|
+
return changed ? result : previous;
|
|
3124
|
+
}
|
|
3125
|
+
let result;
|
|
3126
|
+
const before = indexedKeys(previous, node.keyOf);
|
|
3127
|
+
const after = indexedKeys(current, node.keyOf);
|
|
3128
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3129
|
+
const previousIndex = before.index(key);
|
|
3130
|
+
const currentIndex = after.index(key);
|
|
3131
|
+
if (previousIndex < 0 || currentIndex < 0) return replaceValue(node, previous, current);
|
|
3132
|
+
const next = materializeDocumentValue(node.value, previous[previousIndex], current[currentIndex], childDirty);
|
|
3133
|
+
if (next === previous[previousIndex] && previousIndex === currentIndex) continue;
|
|
3134
|
+
result ??= [...previous];
|
|
3135
|
+
result[currentIndex] = next;
|
|
3136
|
+
}
|
|
3137
|
+
return result ?? previous;
|
|
3138
|
+
};
|
|
3139
|
+
const materializeTree = (node, previous, current, dirty) => {
|
|
3140
|
+
if (!isRecord(previous.nodes) || !isRecord(current.nodes)) return replaceValue(node, previous, current);
|
|
3141
|
+
let result;
|
|
3142
|
+
if (dirty.treeRoot) {
|
|
3143
|
+
const beforePresent = Object.hasOwn(previous, "rootId");
|
|
3144
|
+
const afterPresent = Object.hasOwn(current, "rootId");
|
|
3145
|
+
if (beforePresent !== afterPresent || !Object.is(previous.rootId, current.rootId)) {
|
|
3146
|
+
result = cloneRecord(previous);
|
|
3147
|
+
installOwn(result, "rootId", afterPresent, current.rootId);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
let nodes;
|
|
3151
|
+
for (const id of dirty.treeNodes) {
|
|
3152
|
+
const beforePresent = Object.hasOwn(previous.nodes, id);
|
|
3153
|
+
const afterPresent = Object.hasOwn(current.nodes, id);
|
|
3154
|
+
const before = beforePresent ? previous.nodes[id] : void 0;
|
|
3155
|
+
const after = afterPresent ? current.nodes[id] : void 0;
|
|
3156
|
+
if (beforePresent === afterPresent && equalTreeNode(before, after)) continue;
|
|
3157
|
+
nodes ??= cloneRecord(previous.nodes);
|
|
3158
|
+
installOwn(nodes, id, afterPresent, after && copyTreeNode(after));
|
|
3159
|
+
}
|
|
3160
|
+
if (nodes) {
|
|
3161
|
+
result ??= cloneRecord(previous);
|
|
3162
|
+
installOwn(result, "nodes", true, nodes);
|
|
3163
|
+
}
|
|
3164
|
+
return result ?? previous;
|
|
3165
|
+
};
|
|
3166
|
+
const materializeDocumentValue = (node, previous, current, dirty) => {
|
|
3167
|
+
if (!hasDocumentDirty(dirty)) return previous;
|
|
3168
|
+
if (dirty.replace || previous === void 0 || current === void 0 || node.kind === "field") return replaceValue(node, previous, current);
|
|
3169
|
+
if (node.kind === "object" || node.kind === "variant") {
|
|
3170
|
+
if (!isRecord(previous) || !isRecord(current)) return replaceValue(node, previous, current);
|
|
3171
|
+
return materializeObject(node, previous, current, dirty);
|
|
3172
|
+
}
|
|
3173
|
+
if (node.kind === "map") {
|
|
3174
|
+
if (!isRecord(previous) || !isRecord(current)) return replaceValue(node, previous, current);
|
|
3175
|
+
return materializeMap(node, previous, current, dirty);
|
|
3176
|
+
}
|
|
3177
|
+
if (node.kind === "table") {
|
|
3178
|
+
if (!isRecord(previous) || !isRecord(current)) return replaceValue(node, previous, current);
|
|
3179
|
+
return materializeTable(node, previous, current, dirty);
|
|
3180
|
+
}
|
|
3181
|
+
if (node.kind === "list") {
|
|
3182
|
+
if (!Array.isArray(previous) || !Array.isArray(current)) return replaceValue(node, previous, current);
|
|
3183
|
+
return materializeList(node, previous, current, dirty);
|
|
3184
|
+
}
|
|
3185
|
+
if (!isRecord(previous) || !isRecord(current)) return replaceValue(node, previous, current);
|
|
3186
|
+
return materializeTree(node, previous, current, dirty);
|
|
3187
|
+
};
|
|
3188
|
+
//#endregion
|
|
3189
|
+
//#region core/src/projection/source/dirty.ts
|
|
3190
|
+
const createCollectionPending = () => ({
|
|
3191
|
+
reset: false,
|
|
3192
|
+
order: false,
|
|
3193
|
+
entries: /* @__PURE__ */ new Map()
|
|
3194
|
+
});
|
|
3195
|
+
const resetCollectionPending = (pending) => {
|
|
3196
|
+
pending.reset = true;
|
|
3197
|
+
pending.order = false;
|
|
3198
|
+
pending.entries.clear();
|
|
3199
|
+
};
|
|
3200
|
+
const clearCollectionPending = (pending) => {
|
|
3201
|
+
pending.reset = false;
|
|
3202
|
+
pending.order = false;
|
|
3203
|
+
pending.entries.clear();
|
|
3204
|
+
};
|
|
3205
|
+
const dirtyEntry = (pending, key) => {
|
|
3206
|
+
let dirty = pending.entries.get(key);
|
|
3207
|
+
if (!dirty) {
|
|
3208
|
+
dirty = createDocumentDirty();
|
|
3209
|
+
pending.entries.set(key, dirty);
|
|
3210
|
+
}
|
|
3211
|
+
return dirty;
|
|
3212
|
+
};
|
|
3213
|
+
const mergeValueChanges = (selector, dirty, changes) => {
|
|
3214
|
+
if (selector.tree) return;
|
|
3215
|
+
const at = selector.address;
|
|
3216
|
+
for (const change of changes.changes) {
|
|
3217
|
+
if (change.kind === "reset") {
|
|
3218
|
+
markDocumentReplace(dirty, []);
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
if (change.kind === "members") {
|
|
3222
|
+
for (const member of change.members) {
|
|
3223
|
+
const changed = [...change.at, member.key];
|
|
3224
|
+
if (contains(changed, at)) markDocumentReplace(dirty, []);
|
|
3225
|
+
else if (contains(at, changed)) markDocumentReplace(dirty, changed.slice(at.length));
|
|
3226
|
+
}
|
|
3227
|
+
if (change.order && contains(at, change.at)) markDocumentOrder(dirty, change.at.slice(at.length));
|
|
3228
|
+
continue;
|
|
3229
|
+
}
|
|
3230
|
+
if (contains(at, change.at)) markDocumentTree(dirty, change.at.slice(at.length), change.before !== change.after, change.nodes.map((node) => node.id));
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
const mergeCollectionChanges = (selector, pending, changes) => {
|
|
3234
|
+
if (pending.reset) return;
|
|
3235
|
+
const at = selector.address;
|
|
3236
|
+
for (const change of changes.changes) {
|
|
3237
|
+
if (change.kind === "reset") {
|
|
3238
|
+
resetCollectionPending(pending);
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
if (selector.tree?.kind === "nodes") {
|
|
3242
|
+
if (change.kind === "members") {
|
|
3243
|
+
for (const member of change.members) if (contains([...change.at, member.key], at)) {
|
|
3244
|
+
resetCollectionPending(pending);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
} else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
|
|
3248
|
+
markDocumentReplace(dirtyEntry(pending, node.id), []);
|
|
3249
|
+
pending.order ||= node.kind !== "updated";
|
|
3250
|
+
}
|
|
3251
|
+
else if (contains(change.at, at)) {
|
|
3252
|
+
resetCollectionPending(pending);
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
continue;
|
|
3256
|
+
}
|
|
3257
|
+
if (change.kind === "members") {
|
|
3258
|
+
for (const member of change.members) {
|
|
3259
|
+
const changed = [...change.at, member.key];
|
|
3260
|
+
if (contains(changed, at)) {
|
|
3261
|
+
resetCollectionPending(pending);
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
if (!contains(at, changed)) continue;
|
|
3265
|
+
const relative = changed.slice(at.length);
|
|
3266
|
+
if (!relative.length) {
|
|
3267
|
+
resetCollectionPending(pending);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
markDocumentReplace(dirtyEntry(pending, relative[0]), relative.slice(1));
|
|
3271
|
+
if (relative.length === 1 && member.kind !== "updated") pending.order = true;
|
|
3272
|
+
}
|
|
3273
|
+
if (change.order && contains(at, change.at)) {
|
|
3274
|
+
const relative = change.at.slice(at.length);
|
|
3275
|
+
if (!relative.length) pending.order = true;
|
|
3276
|
+
else markDocumentOrder(dirtyEntry(pending, relative[0]), relative.slice(1));
|
|
3277
|
+
}
|
|
3278
|
+
continue;
|
|
3279
|
+
}
|
|
3280
|
+
if (!contains(at, change.at)) continue;
|
|
3281
|
+
const relative = change.at.slice(at.length);
|
|
3282
|
+
if (!relative.length) {
|
|
3283
|
+
resetCollectionPending(pending);
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
markDocumentTree(dirtyEntry(pending, relative[0]), relative.slice(1), change.before !== change.after, change.nodes.map((node) => node.id));
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
//#endregion
|
|
3290
|
+
//#region core/src/projection/source/boundary.ts
|
|
1433
3291
|
const sourceError = (source, cause) => new ProjectionError("source", source.name, [source.outputs[0].revision()], cause);
|
|
1434
|
-
const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
3292
|
+
const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
|
|
1435
3293
|
const state = createValueOutput();
|
|
1436
3294
|
let boundary;
|
|
1437
|
-
let latest = initial;
|
|
1438
3295
|
let pendingReset = false;
|
|
1439
3296
|
let pendingCause;
|
|
1440
3297
|
let contextCause;
|
|
@@ -1476,7 +3333,8 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1476
3333
|
contextCause = cause;
|
|
1477
3334
|
let active = true;
|
|
1478
3335
|
try {
|
|
1479
|
-
state.begin(() => active, pendingReset)
|
|
3336
|
+
const evaluation = state.begin(() => active, pendingReset);
|
|
3337
|
+
evaluation.output.set(prepare(evaluation.previous));
|
|
1480
3338
|
const changed = state.seal(pendingReset, equality);
|
|
1481
3339
|
recovering = false;
|
|
1482
3340
|
return {
|
|
@@ -1489,11 +3347,13 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1489
3347
|
},
|
|
1490
3348
|
publish: state.publish,
|
|
1491
3349
|
clear: () => {
|
|
3350
|
+
const failed = boundary.fault !== void 0;
|
|
1492
3351
|
state.clear();
|
|
1493
3352
|
pendingReset = false;
|
|
1494
3353
|
pendingCause = void 0;
|
|
1495
3354
|
contextCause = void 0;
|
|
1496
3355
|
recovering = false;
|
|
3356
|
+
clearSource(failed);
|
|
1497
3357
|
},
|
|
1498
3358
|
release: () => {
|
|
1499
3359
|
cleanup?.();
|
|
@@ -1529,9 +3389,6 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1529
3389
|
return {
|
|
1530
3390
|
producer: boundary,
|
|
1531
3391
|
output,
|
|
1532
|
-
setLatest: (value) => {
|
|
1533
|
-
latest = value;
|
|
1534
|
-
},
|
|
1535
3392
|
mark,
|
|
1536
3393
|
fail,
|
|
1537
3394
|
detach: (next) => {
|
|
@@ -1539,7 +3396,34 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1539
3396
|
}
|
|
1540
3397
|
};
|
|
1541
3398
|
};
|
|
1542
|
-
|
|
3399
|
+
/** Applies source hints to an output draft; exact net change semantics remain in the output. */
|
|
3400
|
+
const stageCollectionRead = (state, read, active, options) => {
|
|
3401
|
+
const evaluation = state.begin(active, options.reset);
|
|
3402
|
+
if (options.reset) {
|
|
3403
|
+
const ids = read.ids();
|
|
3404
|
+
profile.collectionView.idsScanned(ids.length);
|
|
3405
|
+
for (const key of ids) {
|
|
3406
|
+
profile.collectionView.mapped();
|
|
3407
|
+
evaluation.output.set(key, read.get(key));
|
|
3408
|
+
}
|
|
3409
|
+
evaluation.output.order(ids);
|
|
3410
|
+
} else {
|
|
3411
|
+
const previous = state.current(() => void 0);
|
|
3412
|
+
const keys = options.candidates ? [...new Set(options.candidates)] : [...new Set([...previous.ids(), ...read.ids()])];
|
|
3413
|
+
if (!options.candidates) profile.collectionView.idsScanned(keys.length);
|
|
3414
|
+
for (const key of keys) if (read.has(key)) {
|
|
3415
|
+
profile.collectionView.mapped();
|
|
3416
|
+
evaluation.output.set(key, read.get(key));
|
|
3417
|
+
} else evaluation.output.remove(key);
|
|
3418
|
+
if (options.orderMayChange || !options.candidates) {
|
|
3419
|
+
const ids = read.ids();
|
|
3420
|
+
if (options.candidates) profile.collectionView.idsScanned(ids.length);
|
|
3421
|
+
evaluation.output.order(ids);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
return state.seal(options.reset, options.isEqual ?? Object.is);
|
|
3425
|
+
};
|
|
3426
|
+
const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSource) => {
|
|
1543
3427
|
const state = createCollectionOutput();
|
|
1544
3428
|
let boundary;
|
|
1545
3429
|
let pendingReset = false;
|
|
@@ -1586,7 +3470,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1586
3470
|
contextCause = cause;
|
|
1587
3471
|
let active = true;
|
|
1588
3472
|
try {
|
|
1589
|
-
const changed = stageCollectionRead(state, readLatest(() => active), () => active, {
|
|
3473
|
+
const changed = stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
|
|
1590
3474
|
reset: pendingReset,
|
|
1591
3475
|
...fullScan ? {} : { candidates },
|
|
1592
3476
|
orderMayChange,
|
|
@@ -1603,6 +3487,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1603
3487
|
},
|
|
1604
3488
|
publish: state.publish,
|
|
1605
3489
|
clear: () => {
|
|
3490
|
+
const failed = boundary.fault !== void 0;
|
|
1606
3491
|
state.clear();
|
|
1607
3492
|
pendingReset = false;
|
|
1608
3493
|
pendingCause = void 0;
|
|
@@ -1611,6 +3496,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1611
3496
|
fullScan = false;
|
|
1612
3497
|
orderMayChange = false;
|
|
1613
3498
|
candidates.clear();
|
|
3499
|
+
clearSource(failed);
|
|
1614
3500
|
},
|
|
1615
3501
|
release: () => {
|
|
1616
3502
|
cleanup?.();
|
|
@@ -1622,7 +3508,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1622
3508
|
scheduler.initialize(() => {
|
|
1623
3509
|
let active = true;
|
|
1624
3510
|
try {
|
|
1625
|
-
stageCollectionRead(state, readLatest(() => active), () => active, {
|
|
3511
|
+
stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
|
|
1626
3512
|
reset: true,
|
|
1627
3513
|
isEqual: equality
|
|
1628
3514
|
});
|
|
@@ -1658,33 +3544,41 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1658
3544
|
}
|
|
1659
3545
|
};
|
|
1660
3546
|
};
|
|
1661
|
-
|
|
3547
|
+
//#endregion
|
|
3548
|
+
//#region core/src/projection/source/document.ts
|
|
3549
|
+
const documentRootTarget = Object.freeze({
|
|
3550
|
+
kind: "value",
|
|
3551
|
+
at: Object.freeze([])
|
|
3552
|
+
});
|
|
3553
|
+
const createDocumentSourceRegistry = (scheduler) => {
|
|
1662
3554
|
const documents = /* @__PURE__ */ new Map();
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
const documentConnection = (runtime) => {
|
|
1667
|
-
const state = accessOf(runtime);
|
|
3555
|
+
const connection = (runtime) => {
|
|
3556
|
+
const context = contextOf(runtime);
|
|
3557
|
+
const state = context.state;
|
|
1668
3558
|
const cached = documents.get(state);
|
|
1669
3559
|
if (cached) return cached;
|
|
1670
3560
|
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1671
3561
|
const bindings = /* @__PURE__ */ new Set();
|
|
3562
|
+
const index = new SubscriptionIndex(state.schema);
|
|
3563
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1672
3564
|
const guard = (locked) => {
|
|
1673
|
-
state.projectionLocks
|
|
3565
|
+
state.projectionLocks += locked ? 1 : -1;
|
|
1674
3566
|
};
|
|
1675
3567
|
scheduler.guards.add(guard);
|
|
1676
3568
|
let closed = false;
|
|
1677
3569
|
let detach = () => void 0;
|
|
1678
|
-
const
|
|
3570
|
+
const documentConnection = {
|
|
1679
3571
|
add(binding) {
|
|
1680
3572
|
if (closed) throw new Error("Document projection connection is closed.");
|
|
1681
3573
|
bindings.add(binding);
|
|
3574
|
+
index.add(binding.target, binding);
|
|
1682
3575
|
let active = true;
|
|
1683
3576
|
return () => {
|
|
1684
3577
|
if (!active) return;
|
|
1685
3578
|
active = false;
|
|
1686
3579
|
bindings.delete(binding);
|
|
1687
|
-
|
|
3580
|
+
index.delete(binding.target, binding);
|
|
3581
|
+
if (!bindings.size) documentConnection.close();
|
|
1688
3582
|
};
|
|
1689
3583
|
},
|
|
1690
3584
|
close() {
|
|
@@ -1692,13 +3586,17 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1692
3586
|
closed = true;
|
|
1693
3587
|
detach();
|
|
1694
3588
|
bindings.clear();
|
|
3589
|
+
candidates.clear();
|
|
3590
|
+
index.clear();
|
|
1695
3591
|
scheduler.guards.delete(guard);
|
|
1696
3592
|
documents.delete(state);
|
|
1697
3593
|
}
|
|
1698
3594
|
};
|
|
1699
|
-
detach = attachProjection(
|
|
3595
|
+
detach = context.notifications.attachProjection({
|
|
1700
3596
|
capture: (commit) => {
|
|
1701
|
-
|
|
3597
|
+
candidates.clear();
|
|
3598
|
+
index.collect(commit.changes, (binding) => candidates.add(binding));
|
|
3599
|
+
candidates.forEach((binding) => binding.capture(commit));
|
|
1702
3600
|
},
|
|
1703
3601
|
settle: scheduler.settle,
|
|
1704
3602
|
flush: scheduler.flush,
|
|
@@ -1707,20 +3605,175 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1707
3605
|
scheduler.run();
|
|
1708
3606
|
}
|
|
1709
3607
|
});
|
|
1710
|
-
documents.set(state,
|
|
1711
|
-
return
|
|
3608
|
+
documents.set(state, documentConnection);
|
|
3609
|
+
return documentConnection;
|
|
3610
|
+
};
|
|
3611
|
+
const materialize = (definition) => {
|
|
3612
|
+
if (definition.source.kind !== "document") throw new Error("Invalid document source.");
|
|
3613
|
+
const { document, selector } = definition.source;
|
|
3614
|
+
const state = contextOf(document).state;
|
|
3615
|
+
const current = () => resolveValue(state.schema, state.document, selector.address);
|
|
3616
|
+
const disposed = () => {
|
|
3617
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
3618
|
+
};
|
|
3619
|
+
if (selector.kind === "value") {
|
|
3620
|
+
const dirty = createDocumentDirty();
|
|
3621
|
+
const read = (previous) => {
|
|
3622
|
+
disposed();
|
|
3623
|
+
const resolved = current();
|
|
3624
|
+
const node = resolved?.node;
|
|
3625
|
+
const value = resolved?.value;
|
|
3626
|
+
if (selector.tree?.kind === "root") return node?.kind === "tree" && isRecord(value) && typeof value.rootId === "string" ? value.rootId : void 0;
|
|
3627
|
+
if (selector.tree?.kind === "node") {
|
|
3628
|
+
if (node?.kind !== "tree" || !isRecord(value) || !isRecord(value.nodes)) return void 0;
|
|
3629
|
+
const entry = Object.hasOwn(value.nodes, selector.tree.id) ? value.nodes[selector.tree.id] : void 0;
|
|
3630
|
+
if (equalTreeNode(previous, entry)) return previous;
|
|
3631
|
+
return entry ? copyTreeNode(entry) : void 0;
|
|
3632
|
+
}
|
|
3633
|
+
if (!node) return void 0;
|
|
3634
|
+
return previous === void 0 ? copyValue(node, value) : materializeDocumentValue(node, previous, value, dirty);
|
|
3635
|
+
};
|
|
3636
|
+
const initial = read();
|
|
3637
|
+
let boundary;
|
|
3638
|
+
boundary = createValueBoundary(scheduler, "document source", initial, Object.is, (previous) => read(previous), (failed) => {
|
|
3639
|
+
if (failed) markDocumentReplace(dirty, []);
|
|
3640
|
+
else clearDocumentDirty(dirty);
|
|
3641
|
+
});
|
|
3642
|
+
const remove = connection(document).add({
|
|
3643
|
+
target: selector,
|
|
3644
|
+
capture: (commit) => {
|
|
3645
|
+
try {
|
|
3646
|
+
mergeValueChanges(selector, dirty, commit.changes);
|
|
3647
|
+
boundary.mark({ reset: commit.changes.changes.some((change) => change.kind === "reset") });
|
|
3648
|
+
} catch (cause) {
|
|
3649
|
+
boundary.fail(cause);
|
|
3650
|
+
}
|
|
3651
|
+
},
|
|
3652
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
3653
|
+
});
|
|
3654
|
+
boundary.detach(remove);
|
|
3655
|
+
return boundary;
|
|
3656
|
+
}
|
|
3657
|
+
const pending = createCollectionPending();
|
|
3658
|
+
const read = (active, previous) => {
|
|
3659
|
+
disposed();
|
|
3660
|
+
const resolved = current();
|
|
3661
|
+
const node = resolved?.node;
|
|
3662
|
+
const value = resolved?.value;
|
|
3663
|
+
const treeNodes = selector.tree?.kind === "nodes";
|
|
3664
|
+
const entryNode = !treeNodes && node ? collectionEntryNode(node) : void 0;
|
|
3665
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
3666
|
+
const replacement = createDocumentDirty();
|
|
3667
|
+
if (pending.reset) markDocumentReplace(replacement, []);
|
|
3668
|
+
const ids = () => {
|
|
3669
|
+
assertScope(active);
|
|
3670
|
+
if (treeNodes) return node?.kind === "tree" && isRecord(value) && isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
|
|
3671
|
+
if (node?.kind === "map" && isRecord(value)) return Object.freeze(Object.keys(value));
|
|
3672
|
+
if (node?.kind === "table" && isRecord(value) && Array.isArray(value.ids)) return Object.freeze([...value.ids]);
|
|
3673
|
+
if (node?.kind === "list" && Array.isArray(value)) return Object.freeze(toArray(indexedKeys(value, node.keyOf)));
|
|
3674
|
+
return Object.freeze([]);
|
|
3675
|
+
};
|
|
3676
|
+
const has = (key) => {
|
|
3677
|
+
assertScope(active);
|
|
3678
|
+
if (treeNodes) return Boolean(node?.kind === "tree" && isRecord(value) && isRecord(value.nodes) && Object.hasOwn(value.nodes, key));
|
|
3679
|
+
if (node?.kind === "map" && isRecord(value)) return Object.hasOwn(value, key);
|
|
3680
|
+
if (node?.kind === "table" && isRecord(value) && isRecord(value.byId)) return Object.hasOwn(value.byId, key);
|
|
3681
|
+
if (node?.kind === "list" && Array.isArray(value)) return indexedKeys(value, node.keyOf).index(key) >= 0;
|
|
3682
|
+
return false;
|
|
3683
|
+
};
|
|
3684
|
+
const raw = (key) => {
|
|
3685
|
+
if (treeNodes) {
|
|
3686
|
+
if (node?.kind !== "tree" || !isRecord(value) || !isRecord(value.nodes)) return void 0;
|
|
3687
|
+
return Object.hasOwn(value.nodes, key) ? value.nodes[key] : void 0;
|
|
3688
|
+
}
|
|
3689
|
+
if (node?.kind === "map" && isRecord(value)) return Object.hasOwn(value, key) ? value[key] : void 0;
|
|
3690
|
+
if (node?.kind === "table" && isRecord(value) && isRecord(value.byId)) return Object.hasOwn(value.byId, key) ? value.byId[key] : void 0;
|
|
3691
|
+
if (node?.kind === "list" && Array.isArray(value)) {
|
|
3692
|
+
const index = indexedKeys(value, node.keyOf).index(key);
|
|
3693
|
+
return index < 0 ? void 0 : value[index];
|
|
3694
|
+
}
|
|
3695
|
+
};
|
|
3696
|
+
return Object.freeze({
|
|
3697
|
+
get: (key) => {
|
|
3698
|
+
assertScope(active);
|
|
3699
|
+
if (!snapshots.has(key)) if (!has(key)) snapshots.set(key, void 0);
|
|
3700
|
+
else {
|
|
3701
|
+
const current = raw(key);
|
|
3702
|
+
if (treeNodes) {
|
|
3703
|
+
const entry = current;
|
|
3704
|
+
const before = previous.has(key) ? previous.get(key) : void 0;
|
|
3705
|
+
snapshots.set(key, equalTreeNode(before, entry) ? before : copyTreeNode(entry));
|
|
3706
|
+
} else if (!entryNode) snapshots.set(key, current);
|
|
3707
|
+
else if (!previous.has(key)) snapshots.set(key, copyValue(entryNode, current));
|
|
3708
|
+
else {
|
|
3709
|
+
const dirty = pending.entries.get(key) ?? (pending.reset ? replacement : void 0);
|
|
3710
|
+
snapshots.set(key, dirty ? materializeDocumentValue(entryNode, previous.get(key), current, dirty) : previous.get(key));
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
return snapshots.get(key);
|
|
3714
|
+
},
|
|
3715
|
+
has,
|
|
3716
|
+
ids
|
|
3717
|
+
});
|
|
3718
|
+
};
|
|
3719
|
+
let boundary;
|
|
3720
|
+
boundary = createCollectionBoundary(scheduler, "document collection source", read, Object.is, (failed) => {
|
|
3721
|
+
if (failed) resetCollectionPending(pending);
|
|
3722
|
+
else clearCollectionPending(pending);
|
|
3723
|
+
});
|
|
3724
|
+
const remove = connection(document).add({
|
|
3725
|
+
target: selector,
|
|
3726
|
+
capture: (commit) => {
|
|
3727
|
+
try {
|
|
3728
|
+
mergeCollectionChanges(selector, pending, commit.changes);
|
|
3729
|
+
boundary.mark({
|
|
3730
|
+
reset: pending.reset,
|
|
3731
|
+
...pending.reset ? {} : { candidates: pending.entries.keys() },
|
|
3732
|
+
orderMayChange: pending.order
|
|
3733
|
+
});
|
|
3734
|
+
} catch (cause) {
|
|
3735
|
+
boundary.fail(cause);
|
|
3736
|
+
}
|
|
3737
|
+
},
|
|
3738
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
3739
|
+
});
|
|
3740
|
+
boundary.detach(remove);
|
|
3741
|
+
return boundary;
|
|
3742
|
+
};
|
|
3743
|
+
return {
|
|
3744
|
+
materialize,
|
|
3745
|
+
attachRoot(runtime, handlers) {
|
|
3746
|
+
return connection(runtime).add({
|
|
3747
|
+
target: documentRootTarget,
|
|
3748
|
+
capture: handlers.capture,
|
|
3749
|
+
dispose: handlers.dispose
|
|
3750
|
+
});
|
|
3751
|
+
},
|
|
3752
|
+
dispose() {
|
|
3753
|
+
documents.forEach((document) => document.close());
|
|
3754
|
+
documents.clear();
|
|
3755
|
+
}
|
|
1712
3756
|
};
|
|
3757
|
+
};
|
|
3758
|
+
//#endregion
|
|
3759
|
+
//#region core/src/projection/source/registry.ts
|
|
3760
|
+
const createSourceRegistry = (scheduler) => {
|
|
3761
|
+
const documents = createDocumentSourceRegistry(scheduler);
|
|
3762
|
+
const readables = /* @__PURE__ */ new Map();
|
|
3763
|
+
const externalValues = /* @__PURE__ */ new WeakMap();
|
|
3764
|
+
const externalCollections = /* @__PURE__ */ new WeakMap();
|
|
1713
3765
|
const valueInput = (definition) => {
|
|
1714
3766
|
if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
|
|
1715
3767
|
const source = definition.source;
|
|
1716
|
-
|
|
3768
|
+
let latest = source.initial;
|
|
3769
|
+
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
|
|
1717
3770
|
return {
|
|
1718
3771
|
...boundary,
|
|
1719
3772
|
write: {
|
|
1720
3773
|
kind: "value",
|
|
1721
3774
|
set(value) {
|
|
1722
3775
|
scheduler.assertIdle();
|
|
1723
|
-
|
|
3776
|
+
latest = value;
|
|
1724
3777
|
boundary.mark();
|
|
1725
3778
|
scheduler.run();
|
|
1726
3779
|
}
|
|
@@ -1730,7 +3783,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1730
3783
|
const collectionInput = (definition) => {
|
|
1731
3784
|
if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
|
|
1732
3785
|
const values = new Map(definition.source.initial);
|
|
1733
|
-
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => mapRead(values), definition.output.equality);
|
|
3786
|
+
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => mapRead(values), definition.output.equality, () => void 0);
|
|
1734
3787
|
return {
|
|
1735
3788
|
...boundary,
|
|
1736
3789
|
write: {
|
|
@@ -1806,103 +3859,15 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1806
3859
|
}
|
|
1807
3860
|
};
|
|
1808
3861
|
};
|
|
1809
|
-
const documentValue = (definition) => {
|
|
1810
|
-
if (definition.source.kind !== "document-value") throw new Error("Invalid document value source.");
|
|
1811
|
-
const { document, selector } = definition.source;
|
|
1812
|
-
const state = accessOf(document);
|
|
1813
|
-
const address = selector?.address ?? Object.freeze([]);
|
|
1814
|
-
const schemaNode = () => nodeAt(state.schema, address, state.document);
|
|
1815
|
-
const read = () => {
|
|
1816
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1817
|
-
const node = schemaNode();
|
|
1818
|
-
if (!node) return void 0;
|
|
1819
|
-
return copyValue(node, selector ? read$1(state.document, address, state.schema) : state.document);
|
|
1820
|
-
};
|
|
1821
|
-
const boundary = createValueBoundary(scheduler, "document source", read(), (left, right) => {
|
|
1822
|
-
const node = schemaNode();
|
|
1823
|
-
return node ? equalValue(node, left, right) : Object.is(left, right);
|
|
1824
|
-
});
|
|
1825
|
-
const remove = documentConnection(document).add({
|
|
1826
|
-
matches: (commit) => !selector || affectsTarget(commit.impact, selector),
|
|
1827
|
-
capture: (commit) => {
|
|
1828
|
-
try {
|
|
1829
|
-
boundary.setLatest(read());
|
|
1830
|
-
boundary.mark({ reset: commit.impact.kind === "reset" });
|
|
1831
|
-
} catch (cause) {
|
|
1832
|
-
boundary.fail(cause);
|
|
1833
|
-
}
|
|
1834
|
-
},
|
|
1835
|
-
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
1836
|
-
});
|
|
1837
|
-
boundary.detach(remove);
|
|
1838
|
-
return boundary;
|
|
1839
|
-
};
|
|
1840
|
-
const documentCollection = (definition) => {
|
|
1841
|
-
if (definition.source.kind !== "document-collection") throw new Error("Invalid document collection source.");
|
|
1842
|
-
const { document, selector } = definition.source;
|
|
1843
|
-
const state = accessOf(document);
|
|
1844
|
-
const read = (active) => {
|
|
1845
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1846
|
-
const raw = collectionAccess({
|
|
1847
|
-
state,
|
|
1848
|
-
active
|
|
1849
|
-
}, selector.address);
|
|
1850
|
-
const snapshots = /* @__PURE__ */ new Map();
|
|
1851
|
-
return Object.freeze({
|
|
1852
|
-
get: (key) => {
|
|
1853
|
-
assertScope(active);
|
|
1854
|
-
if (!snapshots.has(key)) snapshots.set(key, snapshot(raw.get(key)));
|
|
1855
|
-
return snapshots.get(key);
|
|
1856
|
-
},
|
|
1857
|
-
has: (key) => {
|
|
1858
|
-
assertScope(active);
|
|
1859
|
-
return raw.has(key);
|
|
1860
|
-
},
|
|
1861
|
-
ids: () => {
|
|
1862
|
-
assertScope(active);
|
|
1863
|
-
return raw.ids();
|
|
1864
|
-
}
|
|
1865
|
-
});
|
|
1866
|
-
};
|
|
1867
|
-
const collectionNode = () => nodeAt(state.schema, selector.address, state.document);
|
|
1868
|
-
const boundary = createCollectionBoundary(scheduler, "document collection source", read, (left, right) => {
|
|
1869
|
-
const node = collectionNode();
|
|
1870
|
-
const entry = node && collectionEntryNode(node);
|
|
1871
|
-
return entry ? equalValue(entry, left, right) : Object.is(left, right);
|
|
1872
|
-
});
|
|
1873
|
-
const remove = documentConnection(document).add({
|
|
1874
|
-
matches: (commit) => affectsTarget(commit.impact, selector),
|
|
1875
|
-
capture: (commit) => {
|
|
1876
|
-
const impact = collectionImpact(commit.impact, selector);
|
|
1877
|
-
if (impact.kind === "reset") {
|
|
1878
|
-
boundary.mark({ reset: true });
|
|
1879
|
-
return;
|
|
1880
|
-
}
|
|
1881
|
-
const candidates = new Set([
|
|
1882
|
-
...impact.added,
|
|
1883
|
-
...impact.updated,
|
|
1884
|
-
...impact.removed
|
|
1885
|
-
]);
|
|
1886
|
-
boundary.mark({
|
|
1887
|
-
candidates,
|
|
1888
|
-
orderMayChange: Boolean(impact.added.size || impact.removed.size || impact.orderChanged)
|
|
1889
|
-
});
|
|
1890
|
-
},
|
|
1891
|
-
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
1892
|
-
});
|
|
1893
|
-
boundary.detach(remove);
|
|
1894
|
-
return boundary;
|
|
1895
|
-
};
|
|
1896
3862
|
const readable = (definition) => {
|
|
1897
3863
|
if (definition.source.kind !== "readable") throw new Error("Invalid Readable source.");
|
|
1898
3864
|
const { readable, equality } = definition.source;
|
|
1899
|
-
let
|
|
1900
|
-
const boundary = createValueBoundary(scheduler, "external readable",
|
|
3865
|
+
let latest = readable.current();
|
|
3866
|
+
const boundary = createValueBoundary(scheduler, "external readable", latest, equality, () => latest, () => void 0);
|
|
1901
3867
|
const member = {
|
|
1902
3868
|
boundary,
|
|
1903
3869
|
receive(value, metadata) {
|
|
1904
|
-
|
|
1905
|
-
boundary.setLatest(value);
|
|
3870
|
+
latest = value;
|
|
1906
3871
|
boundary.mark(metadata);
|
|
1907
3872
|
}
|
|
1908
3873
|
};
|
|
@@ -1932,9 +3897,8 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1932
3897
|
readables.delete(readable);
|
|
1933
3898
|
}
|
|
1934
3899
|
};
|
|
1935
|
-
const owner =
|
|
1936
|
-
if (owner) detachDocument =
|
|
1937
|
-
matches: () => true,
|
|
3900
|
+
const owner = contextOf(readable, false)?.owner;
|
|
3901
|
+
if (owner) detachDocument = documents.attachRoot(owner, {
|
|
1938
3902
|
capture: () => receive(false),
|
|
1939
3903
|
dispose: () => {
|
|
1940
3904
|
members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
|
|
@@ -1954,11 +3918,12 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1954
3918
|
const externalValue = (definition) => {
|
|
1955
3919
|
if (definition.source.kind !== "external-value") throw new Error("Invalid external value source.");
|
|
1956
3920
|
const { source, equality } = definition.source;
|
|
1957
|
-
|
|
3921
|
+
let latest = source.current();
|
|
3922
|
+
const boundary = createValueBoundary(scheduler, "external source", latest, equality, () => latest, () => void 0);
|
|
1958
3923
|
const member = {
|
|
1959
3924
|
boundary,
|
|
1960
3925
|
receive(value, metadata) {
|
|
1961
|
-
|
|
3926
|
+
latest = value;
|
|
1962
3927
|
boundary.mark(metadata);
|
|
1963
3928
|
}
|
|
1964
3929
|
};
|
|
@@ -2012,7 +3977,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2012
3977
|
has: (key) => current.has(key),
|
|
2013
3978
|
ids: () => current.ids()
|
|
2014
3979
|
});
|
|
2015
|
-
}, definition.output.equality);
|
|
3980
|
+
}, definition.output.equality, () => void 0);
|
|
2016
3981
|
let connection = externalCollections.get(source);
|
|
2017
3982
|
if (!connection) {
|
|
2018
3983
|
const members = /* @__PURE__ */ new Set();
|
|
@@ -2075,17 +4040,15 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2075
4040
|
switch (definition.source.kind) {
|
|
2076
4041
|
case "value-input": return valueInput(definition);
|
|
2077
4042
|
case "collection-input": return collectionInput(definition);
|
|
2078
|
-
case "document
|
|
2079
|
-
case "document-collection": return documentCollection(definition);
|
|
4043
|
+
case "document": return documents.materialize(definition);
|
|
2080
4044
|
case "readable": return readable(definition);
|
|
2081
4045
|
case "external-value": return externalValue(definition);
|
|
2082
4046
|
case "external-collection": return externalCollection(definition);
|
|
2083
4047
|
}
|
|
2084
4048
|
},
|
|
2085
4049
|
dispose() {
|
|
2086
|
-
documents.
|
|
4050
|
+
documents.dispose();
|
|
2087
4051
|
readables.forEach((connection) => connection.close());
|
|
2088
|
-
documents.clear();
|
|
2089
4052
|
readables.clear();
|
|
2090
4053
|
}
|
|
2091
4054
|
};
|
|
@@ -2329,6 +4292,6 @@ const createProjectionRuntime = (options) => {
|
|
|
2329
4292
|
});
|
|
2330
4293
|
};
|
|
2331
4294
|
//#endregion
|
|
2332
|
-
export { DocumentDisposedError, DocumentReentrancyError, ParseError, ProjectionDisposedError, ProjectionError, TransactionRejected, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, snapshot, table, tree, variant };
|
|
4295
|
+
export { DocumentDisposedError, DocumentReentrancyError, ParseError, ProjectionDisposedError, ProjectionError, TransactionRejected, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, select, snapshot, table, tree, variant };
|
|
2333
4296
|
|
|
2334
4297
|
//# sourceMappingURL=index.js.map
|