doxum 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -7
- package/dist/{advanced-FgdTwu5b.d.ts → advanced-CLJCWkHa.d.cts} +3 -3
- package/dist/{advanced-COFJtWMT.js → advanced-CRNHfJBu.js} +482 -446
- package/dist/advanced-CRNHfJBu.js.map +1 -0
- package/dist/{advanced-BRL5pN0y.cjs → advanced-Cle5g3B4.cjs} +615 -459
- package/dist/advanced-Cle5g3B4.cjs.map +1 -0
- package/dist/{advanced-DfKc7TZR.d.cts → advanced-d6J61er9.d.ts} +3 -3
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +1 -1
- package/dist/context-BcLKGfq8.cjs +149 -0
- package/dist/context-BcLKGfq8.cjs.map +1 -0
- package/dist/context-DFEaDUv5.js +84 -0
- package/dist/context-DFEaDUv5.js.map +1 -0
- package/dist/{contract-CdmDHRUq.d.cts → contract-SYJFP0lM.d.cts} +62 -88
- package/dist/{contract-KG5XnQZt.d.ts → contract-ULPQmiEs.d.ts} +62 -88
- package/dist/driver-BD3DQOW6.cjs +382 -0
- package/dist/driver-BD3DQOW6.cjs.map +1 -0
- package/dist/{driver-CLYJOGDQ.js → driver-yFfvDMX5.js} +155 -26
- package/dist/driver-yFfvDMX5.js.map +1 -0
- package/dist/index.cjs +2370 -406
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2293 -330
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +5 -5
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +1 -1
- package/dist/local-sync.d.ts +1 -1
- package/dist/local-sync.js +2 -2
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +2 -69
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +0 -1
- package/dist/react.d.ts +1 -2
- package/dist/react.js +3 -70
- package/dist/react.js.map +1 -1
- package/package.json +1 -6
- package/skills/doxum-runtime/SKILL.md +7 -1
- package/skills/doxum-runtime/references/guide.en.md +17 -6
- package/skills/doxum-runtime/references/guide.zh-CN.md +15 -5
- package/skills/doxum-runtime/references/invariants.en.md +5 -3
- package/skills/doxum-runtime/references/invariants.zh-CN.md +4 -3
- package/skills/doxum-runtime/references/projections.en.md +5 -0
- package/skills/doxum-runtime/references/projections.zh-CN.md +5 -0
- package/dist/access-BeSV5ve-.js +0 -816
- package/dist/access-BeSV5ve-.js.map +0 -1
- package/dist/access-Bienf28h.cjs +0 -911
- package/dist/access-Bienf28h.cjs.map +0 -1
- package/dist/advanced-BRL5pN0y.cjs.map +0 -1
- package/dist/advanced-COFJtWMT.js.map +0 -1
- package/dist/driver-CLYJOGDQ.js.map +0 -1
- package/dist/driver-Djr3BVvo.cjs +0 -241
- package/dist/driver-Djr3BVvo.cjs.map +0 -1
- package/dist/integration-C4Isp8lJ.js +0 -26
- package/dist/integration-C4Isp8lJ.js.map +0 -1
- package/dist/integration-Z6MsfFI7.cjs +0 -31
- package/dist/integration-Z6MsfFI7.cjs.map +0 -1
- package/dist/integration.cjs +0 -12
- package/dist/integration.d.cts +0 -17
- package/dist/integration.d.ts +0 -17
- package/dist/integration.js +0 -4
- package/dist/issue-DjK1xp5K.cjs +0 -881
- package/dist/issue-DjK1xp5K.cjs.map +0 -1
- package/dist/issue-W0hT8u8D.js +0 -642
- package/dist/issue-W0hT8u8D.js.map +0 -1
- package/dist/notification-Bkjau6B6.js +0 -232
- package/dist/notification-Bkjau6B6.js.map +0 -1
- package/dist/notification-DJfouMEb.cjs +0 -309
- package/dist/notification-DJfouMEb.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,105 +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
|
-
const affectsTarget = (impact, value) => queries.get(impact).affects(value);
|
|
10
|
-
const collectionImpact = (impact, selector) => queries.get(impact).collection(selector);
|
|
11
904
|
const createImpact = (schema, changes) => {
|
|
12
905
|
const reset = changes.changes.some((change) => change.kind === "reset");
|
|
13
906
|
let values;
|
|
14
907
|
let orders;
|
|
15
908
|
const index = () => {
|
|
16
909
|
if (!values) {
|
|
17
|
-
|
|
18
|
-
values = new
|
|
19
|
-
orders = new
|
|
20
|
-
|
|
21
|
-
for (const member of change.members) values.add(change.at, true, member.key);
|
|
22
|
-
if (change.order) orders.add(change.at, true);
|
|
23
|
-
} else if (change.kind === "tree") values.add(change.at, true);
|
|
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));
|
|
24
914
|
}
|
|
25
915
|
return values;
|
|
26
916
|
};
|
|
27
917
|
const cache = /* @__PURE__ */ new Map();
|
|
28
|
-
const
|
|
29
|
-
affects(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
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;
|
|
66
955
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
956
|
+
}
|
|
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);
|
|
71
968
|
continue;
|
|
72
969
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
if (!require_issue.contains(at, change.at)) continue;
|
|
79
|
-
if (change.at.length === at.length) continue;
|
|
80
|
-
const id = change.at[at.length];
|
|
81
|
-
updated.add(id);
|
|
970
|
+
const result = { kind: "reset" };
|
|
971
|
+
cache.set(key, result);
|
|
972
|
+
return result;
|
|
82
973
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
added,
|
|
88
|
-
removed,
|
|
89
|
-
updated,
|
|
90
|
-
orderChanged
|
|
91
|
-
};
|
|
92
|
-
cache.set(key, result);
|
|
93
|
-
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);
|
|
94
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;
|
|
95
990
|
};
|
|
96
|
-
|
|
991
|
+
return Object.freeze({
|
|
97
992
|
kind: reset ? "reset" : "incremental",
|
|
98
|
-
affects: (pick) =>
|
|
99
|
-
collection: (pick) =>
|
|
993
|
+
affects: (pick) => affects(require_advanced.compilePath(schema, "value", pick)),
|
|
994
|
+
collection: (pick) => collection(require_advanced.compilePath(schema, "collection", pick))
|
|
100
995
|
});
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
}
|
|
103
1024
|
};
|
|
104
1025
|
//#endregion
|
|
105
1026
|
//#region core/src/history.ts
|
|
@@ -190,7 +1111,7 @@ const createHistory = (input) => {
|
|
|
190
1111
|
current: () => state,
|
|
191
1112
|
revision: () => revision,
|
|
192
1113
|
subscribe: (listener) => {
|
|
193
|
-
if (disposed) throw new
|
|
1114
|
+
if (disposed) throw new DocumentDisposedError();
|
|
194
1115
|
listeners.add(listener);
|
|
195
1116
|
return () => {
|
|
196
1117
|
listeners.delete(listener);
|
|
@@ -272,25 +1193,18 @@ const createHistory = (input) => {
|
|
|
272
1193
|
const installMember = (parent, key, present, value) => {
|
|
273
1194
|
if (Array.isArray(parent)) {
|
|
274
1195
|
const index = Number(key);
|
|
275
|
-
if (present) if (index < 0)
|
|
1196
|
+
if (present) if (index < 0) require_advanced.insert(parent, parent.length, value);
|
|
276
1197
|
else parent[index] = value;
|
|
277
|
-
else if (index >= 0)
|
|
278
|
-
} else
|
|
279
|
-
else if (Object.hasOwn(parent, key)) parent[key] = value;
|
|
280
|
-
else Object.defineProperty(parent, key, {
|
|
281
|
-
value,
|
|
282
|
-
writable: true,
|
|
283
|
-
configurable: true,
|
|
284
|
-
enumerable: true
|
|
285
|
-
});
|
|
1198
|
+
else if (index >= 0) require_advanced.remove(parent, index);
|
|
1199
|
+
} else require_context.installOwn(parent, key, present, value);
|
|
286
1200
|
};
|
|
287
1201
|
const orderOf = (node, value) => {
|
|
288
|
-
if (node.kind === "list") return
|
|
1202
|
+
if (node.kind === "list") return require_advanced.toArray(require_advanced.indexedKeys(value, node.keyOf));
|
|
289
1203
|
if (node.kind === "table") return [...value.ids];
|
|
290
1204
|
throw new Error("Order requires a table or list.");
|
|
291
1205
|
};
|
|
292
1206
|
const installOrder = (node, value, order) => {
|
|
293
|
-
if (node.kind === "list")
|
|
1207
|
+
if (node.kind === "list") require_advanced.install(value, node.keyOf, order);
|
|
294
1208
|
else if (node.kind === "table") value.ids = [...order];
|
|
295
1209
|
else throw new Error("Order requires a table or list.");
|
|
296
1210
|
};
|
|
@@ -309,15 +1223,11 @@ const forgetMember = (group, key) => {
|
|
|
309
1223
|
}
|
|
310
1224
|
};
|
|
311
1225
|
const emptyGroup = (group) => !group.order && (group.layout === "fixed" ? !group.members.some(Boolean) : !group.members?.size);
|
|
312
|
-
const copyNode = (node) => ({
|
|
313
|
-
...node,
|
|
314
|
-
children: [...node.children]
|
|
315
|
-
});
|
|
316
1226
|
const transition = (node, key, beforePresent, before, afterPresent, after) => {
|
|
317
1227
|
if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
|
|
318
|
-
if (node.kind !== "field" && beforePresent === afterPresent &&
|
|
319
|
-
const value = afterPresent ? node.kind === "field" ? after :
|
|
320
|
-
|
|
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");
|
|
321
1231
|
return !beforePresent ? {
|
|
322
1232
|
key,
|
|
323
1233
|
kind: "added",
|
|
@@ -358,7 +1268,7 @@ const diffMember = (changes, members, node, at, key, beforePresent, before, afte
|
|
|
358
1268
|
const left = before, right = after;
|
|
359
1269
|
const childAt = at.concat(key);
|
|
360
1270
|
const children = [];
|
|
361
|
-
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]);
|
|
362
1272
|
publishMembers(changes, childAt, children);
|
|
363
1273
|
return;
|
|
364
1274
|
}
|
|
@@ -371,8 +1281,8 @@ const diffMember = (changes, members, node, at, key, beforePresent, before, afte
|
|
|
371
1281
|
for (const id of Object.keys(left)) diffMember(changes, children, node.value, childAt, id, true, left[id], Object.hasOwn(right, id), right[id]);
|
|
372
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]);
|
|
373
1283
|
let order;
|
|
374
|
-
if (node.kind === "table" && !
|
|
375
|
-
|
|
1284
|
+
if (node.kind === "table" && !require_advanced.equal(a.ids, b.ids)) {
|
|
1285
|
+
require_advanced.profile.recorder("publishedOrderItems", b.ids.length);
|
|
376
1286
|
order = {
|
|
377
1287
|
before: a.ids,
|
|
378
1288
|
after: [...b.ids]
|
|
@@ -389,26 +1299,26 @@ const restore = (fact, schema, root, skip = 0) => {
|
|
|
389
1299
|
const at = skip ? fact.at.slice(skip) : fact.at;
|
|
390
1300
|
if (fact.kind === "member") {
|
|
391
1301
|
const member = fact.member;
|
|
392
|
-
const value = member.present ?
|
|
1302
|
+
const value = member.present ? require_advanced.copyValue(member.node, member.value) : void 0;
|
|
393
1303
|
if (!at.length) return value;
|
|
394
|
-
const location =
|
|
1304
|
+
const location = resolveLocated(schema, root, at);
|
|
395
1305
|
if (!location) throw new Error("Cannot restore an unresolved member.");
|
|
396
1306
|
installMember(location.parent, location.key, member.present, value);
|
|
397
1307
|
return root;
|
|
398
1308
|
}
|
|
399
|
-
const location =
|
|
1309
|
+
const location = resolveValue(schema, root, at);
|
|
400
1310
|
if (!location) throw new Error("Cannot restore an unresolved subtree.");
|
|
401
1311
|
if (fact.kind === "order") installOrder(location.node, location.value, fact.group.order);
|
|
402
1312
|
else {
|
|
403
1313
|
const tree = location.value;
|
|
404
1314
|
if (fact.before === null) delete tree.rootId;
|
|
405
1315
|
else tree.rootId = fact.before;
|
|
406
|
-
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));
|
|
407
1317
|
}
|
|
408
1318
|
return root;
|
|
409
1319
|
};
|
|
410
1320
|
const reconstructBefore = (node, current, records, offset) => {
|
|
411
|
-
let before =
|
|
1321
|
+
let before = require_advanced.copyValue(node, current);
|
|
412
1322
|
for (let i = records.length - 1; i >= 0; i--) if (records[i].kind !== "order") before = restore(records[i], node, before, offset);
|
|
413
1323
|
for (const record of records) if (record.kind === "order") before = restore(record, node, before, offset);
|
|
414
1324
|
return before;
|
|
@@ -417,7 +1327,7 @@ const sealMembers = (fact, changes) => {
|
|
|
417
1327
|
const members = [];
|
|
418
1328
|
for (const member of fact.members?.values() ?? []) {
|
|
419
1329
|
if (!member) continue;
|
|
420
|
-
const key =
|
|
1330
|
+
const key = memberKey(fact, member.key);
|
|
421
1331
|
const present = Object.hasOwn(fact.parent, key);
|
|
422
1332
|
const value = fact.parent[key];
|
|
423
1333
|
if (member.node.kind === "field") {
|
|
@@ -427,10 +1337,10 @@ const sealMembers = (fact, changes) => {
|
|
|
427
1337
|
}
|
|
428
1338
|
let order;
|
|
429
1339
|
if (fact.order) {
|
|
430
|
-
const current = fact.node.kind === "list" ?
|
|
431
|
-
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)) {
|
|
432
1342
|
const after = orderOf(fact.node, fact.value);
|
|
433
|
-
|
|
1343
|
+
require_advanced.profile.recorder("publishedOrderItems", after.length);
|
|
434
1344
|
order = {
|
|
435
1345
|
before: fact.order,
|
|
436
1346
|
after
|
|
@@ -439,20 +1349,19 @@ const sealMembers = (fact, changes) => {
|
|
|
439
1349
|
}
|
|
440
1350
|
publishMembers(changes, fact.at, members, order);
|
|
441
1351
|
};
|
|
442
|
-
const sealTree = (fact,
|
|
443
|
-
const location =
|
|
1352
|
+
const sealTree = (fact, canonical, changes) => {
|
|
1353
|
+
const location = resolveValue(canonical.schema, canonical.document, fact.at);
|
|
444
1354
|
const tree = location?.value;
|
|
445
|
-
|
|
446
|
-
if (node?.kind !== "tree") throw new Error("Tree schema disappeared before sealing.");
|
|
1355
|
+
if ((location?.node)?.kind !== "tree") throw new Error("Tree schema disappeared before sealing.");
|
|
447
1356
|
const nodes = [];
|
|
448
1357
|
for (const [id, before] of fact.nodes) {
|
|
449
1358
|
const after = Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
450
1359
|
if (!before && !after) continue;
|
|
451
|
-
if (before && after &&
|
|
1360
|
+
if (before && after && require_advanced.equalTreeNode(before, after)) continue;
|
|
452
1361
|
nodes.push(!before ? {
|
|
453
1362
|
id,
|
|
454
1363
|
kind: "added",
|
|
455
|
-
after:
|
|
1364
|
+
after: require_advanced.copyTreeNode(after)
|
|
456
1365
|
} : !after ? {
|
|
457
1366
|
id,
|
|
458
1367
|
kind: "removed",
|
|
@@ -461,7 +1370,7 @@ const sealTree = (fact, state, changes) => {
|
|
|
461
1370
|
id,
|
|
462
1371
|
kind: "updated",
|
|
463
1372
|
before,
|
|
464
|
-
after:
|
|
1373
|
+
after: require_advanced.copyTreeNode(after)
|
|
465
1374
|
});
|
|
466
1375
|
}
|
|
467
1376
|
const after = tree.rootId ?? null;
|
|
@@ -484,12 +1393,12 @@ var ChangeRecorder = class {
|
|
|
484
1393
|
this.state = state;
|
|
485
1394
|
}
|
|
486
1395
|
indexGroups() {
|
|
487
|
-
const index = this.index ??= new
|
|
1396
|
+
const index = this.index ??= new require_driver.AddressIndex();
|
|
488
1397
|
if (!this.indexedGroups) {
|
|
489
1398
|
this.indexedGroups = true;
|
|
490
1399
|
for (const fact of this.facts) if (fact.kind === "members") {
|
|
491
1400
|
index.add(fact.at, fact);
|
|
492
|
-
|
|
1401
|
+
require_advanced.profile.recorder("indexedGroups");
|
|
493
1402
|
}
|
|
494
1403
|
}
|
|
495
1404
|
return index;
|
|
@@ -514,7 +1423,7 @@ var ChangeRecorder = class {
|
|
|
514
1423
|
}
|
|
515
1424
|
return;
|
|
516
1425
|
}
|
|
517
|
-
if (fact.order &&
|
|
1426
|
+
if (fact.order && contains(at, fact.at)) children.push({
|
|
518
1427
|
kind: "order",
|
|
519
1428
|
at: fact.at,
|
|
520
1429
|
group: fact
|
|
@@ -522,14 +1431,14 @@ var ChangeRecorder = class {
|
|
|
522
1431
|
for (const member of fact.members?.values() ?? []) {
|
|
523
1432
|
if (!member) continue;
|
|
524
1433
|
const memberAt = fact.at.concat(member.key);
|
|
525
|
-
if (
|
|
1434
|
+
if (contains(at, memberAt)) children.push({
|
|
526
1435
|
kind: "member",
|
|
527
1436
|
at: memberAt,
|
|
528
1437
|
group: fact,
|
|
529
1438
|
member
|
|
530
1439
|
});
|
|
531
1440
|
}
|
|
532
|
-
} else if (
|
|
1441
|
+
} else if (contains(at, fact.at)) children.push(fact);
|
|
533
1442
|
})(at);
|
|
534
1443
|
if (present) value = reconstructBefore(node, value, children, at.length);
|
|
535
1444
|
this.releaseCovered(children);
|
|
@@ -538,7 +1447,7 @@ var ChangeRecorder = class {
|
|
|
538
1447
|
releaseCovered(children) {
|
|
539
1448
|
let changedGroups;
|
|
540
1449
|
for (const child of children) {
|
|
541
|
-
|
|
1450
|
+
require_advanced.profile.recorder("absorbed");
|
|
542
1451
|
if (child.kind === "member") {
|
|
543
1452
|
const { group, member } = child;
|
|
544
1453
|
forgetMember(group, member.key);
|
|
@@ -554,11 +1463,11 @@ var ChangeRecorder = class {
|
|
|
554
1463
|
this.facts.add(fact);
|
|
555
1464
|
if (fact.kind === "members") {
|
|
556
1465
|
this.groups.set(fact.parent, fact);
|
|
557
|
-
|
|
1466
|
+
require_advanced.profile.recorder("groups");
|
|
558
1467
|
if (!this.indexedGroups) return;
|
|
559
|
-
|
|
1468
|
+
require_advanced.profile.recorder("indexedGroups");
|
|
560
1469
|
}
|
|
561
|
-
(this.index ??= new
|
|
1470
|
+
(this.index ??= new require_driver.AddressIndex()).add(fact.at, fact);
|
|
562
1471
|
}
|
|
563
1472
|
unregister(fact) {
|
|
564
1473
|
this.facts.delete(fact);
|
|
@@ -586,14 +1495,14 @@ var ChangeRecorder = class {
|
|
|
586
1495
|
if (group.layout === "dynamic") (group.members ??= /* @__PURE__ */ new Map()).set(key, member);
|
|
587
1496
|
else if (definition.kind === "fixed") group.members[definition.slot] = member;
|
|
588
1497
|
else throw new Error("A fixed member group requires a fixed schema member.");
|
|
589
|
-
|
|
1498
|
+
require_advanced.profile.recorder("facts");
|
|
590
1499
|
}
|
|
591
1500
|
reset() {
|
|
592
1501
|
if (this.resetBefore) return;
|
|
593
1502
|
const value = this.absorb([], this.state.schema, this.state.document, true);
|
|
594
1503
|
this.resetBefore = { value };
|
|
595
1504
|
this.groups = /* @__PURE__ */ new WeakMap();
|
|
596
|
-
|
|
1505
|
+
require_advanced.profile.recorder("facts");
|
|
597
1506
|
}
|
|
598
1507
|
createGroup(container) {
|
|
599
1508
|
const group = container.layout.kind === "fixed" ? {
|
|
@@ -618,18 +1527,18 @@ var ChangeRecorder = class {
|
|
|
618
1527
|
return group;
|
|
619
1528
|
}
|
|
620
1529
|
order(container) {
|
|
621
|
-
|
|
1530
|
+
require_advanced.profile.recorder("orderCaptures");
|
|
622
1531
|
let group = this.groups.get(container.parent);
|
|
623
1532
|
if (group?.order || this.covered(container.at)) return;
|
|
624
1533
|
group ??= this.createGroup(container);
|
|
625
1534
|
group.order = orderOf(container.node, container.value);
|
|
626
|
-
|
|
627
|
-
|
|
1535
|
+
require_advanced.profile.recorder("orderSnapshots");
|
|
1536
|
+
require_advanced.profile.recorder("orderItems", group.order.length);
|
|
628
1537
|
}
|
|
629
1538
|
tree(container, id) {
|
|
630
1539
|
const { at, value: tree } = container;
|
|
631
1540
|
if (this.covered(at)) return;
|
|
632
|
-
const index = this.index ??= new
|
|
1541
|
+
const index = this.index ??= new require_driver.AddressIndex();
|
|
633
1542
|
let fact;
|
|
634
1543
|
for (const item of index.exact(at) ?? []) if (item.kind === "tree") fact = item;
|
|
635
1544
|
if (!fact) {
|
|
@@ -642,8 +1551,8 @@ var ChangeRecorder = class {
|
|
|
642
1551
|
this.register(fact);
|
|
643
1552
|
}
|
|
644
1553
|
if (id !== void 0 && !fact.nodes.has(id)) {
|
|
645
|
-
|
|
646
|
-
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);
|
|
647
1556
|
}
|
|
648
1557
|
}
|
|
649
1558
|
rollback() {
|
|
@@ -656,7 +1565,7 @@ var ChangeRecorder = class {
|
|
|
656
1565
|
const fact = facts[i];
|
|
657
1566
|
if (fact.kind === "members") for (const member of fact.members?.values() ?? []) {
|
|
658
1567
|
if (!member) continue;
|
|
659
|
-
installMember(fact.parent,
|
|
1568
|
+
installMember(fact.parent, memberKey(fact, member.key), member.present, member.present ? require_advanced.copyValue(member.node, member.value) : void 0);
|
|
660
1569
|
}
|
|
661
1570
|
else if (fact.kind === "tree") restore(fact, this.state.schema, this.state.document);
|
|
662
1571
|
}
|
|
@@ -664,19 +1573,19 @@ var ChangeRecorder = class {
|
|
|
664
1573
|
}
|
|
665
1574
|
seal() {
|
|
666
1575
|
if (this.resetBefore) {
|
|
667
|
-
if (
|
|
668
|
-
|
|
1576
|
+
if (require_advanced.equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
|
|
1577
|
+
require_advanced.profile.recorder("sealed");
|
|
669
1578
|
return require_driver.sealChanges([{
|
|
670
1579
|
kind: "reset",
|
|
671
1580
|
before: this.resetBefore.value,
|
|
672
|
-
after:
|
|
1581
|
+
after: require_advanced.copyValue(this.state.schema, this.state.document)
|
|
673
1582
|
}]);
|
|
674
1583
|
}
|
|
675
1584
|
const changes = [];
|
|
676
1585
|
for (const fact of this.facts) if (fact.kind === "members") sealMembers(fact, changes);
|
|
677
1586
|
else sealTree(fact, this.state, changes);
|
|
678
1587
|
const result = require_driver.sealChanges(changes);
|
|
679
|
-
|
|
1588
|
+
require_advanced.profile.recorder("sealed", result.changes.length);
|
|
680
1589
|
return result;
|
|
681
1590
|
}
|
|
682
1591
|
};
|
|
@@ -692,25 +1601,25 @@ var MutationSession = class {
|
|
|
692
1601
|
constructor(state) {
|
|
693
1602
|
this.state = state;
|
|
694
1603
|
this.recorder = new ChangeRecorder(state);
|
|
695
|
-
this.resolver =
|
|
1604
|
+
this.resolver = createAddressResolver(state.schema, state.document);
|
|
696
1605
|
this.resolvedRoot = state.document;
|
|
697
1606
|
}
|
|
698
1607
|
validate(node, value, at) {
|
|
699
|
-
const
|
|
700
|
-
if (
|
|
1608
|
+
const failure = require_advanced.checkValue(node, value, at);
|
|
1609
|
+
if (failure) require_driver.invalidValue(failure);
|
|
701
1610
|
}
|
|
702
1611
|
resolveContainer(at) {
|
|
703
|
-
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.");
|
|
704
1613
|
}
|
|
705
1614
|
/** Bind access-resolved canonical facts without walking the address again. */
|
|
706
1615
|
bind(at, node, value) {
|
|
707
|
-
return
|
|
1616
|
+
return resolveContainer(this.identity, this.generation, at, node, value) ?? require_driver.fail(at, "invalid-address", "Container does not exist.");
|
|
708
1617
|
}
|
|
709
1618
|
bindTree(at, node, value) {
|
|
710
|
-
return
|
|
1619
|
+
return resolveTreeContainer(this.identity, this.generation, at, node, value) ?? require_driver.fail(at, "invalid-tree", "Expected a tree.");
|
|
711
1620
|
}
|
|
712
1621
|
resolveTree(at) {
|
|
713
|
-
const location =
|
|
1622
|
+
const location = resolveValue(this.state.schema, this.state.document, at);
|
|
714
1623
|
return this.bindTree(at, location?.node, location?.value);
|
|
715
1624
|
}
|
|
716
1625
|
replace(at, value) {
|
|
@@ -718,7 +1627,7 @@ var MutationSession = class {
|
|
|
718
1627
|
this.validate(this.state.schema, value, at);
|
|
719
1628
|
if (Object.is(this.state.document, value)) return;
|
|
720
1629
|
this.recorder.reset();
|
|
721
|
-
this.state.document =
|
|
1630
|
+
this.state.document = require_advanced.copyValue(this.state.schema, value);
|
|
722
1631
|
this.invalidate();
|
|
723
1632
|
return;
|
|
724
1633
|
}
|
|
@@ -727,24 +1636,24 @@ var MutationSession = class {
|
|
|
727
1636
|
assignMember(container, key, value) {
|
|
728
1637
|
container = this.refresh(container);
|
|
729
1638
|
const member = this.definition(container, key);
|
|
730
|
-
if (container.layout.kind === "fixed" && member.node.kind !== "field" && member.node.kind !== "variant" && !member.node.optional) return
|
|
731
|
-
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");
|
|
732
1641
|
}
|
|
733
1642
|
removeMember(container, key) {
|
|
734
1643
|
this.writeMember(this.refresh(container), key, void 0, "remove");
|
|
735
1644
|
}
|
|
736
1645
|
refresh(container) {
|
|
737
|
-
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.");
|
|
738
1647
|
return container.generation === this.generation ? container : this.resolveContainer(container.at);
|
|
739
1648
|
}
|
|
740
1649
|
definition(container, key) {
|
|
741
1650
|
const layout = container.layout;
|
|
742
1651
|
const member = layout.kind === "dynamic" ? layout.entry : layout.members.get(key);
|
|
743
|
-
if (!member) return
|
|
1652
|
+
if (!member) return require_driver.fail(container.at.concat(key), "invalid-address", "Address does not exist.");
|
|
744
1653
|
return member;
|
|
745
1654
|
}
|
|
746
1655
|
/** Consume a current command-local container; bulk writes retain its storage. */
|
|
747
|
-
writeMember(container, key, value, operation, physicalKey =
|
|
1656
|
+
writeMember(container, key, value, operation, physicalKey = memberKey(container, key)) {
|
|
748
1657
|
const member = this.definition(container, key);
|
|
749
1658
|
return this.writeLocatedMember(container, key, member, physicalKey, value, operation);
|
|
750
1659
|
}
|
|
@@ -753,27 +1662,27 @@ var MutationSession = class {
|
|
|
753
1662
|
const parentNode = container.node;
|
|
754
1663
|
const node = member.node;
|
|
755
1664
|
const present = operation === "set";
|
|
756
|
-
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.");
|
|
757
1666
|
const entry = container.layout.kind === "dynamic";
|
|
758
|
-
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.");
|
|
759
1668
|
const existed = Object.hasOwn(container.parent, physicalKey);
|
|
760
1669
|
const previous = container.parent[physicalKey];
|
|
761
1670
|
if (existed === present && (!present || Object.is(previous, value))) return false;
|
|
762
1671
|
if (parentNode.kind === "map" || parentNode.kind === "table") {
|
|
763
|
-
const
|
|
764
|
-
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);
|
|
765
1674
|
}
|
|
766
1675
|
if (present) {
|
|
767
1676
|
if (node.kind === "field") {
|
|
768
|
-
const
|
|
769
|
-
if (
|
|
1677
|
+
const failure = require_advanced.checkValue(node, value, []);
|
|
1678
|
+
if (failure) return require_driver.invalidValue(failure, container.at.concat(key, ...failure.address));
|
|
770
1679
|
} else this.validate(node, value, container.at.concat(key));
|
|
771
|
-
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.");
|
|
772
1681
|
}
|
|
773
1682
|
const membershipChanged = entry && existed !== present;
|
|
774
1683
|
if (membershipChanged && (parentNode.kind === "list" || parentNode.kind === "table")) this.recorder.order(container);
|
|
775
1684
|
this.recorder.member(container, key, member, physicalKey);
|
|
776
|
-
const next = present ? node.kind === "field" ? value :
|
|
1685
|
+
const next = present ? node.kind === "field" ? value : require_advanced.copyValue(node, value) : void 0;
|
|
777
1686
|
installMember(container.parent, physicalKey, present, next);
|
|
778
1687
|
if (node.kind !== "field" || membershipChanged) this.invalidate();
|
|
779
1688
|
return membershipChanged;
|
|
@@ -782,7 +1691,7 @@ var MutationSession = class {
|
|
|
782
1691
|
this.generation++;
|
|
783
1692
|
if (this.resolvedRoot !== this.state.document) {
|
|
784
1693
|
this.resolvedRoot = this.state.document;
|
|
785
|
-
this.resolver =
|
|
1694
|
+
this.resolver = createAddressResolver(this.state.schema, this.state.document);
|
|
786
1695
|
} else this.resolver.invalidate();
|
|
787
1696
|
}
|
|
788
1697
|
finish() {
|
|
@@ -802,7 +1711,7 @@ function apply(session, changes, direction) {
|
|
|
802
1711
|
const container = session.resolveContainer(change.at);
|
|
803
1712
|
const { node, value: current } = container;
|
|
804
1713
|
if (change.order) {
|
|
805
|
-
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.");
|
|
806
1715
|
session.recorder.order(container);
|
|
807
1716
|
}
|
|
808
1717
|
let membershipChanged = false;
|
|
@@ -813,12 +1722,12 @@ function apply(session, changes, direction) {
|
|
|
813
1722
|
}
|
|
814
1723
|
if (change.order) {
|
|
815
1724
|
const keys = node.kind === "table" ? Object.keys(container.parent) : orderOf(node, current);
|
|
816
|
-
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.");
|
|
817
1726
|
installOrder(node, current, change.order[side]);
|
|
818
1727
|
session.invalidate();
|
|
819
1728
|
} else if (membershipChanged && node.kind === "table") {
|
|
820
1729
|
const ids = current.ids;
|
|
821
|
-
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.");
|
|
822
1731
|
}
|
|
823
1732
|
} else {
|
|
824
1733
|
const container = session.resolveTree(change.at);
|
|
@@ -830,31 +1739,195 @@ function apply(session, changes, direction) {
|
|
|
830
1739
|
else current.rootId = root;
|
|
831
1740
|
for (const item of change.nodes) {
|
|
832
1741
|
const next = side === "after" ? item.kind !== "removed" ? item.after : void 0 : item.kind !== "added" ? item.before : void 0;
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
1742
|
+
if (next !== void 0) {
|
|
1743
|
+
const diagnostic = require_advanced.checkTreePayload(node.value, next, change.at.concat(item.id));
|
|
1744
|
+
if (diagnostic) require_driver.invalidValue(diagnostic);
|
|
1745
|
+
}
|
|
1746
|
+
installMember(current.nodes, item.id, next !== void 0, next && require_advanced.copyTreeNode(next));
|
|
837
1747
|
}
|
|
838
|
-
|
|
1748
|
+
if (!require_context.validate(current)) return require_driver.fail(change.at, "invalid-tree", "Invalid tree structure.");
|
|
839
1749
|
session.invalidate();
|
|
840
1750
|
}
|
|
841
1751
|
}
|
|
842
1752
|
//#endregion
|
|
1753
|
+
//#region core/src/runtime/notification.ts
|
|
1754
|
+
const attachProjection = (notification, processor) => {
|
|
1755
|
+
const entry = {
|
|
1756
|
+
processor,
|
|
1757
|
+
active: true
|
|
1758
|
+
};
|
|
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
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
};
|
|
1769
|
+
const addRoot = (notification, listener) => {
|
|
1770
|
+
const entry = {
|
|
1771
|
+
listener,
|
|
1772
|
+
active: true
|
|
1773
|
+
};
|
|
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
|
|
843
1914
|
//#region core/src/runtime.ts
|
|
844
1915
|
const createDocument = (input) => {
|
|
845
1916
|
if (input.schema.kind !== "object") throw new TypeError("Document schema must be a root object node.");
|
|
846
|
-
const invalid =
|
|
847
|
-
if (invalid) throw new
|
|
1917
|
+
const invalid = require_advanced.checkValue(input.schema, input.initial);
|
|
1918
|
+
if (invalid) throw new require_advanced.ParseError(invalid);
|
|
848
1919
|
const state = {
|
|
849
1920
|
schema: input.schema,
|
|
850
|
-
document:
|
|
851
|
-
disposed: false
|
|
1921
|
+
document: require_advanced.copyValue(input.schema, input.initial),
|
|
1922
|
+
disposed: false,
|
|
1923
|
+
projectionLocks: 0
|
|
852
1924
|
};
|
|
853
1925
|
let revision = 0, busy = false;
|
|
854
|
-
|
|
1926
|
+
const notifications = createNotificationCenter(input.schema);
|
|
1927
|
+
let runtime, context;
|
|
855
1928
|
const idle = () => {
|
|
856
|
-
if (state.disposed) throw new
|
|
857
|
-
if (busy ||
|
|
1929
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
1930
|
+
if (busy || state.projectionLocks) throw new DocumentReentrancyError();
|
|
858
1931
|
};
|
|
859
1932
|
const writable = (intent) => {
|
|
860
1933
|
idle();
|
|
@@ -893,7 +1966,7 @@ const createDocument = (input) => {
|
|
|
893
1966
|
return {
|
|
894
1967
|
status: "committed",
|
|
895
1968
|
commit,
|
|
896
|
-
observerErrors:
|
|
1969
|
+
observerErrors: notifications.publish(commit, history.flush)
|
|
897
1970
|
};
|
|
898
1971
|
};
|
|
899
1972
|
function mutate(intent, recordHistory, execute) {
|
|
@@ -907,12 +1980,12 @@ const createDocument = (input) => {
|
|
|
907
1980
|
changes = session.finish();
|
|
908
1981
|
} catch (error) {
|
|
909
1982
|
session.rollback();
|
|
910
|
-
if (error instanceof
|
|
1983
|
+
if (error instanceof require_driver.MutationRejected) return {
|
|
911
1984
|
status: "rejected",
|
|
912
1985
|
issues: [error.issue],
|
|
913
1986
|
revision
|
|
914
1987
|
};
|
|
915
|
-
if (intent.kind === "update" && error instanceof
|
|
1988
|
+
if (intent.kind === "update" && error instanceof TransactionRejected) return {
|
|
916
1989
|
status: "rejected",
|
|
917
1990
|
issues: error.issues,
|
|
918
1991
|
revision
|
|
@@ -930,93 +2003,567 @@ const createDocument = (input) => {
|
|
|
930
2003
|
}
|
|
931
2004
|
runtime = {
|
|
932
2005
|
schema: input.schema,
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
2006
|
+
revision: () => revision,
|
|
2007
|
+
update: (run, options) => {
|
|
2008
|
+
const source = options?.source ?? "local";
|
|
2009
|
+
let value;
|
|
2010
|
+
const result = mutate({
|
|
2011
|
+
kind: "update",
|
|
2012
|
+
source
|
|
2013
|
+
}, options?.history ?? true, (session) => {
|
|
2014
|
+
value = run(createAccess({
|
|
2015
|
+
state,
|
|
2016
|
+
session
|
|
2017
|
+
}));
|
|
2018
|
+
if (value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") throw new TypeError("Document updates must be synchronous.");
|
|
2019
|
+
});
|
|
2020
|
+
return result.status === "rejected" ? result : {
|
|
2021
|
+
...result,
|
|
2022
|
+
value
|
|
2023
|
+
};
|
|
2024
|
+
},
|
|
2025
|
+
apply: (inputChanges, options) => mutate({
|
|
2026
|
+
kind: "apply",
|
|
2027
|
+
source: options?.source ?? "local"
|
|
2028
|
+
}, options?.history ?? true, (session) => {
|
|
2029
|
+
if (!options || options.expectedRevision !== revision) require_driver.fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
|
|
2030
|
+
apply(session, require_driver.decodeChanges(inputChanges), "forward");
|
|
2031
|
+
}),
|
|
2032
|
+
replace: (value, options) => {
|
|
2033
|
+
const source = options?.source ?? "system";
|
|
2034
|
+
return mutate({
|
|
2035
|
+
kind: "replace",
|
|
2036
|
+
source
|
|
2037
|
+
}, source !== "remote", (session) => session.replace([], value));
|
|
2038
|
+
},
|
|
2039
|
+
snapshot: () => {
|
|
2040
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2041
|
+
return require_advanced.copyValue(input.schema, state.document);
|
|
2042
|
+
},
|
|
2043
|
+
subscribe: ((pick, listener) => {
|
|
2044
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
2045
|
+
if (!listener) return notifications.subscribe(pick);
|
|
2046
|
+
const picks = Array.isArray(pick) ? pick : [pick];
|
|
2047
|
+
if (!picks.length) throw new TypeError("Expected at least one subscription path.");
|
|
2048
|
+
return notifications.subscribeTargets(picks.map((p) => require_advanced.compilePath(input.schema, "value", p)), listener);
|
|
2049
|
+
}),
|
|
2050
|
+
history: history.api,
|
|
2051
|
+
dispose: () => {
|
|
2052
|
+
if (state.disposed) return;
|
|
2053
|
+
idle();
|
|
2054
|
+
state.disposed = true;
|
|
2055
|
+
try {
|
|
2056
|
+
notifications.dispose();
|
|
2057
|
+
} finally {
|
|
2058
|
+
history.dispose();
|
|
2059
|
+
context.driver = void 0;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
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);
|
|
2072
|
+
return runtime;
|
|
2073
|
+
};
|
|
2074
|
+
//#endregion
|
|
2075
|
+
//#region core/src/runtime/readable.ts
|
|
2076
|
+
const asReadable = (runtime) => {
|
|
2077
|
+
const readable = {
|
|
2078
|
+
revision: runtime.revision,
|
|
2079
|
+
subscribe: runtime.subscribe
|
|
2080
|
+
};
|
|
2081
|
+
require_context.bindContext(readable, require_context.contextOf(runtime));
|
|
2082
|
+
return Object.freeze(readable);
|
|
2083
|
+
};
|
|
2084
|
+
//#endregion
|
|
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
|
+
}
|
|
939
2519
|
},
|
|
2520
|
+
change,
|
|
2521
|
+
revision,
|
|
2522
|
+
reset,
|
|
2523
|
+
cause
|
|
2524
|
+
});
|
|
2525
|
+
return {
|
|
2526
|
+
begin,
|
|
2527
|
+
seal,
|
|
2528
|
+
context,
|
|
2529
|
+
current: (check) => published.read(check),
|
|
940
2530
|
revision: () => revision,
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
source
|
|
947
|
-
}, options?.history ?? true, (session) => {
|
|
948
|
-
value = run(require_access.createAccess({
|
|
949
|
-
state,
|
|
950
|
-
session
|
|
951
|
-
}));
|
|
952
|
-
if (value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function") throw new TypeError("Document updates must be synchronous.");
|
|
953
|
-
});
|
|
954
|
-
return result.status === "rejected" ? result : {
|
|
955
|
-
...result,
|
|
956
|
-
value
|
|
957
|
-
};
|
|
2531
|
+
reset: () => reset,
|
|
2532
|
+
publish: () => {
|
|
2533
|
+
if (change) published.publish(staged, nextIds, change.kind === "reset");
|
|
2534
|
+
if (change && initialized) revision++;
|
|
2535
|
+
initialized = true;
|
|
958
2536
|
},
|
|
959
|
-
|
|
960
|
-
kind: "
|
|
961
|
-
|
|
962
|
-
}, options?.history ?? true, (session) => {
|
|
963
|
-
if (!options || options.expectedRevision !== revision) require_issue.fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
|
|
964
|
-
apply(session, require_driver.decodeChanges(inputChanges), "forward");
|
|
965
|
-
}),
|
|
966
|
-
replace: (value, options) => {
|
|
967
|
-
const source = options?.source ?? "system";
|
|
968
|
-
return mutate({
|
|
969
|
-
kind: "replace",
|
|
970
|
-
source
|
|
971
|
-
}, source !== "remote", (session) => session.replace([], value));
|
|
2537
|
+
emit: (call) => {
|
|
2538
|
+
const resetEvent = Object.freeze({ kind: "reset" });
|
|
2539
|
+
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
972
2540
|
},
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
2541
|
+
clear: () => {
|
|
2542
|
+
staged.clear();
|
|
2543
|
+
nextIds = published.ids();
|
|
2544
|
+
change = void 0;
|
|
2545
|
+
reset = false;
|
|
2546
|
+
explicitOrder = void 0;
|
|
2547
|
+
cleared = false;
|
|
976
2548
|
},
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
} finally {
|
|
992
|
-
history.dispose();
|
|
993
|
-
require_driver.disposeRuntimeDriver(runtime);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
|
-
require_access.bindRuntimeAccess(runtime, state);
|
|
998
|
-
require_driver.bindRuntimeDriver(runtime);
|
|
999
|
-
notification = require_notification.createNotification(runtime);
|
|
1000
|
-
require_notification.bindDocumentReadable(history.api, runtime);
|
|
1001
|
-
return runtime;
|
|
1002
|
-
};
|
|
1003
|
-
//#endregion
|
|
1004
|
-
//#region core/src/runtime/readable.ts
|
|
1005
|
-
const asReadable = (runtime) => {
|
|
1006
|
-
const readable = {
|
|
1007
|
-
address: runtime.address,
|
|
1008
|
-
revision: runtime.revision,
|
|
1009
|
-
subscribe: runtime.subscribe
|
|
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)
|
|
1010
2563
|
};
|
|
1011
|
-
require_access.bindRuntimeAccess(readable, require_access.accessOf(runtime));
|
|
1012
|
-
require_notification.shareNotification(runtime, readable);
|
|
1013
|
-
return Object.freeze(readable);
|
|
1014
2564
|
};
|
|
1015
2565
|
//#endregion
|
|
1016
|
-
//#region core/src/projection/
|
|
1017
|
-
const read = (runtime, selector) => require_access.readWith(runtime, selector);
|
|
1018
|
-
//#endregion
|
|
1019
|
-
//#region core/src/projection/value-output.ts
|
|
2566
|
+
//#region core/src/projection/output/value.ts
|
|
1020
2567
|
const unset = Symbol("projection-value-unset");
|
|
1021
2568
|
/** Owns staged and published state for one scalar Projection output. */
|
|
1022
2569
|
const createValueOutput = () => {
|
|
@@ -1085,7 +2632,7 @@ const createValueOutput = () => {
|
|
|
1085
2632
|
initialized = true;
|
|
1086
2633
|
},
|
|
1087
2634
|
emit: (call) => {
|
|
1088
|
-
|
|
2635
|
+
require_advanced.profile.materialized.notification();
|
|
1089
2636
|
Array.from(listeners).forEach((listener) => call(listener));
|
|
1090
2637
|
},
|
|
1091
2638
|
clear: () => {
|
|
@@ -1108,7 +2655,7 @@ const createValueOutput = () => {
|
|
|
1108
2655
|
};
|
|
1109
2656
|
};
|
|
1110
2657
|
//#endregion
|
|
1111
|
-
//#region core/src/projection/processor.ts
|
|
2658
|
+
//#region core/src/projection/graph/processor.ts
|
|
1112
2659
|
/** Materializes every derive/incremental/group definition through one processor lifecycle. */
|
|
1113
2660
|
const createProcessor = (scheduler, definition, dependencies) => {
|
|
1114
2661
|
scheduler.assertIdle();
|
|
@@ -1148,7 +2695,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1148
2695
|
}
|
|
1149
2696
|
};
|
|
1150
2697
|
}
|
|
1151
|
-
const state =
|
|
2698
|
+
const state = createCollectionOutput();
|
|
1152
2699
|
return {
|
|
1153
2700
|
kind: "collection",
|
|
1154
2701
|
definition: outputDefinition,
|
|
@@ -1202,7 +2749,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1202
2749
|
const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
|
|
1203
2750
|
let outputs = begin(scopeActive, reset);
|
|
1204
2751
|
if (!instance) instance = definition.create();
|
|
1205
|
-
|
|
2752
|
+
require_advanced.profile.materialized[reset ? "rebuilt" : "updated"]();
|
|
1206
2753
|
let result = instance.evaluate({
|
|
1207
2754
|
reset,
|
|
1208
2755
|
cause: runCause,
|
|
@@ -1270,7 +2817,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
1270
2817
|
return processor;
|
|
1271
2818
|
};
|
|
1272
2819
|
//#endregion
|
|
1273
|
-
//#region core/src/projection/
|
|
2820
|
+
//#region core/src/projection/readable/selection.ts
|
|
1274
2821
|
let activeTracker;
|
|
1275
2822
|
const trackKey = (key) => {
|
|
1276
2823
|
activeTracker?.keys.add(key);
|
|
@@ -1321,9 +2868,10 @@ const trackedMap = (source) => {
|
|
|
1321
2868
|
};
|
|
1322
2869
|
const selectionAffects = (selection, change) => {
|
|
1323
2870
|
if (!change || change.kind === "reset") return true;
|
|
1324
|
-
if (selection.all && (change
|
|
1325
|
-
if (selection.structure && (change
|
|
1326
|
-
|
|
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;
|
|
1327
2875
|
return false;
|
|
1328
2876
|
};
|
|
1329
2877
|
const createDirectReadable = (source) => Object.freeze({
|
|
@@ -1391,7 +2939,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
1391
2939
|
if (!change && previousCollection) {
|
|
1392
2940
|
const current = source.current();
|
|
1393
2941
|
if (isMapLike(current)) {
|
|
1394
|
-
change =
|
|
2942
|
+
change = diffCollection(previousCollection, current);
|
|
1395
2943
|
previousCollection = current;
|
|
1396
2944
|
if (!change) {
|
|
1397
2945
|
sourceRevision = source.revision();
|
|
@@ -1430,12 +2978,321 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
1430
2978
|
});
|
|
1431
2979
|
};
|
|
1432
2980
|
//#endregion
|
|
1433
|
-
//#region core/src/projection/source.ts
|
|
2981
|
+
//#region core/src/projection/source/materialization.ts
|
|
2982
|
+
const createDocumentDirty = () => ({
|
|
2983
|
+
replace: false,
|
|
2984
|
+
order: false,
|
|
2985
|
+
treeRoot: false,
|
|
2986
|
+
treeNodes: /* @__PURE__ */ new Set(),
|
|
2987
|
+
children: /* @__PURE__ */ new Map()
|
|
2988
|
+
});
|
|
2989
|
+
const clearDocumentDirty = (dirty) => {
|
|
2990
|
+
dirty.replace = false;
|
|
2991
|
+
dirty.order = false;
|
|
2992
|
+
dirty.treeRoot = false;
|
|
2993
|
+
dirty.treeNodes.clear();
|
|
2994
|
+
dirty.children.clear();
|
|
2995
|
+
};
|
|
2996
|
+
const hasDocumentDirty = (dirty) => dirty.replace || dirty.order || dirty.treeRoot || dirty.treeNodes.size > 0 || dirty.children.size > 0;
|
|
2997
|
+
const locate = (dirty, path) => {
|
|
2998
|
+
let current = dirty;
|
|
2999
|
+
for (const key of path) {
|
|
3000
|
+
if (current.replace) return void 0;
|
|
3001
|
+
let child = current.children.get(key);
|
|
3002
|
+
if (!child) {
|
|
3003
|
+
child = createDocumentDirty();
|
|
3004
|
+
current.children.set(key, child);
|
|
3005
|
+
}
|
|
3006
|
+
current = child;
|
|
3007
|
+
}
|
|
3008
|
+
return current;
|
|
3009
|
+
};
|
|
3010
|
+
const markDocumentReplace = (dirty, path) => {
|
|
3011
|
+
const current = locate(dirty, path);
|
|
3012
|
+
if (!current) return;
|
|
3013
|
+
current.replace = true;
|
|
3014
|
+
current.order = false;
|
|
3015
|
+
current.treeRoot = false;
|
|
3016
|
+
current.treeNodes.clear();
|
|
3017
|
+
current.children.clear();
|
|
3018
|
+
};
|
|
3019
|
+
const markDocumentOrder = (dirty, path) => {
|
|
3020
|
+
const current = locate(dirty, path);
|
|
3021
|
+
if (current && !current.replace) current.order = true;
|
|
3022
|
+
};
|
|
3023
|
+
const markDocumentTree = (dirty, path, root, nodes) => {
|
|
3024
|
+
const current = locate(dirty, path);
|
|
3025
|
+
if (!current || current.replace) return;
|
|
3026
|
+
current.treeRoot ||= root;
|
|
3027
|
+
for (const id of nodes) current.treeNodes.add(id);
|
|
3028
|
+
};
|
|
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);
|
|
3031
|
+
const cloneRecord = (value) => {
|
|
3032
|
+
require_advanced.profile.copy.shallowRecord(Object.getOwnPropertyNames(value).length);
|
|
3033
|
+
return Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
|
|
3034
|
+
};
|
|
3035
|
+
const materializeRecordChild = (node, previous, current, key, dirty) => {
|
|
3036
|
+
const before = Object.hasOwn(previous, key);
|
|
3037
|
+
const after = Object.hasOwn(current, key);
|
|
3038
|
+
if (!before && !after) return { changed: false };
|
|
3039
|
+
if (!after) return {
|
|
3040
|
+
changed: true,
|
|
3041
|
+
present: false,
|
|
3042
|
+
value: void 0
|
|
3043
|
+
};
|
|
3044
|
+
const next = before ? materializeDocumentValue(node, previous[key], current[key], dirty) : require_advanced.copyValue(node, current[key]);
|
|
3045
|
+
if (before && next === previous[key]) return { changed: false };
|
|
3046
|
+
return {
|
|
3047
|
+
changed: true,
|
|
3048
|
+
present: true,
|
|
3049
|
+
value: next
|
|
3050
|
+
};
|
|
3051
|
+
};
|
|
3052
|
+
const materializeObject = (node, previous, current, dirty) => {
|
|
3053
|
+
if (node.kind === "variant" && previous[node.tag] !== current[node.tag]) return replaceValue(node, previous, current);
|
|
3054
|
+
const layout = require_advanced.compiledShape(node, current);
|
|
3055
|
+
if (!layout) return replaceValue(node, previous, current);
|
|
3056
|
+
let result;
|
|
3057
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3058
|
+
const member = layout.members.get(key);
|
|
3059
|
+
if (!member) return replaceValue(node, previous, current);
|
|
3060
|
+
const child = materializeRecordChild(member.node, previous, current, key, childDirty);
|
|
3061
|
+
if (!child.changed) continue;
|
|
3062
|
+
result ??= cloneRecord(previous);
|
|
3063
|
+
require_context.installOwn(result, key, child.present, child.value);
|
|
3064
|
+
}
|
|
3065
|
+
return result ?? previous;
|
|
3066
|
+
};
|
|
3067
|
+
const materializeMap = (node, previous, current, dirty) => {
|
|
3068
|
+
let result;
|
|
3069
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3070
|
+
const child = materializeRecordChild(node.value, previous, current, key, childDirty);
|
|
3071
|
+
if (!child.changed) continue;
|
|
3072
|
+
result ??= cloneRecord(previous);
|
|
3073
|
+
require_context.installOwn(result, key, child.present, child.value);
|
|
3074
|
+
}
|
|
3075
|
+
return result ?? previous;
|
|
3076
|
+
};
|
|
3077
|
+
const materializeTable = (node, previous, current, dirty) => {
|
|
3078
|
+
if (!require_context.isRecord(previous.byId) || !require_context.isRecord(current.byId)) return replaceValue(node, previous, current);
|
|
3079
|
+
const beforeById = previous.byId;
|
|
3080
|
+
const afterById = current.byId;
|
|
3081
|
+
let result;
|
|
3082
|
+
let nextById;
|
|
3083
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3084
|
+
const child = materializeRecordChild(node.value, beforeById, afterById, key, childDirty);
|
|
3085
|
+
if (!child.changed) continue;
|
|
3086
|
+
nextById ??= cloneRecord(beforeById);
|
|
3087
|
+
require_context.installOwn(nextById, key, child.present, child.value);
|
|
3088
|
+
}
|
|
3089
|
+
if (nextById) {
|
|
3090
|
+
result = cloneRecord(previous);
|
|
3091
|
+
require_context.installOwn(result, "byId", true, nextById);
|
|
3092
|
+
}
|
|
3093
|
+
if (dirty.order) {
|
|
3094
|
+
const beforeIds = previous.ids;
|
|
3095
|
+
const afterIds = current.ids;
|
|
3096
|
+
if (!Array.isArray(beforeIds) || !Array.isArray(afterIds)) return replaceValue(node, previous, current);
|
|
3097
|
+
if (!require_advanced.equal(beforeIds, afterIds)) {
|
|
3098
|
+
result ??= cloneRecord(previous);
|
|
3099
|
+
require_context.installOwn(result, "ids", true, [...afterIds]);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
return result ?? previous;
|
|
3103
|
+
};
|
|
3104
|
+
const materializeList = (node, previous, current, dirty) => {
|
|
3105
|
+
if (dirty.order) {
|
|
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);
|
|
3109
|
+
const result = new Array(after.order.length);
|
|
3110
|
+
for (let index = 0; index < after.order.length; index++) {
|
|
3111
|
+
const key = after.order[index];
|
|
3112
|
+
const currentValue = after.values.get(key);
|
|
3113
|
+
if (!before.values.has(key)) {
|
|
3114
|
+
changed = true;
|
|
3115
|
+
result[index] = currentValue;
|
|
3116
|
+
continue;
|
|
3117
|
+
}
|
|
3118
|
+
const previousValue = before.values.get(key);
|
|
3119
|
+
const childDirty = dirty.children.get(key);
|
|
3120
|
+
const next = childDirty ? materializeDocumentValue(node.value, previousValue, currentValue, childDirty) : previousValue;
|
|
3121
|
+
changed ||= next !== previousValue;
|
|
3122
|
+
result[index] = next;
|
|
3123
|
+
}
|
|
3124
|
+
return changed ? result : previous;
|
|
3125
|
+
}
|
|
3126
|
+
let result;
|
|
3127
|
+
const before = require_advanced.indexedKeys(previous, node.keyOf);
|
|
3128
|
+
const after = require_advanced.indexedKeys(current, node.keyOf);
|
|
3129
|
+
for (const [key, childDirty] of dirty.children) {
|
|
3130
|
+
const previousIndex = before.index(key);
|
|
3131
|
+
const currentIndex = after.index(key);
|
|
3132
|
+
if (previousIndex < 0 || currentIndex < 0) return replaceValue(node, previous, current);
|
|
3133
|
+
const next = materializeDocumentValue(node.value, previous[previousIndex], current[currentIndex], childDirty);
|
|
3134
|
+
if (next === previous[previousIndex] && previousIndex === currentIndex) continue;
|
|
3135
|
+
result ??= [...previous];
|
|
3136
|
+
result[currentIndex] = next;
|
|
3137
|
+
}
|
|
3138
|
+
return result ?? previous;
|
|
3139
|
+
};
|
|
3140
|
+
const materializeTree = (node, previous, current, dirty) => {
|
|
3141
|
+
if (!require_context.isRecord(previous.nodes) || !require_context.isRecord(current.nodes)) return replaceValue(node, previous, current);
|
|
3142
|
+
let result;
|
|
3143
|
+
if (dirty.treeRoot) {
|
|
3144
|
+
const beforePresent = Object.hasOwn(previous, "rootId");
|
|
3145
|
+
const afterPresent = Object.hasOwn(current, "rootId");
|
|
3146
|
+
if (beforePresent !== afterPresent || !Object.is(previous.rootId, current.rootId)) {
|
|
3147
|
+
result = cloneRecord(previous);
|
|
3148
|
+
require_context.installOwn(result, "rootId", afterPresent, current.rootId);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
let nodes;
|
|
3152
|
+
for (const id of dirty.treeNodes) {
|
|
3153
|
+
const beforePresent = Object.hasOwn(previous.nodes, id);
|
|
3154
|
+
const afterPresent = Object.hasOwn(current.nodes, id);
|
|
3155
|
+
const before = beforePresent ? previous.nodes[id] : void 0;
|
|
3156
|
+
const after = afterPresent ? current.nodes[id] : void 0;
|
|
3157
|
+
if (beforePresent === afterPresent && require_advanced.equalTreeNode(before, after)) continue;
|
|
3158
|
+
nodes ??= cloneRecord(previous.nodes);
|
|
3159
|
+
require_context.installOwn(nodes, id, afterPresent, after && require_advanced.copyTreeNode(after));
|
|
3160
|
+
}
|
|
3161
|
+
if (nodes) {
|
|
3162
|
+
result ??= cloneRecord(previous);
|
|
3163
|
+
require_context.installOwn(result, "nodes", true, nodes);
|
|
3164
|
+
}
|
|
3165
|
+
return result ?? previous;
|
|
3166
|
+
};
|
|
3167
|
+
const materializeDocumentValue = (node, previous, current, dirty) => {
|
|
3168
|
+
if (!hasDocumentDirty(dirty)) return previous;
|
|
3169
|
+
if (dirty.replace || previous === void 0 || current === void 0 || node.kind === "field") return replaceValue(node, previous, current);
|
|
3170
|
+
if (node.kind === "object" || node.kind === "variant") {
|
|
3171
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
3172
|
+
return materializeObject(node, previous, current, dirty);
|
|
3173
|
+
}
|
|
3174
|
+
if (node.kind === "map") {
|
|
3175
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
3176
|
+
return materializeMap(node, previous, current, dirty);
|
|
3177
|
+
}
|
|
3178
|
+
if (node.kind === "table") {
|
|
3179
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
3180
|
+
return materializeTable(node, previous, current, dirty);
|
|
3181
|
+
}
|
|
3182
|
+
if (node.kind === "list") {
|
|
3183
|
+
if (!Array.isArray(previous) || !Array.isArray(current)) return replaceValue(node, previous, current);
|
|
3184
|
+
return materializeList(node, previous, current, dirty);
|
|
3185
|
+
}
|
|
3186
|
+
if (!require_context.isRecord(previous) || !require_context.isRecord(current)) return replaceValue(node, previous, current);
|
|
3187
|
+
return materializeTree(node, previous, current, dirty);
|
|
3188
|
+
};
|
|
3189
|
+
//#endregion
|
|
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
|
|
1434
3292
|
const sourceError = (source, cause) => new require_advanced.ProjectionError("source", source.name, [source.outputs[0].revision()], cause);
|
|
1435
|
-
const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
3293
|
+
const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
|
|
1436
3294
|
const state = createValueOutput();
|
|
1437
3295
|
let boundary;
|
|
1438
|
-
let latest = initial;
|
|
1439
3296
|
let pendingReset = false;
|
|
1440
3297
|
let pendingCause;
|
|
1441
3298
|
let contextCause;
|
|
@@ -1477,7 +3334,8 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1477
3334
|
contextCause = cause;
|
|
1478
3335
|
let active = true;
|
|
1479
3336
|
try {
|
|
1480
|
-
state.begin(() => active, pendingReset)
|
|
3337
|
+
const evaluation = state.begin(() => active, pendingReset);
|
|
3338
|
+
evaluation.output.set(prepare(evaluation.previous));
|
|
1481
3339
|
const changed = state.seal(pendingReset, equality);
|
|
1482
3340
|
recovering = false;
|
|
1483
3341
|
return {
|
|
@@ -1490,11 +3348,13 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1490
3348
|
},
|
|
1491
3349
|
publish: state.publish,
|
|
1492
3350
|
clear: () => {
|
|
3351
|
+
const failed = boundary.fault !== void 0;
|
|
1493
3352
|
state.clear();
|
|
1494
3353
|
pendingReset = false;
|
|
1495
3354
|
pendingCause = void 0;
|
|
1496
3355
|
contextCause = void 0;
|
|
1497
3356
|
recovering = false;
|
|
3357
|
+
clearSource(failed);
|
|
1498
3358
|
},
|
|
1499
3359
|
release: () => {
|
|
1500
3360
|
cleanup?.();
|
|
@@ -1530,9 +3390,6 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1530
3390
|
return {
|
|
1531
3391
|
producer: boundary,
|
|
1532
3392
|
output,
|
|
1533
|
-
setLatest: (value) => {
|
|
1534
|
-
latest = value;
|
|
1535
|
-
},
|
|
1536
3393
|
mark,
|
|
1537
3394
|
fail,
|
|
1538
3395
|
detach: (next) => {
|
|
@@ -1540,8 +3397,35 @@ const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
|
1540
3397
|
}
|
|
1541
3398
|
};
|
|
1542
3399
|
};
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
+
};
|
|
3427
|
+
const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSource) => {
|
|
3428
|
+
const state = createCollectionOutput();
|
|
1545
3429
|
let boundary;
|
|
1546
3430
|
let pendingReset = false;
|
|
1547
3431
|
let pendingCause;
|
|
@@ -1587,7 +3471,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1587
3471
|
contextCause = cause;
|
|
1588
3472
|
let active = true;
|
|
1589
3473
|
try {
|
|
1590
|
-
const changed =
|
|
3474
|
+
const changed = stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
|
|
1591
3475
|
reset: pendingReset,
|
|
1592
3476
|
...fullScan ? {} : { candidates },
|
|
1593
3477
|
orderMayChange,
|
|
@@ -1604,6 +3488,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1604
3488
|
},
|
|
1605
3489
|
publish: state.publish,
|
|
1606
3490
|
clear: () => {
|
|
3491
|
+
const failed = boundary.fault !== void 0;
|
|
1607
3492
|
state.clear();
|
|
1608
3493
|
pendingReset = false;
|
|
1609
3494
|
pendingCause = void 0;
|
|
@@ -1612,6 +3497,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1612
3497
|
fullScan = false;
|
|
1613
3498
|
orderMayChange = false;
|
|
1614
3499
|
candidates.clear();
|
|
3500
|
+
clearSource(failed);
|
|
1615
3501
|
},
|
|
1616
3502
|
release: () => {
|
|
1617
3503
|
cleanup?.();
|
|
@@ -1623,7 +3509,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1623
3509
|
scheduler.initialize(() => {
|
|
1624
3510
|
let active = true;
|
|
1625
3511
|
try {
|
|
1626
|
-
|
|
3512
|
+
stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
|
|
1627
3513
|
reset: true,
|
|
1628
3514
|
isEqual: equality
|
|
1629
3515
|
});
|
|
@@ -1659,33 +3545,41 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
|
1659
3545
|
}
|
|
1660
3546
|
};
|
|
1661
3547
|
};
|
|
1662
|
-
|
|
3548
|
+
//#endregion
|
|
3549
|
+
//#region core/src/projection/source/document.ts
|
|
3550
|
+
const documentRootTarget = Object.freeze({
|
|
3551
|
+
kind: "value",
|
|
3552
|
+
at: Object.freeze([])
|
|
3553
|
+
});
|
|
3554
|
+
const createDocumentSourceRegistry = (scheduler) => {
|
|
1663
3555
|
const documents = /* @__PURE__ */ new Map();
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
const documentConnection = (runtime) => {
|
|
1668
|
-
const state = require_access.accessOf(runtime);
|
|
3556
|
+
const connection = (runtime) => {
|
|
3557
|
+
const context = require_context.contextOf(runtime);
|
|
3558
|
+
const state = context.state;
|
|
1669
3559
|
const cached = documents.get(state);
|
|
1670
3560
|
if (cached) return cached;
|
|
1671
3561
|
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1672
3562
|
const bindings = /* @__PURE__ */ new Set();
|
|
3563
|
+
const index = new SubscriptionIndex(state.schema);
|
|
3564
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1673
3565
|
const guard = (locked) => {
|
|
1674
|
-
state.projectionLocks
|
|
3566
|
+
state.projectionLocks += locked ? 1 : -1;
|
|
1675
3567
|
};
|
|
1676
3568
|
scheduler.guards.add(guard);
|
|
1677
3569
|
let closed = false;
|
|
1678
3570
|
let detach = () => void 0;
|
|
1679
|
-
const
|
|
3571
|
+
const documentConnection = {
|
|
1680
3572
|
add(binding) {
|
|
1681
3573
|
if (closed) throw new Error("Document projection connection is closed.");
|
|
1682
3574
|
bindings.add(binding);
|
|
3575
|
+
index.add(binding.target, binding);
|
|
1683
3576
|
let active = true;
|
|
1684
3577
|
return () => {
|
|
1685
3578
|
if (!active) return;
|
|
1686
3579
|
active = false;
|
|
1687
3580
|
bindings.delete(binding);
|
|
1688
|
-
|
|
3581
|
+
index.delete(binding.target, binding);
|
|
3582
|
+
if (!bindings.size) documentConnection.close();
|
|
1689
3583
|
};
|
|
1690
3584
|
},
|
|
1691
3585
|
close() {
|
|
@@ -1693,13 +3587,17 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1693
3587
|
closed = true;
|
|
1694
3588
|
detach();
|
|
1695
3589
|
bindings.clear();
|
|
3590
|
+
candidates.clear();
|
|
3591
|
+
index.clear();
|
|
1696
3592
|
scheduler.guards.delete(guard);
|
|
1697
3593
|
documents.delete(state);
|
|
1698
3594
|
}
|
|
1699
3595
|
};
|
|
1700
|
-
detach =
|
|
3596
|
+
detach = context.notifications.attachProjection({
|
|
1701
3597
|
capture: (commit) => {
|
|
1702
|
-
|
|
3598
|
+
candidates.clear();
|
|
3599
|
+
index.collect(commit.changes, (binding) => candidates.add(binding));
|
|
3600
|
+
candidates.forEach((binding) => binding.capture(commit));
|
|
1703
3601
|
},
|
|
1704
3602
|
settle: scheduler.settle,
|
|
1705
3603
|
flush: scheduler.flush,
|
|
@@ -1708,20 +3606,175 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1708
3606
|
scheduler.run();
|
|
1709
3607
|
}
|
|
1710
3608
|
});
|
|
1711
|
-
documents.set(state,
|
|
1712
|
-
return
|
|
3609
|
+
documents.set(state, documentConnection);
|
|
3610
|
+
return documentConnection;
|
|
3611
|
+
};
|
|
3612
|
+
const materialize = (definition) => {
|
|
3613
|
+
if (definition.source.kind !== "document") throw new Error("Invalid document source.");
|
|
3614
|
+
const { document, selector } = definition.source;
|
|
3615
|
+
const state = require_context.contextOf(document).state;
|
|
3616
|
+
const current = () => resolveValue(state.schema, state.document, selector.address);
|
|
3617
|
+
const disposed = () => {
|
|
3618
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
3619
|
+
};
|
|
3620
|
+
if (selector.kind === "value") {
|
|
3621
|
+
const dirty = createDocumentDirty();
|
|
3622
|
+
const read = (previous) => {
|
|
3623
|
+
disposed();
|
|
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;
|
|
3628
|
+
if (selector.tree?.kind === "node") {
|
|
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;
|
|
3633
|
+
}
|
|
3634
|
+
if (!node) return void 0;
|
|
3635
|
+
return previous === void 0 ? require_advanced.copyValue(node, value) : materializeDocumentValue(node, previous, value, dirty);
|
|
3636
|
+
};
|
|
3637
|
+
const initial = read();
|
|
3638
|
+
let boundary;
|
|
3639
|
+
boundary = createValueBoundary(scheduler, "document source", initial, Object.is, (previous) => read(previous), (failed) => {
|
|
3640
|
+
if (failed) markDocumentReplace(dirty, []);
|
|
3641
|
+
else clearDocumentDirty(dirty);
|
|
3642
|
+
});
|
|
3643
|
+
const remove = connection(document).add({
|
|
3644
|
+
target: selector,
|
|
3645
|
+
capture: (commit) => {
|
|
3646
|
+
try {
|
|
3647
|
+
mergeValueChanges(selector, dirty, commit.changes);
|
|
3648
|
+
boundary.mark({ reset: commit.changes.changes.some((change) => change.kind === "reset") });
|
|
3649
|
+
} catch (cause) {
|
|
3650
|
+
boundary.fail(cause);
|
|
3651
|
+
}
|
|
3652
|
+
},
|
|
3653
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
3654
|
+
});
|
|
3655
|
+
boundary.detach(remove);
|
|
3656
|
+
return boundary;
|
|
3657
|
+
}
|
|
3658
|
+
const pending = createCollectionPending();
|
|
3659
|
+
const read = (active, previous) => {
|
|
3660
|
+
disposed();
|
|
3661
|
+
const resolved = current();
|
|
3662
|
+
const node = resolved?.node;
|
|
3663
|
+
const value = resolved?.value;
|
|
3664
|
+
const treeNodes = selector.tree?.kind === "nodes";
|
|
3665
|
+
const entryNode = !treeNodes && node ? require_advanced.collectionEntryNode(node) : void 0;
|
|
3666
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
3667
|
+
const replacement = createDocumentDirty();
|
|
3668
|
+
if (pending.reset) markDocumentReplace(replacement, []);
|
|
3669
|
+
const ids = () => {
|
|
3670
|
+
require_advanced.assertScope(active);
|
|
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)));
|
|
3675
|
+
return Object.freeze([]);
|
|
3676
|
+
};
|
|
3677
|
+
const has = (key) => {
|
|
3678
|
+
require_advanced.assertScope(active);
|
|
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;
|
|
3683
|
+
return false;
|
|
3684
|
+
};
|
|
3685
|
+
const raw = (key) => {
|
|
3686
|
+
if (treeNodes) {
|
|
3687
|
+
if (node?.kind !== "tree" || !require_context.isRecord(value) || !require_context.isRecord(value.nodes)) return void 0;
|
|
3688
|
+
return Object.hasOwn(value.nodes, key) ? value.nodes[key] : void 0;
|
|
3689
|
+
}
|
|
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;
|
|
3692
|
+
if (node?.kind === "list" && Array.isArray(value)) {
|
|
3693
|
+
const index = require_advanced.indexedKeys(value, node.keyOf).index(key);
|
|
3694
|
+
return index < 0 ? void 0 : value[index];
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
return Object.freeze({
|
|
3698
|
+
get: (key) => {
|
|
3699
|
+
require_advanced.assertScope(active);
|
|
3700
|
+
if (!snapshots.has(key)) if (!has(key)) snapshots.set(key, void 0);
|
|
3701
|
+
else {
|
|
3702
|
+
const current = raw(key);
|
|
3703
|
+
if (treeNodes) {
|
|
3704
|
+
const entry = current;
|
|
3705
|
+
const before = previous.has(key) ? previous.get(key) : void 0;
|
|
3706
|
+
snapshots.set(key, require_advanced.equalTreeNode(before, entry) ? before : require_advanced.copyTreeNode(entry));
|
|
3707
|
+
} else if (!entryNode) snapshots.set(key, current);
|
|
3708
|
+
else if (!previous.has(key)) snapshots.set(key, require_advanced.copyValue(entryNode, current));
|
|
3709
|
+
else {
|
|
3710
|
+
const dirty = pending.entries.get(key) ?? (pending.reset ? replacement : void 0);
|
|
3711
|
+
snapshots.set(key, dirty ? materializeDocumentValue(entryNode, previous.get(key), current, dirty) : previous.get(key));
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
return snapshots.get(key);
|
|
3715
|
+
},
|
|
3716
|
+
has,
|
|
3717
|
+
ids
|
|
3718
|
+
});
|
|
3719
|
+
};
|
|
3720
|
+
let boundary;
|
|
3721
|
+
boundary = createCollectionBoundary(scheduler, "document collection source", read, Object.is, (failed) => {
|
|
3722
|
+
if (failed) resetCollectionPending(pending);
|
|
3723
|
+
else clearCollectionPending(pending);
|
|
3724
|
+
});
|
|
3725
|
+
const remove = connection(document).add({
|
|
3726
|
+
target: selector,
|
|
3727
|
+
capture: (commit) => {
|
|
3728
|
+
try {
|
|
3729
|
+
mergeCollectionChanges(selector, pending, commit.changes);
|
|
3730
|
+
boundary.mark({
|
|
3731
|
+
reset: pending.reset,
|
|
3732
|
+
...pending.reset ? {} : { candidates: pending.entries.keys() },
|
|
3733
|
+
orderMayChange: pending.order
|
|
3734
|
+
});
|
|
3735
|
+
} catch (cause) {
|
|
3736
|
+
boundary.fail(cause);
|
|
3737
|
+
}
|
|
3738
|
+
},
|
|
3739
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
3740
|
+
});
|
|
3741
|
+
boundary.detach(remove);
|
|
3742
|
+
return boundary;
|
|
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
|
+
}
|
|
1713
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();
|
|
1714
3766
|
const valueInput = (definition) => {
|
|
1715
3767
|
if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
|
|
1716
3768
|
const source = definition.source;
|
|
1717
|
-
|
|
3769
|
+
let latest = source.initial;
|
|
3770
|
+
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
|
|
1718
3771
|
return {
|
|
1719
3772
|
...boundary,
|
|
1720
3773
|
write: {
|
|
1721
3774
|
kind: "value",
|
|
1722
3775
|
set(value) {
|
|
1723
3776
|
scheduler.assertIdle();
|
|
1724
|
-
|
|
3777
|
+
latest = value;
|
|
1725
3778
|
boundary.mark();
|
|
1726
3779
|
scheduler.run();
|
|
1727
3780
|
}
|
|
@@ -1731,7 +3784,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1731
3784
|
const collectionInput = (definition) => {
|
|
1732
3785
|
if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
|
|
1733
3786
|
const values = new Map(definition.source.initial);
|
|
1734
|
-
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_advanced.mapRead(values), definition.output.equality);
|
|
3787
|
+
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_advanced.mapRead(values), definition.output.equality, () => void 0);
|
|
1735
3788
|
return {
|
|
1736
3789
|
...boundary,
|
|
1737
3790
|
write: {
|
|
@@ -1807,103 +3860,15 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1807
3860
|
}
|
|
1808
3861
|
};
|
|
1809
3862
|
};
|
|
1810
|
-
const documentValue = (definition) => {
|
|
1811
|
-
if (definition.source.kind !== "document-value") throw new Error("Invalid document value source.");
|
|
1812
|
-
const { document, selector } = definition.source;
|
|
1813
|
-
const state = require_access.accessOf(document);
|
|
1814
|
-
const address = selector?.address ?? Object.freeze([]);
|
|
1815
|
-
const schemaNode = () => require_issue.nodeAt(state.schema, address, state.document);
|
|
1816
|
-
const read = () => {
|
|
1817
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1818
|
-
const node = schemaNode();
|
|
1819
|
-
if (!node) return void 0;
|
|
1820
|
-
return require_access.copyValue(node, selector ? require_issue.read(state.document, address, state.schema) : state.document);
|
|
1821
|
-
};
|
|
1822
|
-
const boundary = createValueBoundary(scheduler, "document source", read(), (left, right) => {
|
|
1823
|
-
const node = schemaNode();
|
|
1824
|
-
return node ? require_access.equalValue(node, left, right) : Object.is(left, right);
|
|
1825
|
-
});
|
|
1826
|
-
const remove = documentConnection(document).add({
|
|
1827
|
-
matches: (commit) => !selector || affectsTarget(commit.impact, selector),
|
|
1828
|
-
capture: (commit) => {
|
|
1829
|
-
try {
|
|
1830
|
-
boundary.setLatest(read());
|
|
1831
|
-
boundary.mark({ reset: commit.impact.kind === "reset" });
|
|
1832
|
-
} catch (cause) {
|
|
1833
|
-
boundary.fail(cause);
|
|
1834
|
-
}
|
|
1835
|
-
},
|
|
1836
|
-
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
1837
|
-
});
|
|
1838
|
-
boundary.detach(remove);
|
|
1839
|
-
return boundary;
|
|
1840
|
-
};
|
|
1841
|
-
const documentCollection = (definition) => {
|
|
1842
|
-
if (definition.source.kind !== "document-collection") throw new Error("Invalid document collection source.");
|
|
1843
|
-
const { document, selector } = definition.source;
|
|
1844
|
-
const state = require_access.accessOf(document);
|
|
1845
|
-
const read = (active) => {
|
|
1846
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1847
|
-
const raw = require_access.collectionAccess({
|
|
1848
|
-
state,
|
|
1849
|
-
active
|
|
1850
|
-
}, selector.address);
|
|
1851
|
-
const snapshots = /* @__PURE__ */ new Map();
|
|
1852
|
-
return Object.freeze({
|
|
1853
|
-
get: (key) => {
|
|
1854
|
-
require_advanced.assertScope(active);
|
|
1855
|
-
if (!snapshots.has(key)) snapshots.set(key, require_access.snapshot(raw.get(key)));
|
|
1856
|
-
return snapshots.get(key);
|
|
1857
|
-
},
|
|
1858
|
-
has: (key) => {
|
|
1859
|
-
require_advanced.assertScope(active);
|
|
1860
|
-
return raw.has(key);
|
|
1861
|
-
},
|
|
1862
|
-
ids: () => {
|
|
1863
|
-
require_advanced.assertScope(active);
|
|
1864
|
-
return raw.ids();
|
|
1865
|
-
}
|
|
1866
|
-
});
|
|
1867
|
-
};
|
|
1868
|
-
const collectionNode = () => require_issue.nodeAt(state.schema, selector.address, state.document);
|
|
1869
|
-
const boundary = createCollectionBoundary(scheduler, "document collection source", read, (left, right) => {
|
|
1870
|
-
const node = collectionNode();
|
|
1871
|
-
const entry = node && require_advanced.collectionEntryNode(node);
|
|
1872
|
-
return entry ? require_access.equalValue(entry, left, right) : Object.is(left, right);
|
|
1873
|
-
});
|
|
1874
|
-
const remove = documentConnection(document).add({
|
|
1875
|
-
matches: (commit) => affectsTarget(commit.impact, selector),
|
|
1876
|
-
capture: (commit) => {
|
|
1877
|
-
const impact = collectionImpact(commit.impact, selector);
|
|
1878
|
-
if (impact.kind === "reset") {
|
|
1879
|
-
boundary.mark({ reset: true });
|
|
1880
|
-
return;
|
|
1881
|
-
}
|
|
1882
|
-
const candidates = new Set([
|
|
1883
|
-
...impact.added,
|
|
1884
|
-
...impact.updated,
|
|
1885
|
-
...impact.removed
|
|
1886
|
-
]);
|
|
1887
|
-
boundary.mark({
|
|
1888
|
-
candidates,
|
|
1889
|
-
orderMayChange: Boolean(impact.added.size || impact.removed.size || impact.orderChanged)
|
|
1890
|
-
});
|
|
1891
|
-
},
|
|
1892
|
-
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
1893
|
-
});
|
|
1894
|
-
boundary.detach(remove);
|
|
1895
|
-
return boundary;
|
|
1896
|
-
};
|
|
1897
3863
|
const readable = (definition) => {
|
|
1898
3864
|
if (definition.source.kind !== "readable") throw new Error("Invalid Readable source.");
|
|
1899
3865
|
const { readable, equality } = definition.source;
|
|
1900
|
-
let
|
|
1901
|
-
const boundary = createValueBoundary(scheduler, "external readable",
|
|
3866
|
+
let latest = readable.current();
|
|
3867
|
+
const boundary = createValueBoundary(scheduler, "external readable", latest, equality, () => latest, () => void 0);
|
|
1902
3868
|
const member = {
|
|
1903
3869
|
boundary,
|
|
1904
3870
|
receive(value, metadata) {
|
|
1905
|
-
|
|
1906
|
-
boundary.setLatest(value);
|
|
3871
|
+
latest = value;
|
|
1907
3872
|
boundary.mark(metadata);
|
|
1908
3873
|
}
|
|
1909
3874
|
};
|
|
@@ -1933,9 +3898,8 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1933
3898
|
readables.delete(readable);
|
|
1934
3899
|
}
|
|
1935
3900
|
};
|
|
1936
|
-
const owner =
|
|
1937
|
-
if (owner) detachDocument =
|
|
1938
|
-
matches: () => true,
|
|
3901
|
+
const owner = require_context.contextOf(readable, false)?.owner;
|
|
3902
|
+
if (owner) detachDocument = documents.attachRoot(owner, {
|
|
1939
3903
|
capture: () => receive(false),
|
|
1940
3904
|
dispose: () => {
|
|
1941
3905
|
members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
|
|
@@ -1955,11 +3919,12 @@ const createSourceRegistry = (scheduler) => {
|
|
|
1955
3919
|
const externalValue = (definition) => {
|
|
1956
3920
|
if (definition.source.kind !== "external-value") throw new Error("Invalid external value source.");
|
|
1957
3921
|
const { source, equality } = definition.source;
|
|
1958
|
-
|
|
3922
|
+
let latest = source.current();
|
|
3923
|
+
const boundary = createValueBoundary(scheduler, "external source", latest, equality, () => latest, () => void 0);
|
|
1959
3924
|
const member = {
|
|
1960
3925
|
boundary,
|
|
1961
3926
|
receive(value, metadata) {
|
|
1962
|
-
|
|
3927
|
+
latest = value;
|
|
1963
3928
|
boundary.mark(metadata);
|
|
1964
3929
|
}
|
|
1965
3930
|
};
|
|
@@ -2013,7 +3978,7 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2013
3978
|
has: (key) => current.has(key),
|
|
2014
3979
|
ids: () => current.ids()
|
|
2015
3980
|
});
|
|
2016
|
-
}, definition.output.equality);
|
|
3981
|
+
}, definition.output.equality, () => void 0);
|
|
2017
3982
|
let connection = externalCollections.get(source);
|
|
2018
3983
|
if (!connection) {
|
|
2019
3984
|
const members = /* @__PURE__ */ new Set();
|
|
@@ -2076,17 +4041,15 @@ const createSourceRegistry = (scheduler) => {
|
|
|
2076
4041
|
switch (definition.source.kind) {
|
|
2077
4042
|
case "value-input": return valueInput(definition);
|
|
2078
4043
|
case "collection-input": return collectionInput(definition);
|
|
2079
|
-
case "document
|
|
2080
|
-
case "document-collection": return documentCollection(definition);
|
|
4044
|
+
case "document": return documents.materialize(definition);
|
|
2081
4045
|
case "readable": return readable(definition);
|
|
2082
4046
|
case "external-value": return externalValue(definition);
|
|
2083
4047
|
case "external-collection": return externalCollection(definition);
|
|
2084
4048
|
}
|
|
2085
4049
|
},
|
|
2086
4050
|
dispose() {
|
|
2087
|
-
documents.
|
|
4051
|
+
documents.dispose();
|
|
2088
4052
|
readables.forEach((connection) => connection.close());
|
|
2089
|
-
documents.clear();
|
|
2090
4053
|
readables.clear();
|
|
2091
4054
|
}
|
|
2092
4055
|
};
|
|
@@ -2330,12 +4293,12 @@ const createProjectionRuntime = (options) => {
|
|
|
2330
4293
|
});
|
|
2331
4294
|
};
|
|
2332
4295
|
//#endregion
|
|
2333
|
-
exports.DocumentDisposedError =
|
|
2334
|
-
exports.DocumentReentrancyError =
|
|
2335
|
-
exports.ParseError =
|
|
4296
|
+
exports.DocumentDisposedError = DocumentDisposedError;
|
|
4297
|
+
exports.DocumentReentrancyError = DocumentReentrancyError;
|
|
4298
|
+
exports.ParseError = require_advanced.ParseError;
|
|
2336
4299
|
exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
|
|
2337
4300
|
exports.ProjectionError = require_advanced.ProjectionError;
|
|
2338
|
-
exports.TransactionRejected =
|
|
4301
|
+
exports.TransactionRejected = TransactionRejected;
|
|
2339
4302
|
exports.asReadable = asReadable;
|
|
2340
4303
|
exports.createDocument = createDocument;
|
|
2341
4304
|
exports.createProjectionRuntime = createProjectionRuntime;
|
|
@@ -2347,10 +4310,11 @@ exports.map = require_advanced.map;
|
|
|
2347
4310
|
exports.object = require_advanced.object;
|
|
2348
4311
|
exports.observe = require_advanced.observe;
|
|
2349
4312
|
exports.optional = require_advanced.optional;
|
|
2350
|
-
exports.parse =
|
|
4313
|
+
exports.parse = require_advanced.parse;
|
|
2351
4314
|
exports.read = read;
|
|
2352
|
-
exports.replace =
|
|
2353
|
-
exports.
|
|
4315
|
+
exports.replace = replace;
|
|
4316
|
+
exports.select = select;
|
|
4317
|
+
exports.snapshot = snapshot;
|
|
2354
4318
|
exports.table = require_advanced.table;
|
|
2355
4319
|
exports.tree = require_advanced.tree;
|
|
2356
4320
|
exports.variant = require_advanced.variant;
|