doxum 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +38 -13
  2. package/dist/{contract-I99DUMbN.d.ts → contract-DNZ4D53r.d.ts} +87 -76
  3. package/dist/{contract-uYJTmT8o.d.cts → contract-j3SLGAwh.d.cts} +87 -76
  4. package/dist/index.cjs +221 -349
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +3 -3
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +209 -327
  9. package/dist/index.js.map +1 -1
  10. package/dist/{integration-xupBIKjU.js → integration-B56u1l9V.js} +2 -2
  11. package/dist/integration-B56u1l9V.js.map +1 -0
  12. package/dist/{integration-Bs3pOk65.cjs → integration-CZwCwFBS.cjs} +2 -2
  13. package/dist/integration-CZwCwFBS.cjs.map +1 -0
  14. package/dist/integration.cjs +10 -2
  15. package/dist/integration.d.cts +3 -3
  16. package/dist/integration.d.ts +3 -3
  17. package/dist/integration.js +3 -3
  18. package/dist/local-sync.cjs +56 -17
  19. package/dist/local-sync.cjs.map +1 -1
  20. package/dist/local-sync.d.cts +2 -2
  21. package/dist/local-sync.d.ts +2 -2
  22. package/dist/local-sync.js +56 -17
  23. package/dist/local-sync.js.map +1 -1
  24. package/dist/react.cjs +27 -19
  25. package/dist/react.cjs.map +1 -1
  26. package/dist/react.js +27 -19
  27. package/dist/react.js.map +1 -1
  28. package/dist/{runtime-CRijXjYp.cjs → runtime-0mOFbe_H.cjs} +899 -451
  29. package/dist/runtime-0mOFbe_H.cjs.map +1 -0
  30. package/dist/{runtime-CUcvhFNP.d.ts → runtime-B22tuj9A.d.cts} +18 -20
  31. package/dist/{runtime-C_vKHmhy.d.cts → runtime-BFmhzPpZ.d.ts} +18 -20
  32. package/dist/{runtime-BJRXF0gq.js → runtime-DF1q9Gje.js} +823 -429
  33. package/dist/runtime-DF1q9Gje.js.map +1 -0
  34. package/package.json +1 -1
  35. package/skills/doxum-runtime/SKILL.md +2 -1
  36. package/skills/doxum-runtime/references/guide.en.md +18 -18
  37. package/skills/doxum-runtime/references/guide.zh-CN.md +12 -17
  38. package/skills/doxum-runtime/references/invariants.en.md +1 -1
  39. package/skills/doxum-runtime/references/invariants.zh-CN.md +1 -1
  40. package/skills/doxum-runtime/references/patterns.en.md +9 -20
  41. package/skills/doxum-runtime/references/patterns.zh-CN.md +5 -18
  42. package/dist/integration-Bs3pOk65.cjs.map +0 -1
  43. package/dist/integration-xupBIKjU.js.map +0 -1
  44. package/dist/runtime-BJRXF0gq.js.map +0 -1
  45. package/dist/runtime-CRijXjYp.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as dict, B as tree, C as debugKey, D as resolveAddress, E as read, F as optional, I as record, L as schema, M as list, N as map, O as resolveLocated, P as object, R as single, S as contains, T as overlaps, V as variant, _ as address$2, a as createNotification, b as impact_target_exports, c as shareNotification, d as accessOf, f as bindRuntimeAccess, g as documentReader, h as DocumentReentrancyError, i as ProjectionError, j as field, k as set$1, l as subscribeRoot, m as DocumentDisposedError, o as disposeNotification, p as readWith, r as ProjectionDisposedError, s as notify, t as createProjectionRuntime, u as subscribeTargets, v as belongs, w as locate, y as id, z as table } from "./runtime-BJRXF0gq.js";
1
+ import { A as id, B as set$1, C as restore, D as DocumentReentrancyError, E as DocumentDisposedError, F as locate, G as object, H as field, I as overlaps, J as table, K as optional, L as read, N as contains$1, O as address$1, P as debugKey, R as resolveAddress, S as keys, T as validPositions, U as list, V as dict, W as map, X as variant, Y as tree, a as bindDocumentReadable, b as at$1, c as notify, d as subscribeTargets, f as accessOf, h as commandFootprint, i as ProjectionError, j as impact_target_exports, k as belongs, l as shareNotification, m as readWith, o as createNotification, p as bindRuntimeAccess, q as schema, r as ProjectionDisposedError, s as disposeNotification, t as createProjectionRuntime, u as subscribeRoot, v as documentReader, w as valid, x as index, y as afterRemove, z as resolveLocated } from "./runtime-DF1q9Gje.js";
2
2
  import { a as isStablePayload, c as snapshotPayload, i as isRecord, l as transferPayload, n as deepEqual, o as ownPayload, r as isPlainObject, s as sameStructuralValue, t as cloneValue, u as profile } from "./ownership-CduRygE7.js";
3
3
  import { n as bindRuntimeDriver, r as disposeRuntimeDriver, t as assertRuntimeWritable } from "./driver-CbzfW5MR.js";
4
4
  //#region core/src/impact.ts
