doxum 0.1.22 → 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 +14 -5
- package/dist/{advanced-CqrR_4dh.d.ts → advanced-CLJCWkHa.d.cts} +2 -2
- package/dist/{advanced-BTStm2cW.js → advanced-CRNHfJBu.js} +457 -425
- package/dist/advanced-CRNHfJBu.js.map +1 -0
- package/dist/{advanced-GKZ-CZ6a.cjs → advanced-Cle5g3B4.cjs} +590 -438
- package/dist/advanced-Cle5g3B4.cjs.map +1 -0
- package/dist/{advanced-CyR5o3Xr.d.cts → advanced-d6J61er9.d.ts} +2 -2
- 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-CxiNcqMn.d.ts → contract-SYJFP0lM.d.cts} +19 -75
- package/dist/{contract-CRrZ-3sF.d.cts → contract-ULPQmiEs.d.ts} +19 -75
- package/dist/driver-BD3DQOW6.cjs +382 -0
- package/dist/driver-BD3DQOW6.cjs.map +1 -0
- package/dist/{driver-BqRjwFmU.js → driver-yFfvDMX5.js} +155 -26
- package/dist/driver-yFfvDMX5.js.map +1 -0
- package/dist/index.cjs +2130 -529
- 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 +2003 -403
- 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 -70
- 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 -71
- package/dist/react.js.map +1 -1
- package/package.json +1 -6
- package/skills/doxum-runtime/SKILL.md +2 -1
- package/skills/doxum-runtime/references/guide.en.md +8 -6
- package/skills/doxum-runtime/references/guide.zh-CN.md +7 -5
- package/skills/doxum-runtime/references/invariants.en.md +5 -3
- package/skills/doxum-runtime/references/invariants.zh-CN.md +4 -3
- package/dist/access-CZXv_DIF.js +0 -958
- package/dist/access-CZXv_DIF.js.map +0 -1
- package/dist/access-D64MFRSm.cjs +0 -1101
- package/dist/access-D64MFRSm.cjs.map +0 -1
- package/dist/advanced-BTStm2cW.js.map +0 -1
- package/dist/advanced-GKZ-CZ6a.cjs.map +0 -1
- package/dist/driver-BC06FEMF.cjs +0 -241
- package/dist/driver-BC06FEMF.cjs.map +0 -1
- package/dist/driver-BqRjwFmU.js.map +0 -1
- package/dist/integration-BqDw7e2w.cjs +0 -30
- package/dist/integration-BqDw7e2w.cjs.map +0 -1
- package/dist/integration-CSXaF8HP.js +0 -25
- package/dist/integration-CSXaF8HP.js.map +0 -1
- package/dist/integration.cjs +0 -13
- package/dist/integration.d.cts +0 -17
- package/dist/integration.d.ts +0 -17
- package/dist/integration.js +0 -5
- package/dist/issue-9U6YLYz3.js +0 -647
- package/dist/issue-9U6YLYz3.js.map +0 -1
- package/dist/issue-Bo0FIO3b.cjs +0 -886
- package/dist/issue-Bo0FIO3b.cjs.map +0 -1
- package/dist/notification-CrZHVx3a.js +0 -176
- package/dist/notification-CrZHVx3a.js.map +0 -1
- package/dist/notification-DFoA9bQY.cjs +0 -235
- package/dist/notification-DFoA9bQY.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,107 +1,1026 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
2
|
+
const require_advanced = require("./advanced-Cle5g3B4.cjs");
|
|
3
|
+
const require_context = require("./context-BcLKGfq8.cjs");
|
|
4
|
+
const require_driver = require("./driver-BD3DQOW6.cjs");
|
|
5
|
+
//#region core/src/address/resolve.ts
|
|
6
|
+
const variantNode = (node, value) => {
|
|
7
|
+
const tag = require_context.isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
|
|
8
|
+
return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
|
|
9
|
+
};
|
|
10
|
+
const step = (nodeInput, value, segment) => {
|
|
11
|
+
let node = nodeInput;
|
|
12
|
+
if (!node) return void 0;
|
|
13
|
+
if (node.kind === "variant") node = variantNode(node, value);
|
|
14
|
+
if (!node) return void 0;
|
|
15
|
+
if (node.kind === "object") return Object.hasOwn(node.shape, segment) ? node.shape[segment] : void 0;
|
|
16
|
+
if (node.kind === "table" || node.kind === "map") return node.value;
|
|
17
|
+
if (node.kind === "list") return node.value;
|
|
18
|
+
};
|
|
19
|
+
const nodeAt = (schema, address, document) => {
|
|
20
|
+
let node = schema;
|
|
21
|
+
let value = document;
|
|
22
|
+
for (const segment of address) {
|
|
23
|
+
const resolved = step(node, value, segment);
|
|
24
|
+
value = readSegment(value, segment, node);
|
|
25
|
+
node = resolved;
|
|
26
|
+
if (!node) return void 0;
|
|
27
|
+
}
|
|
28
|
+
return node;
|
|
29
|
+
};
|
|
30
|
+
const readSegment = (value, segment, node) => {
|
|
31
|
+
if (!require_context.isRecord(value) && !Array.isArray(value)) return void 0;
|
|
32
|
+
if (node?.kind === "list" && Array.isArray(value)) return value[require_advanced.indexedKeys(value, node.keyOf).index(segment)];
|
|
33
|
+
if (node?.kind === "table" && require_context.isRecord(value) && require_context.isRecord(value.byId)) return Object.hasOwn(value.byId, segment) ? value.byId[segment] : void 0;
|
|
34
|
+
return Object.prototype.hasOwnProperty.call(value, segment) ? value[segment] : void 0;
|
|
35
|
+
};
|
|
36
|
+
const resolveContainer = (owner, generation, at, node, value) => {
|
|
37
|
+
if (!node || !require_context.isRecord(value) && !Array.isArray(value)) return void 0;
|
|
38
|
+
const parent = node.kind === "table" && require_context.isRecord(value) ? value.byId : value;
|
|
39
|
+
if (!require_context.isRecord(parent) && !Array.isArray(parent)) return void 0;
|
|
40
|
+
if (node.kind === "tree") return void 0;
|
|
41
|
+
const layout = require_advanced.compiledLayout(node, value);
|
|
42
|
+
if (!layout) return void 0;
|
|
43
|
+
return {
|
|
44
|
+
owner,
|
|
45
|
+
generation,
|
|
46
|
+
at,
|
|
47
|
+
node,
|
|
48
|
+
parent,
|
|
49
|
+
value,
|
|
50
|
+
layout
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const resolveTreeContainer = (owner, generation, at, node, value) => node?.kind === "tree" && require_context.is(value) ? {
|
|
54
|
+
owner,
|
|
55
|
+
generation,
|
|
56
|
+
at,
|
|
57
|
+
node,
|
|
58
|
+
value
|
|
59
|
+
} : void 0;
|
|
60
|
+
const memberKey = (container, key) => container.node.kind === "list" ? require_advanced.indexedKeys(container.parent, container.node.keyOf).index(key) : key;
|
|
61
|
+
/** One recent path per transaction; structural writes invalidate all retained locations. */
|
|
62
|
+
const createAddressResolver = (schema, root) => {
|
|
63
|
+
const prefix = [];
|
|
64
|
+
return {
|
|
65
|
+
container: (owner, generation, at) => {
|
|
66
|
+
if (!at.length) return resolveContainer(owner, generation, at, schema, root);
|
|
67
|
+
const location = resolveWithPrefix(schema, root, at, prefix);
|
|
68
|
+
if (!location || !Object.hasOwn(location.parent, location.key)) return void 0;
|
|
69
|
+
return resolveContainer(owner, generation, at, location.node, location.parent[location.key]);
|
|
70
|
+
},
|
|
71
|
+
invalidate: () => {
|
|
72
|
+
prefix.length = 0;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/** A subtree root is a value, not a fabricated member of an ObjectNode. */
|
|
77
|
+
const resolveValue = (schema, root, address, prefix) => {
|
|
78
|
+
if (!address.length) return {
|
|
79
|
+
node: schema,
|
|
80
|
+
value: root
|
|
81
|
+
};
|
|
82
|
+
const location = resolveWithPrefix(schema, root, address, prefix);
|
|
83
|
+
return location && {
|
|
84
|
+
node: location.node,
|
|
85
|
+
value: Object.hasOwn(location.parent, location.key) ? location.parent[location.key] : void 0
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/** Resolves schema and document location in one address traversal. */
|
|
89
|
+
const resolveLocated = (schema, root, address) => resolveWithPrefix(schema, root, address);
|
|
90
|
+
const resolveWithPrefix = (schema, root, address, prefix) => {
|
|
91
|
+
if (address.length === 0) return void 0;
|
|
92
|
+
let node = schema;
|
|
93
|
+
let current = root;
|
|
94
|
+
let start = 0;
|
|
95
|
+
if (prefix) {
|
|
96
|
+
while (start < prefix.length && start < address.length - 1 && prefix[start].segment === address[start]) start++;
|
|
97
|
+
prefix.length = start;
|
|
98
|
+
if (start) {
|
|
99
|
+
const retained = prefix[start - 1];
|
|
100
|
+
node = retained.node;
|
|
101
|
+
current = retained.value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let cacheable = true;
|
|
105
|
+
for (let index = start; index < address.length - 1; index += 1) {
|
|
106
|
+
require_advanced.profile.address.schemaStep();
|
|
107
|
+
require_advanced.profile.address.documentStep();
|
|
108
|
+
const resolved = step(node, current, address[index]);
|
|
109
|
+
if (!resolved) return void 0;
|
|
110
|
+
if (node?.kind !== "object" && node?.kind !== "table" && node?.kind !== "map") cacheable = false;
|
|
111
|
+
current = readSegment(current, address[index], node);
|
|
112
|
+
node = resolved;
|
|
113
|
+
if (prefix && cacheable) prefix.push({
|
|
114
|
+
segment: address[index],
|
|
115
|
+
node: resolved,
|
|
116
|
+
value: current
|
|
117
|
+
});
|
|
118
|
+
if (!node) return void 0;
|
|
119
|
+
}
|
|
120
|
+
const last = address[address.length - 1];
|
|
121
|
+
return resolveChild(node, current, last);
|
|
122
|
+
};
|
|
123
|
+
/** Resolves one member from an already current schema/container pair. */
|
|
124
|
+
const resolveChild = (node, current, last) => {
|
|
125
|
+
require_advanced.profile.address.schemaStep();
|
|
126
|
+
require_advanced.profile.address.documentStep();
|
|
127
|
+
const resolved = step(node, current, last);
|
|
128
|
+
if (!resolved || !require_context.isRecord(current) && !Array.isArray(current)) return void 0;
|
|
129
|
+
if (node?.kind === "table" && require_context.isRecord(current) && require_context.isRecord(current.byId)) return {
|
|
130
|
+
node: resolved,
|
|
131
|
+
parent: current.byId,
|
|
132
|
+
key: last
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
node: resolved,
|
|
136
|
+
parent: current,
|
|
137
|
+
key: node?.kind === "list" && Array.isArray(current) ? require_advanced.indexedKeys(current, node.keyOf).index(last) : last
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region core/src/mutation/operations/map.ts
|
|
142
|
+
function put(session, container, id, value) {
|
|
143
|
+
if (container.node.kind !== "map") return require_driver.fail(container.at, "invalid-collection", "Expected a map.");
|
|
144
|
+
session.writeMember(container, id, value, "set");
|
|
145
|
+
}
|
|
146
|
+
function remove$3(session, container, id) {
|
|
147
|
+
if (container.node.kind !== "map") return require_driver.fail(container.at, "invalid-collection", "Expected a map.");
|
|
148
|
+
session.writeMember(container, id, void 0, "remove");
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region core/src/order/anchor.ts
|
|
152
|
+
const length = (keys) => keys.length;
|
|
153
|
+
const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
|
|
154
|
+
/** Plan one relative move against the order that remains after removing the selection. */
|
|
155
|
+
const moveSelection = (current, selection, position) => {
|
|
156
|
+
if (!selection.length) return {
|
|
157
|
+
kind: "ok",
|
|
158
|
+
order: current
|
|
159
|
+
};
|
|
160
|
+
const selected = /* @__PURE__ */ new Set();
|
|
161
|
+
for (const key of selection) {
|
|
162
|
+
if (selected.has(key)) return { kind: "duplicate" };
|
|
163
|
+
selected.add(key);
|
|
164
|
+
}
|
|
165
|
+
const moved = [];
|
|
166
|
+
const remaining = [];
|
|
167
|
+
for (const key of current) (selected.has(key) ? moved : remaining).push(key);
|
|
168
|
+
if (moved.length !== selected.size) return { kind: "missing" };
|
|
169
|
+
if (!valid(remaining, position)) return { kind: "invalid-anchor" };
|
|
170
|
+
const insertion = index(remaining, position);
|
|
171
|
+
return {
|
|
172
|
+
kind: "ok",
|
|
173
|
+
order: [
|
|
174
|
+
...remaining.slice(0, insertion),
|
|
175
|
+
...moved,
|
|
176
|
+
...remaining.slice(insertion)
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
const index = (keys, anchor) => {
|
|
181
|
+
if (!anchor) return length(keys);
|
|
182
|
+
if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
|
|
183
|
+
if ("before" in anchor) return keyIndex(keys, anchor.before);
|
|
184
|
+
return keyIndex(keys, anchor.after) + 1;
|
|
185
|
+
};
|
|
186
|
+
const valid = (keys, anchor) => {
|
|
187
|
+
if (!anchor) return true;
|
|
188
|
+
if (typeof anchor !== "object" || anchor === null) return false;
|
|
189
|
+
if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
|
|
190
|
+
if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
|
|
191
|
+
return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region core/src/mutation/operations/table.ts
|
|
195
|
+
function create(session, container, entries, position) {
|
|
196
|
+
const { at, node, value } = container;
|
|
197
|
+
if (node.kind !== "table") return require_driver.fail(at, "invalid-collection", "Expected a table.");
|
|
198
|
+
const table = value;
|
|
199
|
+
if (!valid(table.ids, position)) return require_driver.fail(at, "invalid-anchor", "Unknown table anchor.");
|
|
200
|
+
const ids = /* @__PURE__ */ new Set();
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
if (Object.hasOwn(table.byId, entry.id) || ids.has(entry.id)) return require_driver.fail(at, "duplicate-entity", "Table key already exists.");
|
|
203
|
+
ids.add(entry.id);
|
|
204
|
+
}
|
|
205
|
+
if (!entries.length) return;
|
|
206
|
+
const index$1 = index(table.ids, position);
|
|
207
|
+
for (const entry of entries) session.writeMember(container, entry.id, entry.value, "set");
|
|
208
|
+
const length = table.ids.length;
|
|
209
|
+
table.ids.length += entries.length;
|
|
210
|
+
table.ids.copyWithin(index$1 + entries.length, index$1, length);
|
|
211
|
+
for (let i = 0; i < entries.length; i++) table.ids[index$1 + i] = entries[i].id;
|
|
212
|
+
}
|
|
213
|
+
function remove$2(session, container, ids) {
|
|
214
|
+
const { at, node, value } = container;
|
|
215
|
+
if (node.kind !== "table") return require_driver.fail(at, "invalid-collection", "Expected a table.");
|
|
216
|
+
const table = value;
|
|
217
|
+
for (const id of ids) if (!Object.hasOwn(table.byId, id)) return require_driver.fail(at, "missing-entity", "Table key does not exist.");
|
|
218
|
+
if (!ids.length) return;
|
|
219
|
+
const removed = new Set(ids);
|
|
220
|
+
for (const id of removed) session.writeMember(container, id, void 0, "remove");
|
|
221
|
+
table.ids = table.ids.filter((id) => !removed.has(id));
|
|
222
|
+
}
|
|
223
|
+
function replace$3(session, container, id, value) {
|
|
224
|
+
const { at, node, value: tableValue } = container;
|
|
225
|
+
if (node.kind !== "table") return require_driver.fail(at, "invalid-collection", "Expected a table.");
|
|
226
|
+
const table = tableValue;
|
|
227
|
+
if (!Object.hasOwn(table.byId, id)) return require_driver.fail(at.concat(id), "missing-entity", "Table key does not exist.");
|
|
228
|
+
session.writeMember(container, id, value, "set");
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region core/src/mutation/operations/list.ts
|
|
232
|
+
function insert$1(session, container, value, position) {
|
|
233
|
+
const { at, node, value: current } = container;
|
|
234
|
+
if (node.kind !== "list") return require_driver.fail(at, "invalid-list-key", "Expected a list.");
|
|
235
|
+
session.validate(node.value, value, at);
|
|
236
|
+
const id = node.keyOf(value);
|
|
237
|
+
if (typeof id !== "string") return require_driver.fail(at, "invalid-list-key", "List key must be a string.");
|
|
238
|
+
const items = current, keys$1 = require_advanced.keys(items, node.keyOf);
|
|
239
|
+
if (keys$1.index(id) >= 0) return require_driver.fail(at, "duplicate-list-item", "List key already exists.");
|
|
240
|
+
if (!valid(keys$1, position)) return require_driver.fail(at, "invalid-anchor", "Unknown list anchor.");
|
|
241
|
+
session.recorder.order(container);
|
|
242
|
+
session.recorder.member(container, id, session.definition(container, id), -1);
|
|
243
|
+
require_advanced.insert(items, index(keys$1, position), value);
|
|
244
|
+
session.invalidate();
|
|
245
|
+
}
|
|
246
|
+
function replace$2(session, container, id, value) {
|
|
247
|
+
const { at, node, value: items } = container;
|
|
248
|
+
if (node.kind !== "list") return require_driver.fail(at, "invalid-list-key", "Expected a list.");
|
|
249
|
+
const index = require_advanced.indexedKeys(items, node.keyOf).index(id);
|
|
250
|
+
if (index < 0) return require_driver.fail(at, "missing-list-item", "List key does not exist.");
|
|
251
|
+
session.writeMember(container, id, value, "set", index);
|
|
252
|
+
}
|
|
253
|
+
function remove$1(session, container, id) {
|
|
254
|
+
const { at, node, value } = container;
|
|
255
|
+
if (node.kind !== "list") return require_driver.fail(at, "missing-list-item", "List key does not exist.");
|
|
256
|
+
const index = require_advanced.keys(value, node.keyOf).index(id);
|
|
257
|
+
if (index < 0) return require_driver.fail(at, "missing-list-item", "List key does not exist.");
|
|
258
|
+
session.writeMember(container, id, void 0, "remove", index);
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region core/src/mutation/operations/tree.ts
|
|
262
|
+
const node$1 = (tree, id) => Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
263
|
+
const positionIndex = (index, length, at) => {
|
|
264
|
+
if (index !== void 0 && (!Number.isInteger(index) || index < 0)) return require_driver.fail(at, "invalid-tree-index", "Tree index must be nonnegative.");
|
|
265
|
+
return Math.min(index ?? length, length);
|
|
266
|
+
};
|
|
267
|
+
const insert = (session, container, id, value, position) => {
|
|
268
|
+
const { at, node: schema, value: tree } = container;
|
|
269
|
+
if (typeof id !== "string") return require_driver.fail(at, "invalid-key", "Tree keys must be strings.");
|
|
270
|
+
if (require_context.contains(tree, id)) return require_driver.fail(at, "duplicate-tree-node", "Tree node already exists.");
|
|
271
|
+
const parentId = position?.parentId;
|
|
272
|
+
const parent = parentId === void 0 ? void 0 : node$1(tree, parentId);
|
|
273
|
+
if (tree.rootId !== void 0 && !parent || tree.rootId === void 0 && parentId !== void 0) return require_driver.fail(at, "missing-tree-parent", "A nonempty tree requires an existing parent.");
|
|
274
|
+
const index = positionIndex(position?.index, parent?.children.length ?? 0, at);
|
|
275
|
+
session.validate(schema.value, value, at.concat(id));
|
|
276
|
+
session.recorder.tree(container, id);
|
|
277
|
+
if (parentId !== void 0) session.recorder.tree(container, parentId);
|
|
278
|
+
Object.defineProperty(tree.nodes, id, {
|
|
279
|
+
value: {
|
|
280
|
+
...parentId === void 0 ? {} : { parentId },
|
|
281
|
+
children: [],
|
|
282
|
+
value
|
|
283
|
+
},
|
|
284
|
+
writable: true,
|
|
285
|
+
configurable: true,
|
|
286
|
+
enumerable: true
|
|
287
|
+
});
|
|
288
|
+
if (parent) parent.children.splice(index, 0, id);
|
|
289
|
+
else tree.rootId = id;
|
|
290
|
+
session.invalidate();
|
|
291
|
+
};
|
|
292
|
+
const replace$1 = (session, container, id, value) => {
|
|
293
|
+
const { at, node: schema, value: tree } = container;
|
|
294
|
+
const entry = node$1(tree, id);
|
|
295
|
+
if (!entry) return require_driver.fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
296
|
+
if (Object.is(entry.value, value) && Object.hasOwn(entry, "value")) return;
|
|
297
|
+
session.validate(schema.value, value, at.concat(id));
|
|
298
|
+
session.recorder.tree(container, id);
|
|
299
|
+
entry.value = value;
|
|
300
|
+
};
|
|
301
|
+
const remove = (session, container, id) => {
|
|
302
|
+
const { at, value: tree } = container;
|
|
303
|
+
const entry = node$1(tree, id);
|
|
304
|
+
if (!entry) return require_driver.fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
305
|
+
const ids = [], stack = [id];
|
|
306
|
+
while (stack.length) {
|
|
307
|
+
const current = stack.pop();
|
|
308
|
+
ids.push(current);
|
|
309
|
+
for (const child of tree.nodes[current].children) stack.push(child);
|
|
310
|
+
}
|
|
311
|
+
for (const key of ids) session.recorder.tree(container, key);
|
|
312
|
+
if (entry.parentId !== void 0) session.recorder.tree(container, entry.parentId);
|
|
313
|
+
if (entry.parentId === void 0) delete tree.rootId;
|
|
314
|
+
else {
|
|
315
|
+
const children = tree.nodes[entry.parentId].children;
|
|
316
|
+
children.splice(children.indexOf(id), 1);
|
|
317
|
+
}
|
|
318
|
+
for (const key of ids) delete tree.nodes[key];
|
|
319
|
+
session.invalidate();
|
|
320
|
+
};
|
|
321
|
+
const move$1 = (session, container, id, position) => {
|
|
322
|
+
const { at, value: tree } = container;
|
|
323
|
+
const entry = node$1(tree, id), parentId = position?.parentId;
|
|
324
|
+
if (!entry) return require_driver.fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
325
|
+
if (entry.parentId === void 0 || parentId === void 0 || !require_context.contains(tree, parentId)) return require_driver.fail(at, "missing-tree-parent", "Moving requires a non-root node and an existing parent.");
|
|
326
|
+
for (let ancestor = parentId; ancestor !== void 0; ancestor = tree.nodes[ancestor].parentId) if (ancestor === id) return require_driver.fail(at, "tree-cycle", "Cannot move a node under its descendant.");
|
|
327
|
+
const old = tree.nodes[entry.parentId], next = tree.nodes[parentId];
|
|
328
|
+
const index = positionIndex(position?.index, next.children.length - (old === next ? 1 : 0), at);
|
|
329
|
+
const previous = old.children.indexOf(id);
|
|
330
|
+
if (old === next && previous === index) return;
|
|
331
|
+
session.recorder.tree(container, id);
|
|
332
|
+
session.recorder.tree(container, entry.parentId);
|
|
333
|
+
session.recorder.tree(container, parentId);
|
|
334
|
+
old.children.splice(previous, 1);
|
|
335
|
+
next.children.splice(index, 0, id);
|
|
336
|
+
entry.parentId = parentId;
|
|
337
|
+
session.invalidate();
|
|
338
|
+
};
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region core/src/mutation/operations/order.ts
|
|
341
|
+
const assertOrdered = (container) => {
|
|
342
|
+
const { at, node } = container;
|
|
343
|
+
if (node.kind !== "table" && node.kind !== "list") return require_driver.fail(at, "invalid-collection", "Expected an ordered container.");
|
|
344
|
+
return node;
|
|
345
|
+
};
|
|
346
|
+
const plannedOrder = (at, current, selection, position) => {
|
|
347
|
+
const plan = moveSelection(current, selection, position);
|
|
348
|
+
switch (plan.kind) {
|
|
349
|
+
case "ok": return plan.order;
|
|
350
|
+
case "duplicate": return require_driver.fail(at, "invalid-key", "Moved keys must be unique.");
|
|
351
|
+
case "missing": return require_driver.fail(at, "missing-entity", "Ordered key does not exist.");
|
|
352
|
+
case "invalid-anchor": return require_driver.fail(at, "invalid-anchor", "Unknown order anchor.");
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
function move(session, container, ids, position) {
|
|
356
|
+
const node = assertOrdered(container);
|
|
357
|
+
const selection = typeof ids === "string" ? [ids] : ids;
|
|
358
|
+
if (!selection.length) return;
|
|
359
|
+
if (node.kind === "table") {
|
|
360
|
+
const order = container.value.ids;
|
|
361
|
+
const next = plannedOrder(container.at, order, selection, position);
|
|
362
|
+
if (require_advanced.equal(order, next)) return;
|
|
363
|
+
session.recorder.order(container);
|
|
364
|
+
require_advanced.installKeys(order, next);
|
|
365
|
+
} else {
|
|
366
|
+
const items = container.value;
|
|
367
|
+
const current = require_advanced.listSequence(items, node.keyOf);
|
|
368
|
+
const next = plannedOrder(container.at, current.order, selection, position);
|
|
369
|
+
if (require_advanced.equal(current.order, next)) return;
|
|
370
|
+
session.recorder.order(container);
|
|
371
|
+
require_advanced.installList(items, node.keyOf, current, next);
|
|
372
|
+
}
|
|
373
|
+
session.invalidate();
|
|
374
|
+
}
|
|
375
|
+
function reorder(session, container, next) {
|
|
376
|
+
const node = assertOrdered(container);
|
|
377
|
+
if (node.kind === "table") {
|
|
378
|
+
const current = container.value.ids;
|
|
379
|
+
if (!require_advanced.matches(next, current)) return require_driver.fail(container.at, "invalid-collection", "Order must contain every key exactly once.");
|
|
380
|
+
if (require_advanced.equal(current, next)) return;
|
|
381
|
+
session.recorder.order(container);
|
|
382
|
+
require_advanced.installKeys(current, next);
|
|
383
|
+
} else {
|
|
384
|
+
const items = container.value;
|
|
385
|
+
const current = require_advanced.listSequence(items, node.keyOf);
|
|
386
|
+
if (!require_advanced.matches(next, current.order)) return require_driver.fail(container.at, "invalid-collection", "Order must contain every key exactly once.");
|
|
387
|
+
if (require_advanced.equal(current.order, next)) return;
|
|
388
|
+
session.recorder.order(container);
|
|
389
|
+
require_advanced.installList(items, node.keyOf, current, next);
|
|
390
|
+
}
|
|
391
|
+
session.invalidate();
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region core/src/impact/target.ts
|
|
395
|
+
const treeRootSegment = "rootId";
|
|
396
|
+
const treeNodesSegment = "nodes";
|
|
397
|
+
const ordinaryNamespace = "\0value";
|
|
398
|
+
const treeNamespace = "\0tree";
|
|
399
|
+
const tree$1 = (at, selection) => ({
|
|
400
|
+
kind: "value",
|
|
401
|
+
at,
|
|
402
|
+
tree: selection
|
|
403
|
+
});
|
|
404
|
+
const address = (target) => "at" in target ? target.at : target.address;
|
|
405
|
+
const id = (target) => target.kind === "collection" && "id" in target ? target.id : void 0;
|
|
406
|
+
const belongs = (target, schema) => !("schema" in target) || target.schema === schema;
|
|
407
|
+
const treeTarget = (target) => "tree" in target ? target.tree : void 0;
|
|
408
|
+
const sameTreeTarget = (left, right) => {
|
|
409
|
+
if (!left || !right) return left === right;
|
|
410
|
+
if (left.kind !== right.kind) return false;
|
|
411
|
+
return left.kind !== "node" || right.kind === "node" && left.id === right.id;
|
|
412
|
+
};
|
|
413
|
+
const same = (left, right) => {
|
|
414
|
+
if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
|
|
415
|
+
if (!sameTreeTarget(treeTarget(left), treeTarget(right))) return false;
|
|
416
|
+
if (left.kind !== right.kind) return false;
|
|
417
|
+
const leftAddress = address(left);
|
|
418
|
+
const rightAddress = address(right);
|
|
419
|
+
if (leftAddress.length !== rightAddress.length) return false;
|
|
420
|
+
for (let index = 0; index < leftAddress.length; index += 1) if (leftAddress[index] !== rightAddress[index]) return false;
|
|
421
|
+
return left.kind !== "collection" || id(left) === id(right);
|
|
422
|
+
};
|
|
423
|
+
const indexedAddress = (target) => {
|
|
424
|
+
const tree = treeTarget(target);
|
|
425
|
+
if (tree?.kind === "root") return [
|
|
426
|
+
treeNamespace,
|
|
427
|
+
...address(target),
|
|
428
|
+
treeRootSegment
|
|
429
|
+
];
|
|
430
|
+
if (tree?.kind === "nodes") return [
|
|
431
|
+
treeNamespace,
|
|
432
|
+
...address(target),
|
|
433
|
+
treeNodesSegment
|
|
434
|
+
];
|
|
435
|
+
if (tree?.kind === "node") return [
|
|
436
|
+
treeNamespace,
|
|
437
|
+
...address(target),
|
|
438
|
+
treeNodesSegment,
|
|
439
|
+
tree.id
|
|
440
|
+
];
|
|
441
|
+
const key = id(target);
|
|
442
|
+
return key === void 0 ? [ordinaryNamespace, ...address(target)] : [
|
|
443
|
+
ordinaryNamespace,
|
|
444
|
+
...address(target),
|
|
445
|
+
key
|
|
446
|
+
];
|
|
447
|
+
};
|
|
448
|
+
const visitChangedLocations = (changes, value, order) => {
|
|
449
|
+
for (const change of changes.changes) {
|
|
450
|
+
if (change.kind === "reset") continue;
|
|
451
|
+
if (change.kind === "members") {
|
|
452
|
+
for (const member of change.members) {
|
|
453
|
+
value([
|
|
454
|
+
ordinaryNamespace,
|
|
455
|
+
...change.at,
|
|
456
|
+
member.key
|
|
457
|
+
]);
|
|
458
|
+
value([
|
|
459
|
+
treeNamespace,
|
|
460
|
+
...change.at,
|
|
461
|
+
member.key
|
|
462
|
+
]);
|
|
463
|
+
}
|
|
464
|
+
if (change.order) order([ordinaryNamespace, ...change.at]);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
value([ordinaryNamespace, ...change.at]);
|
|
468
|
+
if (change.before !== change.after) value([
|
|
469
|
+
treeNamespace,
|
|
470
|
+
...change.at,
|
|
471
|
+
treeRootSegment
|
|
472
|
+
]);
|
|
473
|
+
for (const node of change.nodes) value([
|
|
474
|
+
treeNamespace,
|
|
475
|
+
...change.at,
|
|
476
|
+
treeNodesSegment,
|
|
477
|
+
node.id
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const affected = (target, values, orders) => {
|
|
482
|
+
const at = indexedAddress(target);
|
|
483
|
+
return id(target) !== void 0 ? values.hasAncestor(at) : values.overlaps(at) || orders.hasDescendant(at);
|
|
484
|
+
};
|
|
485
|
+
/** Targets are classified once; commit matching never builds a reverse impact index. */
|
|
486
|
+
var SubscriptionIndex = class {
|
|
487
|
+
ordinary = new require_driver.AddressIndex();
|
|
488
|
+
membership = new require_driver.AddressIndex();
|
|
489
|
+
constructor(schema) {
|
|
490
|
+
this.schema = schema;
|
|
491
|
+
}
|
|
492
|
+
add(target, value) {
|
|
493
|
+
if (!belongs(target, this.schema)) return;
|
|
494
|
+
(id(target) === void 0 ? this.ordinary : this.membership).add(indexedAddress(target), value);
|
|
495
|
+
}
|
|
496
|
+
delete(target, value) {
|
|
497
|
+
if (!belongs(target, this.schema)) return;
|
|
498
|
+
(id(target) === void 0 ? this.ordinary : this.membership).delete(indexedAddress(target), value);
|
|
499
|
+
}
|
|
500
|
+
collect(changes, visit) {
|
|
501
|
+
const values = this.ordinary.query(visit);
|
|
502
|
+
const members = this.membership.query(visit, "descendants");
|
|
503
|
+
const orders = this.ordinary.query(visit, "ancestors");
|
|
504
|
+
if (changes.changes.some((change) => change.kind === "reset")) {
|
|
505
|
+
values([]);
|
|
506
|
+
members([]);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
visitChangedLocations(changes, (at) => {
|
|
510
|
+
values(at);
|
|
511
|
+
members(at);
|
|
512
|
+
}, orders);
|
|
513
|
+
}
|
|
514
|
+
clear() {
|
|
515
|
+
this.ordinary.clear();
|
|
516
|
+
this.membership.clear();
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region core/src/access/scope.ts
|
|
521
|
+
const locations = /* @__PURE__ */ new WeakMap();
|
|
522
|
+
const locationOf = (value) => locations.get(value);
|
|
523
|
+
const addressOf = (target) => {
|
|
524
|
+
if (target.at) return target.at;
|
|
525
|
+
require_advanced.profile.access("addresses");
|
|
526
|
+
return target.at = addressOf(target.parent).concat(target.key);
|
|
527
|
+
};
|
|
528
|
+
const assertActive = (context) => {
|
|
529
|
+
if (context.active && !context.active()) throw new Error("Document access scope has expired.");
|
|
530
|
+
};
|
|
531
|
+
const collect = (context, at, kind = "value", id) => {
|
|
532
|
+
context.dependencies?.record(kind === "value" ? {
|
|
533
|
+
kind,
|
|
534
|
+
at
|
|
535
|
+
} : {
|
|
536
|
+
kind,
|
|
537
|
+
at,
|
|
538
|
+
...id === void 0 ? {} : { id }
|
|
539
|
+
});
|
|
540
|
+
};
|
|
541
|
+
const collectTree = (context, at, selection) => context.dependencies?.record(tree$1(at, selection));
|
|
542
|
+
const snapshot = (value) => {
|
|
543
|
+
const location = value && typeof value === "object" ? locationOf(value) : void 0;
|
|
544
|
+
if (!location) return value;
|
|
545
|
+
const { context } = location;
|
|
546
|
+
const at = addressOf(location.target);
|
|
547
|
+
require_advanced.profile.access("snapshots");
|
|
548
|
+
collect(context, at);
|
|
549
|
+
const resolved = resolve(context, location.target);
|
|
550
|
+
if (!resolved.node) throw new Error("The selected schema address no longer exists.");
|
|
551
|
+
return require_advanced.copyValue(resolved.node, resolved.value);
|
|
552
|
+
};
|
|
553
|
+
const replace = (container, key, value) => {
|
|
554
|
+
if (!locationOf(container)) throw new TypeError("replace requires scoped document access.");
|
|
555
|
+
if (typeof key !== "string") throw new TypeError("Document keys must be strings.");
|
|
556
|
+
Reflect.set(container, key, value);
|
|
557
|
+
};
|
|
558
|
+
const resolve = (context, target) => {
|
|
559
|
+
assertActive(context);
|
|
560
|
+
const generation = context.session?.generation ?? 0;
|
|
561
|
+
if (target.generation !== generation) {
|
|
562
|
+
require_advanced.profile.access("resolutions");
|
|
563
|
+
if (!target.parent && !target.at.length) {
|
|
564
|
+
target.node = context.state.schema;
|
|
565
|
+
target.value = context.state.document;
|
|
566
|
+
} else {
|
|
567
|
+
const parent = target.parent && resolve(context, target.parent);
|
|
568
|
+
const location = parent ? resolveChild(parent.node, parent.value, target.key) : resolveLocated(context.state.schema, context.state.document, target.at);
|
|
569
|
+
target.node = location?.node ?? nodeAt(context.state.schema, addressOf(target), context.state.document);
|
|
570
|
+
target.value = location && Object.hasOwn(location.parent, location.key) ? location.parent[location.key] : void 0;
|
|
571
|
+
}
|
|
572
|
+
target.generation = generation;
|
|
573
|
+
}
|
|
574
|
+
return target;
|
|
575
|
+
};
|
|
576
|
+
const createAccess = (context, initial = []) => {
|
|
577
|
+
require_advanced.profile.access("scopes");
|
|
578
|
+
const mutable = () => {
|
|
579
|
+
assertActive(context);
|
|
580
|
+
if (!context.session) throw new TypeError("Cannot modify read-only document access.");
|
|
581
|
+
return context.session;
|
|
582
|
+
};
|
|
583
|
+
const writableContainer = (target, session) => {
|
|
584
|
+
if (target.container?.generation === session.generation && "layout" in target.container) return target.container;
|
|
585
|
+
return target.container = session.bind(addressOf(target), target.node, target.value);
|
|
586
|
+
};
|
|
587
|
+
const childAt = (target, key) => {
|
|
588
|
+
require_advanced.profile.access("addresses");
|
|
589
|
+
return addressOf(target).concat(key);
|
|
590
|
+
};
|
|
591
|
+
const access = (target) => {
|
|
592
|
+
if (target.proxy) return target.proxy;
|
|
593
|
+
const proxy = new Proxy(target, handler);
|
|
594
|
+
require_advanced.profile.access("proxies");
|
|
595
|
+
locations.set(proxy, {
|
|
596
|
+
context,
|
|
597
|
+
target
|
|
598
|
+
});
|
|
599
|
+
target.proxy = proxy;
|
|
600
|
+
return proxy;
|
|
601
|
+
};
|
|
602
|
+
const createTarget = (at, parent, key) => ({
|
|
603
|
+
at,
|
|
604
|
+
parent,
|
|
605
|
+
key,
|
|
606
|
+
node: void 0,
|
|
607
|
+
value: void 0,
|
|
608
|
+
generation: -1,
|
|
609
|
+
children: void 0,
|
|
610
|
+
proxy: void 0
|
|
611
|
+
});
|
|
612
|
+
const childAccess = (target, member, node, value) => {
|
|
613
|
+
const key = typeof member === "string" ? member : member.key;
|
|
614
|
+
if (node.kind === "field" || value === void 0) {
|
|
615
|
+
if (context.dependencies) collect(context, childAt(target, key));
|
|
616
|
+
return value;
|
|
617
|
+
}
|
|
618
|
+
let children = target.children;
|
|
619
|
+
let child;
|
|
620
|
+
if (typeof member !== "string") {
|
|
621
|
+
if (children?.kind !== "fixed" || children.schema !== target.node) children = {
|
|
622
|
+
kind: "fixed",
|
|
623
|
+
schema: target.node,
|
|
624
|
+
slots: new Array(require_advanced.compiledShape(target.node, target.value).members.size)
|
|
625
|
+
};
|
|
626
|
+
child = children.slots[member.slot] ??= createTarget(void 0, target, key);
|
|
627
|
+
} else {
|
|
628
|
+
if (children?.kind !== "dynamic") children = {
|
|
629
|
+
kind: "dynamic",
|
|
630
|
+
keys: /* @__PURE__ */ new Map()
|
|
631
|
+
};
|
|
632
|
+
const retained = children.keys.get(key);
|
|
633
|
+
child = retained ?? createTarget(void 0, target, key);
|
|
634
|
+
if (!retained) children.keys.set(key, child);
|
|
635
|
+
}
|
|
636
|
+
target.children = children;
|
|
637
|
+
if (child.generation === target.generation) return child.proxy;
|
|
638
|
+
child.node = node;
|
|
639
|
+
child.value = value;
|
|
640
|
+
child.generation = target.generation;
|
|
641
|
+
return access(child);
|
|
642
|
+
};
|
|
643
|
+
const collectionValue = (target, node) => {
|
|
644
|
+
const current = resolve(context, target);
|
|
645
|
+
if (current.node !== node || current.value === void 0) throw new TypeError("The collection method belongs to a replaced schema branch.");
|
|
646
|
+
return current.value;
|
|
647
|
+
};
|
|
648
|
+
const writableCollection = (target, node) => {
|
|
649
|
+
const session = mutable();
|
|
650
|
+
collectionValue(target, node);
|
|
651
|
+
return writableContainer(target, session);
|
|
652
|
+
};
|
|
653
|
+
const writableTree = (target, node) => {
|
|
654
|
+
const session = mutable();
|
|
655
|
+
collectionValue(target, node);
|
|
656
|
+
if (target.container?.generation === session.generation && !("layout" in target.container)) return target.container;
|
|
657
|
+
return target.container = session.bindTree(addressOf(target), target.node, target.value);
|
|
658
|
+
};
|
|
659
|
+
const replaceCollection = (target, node, value) => {
|
|
660
|
+
const session = mutable();
|
|
661
|
+
collectionValue(target, node);
|
|
662
|
+
session.replace(addressOf(target), value);
|
|
663
|
+
};
|
|
664
|
+
const mapMethod = (target, property, node) => {
|
|
665
|
+
if (property === "ids") return () => {
|
|
666
|
+
const current = collectionValue(target, node);
|
|
667
|
+
if (context.dependencies) collect(context, addressOf(target), "collection");
|
|
668
|
+
return Object.keys(current);
|
|
669
|
+
};
|
|
670
|
+
if (property === "has") return (id) => {
|
|
671
|
+
const current = collectionValue(target, node);
|
|
672
|
+
if (context.dependencies) collect(context, addressOf(target), "collection", id);
|
|
673
|
+
return Object.hasOwn(current, id);
|
|
674
|
+
};
|
|
675
|
+
if (property === "get") return (id) => {
|
|
676
|
+
const current = collectionValue(target, node);
|
|
677
|
+
if (context.dependencies) collect(context, addressOf(target), "collection", id);
|
|
678
|
+
if (!Object.hasOwn(current, id)) return void 0;
|
|
679
|
+
if (node.value.kind === "field") {
|
|
680
|
+
if (context.dependencies) collect(context, childAt(target, id));
|
|
681
|
+
return current[id];
|
|
682
|
+
}
|
|
683
|
+
return childAccess(target, id, node.value, current[id]);
|
|
684
|
+
};
|
|
685
|
+
if (property === "put") return (id, value) => {
|
|
686
|
+
const writable = writableCollection(target, node);
|
|
687
|
+
put(context.session, writable, id, value);
|
|
688
|
+
};
|
|
689
|
+
if (property === "remove") return (id) => {
|
|
690
|
+
const writable = writableCollection(target, node);
|
|
691
|
+
remove$3(context.session, writable, id);
|
|
692
|
+
};
|
|
693
|
+
if (property === "replace") return (value) => replaceCollection(target, node, value);
|
|
694
|
+
};
|
|
695
|
+
const orderedMethod = (target, property, node) => {
|
|
696
|
+
const at = addressOf(target);
|
|
697
|
+
if (property === "ids") return () => {
|
|
698
|
+
const current = collectionValue(target, node);
|
|
699
|
+
collect(context, at, "collection");
|
|
700
|
+
return node.kind === "table" ? [...current.ids] : current.map(node.keyOf);
|
|
701
|
+
};
|
|
702
|
+
if (property === "has") return (id) => {
|
|
703
|
+
const current = collectionValue(target, node);
|
|
704
|
+
collect(context, at, "collection", id);
|
|
705
|
+
return node.kind === "table" ? Object.hasOwn(current.byId, id) : require_advanced.indexedKeys(current, node.keyOf).index(id) >= 0;
|
|
706
|
+
};
|
|
707
|
+
if (property === "get") return (id) => {
|
|
708
|
+
const current = collectionValue(target, node);
|
|
709
|
+
collect(context, at, "collection", id);
|
|
710
|
+
const location = resolveChild(node, current, id);
|
|
711
|
+
if (!location || !Object.hasOwn(location.parent, location.key)) return void 0;
|
|
712
|
+
return childAccess(target, id, location.node, location.parent[location.key]);
|
|
713
|
+
};
|
|
714
|
+
if (property === "move") return (ids, position) => {
|
|
715
|
+
const writable = writableCollection(target, node);
|
|
716
|
+
move(context.session, writable, ids, position);
|
|
717
|
+
};
|
|
718
|
+
if (property === "reorder") return (ids) => {
|
|
719
|
+
const writable = writableCollection(target, node);
|
|
720
|
+
reorder(context.session, writable, ids);
|
|
721
|
+
};
|
|
722
|
+
};
|
|
723
|
+
const tableMethod = (target, property, node) => {
|
|
724
|
+
if (property === "create") return (input, valueOrPosition, position) => {
|
|
725
|
+
const writable = writableCollection(target, node);
|
|
726
|
+
create(context.session, writable, Array.isArray(input) ? input : [{
|
|
727
|
+
id: input,
|
|
728
|
+
value: valueOrPosition
|
|
729
|
+
}], Array.isArray(input) ? valueOrPosition : position);
|
|
730
|
+
};
|
|
731
|
+
if (property === "remove") return (ids) => {
|
|
732
|
+
const writable = writableCollection(target, node);
|
|
733
|
+
remove$2(context.session, writable, typeof ids === "string" ? [ids] : ids);
|
|
734
|
+
};
|
|
735
|
+
if (property === "replace") return (...args) => {
|
|
736
|
+
const [valueOrId, value] = args;
|
|
737
|
+
if (args.length === 1) return replaceCollection(target, node, valueOrId);
|
|
738
|
+
const writable = writableCollection(target, node);
|
|
739
|
+
replace$3(context.session, writable, valueOrId, value);
|
|
740
|
+
};
|
|
741
|
+
return orderedMethod(target, property, node);
|
|
742
|
+
};
|
|
743
|
+
const listMethod = (target, property, node) => {
|
|
744
|
+
if (property === "insert") return (value, position) => {
|
|
745
|
+
const writable = writableCollection(target, node);
|
|
746
|
+
insert$1(context.session, writable, value, position);
|
|
747
|
+
};
|
|
748
|
+
if (property === "remove") return (id) => {
|
|
749
|
+
const writable = writableCollection(target, node);
|
|
750
|
+
remove$1(context.session, writable, id);
|
|
751
|
+
};
|
|
752
|
+
if (property === "replace") return (...args) => {
|
|
753
|
+
const [valueOrId, value] = args;
|
|
754
|
+
if (args.length === 1) return replaceCollection(target, node, valueOrId);
|
|
755
|
+
const writable = writableCollection(target, node);
|
|
756
|
+
replace$2(context.session, writable, valueOrId, value);
|
|
757
|
+
};
|
|
758
|
+
return orderedMethod(target, property, node);
|
|
759
|
+
};
|
|
760
|
+
const treeMethod = (target, property, node) => {
|
|
761
|
+
const at = addressOf(target);
|
|
762
|
+
if (property === "insert") return (id, value, position) => {
|
|
763
|
+
const writable = writableTree(target, node);
|
|
764
|
+
insert(context.session, writable, id, value, position);
|
|
765
|
+
};
|
|
766
|
+
if (property === "remove") return (id) => {
|
|
767
|
+
const writable = writableTree(target, node);
|
|
768
|
+
remove(context.session, writable, id);
|
|
769
|
+
};
|
|
770
|
+
if (property === "move") return (id, position) => {
|
|
771
|
+
const writable = writableTree(target, node);
|
|
772
|
+
move$1(context.session, writable, id, position);
|
|
773
|
+
};
|
|
774
|
+
if (property === "replace") return (...args) => {
|
|
775
|
+
const [valueOrId, value] = args;
|
|
776
|
+
if (args.length === 1) return replaceCollection(target, node, valueOrId);
|
|
777
|
+
const writable = writableTree(target, node);
|
|
778
|
+
replace$1(context.session, writable, valueOrId, value);
|
|
779
|
+
};
|
|
780
|
+
const current = () => collectionValue(target, node);
|
|
781
|
+
if (property === "rootId") return () => {
|
|
782
|
+
collectTree(context, at, { kind: "root" });
|
|
783
|
+
return current().rootId;
|
|
784
|
+
};
|
|
785
|
+
if (property === "get") return (id) => {
|
|
786
|
+
collectTree(context, at, {
|
|
787
|
+
kind: "node",
|
|
788
|
+
id
|
|
789
|
+
});
|
|
790
|
+
const value = current();
|
|
791
|
+
return require_context.contains(value, id) ? value.nodes[id].value : void 0;
|
|
792
|
+
};
|
|
793
|
+
if (property === "has") return (id) => {
|
|
794
|
+
collectTree(context, at, {
|
|
795
|
+
kind: "node",
|
|
796
|
+
id
|
|
797
|
+
});
|
|
798
|
+
return require_context.contains(current(), id);
|
|
799
|
+
};
|
|
800
|
+
if (property === "parent") return (id) => {
|
|
801
|
+
collectTree(context, at, {
|
|
802
|
+
kind: "node",
|
|
803
|
+
id
|
|
804
|
+
});
|
|
805
|
+
return require_context.parent(current(), id);
|
|
806
|
+
};
|
|
807
|
+
if (property === "children") return (id) => {
|
|
808
|
+
collectTree(context, at, {
|
|
809
|
+
kind: "node",
|
|
810
|
+
id
|
|
811
|
+
});
|
|
812
|
+
const children$1 = require_context.children(current(), id);
|
|
813
|
+
return children$1 ? [...children$1] : void 0;
|
|
814
|
+
};
|
|
815
|
+
};
|
|
816
|
+
const handler = {
|
|
817
|
+
get: (target, property) => {
|
|
818
|
+
const { node, value } = resolve(context, target);
|
|
819
|
+
if (!node || value === void 0) return void 0;
|
|
820
|
+
if (typeof property !== "string") return void 0;
|
|
821
|
+
if (node.kind === "map") return mapMethod(target, property, node);
|
|
822
|
+
if (node.kind === "table") return tableMethod(target, property, node);
|
|
823
|
+
if (node.kind === "list") return listMethod(target, property, node);
|
|
824
|
+
if (node.kind === "tree") return treeMethod(target, property, node);
|
|
825
|
+
if (node.kind === "variant" && property === node.tag) {
|
|
826
|
+
collect(context, childAt(target, property));
|
|
827
|
+
return value[property];
|
|
828
|
+
}
|
|
829
|
+
const member = require_advanced.compiledShape(node, value)?.members.get(property);
|
|
830
|
+
if (!member) return void 0;
|
|
831
|
+
if (member.field) {
|
|
832
|
+
if (context.dependencies) collect(context, childAt(target, property));
|
|
833
|
+
return value[property];
|
|
834
|
+
}
|
|
835
|
+
return childAccess(target, node.kind === "object" ? member : property, member.node, value[property]);
|
|
836
|
+
},
|
|
837
|
+
set: (target, property, value) => {
|
|
838
|
+
const session = mutable(), { node } = resolve(context, target);
|
|
839
|
+
if (typeof property !== "string" || !node || node.kind !== "object" && node.kind !== "variant") throw new TypeError("Invalid structural assignment.");
|
|
840
|
+
if (node.kind === "variant" && node.tag === property) throw new TypeError("Variant discriminants are read-only.");
|
|
841
|
+
session.assignMember(writableContainer(target, session), property, value);
|
|
842
|
+
return true;
|
|
843
|
+
},
|
|
844
|
+
deleteProperty: (target, property) => {
|
|
845
|
+
const session = mutable(), { node } = resolve(context, target);
|
|
846
|
+
if (typeof property !== "string" || node?.kind === "map" || node?.kind === "variant" && node.tag === property) throw new TypeError("Invalid structural deletion.");
|
|
847
|
+
session.removeMember(writableContainer(target, session), property);
|
|
848
|
+
return true;
|
|
849
|
+
},
|
|
850
|
+
has: (target, property) => {
|
|
851
|
+
const { node, value } = resolve(context, target);
|
|
852
|
+
if (typeof property !== "string") return false;
|
|
853
|
+
if (node?.kind === "map") return false;
|
|
854
|
+
collect(context, addressOf(target));
|
|
855
|
+
return value !== void 0 && Object.hasOwn(value, property);
|
|
856
|
+
},
|
|
857
|
+
ownKeys: (target) => {
|
|
858
|
+
const { node, value } = resolve(context, target);
|
|
859
|
+
if (node?.kind === "map") return [];
|
|
860
|
+
if (context.dependencies) collect(context, addressOf(target));
|
|
861
|
+
return value && typeof value === "object" ? Object.keys(value) : [];
|
|
862
|
+
},
|
|
863
|
+
getOwnPropertyDescriptor: (target, property) => {
|
|
864
|
+
const { node, value } = resolve(context, target);
|
|
865
|
+
if (node?.kind === "map") return void 0;
|
|
866
|
+
return value && Object.hasOwn(value, property) ? {
|
|
867
|
+
enumerable: true,
|
|
868
|
+
configurable: true
|
|
869
|
+
} : void 0;
|
|
870
|
+
},
|
|
871
|
+
defineProperty: () => {
|
|
872
|
+
assertActive(context);
|
|
873
|
+
throw new TypeError("Use schema assignments.");
|
|
874
|
+
},
|
|
875
|
+
setPrototypeOf: () => {
|
|
876
|
+
assertActive(context);
|
|
877
|
+
throw new TypeError("Document prototypes are immutable.");
|
|
878
|
+
},
|
|
879
|
+
preventExtensions: () => {
|
|
880
|
+
assertActive(context);
|
|
881
|
+
throw new TypeError("Document access cannot be frozen.");
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
const root = resolve(context, createTarget(initial, void 0, ""));
|
|
885
|
+
if (!root.node || root.node.kind === "field" || root.value === void 0) {
|
|
886
|
+
if (context.dependencies) collect(context, initial);
|
|
887
|
+
return root.value;
|
|
888
|
+
}
|
|
889
|
+
return access(root);
|
|
890
|
+
};
|
|
891
|
+
//#endregion
|
|
892
|
+
//#region core/src/address/relation.ts
|
|
893
|
+
const contains = (parent, child) => {
|
|
894
|
+
require_advanced.profile.address.prefixComparison();
|
|
895
|
+
if (parent.length > child.length) return false;
|
|
896
|
+
for (let index = 0; index < parent.length; index += 1) {
|
|
897
|
+
require_advanced.profile.address.segmentCompared();
|
|
898
|
+
if (parent[index] !== child[index]) return false;
|
|
899
|
+
}
|
|
900
|
+
return true;
|
|
901
|
+
};
|
|
902
|
+
//#endregion
|
|
7
903
|
//#region core/src/impact.ts
|
|
8
|
-
const queries = /* @__PURE__ */ new WeakMap();
|
|
9
904
|
const createImpact = (schema, changes) => {
|
|
10
905
|
const reset = changes.changes.some((change) => change.kind === "reset");
|
|
11
906
|
let values;
|
|
12
907
|
let orders;
|
|
13
908
|
const index = () => {
|
|
14
909
|
if (!values) {
|
|
15
|
-
|
|
16
|
-
values = new
|
|
17
|
-
orders = new
|
|
18
|
-
|
|
910
|
+
require_advanced.profile.impact.index();
|
|
911
|
+
values = new require_driver.AddressIndex();
|
|
912
|
+
orders = new require_driver.AddressIndex();
|
|
913
|
+
visitChangedLocations(changes, (at) => values.add(at, true), (at) => orders.add(at, true));
|
|
19
914
|
}
|
|
20
915
|
return values;
|
|
21
916
|
};
|
|
22
917
|
const cache = /* @__PURE__ */ new Map();
|
|
23
|
-
const
|
|
24
|
-
affects(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (change.
|
|
46
|
-
if (change.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
previous = position;
|
|
61
|
-
}
|
|
918
|
+
const affects = (value) => {
|
|
919
|
+
require_advanced.profile.impact.affects();
|
|
920
|
+
if (!belongs(value, schema)) return false;
|
|
921
|
+
if (reset) return true;
|
|
922
|
+
return affected(value, index(), orders);
|
|
923
|
+
};
|
|
924
|
+
const collection = (selector) => {
|
|
925
|
+
if (selector.schema !== schema) throw new TypeError("Collection belongs to another root model.");
|
|
926
|
+
const key = JSON.stringify(indexedAddress(selector)), cached = cache.get(key);
|
|
927
|
+
if (cached) return cached;
|
|
928
|
+
if (reset) {
|
|
929
|
+
const result = { kind: "reset" };
|
|
930
|
+
cache.set(key, result);
|
|
931
|
+
return result;
|
|
932
|
+
}
|
|
933
|
+
const at = selector.address;
|
|
934
|
+
const tree = selector.tree;
|
|
935
|
+
const added = /* @__PURE__ */ new Set(), removed = /* @__PURE__ */ new Set(), updated = /* @__PURE__ */ new Set();
|
|
936
|
+
let orderChanged = false;
|
|
937
|
+
for (const change of changes.changes) {
|
|
938
|
+
if (change.kind === "reset") continue;
|
|
939
|
+
if (change.kind === "members") {
|
|
940
|
+
if (change.at.length < at.length && contains(change.at, at)) {
|
|
941
|
+
if (change.members.some((member) => member.key === at[change.at.length])) {
|
|
942
|
+
const result = { kind: "reset" };
|
|
943
|
+
cache.set(key, result);
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
} else if (contains(at, change.at)) if (change.at.length === at.length) {
|
|
947
|
+
if (change.order) {
|
|
948
|
+
const positions = new Map(change.order.after.map((id, i) => [id, i]));
|
|
949
|
+
let previous = -1;
|
|
950
|
+
for (const id of change.order.before) {
|
|
951
|
+
const position = positions.get(id);
|
|
952
|
+
if (position === void 0) continue;
|
|
953
|
+
if (position < previous) orderChanged = true;
|
|
954
|
+
previous = position;
|
|
62
955
|
}
|
|
63
|
-
for (const member of change.members) if (member.kind === "added") added.add(member.key);
|
|
64
|
-
else if (member.kind === "removed") removed.add(member.key);
|
|
65
|
-
else updated.add(member.key);
|
|
66
|
-
} else updated.add(change.at[at.length]);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (change.kind === "tree" && require_issue.contains(change.at, at)) {
|
|
70
|
-
if (tree?.kind === "nodes" && change.at.length === at.length && require_issue.contains(at, change.at)) {
|
|
71
|
-
for (const node of change.nodes) if (node.kind === "added") added.add(node.id);
|
|
72
|
-
else if (node.kind === "removed") removed.add(node.id);
|
|
73
|
-
else updated.add(node.id);
|
|
74
|
-
continue;
|
|
75
956
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
957
|
+
for (const member of change.members) if (member.kind === "added") added.add(member.key);
|
|
958
|
+
else if (member.kind === "removed") removed.add(member.key);
|
|
959
|
+
else updated.add(member.key);
|
|
960
|
+
} else updated.add(change.at[at.length]);
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
if (change.kind === "tree" && contains(change.at, at)) {
|
|
964
|
+
if (tree?.kind === "nodes" && change.at.length === at.length && contains(at, change.at)) {
|
|
965
|
+
for (const node of change.nodes) if (node.kind === "added") added.add(node.id);
|
|
966
|
+
else if (node.kind === "removed") removed.add(node.id);
|
|
967
|
+
else updated.add(node.id);
|
|
968
|
+
continue;
|
|
79
969
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
updated.add(id);
|
|
970
|
+
const result = { kind: "reset" };
|
|
971
|
+
cache.set(key, result);
|
|
972
|
+
return result;
|
|
84
973
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
added,
|
|
90
|
-
removed,
|
|
91
|
-
updated,
|
|
92
|
-
orderChanged
|
|
93
|
-
};
|
|
94
|
-
cache.set(key, result);
|
|
95
|
-
return result;
|
|
974
|
+
if (!contains(at, change.at)) continue;
|
|
975
|
+
if (change.at.length === at.length) continue;
|
|
976
|
+
const id = change.at[at.length];
|
|
977
|
+
updated.add(id);
|
|
96
978
|
}
|
|
979
|
+
added.forEach((id) => updated.delete(id));
|
|
980
|
+
removed.forEach((id) => updated.delete(id));
|
|
981
|
+
const result = {
|
|
982
|
+
kind: "incremental",
|
|
983
|
+
added,
|
|
984
|
+
removed,
|
|
985
|
+
updated,
|
|
986
|
+
orderChanged
|
|
987
|
+
};
|
|
988
|
+
cache.set(key, result);
|
|
989
|
+
return result;
|
|
97
990
|
};
|
|
98
|
-
|
|
991
|
+
return Object.freeze({
|
|
99
992
|
kind: reset ? "reset" : "incremental",
|
|
100
|
-
affects: (pick) =>
|
|
101
|
-
collection: (pick) =>
|
|
993
|
+
affects: (pick) => affects(require_advanced.compilePath(schema, "value", pick)),
|
|
994
|
+
collection: (pick) => collection(require_advanced.compilePath(schema, "collection", pick))
|
|
102
995
|
});
|
|
103
|
-
|
|
104
|
-
|
|
996
|
+
};
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region core/src/runtime/contract.ts
|
|
999
|
+
var DocumentReentrancyError = class extends Error {
|
|
1000
|
+
constructor() {
|
|
1001
|
+
super("Document writes cannot be re-entered during an update or notification.");
|
|
1002
|
+
this.name = "DocumentReentrancyError";
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
var DocumentDisposedError = class extends Error {
|
|
1006
|
+
constructor() {
|
|
1007
|
+
super("Doxum runtime has been disposed.");
|
|
1008
|
+
this.name = "DocumentDisposedError";
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var TransactionRejected = class extends Error {
|
|
1012
|
+
issues;
|
|
1013
|
+
constructor(input) {
|
|
1014
|
+
super("Document update rejected.");
|
|
1015
|
+
this.name = "TransactionRejected";
|
|
1016
|
+
const issues = Array.isArray(input) ? input : [input];
|
|
1017
|
+
this.issues = Object.freeze(issues.map((issue) => Object.freeze({
|
|
1018
|
+
code: issue.code,
|
|
1019
|
+
message: issue.message,
|
|
1020
|
+
source: "application",
|
|
1021
|
+
...issue.address === void 0 ? {} : { address: Object.freeze([...issue.address]) }
|
|
1022
|
+
})));
|
|
1023
|
+
}
|
|
105
1024
|
};
|
|
106
1025
|
//#endregion
|
|
107
1026
|
//#region core/src/history.ts
|
|
@@ -192,7 +1111,7 @@ const createHistory = (input) => {
|
|
|
192
1111
|
current: () => state,
|
|
193
1112
|
revision: () => revision,
|
|
194
1113
|
subscribe: (listener) => {
|
|
195
|
-
if (disposed) throw new
|
|
1114
|
+
if (disposed) throw new DocumentDisposedError();
|
|
196
1115
|
listeners.add(listener);
|
|
197
1116
|
return () => {
|
|
198
1117
|
listeners.delete(listener);
|
|
@@ -274,25 +1193,18 @@ const createHistory = (input) => {
|
|
|
274
1193
|
const installMember = (parent, key, present, value) => {
|
|
275
1194
|
if (Array.isArray(parent)) {
|
|
276
1195
|
const index = Number(key);
|
|
277
|
-
if (present) if (index < 0)
|
|
1196
|
+
if (present) if (index < 0) require_advanced.insert(parent, parent.length, value);
|
|
278
1197
|
else parent[index] = value;
|
|
279
|
-
else if (index >= 0)
|
|
280
|
-
} else
|
|
281
|
-
else if (Object.hasOwn(parent, key)) parent[key] = value;
|
|
282
|
-
else Object.defineProperty(parent, key, {
|
|
283
|
-
value,
|
|
284
|
-
writable: true,
|
|
285
|
-
configurable: true,
|
|
286
|
-
enumerable: true
|
|
287
|
-
});
|
|
1198
|
+
else if (index >= 0) require_advanced.remove(parent, index);
|
|
1199
|
+
} else require_context.installOwn(parent, key, present, value);
|
|
288
1200
|
};
|
|
289
1201
|
const orderOf = (node, value) => {
|
|
290
|
-
if (node.kind === "list") return
|
|
1202
|
+
if (node.kind === "list") return require_advanced.toArray(require_advanced.indexedKeys(value, node.keyOf));
|
|
291
1203
|
if (node.kind === "table") return [...value.ids];
|
|
292
1204
|
throw new Error("Order requires a table or list.");
|
|
293
1205
|
};
|
|
294
1206
|
const installOrder = (node, value, order) => {
|
|
295
|
-
if (node.kind === "list")
|
|
1207
|
+
if (node.kind === "list") require_advanced.install(value, node.keyOf, order);
|
|
296
1208
|
else if (node.kind === "table") value.ids = [...order];
|
|
297
1209
|
else throw new Error("Order requires a table or list.");
|
|
298
1210
|
};
|
|
@@ -313,9 +1225,9 @@ const forgetMember = (group, key) => {
|
|
|
313
1225
|
const emptyGroup = (group) => !group.order && (group.layout === "fixed" ? !group.members.some(Boolean) : !group.members?.size);
|
|
314
1226
|
const transition = (node, key, beforePresent, before, afterPresent, after) => {
|
|
315
1227
|
if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
|
|
316
|
-
if (node.kind !== "field" && beforePresent === afterPresent &&
|
|
317
|
-
const value = afterPresent ? node.kind === "field" ? after :
|
|
318
|
-
|
|
1228
|
+
if (node.kind !== "field" && beforePresent === afterPresent && require_advanced.equalValue(node, before, after)) return;
|
|
1229
|
+
const value = afterPresent ? node.kind === "field" ? after : require_advanced.copyValue(node, after) : void 0;
|
|
1230
|
+
require_advanced.profile.recorder("transitions");
|
|
319
1231
|
return !beforePresent ? {
|
|
320
1232
|
key,
|
|
321
1233
|
kind: "added",
|
|
@@ -356,7 +1268,7 @@ const diffMember = (changes, members, node, at, key, beforePresent, before, afte
|
|
|
356
1268
|
const left = before, right = after;
|
|
357
1269
|
const childAt = at.concat(key);
|
|
358
1270
|
const children = [];
|
|
359
|
-
for (const [child, definition] of
|
|
1271
|
+
for (const [child, definition] of require_advanced.compiledShape(node, before).members) diffMember(changes, children, definition.node, childAt, child, Object.hasOwn(left, child), left[child], Object.hasOwn(right, child), right[child]);
|
|
360
1272
|
publishMembers(changes, childAt, children);
|
|
361
1273
|
return;
|
|
362
1274
|
}
|
|
@@ -369,8 +1281,8 @@ const diffMember = (changes, members, node, at, key, beforePresent, before, afte
|
|
|
369
1281
|
for (const id of Object.keys(left)) diffMember(changes, children, node.value, childAt, id, true, left[id], Object.hasOwn(right, id), right[id]);
|
|
370
1282
|
for (const id of Object.keys(right)) if (!Object.hasOwn(left, id)) diffMember(changes, children, node.value, childAt, id, false, void 0, true, right[id]);
|
|
371
1283
|
let order;
|
|
372
|
-
if (node.kind === "table" && !
|
|
373
|
-
|
|
1284
|
+
if (node.kind === "table" && !require_advanced.equal(a.ids, b.ids)) {
|
|
1285
|
+
require_advanced.profile.recorder("publishedOrderItems", b.ids.length);
|
|
374
1286
|
order = {
|
|
375
1287
|
before: a.ids,
|
|
376
1288
|
after: [...b.ids]
|
|
@@ -387,26 +1299,26 @@ const restore = (fact, schema, root, skip = 0) => {
|
|
|
387
1299
|
const at = skip ? fact.at.slice(skip) : fact.at;
|
|
388
1300
|
if (fact.kind === "member") {
|
|
389
1301
|
const member = fact.member;
|
|
390
|
-
const value = member.present ?
|
|
1302
|
+
const value = member.present ? require_advanced.copyValue(member.node, member.value) : void 0;
|
|
391
1303
|
if (!at.length) return value;
|
|
392
|
-
const location =
|
|
1304
|
+
const location = resolveLocated(schema, root, at);
|
|
393
1305
|
if (!location) throw new Error("Cannot restore an unresolved member.");
|
|
394
1306
|
installMember(location.parent, location.key, member.present, value);
|
|
395
1307
|
return root;
|
|
396
1308
|
}
|
|
397
|
-
const location =
|
|
1309
|
+
const location = resolveValue(schema, root, at);
|
|
398
1310
|
if (!location) throw new Error("Cannot restore an unresolved subtree.");
|
|
399
1311
|
if (fact.kind === "order") installOrder(location.node, location.value, fact.group.order);
|
|
400
1312
|
else {
|
|
401
1313
|
const tree = location.value;
|
|
402
1314
|
if (fact.before === null) delete tree.rootId;
|
|
403
1315
|
else tree.rootId = fact.before;
|
|
404
|
-
for (const [id, node] of fact.nodes) installMember(tree.nodes, id, node !== void 0, node &&
|
|
1316
|
+
for (const [id, node] of fact.nodes) installMember(tree.nodes, id, node !== void 0, node && require_advanced.copyTreeNode(node));
|
|
405
1317
|
}
|
|
406
1318
|
return root;
|
|
407
1319
|
};
|
|
408
1320
|
const reconstructBefore = (node, current, records, offset) => {
|
|
409
|
-
let before =
|
|
1321
|
+
let before = require_advanced.copyValue(node, current);
|
|
410
1322
|
for (let i = records.length - 1; i >= 0; i--) if (records[i].kind !== "order") before = restore(records[i], node, before, offset);
|
|
411
1323
|
for (const record of records) if (record.kind === "order") before = restore(record, node, before, offset);
|
|
412
1324
|
return before;
|
|
@@ -415,7 +1327,7 @@ const sealMembers = (fact, changes) => {
|
|
|
415
1327
|
const members = [];
|
|
416
1328
|
for (const member of fact.members?.values() ?? []) {
|
|
417
1329
|
if (!member) continue;
|
|
418
|
-
const key =
|
|
1330
|
+
const key = memberKey(fact, member.key);
|
|
419
1331
|
const present = Object.hasOwn(fact.parent, key);
|
|
420
1332
|
const value = fact.parent[key];
|
|
421
1333
|
if (member.node.kind === "field") {
|
|
@@ -425,10 +1337,10 @@ const sealMembers = (fact, changes) => {
|
|
|
425
1337
|
}
|
|
426
1338
|
let order;
|
|
427
1339
|
if (fact.order) {
|
|
428
|
-
const current = fact.node.kind === "list" ?
|
|
429
|
-
if (!
|
|
1340
|
+
const current = fact.node.kind === "list" ? require_advanced.indexedKeys(fact.value, fact.node.keyOf) : fact.value.ids;
|
|
1341
|
+
if (!require_advanced.equal(fact.order, current)) {
|
|
430
1342
|
const after = orderOf(fact.node, fact.value);
|
|
431
|
-
|
|
1343
|
+
require_advanced.profile.recorder("publishedOrderItems", after.length);
|
|
432
1344
|
order = {
|
|
433
1345
|
before: fact.order,
|
|
434
1346
|
after
|
|
@@ -437,19 +1349,19 @@ const sealMembers = (fact, changes) => {
|
|
|
437
1349
|
}
|
|
438
1350
|
publishMembers(changes, fact.at, members, order);
|
|
439
1351
|
};
|
|
440
|
-
const sealTree = (fact,
|
|
441
|
-
const location =
|
|
1352
|
+
const sealTree = (fact, canonical, changes) => {
|
|
1353
|
+
const location = resolveValue(canonical.schema, canonical.document, fact.at);
|
|
442
1354
|
const tree = location?.value;
|
|
443
1355
|
if ((location?.node)?.kind !== "tree") throw new Error("Tree schema disappeared before sealing.");
|
|
444
1356
|
const nodes = [];
|
|
445
1357
|
for (const [id, before] of fact.nodes) {
|
|
446
1358
|
const after = Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
447
1359
|
if (!before && !after) continue;
|
|
448
|
-
if (before && after &&
|
|
1360
|
+
if (before && after && require_advanced.equalTreeNode(before, after)) continue;
|
|
449
1361
|
nodes.push(!before ? {
|
|
450
1362
|
id,
|
|
451
1363
|
kind: "added",
|
|
452
|
-
after:
|
|
1364
|
+
after: require_advanced.copyTreeNode(after)
|
|
453
1365
|
} : !after ? {
|
|
454
1366
|
id,
|
|
455
1367
|
kind: "removed",
|
|
@@ -458,7 +1370,7 @@ const sealTree = (fact, state, changes) => {
|
|
|
458
1370
|
id,
|
|
459
1371
|
kind: "updated",
|
|
460
1372
|
before,
|
|
461
|
-
after:
|
|
1373
|
+
after: require_advanced.copyTreeNode(after)
|
|
462
1374
|
});
|
|
463
1375
|
}
|
|
464
1376
|
const after = tree.rootId ?? null;
|
|
@@ -481,12 +1393,12 @@ var ChangeRecorder = class {
|
|
|
481
1393
|
this.state = state;
|
|
482
1394
|
}
|
|
483
1395
|
indexGroups() {
|
|
484
|
-
const index = this.index ??= new
|
|
1396
|
+
const index = this.index ??= new require_driver.AddressIndex();
|
|
485
1397
|
if (!this.indexedGroups) {
|
|
486
1398
|
this.indexedGroups = true;
|
|
487
1399
|
for (const fact of this.facts) if (fact.kind === "members") {
|
|
488
1400
|
index.add(fact.at, fact);
|
|
489
|
-
|
|
1401
|
+
require_advanced.profile.recorder("indexedGroups");
|
|
490
1402
|
}
|
|
491
1403
|
}
|
|
492
1404
|
return index;
|
|
@@ -511,7 +1423,7 @@ var ChangeRecorder = class {
|
|
|
511
1423
|
}
|
|
512
1424
|
return;
|
|
513
1425
|
}
|
|
514
|
-
if (fact.order &&
|
|
1426
|
+
if (fact.order && contains(at, fact.at)) children.push({
|
|
515
1427
|
kind: "order",
|
|
516
1428
|
at: fact.at,
|
|
517
1429
|
group: fact
|
|
@@ -519,14 +1431,14 @@ var ChangeRecorder = class {
|
|
|
519
1431
|
for (const member of fact.members?.values() ?? []) {
|
|
520
1432
|
if (!member) continue;
|
|
521
1433
|
const memberAt = fact.at.concat(member.key);
|
|
522
|
-
if (
|
|
1434
|
+
if (contains(at, memberAt)) children.push({
|
|
523
1435
|
kind: "member",
|
|
524
1436
|
at: memberAt,
|
|
525
1437
|
group: fact,
|
|
526
1438
|
member
|
|
527
1439
|
});
|
|
528
1440
|
}
|
|
529
|
-
} else if (
|
|
1441
|
+
} else if (contains(at, fact.at)) children.push(fact);
|
|
530
1442
|
})(at);
|
|
531
1443
|
if (present) value = reconstructBefore(node, value, children, at.length);
|
|
532
1444
|
this.releaseCovered(children);
|
|
@@ -535,7 +1447,7 @@ var ChangeRecorder = class {
|
|
|
535
1447
|
releaseCovered(children) {
|
|
536
1448
|
let changedGroups;
|
|
537
1449
|
for (const child of children) {
|
|
538
|
-
|
|
1450
|
+
require_advanced.profile.recorder("absorbed");
|
|
539
1451
|
if (child.kind === "member") {
|
|
540
1452
|
const { group, member } = child;
|
|
541
1453
|
forgetMember(group, member.key);
|
|
@@ -551,11 +1463,11 @@ var ChangeRecorder = class {
|
|
|
551
1463
|
this.facts.add(fact);
|
|
552
1464
|
if (fact.kind === "members") {
|
|
553
1465
|
this.groups.set(fact.parent, fact);
|
|
554
|
-
|
|
1466
|
+
require_advanced.profile.recorder("groups");
|
|
555
1467
|
if (!this.indexedGroups) return;
|
|
556
|
-
|
|
1468
|
+
require_advanced.profile.recorder("indexedGroups");
|
|
557
1469
|
}
|
|
558
|
-
(this.index ??= new
|
|
1470
|
+
(this.index ??= new require_driver.AddressIndex()).add(fact.at, fact);
|
|
559
1471
|
}
|
|
560
1472
|
unregister(fact) {
|
|
561
1473
|
this.facts.delete(fact);
|
|
@@ -583,14 +1495,14 @@ var ChangeRecorder = class {
|
|
|
583
1495
|
if (group.layout === "dynamic") (group.members ??= /* @__PURE__ */ new Map()).set(key, member);
|
|
584
1496
|
else if (definition.kind === "fixed") group.members[definition.slot] = member;
|
|
585
1497
|
else throw new Error("A fixed member group requires a fixed schema member.");
|
|
586
|
-
|
|
1498
|
+
require_advanced.profile.recorder("facts");
|
|
587
1499
|
}
|
|
588
1500
|
reset() {
|
|
589
1501
|
if (this.resetBefore) return;
|
|
590
1502
|
const value = this.absorb([], this.state.schema, this.state.document, true);
|
|
591
1503
|
this.resetBefore = { value };
|
|
592
1504
|
this.groups = /* @__PURE__ */ new WeakMap();
|
|
593
|
-
|
|
1505
|
+
require_advanced.profile.recorder("facts");
|
|
594
1506
|
}
|
|
595
1507
|
createGroup(container) {
|
|
596
1508
|
const group = container.layout.kind === "fixed" ? {
|
|
@@ -615,18 +1527,18 @@ var ChangeRecorder = class {
|
|
|
615
1527
|
return group;
|
|
616
1528
|
}
|
|
617
1529
|
order(container) {
|
|
618
|
-
|
|
1530
|
+
require_advanced.profile.recorder("orderCaptures");
|
|
619
1531
|
let group = this.groups.get(container.parent);
|
|
620
1532
|
if (group?.order || this.covered(container.at)) return;
|
|
621
1533
|
group ??= this.createGroup(container);
|
|
622
1534
|
group.order = orderOf(container.node, container.value);
|
|
623
|
-
|
|
624
|
-
|
|
1535
|
+
require_advanced.profile.recorder("orderSnapshots");
|
|
1536
|
+
require_advanced.profile.recorder("orderItems", group.order.length);
|
|
625
1537
|
}
|
|
626
1538
|
tree(container, id) {
|
|
627
1539
|
const { at, value: tree } = container;
|
|
628
1540
|
if (this.covered(at)) return;
|
|
629
|
-
const index = this.index ??= new
|
|
1541
|
+
const index = this.index ??= new require_driver.AddressIndex();
|
|
630
1542
|
let fact;
|
|
631
1543
|
for (const item of index.exact(at) ?? []) if (item.kind === "tree") fact = item;
|
|
632
1544
|
if (!fact) {
|
|
@@ -639,8 +1551,8 @@ var ChangeRecorder = class {
|
|
|
639
1551
|
this.register(fact);
|
|
640
1552
|
}
|
|
641
1553
|
if (id !== void 0 && !fact.nodes.has(id)) {
|
|
642
|
-
|
|
643
|
-
fact.nodes.set(id, Object.hasOwn(tree.nodes, id) ?
|
|
1554
|
+
require_advanced.profile.recorder("treeNodes");
|
|
1555
|
+
fact.nodes.set(id, Object.hasOwn(tree.nodes, id) ? require_advanced.copyTreeNode(tree.nodes[id]) : void 0);
|
|
644
1556
|
}
|
|
645
1557
|
}
|
|
646
1558
|
rollback() {
|
|
@@ -653,7 +1565,7 @@ var ChangeRecorder = class {
|
|
|
653
1565
|
const fact = facts[i];
|
|
654
1566
|
if (fact.kind === "members") for (const member of fact.members?.values() ?? []) {
|
|
655
1567
|
if (!member) continue;
|
|
656
|
-
installMember(fact.parent,
|
|
1568
|
+
installMember(fact.parent, memberKey(fact, member.key), member.present, member.present ? require_advanced.copyValue(member.node, member.value) : void 0);
|
|
657
1569
|
}
|
|
658
1570
|
else if (fact.kind === "tree") restore(fact, this.state.schema, this.state.document);
|
|
659
1571
|
}
|
|
@@ -661,19 +1573,19 @@ var ChangeRecorder = class {
|
|
|
661
1573
|
}
|
|
662
1574
|
seal() {
|
|
663
1575
|
if (this.resetBefore) {
|
|
664
|
-
if (
|
|
665
|
-
|
|
1576
|
+
if (require_advanced.equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
|
|
1577
|
+
require_advanced.profile.recorder("sealed");
|
|
666
1578
|
return require_driver.sealChanges([{
|
|
667
1579
|
kind: "reset",
|
|
668
1580
|
before: this.resetBefore.value,
|
|
669
|
-
after:
|
|
1581
|
+
after: require_advanced.copyValue(this.state.schema, this.state.document)
|
|
670
1582
|
}]);
|
|
671
1583
|
}
|
|
672
1584
|
const changes = [];
|
|
673
1585
|
for (const fact of this.facts) if (fact.kind === "members") sealMembers(fact, changes);
|
|
674
1586
|
else sealTree(fact, this.state, changes);
|
|
675
1587
|
const result = require_driver.sealChanges(changes);
|
|
676
|
-
|
|
1588
|
+
require_advanced.profile.recorder("sealed", result.changes.length);
|
|
677
1589
|
return result;
|
|
678
1590
|
}
|
|
679
1591
|
};
|
|
@@ -689,25 +1601,25 @@ var MutationSession = class {
|
|
|
689
1601
|
constructor(state) {
|
|
690
1602
|
this.state = state;
|
|
691
1603
|
this.recorder = new ChangeRecorder(state);
|
|
692
|
-
this.resolver =
|
|
1604
|
+
this.resolver = createAddressResolver(state.schema, state.document);
|
|
693
1605
|
this.resolvedRoot = state.document;
|
|
694
1606
|
}
|
|
695
1607
|
validate(node, value, at) {
|
|
696
|
-
const
|
|
697
|
-
if (
|
|
1608
|
+
const failure = require_advanced.checkValue(node, value, at);
|
|
1609
|
+
if (failure) require_driver.invalidValue(failure);
|
|
698
1610
|
}
|
|
699
1611
|
resolveContainer(at) {
|
|
700
|
-
return this.resolver.container(this.identity, this.generation, at) ??
|
|
1612
|
+
return this.resolver.container(this.identity, this.generation, at) ?? require_driver.fail(at, "invalid-address", "Container does not exist.");
|
|
701
1613
|
}
|
|
702
1614
|
/** Bind access-resolved canonical facts without walking the address again. */
|
|
703
1615
|
bind(at, node, value) {
|
|
704
|
-
return
|
|
1616
|
+
return resolveContainer(this.identity, this.generation, at, node, value) ?? require_driver.fail(at, "invalid-address", "Container does not exist.");
|
|
705
1617
|
}
|
|
706
1618
|
bindTree(at, node, value) {
|
|
707
|
-
return
|
|
1619
|
+
return resolveTreeContainer(this.identity, this.generation, at, node, value) ?? require_driver.fail(at, "invalid-tree", "Expected a tree.");
|
|
708
1620
|
}
|
|
709
1621
|
resolveTree(at) {
|
|
710
|
-
const location =
|
|
1622
|
+
const location = resolveValue(this.state.schema, this.state.document, at);
|
|
711
1623
|
return this.bindTree(at, location?.node, location?.value);
|
|
712
1624
|
}
|
|
713
1625
|
replace(at, value) {
|
|
@@ -715,7 +1627,7 @@ var MutationSession = class {
|
|
|
715
1627
|
this.validate(this.state.schema, value, at);
|
|
716
1628
|
if (Object.is(this.state.document, value)) return;
|
|
717
1629
|
this.recorder.reset();
|
|
718
|
-
this.state.document =
|
|
1630
|
+
this.state.document = require_advanced.copyValue(this.state.schema, value);
|
|
719
1631
|
this.invalidate();
|
|
720
1632
|
return;
|
|
721
1633
|
}
|
|
@@ -724,24 +1636,24 @@ var MutationSession = class {
|
|
|
724
1636
|
assignMember(container, key, value) {
|
|
725
1637
|
container = this.refresh(container);
|
|
726
1638
|
const member = this.definition(container, key);
|
|
727
|
-
if (container.layout.kind === "fixed" && member.node.kind !== "field" && member.node.kind !== "variant" && !member.node.optional) return
|
|
728
|
-
this.writeLocatedMember(container, key, member,
|
|
1639
|
+
if (container.layout.kind === "fixed" && member.node.kind !== "field" && member.node.kind !== "variant" && !member.node.optional) return require_driver.fail(container.at.concat(key), "invalid-value", "Replace fields, variants, or collection entries; edit object members individually.");
|
|
1640
|
+
this.writeLocatedMember(container, key, member, memberKey(container, key), value, "set");
|
|
729
1641
|
}
|
|
730
1642
|
removeMember(container, key) {
|
|
731
1643
|
this.writeMember(this.refresh(container), key, void 0, "remove");
|
|
732
1644
|
}
|
|
733
1645
|
refresh(container) {
|
|
734
|
-
if (container.owner !== this.identity) return
|
|
1646
|
+
if (container.owner !== this.identity) return require_driver.fail(container.at, "invalid-address", "Container belongs to another mutation session.");
|
|
735
1647
|
return container.generation === this.generation ? container : this.resolveContainer(container.at);
|
|
736
1648
|
}
|
|
737
1649
|
definition(container, key) {
|
|
738
1650
|
const layout = container.layout;
|
|
739
1651
|
const member = layout.kind === "dynamic" ? layout.entry : layout.members.get(key);
|
|
740
|
-
if (!member) return
|
|
1652
|
+
if (!member) return require_driver.fail(container.at.concat(key), "invalid-address", "Address does not exist.");
|
|
741
1653
|
return member;
|
|
742
1654
|
}
|
|
743
1655
|
/** Consume a current command-local container; bulk writes retain its storage. */
|
|
744
|
-
writeMember(container, key, value, operation, physicalKey =
|
|
1656
|
+
writeMember(container, key, value, operation, physicalKey = memberKey(container, key)) {
|
|
745
1657
|
const member = this.definition(container, key);
|
|
746
1658
|
return this.writeLocatedMember(container, key, member, physicalKey, value, operation);
|
|
747
1659
|
}
|
|
@@ -750,27 +1662,27 @@ var MutationSession = class {
|
|
|
750
1662
|
const parentNode = container.node;
|
|
751
1663
|
const node = member.node;
|
|
752
1664
|
const present = operation === "set";
|
|
753
|
-
if (parentNode.kind === "variant" && parentNode.tag === key) return
|
|
1665
|
+
if (parentNode.kind === "variant" && parentNode.tag === key) return require_driver.fail(container.at.concat(key), "invalid-value", "Variant discriminants are read-only.");
|
|
754
1666
|
const entry = container.layout.kind === "dynamic";
|
|
755
|
-
if (!present && !node.optional && !entry) return
|
|
1667
|
+
if (!present && !node.optional && !entry) return require_driver.fail(container.at.concat(key), "required-field", "A required value cannot be removed.");
|
|
756
1668
|
const existed = Object.hasOwn(container.parent, physicalKey);
|
|
757
1669
|
const previous = container.parent[physicalKey];
|
|
758
1670
|
if (existed === present && (!present || Object.is(previous, value))) return false;
|
|
759
1671
|
if (parentNode.kind === "map" || parentNode.kind === "table") {
|
|
760
|
-
const
|
|
761
|
-
if (
|
|
1672
|
+
const failure = require_advanced.checkKey(parentNode.key, key, []);
|
|
1673
|
+
if (failure) return require_driver.fail(container.at.concat(key, ...failure.address), "invalid-key", failure.message);
|
|
762
1674
|
}
|
|
763
1675
|
if (present) {
|
|
764
1676
|
if (node.kind === "field") {
|
|
765
|
-
const
|
|
766
|
-
if (
|
|
1677
|
+
const failure = require_advanced.checkValue(node, value, []);
|
|
1678
|
+
if (failure) return require_driver.invalidValue(failure, container.at.concat(key, ...failure.address));
|
|
767
1679
|
} else this.validate(node, value, container.at.concat(key));
|
|
768
|
-
if (parentNode.kind === "list" && parentNode.keyOf(value) !== key) return
|
|
1680
|
+
if (parentNode.kind === "list" && parentNode.keyOf(value) !== key) return require_driver.fail(container.at.concat(key), "invalid-list-key", "Replacing an item must retain its addressed key.");
|
|
769
1681
|
}
|
|
770
1682
|
const membershipChanged = entry && existed !== present;
|
|
771
1683
|
if (membershipChanged && (parentNode.kind === "list" || parentNode.kind === "table")) this.recorder.order(container);
|
|
772
1684
|
this.recorder.member(container, key, member, physicalKey);
|
|
773
|
-
const next = present ? node.kind === "field" ? value :
|
|
1685
|
+
const next = present ? node.kind === "field" ? value : require_advanced.copyValue(node, value) : void 0;
|
|
774
1686
|
installMember(container.parent, physicalKey, present, next);
|
|
775
1687
|
if (node.kind !== "field" || membershipChanged) this.invalidate();
|
|
776
1688
|
return membershipChanged;
|
|
@@ -779,7 +1691,7 @@ var MutationSession = class {
|
|
|
779
1691
|
this.generation++;
|
|
780
1692
|
if (this.resolvedRoot !== this.state.document) {
|
|
781
1693
|
this.resolvedRoot = this.state.document;
|
|
782
|
-
this.resolver =
|
|
1694
|
+
this.resolver = createAddressResolver(this.state.schema, this.state.document);
|
|
783
1695
|
} else this.resolver.invalidate();
|
|
784
1696
|
}
|
|
785
1697
|
finish() {
|
|
@@ -799,7 +1711,7 @@ function apply(session, changes, direction) {
|
|
|
799
1711
|
const container = session.resolveContainer(change.at);
|
|
800
1712
|
const { node, value: current } = container;
|
|
801
1713
|
if (change.order) {
|
|
802
|
-
if (node.kind !== "table" && node.kind !== "list") return
|
|
1714
|
+
if (node.kind !== "table" && node.kind !== "list") return require_driver.fail(change.at, "invalid-changes", "Order requires an ordered container.");
|
|
803
1715
|
session.recorder.order(container);
|
|
804
1716
|
}
|
|
805
1717
|
let membershipChanged = false;
|
|
@@ -810,12 +1722,12 @@ function apply(session, changes, direction) {
|
|
|
810
1722
|
}
|
|
811
1723
|
if (change.order) {
|
|
812
1724
|
const keys = node.kind === "table" ? Object.keys(container.parent) : orderOf(node, current);
|
|
813
|
-
if (!
|
|
1725
|
+
if (!require_advanced.matches(change.order[side], keys)) return require_driver.fail(change.at, "invalid-changes", "Order must contain exactly the resulting keys.");
|
|
814
1726
|
installOrder(node, current, change.order[side]);
|
|
815
1727
|
session.invalidate();
|
|
816
1728
|
} else if (membershipChanged && node.kind === "table") {
|
|
817
1729
|
const ids = current.ids;
|
|
818
|
-
if (!
|
|
1730
|
+
if (!require_advanced.matches(ids, Object.keys(container.parent))) return require_driver.fail(change.at, "invalid-changes", "Table membership changes require a matching order.");
|
|
819
1731
|
}
|
|
820
1732
|
} else {
|
|
821
1733
|
const container = session.resolveTree(change.at);
|
|
@@ -828,57 +1740,220 @@ function apply(session, changes, direction) {
|
|
|
828
1740
|
for (const item of change.nodes) {
|
|
829
1741
|
const next = side === "after" ? item.kind !== "removed" ? item.after : void 0 : item.kind !== "added" ? item.before : void 0;
|
|
830
1742
|
if (next !== void 0) {
|
|
831
|
-
const
|
|
832
|
-
if (
|
|
1743
|
+
const diagnostic = require_advanced.checkTreePayload(node.value, next, change.at.concat(item.id));
|
|
1744
|
+
if (diagnostic) require_driver.invalidValue(diagnostic);
|
|
833
1745
|
}
|
|
834
|
-
installMember(current.nodes, item.id, next !== void 0, next &&
|
|
1746
|
+
installMember(current.nodes, item.id, next !== void 0, next && require_advanced.copyTreeNode(next));
|
|
835
1747
|
}
|
|
836
|
-
if (!
|
|
1748
|
+
if (!require_context.validate(current)) return require_driver.fail(change.at, "invalid-tree", "Invalid tree structure.");
|
|
837
1749
|
session.invalidate();
|
|
838
1750
|
}
|
|
839
1751
|
}
|
|
840
1752
|
//#endregion
|
|
841
|
-
//#region core/src/runtime.ts
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const state = {
|
|
847
|
-
schema: input.schema,
|
|
848
|
-
document: require_access.copyValue(input.schema, input.initial),
|
|
849
|
-
disposed: false
|
|
1753
|
+
//#region core/src/runtime/notification.ts
|
|
1754
|
+
const attachProjection = (notification, processor) => {
|
|
1755
|
+
const entry = {
|
|
1756
|
+
processor,
|
|
1757
|
+
active: true
|
|
850
1758
|
};
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
if (
|
|
1759
|
+
notification.processors.push(entry);
|
|
1760
|
+
return () => {
|
|
1761
|
+
if (!entry.active) return;
|
|
1762
|
+
entry.active = false;
|
|
1763
|
+
if (!notification.notifying) {
|
|
1764
|
+
const index = notification.processors.indexOf(entry);
|
|
1765
|
+
if (index >= 0) notification.processors.splice(index, 1);
|
|
1766
|
+
}
|
|
856
1767
|
};
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1768
|
+
};
|
|
1769
|
+
const addRoot = (notification, listener) => {
|
|
1770
|
+
const entry = {
|
|
1771
|
+
listener,
|
|
1772
|
+
active: true
|
|
860
1773
|
};
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1774
|
+
notification.root.add(entry);
|
|
1775
|
+
return () => {
|
|
1776
|
+
entry.active = false;
|
|
1777
|
+
if (!notification.notifying) notification.root.delete(entry);
|
|
1778
|
+
else notification.cleanups.add(() => {
|
|
1779
|
+
notification.root.delete(entry);
|
|
1780
|
+
});
|
|
1781
|
+
};
|
|
1782
|
+
};
|
|
1783
|
+
const addFiltered = (notification, targets, listener) => {
|
|
1784
|
+
const entry = {
|
|
1785
|
+
targets: Object.freeze(targets.slice()),
|
|
1786
|
+
listener,
|
|
1787
|
+
active: true
|
|
1788
|
+
};
|
|
1789
|
+
notification.filtered.add(entry);
|
|
1790
|
+
entry.targets.forEach((value) => notification.index.add(value, entry));
|
|
1791
|
+
const remove = () => {
|
|
1792
|
+
notification.filtered.delete(entry);
|
|
1793
|
+
entry.targets.forEach((value) => notification.index.delete(value, entry));
|
|
1794
|
+
};
|
|
1795
|
+
return () => {
|
|
1796
|
+
if (!entry.active) return;
|
|
1797
|
+
entry.active = false;
|
|
1798
|
+
if (notification.notifying) notification.cleanups.add(remove);
|
|
1799
|
+
else remove();
|
|
1800
|
+
};
|
|
1801
|
+
};
|
|
1802
|
+
const publishState = (notification, commit, afterSettle) => {
|
|
1803
|
+
notification.notifying = true;
|
|
1804
|
+
const errors = [];
|
|
1805
|
+
const call = (listener) => {
|
|
1806
|
+
try {
|
|
1807
|
+
listener(commit);
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
errors.push(Object.freeze({
|
|
1810
|
+
phase: "listener",
|
|
1811
|
+
error
|
|
1812
|
+
}));
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
try {
|
|
1816
|
+
const processorCount = notification.processors.length;
|
|
1817
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1818
|
+
const entry = notification.processors[index];
|
|
1819
|
+
if (!entry?.active) continue;
|
|
1820
|
+
try {
|
|
1821
|
+
entry.processor.capture(commit);
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
errors.push(Object.freeze({
|
|
1824
|
+
phase: "processor",
|
|
1825
|
+
error
|
|
1826
|
+
}));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1830
|
+
const entry = notification.processors[index];
|
|
1831
|
+
if (!entry?.active) continue;
|
|
1832
|
+
try {
|
|
1833
|
+
entry.processor.settle();
|
|
1834
|
+
} catch (error) {
|
|
1835
|
+
errors.push(Object.freeze({
|
|
1836
|
+
phase: "processor",
|
|
1837
|
+
error
|
|
1838
|
+
}));
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1842
|
+
const entry = notification.processors[index];
|
|
1843
|
+
if (!entry?.active) continue;
|
|
1844
|
+
try {
|
|
1845
|
+
errors.push(...entry.processor.flush());
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
errors.push(Object.freeze({
|
|
1848
|
+
phase: "flush",
|
|
1849
|
+
error
|
|
1850
|
+
}));
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (afterSettle) errors.push(...afterSettle());
|
|
1854
|
+
const candidates = notification.candidates;
|
|
1855
|
+
candidates.clear();
|
|
1856
|
+
if (notification.filtered.size) notification.index.collect(commit.changes, (entry) => candidates.add(entry));
|
|
1857
|
+
candidates.forEach((entry) => {
|
|
1858
|
+
if (entry.active) call(entry.listener);
|
|
1859
|
+
});
|
|
1860
|
+
const rootSnapshot = notification.rootSnapshot;
|
|
1861
|
+
rootSnapshot.length = 0;
|
|
1862
|
+
notification.root.forEach((entry) => rootSnapshot.push(entry));
|
|
1863
|
+
for (const entry of rootSnapshot) if (entry.active) call(entry.listener);
|
|
1864
|
+
} finally {
|
|
1865
|
+
notification.notifying = false;
|
|
1866
|
+
let writeIndex = 0;
|
|
1867
|
+
for (const entry of notification.processors) if (entry.active) notification.processors[writeIndex++] = entry;
|
|
1868
|
+
notification.processors.length = writeIndex;
|
|
1869
|
+
notification.cleanups.forEach((cleanup) => cleanup());
|
|
1870
|
+
notification.cleanups.clear();
|
|
1871
|
+
}
|
|
1872
|
+
return Object.freeze(errors);
|
|
1873
|
+
};
|
|
1874
|
+
const disposeState = (notification) => {
|
|
1875
|
+
const attachments = notification.processors.slice();
|
|
1876
|
+
notification.root.clear();
|
|
1877
|
+
notification.filtered.clear();
|
|
1878
|
+
notification.index.clear();
|
|
1879
|
+
notification.cleanups.clear();
|
|
1880
|
+
notification.processors.length = 0;
|
|
1881
|
+
notification.candidates.clear();
|
|
1882
|
+
notification.rootSnapshot.length = 0;
|
|
1883
|
+
const errors = [];
|
|
1884
|
+
attachments.forEach((entry) => {
|
|
1885
|
+
if (!entry.active) return;
|
|
1886
|
+
try {
|
|
1887
|
+
entry.processor.dispose();
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
errors.push(error);
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
if (errors.length) throw new AggregateError(errors, "Projection disposal notification failed.");
|
|
1893
|
+
};
|
|
1894
|
+
const createNotificationCenter = (schema) => {
|
|
1895
|
+
const state = {
|
|
1896
|
+
root: /* @__PURE__ */ new Set(),
|
|
1897
|
+
filtered: /* @__PURE__ */ new Set(),
|
|
1898
|
+
index: new SubscriptionIndex(schema),
|
|
1899
|
+
cleanups: /* @__PURE__ */ new Set(),
|
|
1900
|
+
processors: [],
|
|
1901
|
+
candidates: /* @__PURE__ */ new Set(),
|
|
1902
|
+
rootSnapshot: [],
|
|
1903
|
+
notifying: false
|
|
1904
|
+
};
|
|
1905
|
+
return Object.freeze({
|
|
1906
|
+
subscribe: (listener) => addRoot(state, listener),
|
|
1907
|
+
subscribeTargets: (targets, listener) => addFiltered(state, targets, listener),
|
|
1908
|
+
attachProjection: (processor) => attachProjection(state, processor),
|
|
1909
|
+
publish: (commit, afterSettle) => publishState(state, commit, afterSettle),
|
|
1910
|
+
dispose: () => disposeState(state)
|
|
1911
|
+
});
|
|
1912
|
+
};
|
|
1913
|
+
//#endregion
|
|
1914
|
+
//#region core/src/runtime.ts
|
|
1915
|
+
const createDocument = (input) => {
|
|
1916
|
+
if (input.schema.kind !== "object") throw new TypeError("Document schema must be a root object node.");
|
|
1917
|
+
const invalid = require_advanced.checkValue(input.schema, input.initial);
|
|
1918
|
+
if (invalid) throw new require_advanced.ParseError(invalid);
|
|
1919
|
+
const state = {
|
|
1920
|
+
schema: input.schema,
|
|
1921
|
+
document: require_advanced.copyValue(input.schema, input.initial),
|
|
1922
|
+
disposed: false,
|
|
1923
|
+
projectionLocks: 0
|
|
1924
|
+
};
|
|
1925
|
+
let revision = 0, busy = false;
|
|
1926
|
+
const notifications = createNotificationCenter(input.schema);
|
|
1927
|
+
let runtime, context;
|
|
1928
|
+
const idle = () => {
|
|
1929
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
1930
|
+
if (busy || state.projectionLocks) throw new DocumentReentrancyError();
|
|
1931
|
+
};
|
|
1932
|
+
const writable = (intent) => {
|
|
1933
|
+
idle();
|
|
1934
|
+
require_driver.assertRuntimeWritable(runtime, intent);
|
|
1935
|
+
};
|
|
1936
|
+
const history = createHistory({
|
|
1937
|
+
capacity: input.history === false ? 0 : Math.max(0, input.history?.capacity ?? 100),
|
|
1938
|
+
revision: () => revision,
|
|
1939
|
+
apply: (changes, direction) => mutate({
|
|
1940
|
+
kind: "apply",
|
|
1941
|
+
source: "history"
|
|
1942
|
+
}, false, (session) => {
|
|
1943
|
+
for (const change of changes) apply(session, change, direction);
|
|
1944
|
+
}),
|
|
1945
|
+
assertIdle: idle,
|
|
1946
|
+
notify: (run) => {
|
|
1947
|
+
busy = true;
|
|
1948
|
+
try {
|
|
1949
|
+
return run();
|
|
1950
|
+
} finally {
|
|
1951
|
+
busy = false;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
const publish = (changes, source, recordHistory) => {
|
|
1956
|
+
const commit = {
|
|
882
1957
|
revision: ++revision,
|
|
883
1958
|
source,
|
|
884
1959
|
changes,
|
|
@@ -891,7 +1966,7 @@ const createDocument = (input) => {
|
|
|
891
1966
|
return {
|
|
892
1967
|
status: "committed",
|
|
893
1968
|
commit,
|
|
894
|
-
observerErrors:
|
|
1969
|
+
observerErrors: notifications.publish(commit, history.flush)
|
|
895
1970
|
};
|
|
896
1971
|
};
|
|
897
1972
|
function mutate(intent, recordHistory, execute) {
|
|
@@ -905,12 +1980,12 @@ const createDocument = (input) => {
|
|
|
905
1980
|
changes = session.finish();
|
|
906
1981
|
} catch (error) {
|
|
907
1982
|
session.rollback();
|
|
908
|
-
if (error instanceof
|
|
1983
|
+
if (error instanceof require_driver.MutationRejected) return {
|
|
909
1984
|
status: "rejected",
|
|
910
1985
|
issues: [error.issue],
|
|
911
1986
|
revision
|
|
912
1987
|
};
|
|
913
|
-
if (intent.kind === "update" && error instanceof
|
|
1988
|
+
if (intent.kind === "update" && error instanceof TransactionRejected) return {
|
|
914
1989
|
status: "rejected",
|
|
915
1990
|
issues: error.issues,
|
|
916
1991
|
revision
|
|
@@ -928,13 +2003,6 @@ const createDocument = (input) => {
|
|
|
928
2003
|
}
|
|
929
2004
|
runtime = {
|
|
930
2005
|
schema: input.schema,
|
|
931
|
-
address: {
|
|
932
|
-
resolve: (at) => require_issue.resolveAddress(input.schema, at, state.document),
|
|
933
|
-
read: (at) => require_issue.read(state.document, at, input.schema),
|
|
934
|
-
contains: require_issue.contains,
|
|
935
|
-
overlaps: require_issue.overlaps,
|
|
936
|
-
debugKey: require_issue.debugKey
|
|
937
|
-
},
|
|
938
2006
|
revision: () => revision,
|
|
939
2007
|
update: (run, options) => {
|
|
940
2008
|
const source = options?.source ?? "local";
|
|
@@ -943,7 +2011,7 @@ const createDocument = (input) => {
|
|
|
943
2011
|
kind: "update",
|
|
944
2012
|
source
|
|
945
2013
|
}, options?.history ?? true, (session) => {
|
|
946
|
-
value = run(
|
|
2014
|
+
value = run(createAccess({
|
|
947
2015
|
state,
|
|
948
2016
|
session
|
|
949
2017
|
}));
|
|
@@ -958,7 +2026,7 @@ const createDocument = (input) => {
|
|
|
958
2026
|
kind: "apply",
|
|
959
2027
|
source: options?.source ?? "local"
|
|
960
2028
|
}, options?.history ?? true, (session) => {
|
|
961
|
-
if (!options || options.expectedRevision !== revision)
|
|
2029
|
+
if (!options || options.expectedRevision !== revision) require_driver.fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
|
|
962
2030
|
apply(session, require_driver.decodeChanges(inputChanges), "forward");
|
|
963
2031
|
}),
|
|
964
2032
|
replace: (value, options) => {
|
|
@@ -969,15 +2037,15 @@ const createDocument = (input) => {
|
|
|
969
2037
|
}, source !== "remote", (session) => session.replace([], value));
|
|
970
2038
|
},
|
|
971
2039
|
snapshot: () => {
|
|
972
|
-
if (state.disposed) throw new
|
|
973
|
-
return
|
|
2040
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2041
|
+
return require_advanced.copyValue(input.schema, state.document);
|
|
974
2042
|
},
|
|
975
2043
|
subscribe: ((pick, listener) => {
|
|
976
|
-
if (state.disposed) throw new
|
|
977
|
-
if (!listener) return
|
|
2044
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2045
|
+
if (!listener) return notifications.subscribe(pick);
|
|
978
2046
|
const picks = Array.isArray(pick) ? pick : [pick];
|
|
979
2047
|
if (!picks.length) throw new TypeError("Expected at least one subscription path.");
|
|
980
|
-
return
|
|
2048
|
+
return notifications.subscribeTargets(picks.map((p) => require_advanced.compilePath(input.schema, "value", p)), listener);
|
|
981
2049
|
}),
|
|
982
2050
|
history: history.api,
|
|
983
2051
|
dispose: () => {
|
|
@@ -985,36 +2053,517 @@ const createDocument = (input) => {
|
|
|
985
2053
|
idle();
|
|
986
2054
|
state.disposed = true;
|
|
987
2055
|
try {
|
|
988
|
-
|
|
2056
|
+
notifications.dispose();
|
|
989
2057
|
} finally {
|
|
990
2058
|
history.dispose();
|
|
991
|
-
|
|
2059
|
+
context.driver = void 0;
|
|
992
2060
|
}
|
|
993
2061
|
}
|
|
994
2062
|
};
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
2063
|
+
context = {
|
|
2064
|
+
state,
|
|
2065
|
+
owner: runtime,
|
|
2066
|
+
notifications,
|
|
2067
|
+
driver: void 0,
|
|
2068
|
+
bypassDepth: 0
|
|
2069
|
+
};
|
|
2070
|
+
require_context.bindContext(runtime, context);
|
|
2071
|
+
require_context.bindContext(history.api, context);
|
|
999
2072
|
return runtime;
|
|
1000
2073
|
};
|
|
1001
2074
|
//#endregion
|
|
1002
2075
|
//#region core/src/runtime/readable.ts
|
|
1003
2076
|
const asReadable = (runtime) => {
|
|
1004
2077
|
const readable = {
|
|
1005
|
-
address: runtime.address,
|
|
1006
2078
|
revision: runtime.revision,
|
|
1007
2079
|
subscribe: runtime.subscribe
|
|
1008
2080
|
};
|
|
1009
|
-
|
|
1010
|
-
require_notification.shareNotification(runtime, readable);
|
|
2081
|
+
require_context.bindContext(readable, require_context.contextOf(runtime));
|
|
1011
2082
|
return Object.freeze(readable);
|
|
1012
2083
|
};
|
|
1013
2084
|
//#endregion
|
|
1014
|
-
//#region core/src/
|
|
1015
|
-
const
|
|
2085
|
+
//#region core/src/access/dependency.ts
|
|
2086
|
+
const createDependencyTracker = () => {
|
|
2087
|
+
const targets = [];
|
|
2088
|
+
return {
|
|
2089
|
+
record: (value) => {
|
|
2090
|
+
if (!targets.some((entry) => same(entry, value))) targets.push(value);
|
|
2091
|
+
},
|
|
2092
|
+
snapshot: () => Object.freeze(targets.slice())
|
|
2093
|
+
};
|
|
2094
|
+
};
|
|
2095
|
+
//#endregion
|
|
2096
|
+
//#region core/src/runtime/access.ts
|
|
2097
|
+
const readWith = (runtime, run, dependencies) => {
|
|
2098
|
+
const state = require_context.contextOf(runtime).state;
|
|
2099
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2100
|
+
state.projectionLocks += 1;
|
|
2101
|
+
try {
|
|
2102
|
+
return run(createAccess({
|
|
2103
|
+
state,
|
|
2104
|
+
dependencies
|
|
2105
|
+
}));
|
|
2106
|
+
} finally {
|
|
2107
|
+
state.projectionLocks--;
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
//#endregion
|
|
2111
|
+
//#region core/src/runtime/select.ts
|
|
2112
|
+
const read = (document, selector) => readWith(document, selector);
|
|
2113
|
+
const sameDependencySet = (left, right) => {
|
|
2114
|
+
if (left.length !== right.length) return false;
|
|
2115
|
+
for (let index = 0; index < left.length; index += 1) if (!same(left[index], right[index])) return false;
|
|
2116
|
+
return true;
|
|
2117
|
+
};
|
|
2118
|
+
const select = (document, selector, equality = Object.is) => {
|
|
2119
|
+
const context = require_context.contextOf(document);
|
|
2120
|
+
let initialized = false;
|
|
2121
|
+
let value;
|
|
2122
|
+
let selectedRevision = 0;
|
|
2123
|
+
let documentRevision = -1;
|
|
2124
|
+
let dependencies = Object.freeze([]);
|
|
2125
|
+
let unsubscribeDocument;
|
|
2126
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2127
|
+
const evaluate = () => {
|
|
2128
|
+
const tracker = createDependencyTracker();
|
|
2129
|
+
const next = readWith(document, selector, tracker);
|
|
2130
|
+
const nextDependencies = tracker.snapshot();
|
|
2131
|
+
const changed = !initialized || !equality(value, next);
|
|
2132
|
+
const rebound = !sameDependencySet(dependencies, nextDependencies);
|
|
2133
|
+
if (changed) {
|
|
2134
|
+
if (initialized) selectedRevision += 1;
|
|
2135
|
+
value = next;
|
|
2136
|
+
}
|
|
2137
|
+
dependencies = nextDependencies;
|
|
2138
|
+
initialized = true;
|
|
2139
|
+
documentRevision = document.revision();
|
|
2140
|
+
return {
|
|
2141
|
+
changed,
|
|
2142
|
+
rebound
|
|
2143
|
+
};
|
|
2144
|
+
};
|
|
2145
|
+
const installSubscription = () => {
|
|
2146
|
+
unsubscribeDocument?.();
|
|
2147
|
+
unsubscribeDocument = dependencies.length > 0 ? context.notifications.subscribeTargets(dependencies, () => {
|
|
2148
|
+
const result = evaluate();
|
|
2149
|
+
if (result.rebound) installSubscription();
|
|
2150
|
+
if (result.changed) for (const listener of [...listeners]) listener();
|
|
2151
|
+
}) : void 0;
|
|
2152
|
+
};
|
|
2153
|
+
const ensureCurrent = () => {
|
|
2154
|
+
if (context.state.disposed) throw new DocumentDisposedError();
|
|
2155
|
+
if (!initialized || !unsubscribeDocument && documentRevision !== document.revision()) evaluate();
|
|
2156
|
+
return value;
|
|
2157
|
+
};
|
|
2158
|
+
return Object.freeze({
|
|
2159
|
+
current: ensureCurrent,
|
|
2160
|
+
revision: () => {
|
|
2161
|
+
ensureCurrent();
|
|
2162
|
+
return selectedRevision;
|
|
2163
|
+
},
|
|
2164
|
+
subscribe: (listener) => {
|
|
2165
|
+
ensureCurrent();
|
|
2166
|
+
listeners.add(listener);
|
|
2167
|
+
if (listeners.size === 1) installSubscription();
|
|
2168
|
+
return () => {
|
|
2169
|
+
listeners.delete(listener);
|
|
2170
|
+
if (!listeners.size) {
|
|
2171
|
+
unsubscribeDocument?.();
|
|
2172
|
+
unsubscribeDocument = void 0;
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
};
|
|
2178
|
+
//#endregion
|
|
2179
|
+
//#region core/src/projection/collection/change.ts
|
|
2180
|
+
const commonOrderChanged = (before, after, added, removed) => {
|
|
2181
|
+
const beforeCommon = before.filter((key) => !removed.has(key));
|
|
2182
|
+
const afterCommon = after.filter((key) => !added.has(key));
|
|
2183
|
+
return beforeCommon.some((key, index) => afterCommon[index] !== key);
|
|
2184
|
+
};
|
|
2185
|
+
/** Builds the canonical incremental CollectionChange from already-known net transitions. */
|
|
2186
|
+
const createCollectionChange = (input) => {
|
|
2187
|
+
const addedKeys = new Set(input.added.map((entry) => entry.key));
|
|
2188
|
+
const removedKeys = new Set(input.removed.map((entry) => entry.key));
|
|
2189
|
+
const orderChanged = input.beforeOrder !== input.afterOrder && commonOrderChanged(input.beforeOrder, input.afterOrder, addedKeys, removedKeys);
|
|
2190
|
+
if (!input.added.length && !input.updated.length && !input.removed.length && !orderChanged) return void 0;
|
|
2191
|
+
return Object.freeze({
|
|
2192
|
+
kind: "incremental",
|
|
2193
|
+
added: Object.freeze([...input.added]),
|
|
2194
|
+
updated: Object.freeze([...input.updated]),
|
|
2195
|
+
removed: Object.freeze([...input.removed]),
|
|
2196
|
+
...orderChanged ? { order: Object.freeze({
|
|
2197
|
+
before: Object.freeze([...input.beforeOrder]),
|
|
2198
|
+
after: Object.freeze([...input.afterOrder])
|
|
2199
|
+
}) } : {}
|
|
2200
|
+
});
|
|
2201
|
+
};
|
|
2202
|
+
/** Exact fallback diff between two published map-like snapshots. */
|
|
2203
|
+
const diffCollection = (previous, current, isEqual = Object.is) => {
|
|
2204
|
+
const added = [];
|
|
2205
|
+
const updated = [];
|
|
2206
|
+
const removed = [];
|
|
2207
|
+
for (const [key, before] of previous) if (!current.has(key)) removed.push({
|
|
2208
|
+
key,
|
|
2209
|
+
before
|
|
2210
|
+
});
|
|
2211
|
+
else {
|
|
2212
|
+
const after = current.get(key);
|
|
2213
|
+
if (!isEqual(before, after)) updated.push({
|
|
2214
|
+
key,
|
|
2215
|
+
before,
|
|
2216
|
+
after
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
for (const [key, after] of current) if (!previous.has(key)) added.push({
|
|
2220
|
+
key,
|
|
2221
|
+
after
|
|
2222
|
+
});
|
|
2223
|
+
return createCollectionChange({
|
|
2224
|
+
added,
|
|
2225
|
+
updated,
|
|
2226
|
+
removed,
|
|
2227
|
+
beforeOrder: [...previous.keys()],
|
|
2228
|
+
afterOrder: [...current.keys()]
|
|
2229
|
+
});
|
|
2230
|
+
};
|
|
2231
|
+
const collectionChangedKeys = (change) => change.kind === "reset" ? /* @__PURE__ */ new Set() : new Set([
|
|
2232
|
+
...change.added.map((entry) => entry.key),
|
|
2233
|
+
...change.updated.map((entry) => entry.key),
|
|
2234
|
+
...change.removed.map((entry) => entry.key)
|
|
2235
|
+
]);
|
|
2236
|
+
const collectionHasStructuralChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.removed.length || change.order);
|
|
2237
|
+
const collectionHasAnyChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.updated.length || change.removed.length || change.order);
|
|
2238
|
+
//#endregion
|
|
2239
|
+
//#region core/src/projection/collection/index.ts
|
|
2240
|
+
const height = (node) => node?.height ?? 0;
|
|
2241
|
+
const node = (key, value, left, right) => {
|
|
2242
|
+
require_advanced.profile.collectionIndex.node();
|
|
2243
|
+
return Object.freeze({
|
|
2244
|
+
key,
|
|
2245
|
+
value,
|
|
2246
|
+
left,
|
|
2247
|
+
right,
|
|
2248
|
+
height: Math.max(height(left), height(right)) + 1
|
|
2249
|
+
});
|
|
2250
|
+
};
|
|
2251
|
+
const rotateLeft = (current) => {
|
|
2252
|
+
const right = current.right;
|
|
2253
|
+
return node(right.key, right.value, node(current.key, current.value, current.left, right.left), right.right);
|
|
2254
|
+
};
|
|
2255
|
+
const rotateRight = (current) => {
|
|
2256
|
+
const left = current.left;
|
|
2257
|
+
return node(left.key, left.value, left.left, node(current.key, current.value, left.right, current.right));
|
|
2258
|
+
};
|
|
2259
|
+
const rebalance = (current) => {
|
|
2260
|
+
const balance = height(current.left) - height(current.right);
|
|
2261
|
+
if (balance > 1) {
|
|
2262
|
+
if (height(current.left.left) < height(current.left.right)) return rotateRight(node(current.key, current.value, rotateLeft(current.left), current.right));
|
|
2263
|
+
return rotateRight(current);
|
|
2264
|
+
}
|
|
2265
|
+
if (balance < -1) {
|
|
2266
|
+
if (height(current.right.right) < height(current.right.left)) return rotateLeft(node(current.key, current.value, current.left, rotateRight(current.right)));
|
|
2267
|
+
return rotateLeft(current);
|
|
2268
|
+
}
|
|
2269
|
+
return current;
|
|
2270
|
+
};
|
|
2271
|
+
const setNode = (current, key, value) => {
|
|
2272
|
+
if (!current) return node(key, value);
|
|
2273
|
+
if (key === current.key) return node(key, value, current.left, current.right);
|
|
2274
|
+
if (key < current.key) return rebalance(node(current.key, current.value, setNode(current.left, key, value), current.right));
|
|
2275
|
+
return rebalance(node(current.key, current.value, current.left, setNode(current.right, key, value)));
|
|
2276
|
+
};
|
|
2277
|
+
const minNode = (current) => current.left ? minNode(current.left) : current;
|
|
2278
|
+
const removeNode = (current, key) => {
|
|
2279
|
+
if (!current) return void 0;
|
|
2280
|
+
if (key < current.key) return rebalance(node(current.key, current.value, removeNode(current.left, key), current.right));
|
|
2281
|
+
if (key > current.key) return rebalance(node(current.key, current.value, current.left, removeNode(current.right, key)));
|
|
2282
|
+
if (!current.left) return current.right;
|
|
2283
|
+
if (!current.right) return current.left;
|
|
2284
|
+
const next = minNode(current.right);
|
|
2285
|
+
return rebalance(node(next.key, next.value, current.left, removeNode(current.right, next.key)));
|
|
2286
|
+
};
|
|
2287
|
+
const fromSorted = (entries, start = 0, end = entries.length) => {
|
|
2288
|
+
if (start >= end) return void 0;
|
|
2289
|
+
const middle = start + end >> 1;
|
|
2290
|
+
const [key, value] = entries[middle];
|
|
2291
|
+
return node(key, value, fromSorted(entries, start, middle), fromSorted(entries, middle + 1, end));
|
|
2292
|
+
};
|
|
2293
|
+
/** Immutable keyed lookup used to keep published collection snapshots durable across revisions. */
|
|
2294
|
+
var PersistentKeyedIndex = class PersistentKeyedIndex {
|
|
2295
|
+
constructor(root) {
|
|
2296
|
+
this.root = root;
|
|
2297
|
+
}
|
|
2298
|
+
static empty() {
|
|
2299
|
+
return new PersistentKeyedIndex();
|
|
2300
|
+
}
|
|
2301
|
+
static from(entries) {
|
|
2302
|
+
const sorted = [...entries].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
2303
|
+
require_advanced.profile.collectionIndex.build(sorted.length);
|
|
2304
|
+
return new PersistentKeyedIndex(fromSorted(sorted));
|
|
2305
|
+
}
|
|
2306
|
+
get(key) {
|
|
2307
|
+
let current = this.root;
|
|
2308
|
+
while (current) {
|
|
2309
|
+
if (key === current.key) return current.value;
|
|
2310
|
+
current = key < current.key ? current.left : current.right;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
has(key) {
|
|
2314
|
+
let current = this.root;
|
|
2315
|
+
while (current) {
|
|
2316
|
+
if (key === current.key) return true;
|
|
2317
|
+
current = key < current.key ? current.left : current.right;
|
|
2318
|
+
}
|
|
2319
|
+
return false;
|
|
2320
|
+
}
|
|
2321
|
+
set(key, value) {
|
|
2322
|
+
return new PersistentKeyedIndex(setNode(this.root, key, value));
|
|
2323
|
+
}
|
|
2324
|
+
remove(key) {
|
|
2325
|
+
return new PersistentKeyedIndex(removeNode(this.root, key));
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2328
|
+
//#endregion
|
|
2329
|
+
//#region core/src/projection/output/collection.ts
|
|
2330
|
+
const createPublishedCollection = () => {
|
|
2331
|
+
const values = /* @__PURE__ */ new Map();
|
|
2332
|
+
let ids = Object.freeze([]);
|
|
2333
|
+
let index = PersistentKeyedIndex.empty();
|
|
2334
|
+
return {
|
|
2335
|
+
get: (key) => values.get(key),
|
|
2336
|
+
has: (key) => values.has(key),
|
|
2337
|
+
keys: () => values.keys(),
|
|
2338
|
+
size: () => values.size,
|
|
2339
|
+
ids: () => ids,
|
|
2340
|
+
read: (check) => {
|
|
2341
|
+
const snapshotIndex = index;
|
|
2342
|
+
const snapshotIds = ids;
|
|
2343
|
+
return Object.freeze({
|
|
2344
|
+
get: (key) => {
|
|
2345
|
+
check();
|
|
2346
|
+
return snapshotIndex.get(key);
|
|
2347
|
+
},
|
|
2348
|
+
has: (key) => {
|
|
2349
|
+
check();
|
|
2350
|
+
return snapshotIndex.has(key);
|
|
2351
|
+
},
|
|
2352
|
+
ids: () => {
|
|
2353
|
+
check();
|
|
2354
|
+
return snapshotIds;
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
},
|
|
2358
|
+
publish: (staged, nextIds, reset) => {
|
|
2359
|
+
if (reset) {
|
|
2360
|
+
const entries = [];
|
|
2361
|
+
for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
|
|
2362
|
+
index = PersistentKeyedIndex.from(entries);
|
|
2363
|
+
values.clear();
|
|
2364
|
+
} else for (const [key, entry] of staged) index = entry.present ? index.set(key, entry.value) : index.remove(key);
|
|
2365
|
+
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
2366
|
+
else values.delete(key);
|
|
2367
|
+
ids = nextIds;
|
|
2368
|
+
},
|
|
2369
|
+
release: () => {
|
|
2370
|
+
values.clear();
|
|
2371
|
+
ids = Object.freeze([]);
|
|
2372
|
+
index = PersistentKeyedIndex.empty();
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
};
|
|
2376
|
+
/** Owns staged keyed intent, exact transitions and published collection state. */
|
|
2377
|
+
const createCollectionOutput = () => {
|
|
2378
|
+
const published = createPublishedCollection();
|
|
2379
|
+
let staged = /* @__PURE__ */ new Map();
|
|
2380
|
+
let nextIds = published.ids();
|
|
2381
|
+
let change;
|
|
2382
|
+
let initialized = false;
|
|
2383
|
+
let reset = false;
|
|
2384
|
+
let revision = 0;
|
|
2385
|
+
let explicitOrder;
|
|
2386
|
+
let cleared = false;
|
|
2387
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2388
|
+
const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && published.has(key);
|
|
2389
|
+
const getNext = (key) => {
|
|
2390
|
+
const entry = staged.get(key);
|
|
2391
|
+
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : published.get(key);
|
|
2392
|
+
};
|
|
2393
|
+
const deriveIds = () => {
|
|
2394
|
+
const result = cleared ? [] : published.ids().filter(hasNext);
|
|
2395
|
+
for (const [key, entry] of staged) if (entry.present && (cleared || !published.has(key))) result.push(key);
|
|
2396
|
+
return result;
|
|
2397
|
+
};
|
|
2398
|
+
const begin = (active, initialize) => {
|
|
2399
|
+
staged = /* @__PURE__ */ new Map();
|
|
2400
|
+
nextIds = published.ids();
|
|
2401
|
+
change = void 0;
|
|
2402
|
+
explicitOrder = void 0;
|
|
2403
|
+
cleared = initialize;
|
|
2404
|
+
reset = initialize;
|
|
2405
|
+
const read = (next) => ({
|
|
2406
|
+
get: (key) => {
|
|
2407
|
+
require_advanced.assertScope(active);
|
|
2408
|
+
return next ? getNext(key) : published.get(key);
|
|
2409
|
+
},
|
|
2410
|
+
has: (key) => {
|
|
2411
|
+
require_advanced.assertScope(active);
|
|
2412
|
+
return next ? hasNext(key) : published.has(key);
|
|
2413
|
+
},
|
|
2414
|
+
ids: () => {
|
|
2415
|
+
require_advanced.assertScope(active);
|
|
2416
|
+
return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : published.ids();
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
return {
|
|
2420
|
+
previous: read(false),
|
|
2421
|
+
next: read(true),
|
|
2422
|
+
output: {
|
|
2423
|
+
set: (key, value) => {
|
|
2424
|
+
require_advanced.assertScope(active);
|
|
2425
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2426
|
+
staged.set(key, {
|
|
2427
|
+
present: true,
|
|
2428
|
+
value
|
|
2429
|
+
});
|
|
2430
|
+
},
|
|
2431
|
+
remove: (key) => {
|
|
2432
|
+
require_advanced.assertScope(active);
|
|
2433
|
+
staged.set(key, { present: false });
|
|
2434
|
+
},
|
|
2435
|
+
order: (order) => {
|
|
2436
|
+
require_advanced.assertScope(active);
|
|
2437
|
+
explicitOrder = Object.freeze([...order]);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
};
|
|
2441
|
+
};
|
|
2442
|
+
const seal = (nextReset, isEqual) => {
|
|
2443
|
+
if (cleared) {
|
|
2444
|
+
for (const key of published.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
2445
|
+
}
|
|
2446
|
+
require_advanced.profile.projection("touchedKeys", staged.size);
|
|
2447
|
+
const added = /* @__PURE__ */ new Set();
|
|
2448
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2449
|
+
const updated = /* @__PURE__ */ new Set();
|
|
2450
|
+
for (const [key, entry] of staged) {
|
|
2451
|
+
const existed = published.has(key);
|
|
2452
|
+
if (entry.present) {
|
|
2453
|
+
if (!existed) added.add(key);
|
|
2454
|
+
else if (!isEqual(published.get(key), entry.value)) updated.add(key);
|
|
2455
|
+
else if (!nextReset) staged.delete(key);
|
|
2456
|
+
} else if (existed) removed.add(key);
|
|
2457
|
+
else staged.delete(key);
|
|
2458
|
+
}
|
|
2459
|
+
const ids = published.ids();
|
|
2460
|
+
if (explicitOrder || added.size || removed.size) {
|
|
2461
|
+
const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
|
|
2462
|
+
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.");
|
|
2463
|
+
nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
|
|
2464
|
+
} else nextIds = ids;
|
|
2465
|
+
const changedKeys = new Set([
|
|
2466
|
+
...added,
|
|
2467
|
+
...removed,
|
|
2468
|
+
...updated
|
|
2469
|
+
]);
|
|
2470
|
+
require_advanced.profile.projection("changedKeys", changedKeys.size);
|
|
2471
|
+
const addedEntries = [];
|
|
2472
|
+
const updatedEntries = [];
|
|
2473
|
+
const removedEntries = [];
|
|
2474
|
+
for (const key of changedKeys) {
|
|
2475
|
+
const beforePresent = published.has(key);
|
|
2476
|
+
const afterPresent = hasNext(key);
|
|
2477
|
+
if (beforePresent === afterPresent) {
|
|
2478
|
+
if (!beforePresent || isEqual(published.get(key), getNext(key))) continue;
|
|
2479
|
+
}
|
|
2480
|
+
if (!beforePresent) addedEntries.push({
|
|
2481
|
+
key,
|
|
2482
|
+
after: getNext(key)
|
|
2483
|
+
});
|
|
2484
|
+
else if (!afterPresent) removedEntries.push({
|
|
2485
|
+
key,
|
|
2486
|
+
before: published.get(key)
|
|
2487
|
+
});
|
|
2488
|
+
else updatedEntries.push({
|
|
2489
|
+
key,
|
|
2490
|
+
before: published.get(key),
|
|
2491
|
+
after: getNext(key)
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
change = nextReset ? Object.freeze({ kind: "reset" }) : createCollectionChange({
|
|
2495
|
+
added: addedEntries,
|
|
2496
|
+
updated: updatedEntries,
|
|
2497
|
+
removed: removedEntries,
|
|
2498
|
+
beforeOrder: ids,
|
|
2499
|
+
afterOrder: nextIds
|
|
2500
|
+
});
|
|
2501
|
+
reset = nextReset;
|
|
2502
|
+
return change !== void 0;
|
|
2503
|
+
};
|
|
2504
|
+
const context = (active, cause) => Object.freeze({
|
|
2505
|
+
kind: "collection",
|
|
2506
|
+
read: {
|
|
2507
|
+
get: (key) => {
|
|
2508
|
+
require_advanced.assertScope(active);
|
|
2509
|
+
return getNext(key);
|
|
2510
|
+
},
|
|
2511
|
+
has: (key) => {
|
|
2512
|
+
require_advanced.assertScope(active);
|
|
2513
|
+
return hasNext(key);
|
|
2514
|
+
},
|
|
2515
|
+
ids: () => {
|
|
2516
|
+
require_advanced.assertScope(active);
|
|
2517
|
+
return nextIds;
|
|
2518
|
+
}
|
|
2519
|
+
},
|
|
2520
|
+
change,
|
|
2521
|
+
revision,
|
|
2522
|
+
reset,
|
|
2523
|
+
cause
|
|
2524
|
+
});
|
|
2525
|
+
return {
|
|
2526
|
+
begin,
|
|
2527
|
+
seal,
|
|
2528
|
+
context,
|
|
2529
|
+
current: (check) => published.read(check),
|
|
2530
|
+
revision: () => revision,
|
|
2531
|
+
reset: () => reset,
|
|
2532
|
+
publish: () => {
|
|
2533
|
+
if (change) published.publish(staged, nextIds, change.kind === "reset");
|
|
2534
|
+
if (change && initialized) revision++;
|
|
2535
|
+
initialized = true;
|
|
2536
|
+
},
|
|
2537
|
+
emit: (call) => {
|
|
2538
|
+
const resetEvent = Object.freeze({ kind: "reset" });
|
|
2539
|
+
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
2540
|
+
},
|
|
2541
|
+
clear: () => {
|
|
2542
|
+
staged.clear();
|
|
2543
|
+
nextIds = published.ids();
|
|
2544
|
+
change = void 0;
|
|
2545
|
+
reset = false;
|
|
2546
|
+
explicitOrder = void 0;
|
|
2547
|
+
cleared = false;
|
|
2548
|
+
},
|
|
2549
|
+
release: () => {
|
|
2550
|
+
published.release();
|
|
2551
|
+
staged.clear();
|
|
2552
|
+
listeners.clear();
|
|
2553
|
+
nextIds = Object.freeze([]);
|
|
2554
|
+
change = void 0;
|
|
2555
|
+
initialized = false;
|
|
2556
|
+
revision = 0;
|
|
2557
|
+
reset = false;
|
|
2558
|
+
explicitOrder = void 0;
|
|
2559
|
+
cleared = false;
|
|
2560
|
+
},
|
|
2561
|
+
subscribe: (listener) => listeners.add(listener),
|
|
2562
|
+
unsubscribe: (listener) => listeners.delete(listener)
|
|
2563
|
+
};
|
|
2564
|
+
};
|
|
1016
2565
|
//#endregion
|
|
1017
|
-
//#region core/src/projection/value
|
|
2566
|
+
//#region core/src/projection/output/value.ts
|
|
1018
2567
|
const unset = Symbol("projection-value-unset");
|
|
1019
2568
|
/** Owns staged and published state for one scalar Projection output. */
|
|
1020
2569
|
const createValueOutput = () => {
|
|
@@ -1083,7 +2632,7 @@ const createValueOutput = () => {
|
|
|
1083
2632
|
initialized = true;
|
|
1084
2633
|
},
|
|
1085
2634
|
emit: (call) => {
|
|
1086
|
-
|
|
2635
|
+
require_advanced.profile.materialized.notification();
|
|
1087
2636
|
Array.from(listeners).forEach((listener) => call(listener));
|
|
1088
2637
|
},
|
|
1089
2638
|
clear: () => {
|
|
@@ -1106,7 +2655,7 @@ const createValueOutput = () => {
|
|
|
1106
2655
|
};
|
|
1107
2656
|
};
|
|
1108
2657
|
//#endregion
|
|
1109
|
-
//#region core/src/projection/processor.ts
|
|
2658
|
+
//#region core/src/projection/graph/processor.ts
|
|
1110
2659
|
/** Materializes every derive/incremental/group definition through one processor lifecycle. */
|
|
1111
2660
|
const createProcessor = (scheduler, definition, dependencies) => {
|
|
1112
2661
|
scheduler.assertIdle();
|
|
@@ -1146,7 +2695,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1146
2695
|
}
|
|
1147
2696
|
};
|
|
1148
2697
|
}
|
|
1149
|
-
const state =
|
|
2698
|
+
const state = createCollectionOutput();
|
|
1150
2699
|
return {
|
|
1151
2700
|
kind: "collection",
|
|
1152
2701
|
definition: outputDefinition,
|
|
@@ -1200,7 +2749,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1200
2749
|
const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
|
|
1201
2750
|
let outputs = begin(scopeActive, reset);
|
|
1202
2751
|
if (!instance) instance = definition.create();
|
|
1203
|
-
|
|
2752
|
+
require_advanced.profile.materialized[reset ? "rebuilt" : "updated"]();
|
|
1204
2753
|
let result = instance.evaluate({
|
|
1205
2754
|
reset,
|
|
1206
2755
|
cause: runCause,
|
|
@@ -1268,7 +2817,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1268
2817
|
return processor;
|
|
1269
2818
|
};
|
|
1270
2819
|
//#endregion
|
|
1271
|
-
//#region core/src/projection/
|
|
2820
|
+
//#region core/src/projection/readable/selection.ts
|
|
1272
2821
|
let activeTracker;
|
|
1273
2822
|
const trackKey = (key) => {
|
|
1274
2823
|
activeTracker?.keys.add(key);
|
|
@@ -1319,9 +2868,10 @@ const trackedMap = (source) => {
|
|
|
1319
2868
|
};
|
|
1320
2869
|
const selectionAffects = (selection, change) => {
|
|
1321
2870
|
if (!change || change.kind === "reset") return true;
|
|
1322
|
-
if (selection.all && (change
|
|
1323
|
-
if (selection.structure && (change
|
|
1324
|
-
|
|
2871
|
+
if (selection.all && collectionHasAnyChange(change)) return true;
|
|
2872
|
+
if (selection.structure && collectionHasStructuralChange(change)) return true;
|
|
2873
|
+
const changedKeys = collectionChangedKeys(change);
|
|
2874
|
+
for (const key of selection.keys) if (changedKeys.has(key)) return true;
|
|
1325
2875
|
return false;
|
|
1326
2876
|
};
|
|
1327
2877
|
const createDirectReadable = (source) => Object.freeze({
|
|
@@ -1389,7 +2939,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
1389
2939
|
if (!change && previousCollection) {
|
|
1390
2940
|
const current = source.current();
|
|
1391
2941
|
if (isMapLike(current)) {
|
|
1392
|
-
change =
|
|
2942
|
+
change = diffCollection(previousCollection, current);
|
|
1393
2943
|
previousCollection = current;
|
|
1394
2944
|
if (!change) {
|
|
1395
2945
|
sourceRevision = source.revision();
|
|
@@ -1428,7 +2978,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
1428
2978
|
});
|
|
1429
2979
|
};
|
|
1430
2980
|
//#endregion
|
|
1431
|
-
//#region core/src/projection/
|
|
2981
|
+
//#region core/src/projection/source/materialization.ts
|
|
1432
2982
|
const createDocumentDirty = () => ({
|
|
1433
2983
|
replace: false,
|
|
1434
2984
|
order: false,
|
|
@@ -1476,10 +3026,10 @@ const markDocumentTree = (dirty, path, root, nodes) => {
|
|
|
1476
3026
|
current.treeRoot ||= root;
|
|
1477
3027
|
for (const id of nodes) current.treeNodes.add(id);
|
|
1478
3028
|
};
|
|
1479
|
-
const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) :
|
|
1480
|
-
const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous :
|
|
3029
|
+
const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) : require_advanced.equalValue(node, left, right);
|
|
3030
|
+
const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous : require_advanced.copyValue(node, current);
|
|
1481
3031
|
const cloneRecord = (value) => {
|
|
1482
|
-
|
|
3032
|
+
require_advanced.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
|
|
1483
3033
|
return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
|
|
1484
3034
|
};
|
|
1485
3035
|
const materializeRecordChild = (node, previous, current, key, dirty) => {
|
|
@@ -1491,7 +3041,7 @@ const materializeRecordChild = (node, previous, current, key, dirty) => {
|
|
|
1491
3041
|
present: false,
|
|
1492
3042
|
value: void 0
|
|
1493
3043
|
};
|
|
1494
|
-
const next = before ? materializeDocumentValue(node, previous[key], current[key], dirty) :
|
|
3044
|
+
const next = before ? materializeDocumentValue(node, previous[key], current[key], dirty) : require_advanced.copyValue(node, current[key]);
|
|
1495
3045
|
if (before && next === previous[key]) return { changed: false };
|
|
1496
3046
|
return {
|
|
1497
3047
|
changed: true,
|
|
@@ -1501,7 +3051,7 @@ const materializeRecordChild = (node, previous, current, key, dirty) => {
|
|
|
1501
3051
|
};
|
|
1502
3052
|
const materializeObject = (node, previous, current, dirty) => {
|
|
1503
3053
|
if (node.kind === "variant" && previous[node.tag] !== current[node.tag]) return replaceValue(node, previous, current);
|
|
1504
|
-
const layout =
|
|
3054
|
+
const layout = require_advanced.compiledShape(node, current);
|
|
1505
3055
|
if (!layout) return replaceValue(node, previous, current);
|
|
1506
3056
|
let result;
|
|
1507
3057
|
for (const [key, childDirty] of dirty.children) {
|
|
@@ -1510,7 +3060,7 @@ const materializeObject = (node, previous, current, dirty) => {
|
|
|
1510
3060
|
const child = materializeRecordChild(member.node, previous, current, key, childDirty);
|
|
1511
3061
|
if (!child.changed) continue;
|
|
1512
3062
|
result ??= cloneRecord(previous);
|
|
1513
|
-
|
|
3063
|
+
require_context.installOwn(result, key, child.present, child.value);
|
|
1514
3064
|
}
|
|
1515
3065
|
return result ?? previous;
|
|
1516
3066
|
};
|
|
@@ -1520,12 +3070,12 @@ const materializeMap = (node, previous, current, dirty) => {
|
|
|
1520
3070
|
const child = materializeRecordChild(node.value, previous, current, key, childDirty);
|
|
1521
3071
|
if (!child.changed) continue;
|
|
1522
3072
|
result ??= cloneRecord(previous);
|
|
1523
|
-
|
|
3073
|
+
require_context.installOwn(result, key, child.present, child.value);
|
|
1524
3074
|
}
|
|
1525
3075
|
return result ?? previous;
|
|
1526
3076
|
};
|
|
1527
3077
|
const materializeTable = (node, previous, current, dirty) => {
|
|
1528
|
-
if (!
|
|
3078
|
+
if (!require_context.isRecord(previous.byId) || !require_context.isRecord(current.byId)) return replaceValue(node, previous, current);
|
|
1529
3079
|
const beforeById = previous.byId;
|
|
1530
3080
|
const afterById = current.byId;
|
|
1531
3081
|
let result;
|
|
@@ -1534,28 +3084,28 @@ const materializeTable = (node, previous, current, dirty) => {
|
|
|
1534
3084
|
const child = materializeRecordChild(node.value, beforeById, afterById, key, childDirty);
|
|
1535
3085
|
if (!child.changed) continue;
|
|
1536
3086
|
nextById ??= cloneRecord(beforeById);
|
|
1537
|
-
|
|
3087
|
+
require_context.installOwn(nextById, key, child.present, child.value);
|
|
1538
3088
|
}
|
|
1539
3089
|
if (nextById) {
|
|
1540
3090
|
result = cloneRecord(previous);
|
|
1541
|
-
|
|
3091
|
+
require_context.installOwn(result, "byId", true, nextById);
|
|
1542
3092
|
}
|
|
1543
3093
|
if (dirty.order) {
|
|
1544
3094
|
const beforeIds = previous.ids;
|
|
1545
3095
|
const afterIds = current.ids;
|
|
1546
3096
|
if (!Array.isArray(beforeIds) || !Array.isArray(afterIds)) return replaceValue(node, previous, current);
|
|
1547
|
-
if (!
|
|
3097
|
+
if (!require_advanced.equal(beforeIds, afterIds)) {
|
|
1548
3098
|
result ??= cloneRecord(previous);
|
|
1549
|
-
|
|
3099
|
+
require_context.installOwn(result, "ids", true, [...afterIds]);
|
|
1550
3100
|
}
|
|
1551
3101
|
}
|
|
1552
3102
|
return result ?? previous;
|
|
1553
3103
|
};
|
|
1554
3104
|
const materializeList = (node, previous, current, dirty) => {
|
|
1555
3105
|
if (dirty.order) {
|
|
1556
|
-
const before =
|
|
1557
|
-
const after =
|
|
1558
|
-
let changed = !
|
|
3106
|
+
const before = require_advanced.listSequence(previous, node.keyOf);
|
|
3107
|
+
const after = require_advanced.listSequence(current, node.keyOf);
|
|
3108
|
+
let changed = !require_advanced.equal(before.order, after.order);
|
|
1559
3109
|
const result = new Array(after.order.length);
|
|
1560
3110
|
for (let index = 0; index < after.order.length; index++) {
|
|
1561
3111
|
const key = after.order[index];
|
|
@@ -1574,8 +3124,8 @@ const materializeList = (node, previous, current, dirty) => {
|
|
|
1574
3124
|
return changed ? result : previous;
|
|
1575
3125
|
}
|
|
1576
3126
|
let result;
|
|
1577
|
-
const before =
|
|
1578
|
-
const after =
|
|
3127
|
+
const before = require_advanced.indexedKeys(previous, node.keyOf);
|
|
3128
|
+
const after = require_advanced.indexedKeys(current, node.keyOf);
|
|
1579
3129
|
for (const [key, childDirty] of dirty.children) {
|
|
1580
3130
|
const previousIndex = before.index(key);
|
|
1581
3131
|
const currentIndex = after.index(key);
|
|
@@ -1588,14 +3138,14 @@ const materializeList = (node, previous, current, dirty) => {
|
|
|
1588
3138
|
return result ?? previous;
|
|
1589
3139
|
};
|
|
1590
3140
|
const materializeTree = (node, previous, current, dirty) => {
|
|
1591
|
-
if (!
|
|
3141
|
+
if (!require_context.isRecord(previous.nodes) || !require_context.isRecord(current.nodes)) return replaceValue(node, previous, current);
|
|
1592
3142
|
let result;
|
|
1593
3143
|
if (dirty.treeRoot) {
|
|
1594
3144
|
const beforePresent = Object.hasOwn(previous, "rootId");
|
|
1595
3145
|
const afterPresent = Object.hasOwn(current, "rootId");
|
|
1596
3146
|
if (beforePresent !== afterPresent || !Object.is(previous.rootId, current.rootId)) {
|
|
1597
3147
|
result = cloneRecord(previous);
|
|
1598
|
-
|
|
3148
|
+
require_context.installOwn(result, "rootId", afterPresent, current.rootId);
|
|
1599
3149
|
}
|
|
1600
3150
|
}
|
|
1601
3151
|
let nodes;
|
|
@@ -1604,13 +3154,13 @@ const materializeTree = (node, previous, current, dirty) => {
|
|
|
1604
3154
|
const afterPresent = Object.hasOwn(current.nodes, id);
|
|
1605
3155
|
const before = beforePresent ? previous.nodes[id] : void 0;
|
|
1606
3156
|
const after = afterPresent ? current.nodes[id] : void 0;
|
|
1607
|
-
if (beforePresent === afterPresent &&
|
|
3157
|
+
if (beforePresent === afterPresent && require_advanced.equalTreeNode(before, after)) continue;
|
|
1608
3158
|
nodes ??= cloneRecord(previous.nodes);
|
|
1609
|
-
|
|
3159
|
+
require_context.installOwn(nodes, id, afterPresent, after && require_advanced.copyTreeNode(after));
|
|
1610
3160
|
}
|
|
1611
3161
|
if (nodes) {
|
|
1612
3162
|
result ??= cloneRecord(previous);
|
|
1613
|
-
|
|
3163
|
+
require_context.installOwn(result, "nodes", true, nodes);
|
|
1614
3164
|
}
|
|
1615
3165
|
return result ?? previous;
|
|
1616
3166
|
};
|
|
@@ -1618,28 +3168,129 @@ const materializeDocumentValue = (node, previous, current, dirty) => {
|
|
|
1618
3168
|
if (!hasDocumentDirty(dirty)) return previous;
|
|
1619
3169
|
if (dirty.replace || previous === void 0 || current === void 0 || node.kind === "field") return replaceValue(node, previous, current);
|
|
1620
3170
|
if (node.kind === "object" || node.kind === "variant") {
|
|
1621
|
-
if (!
|
|
3171
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
1622
3172
|
return materializeObject(node, previous, current, dirty);
|
|
1623
3173
|
}
|
|
1624
3174
|
if (node.kind === "map") {
|
|
1625
|
-
if (!
|
|
3175
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
1626
3176
|
return materializeMap(node, previous, current, dirty);
|
|
1627
3177
|
}
|
|
1628
3178
|
if (node.kind === "table") {
|
|
1629
|
-
if (!
|
|
3179
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
1630
3180
|
return materializeTable(node, previous, current, dirty);
|
|
1631
3181
|
}
|
|
1632
3182
|
if (node.kind === "list") {
|
|
1633
3183
|
if (!Array.isArray(previous) || !Array.isArray(current)) return replaceValue(node, previous, current);
|
|
1634
3184
|
return materializeList(node, previous, current, dirty);
|
|
1635
3185
|
}
|
|
1636
|
-
if (!
|
|
3186
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
1637
3187
|
return materializeTree(node, previous, current, dirty);
|
|
1638
3188
|
};
|
|
1639
3189
|
//#endregion
|
|
1640
|
-
//#region core/src/projection/source.ts
|
|
1641
|
-
const
|
|
1642
|
-
|
|
3190
|
+
//#region core/src/projection/source/dirty.ts
|
|
3191
|
+
const createCollectionPending = () => ({
|
|
3192
|
+
reset: false,
|
|
3193
|
+
order: false,
|
|
3194
|
+
entries: /* @__PURE__ */ new Map()
|
|
3195
|
+
});
|
|
3196
|
+
const resetCollectionPending = (pending) => {
|
|
3197
|
+
pending.reset = true;
|
|
3198
|
+
pending.order = false;
|
|
3199
|
+
pending.entries.clear();
|
|
3200
|
+
};
|
|
3201
|
+
const clearCollectionPending = (pending) => {
|
|
3202
|
+
pending.reset = false;
|
|
3203
|
+
pending.order = false;
|
|
3204
|
+
pending.entries.clear();
|
|
3205
|
+
};
|
|
3206
|
+
const dirtyEntry = (pending, key) => {
|
|
3207
|
+
let dirty = pending.entries.get(key);
|
|
3208
|
+
if (!dirty) {
|
|
3209
|
+
dirty = createDocumentDirty();
|
|
3210
|
+
pending.entries.set(key, dirty);
|
|
3211
|
+
}
|
|
3212
|
+
return dirty;
|
|
3213
|
+
};
|
|
3214
|
+
const mergeValueChanges = (selector, dirty, changes) => {
|
|
3215
|
+
if (selector.tree) return;
|
|
3216
|
+
const at = selector.address;
|
|
3217
|
+
for (const change of changes.changes) {
|
|
3218
|
+
if (change.kind === "reset") {
|
|
3219
|
+
markDocumentReplace(dirty, []);
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
if (change.kind === "members") {
|
|
3223
|
+
for (const member of change.members) {
|
|
3224
|
+
const changed = [...change.at, member.key];
|
|
3225
|
+
if (contains(changed, at)) markDocumentReplace(dirty, []);
|
|
3226
|
+
else if (contains(at, changed)) markDocumentReplace(dirty, changed.slice(at.length));
|
|
3227
|
+
}
|
|
3228
|
+
if (change.order && contains(at, change.at)) markDocumentOrder(dirty, change.at.slice(at.length));
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
if (contains(at, change.at)) markDocumentTree(dirty, change.at.slice(at.length), change.before !== change.after, change.nodes.map((node) => node.id));
|
|
3232
|
+
}
|
|
3233
|
+
};
|
|
3234
|
+
const mergeCollectionChanges = (selector, pending, changes) => {
|
|
3235
|
+
if (pending.reset) return;
|
|
3236
|
+
const at = selector.address;
|
|
3237
|
+
for (const change of changes.changes) {
|
|
3238
|
+
if (change.kind === "reset") {
|
|
3239
|
+
resetCollectionPending(pending);
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
if (selector.tree?.kind === "nodes") {
|
|
3243
|
+
if (change.kind === "members") {
|
|
3244
|
+
for (const member of change.members) if (contains([...change.at, member.key], at)) {
|
|
3245
|
+
resetCollectionPending(pending);
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
} else if (change.at.length === at.length && contains(change.at, at)) for (const node of change.nodes) {
|
|
3249
|
+
markDocumentReplace(dirtyEntry(pending, node.id), []);
|
|
3250
|
+
pending.order ||= node.kind !== "updated";
|
|
3251
|
+
}
|
|
3252
|
+
else if (contains(change.at, at)) {
|
|
3253
|
+
resetCollectionPending(pending);
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
if (change.kind === "members") {
|
|
3259
|
+
for (const member of change.members) {
|
|
3260
|
+
const changed = [...change.at, member.key];
|
|
3261
|
+
if (contains(changed, at)) {
|
|
3262
|
+
resetCollectionPending(pending);
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
if (!contains(at, changed)) continue;
|
|
3266
|
+
const relative = changed.slice(at.length);
|
|
3267
|
+
if (!relative.length) {
|
|
3268
|
+
resetCollectionPending(pending);
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
markDocumentReplace(dirtyEntry(pending, relative[0]), relative.slice(1));
|
|
3272
|
+
if (relative.length === 1 && member.kind !== "updated") pending.order = true;
|
|
3273
|
+
}
|
|
3274
|
+
if (change.order && contains(at, change.at)) {
|
|
3275
|
+
const relative = change.at.slice(at.length);
|
|
3276
|
+
if (!relative.length) pending.order = true;
|
|
3277
|
+
else markDocumentOrder(dirtyEntry(pending, relative[0]), relative.slice(1));
|
|
3278
|
+
}
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
if (!contains(at, change.at)) continue;
|
|
3282
|
+
const relative = change.at.slice(at.length);
|
|
3283
|
+
if (!relative.length) {
|
|
3284
|
+
resetCollectionPending(pending);
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
markDocumentTree(dirtyEntry(pending, relative[0]), relative.slice(1), change.before !== change.after, change.nodes.map((node) => node.id));
|
|
3288
|
+
}
|
|
3289
|
+
};
|
|
3290
|
+
//#endregion
|
|
3291
|
+
//#region core/src/projection/source/boundary.ts
|
|
3292
|
+
const sourceError = (source, cause) => new require_advanced.ProjectionError("source", source.name, [source.outputs[0].revision()], cause);
|
|
3293
|
+
const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
|
|
1643
3294
|
const state = createValueOutput();
|
|
1644
3295
|
let boundary;
|
|
1645
3296
|
let pendingReset = false;
|
|
@@ -1746,8 +3397,35 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
|
|
|
1746
3397
|
}
|
|
1747
3398
|
};
|
|
1748
3399
|
};
|
|
3400
|
+
/** Applies source hints to an output draft; exact net change semantics remain in the output. */
|
|
3401
|
+
const stageCollectionRead = (state, read, active, options) => {
|
|
3402
|
+
const evaluation = state.begin(active, options.reset);
|
|
3403
|
+
if (options.reset) {
|
|
3404
|
+
const ids = read.ids();
|
|
3405
|
+
require_advanced.profile.collectionView.idsScanned(ids.length);
|
|
3406
|
+
for (const key of ids) {
|
|
3407
|
+
require_advanced.profile.collectionView.mapped();
|
|
3408
|
+
evaluation.output.set(key, read.get(key));
|
|
3409
|
+
}
|
|
3410
|
+
evaluation.output.order(ids);
|
|
3411
|
+
} else {
|
|
3412
|
+
const previous = state.current(() => void 0);
|
|
3413
|
+
const keys = options.candidates ? [...new Set(options.candidates)] : [...new Set([...previous.ids(), ...read.ids()])];
|
|
3414
|
+
if (!options.candidates) require_advanced.profile.collectionView.idsScanned(keys.length);
|
|
3415
|
+
for (const key of keys) if (read.has(key)) {
|
|
3416
|
+
require_advanced.profile.collectionView.mapped();
|
|
3417
|
+
evaluation.output.set(key, read.get(key));
|
|
3418
|
+
} else evaluation.output.remove(key);
|
|
3419
|
+
if (options.orderMayChange || !options.candidates) {
|
|
3420
|
+
const ids = read.ids();
|
|
3421
|
+
if (options.candidates) require_advanced.profile.collectionView.idsScanned(ids.length);
|
|
3422
|
+
evaluation.output.order(ids);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
return state.seal(options.reset, options.isEqual ?? Object.is);
|
|
3426
|
+
};
|
|
1749
3427
|
const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSource) => {
|
|
1750
|
-
const state =
|
|
3428
|
+
const state = createCollectionOutput();
|
|
1751
3429
|
let boundary;
|
|
1752
3430
|
let pendingReset = false;
|
|
1753
3431
|
let pendingCause;
|
|
@@ -1793,7 +3471,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
1793
3471
|
contextCause = cause;
|
|
1794
3472
|
let active = true;
|
|
1795
3473
|
try {
|
|
1796
|
-
const changed =
|
|
3474
|
+
const changed = stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
|
|
1797
3475
|
reset: pendingReset,
|
|
1798
3476
|
...fullScan ? {} : { candidates },
|
|
1799
3477
|
orderMayChange,
|
|
@@ -1831,7 +3509,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
1831
3509
|
scheduler.initialize(() => {
|
|
1832
3510
|
let active = true;
|
|
1833
3511
|
try {
|
|
1834
|
-
|
|
3512
|
+
stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
|
|
1835
3513
|
reset: true,
|
|
1836
3514
|
isEqual: equality
|
|
1837
3515
|
});
|
|
@@ -1867,129 +3545,30 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
1867
3545
|
}
|
|
1868
3546
|
};
|
|
1869
3547
|
};
|
|
3548
|
+
//#endregion
|
|
3549
|
+
//#region core/src/projection/source/document.ts
|
|
1870
3550
|
const documentRootTarget = Object.freeze({
|
|
1871
3551
|
kind: "value",
|
|
1872
3552
|
at: Object.freeze([])
|
|
1873
3553
|
});
|
|
1874
|
-
const
|
|
1875
|
-
reset: false,
|
|
1876
|
-
order: false,
|
|
1877
|
-
entries: /* @__PURE__ */ new Map()
|
|
1878
|
-
});
|
|
1879
|
-
const resetCollectionPending = (pending) => {
|
|
1880
|
-
pending.reset = true;
|
|
1881
|
-
pending.order = false;
|
|
1882
|
-
pending.entries.clear();
|
|
1883
|
-
};
|
|
1884
|
-
const clearCollectionPending = (pending) => {
|
|
1885
|
-
pending.reset = false;
|
|
1886
|
-
pending.order = false;
|
|
1887
|
-
pending.entries.clear();
|
|
1888
|
-
};
|
|
1889
|
-
const dirtyEntry = (pending, key) => {
|
|
1890
|
-
let dirty = pending.entries.get(key);
|
|
1891
|
-
if (!dirty) {
|
|
1892
|
-
dirty = createDocumentDirty();
|
|
1893
|
-
pending.entries.set(key, dirty);
|
|
1894
|
-
}
|
|
1895
|
-
return dirty;
|
|
1896
|
-
};
|
|
1897
|
-
const mergeValueChanges = (selector, dirty, changes) => {
|
|
1898
|
-
if (selector.tree) return;
|
|
1899
|
-
const at = selector.address;
|
|
1900
|
-
for (const change of changes.changes) {
|
|
1901
|
-
if (change.kind === "reset") {
|
|
1902
|
-
markDocumentReplace(dirty, []);
|
|
1903
|
-
return;
|
|
1904
|
-
}
|
|
1905
|
-
if (change.kind === "members") {
|
|
1906
|
-
for (const member of change.members) {
|
|
1907
|
-
const changed = [...change.at, member.key];
|
|
1908
|
-
if (require_issue.contains(changed, at)) markDocumentReplace(dirty, []);
|
|
1909
|
-
else if (require_issue.contains(at, changed)) markDocumentReplace(dirty, changed.slice(at.length));
|
|
1910
|
-
}
|
|
1911
|
-
if (change.order && require_issue.contains(at, change.at)) markDocumentOrder(dirty, change.at.slice(at.length));
|
|
1912
|
-
continue;
|
|
1913
|
-
}
|
|
1914
|
-
if (require_issue.contains(at, change.at)) markDocumentTree(dirty, change.at.slice(at.length), change.before !== change.after, change.nodes.map((node) => node.id));
|
|
1915
|
-
}
|
|
1916
|
-
};
|
|
1917
|
-
const mergeCollectionChanges = (selector, pending, changes) => {
|
|
1918
|
-
if (pending.reset) return;
|
|
1919
|
-
const at = selector.address;
|
|
1920
|
-
for (const change of changes.changes) {
|
|
1921
|
-
if (change.kind === "reset") {
|
|
1922
|
-
resetCollectionPending(pending);
|
|
1923
|
-
return;
|
|
1924
|
-
}
|
|
1925
|
-
if (selector.tree?.kind === "nodes") {
|
|
1926
|
-
if (change.kind === "members") {
|
|
1927
|
-
for (const member of change.members) if (require_issue.contains([...change.at, member.key], at)) {
|
|
1928
|
-
resetCollectionPending(pending);
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
} else if (change.at.length === at.length && require_issue.contains(change.at, at)) for (const node of change.nodes) {
|
|
1932
|
-
markDocumentReplace(dirtyEntry(pending, node.id), []);
|
|
1933
|
-
pending.order ||= node.kind !== "updated";
|
|
1934
|
-
}
|
|
1935
|
-
else if (require_issue.contains(change.at, at)) {
|
|
1936
|
-
resetCollectionPending(pending);
|
|
1937
|
-
return;
|
|
1938
|
-
}
|
|
1939
|
-
continue;
|
|
1940
|
-
}
|
|
1941
|
-
if (change.kind === "members") {
|
|
1942
|
-
for (const member of change.members) {
|
|
1943
|
-
const changed = [...change.at, member.key];
|
|
1944
|
-
if (require_issue.contains(changed, at)) {
|
|
1945
|
-
resetCollectionPending(pending);
|
|
1946
|
-
return;
|
|
1947
|
-
}
|
|
1948
|
-
if (!require_issue.contains(at, changed)) continue;
|
|
1949
|
-
const relative = changed.slice(at.length);
|
|
1950
|
-
if (!relative.length) {
|
|
1951
|
-
resetCollectionPending(pending);
|
|
1952
|
-
return;
|
|
1953
|
-
}
|
|
1954
|
-
markDocumentReplace(dirtyEntry(pending, relative[0]), relative.slice(1));
|
|
1955
|
-
if (relative.length === 1 && member.kind !== "updated") pending.order = true;
|
|
1956
|
-
}
|
|
1957
|
-
if (change.order && require_issue.contains(at, change.at)) {
|
|
1958
|
-
const relative = change.at.slice(at.length);
|
|
1959
|
-
if (!relative.length) pending.order = true;
|
|
1960
|
-
else markDocumentOrder(dirtyEntry(pending, relative[0]), relative.slice(1));
|
|
1961
|
-
}
|
|
1962
|
-
continue;
|
|
1963
|
-
}
|
|
1964
|
-
if (!require_issue.contains(at, change.at)) continue;
|
|
1965
|
-
const relative = change.at.slice(at.length);
|
|
1966
|
-
if (!relative.length) {
|
|
1967
|
-
resetCollectionPending(pending);
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
markDocumentTree(dirtyEntry(pending, relative[0]), relative.slice(1), change.before !== change.after, change.nodes.map((node) => node.id));
|
|
1971
|
-
}
|
|
1972
|
-
};
|
|
1973
|
-
const createSourceRegistry = (scheduler) => {
|
|
3554
|
+
const createDocumentSourceRegistry = (scheduler) => {
|
|
1974
3555
|
const documents = /* @__PURE__ */ new Map();
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
const documentConnection = (runtime) => {
|
|
1979
|
-
const state = require_access.accessOf(runtime);
|
|
3556
|
+
const connection = (runtime) => {
|
|
3557
|
+
const context = require_context.contextOf(runtime);
|
|
3558
|
+
const state = context.state;
|
|
1980
3559
|
const cached = documents.get(state);
|
|
1981
3560
|
if (cached) return cached;
|
|
1982
3561
|
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1983
3562
|
const bindings = /* @__PURE__ */ new Set();
|
|
1984
|
-
const index = new
|
|
3563
|
+
const index = new SubscriptionIndex(state.schema);
|
|
1985
3564
|
const candidates = /* @__PURE__ */ new Set();
|
|
1986
3565
|
const guard = (locked) => {
|
|
1987
|
-
state.projectionLocks
|
|
3566
|
+
state.projectionLocks += locked ? 1 : -1;
|
|
1988
3567
|
};
|
|
1989
3568
|
scheduler.guards.add(guard);
|
|
1990
3569
|
let closed = false;
|
|
1991
3570
|
let detach = () => void 0;
|
|
1992
|
-
const
|
|
3571
|
+
const documentConnection = {
|
|
1993
3572
|
add(binding) {
|
|
1994
3573
|
if (closed) throw new Error("Document projection connection is closed.");
|
|
1995
3574
|
bindings.add(binding);
|
|
@@ -2000,7 +3579,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2000
3579
|
active = false;
|
|
2001
3580
|
bindings.delete(binding);
|
|
2002
3581
|
index.delete(binding.target, binding);
|
|
2003
|
-
if (!bindings.size)
|
|
3582
|
+
if (!bindings.size) documentConnection.close();
|
|
2004
3583
|
};
|
|
2005
3584
|
},
|
|
2006
3585
|
close() {
|
|
@@ -2014,7 +3593,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2014
3593
|
documents.delete(state);
|
|
2015
3594
|
}
|
|
2016
3595
|
};
|
|
2017
|
-
detach =
|
|
3596
|
+
detach = context.notifications.attachProjection({
|
|
2018
3597
|
capture: (commit) => {
|
|
2019
3598
|
candidates.clear();
|
|
2020
3599
|
index.collect(commit.changes, (binding) => candidates.add(binding));
|
|
@@ -2027,112 +3606,14 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2027
3606
|
scheduler.run();
|
|
2028
3607
|
}
|
|
2029
3608
|
});
|
|
2030
|
-
documents.set(state,
|
|
2031
|
-
return
|
|
3609
|
+
documents.set(state, documentConnection);
|
|
3610
|
+
return documentConnection;
|
|
2032
3611
|
};
|
|
2033
|
-
const
|
|
2034
|
-
if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
|
|
2035
|
-
const source = definition.source;
|
|
2036
|
-
let latest = source.initial;
|
|
2037
|
-
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
|
|
2038
|
-
return {
|
|
2039
|
-
...boundary,
|
|
2040
|
-
write: {
|
|
2041
|
-
kind: "value",
|
|
2042
|
-
set(value) {
|
|
2043
|
-
scheduler.assertIdle();
|
|
2044
|
-
latest = value;
|
|
2045
|
-
boundary.mark();
|
|
2046
|
-
scheduler.run();
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
};
|
|
2050
|
-
};
|
|
2051
|
-
const collectionInput = (definition) => {
|
|
2052
|
-
if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
|
|
2053
|
-
const values = new Map(definition.source.initial);
|
|
2054
|
-
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_advanced.mapRead(values), definition.output.equality, () => void 0);
|
|
2055
|
-
return {
|
|
2056
|
-
...boundary,
|
|
2057
|
-
write: {
|
|
2058
|
-
kind: "collection",
|
|
2059
|
-
update(run) {
|
|
2060
|
-
scheduler.assertIdle();
|
|
2061
|
-
const staged = /* @__PURE__ */ new Map();
|
|
2062
|
-
const operations = [];
|
|
2063
|
-
let active = true;
|
|
2064
|
-
const draft = Object.freeze({
|
|
2065
|
-
get: (key) => {
|
|
2066
|
-
require_advanced.assertScope(() => active);
|
|
2067
|
-
const entry = staged.get(key);
|
|
2068
|
-
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
2069
|
-
},
|
|
2070
|
-
has: (key) => {
|
|
2071
|
-
require_advanced.assertScope(() => active);
|
|
2072
|
-
const entry = staged.get(key);
|
|
2073
|
-
return entry ? entry.present : values.has(key);
|
|
2074
|
-
},
|
|
2075
|
-
set: (key, value) => {
|
|
2076
|
-
require_advanced.assertScope(() => active);
|
|
2077
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2078
|
-
staged.set(key, {
|
|
2079
|
-
present: true,
|
|
2080
|
-
value
|
|
2081
|
-
});
|
|
2082
|
-
operations.push({
|
|
2083
|
-
kind: "set",
|
|
2084
|
-
key,
|
|
2085
|
-
value
|
|
2086
|
-
});
|
|
2087
|
-
},
|
|
2088
|
-
remove: (key) => {
|
|
2089
|
-
require_advanced.assertScope(() => active);
|
|
2090
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2091
|
-
staged.set(key, { present: false });
|
|
2092
|
-
operations.push({
|
|
2093
|
-
kind: "remove",
|
|
2094
|
-
key
|
|
2095
|
-
});
|
|
2096
|
-
}
|
|
2097
|
-
});
|
|
2098
|
-
try {
|
|
2099
|
-
require_advanced.assertSynchronous(run(draft));
|
|
2100
|
-
} finally {
|
|
2101
|
-
active = false;
|
|
2102
|
-
}
|
|
2103
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
2104
|
-
let structural = false;
|
|
2105
|
-
let changed = false;
|
|
2106
|
-
for (const operation of operations) {
|
|
2107
|
-
const present = values.has(operation.key);
|
|
2108
|
-
if (operation.kind === "set") {
|
|
2109
|
-
if (present && Object.is(values.get(operation.key), operation.value)) continue;
|
|
2110
|
-
structural ||= !present;
|
|
2111
|
-
values.set(operation.key, operation.value);
|
|
2112
|
-
} else {
|
|
2113
|
-
if (!present) continue;
|
|
2114
|
-
structural = true;
|
|
2115
|
-
values.delete(operation.key);
|
|
2116
|
-
}
|
|
2117
|
-
candidates.add(operation.key);
|
|
2118
|
-
changed = true;
|
|
2119
|
-
}
|
|
2120
|
-
if (!changed) return;
|
|
2121
|
-
boundary.mark({
|
|
2122
|
-
candidates,
|
|
2123
|
-
orderMayChange: structural
|
|
2124
|
-
});
|
|
2125
|
-
scheduler.run();
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
};
|
|
2129
|
-
};
|
|
2130
|
-
const documentSource = (definition) => {
|
|
3612
|
+
const materialize = (definition) => {
|
|
2131
3613
|
if (definition.source.kind !== "document") throw new Error("Invalid document source.");
|
|
2132
3614
|
const { document, selector } = definition.source;
|
|
2133
|
-
const state =
|
|
2134
|
-
const
|
|
2135
|
-
const currentValue = () => require_issue.read(state.document, selector.address, state.schema);
|
|
3615
|
+
const state = require_context.contextOf(document).state;
|
|
3616
|
+
const current = () => resolveValue(state.schema, state.document, selector.address);
|
|
2136
3617
|
const disposed = () => {
|
|
2137
3618
|
if (state.disposed) throw new Error("Document has been disposed.");
|
|
2138
3619
|
};
|
|
@@ -2140,17 +3621,18 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2140
3621
|
const dirty = createDocumentDirty();
|
|
2141
3622
|
const read = (previous) => {
|
|
2142
3623
|
disposed();
|
|
2143
|
-
const
|
|
2144
|
-
const
|
|
2145
|
-
|
|
3624
|
+
const resolved = current();
|
|
3625
|
+
const node = resolved?.node;
|
|
3626
|
+
const value = resolved?.value;
|
|
3627
|
+
if (selector.tree?.kind === "root") return node?.kind === "tree" && require_context.isRecord(value) && typeof value.rootId === "string" ? value.rootId : void 0;
|
|
2146
3628
|
if (selector.tree?.kind === "node") {
|
|
2147
|
-
if (node?.kind !== "tree" || !
|
|
2148
|
-
const entry = Object.hasOwn(
|
|
2149
|
-
if (
|
|
2150
|
-
return entry ?
|
|
3629
|
+
if (node?.kind !== "tree" || !require_context.isRecord(value) || !require_context.isRecord(value.nodes)) return void 0;
|
|
3630
|
+
const entry = Object.hasOwn(value.nodes, selector.tree.id) ? value.nodes[selector.tree.id] : void 0;
|
|
3631
|
+
if (require_advanced.equalTreeNode(previous, entry)) return previous;
|
|
3632
|
+
return entry ? require_advanced.copyTreeNode(entry) : void 0;
|
|
2151
3633
|
}
|
|
2152
3634
|
if (!node) return void 0;
|
|
2153
|
-
return previous === void 0 ?
|
|
3635
|
+
return previous === void 0 ? require_advanced.copyValue(node, value) : materializeDocumentValue(node, previous, value, dirty);
|
|
2154
3636
|
};
|
|
2155
3637
|
const initial = read();
|
|
2156
3638
|
let boundary;
|
|
@@ -2158,7 +3640,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2158
3640
|
if (failed) markDocumentReplace(dirty, []);
|
|
2159
3641
|
else clearDocumentDirty(dirty);
|
|
2160
3642
|
});
|
|
2161
|
-
const remove =
|
|
3643
|
+
const remove = connection(document).add({
|
|
2162
3644
|
target: selector,
|
|
2163
3645
|
capture: (commit) => {
|
|
2164
3646
|
try {
|
|
@@ -2176,8 +3658,9 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2176
3658
|
const pending = createCollectionPending();
|
|
2177
3659
|
const read = (active, previous) => {
|
|
2178
3660
|
disposed();
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
3661
|
+
const resolved = current();
|
|
3662
|
+
const node = resolved?.node;
|
|
3663
|
+
const value = resolved?.value;
|
|
2181
3664
|
const treeNodes = selector.tree?.kind === "nodes";
|
|
2182
3665
|
const entryNode = !treeNodes && node ? require_advanced.collectionEntryNode(node) : void 0;
|
|
2183
3666
|
const snapshots = /* @__PURE__ */ new Map();
|
|
@@ -2185,29 +3668,29 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2185
3668
|
if (pending.reset) markDocumentReplace(replacement, []);
|
|
2186
3669
|
const ids = () => {
|
|
2187
3670
|
require_advanced.assertScope(active);
|
|
2188
|
-
if (treeNodes) return node?.kind === "tree" &&
|
|
2189
|
-
if (node?.kind === "map" &&
|
|
2190
|
-
if (node?.kind === "table" &&
|
|
2191
|
-
if (node?.kind === "list" && Array.isArray(value)) return Object.freeze(
|
|
3671
|
+
if (treeNodes) return node?.kind === "tree" && require_context.isRecord(value) && require_context.isRecord(value.nodes) ? Object.freeze(Object.keys(value.nodes)) : Object.freeze([]);
|
|
3672
|
+
if (node?.kind === "map" && require_context.isRecord(value)) return Object.freeze(Object.keys(value));
|
|
3673
|
+
if (node?.kind === "table" && require_context.isRecord(value) && Array.isArray(value.ids)) return Object.freeze([...value.ids]);
|
|
3674
|
+
if (node?.kind === "list" && Array.isArray(value)) return Object.freeze(require_advanced.toArray(require_advanced.indexedKeys(value, node.keyOf)));
|
|
2192
3675
|
return Object.freeze([]);
|
|
2193
3676
|
};
|
|
2194
3677
|
const has = (key) => {
|
|
2195
3678
|
require_advanced.assertScope(active);
|
|
2196
|
-
if (treeNodes) return Boolean(node?.kind === "tree" &&
|
|
2197
|
-
if (node?.kind === "map" &&
|
|
2198
|
-
if (node?.kind === "table" &&
|
|
2199
|
-
if (node?.kind === "list" && Array.isArray(value)) return
|
|
3679
|
+
if (treeNodes) return Boolean(node?.kind === "tree" && require_context.isRecord(value) && require_context.isRecord(value.nodes) && Object.hasOwn(value.nodes, key));
|
|
3680
|
+
if (node?.kind === "map" && require_context.isRecord(value)) return Object.hasOwn(value, key);
|
|
3681
|
+
if (node?.kind === "table" && require_context.isRecord(value) && require_context.isRecord(value.byId)) return Object.hasOwn(value.byId, key);
|
|
3682
|
+
if (node?.kind === "list" && Array.isArray(value)) return require_advanced.indexedKeys(value, node.keyOf).index(key) >= 0;
|
|
2200
3683
|
return false;
|
|
2201
3684
|
};
|
|
2202
3685
|
const raw = (key) => {
|
|
2203
3686
|
if (treeNodes) {
|
|
2204
|
-
if (node?.kind !== "tree" || !
|
|
3687
|
+
if (node?.kind !== "tree" || !require_context.isRecord(value) || !require_context.isRecord(value.nodes)) return void 0;
|
|
2205
3688
|
return Object.hasOwn(value.nodes, key) ? value.nodes[key] : void 0;
|
|
2206
3689
|
}
|
|
2207
|
-
if (node?.kind === "map" &&
|
|
2208
|
-
if (node?.kind === "table" &&
|
|
3690
|
+
if (node?.kind === "map" && require_context.isRecord(value)) return Object.hasOwn(value, key) ? value[key] : void 0;
|
|
3691
|
+
if (node?.kind === "table" && require_context.isRecord(value) && require_context.isRecord(value.byId)) return Object.hasOwn(value.byId, key) ? value.byId[key] : void 0;
|
|
2209
3692
|
if (node?.kind === "list" && Array.isArray(value)) {
|
|
2210
|
-
const index =
|
|
3693
|
+
const index = require_advanced.indexedKeys(value, node.keyOf).index(key);
|
|
2211
3694
|
return index < 0 ? void 0 : value[index];
|
|
2212
3695
|
}
|
|
2213
3696
|
};
|
|
@@ -2220,9 +3703,9 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2220
3703
|
if (treeNodes) {
|
|
2221
3704
|
const entry = current;
|
|
2222
3705
|
const before = previous.has(key) ? previous.get(key) : void 0;
|
|
2223
|
-
snapshots.set(key,
|
|
3706
|
+
snapshots.set(key, require_advanced.equalTreeNode(before, entry) ? before : require_advanced.copyTreeNode(entry));
|
|
2224
3707
|
} else if (!entryNode) snapshots.set(key, current);
|
|
2225
|
-
else if (!previous.has(key)) snapshots.set(key,
|
|
3708
|
+
else if (!previous.has(key)) snapshots.set(key, require_advanced.copyValue(entryNode, current));
|
|
2226
3709
|
else {
|
|
2227
3710
|
const dirty = pending.entries.get(key) ?? (pending.reset ? replacement : void 0);
|
|
2228
3711
|
snapshots.set(key, dirty ? materializeDocumentValue(entryNode, previous.get(key), current, dirty) : previous.get(key));
|
|
@@ -2239,7 +3722,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2239
3722
|
if (failed) resetCollectionPending(pending);
|
|
2240
3723
|
else clearCollectionPending(pending);
|
|
2241
3724
|
});
|
|
2242
|
-
const remove =
|
|
3725
|
+
const remove = connection(document).add({
|
|
2243
3726
|
target: selector,
|
|
2244
3727
|
capture: (commit) => {
|
|
2245
3728
|
try {
|
|
@@ -2258,6 +3741,125 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2258
3741
|
boundary.detach(remove);
|
|
2259
3742
|
return boundary;
|
|
2260
3743
|
};
|
|
3744
|
+
return {
|
|
3745
|
+
materialize,
|
|
3746
|
+
attachRoot(runtime, handlers) {
|
|
3747
|
+
return connection(runtime).add({
|
|
3748
|
+
target: documentRootTarget,
|
|
3749
|
+
capture: handlers.capture,
|
|
3750
|
+
dispose: handlers.dispose
|
|
3751
|
+
});
|
|
3752
|
+
},
|
|
3753
|
+
dispose() {
|
|
3754
|
+
documents.forEach((document) => document.close());
|
|
3755
|
+
documents.clear();
|
|
3756
|
+
}
|
|
3757
|
+
};
|
|
3758
|
+
};
|
|
3759
|
+
//#endregion
|
|
3760
|
+
//#region core/src/projection/source/registry.ts
|
|
3761
|
+
const createSourceRegistry = (scheduler) => {
|
|
3762
|
+
const documents = createDocumentSourceRegistry(scheduler);
|
|
3763
|
+
const readables = /* @__PURE__ */ new Map();
|
|
3764
|
+
const externalValues = /* @__PURE__ */ new WeakMap();
|
|
3765
|
+
const externalCollections = /* @__PURE__ */ new WeakMap();
|
|
3766
|
+
const valueInput = (definition) => {
|
|
3767
|
+
if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
|
|
3768
|
+
const source = definition.source;
|
|
3769
|
+
let latest = source.initial;
|
|
3770
|
+
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
|
|
3771
|
+
return {
|
|
3772
|
+
...boundary,
|
|
3773
|
+
write: {
|
|
3774
|
+
kind: "value",
|
|
3775
|
+
set(value) {
|
|
3776
|
+
scheduler.assertIdle();
|
|
3777
|
+
latest = value;
|
|
3778
|
+
boundary.mark();
|
|
3779
|
+
scheduler.run();
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
};
|
|
3783
|
+
};
|
|
3784
|
+
const collectionInput = (definition) => {
|
|
3785
|
+
if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
|
|
3786
|
+
const values = new Map(definition.source.initial);
|
|
3787
|
+
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_advanced.mapRead(values), definition.output.equality, () => void 0);
|
|
3788
|
+
return {
|
|
3789
|
+
...boundary,
|
|
3790
|
+
write: {
|
|
3791
|
+
kind: "collection",
|
|
3792
|
+
update(run) {
|
|
3793
|
+
scheduler.assertIdle();
|
|
3794
|
+
const staged = /* @__PURE__ */ new Map();
|
|
3795
|
+
const operations = [];
|
|
3796
|
+
let active = true;
|
|
3797
|
+
const draft = Object.freeze({
|
|
3798
|
+
get: (key) => {
|
|
3799
|
+
require_advanced.assertScope(() => active);
|
|
3800
|
+
const entry = staged.get(key);
|
|
3801
|
+
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
3802
|
+
},
|
|
3803
|
+
has: (key) => {
|
|
3804
|
+
require_advanced.assertScope(() => active);
|
|
3805
|
+
const entry = staged.get(key);
|
|
3806
|
+
return entry ? entry.present : values.has(key);
|
|
3807
|
+
},
|
|
3808
|
+
set: (key, value) => {
|
|
3809
|
+
require_advanced.assertScope(() => active);
|
|
3810
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
3811
|
+
staged.set(key, {
|
|
3812
|
+
present: true,
|
|
3813
|
+
value
|
|
3814
|
+
});
|
|
3815
|
+
operations.push({
|
|
3816
|
+
kind: "set",
|
|
3817
|
+
key,
|
|
3818
|
+
value
|
|
3819
|
+
});
|
|
3820
|
+
},
|
|
3821
|
+
remove: (key) => {
|
|
3822
|
+
require_advanced.assertScope(() => active);
|
|
3823
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
3824
|
+
staged.set(key, { present: false });
|
|
3825
|
+
operations.push({
|
|
3826
|
+
kind: "remove",
|
|
3827
|
+
key
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3830
|
+
});
|
|
3831
|
+
try {
|
|
3832
|
+
require_advanced.assertSynchronous(run(draft));
|
|
3833
|
+
} finally {
|
|
3834
|
+
active = false;
|
|
3835
|
+
}
|
|
3836
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
3837
|
+
let structural = false;
|
|
3838
|
+
let changed = false;
|
|
3839
|
+
for (const operation of operations) {
|
|
3840
|
+
const present = values.has(operation.key);
|
|
3841
|
+
if (operation.kind === "set") {
|
|
3842
|
+
if (present && Object.is(values.get(operation.key), operation.value)) continue;
|
|
3843
|
+
structural ||= !present;
|
|
3844
|
+
values.set(operation.key, operation.value);
|
|
3845
|
+
} else {
|
|
3846
|
+
if (!present) continue;
|
|
3847
|
+
structural = true;
|
|
3848
|
+
values.delete(operation.key);
|
|
3849
|
+
}
|
|
3850
|
+
candidates.add(operation.key);
|
|
3851
|
+
changed = true;
|
|
3852
|
+
}
|
|
3853
|
+
if (!changed) return;
|
|
3854
|
+
boundary.mark({
|
|
3855
|
+
candidates,
|
|
3856
|
+
orderMayChange: structural
|
|
3857
|
+
});
|
|
3858
|
+
scheduler.run();
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
};
|
|
2261
3863
|
const readable = (definition) => {
|
|
2262
3864
|
if (definition.source.kind !== "readable") throw new Error("Invalid Readable source.");
|
|
2263
3865
|
const { readable, equality } = definition.source;
|
|
@@ -2296,9 +3898,8 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2296
3898
|
readables.delete(readable);
|
|
2297
3899
|
}
|
|
2298
3900
|
};
|
|
2299
|
-
const owner =
|
|
2300
|
-
if (owner) detachDocument =
|
|
2301
|
-
target: documentRootTarget,
|
|
3901
|
+
const owner = require_context.contextOf(readable, false)?.owner;
|
|
3902
|
+
if (owner) detachDocument = documents.attachRoot(owner, {
|
|
2302
3903
|
capture: () => receive(false),
|
|
2303
3904
|
dispose: () => {
|
|
2304
3905
|
members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
|
|
@@ -2440,16 +4041,15 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2440
4041
|
switch (definition.source.kind) {
|
|
2441
4042
|
case "value-input": return valueInput(definition);
|
|
2442
4043
|
case "collection-input": return collectionInput(definition);
|
|
2443
|
-
case "document": return
|
|
4044
|
+
case "document": return documents.materialize(definition);
|
|
2444
4045
|
case "readable": return readable(definition);
|
|
2445
4046
|
case "external-value": return externalValue(definition);
|
|
2446
4047
|
case "external-collection": return externalCollection(definition);
|
|
2447
4048
|
}
|
|
2448
4049
|
},
|
|
2449
4050
|
dispose() {
|
|
2450
|
-
documents.
|
|
4051
|
+
documents.dispose();
|
|
2451
4052
|
readables.forEach((connection) => connection.close());
|
|
2452
|
-
documents.clear();
|
|
2453
4053
|
readables.clear();
|
|
2454
4054
|
}
|
|
2455
4055
|
};
|
|
@@ -2693,12 +4293,12 @@ const createProjectionRuntime = (options) => {
|
|
|
2693
4293
|
});
|
|
2694
4294
|
};
|
|
2695
4295
|
//#endregion
|
|
2696
|
-
exports.DocumentDisposedError =
|
|
2697
|
-
exports.DocumentReentrancyError =
|
|
2698
|
-
exports.ParseError =
|
|
4296
|
+
exports.DocumentDisposedError = DocumentDisposedError;
|
|
4297
|
+
exports.DocumentReentrancyError = DocumentReentrancyError;
|
|
4298
|
+
exports.ParseError = require_advanced.ParseError;
|
|
2699
4299
|
exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
|
|
2700
4300
|
exports.ProjectionError = require_advanced.ProjectionError;
|
|
2701
|
-
exports.TransactionRejected =
|
|
4301
|
+
exports.TransactionRejected = TransactionRejected;
|
|
2702
4302
|
exports.asReadable = asReadable;
|
|
2703
4303
|
exports.createDocument = createDocument;
|
|
2704
4304
|
exports.createProjectionRuntime = createProjectionRuntime;
|
|
@@ -2710,10 +4310,11 @@ exports.map = require_advanced.map;
|
|
|
2710
4310
|
exports.object = require_advanced.object;
|
|
2711
4311
|
exports.observe = require_advanced.observe;
|
|
2712
4312
|
exports.optional = require_advanced.optional;
|
|
2713
|
-
exports.parse =
|
|
4313
|
+
exports.parse = require_advanced.parse;
|
|
2714
4314
|
exports.read = read;
|
|
2715
|
-
exports.replace =
|
|
2716
|
-
exports.
|
|
4315
|
+
exports.replace = replace;
|
|
4316
|
+
exports.select = select;
|
|
4317
|
+
exports.snapshot = snapshot;
|
|
2717
4318
|
exports.table = require_advanced.table;
|
|
2718
4319
|
exports.tree = require_advanced.tree;
|
|
2719
4320
|
exports.variant = require_advanced.variant;
|