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