@@ -38,9 +38,9 @@ const createImpact = (input) => {
38
38
  profile.impact.affects();
39
39
  if (kind === "reset") return true;
40
40
  if (!belongs(value, input.schema)) return false;
41
- if (pathsAffect(address$2(value))) return true;
41
+ if (pathsAffect(address$1(value))) return true;
42
42
  if (value.kind === "collection") {
43
- const collection = findCollection(address$2(value));
43
+ const collection = findCollection(address$1(value));
44
44
  if (!collection) return false;
45
45
  const id$1 = id(value);
46
46
  if (id$1 !== void 0) return collection.added.has(id$1) || collection.removed.has(id$1) || collection.updated.has(id$1);
@@ -54,7 +54,7 @@ const createImpact = (input) => {
54
54
  const cached = collectionCache?.find((entry) => entry.address.length === selector.address.length && entry.address.every((segment, index) => segment === selector.address[index]))?.value;
55
55
  if (cached) return cached;
56
56
  const exact = findCollection(selector.address);
57
- if (somePath(selector.address, (changed) => contains(changed, selector.address) && changed.length < selector.address.length)) {
57
+ if (somePath(selector.address, (changed) => contains$1(changed, selector.address) && changed.length < selector.address.length)) {
58
58
  const reset = { kind: "reset" };
59
59
  (collectionCache ??= []).push({
60
60
  address: selector.address,
@@ -88,78 +88,170 @@ const createImpact = (input) => {
88
88
  //#endregion
89
89
  //#region core/src/history.ts
90
90
  const createHistory = (input) => {
91
- const undos = [];
92
- const redos = [];
91
+ let undos = [];
92
+ let redos = [];
93
93
  const listeners = /* @__PURE__ */ new Set();
94
94
  let state = Object.freeze({
95
95
  undoDepth: 0,
96
96
  redoDepth: 0
97
97
  });
98
- const current = () => {
99
- if (state.undoDepth !== undos.length || state.redoDepth !== redos.length) state = Object.freeze({
98
+ let revision = 0;
99
+ let pending = false;
100
+ let disposed = false;
101
+ let group;
102
+ const unchanged = () => ({
103
+ status: "unchanged",
104
+ revision: input.revision()
105
+ });
106
+ const publish = () => {
107
+ if (state.undoDepth === undos.length && state.redoDepth === redos.length) return;
108
+ state = Object.freeze({
100
109
  undoDepth: undos.length,
101
110
  redoDepth: redos.length
102
111
  });
103
- return state;
112
+ revision++;
113
+ pending = true;
104
114
  };
105
- const emit = () => {
106
- if (listeners.size === 0) return;
107
- current();
108
- Array.from(listeners).forEach((listener) => listener());
115
+ const flush = () => {
116
+ if (!pending) return [];
117
+ pending = false;
118
+ const errors = [];
119
+ for (const listener of [...listeners]) {
120
+ if (!listeners.has(listener)) continue;
121
+ try {
122
+ listener();
123
+ } catch (error) {
124
+ errors.push({
125
+ phase: "listener",
126
+ error
127
+ });
128
+ }
129
+ }
130
+ return errors;
109
131
  };
110
132
  const clear = () => {
111
- if (undos.length || redos.length) {
112
- undos.length = 0;
113
- redos.length = 0;
114
- emit();
133
+ group = void 0;
134
+ undos.length = redos.length = 0;
135
+ publish();
136
+ };
137
+ const travel = (direction) => {
138
+ input.assertIdle();
139
+ const from = direction === "redo" ? redos : undos;
140
+ const to = direction === "redo" ? undos : redos;
141
+ const previousUndos = undos;
142
+ const previousRedos = redos;
143
+ const entry = direction === "cancel" ? from.at(-1) : from.pop();
144
+ if (!entry) return unchanged();
145
+ if (direction === "cancel") {
146
+ if (!group || group.entry !== entry) throw new Error("History group no longer owns its entry.");
147
+ undos = group.undos;
148
+ redos = group.redos;
149
+ } else to.push(entry);
150
+ let committed = false;
151
+ try {
152
+ const operations = direction === "redo" ? entry.batches.flatMap((batch) => batch.operations) : entry.batches.slice().reverse().flatMap((batch) => batch.inverse);
153
+ const result = input.apply(operations);
154
+ committed = result.status !== "rejected";
155
+ if (committed && direction === "cancel") group = void 0;
156
+ if (result.status === "unchanged") {
157
+ publish();
158
+ const errors = input.notify(flush);
159
+ if (errors.length) throw new AggregateError(errors.map((entry) => entry.error), "History listeners failed.");
160
+ }
161
+ return result;
162
+ } finally {
163
+ if (!committed) if (direction === "cancel") {
164
+ undos = previousUndos;
165
+ redos = previousRedos;
166
+ } else {
167
+ to.pop();
168
+ from.push(entry);
169
+ }
115
170
  }
116
171
  };
117
172
  return {
118
173
  api: {
119
- current,
174
+ current: () => state,
175
+ revision: () => revision,
120
176
  subscribe: (listener) => {
177
+ if (disposed) throw new DocumentDisposedError();
121
178
  listeners.add(listener);
122
- return () => listeners.delete(listener);
179
+ return () => {
180
+ listeners.delete(listener);
181
+ };
123
182
  },
124
183
  undo: () => {
125
- const entry = undos.pop();
126
- if (!entry) return {
127
- status: "unchanged",
128
- revision: input.revision()
129
- };
130
- const result = input.apply(entry.inverse);
131
- if (result.status === "committed") {
132
- redos.push(entry);
133
- emit();
134
- } else undos.push(entry);
135
- return result;
184
+ input.assertIdle();
185
+ group = void 0;
186
+ return travel("undo");
136
187
  },
137
188
  redo: () => {
138
- const entry = redos.pop();
139
- if (!entry) return {
140
- status: "unchanged",
141
- revision: input.revision()
142
- };
143
- const result = input.apply(entry.operations);
144
- if (result.status === "committed") {
145
- undos.push(entry);
146
- emit();
147
- } else redos.push(entry);
148
- return result;
189
+ input.assertIdle();
190
+ group = void 0;
191
+ return travel("redo");
149
192
  },
150
- clear
193
+ clear: () => {
194
+ input.assertIdle();
195
+ clear();
196
+ const errors = input.notify(flush);
197
+ if (errors.length) throw new AggregateError(errors.map((entry) => entry.error), "History listeners failed.");
198
+ },
199
+ group: () => {
200
+ input.assertIdle();
201
+ if (group) throw new Error("A history group is already active.");
202
+ if (input.capacity <= 0) throw new Error("History is disabled.");
203
+ const scope = {
204
+ undos: undos.slice(),
205
+ redos: redos.slice()
206
+ };
207
+ group = scope;
208
+ return Object.freeze({
209
+ end: () => {
210
+ input.assertIdle();
211
+ if (group === scope) group = void 0;
212
+ },
213
+ cancel: () => {
214
+ input.assertIdle();
215
+ if (group !== scope) return unchanged();
216
+ if (!scope.entry) {
217
+ group = void 0;
218
+ return unchanged();
219
+ }
220
+ const result = travel("cancel");
221
+ if (result.status !== "rejected") group = void 0;
222
+ return result;
223
+ }
224
+ });
225
+ }
151
226
  },
152
227
  record: (operations, inverse) => {
153
228
  if (input.capacity <= 0) return;
154
- undos.push({
229
+ const batch = {
155
230
  operations,
156
231
  inverse
157
- });
158
- if (undos.length > input.capacity) undos.shift();
232
+ };
233
+ if (group?.entry) group.entry.batches.push(batch);
234
+ else {
235
+ const entry = { batches: [batch] };
236
+ undos.push(entry);
237
+ if (group) group.entry = entry;
238
+ if (undos.length > input.capacity) undos.shift();
239
+ }
159
240
  redos.length = 0;
160
- emit();
241
+ publish();
242
+ },
243
+ publish,
244
+ flush,
245
+ endGroup: () => {
246
+ group = void 0;
161
247
  },
162
- invalidate: clear
248
+ invalidate: clear,
249
+ dispose: () => {
250
+ disposed = true;
251
+ listeners.clear();
252
+ clear();
253
+ pending = false;
254
+ }
163
255
  };
164
256
  };
165
257
  //#endregion
@@ -179,7 +271,7 @@ const writerFor = (node, inputAddress, sink) => {
179
271
  at: address
180
272
  }) : void 0
181
273
  };
182
- if (node.kind === "dict" || node.kind === "record") return {
274
+ if (node.kind === "dict") return {
183
275
  set: (key, value) => emit(sink, {
184
276
  type: "dict.set",
185
277
  at: address,
@@ -232,20 +324,20 @@ const writerFor = (node, inputAddress, sink) => {
232
324
  }) } : {}
233
325
  };
234
326
  if (node.kind === "tree") return {
235
- insert: (treeNodeId, value, parentId, index) => emit(sink, {
327
+ insert: (treeNodeId, value, position) => emit(sink, {
236
328
  type: "tree.insert",
237
329
  at: address,
238
330
  treeNodeId,
239
331
  value,
240
- parentId,
241
- index
332
+ parentId: position?.parentId,
333
+ index: position?.index
242
334
  }),
243
- move: (treeNodeId, parentId, index) => emit(sink, {
335
+ move: (treeNodeId, position) => emit(sink, {
244
336
  type: "tree.move",
245
337
  at: address,
246
338
  treeNodeId,
247
- parentId,
248
- index
339
+ parentId: position?.parentId,
340
+ index: position?.index
249
341
  }),
250
342
  remove: (treeNodeId) => emit(sink, {
251
343
  type: "tree.remove",
@@ -329,7 +421,6 @@ const writerFor = (node, inputAddress, sink) => {
329
421
  })
330
422
  };
331
423
  }
332
- if (node.kind === "single") return writerFor(node.value, address, sink);
333
424
  if (node.kind === "variant") return {
334
425
  replace: (value) => emit(sink, {
335
426
  type: "variant.replace",
@@ -355,69 +446,15 @@ const documentWriter = (schema, sink) => writerFor(schemaRoot(schema), [], sink)
355
446
  //#endregion
356
447
  //#region core/src/mutation/issue.ts
357
448
  const ownAddress$1 = (address) => Object.freeze(address.slice());
358
- const at$1 = (address, code, message, operation) => Object.freeze({
449
+ const at = (address, code, message, operation) => Object.freeze({
359
450
  source: "mutation",
360
451
  code,
361
452
  address: ownAddress$1(address),
362
453
  message,
363
454
  ...operation === void 0 ? {} : { operation }
364
455
  });
365
- const from = (operation, code, message) => at$1(operation.at, code, message, operation.type);
366
- const input = (code, message) => at$1([], code, message);
367
- //#endregion
368
- //#region core/src/mutation/anchor.ts
369
- const length = (keys) => keys.length;
370
- const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
371
- const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
372
- const keys = (values, keyOf) => ({
373
- length: values.length,
374
- at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
375
- index: (key) => {
376
- for (let index = 0; index < values.length; index += 1) if (keyOf(values[index]) === key) return index;
377
- return -1;
378
- }
379
- });
380
- const index = (keys, anchor) => {
381
- if (!anchor) return length(keys);
382
- if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
383
- if ("before" in anchor) return keyIndex(keys, anchor.before);
384
- return keyIndex(keys, anchor.after) + 1;
385
- };
386
- const at = (keys, position) => position <= 0 ? { at: "start" } : { after: keyAt(keys, position - 1) };
387
- const valid = (keys, anchor) => {
388
- if (!anchor) return true;
389
- if (typeof anchor !== "object" || anchor === null) return false;
390
- if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
391
- if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
392
- return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
393
- };
394
- const afterRemove = (keys, removedIndex, anchor) => {
395
- const position = index(keys, anchor);
396
- return position > removedIndex ? position - 1 : position;
397
- };
398
- const validPositions = (length, positions) => {
399
- let previous = -1;
400
- for (let index = 0; index < positions.length; index += 1) {
401
- const position = positions[index];
402
- if (!Number.isInteger(position) || position < 0 || position <= previous || position > length + index) return false;
403
- previous = position;
404
- }
405
- return true;
406
- };
407
- const restore = (keys, inserted, positions) => {
408
- const currentLength = keys.length;
409
- if (inserted.length !== positions.length || !validPositions(currentLength, positions)) throw new Error("Ordered restore positions are invalid.");
410
- let readIndex = currentLength - 1;
411
- let insertedIndex = inserted.length - 1;
412
- keys.length = currentLength + inserted.length;
413
- for (let writeIndex = keys.length - 1; writeIndex >= 0; writeIndex -= 1) if (insertedIndex >= 0 && positions[insertedIndex] === writeIndex) {
414
- keys[writeIndex] = inserted[insertedIndex];
415
- insertedIndex -= 1;
416
- } else {
417
- keys[writeIndex] = keys[readIndex];
418
- readIndex -= 1;
419
- }
420
- };
456
+ const from = (operation, code, message) => at(operation.at, code, message, operation.type);
457
+ const input = (code, message) => at([], code, message);
421
458
  //#endregion
422
459
  //#region core/src/mutation/execute-entity.ts
423
460
  const resolveEntityCollection = (node, value) => {
@@ -542,7 +579,7 @@ const executeEntity = (target, operation, copyPayload) => {
542
579
  type: "entity.move",
543
580
  at: operation.at,
544
581
  id: operation.id,
545
- anchor: at(ids, position)
582
+ anchor: at$1(ids, position)
546
583
  };
547
584
  ids.splice(position, 1);
548
585
  ids.splice(nextIndex, 0, operation.id);
@@ -617,7 +654,7 @@ const executeList = (target, operation, copyPayload) => {
617
654
  at: operation.at,
618
655
  key: operation.key,
619
656
  value: cloneValue(list[position], "inverse"),
620
- anchor: at(orderedKeys, position)
657
+ anchor: at$1(orderedKeys, position)
621
658
  };
622
659
  list.splice(position, 1);
623
660
  return {
@@ -633,7 +670,7 @@ const executeList = (target, operation, copyPayload) => {
633
670
  type: "list.move",
634
671
  at: operation.at,
635
672
  key: operation.key,
636
- anchor: at(orderedKeys, position)
673
+ anchor: at$1(orderedKeys, position)
637
674
  };
638
675
  const [entry] = list.splice(position, 1);
639
676
  list.splice(nextIndex, 0, entry);
@@ -816,7 +853,7 @@ const changeChanged = (change, value) => {
816
853
  return false;
817
854
  };
818
855
  const is = (value) => isRecord(value) && isRecord(value.nodes);
819
- const contains$1 = (tree, id) => has$1(tree.nodes, id);
856
+ const contains = (tree, id) => has$1(tree.nodes, id);
820
857
  const validate = (value) => {
821
858
  if (!is(value)) return false;
822
859
  const ids = Object.keys(value.nodes);
@@ -858,7 +895,6 @@ const documentTreeError = (node, value, address) => {
858
895
  }
859
896
  return;
860
897
  }
861
- if (node.kind === "single") return documentTreeError(node.value, value, address);
862
898
  if (node.kind === "variant") {
863
899
  if (!isRecord(value) || typeof value[node.tag] !== "string") return void 0;
864
900
  const variant = node.variants[value[node.tag]];
@@ -897,7 +933,7 @@ const replace = (current, operation) => {
897
933
  };
898
934
  };
899
935
  const insert = (tree, operation, value) => {
900
- if (contains$1(tree, operation.treeNodeId)) return rejected$2("duplicate-tree-node", `Tree node '${operation.treeNodeId}' already exists.`);
936
+ if (contains(tree, operation.treeNodeId)) return rejected$2("duplicate-tree-node", `Tree node '${operation.treeNodeId}' already exists.`);
901
937
  if (!validIndex(operation.index)) return rejected$2("invalid-tree-index", "Tree index is invalid.");
902
938
  if (tree.rootId === void 0) {
903
939
  if (operation.parentId !== void 0) return rejected$2("missing-tree-parent", `Tree parent '${operation.parentId}' does not exist.`);
@@ -1022,9 +1058,8 @@ const move = (tree, operation) => {
1022
1058
  if (!currentParent || parentId === void 0) return rejected$2("invalid-tree", "Tree parent links are inconsistent.");
1023
1059
  const originalIndex = childIndex(currentParent, operation.treeNodeId);
1024
1060
  if (originalIndex < 0) return rejected$2("invalid-tree", "Tree parent links are inconsistent.");
1025
- let nextIndex = Math.min(operation.index ?? nextParent.children.length, nextParent.children.length);
1026
1061
  currentParent.children.splice(originalIndex, 1);
1027
- if (parentId === operation.parentId && nextIndex > originalIndex) nextIndex -= 1;
1062
+ const nextIndex = Math.min(operation.index ?? nextParent.children.length, nextParent.children.length);
1028
1063
  if (parentId === operation.parentId && nextIndex === originalIndex) {
1029
1064
  currentParent.children.splice(originalIndex, 0, operation.treeNodeId);
1030
1065
  return { status: "unchanged" };
@@ -1128,6 +1163,16 @@ const executeValue = (target, operation, copyPayload) => {
1128
1163
  }
1129
1164
  if (operation.type === "variant.replace") {
1130
1165
  if (sameStructuralValue(target.value, operation.value)) return { status: "unchanged" };
1166
+ if (target.value === void 0 && target.node.optional) {
1167
+ target.parent[target.key] = own(operation.value);
1168
+ return {
1169
+ status: "changed",
1170
+ inverse: [{
1171
+ type: "value.clear",
1172
+ at: operation.at
1173
+ }]
1174
+ };
1175
+ }
1131
1176
  const inverse = {
1132
1177
  type: "variant.replace",
1133
1178
  at: operation.at,
@@ -1206,7 +1251,7 @@ const executeClear = (target, operation) => {
1206
1251
  issue: from(operation, "required-field", "Only optional values can be cleared.")
1207
1252
  };
1208
1253
  if (!hasOwn$1(target.parent, target.key)) return { status: "unchanged" };
1209
- if (target.node.kind !== "field" && target.node.kind !== "variant" && target.node.kind !== "dict" && target.node.kind !== "record" && target.node.kind !== "list" && target.node.kind !== "tree") return {
1254
+ if (target.node.kind !== "field" && target.node.kind !== "variant" && target.node.kind !== "dict" && target.node.kind !== "list" && target.node.kind !== "tree") return {
1210
1255
  status: "rejected",
1211
1256
  issue: from(operation, "invalid-operation", "This optional value has no clear operation.")
1212
1257
  };
@@ -1222,8 +1267,7 @@ const executeClear = (target, operation) => {
1222
1267
  at: operation.at,
1223
1268
  value: cloneValue(target.value, "inverse")
1224
1269
  };
1225
- case "dict":
1226
- case "record": return {
1270
+ case "dict": return {
1227
1271
  type: "dict.replace",
1228
1272
  at: operation.at,
1229
1273
  value: cloneValue(target.value, "inverse")
@@ -1262,7 +1306,7 @@ const execute = (root, operation, target, copyPayload) => {
1262
1306
  if (operation.type === "value.clear") return !target ? invalidTarget(operation) : executeClear(target, operation);
1263
1307
  if (operation.type === "field.set" || operation.type === "field.clear") return !target || target.node.kind !== "field" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1264
1308
  if (operation.type === "variant.replace") return !target || target.node.kind !== "variant" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1265
- if (operation.type === "dict.set" || operation.type === "dict.delete" || operation.type === "dict.replace") return !target || target.node.kind !== "dict" && target.node.kind !== "record" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1309
+ if (operation.type === "dict.set" || operation.type === "dict.delete" || operation.type === "dict.replace") return !target || target.node.kind !== "dict" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1266
1310
  if (operation.type === "entity.create" || operation.type === "entity.remove" || operation.type === "entity.move") return !target || target.node.kind !== "table" && target.node.kind !== "map" ? invalidTarget(operation) : executeEntity(target, operation, copyPayload);
1267
1311
  if (operation.type === "list.insert" || operation.type === "list.move" || operation.type === "list.remove" || operation.type === "list.replace") return !target || target.node.kind !== "list" ? invalidTarget(operation) : executeList(target, operation, copyPayload);
1268
1312
  if (operation.type === "tree.insert" || operation.type === "tree.move" || operation.type === "tree.remove" || operation.type === "tree.set" || operation.type === "tree.replace") return !target || target.node.kind !== "tree" ? invalidTarget(operation) : executeTree(root, target, operation, copyPayload);
@@ -1275,7 +1319,7 @@ const execute = (root, operation, target, copyPayload) => {
1275
1319
  //#region core/src/mutation/journal.ts
1276
1320
  const indexNode = () => ({ children: /* @__PURE__ */ new Map() });
1277
1321
  const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1278
- const sameAddress$1 = (left, right) => {
1322
+ const sameAddress = (left, right) => {
1279
1323
  if (left.length !== right.length) return false;
1280
1324
  for (let index = 0; index < left.length; index += 1) if (left[index] !== right[index]) return false;
1281
1325
  return true;
@@ -1374,7 +1418,7 @@ const restoreEntity = (value, subject) => {
1374
1418
  };
1375
1419
  const restoreRootSubject = (value, subject) => {
1376
1420
  if (subject.kind === "value") return subject.before.status === "present" ? subject.before.value : void 0;
1377
- if (subject.kind === "variant") return subject.before;
1421
+ if (subject.kind === "variant") return subject.before.status === "present" ? subject.before.value : void 0;
1378
1422
  if (subject.kind === "dict") {
1379
1423
  if (isRecord(value)) restoreDict(value, subject.state);
1380
1424
  return value;
@@ -1390,14 +1434,14 @@ const restoreRootSubject = (value, subject) => {
1390
1434
  return restoreChange(value, subject.state);
1391
1435
  };
1392
1436
  const restoreSubject = (value, prefix, subject) => {
1393
- if (sameAddress$1(prefix, subject.address)) return restoreRootSubject(value, subject);
1437
+ if (sameAddress(prefix, subject.address)) return restoreRootSubject(value, subject);
1394
1438
  const address = subject.address.slice(prefix.length);
1395
1439
  if (subject.kind === "value") {
1396
1440
  writePresence(value, address, subject.before);
1397
1441
  return value;
1398
1442
  }
1399
1443
  if (subject.kind === "variant") {
1400
- writeContainer(value, address, subject.before);
1444
+ writePresence(value, address, subject.before);
1401
1445
  return value;
1402
1446
  }
1403
1447
  const current = read(value, address);
@@ -1498,7 +1542,11 @@ const analyzeEntity = (root, subject) => {
1498
1542
  };
1499
1543
  const subjectChanged = (root, subject) => {
1500
1544
  if (subject.kind === "value") return valueChanged(root, subject);
1501
- if (subject.kind === "variant") return !sameStructuralValue(subject.before, read(root, subject.address));
1545
+ if (subject.kind === "variant") {
1546
+ const target = locate(root, subject.address);
1547
+ const exists = !!target && hasOwn(target.parent, target.key);
1548
+ return subject.before.status === "absent" ? exists : !exists || !sameStructuralValue(subject.before.value, target?.value);
1549
+ }
1502
1550
  if (subject.kind === "dict") return dictChanged(root, subject);
1503
1551
  if (subject.kind === "list") return listChanged(root, subject);
1504
1552
  return changeChanged(subject.state, read(root, subject.address));
@@ -1617,7 +1665,7 @@ const createChangeJournal = (root) => {
1617
1665
  const candidates = buckets.get(addressHash) ?? [];
1618
1666
  for (const subject of candidates) {
1619
1667
  profile.journal.comparison();
1620
- if (subject.kind === kind && sameAddress$1(subject.address, address)) return subject;
1668
+ if (subject.kind === kind && sameAddress(subject.address, address)) return subject;
1621
1669
  }
1622
1670
  }
1623
1671
  const ownerOf = (address) => {
@@ -1660,11 +1708,11 @@ const createChangeJournal = (root) => {
1660
1708
  const recordVariant = (resolved, operation, inverse) => {
1661
1709
  if (find("variant", operation.at, resolved.addressHash)) return;
1662
1710
  const first = inverse[0];
1663
- if (first?.type !== "variant.replace") return inverseMismatch(operation);
1711
+ if (first?.type !== "variant.replace" && first?.type !== "value.clear") return inverseMismatch(operation);
1664
1712
  add({
1665
1713
  ...base(resolved, operation),
1666
1714
  kind: "variant",
1667
- before: absorb(first.value, operation.at)
1715
+ before: first.type === "value.clear" ? absent : present(absorb(first.value, operation.at))
1668
1716
  });
1669
1717
  };
1670
1718
  const recordDict = (resolved, operation, inverse) => {
@@ -1828,6 +1876,10 @@ const createChangeJournal = (root) => {
1828
1876
  record: (resolved, operation, inverse) => {
1829
1877
  profile.batch.journalRecord();
1830
1878
  if (ownerOf(operation.at)) return;
1879
+ if (operation.type === "value.clear" && resolved.node.kind === "variant") {
1880
+ recordVariant(resolved, operation, inverse);
1881
+ return;
1882
+ }
1831
1883
  if (operation.type === "field.set" || operation.type === "field.clear" || operation.type === "value.clear") {
1832
1884
  recordValue(resolved, operation, inverse);
1833
1885
  return;
@@ -1869,7 +1921,7 @@ const createChangeJournal = (root) => {
1869
1921
  const changeAt = (address, addressHash) => {
1870
1922
  const candidates = collectionBuckets.get(addressHash) ?? [];
1871
1923
  collectionBuckets.set(addressHash, candidates);
1872
- let change = candidates.find((entry) => sameAddress$1(entry.address, address));
1924
+ let change = candidates.find((entry) => sameAddress(entry.address, address));
1873
1925
  if (!change) {
1874
1926
  change = {
1875
1927
  address,
@@ -1913,7 +1965,7 @@ const createChangeJournal = (root) => {
1913
1965
  const has = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1914
1966
  const stringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
1915
1967
  const positions = (value) => Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0);
1916
- const address$1 = (value) => stringArray(value);
1968
+ const address = (value) => stringArray(value);
1917
1969
  const anchor = (value) => {
1918
1970
  if (!isRecord(value)) return false;
1919
1971
  if (Number(has(value, "at")) + Number(has(value, "before")) + Number(has(value, "after")) !== 1) return false;
@@ -1934,7 +1986,7 @@ const rejected = (code, message) => ({
1934
1986
  });
1935
1987
  const decode = (input) => {
1936
1988
  if (!isRecord(input)) return rejected("invalid-operation", "Operation must be an object.");
1937
- if (!address$1(input.at)) return rejected("invalid-address", "Operation address is malformed.");
1989
+ if (!address(input.at)) return rejected("invalid-address", "Operation address is malformed.");
1938
1990
  if (typeof input.type !== "string") return rejected("invalid-operation", "Operation type must be a string.");
1939
1991
  switch (input.type) {
1940
1992
  case "field.set":
@@ -2110,192 +2162,6 @@ const mutateOperations = (root, schema, operations, options = {}) => {
2110
2162
  return session.finish();
2111
2163
  };
2112
2164
  //#endregion
2113
- //#region core/src/mutation/footprint.ts
2114
- const sameAddress = (left, right) => left.length === right.length && left.every((segment, index) => segment === right[index]);
2115
- const containsAddress = (parent, child) => parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
2116
- const address = (value) => Array.isArray(value) && value.every((segment) => typeof segment === "string") ? Object.freeze(value.slice()) : void 0;
2117
- const targetKey = (target) => {
2118
- const at = target.at.join("\0");
2119
- if (target.kind === "dictionary-key" || target.kind === "list-item") return `${target.kind}:${at}:${target.key}`;
2120
- if (target.kind === "entity" || target.kind === "tree-node") return `${target.kind}:${at}:${target.id}`;
2121
- return `${target.kind}:${at}`;
2122
- };
2123
- const freezeTarget = (target) => Object.freeze({
2124
- ...target,
2125
- at: Object.freeze(target.at.slice())
2126
- });
2127
- const add = (targets, target) => {
2128
- const stable = freezeTarget(target);
2129
- targets.set(targetKey(stable), stable);
2130
- };
2131
- const commandFootprint = (operations) => {
2132
- const targets = /* @__PURE__ */ new Map();
2133
- for (const operation of operations) switch (operation.type) {
2134
- case "field.set":
2135
- case "field.clear":
2136
- case "value.clear":
2137
- case "variant.replace":
2138
- add(targets, {
2139
- kind: "value",
2140
- at: operation.at
2141
- });
2142
- break;
2143
- case "dict.replace":
2144
- add(targets, {
2145
- kind: "dictionary",
2146
- at: operation.at
2147
- });
2148
- break;
2149
- case "dict.set":
2150
- case "dict.delete":
2151
- add(targets, {
2152
- kind: "dictionary-key",
2153
- at: operation.at,
2154
- key: operation.key
2155
- });
2156
- break;
2157
- case "entity.create":
2158
- add(targets, {
2159
- kind: "collection",
2160
- at: operation.at
2161
- });
2162
- for (const entry of operation.entries) add(targets, {
2163
- kind: "entity",
2164
- at: operation.at,
2165
- id: entry.id
2166
- });
2167
- break;
2168
- case "entity.remove":
2169
- add(targets, {
2170
- kind: "collection",
2171
- at: operation.at
2172
- });
2173
- for (const id of operation.ids) add(targets, {
2174
- kind: "entity",
2175
- at: operation.at,
2176
- id
2177
- });
2178
- break;
2179
- case "entity.move":
2180
- add(targets, {
2181
- kind: "collection",
2182
- at: operation.at
2183
- });
2184
- add(targets, {
2185
- kind: "entity",
2186
- at: operation.at,
2187
- id: operation.id
2188
- });
2189
- break;
2190
- case "list.insert":
2191
- case "list.move":
2192
- case "list.remove":
2193
- add(targets, {
2194
- kind: "list",
2195
- at: operation.at
2196
- });
2197
- add(targets, {
2198
- kind: "list-item",
2199
- at: operation.at,
2200
- key: operation.key
2201
- });
2202
- break;
2203
- case "list.replace":
2204
- add(targets, {
2205
- kind: "list",
2206
- at: operation.at
2207
- });
2208
- for (const key of operation.keys) add(targets, {
2209
- kind: "list-item",
2210
- at: operation.at,
2211
- key
2212
- });
2213
- break;
2214
- case "tree.insert":
2215
- case "tree.move":
2216
- case "tree.remove":
2217
- case "tree.set":
2218
- add(targets, {
2219
- kind: "tree",
2220
- at: operation.at
2221
- });
2222
- add(targets, {
2223
- kind: "tree-node",
2224
- at: operation.at,
2225
- id: operation.treeNodeId
2226
- });
2227
- break;
2228
- case "tree.replace":
2229
- add(targets, {
2230
- kind: "tree",
2231
- at: operation.at
2232
- });
2233
- break;
2234
- }
2235
- return Object.freeze(Array.from(targets.values()));
2236
- };
2237
- const overlapsValueAndEntity = (value, entity) => containsAddress([...entity.at, entity.id], value.at) || containsAddress(value.at, [...entity.at, entity.id]);
2238
- const targetsOverlap = (left, right) => {
2239
- if (left.kind === "value" && right.kind === "value") return containsAddress(left.at, right.at) || containsAddress(right.at, left.at);
2240
- if (left.kind === "value" && right.kind === "entity") return overlapsValueAndEntity(left, right);
2241
- if (left.kind === "entity" && right.kind === "value") return overlapsValueAndEntity(right, left);
2242
- if (left.kind === "dictionary" && right.kind === "dictionary") return sameAddress(left.at, right.at);
2243
- if (left.kind === "dictionary" && right.kind === "dictionary-key") return sameAddress(left.at, right.at);
2244
- if (left.kind === "dictionary-key" && right.kind === "dictionary") return sameAddress(left.at, right.at);
2245
- if (left.kind === "dictionary-key" && right.kind === "dictionary-key") return sameAddress(left.at, right.at) && left.key === right.key;
2246
- if (left.kind === "collection" && right.kind === "collection") return sameAddress(left.at, right.at);
2247
- if (left.kind === "collection" && right.kind === "entity") return sameAddress(left.at, right.at);
2248
- if (left.kind === "entity" && right.kind === "collection") return sameAddress(left.at, right.at);
2249
- if (left.kind === "entity" && right.kind === "entity") return sameAddress(left.at, right.at) && left.id === right.id;
2250
- if (left.kind === "list" && (right.kind === "list" || right.kind === "list-item")) return sameAddress(left.at, right.at);
2251
- if (right.kind === "list" && left.kind === "list-item") return sameAddress(left.at, right.at);
2252
- if (left.kind === "list-item" && right.kind === "list-item") return sameAddress(left.at, right.at) && left.key === right.key;
2253
- if (left.kind === "tree" && (right.kind === "tree" || right.kind === "tree-node")) return sameAddress(left.at, right.at);
2254
- if (right.kind === "tree" && left.kind === "tree-node") return sameAddress(left.at, right.at);
2255
- if (left.kind === "tree-node" && right.kind === "tree-node") return sameAddress(left.at, right.at) && left.id === right.id;
2256
- return false;
2257
- };
2258
- const footprintsOverlap = (left, right) => left.some((leftTarget) => right.some((rightTarget) => targetsOverlap(leftTarget, rightTarget)));
2259
- const decodedTarget = (value) => {
2260
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
2261
- const record = value;
2262
- const at = address(record.at);
2263
- if (!at || typeof record.kind !== "string") return void 0;
2264
- switch (record.kind) {
2265
- case "value":
2266
- case "dictionary":
2267
- case "collection":
2268
- case "list":
2269
- case "tree": return freezeTarget({
2270
- kind: record.kind,
2271
- at
2272
- });
2273
- case "dictionary-key":
2274
- case "list-item": return typeof record.key === "string" ? freezeTarget({
2275
- kind: record.kind,
2276
- at,
2277
- key: record.key
2278
- }) : void 0;
2279
- case "entity":
2280
- case "tree-node": return typeof record.id === "string" ? freezeTarget({
2281
- kind: record.kind,
2282
- at,
2283
- id: record.id
2284
- }) : void 0;
2285
- default: return;
2286
- }
2287
- };
2288
- const decodeCommandFootprint = (value) => {
2289
- if (!Array.isArray(value)) return void 0;
2290
- const targets = /* @__PURE__ */ new Map();
2291
- for (const entry of value) {
2292
- const target = decodedTarget(entry);
2293
- if (!target) return void 0;
2294
- targets.set(targetKey(target), target);
2295
- }
2296
- return Object.freeze(Array.from(targets.values()));
2297
- };
2298
- //#endregion
2299
2165
  //#region core/src/runtime.ts
2300
2166
  var RejectedUpdate = class extends Error {
2301
2167
  issues;
@@ -2307,6 +2173,7 @@ var RejectedUpdate = class extends Error {
2307
2173
  const EMPTY = Object.freeze([]);
2308
2174
  const publishDiagnostic = (value) => Object.freeze({
2309
2175
  ...value,
2176
+ source: "application",
2310
2177
  ...value.address === void 0 ? {} : { address: Object.freeze(value.address.slice()) }
2311
2178
  });
2312
2179
  const createDocument = (input) => {
@@ -2367,7 +2234,19 @@ const createDocument = (input) => {
2367
2234
  const history = createHistory({
2368
2235
  capacity: input.history === false ? 0 : Math.max(0, input.history?.capacity ?? 100),
2369
2236
  revision: () => revision,
2370
- apply: (operations) => applyBatch(operations, "history", false)
2237
+ apply: (operations) => applyBatch(operations, "history", false),
2238
+ assertIdle: () => {
2239
+ if (state.disposed) throw new DocumentDisposedError();
2240
+ if (busy || accessOf(runtime).projectionLocks) throw new DocumentReentrancyError();
2241
+ },
2242
+ notify: (run) => {
2243
+ busy = true;
2244
+ try {
2245
+ return run();
2246
+ } finally {
2247
+ busy = false;
2248
+ }
2249
+ }
2371
2250
  });
2372
2251
  const publish = (batch, source, kind, recordHistory) => {
2373
2252
  revision += 1;
@@ -2381,10 +2260,12 @@ const createDocument = (input) => {
2381
2260
  });
2382
2261
  if (kind === "replace" || source === "remote") history.invalidate();
2383
2262
  else if (recordHistory && (source === "local" || source === "system")) history.record(batch.operations, batch.inverse);
2263
+ else if (source === "history") history.publish();
2264
+ else history.endGroup();
2384
2265
  busy = true;
2385
2266
  let observerErrors;
2386
2267
  try {
2387
- observerErrors = notify(notification, commit);
2268
+ observerErrors = notify(notification, commit, history.flush);
2388
2269
  } finally {
2389
2270
  busy = false;
2390
2271
  }
@@ -2416,7 +2297,7 @@ const createDocument = (input) => {
2416
2297
  address: {
2417
2298
  resolve: (address) => resolveAddress(input.schema, address, state.document),
2418
2299
  read: (address) => read(state.document, address),
2419
- contains,
2300
+ contains: contains$1,
2420
2301
  overlaps,
2421
2302
  debugKey
2422
2303
  },
@@ -2527,7 +2408,7 @@ const createDocument = (input) => {
2527
2408
  const invalidTree = invalidDocument(input.schema, document);
2528
2409
  if (invalidTree) return {
2529
2410
  status: "rejected",
2530
- issues: [at$1(invalidTree, "invalid-tree", "Document replacement contains an invalid tree.")],
2411
+ issues: [at(invalidTree, "invalid-tree", "Document replacement contains an invalid tree.")],
2531
2412
  revision
2532
2413
  };
2533
2414
  if (deepEqual(state.document, document)) return {
@@ -2562,7 +2443,7 @@ const createDocument = (input) => {
2562
2443
  try {
2563
2444
  disposeNotification(notification);
2564
2445
  } finally {
2565
- history.api.clear();
2446
+ history.dispose();
2566
2447
  disposeRuntimeDriver(runtime);
2567
2448
  }
2568
2449
  }
@@ -2570,6 +2451,7 @@ const createDocument = (input) => {
2570
2451
  bindRuntimeAccess(runtime, state);
2571
2452
  bindRuntimeDriver(runtime);
2572
2453
  notification = createNotification(runtime);
2454
+ bindDocumentReadable(history.api, runtime);
2573
2455
  return runtime;
2574
2456
  };
2575
2457
  //#endregion
@@ -2588,6 +2470,6 @@ const asReadable = (runtime) => {
2588
2470
  //#region core/src/projection/select.ts
2589
2471
  const select = (runtime, selector) => readWith(runtime, selector);
2590
2472
  //#endregion
2591
- export { DocumentDisposedError, DocumentReentrancyError, ProjectionDisposedError, ProjectionError, asReadable, commandFootprint, contains, createDocument, createProjectionRuntime, debugKey, decodeCommandFootprint, dict, field, footprintsOverlap, list, map, object, optional, overlaps, read as readAddress, record, resolveAddress, schema, select, single, table, impact_target_exports as target, tree, variant };
2473
+ export { DocumentDisposedError, DocumentReentrancyError, ProjectionDisposedError, ProjectionError, asReadable, createDocument, createProjectionRuntime, dict, field, list, map, object, optional, schema, select, table, impact_target_exports as target, tree, variant };
2592
2474
 
2593
2475
  //# sourceMappingURL=index.js.map