doxum 0.1.5 → 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 (53) hide show
  1. package/README.md +75 -9
  2. package/dist/{contract-4E3moCbY.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 +284 -947
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +3 -64
  7. package/dist/index.d.ts +3 -64
  8. package/dist/index.js +218 -872
  9. package/dist/index.js.map +1 -1
  10. package/dist/integration-B56u1l9V.js +30 -0
  11. package/dist/integration-B56u1l9V.js.map +1 -0
  12. package/dist/integration-CZwCwFBS.cjs +35 -0
  13. package/dist/integration-CZwCwFBS.cjs.map +1 -0
  14. package/dist/integration.cjs +12 -14
  15. package/dist/integration.d.cts +3 -2
  16. package/dist/integration.d.ts +3 -2
  17. package/dist/integration.js +3 -14
  18. package/dist/local-sync.cjs +57 -18
  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 +57 -18
  23. package/dist/local-sync.js.map +1 -1
  24. package/dist/{ownership-CU-9DKJQ.cjs → ownership-CY0nPXGF.cjs} +4 -1
  25. package/dist/ownership-CY0nPXGF.cjs.map +1 -0
  26. package/dist/{ownership-B-VAPcce.js → ownership-CduRygE7.js} +4 -1
  27. package/dist/ownership-CduRygE7.js.map +1 -0
  28. package/dist/react.cjs +27 -19
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.js +27 -19
  31. package/dist/react.js.map +1 -1
  32. package/dist/runtime-0mOFbe_H.cjs +2439 -0
  33. package/dist/runtime-0mOFbe_H.cjs.map +1 -0
  34. package/dist/runtime-B22tuj9A.d.cts +150 -0
  35. package/dist/runtime-BFmhzPpZ.d.ts +150 -0
  36. package/dist/runtime-DF1q9Gje.js +2129 -0
  37. package/dist/runtime-DF1q9Gje.js.map +1 -0
  38. package/package.json +1 -1
  39. package/skills/doxum-runtime/SKILL.md +2 -1
  40. package/skills/doxum-runtime/references/guide.en.md +38 -26
  41. package/skills/doxum-runtime/references/guide.zh-CN.md +24 -21
  42. package/skills/doxum-runtime/references/invariants.en.md +13 -10
  43. package/skills/doxum-runtime/references/invariants.zh-CN.md +3 -3
  44. package/skills/doxum-runtime/references/patterns.en.md +26 -64
  45. package/skills/doxum-runtime/references/patterns.zh-CN.md +15 -52
  46. package/dist/dependency-C5_R1tvQ.js +0 -496
  47. package/dist/dependency-C5_R1tvQ.js.map +0 -1
  48. package/dist/dependency-Dfzs3khk.cjs +0 -722
  49. package/dist/dependency-Dfzs3khk.cjs.map +0 -1
  50. package/dist/integration.cjs.map +0 -1
  51. package/dist/integration.js.map +0 -1
  52. package/dist/ownership-B-VAPcce.js.map +0 -1
  53. package/dist/ownership-CU-9DKJQ.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dependency = require("./dependency-Dfzs3khk.cjs");
3
- const require_ownership = require("./ownership-CU-9DKJQ.cjs");
2
+ const require_runtime = require("./runtime-0mOFbe_H.cjs");
3
+ const require_ownership = require("./ownership-CY0nPXGF.cjs");
4
4
  const require_driver = require("./driver-CNxqMVFH.cjs");
5
5
  //#region core/src/impact.ts
6
6
  const emptyCollectionImpact = Object.freeze({
@@ -29,7 +29,7 @@ const createImpact = (input) => {
29
29
  if (pathBuckets.get(void 0)?.some(test)) return true;
30
30
  return pathBuckets.get(address[0])?.some(test) ?? false;
31
31
  };
32
- const pathsAffect = (address) => address.length === 0 ? paths.length > 0 : somePath(address, (changed) => require_dependency.overlaps(changed, address));
32
+ const pathsAffect = (address) => address.length === 0 ? paths.length > 0 : somePath(address, (changed) => require_runtime.overlaps(changed, address));
33
33
  let collectionCache;
34
34
  const findCollection = (address) => input.collections?.find((entry) => entry.address.length === address.length && entry.address.every((segment, index) => segment === address[index]));
35
35
  return {
@@ -38,12 +38,12 @@ const createImpact = (input) => {
38
38
  affects: (value) => {
39
39
  require_ownership.profile.impact.affects();
40
40
  if (kind === "reset") return true;
41
- if (!require_dependency.belongs(value, input.schema)) return false;
42
- if (pathsAffect(require_dependency.address(value))) return true;
41
+ if (!require_runtime.belongs(value, input.schema)) return false;
42
+ if (pathsAffect(require_runtime.address(value))) return true;
43
43
  if (value.kind === "collection") {
44
- const collection = findCollection(require_dependency.address(value));
44
+ const collection = findCollection(require_runtime.address(value));
45
45
  if (!collection) return false;
46
- const id$1 = require_dependency.id(value);
46
+ const id$1 = require_runtime.id(value);
47
47
  if (id$1 !== void 0) return collection.added.has(id$1) || collection.removed.has(id$1) || collection.updated.has(id$1);
48
48
  return collection.added.size > 0 || collection.removed.size > 0 || collection.updated.size > 0 || collection.orderChanged;
49
49
  }
@@ -55,7 +55,7 @@ const createImpact = (input) => {
55
55
  const cached = collectionCache?.find((entry) => entry.address.length === selector.address.length && entry.address.every((segment, index) => segment === selector.address[index]))?.value;
56
56
  if (cached) return cached;
57
57
  const exact = findCollection(selector.address);
58
- if (somePath(selector.address, (changed) => require_dependency.contains(changed, selector.address) && changed.length < selector.address.length)) {
58
+ if (somePath(selector.address, (changed) => require_runtime.contains(changed, selector.address) && changed.length < selector.address.length)) {
59
59
  const reset = { kind: "reset" };
60
60
  (collectionCache ??= []).push({
61
61
  address: selector.address,
@@ -89,83 +89,175 @@ const createImpact = (input) => {
89
89
  //#endregion
90
90
  //#region core/src/history.ts
91
91
  const createHistory = (input) => {
92
- const undos = [];
93
- const redos = [];
92
+ let undos = [];
93
+ let redos = [];
94
94
  const listeners = /* @__PURE__ */ new Set();
95
95
  let state = Object.freeze({
96
96
  undoDepth: 0,
97
97
  redoDepth: 0
98
98
  });
99
- const current = () => {
100
- if (state.undoDepth !== undos.length || state.redoDepth !== redos.length) state = Object.freeze({
99
+ let revision = 0;
100
+ let pending = false;
101
+ let disposed = false;
102
+ let group;
103
+ const unchanged = () => ({
104
+ status: "unchanged",
105
+ revision: input.revision()
106
+ });
107
+ const publish = () => {
108
+ if (state.undoDepth === undos.length && state.redoDepth === redos.length) return;
109
+ state = Object.freeze({
101
110
  undoDepth: undos.length,
102
111
  redoDepth: redos.length
103
112
  });
104
- return state;
105
- };
106
- const emit = () => {
107
- if (listeners.size === 0) return;
108
- current();
109
- Array.from(listeners).forEach((listener) => listener());
113
+ revision++;
114
+ pending = true;
115
+ };
116
+ const flush = () => {
117
+ if (!pending) return [];
118
+ pending = false;
119
+ const errors = [];
120
+ for (const listener of [...listeners]) {
121
+ if (!listeners.has(listener)) continue;
122
+ try {
123
+ listener();
124
+ } catch (error) {
125
+ errors.push({
126
+ phase: "listener",
127
+ error
128
+ });
129
+ }
130
+ }
131
+ return errors;
110
132
  };
111
133
  const clear = () => {
112
- if (undos.length || redos.length) {
113
- undos.length = 0;
114
- redos.length = 0;
115
- emit();
134
+ group = void 0;
135
+ undos.length = redos.length = 0;
136
+ publish();
137
+ };
138
+ const travel = (direction) => {
139
+ input.assertIdle();
140
+ const from = direction === "redo" ? redos : undos;
141
+ const to = direction === "redo" ? undos : redos;
142
+ const previousUndos = undos;
143
+ const previousRedos = redos;
144
+ const entry = direction === "cancel" ? from.at(-1) : from.pop();
145
+ if (!entry) return unchanged();
146
+ if (direction === "cancel") {
147
+ if (!group || group.entry !== entry) throw new Error("History group no longer owns its entry.");
148
+ undos = group.undos;
149
+ redos = group.redos;
150
+ } else to.push(entry);
151
+ let committed = false;
152
+ try {
153
+ const operations = direction === "redo" ? entry.batches.flatMap((batch) => batch.operations) : entry.batches.slice().reverse().flatMap((batch) => batch.inverse);
154
+ const result = input.apply(operations);
155
+ committed = result.status !== "rejected";
156
+ if (committed && direction === "cancel") group = void 0;
157
+ if (result.status === "unchanged") {
158
+ publish();
159
+ const errors = input.notify(flush);
160
+ if (errors.length) throw new AggregateError(errors.map((entry) => entry.error), "History listeners failed.");
161
+ }
162
+ return result;
163
+ } finally {
164
+ if (!committed) if (direction === "cancel") {
165
+ undos = previousUndos;
166
+ redos = previousRedos;
167
+ } else {
168
+ to.pop();
169
+ from.push(entry);
170
+ }
116
171
  }
117
172
  };
118
173
  return {
119
174
  api: {
120
- current,
175
+ current: () => state,
176
+ revision: () => revision,
121
177
  subscribe: (listener) => {
178
+ if (disposed) throw new require_runtime.DocumentDisposedError();
122
179
  listeners.add(listener);
123
- return () => listeners.delete(listener);
180
+ return () => {
181
+ listeners.delete(listener);
182
+ };
124
183
  },
125
184
  undo: () => {
126
- const entry = undos.pop();
127
- if (!entry) return {
128
- status: "unchanged",
129
- revision: input.revision()
130
- };
131
- const result = input.apply(entry.inverse);
132
- if (result.status === "committed") {
133
- redos.push(entry);
134
- emit();
135
- } else undos.push(entry);
136
- return result;
185
+ input.assertIdle();
186
+ group = void 0;
187
+ return travel("undo");
137
188
  },
138
189
  redo: () => {
139
- const entry = redos.pop();
140
- if (!entry) return {
141
- status: "unchanged",
142
- revision: input.revision()
143
- };
144
- const result = input.apply(entry.operations);
145
- if (result.status === "committed") {
146
- undos.push(entry);
147
- emit();
148
- } else redos.push(entry);
149
- return result;
190
+ input.assertIdle();
191
+ group = void 0;
192
+ return travel("redo");
150
193
  },
151
- clear
194
+ clear: () => {
195
+ input.assertIdle();
196
+ clear();
197
+ const errors = input.notify(flush);
198
+ if (errors.length) throw new AggregateError(errors.map((entry) => entry.error), "History listeners failed.");
199
+ },
200
+ group: () => {
201
+ input.assertIdle();
202
+ if (group) throw new Error("A history group is already active.");
203
+ if (input.capacity <= 0) throw new Error("History is disabled.");
204
+ const scope = {
205
+ undos: undos.slice(),
206
+ redos: redos.slice()
207
+ };
208
+ group = scope;
209
+ return Object.freeze({
210
+ end: () => {
211
+ input.assertIdle();
212
+ if (group === scope) group = void 0;
213
+ },
214
+ cancel: () => {
215
+ input.assertIdle();
216
+ if (group !== scope) return unchanged();
217
+ if (!scope.entry) {
218
+ group = void 0;
219
+ return unchanged();
220
+ }
221
+ const result = travel("cancel");
222
+ if (result.status !== "rejected") group = void 0;
223
+ return result;
224
+ }
225
+ });
226
+ }
152
227
  },
153
228
  record: (operations, inverse) => {
154
229
  if (input.capacity <= 0) return;
155
- undos.push({
230
+ const batch = {
156
231
  operations,
157
232
  inverse
158
- });
159
- if (undos.length > input.capacity) undos.shift();
233
+ };
234
+ if (group?.entry) group.entry.batches.push(batch);
235
+ else {
236
+ const entry = { batches: [batch] };
237
+ undos.push(entry);
238
+ if (group) group.entry = entry;
239
+ if (undos.length > input.capacity) undos.shift();
240
+ }
160
241
  redos.length = 0;
161
- emit();
242
+ publish();
243
+ },
244
+ publish,
245
+ flush,
246
+ endGroup: () => {
247
+ group = void 0;
162
248
  },
163
- invalidate: clear
249
+ invalidate: clear,
250
+ dispose: () => {
251
+ disposed = true;
252
+ listeners.clear();
253
+ clear();
254
+ pending = false;
255
+ }
164
256
  };
165
257
  };
166
258
  //#endregion
167
259
  //#region core/src/access/writer.ts
168
- const schemaRoot = (schema) => require_dependency.object(schema.shape);
260
+ const schemaRoot = (schema) => require_runtime.object(schema.shape);
169
261
  const emit = (sink, operation) => sink(operation);
170
262
  const writerFor = (node, inputAddress, sink) => {
171
263
  const address = Object.freeze(inputAddress);
@@ -180,7 +272,7 @@ const writerFor = (node, inputAddress, sink) => {
180
272
  at: address
181
273
  }) : void 0
182
274
  };
183
- if (node.kind === "dict" || node.kind === "record") return {
275
+ if (node.kind === "dict") return {
184
276
  set: (key, value) => emit(sink, {
185
277
  type: "dict.set",
186
278
  at: address,
@@ -233,20 +325,20 @@ const writerFor = (node, inputAddress, sink) => {
233
325
  }) } : {}
234
326
  };
235
327
  if (node.kind === "tree") return {
236
- insert: (treeNodeId, value, parentId, index) => emit(sink, {
328
+ insert: (treeNodeId, value, position) => emit(sink, {
237
329
  type: "tree.insert",
238
330
  at: address,
239
331
  treeNodeId,
240
332
  value,
241
- parentId,
242
- index
333
+ parentId: position?.parentId,
334
+ index: position?.index
243
335
  }),
244
- move: (treeNodeId, parentId, index) => emit(sink, {
336
+ move: (treeNodeId, position) => emit(sink, {
245
337
  type: "tree.move",
246
338
  at: address,
247
339
  treeNodeId,
248
- parentId,
249
- index
340
+ parentId: position?.parentId,
341
+ index: position?.index
250
342
  }),
251
343
  remove: (treeNodeId) => emit(sink, {
252
344
  type: "tree.remove",
@@ -330,7 +422,6 @@ const writerFor = (node, inputAddress, sink) => {
330
422
  })
331
423
  };
332
424
  }
333
- if (node.kind === "single") return writerFor(node.value, address, sink);
334
425
  if (node.kind === "variant") return {
335
426
  replace: (value) => emit(sink, {
336
427
  type: "variant.replace",
@@ -356,69 +447,15 @@ const documentWriter = (schema, sink) => writerFor(schemaRoot(schema), [], sink)
356
447
  //#endregion
357
448
  //#region core/src/mutation/issue.ts
358
449
  const ownAddress$1 = (address) => Object.freeze(address.slice());
359
- const at$1 = (address, code, message, operation) => Object.freeze({
450
+ const at = (address, code, message, operation) => Object.freeze({
360
451
  source: "mutation",
361
452
  code,
362
453
  address: ownAddress$1(address),
363
454
  message,
364
455
  ...operation === void 0 ? {} : { operation }
365
456
  });
366
- const from = (operation, code, message) => at$1(operation.at, code, message, operation.type);
367
- const input = (code, message) => at$1([], code, message);
368
- //#endregion
369
- //#region core/src/mutation/anchor.ts
370
- const length = (keys) => keys.length;
371
- const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
372
- const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
373
- const keys = (values, keyOf) => ({
374
- length: values.length,
375
- at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
376
- index: (key) => {
377
- for (let index = 0; index < values.length; index += 1) if (keyOf(values[index]) === key) return index;
378
- return -1;
379
- }
380
- });
381
- const index = (keys, anchor) => {
382
- if (!anchor) return length(keys);
383
- if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
384
- if ("before" in anchor) return keyIndex(keys, anchor.before);
385
- return keyIndex(keys, anchor.after) + 1;
386
- };
387
- const at = (keys, position) => position <= 0 ? { at: "start" } : { after: keyAt(keys, position - 1) };
388
- const valid = (keys, anchor) => {
389
- if (!anchor) return true;
390
- if (typeof anchor !== "object" || anchor === null) return false;
391
- if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
392
- if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
393
- return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
394
- };
395
- const afterRemove = (keys, removedIndex, anchor) => {
396
- const position = index(keys, anchor);
397
- return position > removedIndex ? position - 1 : position;
398
- };
399
- const validPositions = (length, positions) => {
400
- let previous = -1;
401
- for (let index = 0; index < positions.length; index += 1) {
402
- const position = positions[index];
403
- if (!Number.isInteger(position) || position < 0 || position <= previous || position > length + index) return false;
404
- previous = position;
405
- }
406
- return true;
407
- };
408
- const restore = (keys, inserted, positions) => {
409
- const currentLength = keys.length;
410
- if (inserted.length !== positions.length || !validPositions(currentLength, positions)) throw new Error("Ordered restore positions are invalid.");
411
- let readIndex = currentLength - 1;
412
- let insertedIndex = inserted.length - 1;
413
- keys.length = currentLength + inserted.length;
414
- for (let writeIndex = keys.length - 1; writeIndex >= 0; writeIndex -= 1) if (insertedIndex >= 0 && positions[insertedIndex] === writeIndex) {
415
- keys[writeIndex] = inserted[insertedIndex];
416
- insertedIndex -= 1;
417
- } else {
418
- keys[writeIndex] = keys[readIndex];
419
- readIndex -= 1;
420
- }
421
- };
457
+ const from = (operation, code, message) => at(operation.at, code, message, operation.type);
458
+ const input = (code, message) => at([], code, message);
422
459
  //#endregion
423
460
  //#region core/src/mutation/execute-entity.ts
424
461
  const resolveEntityCollection = (node, value) => {
@@ -463,13 +500,13 @@ const executeEntity = (target, operation, copyPayload) => {
463
500
  }
464
501
  if (collection.kind === "map" && (operation.anchor || operation.positions)) return rejected$4(operation, "invalid-anchor", "Map entries do not support ordering metadata.");
465
502
  if (ids && operation.positions) {
466
- if (operation.positions.length !== operation.entries.length || !validPositions(ids.length, operation.positions)) return rejected$4(operation, "invalid-operation", "Entity restore positions are invalid.");
467
- } else if (ids && !valid(ids, operation.anchor)) return rejected$4(operation, "invalid-anchor", "Collection anchor is invalid.");
503
+ if (operation.positions.length !== operation.entries.length || !require_runtime.validPositions(ids.length, operation.positions)) return rejected$4(operation, "invalid-operation", "Entity restore positions are invalid.");
504
+ } else if (ids && !require_runtime.valid(ids, operation.anchor)) return rejected$4(operation, "invalid-anchor", "Collection anchor is invalid.");
468
505
  for (const entry of operation.entries) byId[entry.id] = copyPayload ? require_ownership.cloneValue(entry.value, "canonical") : require_ownership.transferPayload(entry.value);
469
- if (ids) if (operation.positions) restore(ids, operation.entries.map((entry) => entry.id), operation.positions);
506
+ if (ids) if (operation.positions) require_runtime.restore(ids, operation.entries.map((entry) => entry.id), operation.positions);
470
507
  else if (!operation.anchor || "at" in operation.anchor && operation.anchor.at === "end") for (const entry of operation.entries) ids.push(entry.id);
471
508
  else {
472
- const insertion = index(ids, operation.anchor);
509
+ const insertion = require_runtime.index(ids, operation.anchor);
473
510
  const currentLength = ids.length;
474
511
  ids.length = currentLength + operation.entries.length;
475
512
  for (let index = currentLength - 1; index >= insertion; index -= 1) ids[index + operation.entries.length] = ids[index];
@@ -535,15 +572,15 @@ const executeEntity = (target, operation, copyPayload) => {
535
572
  const position = ids?.indexOf(operation.id) ?? -1;
536
573
  if (!exists) return rejected$4(operation, "missing-entity", `Entity '${operation.id}' does not exist.`);
537
574
  if (!ids || position < 0) return rejected$4(operation, "invalid-collection", `Collection index is missing '${operation.id}'.`);
538
- if (!valid(ids, operation.anchor)) return rejected$4(operation, "invalid-anchor", "Collection anchor is invalid.");
575
+ if (!require_runtime.valid(ids, operation.anchor)) return rejected$4(operation, "invalid-anchor", "Collection anchor is invalid.");
539
576
  if (operation.anchor && ("before" in operation.anchor && operation.anchor.before === operation.id || "after" in operation.anchor && operation.anchor.after === operation.id)) return rejected$4(operation, "invalid-anchor", "An entity cannot be moved relative to itself.");
540
- const nextIndex = afterRemove(ids, position, operation.anchor);
577
+ const nextIndex = require_runtime.afterRemove(ids, position, operation.anchor);
541
578
  if (nextIndex === position) return { status: "unchanged" };
542
579
  const inverse = {
543
580
  type: "entity.move",
544
581
  at: operation.at,
545
582
  id: operation.id,
546
- anchor: at(ids, position)
583
+ anchor: require_runtime.at(ids, position)
547
584
  };
548
585
  ids.splice(position, 1);
549
586
  ids.splice(nextIndex, 0, operation.id);
@@ -564,7 +601,7 @@ const executeList = (target, operation, copyPayload) => {
564
601
  if (!Array.isArray(target.value) && !absent) return rejected$3(operation, "invalid-address", "List target is invalid.");
565
602
  if (absent && operation.type !== "list.replace") return rejected$3(operation, "invalid-address", "Only list replacement can initialize an optional list.");
566
603
  const list = Array.isArray(target.value) ? target.value : [];
567
- const orderedKeys = keys(list, target.node.keyOf);
604
+ const orderedKeys = require_runtime.keys(list, target.node.keyOf);
568
605
  const own = (value) => copyPayload ? require_ownership.ownPayload(value) : value;
569
606
  if (operation.type === "list.replace") {
570
607
  if (operation.keys.length !== operation.value.length) return rejected$3(operation, "invalid-list-keys", "List replacement keys are invalid.");
@@ -599,8 +636,8 @@ const executeList = (target, operation, copyPayload) => {
599
636
  if (operation.type === "list.insert") {
600
637
  if (target.node.keyOf(operation.value) !== operation.key) return rejected$3(operation, "invalid-list-key", "List item key does not match schema.");
601
638
  if (orderedKeys.index(operation.key) >= 0) return rejected$3(operation, "duplicate-list-item", `List item '${operation.key}' already exists.`);
602
- if (!valid(orderedKeys, operation.anchor)) return rejected$3(operation, "invalid-anchor", "List anchor is invalid.");
603
- list.splice(index(orderedKeys, operation.anchor), 0, own(operation.value));
639
+ if (!require_runtime.valid(orderedKeys, operation.anchor)) return rejected$3(operation, "invalid-anchor", "List anchor is invalid.");
640
+ list.splice(require_runtime.index(orderedKeys, operation.anchor), 0, own(operation.value));
604
641
  return {
605
642
  status: "changed",
606
643
  inverse: [{
@@ -618,7 +655,7 @@ const executeList = (target, operation, copyPayload) => {
618
655
  at: operation.at,
619
656
  key: operation.key,
620
657
  value: require_ownership.cloneValue(list[position], "inverse"),
621
- anchor: at(orderedKeys, position)
658
+ anchor: require_runtime.at(orderedKeys, position)
622
659
  };
623
660
  list.splice(position, 1);
624
661
  return {
@@ -626,15 +663,15 @@ const executeList = (target, operation, copyPayload) => {
626
663
  inverse: [inverse]
627
664
  };
628
665
  }
629
- if (!valid(orderedKeys, operation.anchor)) return rejected$3(operation, "invalid-anchor", "List anchor is invalid.");
666
+ if (!require_runtime.valid(orderedKeys, operation.anchor)) return rejected$3(operation, "invalid-anchor", "List anchor is invalid.");
630
667
  if (operation.anchor && ("before" in operation.anchor && operation.anchor.before === operation.key || "after" in operation.anchor && operation.anchor.after === operation.key)) return rejected$3(operation, "invalid-anchor", "A list item cannot be moved relative to itself.");
631
- const nextIndex = afterRemove(orderedKeys, position, operation.anchor);
668
+ const nextIndex = require_runtime.afterRemove(orderedKeys, position, operation.anchor);
632
669
  if (nextIndex === position) return { status: "unchanged" };
633
670
  const inverse = {
634
671
  type: "list.move",
635
672
  at: operation.at,
636
673
  key: operation.key,
637
- anchor: at(orderedKeys, position)
674
+ anchor: require_runtime.at(orderedKeys, position)
638
675
  };
639
676
  const [entry] = list.splice(position, 1);
640
677
  list.splice(nextIndex, 0, entry);
@@ -817,7 +854,7 @@ const changeChanged = (change, value) => {
817
854
  return false;
818
855
  };
819
856
  const is = (value) => require_ownership.isRecord(value) && require_ownership.isRecord(value.nodes);
820
- const contains$1 = (tree, id) => has$1(tree.nodes, id);
857
+ const contains = (tree, id) => has$1(tree.nodes, id);
821
858
  const validate = (value) => {
822
859
  if (!is(value)) return false;
823
860
  const ids = Object.keys(value.nodes);
@@ -859,7 +896,6 @@ const documentTreeError = (node, value, address) => {
859
896
  }
860
897
  return;
861
898
  }
862
- if (node.kind === "single") return documentTreeError(node.value, value, address);
863
899
  if (node.kind === "variant") {
864
900
  if (!require_ownership.isRecord(value) || typeof value[node.tag] !== "string") return void 0;
865
901
  const variant = node.variants[value[node.tag]];
@@ -898,7 +934,7 @@ const replace = (current, operation) => {
898
934
  };
899
935
  };
900
936
  const insert = (tree, operation, value) => {
901
- if (contains$1(tree, operation.treeNodeId)) return rejected$2("duplicate-tree-node", `Tree node '${operation.treeNodeId}' already exists.`);
937
+ if (contains(tree, operation.treeNodeId)) return rejected$2("duplicate-tree-node", `Tree node '${operation.treeNodeId}' already exists.`);
902
938
  if (!validIndex(operation.index)) return rejected$2("invalid-tree-index", "Tree index is invalid.");
903
939
  if (tree.rootId === void 0) {
904
940
  if (operation.parentId !== void 0) return rejected$2("missing-tree-parent", `Tree parent '${operation.parentId}' does not exist.`);
@@ -1023,9 +1059,8 @@ const move = (tree, operation) => {
1023
1059
  if (!currentParent || parentId === void 0) return rejected$2("invalid-tree", "Tree parent links are inconsistent.");
1024
1060
  const originalIndex = childIndex(currentParent, operation.treeNodeId);
1025
1061
  if (originalIndex < 0) return rejected$2("invalid-tree", "Tree parent links are inconsistent.");
1026
- let nextIndex = Math.min(operation.index ?? nextParent.children.length, nextParent.children.length);
1027
1062
  currentParent.children.splice(originalIndex, 1);
1028
- if (parentId === operation.parentId && nextIndex > originalIndex) nextIndex -= 1;
1063
+ const nextIndex = Math.min(operation.index ?? nextParent.children.length, nextParent.children.length);
1029
1064
  if (parentId === operation.parentId && nextIndex === originalIndex) {
1030
1065
  currentParent.children.splice(originalIndex, 0, operation.treeNodeId);
1031
1066
  return { status: "unchanged" };
@@ -1076,7 +1111,7 @@ const executeTree = (root, target, operation, copyPayload) => {
1076
1111
  issue: from(operation, "invalid-address", "Tree target is invalid.")
1077
1112
  };
1078
1113
  const result = replace(target.value, operation);
1079
- if (result.status === "changed") require_dependency.set(root, operation.at, require_ownership.ownPayload(operation.value));
1114
+ if (result.status === "changed") require_runtime.set(root, operation.at, require_ownership.ownPayload(operation.value));
1080
1115
  return outcome(operation, result);
1081
1116
  }
1082
1117
  if (!is(target.value)) return {
@@ -1129,6 +1164,16 @@ const executeValue = (target, operation, copyPayload) => {
1129
1164
  }
1130
1165
  if (operation.type === "variant.replace") {
1131
1166
  if (require_ownership.sameStructuralValue(target.value, operation.value)) return { status: "unchanged" };
1167
+ if (target.value === void 0 && target.node.optional) {
1168
+ target.parent[target.key] = own(operation.value);
1169
+ return {
1170
+ status: "changed",
1171
+ inverse: [{
1172
+ type: "value.clear",
1173
+ at: operation.at
1174
+ }]
1175
+ };
1176
+ }
1132
1177
  const inverse = {
1133
1178
  type: "variant.replace",
1134
1179
  at: operation.at,
@@ -1207,7 +1252,7 @@ const executeClear = (target, operation) => {
1207
1252
  issue: from(operation, "required-field", "Only optional values can be cleared.")
1208
1253
  };
1209
1254
  if (!hasOwn$1(target.parent, target.key)) return { status: "unchanged" };
1210
- 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 {
1255
+ if (target.node.kind !== "field" && target.node.kind !== "variant" && target.node.kind !== "dict" && target.node.kind !== "list" && target.node.kind !== "tree") return {
1211
1256
  status: "rejected",
1212
1257
  issue: from(operation, "invalid-operation", "This optional value has no clear operation.")
1213
1258
  };
@@ -1223,8 +1268,7 @@ const executeClear = (target, operation) => {
1223
1268
  at: operation.at,
1224
1269
  value: require_ownership.cloneValue(target.value, "inverse")
1225
1270
  };
1226
- case "dict":
1227
- case "record": return {
1271
+ case "dict": return {
1228
1272
  type: "dict.replace",
1229
1273
  at: operation.at,
1230
1274
  value: require_ownership.cloneValue(target.value, "inverse")
@@ -1263,7 +1307,7 @@ const execute = (root, operation, target, copyPayload) => {
1263
1307
  if (operation.type === "value.clear") return !target ? invalidTarget(operation) : executeClear(target, operation);
1264
1308
  if (operation.type === "field.set" || operation.type === "field.clear") return !target || target.node.kind !== "field" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1265
1309
  if (operation.type === "variant.replace") return !target || target.node.kind !== "variant" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1266
- 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);
1310
+ 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);
1267
1311
  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);
1268
1312
  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);
1269
1313
  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);
@@ -1276,7 +1320,7 @@ const execute = (root, operation, target, copyPayload) => {
1276
1320
  //#region core/src/mutation/journal.ts
1277
1321
  const indexNode = () => ({ children: /* @__PURE__ */ new Map() });
1278
1322
  const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1279
- const sameAddress$1 = (left, right) => {
1323
+ const sameAddress = (left, right) => {
1280
1324
  if (left.length !== right.length) return false;
1281
1325
  for (let index = 0; index < left.length; index += 1) if (left[index] !== right[index]) return false;
1282
1326
  return true;
@@ -1326,14 +1370,14 @@ const indexedSubjects = (root, address) => {
1326
1370
  return subjects;
1327
1371
  };
1328
1372
  const writePresence = (root, address, before) => {
1329
- const target = require_dependency.locate(root, address);
1373
+ const target = require_runtime.locate(root, address);
1330
1374
  if (!target) return;
1331
1375
  if (before.status === "present") target.parent[target.key] = before.value;
1332
1376
  else if (Array.isArray(target.parent)) target.parent.splice(Number(target.key), 1);
1333
1377
  else delete target.parent[String(target.key)];
1334
1378
  };
1335
1379
  const writeContainer = (root, address, value) => {
1336
- const target = require_dependency.locate(root, address);
1380
+ const target = require_runtime.locate(root, address);
1337
1381
  if (!target) return;
1338
1382
  target.parent[target.key] = value;
1339
1383
  };
@@ -1375,7 +1419,7 @@ const restoreEntity = (value, subject) => {
1375
1419
  };
1376
1420
  const restoreRootSubject = (value, subject) => {
1377
1421
  if (subject.kind === "value") return subject.before.status === "present" ? subject.before.value : void 0;
1378
- if (subject.kind === "variant") return subject.before;
1422
+ if (subject.kind === "variant") return subject.before.status === "present" ? subject.before.value : void 0;
1379
1423
  if (subject.kind === "dict") {
1380
1424
  if (require_ownership.isRecord(value)) restoreDict(value, subject.state);
1381
1425
  return value;
@@ -1391,17 +1435,17 @@ const restoreRootSubject = (value, subject) => {
1391
1435
  return restoreChange(value, subject.state);
1392
1436
  };
1393
1437
  const restoreSubject = (value, prefix, subject) => {
1394
- if (sameAddress$1(prefix, subject.address)) return restoreRootSubject(value, subject);
1438
+ if (sameAddress(prefix, subject.address)) return restoreRootSubject(value, subject);
1395
1439
  const address = subject.address.slice(prefix.length);
1396
1440
  if (subject.kind === "value") {
1397
1441
  writePresence(value, address, subject.before);
1398
1442
  return value;
1399
1443
  }
1400
1444
  if (subject.kind === "variant") {
1401
- writeContainer(value, address, subject.before);
1445
+ writePresence(value, address, subject.before);
1402
1446
  return value;
1403
1447
  }
1404
- const current = require_dependency.read(value, address);
1448
+ const current = require_runtime.read(value, address);
1405
1449
  if (subject.kind === "dict") {
1406
1450
  if (require_ownership.isRecord(current)) restoreDict(current, subject.state);
1407
1451
  return value;
@@ -1419,13 +1463,13 @@ const restoreSubject = (value, prefix, subject) => {
1419
1463
  return value;
1420
1464
  };
1421
1465
  const valueChanged = (root, subject) => {
1422
- const target = require_dependency.locate(root, subject.address);
1466
+ const target = require_runtime.locate(root, subject.address);
1423
1467
  const exists = !!target && hasOwn(target.parent, target.key);
1424
1468
  if (subject.before.status === "absent") return exists;
1425
1469
  return !exists || !Object.is(subject.before.value, target?.value);
1426
1470
  };
1427
1471
  const dictChanged = (root, subject) => {
1428
- const value = require_dependency.read(root, subject.address);
1472
+ const value = require_runtime.read(root, subject.address);
1429
1473
  if (!require_ownership.isRecord(value)) return true;
1430
1474
  if (subject.state.mode === "whole") return !require_ownership.sameStructuralValue(subject.state.value, value);
1431
1475
  for (const [key, before] of subject.state.entries) {
@@ -1435,7 +1479,7 @@ const dictChanged = (root, subject) => {
1435
1479
  return false;
1436
1480
  };
1437
1481
  const listChanged = (root, subject) => {
1438
- const value = require_dependency.read(root, subject.address);
1482
+ const value = require_runtime.read(root, subject.address);
1439
1483
  if (!Array.isArray(value)) return true;
1440
1484
  if (subject.state.mode === "whole") return !require_ownership.sameStructuralValue(subject.state.value, value);
1441
1485
  const positions = /* @__PURE__ */ new Map();
@@ -1467,7 +1511,7 @@ const commonOrderChanged = (before, after) => {
1467
1511
  return false;
1468
1512
  };
1469
1513
  const analyzeEntity = (root, subject) => {
1470
- const resolution = entityCollection(subject, require_dependency.read(root, subject.address));
1514
+ const resolution = entityCollection(subject, require_runtime.read(root, subject.address));
1471
1515
  const added = /* @__PURE__ */ new Set();
1472
1516
  const removed = /* @__PURE__ */ new Set();
1473
1517
  const updated = /* @__PURE__ */ new Set();
@@ -1499,22 +1543,26 @@ const analyzeEntity = (root, subject) => {
1499
1543
  };
1500
1544
  const subjectChanged = (root, subject) => {
1501
1545
  if (subject.kind === "value") return valueChanged(root, subject);
1502
- if (subject.kind === "variant") return !require_ownership.sameStructuralValue(subject.before, require_dependency.read(root, subject.address));
1546
+ if (subject.kind === "variant") {
1547
+ const target = require_runtime.locate(root, subject.address);
1548
+ const exists = !!target && hasOwn(target.parent, target.key);
1549
+ return subject.before.status === "absent" ? exists : !exists || !require_ownership.sameStructuralValue(subject.before.value, target?.value);
1550
+ }
1503
1551
  if (subject.kind === "dict") return dictChanged(root, subject);
1504
1552
  if (subject.kind === "list") return listChanged(root, subject);
1505
- return changeChanged(subject.state, require_dependency.read(root, subject.address));
1553
+ return changeChanged(subject.state, require_runtime.read(root, subject.address));
1506
1554
  };
1507
1555
  const listOrderBefore = (value, node, inverse) => {
1508
1556
  const order = value.map(node.keyOf);
1509
1557
  if (inverse.type === "list.remove") return order.filter((key) => key !== inverse.key);
1510
1558
  if (inverse.type === "list.insert") {
1511
- order.splice(index(order, inverse.anchor), 0, inverse.key);
1559
+ order.splice(require_runtime.index(order, inverse.anchor), 0, inverse.key);
1512
1560
  return order;
1513
1561
  }
1514
1562
  if (inverse.type === "list.move") {
1515
1563
  const position = order.indexOf(inverse.key);
1516
1564
  if (position >= 0) order.splice(position, 1);
1517
- order.splice(index(order, inverse.anchor), 0, inverse.key);
1565
+ order.splice(require_runtime.index(order, inverse.anchor), 0, inverse.key);
1518
1566
  }
1519
1567
  return order;
1520
1568
  };
@@ -1556,12 +1604,12 @@ const restoreEntityOrder = (order, inverses) => {
1556
1604
  continue;
1557
1605
  }
1558
1606
  if (inverse.kind === "restore") {
1559
- restore(order, inverse.ids, inverse.positions);
1607
+ require_runtime.restore(order, inverse.ids, inverse.positions);
1560
1608
  continue;
1561
1609
  }
1562
1610
  const position = order.indexOf(inverse.id);
1563
- if (position < 0 || !valid(order, inverse.anchor)) throw new Error(`Entity order inverse cannot restore '${inverse.id}'.`);
1564
- const nextIndex = afterRemove(order, position, inverse.anchor);
1611
+ if (position < 0 || !require_runtime.valid(order, inverse.anchor)) throw new Error(`Entity order inverse cannot restore '${inverse.id}'.`);
1612
+ const nextIndex = require_runtime.afterRemove(order, position, inverse.anchor);
1565
1613
  order.splice(position, 1);
1566
1614
  order.splice(nextIndex, 0, inverse.id);
1567
1615
  }
@@ -1618,7 +1666,7 @@ const createChangeJournal = (root) => {
1618
1666
  const candidates = buckets.get(addressHash) ?? [];
1619
1667
  for (const subject of candidates) {
1620
1668
  require_ownership.profile.journal.comparison();
1621
- if (subject.kind === kind && sameAddress$1(subject.address, address)) return subject;
1669
+ if (subject.kind === kind && sameAddress(subject.address, address)) return subject;
1622
1670
  }
1623
1671
  }
1624
1672
  const ownerOf = (address) => {
@@ -1661,11 +1709,11 @@ const createChangeJournal = (root) => {
1661
1709
  const recordVariant = (resolved, operation, inverse) => {
1662
1710
  if (find("variant", operation.at, resolved.addressHash)) return;
1663
1711
  const first = inverse[0];
1664
- if (first?.type !== "variant.replace") return inverseMismatch(operation);
1712
+ if (first?.type !== "variant.replace" && first?.type !== "value.clear") return inverseMismatch(operation);
1665
1713
  add({
1666
1714
  ...base(resolved, operation),
1667
1715
  kind: "variant",
1668
- before: absorb(first.value, operation.at)
1716
+ before: first.type === "value.clear" ? absent : present(absorb(first.value, operation.at))
1669
1717
  });
1670
1718
  };
1671
1719
  const recordDict = (resolved, operation, inverse) => {
@@ -1722,7 +1770,7 @@ const createChangeJournal = (root) => {
1722
1770
  });
1723
1771
  return;
1724
1772
  }
1725
- const value = require_dependency.read(root, operation.at);
1773
+ const value = require_runtime.read(root, operation.at);
1726
1774
  if (!Array.isArray(value)) return inverseMismatch(operation);
1727
1775
  subject = add({
1728
1776
  ...base(resolved, operation),
@@ -1760,7 +1808,7 @@ const createChangeJournal = (root) => {
1760
1808
  };
1761
1809
  const recordEntity = (resolved, operation, inverse) => {
1762
1810
  if (resolved.node.kind !== "table" && resolved.node.kind !== "map") return inverseMismatch(operation);
1763
- const resolution = resolveEntityCollection(resolved.node, require_dependency.read(root, operation.at));
1811
+ const resolution = resolveEntityCollection(resolved.node, require_runtime.read(root, operation.at));
1764
1812
  if (resolution.status === "invalid") return inverseMismatch(operation);
1765
1813
  const collection = resolution.collection;
1766
1814
  const subject = find("entity", operation.at, resolved.addressHash) ?? add({
@@ -1796,7 +1844,7 @@ const createChangeJournal = (root) => {
1796
1844
  return inverseMismatch(operation);
1797
1845
  };
1798
1846
  const recordTree = (resolved, operation, inverse) => {
1799
- const current = require_dependency.read(root, operation.at);
1847
+ const current = require_runtime.read(root, operation.at);
1800
1848
  if (inverse.length === 1 && inverse[0].type === "value.clear" && operation.type === "tree.replace") {
1801
1849
  const subject = find("tree", operation.at, resolved.addressHash);
1802
1850
  if (subject) subject.state = {
@@ -1829,6 +1877,10 @@ const createChangeJournal = (root) => {
1829
1877
  record: (resolved, operation, inverse) => {
1830
1878
  require_ownership.profile.batch.journalRecord();
1831
1879
  if (ownerOf(operation.at)) return;
1880
+ if (operation.type === "value.clear" && resolved.node.kind === "variant") {
1881
+ recordVariant(resolved, operation, inverse);
1882
+ return;
1883
+ }
1832
1884
  if (operation.type === "field.set" || operation.type === "field.clear" || operation.type === "value.clear") {
1833
1885
  recordValue(resolved, operation, inverse);
1834
1886
  return;
@@ -1870,7 +1922,7 @@ const createChangeJournal = (root) => {
1870
1922
  const changeAt = (address, addressHash) => {
1871
1923
  const candidates = collectionBuckets.get(addressHash) ?? [];
1872
1924
  collectionBuckets.set(addressHash, candidates);
1873
- let change = candidates.find((entry) => sameAddress$1(entry.address, address));
1925
+ let change = candidates.find((entry) => sameAddress(entry.address, address));
1874
1926
  if (!change) {
1875
1927
  change = {
1876
1928
  address,
@@ -1914,7 +1966,7 @@ const createChangeJournal = (root) => {
1914
1966
  const has = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1915
1967
  const stringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
1916
1968
  const positions = (value) => Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0);
1917
- const address$1 = (value) => stringArray(value);
1969
+ const address = (value) => stringArray(value);
1918
1970
  const anchor = (value) => {
1919
1971
  if (!require_ownership.isRecord(value)) return false;
1920
1972
  if (Number(has(value, "at")) + Number(has(value, "before")) + Number(has(value, "after")) !== 1) return false;
@@ -1935,7 +1987,7 @@ const rejected = (code, message) => ({
1935
1987
  });
1936
1988
  const decode = (input) => {
1937
1989
  if (!require_ownership.isRecord(input)) return rejected("invalid-operation", "Operation must be an object.");
1938
- if (!address$1(input.at)) return rejected("invalid-address", "Operation address is malformed.");
1990
+ if (!address(input.at)) return rejected("invalid-address", "Operation address is malformed.");
1939
1991
  if (typeof input.type !== "string") return rejected("invalid-operation", "Operation type must be a string.");
1940
1992
  switch (input.type) {
1941
1993
  case "field.set":
@@ -2026,7 +2078,7 @@ const createMutationSession = (root, schema, options = {}) => {
2026
2078
  const rollback = () => {
2027
2079
  if (state === "rolled-back") return;
2028
2080
  for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) for (const inverse of steps[stepIndex].inverse) {
2029
- const result = execute(root, inverse, require_dependency.resolveLocated(schema, root, inverse.at), true);
2081
+ const result = execute(root, inverse, require_runtime.resolveLocated(schema, root, inverse.at), true);
2030
2082
  if (result.status === "rejected") throw new Error(`Document rollback failed: ${result.issue.message}`);
2031
2083
  }
2032
2084
  state = "rolled-back";
@@ -2042,7 +2094,7 @@ const createMutationSession = (root, schema, options = {}) => {
2042
2094
  }
2043
2095
  const normalized = normalize(decoded.operation);
2044
2096
  require_ownership.profile.batch.entry(normalized.type === "entity.create" ? normalized.entries.length : normalized.type === "entity.remove" ? normalized.ids.length : 1);
2045
- const resolved = require_dependency.resolveLocated(schema, root, normalized.at);
2097
+ const resolved = require_runtime.resolveLocated(schema, root, normalized.at);
2046
2098
  if (resolved?.collection) require_ownership.profile.batch.collectionResolved();
2047
2099
  require_ownership.profile.mutation.executed();
2048
2100
  const result = execute(root, normalized, resolved, options.copyPayload === true || requiresPayloadCopy(normalized));
@@ -2111,352 +2163,6 @@ const mutateOperations = (root, schema, operations, options = {}) => {
2111
2163
  return session.finish();
2112
2164
  };
2113
2165
  //#endregion
2114
- //#region core/src/mutation/footprint.ts
2115
- const sameAddress = (left, right) => left.length === right.length && left.every((segment, index) => segment === right[index]);
2116
- const containsAddress = (parent, child) => parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
2117
- const address = (value) => Array.isArray(value) && value.every((segment) => typeof segment === "string") ? Object.freeze(value.slice()) : void 0;
2118
- const targetKey = (target) => {
2119
- const at = target.at.join("\0");
2120
- if (target.kind === "dictionary-key" || target.kind === "list-item") return `${target.kind}:${at}:${target.key}`;
2121
- if (target.kind === "entity" || target.kind === "tree-node") return `${target.kind}:${at}:${target.id}`;
2122
- return `${target.kind}:${at}`;
2123
- };
2124
- const freezeTarget = (target) => Object.freeze({
2125
- ...target,
2126
- at: Object.freeze(target.at.slice())
2127
- });
2128
- const add = (targets, target) => {
2129
- const stable = freezeTarget(target);
2130
- targets.set(targetKey(stable), stable);
2131
- };
2132
- const commandFootprint = (operations) => {
2133
- const targets = /* @__PURE__ */ new Map();
2134
- for (const operation of operations) switch (operation.type) {
2135
- case "field.set":
2136
- case "field.clear":
2137
- case "value.clear":
2138
- case "variant.replace":
2139
- add(targets, {
2140
- kind: "value",
2141
- at: operation.at
2142
- });
2143
- break;
2144
- case "dict.replace":
2145
- add(targets, {
2146
- kind: "dictionary",
2147
- at: operation.at
2148
- });
2149
- break;
2150
- case "dict.set":
2151
- case "dict.delete":
2152
- add(targets, {
2153
- kind: "dictionary-key",
2154
- at: operation.at,
2155
- key: operation.key
2156
- });
2157
- break;
2158
- case "entity.create":
2159
- add(targets, {
2160
- kind: "collection",
2161
- at: operation.at
2162
- });
2163
- for (const entry of operation.entries) add(targets, {
2164
- kind: "entity",
2165
- at: operation.at,
2166
- id: entry.id
2167
- });
2168
- break;
2169
- case "entity.remove":
2170
- add(targets, {
2171
- kind: "collection",
2172
- at: operation.at
2173
- });
2174
- for (const id of operation.ids) add(targets, {
2175
- kind: "entity",
2176
- at: operation.at,
2177
- id
2178
- });
2179
- break;
2180
- case "entity.move":
2181
- add(targets, {
2182
- kind: "collection",
2183
- at: operation.at
2184
- });
2185
- add(targets, {
2186
- kind: "entity",
2187
- at: operation.at,
2188
- id: operation.id
2189
- });
2190
- break;
2191
- case "list.insert":
2192
- case "list.move":
2193
- case "list.remove":
2194
- add(targets, {
2195
- kind: "list",
2196
- at: operation.at
2197
- });
2198
- add(targets, {
2199
- kind: "list-item",
2200
- at: operation.at,
2201
- key: operation.key
2202
- });
2203
- break;
2204
- case "list.replace":
2205
- add(targets, {
2206
- kind: "list",
2207
- at: operation.at
2208
- });
2209
- for (const key of operation.keys) add(targets, {
2210
- kind: "list-item",
2211
- at: operation.at,
2212
- key
2213
- });
2214
- break;
2215
- case "tree.insert":
2216
- case "tree.move":
2217
- case "tree.remove":
2218
- case "tree.set":
2219
- add(targets, {
2220
- kind: "tree",
2221
- at: operation.at
2222
- });
2223
- add(targets, {
2224
- kind: "tree-node",
2225
- at: operation.at,
2226
- id: operation.treeNodeId
2227
- });
2228
- break;
2229
- case "tree.replace":
2230
- add(targets, {
2231
- kind: "tree",
2232
- at: operation.at
2233
- });
2234
- break;
2235
- }
2236
- return Object.freeze(Array.from(targets.values()));
2237
- };
2238
- const overlapsValueAndEntity = (value, entity) => containsAddress([...entity.at, entity.id], value.at) || containsAddress(value.at, [...entity.at, entity.id]);
2239
- const targetsOverlap = (left, right) => {
2240
- if (left.kind === "value" && right.kind === "value") return containsAddress(left.at, right.at) || containsAddress(right.at, left.at);
2241
- if (left.kind === "value" && right.kind === "entity") return overlapsValueAndEntity(left, right);
2242
- if (left.kind === "entity" && right.kind === "value") return overlapsValueAndEntity(right, left);
2243
- if (left.kind === "dictionary" && right.kind === "dictionary") return sameAddress(left.at, right.at);
2244
- if (left.kind === "dictionary" && right.kind === "dictionary-key") return sameAddress(left.at, right.at);
2245
- if (left.kind === "dictionary-key" && right.kind === "dictionary") return sameAddress(left.at, right.at);
2246
- if (left.kind === "dictionary-key" && right.kind === "dictionary-key") return sameAddress(left.at, right.at) && left.key === right.key;
2247
- if (left.kind === "collection" && right.kind === "collection") return sameAddress(left.at, right.at);
2248
- if (left.kind === "collection" && right.kind === "entity") return sameAddress(left.at, right.at);
2249
- if (left.kind === "entity" && right.kind === "collection") return sameAddress(left.at, right.at);
2250
- if (left.kind === "entity" && right.kind === "entity") return sameAddress(left.at, right.at) && left.id === right.id;
2251
- if (left.kind === "list" && (right.kind === "list" || right.kind === "list-item")) return sameAddress(left.at, right.at);
2252
- if (right.kind === "list" && left.kind === "list-item") return sameAddress(left.at, right.at);
2253
- if (left.kind === "list-item" && right.kind === "list-item") return sameAddress(left.at, right.at) && left.key === right.key;
2254
- if (left.kind === "tree" && (right.kind === "tree" || right.kind === "tree-node")) return sameAddress(left.at, right.at);
2255
- if (right.kind === "tree" && left.kind === "tree-node") return sameAddress(left.at, right.at);
2256
- if (left.kind === "tree-node" && right.kind === "tree-node") return sameAddress(left.at, right.at) && left.id === right.id;
2257
- return false;
2258
- };
2259
- const footprintsOverlap = (left, right) => left.some((leftTarget) => right.some((rightTarget) => targetsOverlap(leftTarget, rightTarget)));
2260
- const decodedTarget = (value) => {
2261
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
2262
- const record = value;
2263
- const at = address(record.at);
2264
- if (!at || typeof record.kind !== "string") return void 0;
2265
- switch (record.kind) {
2266
- case "value":
2267
- case "dictionary":
2268
- case "collection":
2269
- case "list":
2270
- case "tree": return freezeTarget({
2271
- kind: record.kind,
2272
- at
2273
- });
2274
- case "dictionary-key":
2275
- case "list-item": return typeof record.key === "string" ? freezeTarget({
2276
- kind: record.kind,
2277
- at,
2278
- key: record.key
2279
- }) : void 0;
2280
- case "entity":
2281
- case "tree-node": return typeof record.id === "string" ? freezeTarget({
2282
- kind: record.kind,
2283
- at,
2284
- id: record.id
2285
- }) : void 0;
2286
- default: return;
2287
- }
2288
- };
2289
- const decodeCommandFootprint = (value) => {
2290
- if (!Array.isArray(value)) return void 0;
2291
- const targets = /* @__PURE__ */ new Map();
2292
- for (const entry of value) {
2293
- const target = decodedTarget(entry);
2294
- if (!target) return void 0;
2295
- targets.set(targetKey(target), target);
2296
- }
2297
- return Object.freeze(Array.from(targets.values()));
2298
- };
2299
- //#endregion
2300
- //#region core/src/runtime/notification.ts
2301
- const ROOT_BUCKET = Symbol("document-root");
2302
- const notifications = /* @__PURE__ */ new WeakMap();
2303
- const key = (value) => require_dependency.bucket(value) ?? ROOT_BUCKET;
2304
- const createNotification = (runtime) => {
2305
- const notification = {
2306
- root: /* @__PURE__ */ new Set(),
2307
- filtered: /* @__PURE__ */ new Set(),
2308
- buckets: /* @__PURE__ */ new Map(),
2309
- processors: [],
2310
- candidates: /* @__PURE__ */ new Set(),
2311
- rootSnapshot: [],
2312
- notifying: false
2313
- };
2314
- notifications.set(runtime, notification);
2315
- return notification;
2316
- };
2317
- const notificationOf = (runtime) => {
2318
- const value = notifications.get(runtime);
2319
- if (!value) throw new Error("Unknown Doxum runtime.");
2320
- return value;
2321
- };
2322
- const shareNotification = (source, target) => {
2323
- notifications.set(target, notificationOf(source));
2324
- };
2325
- const registerProcessor = (runtime, processor) => {
2326
- const notification = notificationOf(runtime);
2327
- const entry = {
2328
- processor,
2329
- active: true
2330
- };
2331
- notification.processors.push(entry);
2332
- return () => {
2333
- if (!entry.active) return;
2334
- entry.active = false;
2335
- if (!notification.notifying) {
2336
- const index = notification.processors.indexOf(entry);
2337
- if (index >= 0) notification.processors.splice(index, 1);
2338
- }
2339
- };
2340
- };
2341
- const subscribeRoot = (notification, listener) => {
2342
- const entry = {
2343
- listener,
2344
- active: true
2345
- };
2346
- notification.root.add(entry);
2347
- return () => {
2348
- entry.active = false;
2349
- if (!notification.notifying) notification.root.delete(entry);
2350
- };
2351
- };
2352
- const subscribeTargets = (notification, targets, listener) => {
2353
- const entry = {
2354
- targets: Object.freeze(targets.slice()),
2355
- listener,
2356
- active: true
2357
- };
2358
- notification.filtered.add(entry);
2359
- const keys = new Set(targets.map(key));
2360
- keys.forEach((key) => {
2361
- const bucket = notification.buckets.get(key) ?? /* @__PURE__ */ new Set();
2362
- bucket.add(entry);
2363
- notification.buckets.set(key, bucket);
2364
- });
2365
- return () => {
2366
- if (!entry.active) return;
2367
- entry.active = false;
2368
- if (notification.notifying) return;
2369
- notification.filtered.delete(entry);
2370
- keys.forEach((key) => {
2371
- const bucket = notification.buckets.get(key);
2372
- bucket?.delete(entry);
2373
- if (bucket?.size === 0) notification.buckets.delete(key);
2374
- });
2375
- };
2376
- };
2377
- const notify = (notification, commit) => {
2378
- notification.notifying = true;
2379
- const errors = [];
2380
- const call = (listener) => {
2381
- try {
2382
- listener(commit);
2383
- } catch (error) {
2384
- errors.push(Object.freeze({
2385
- phase: "listener",
2386
- error
2387
- }));
2388
- }
2389
- };
2390
- try {
2391
- const processorCount = notification.processors.length;
2392
- for (let index = 0; index < processorCount; index += 1) {
2393
- const entry = notification.processors[index];
2394
- if (!entry?.active) continue;
2395
- try {
2396
- entry.processor.process(commit);
2397
- } catch (error) {
2398
- errors.push(Object.freeze({
2399
- phase: "processor",
2400
- error
2401
- }));
2402
- }
2403
- }
2404
- for (let index = 0; index < processorCount; index += 1) {
2405
- const entry = notification.processors[index];
2406
- if (!entry?.active) continue;
2407
- try {
2408
- entry.processor.flush();
2409
- } catch (error) {
2410
- errors.push(Object.freeze({
2411
- phase: "flush",
2412
- error
2413
- }));
2414
- }
2415
- }
2416
- const candidates = notification.candidates;
2417
- candidates.clear();
2418
- if (commit.impact.kind === "reset") notification.filtered.forEach((entry) => candidates.add(entry));
2419
- else for (const operation of commit.operations) {
2420
- const key = operation.at[0] ?? ROOT_BUCKET;
2421
- notification.buckets.get(key)?.forEach((entry) => candidates.add(entry));
2422
- notification.buckets.get(ROOT_BUCKET)?.forEach((entry) => candidates.add(entry));
2423
- }
2424
- candidates.forEach((entry) => {
2425
- if (entry.active && entry.targets.some(commit.impact.affects)) call(entry.listener);
2426
- });
2427
- const rootSnapshot = notification.rootSnapshot;
2428
- rootSnapshot.length = 0;
2429
- notification.root.forEach((entry) => rootSnapshot.push(entry));
2430
- for (const entry of rootSnapshot) if (entry.active) call(entry.listener);
2431
- } finally {
2432
- notification.notifying = false;
2433
- let writeIndex = 0;
2434
- for (const entry of notification.processors) if (entry.active) notification.processors[writeIndex++] = entry;
2435
- notification.processors.length = writeIndex;
2436
- notification.root.forEach((entry) => {
2437
- if (!entry.active) notification.root.delete(entry);
2438
- });
2439
- notification.filtered.forEach((entry) => {
2440
- if (!entry.active) notification.filtered.delete(entry);
2441
- });
2442
- notification.buckets.forEach((bucket, key) => {
2443
- bucket.forEach((entry) => {
2444
- if (!entry.active) bucket.delete(entry);
2445
- });
2446
- if (bucket.size === 0) notification.buckets.delete(key);
2447
- });
2448
- }
2449
- return Object.freeze(errors);
2450
- };
2451
- const disposeNotification = (notification) => {
2452
- notification.root.clear();
2453
- notification.filtered.clear();
2454
- notification.buckets.clear();
2455
- notification.processors.length = 0;
2456
- notification.candidates.clear();
2457
- notification.rootSnapshot.length = 0;
2458
- };
2459
- //#endregion
2460
2166
  //#region core/src/runtime.ts
2461
2167
  var RejectedUpdate = class extends Error {
2462
2168
  issues;
@@ -2468,6 +2174,7 @@ var RejectedUpdate = class extends Error {
2468
2174
  const EMPTY = Object.freeze([]);
2469
2175
  const publishDiagnostic = (value) => Object.freeze({
2470
2176
  ...value,
2177
+ source: "application",
2471
2178
  ...value.address === void 0 ? {} : { address: Object.freeze(value.address.slice()) }
2472
2179
  });
2473
2180
  const createDocument = (input) => {
@@ -2484,15 +2191,16 @@ const createDocument = (input) => {
2484
2191
  let runtime;
2485
2192
  let notification;
2486
2193
  const assertWritable = (intent) => {
2487
- if (state.disposed) throw new require_dependency.DocumentDisposedError();
2488
- if (busy) throw new require_dependency.DocumentReentrancyError();
2194
+ if (state.disposed) throw new require_runtime.DocumentDisposedError();
2195
+ if (busy) throw new require_runtime.DocumentReentrancyError();
2196
+ if (require_runtime.accessOf(runtime).projectionLocks) throw new require_runtime.DocumentReentrancyError();
2489
2197
  require_driver.assertRuntimeWritable(runtime, intent);
2490
2198
  };
2491
2199
  const runTransaction = (session, run) => {
2492
2200
  let reports;
2493
2201
  let active = true;
2494
2202
  const transaction = {
2495
- read: require_dependency.documentReader(input.schema, () => state.document, () => active),
2203
+ read: require_runtime.documentReader(input.schema, () => state.document, () => active),
2496
2204
  write: documentWriter(input.schema, (operation) => {
2497
2205
  if (!active) throw new Error("Document writer is no longer active.");
2498
2206
  const rejected = session.apply(operation);
@@ -2527,7 +2235,19 @@ const createDocument = (input) => {
2527
2235
  const history = createHistory({
2528
2236
  capacity: input.history === false ? 0 : Math.max(0, input.history?.capacity ?? 100),
2529
2237
  revision: () => revision,
2530
- apply: (operations) => applyBatch(operations, "history", false)
2238
+ apply: (operations) => applyBatch(operations, "history", false),
2239
+ assertIdle: () => {
2240
+ if (state.disposed) throw new require_runtime.DocumentDisposedError();
2241
+ if (busy || require_runtime.accessOf(runtime).projectionLocks) throw new require_runtime.DocumentReentrancyError();
2242
+ },
2243
+ notify: (run) => {
2244
+ busy = true;
2245
+ try {
2246
+ return run();
2247
+ } finally {
2248
+ busy = false;
2249
+ }
2250
+ }
2531
2251
  });
2532
2252
  const publish = (batch, source, kind, recordHistory) => {
2533
2253
  revision += 1;
@@ -2541,10 +2261,12 @@ const createDocument = (input) => {
2541
2261
  });
2542
2262
  if (kind === "replace" || source === "remote") history.invalidate();
2543
2263
  else if (recordHistory && (source === "local" || source === "system")) history.record(batch.operations, batch.inverse);
2264
+ else if (source === "history") history.publish();
2265
+ else history.endGroup();
2544
2266
  busy = true;
2545
2267
  let observerErrors;
2546
2268
  try {
2547
- observerErrors = notify(notification, commit);
2269
+ observerErrors = require_runtime.notify(notification, commit, history.flush);
2548
2270
  } finally {
2549
2271
  busy = false;
2550
2272
  }
@@ -2574,11 +2296,11 @@ const createDocument = (input) => {
2574
2296
  runtime = {
2575
2297
  schema: input.schema,
2576
2298
  address: {
2577
- resolve: (address) => require_dependency.resolveAddress(input.schema, address, state.document),
2578
- read: (address) => require_dependency.read(state.document, address),
2579
- contains: require_dependency.contains,
2580
- overlaps: require_dependency.overlaps,
2581
- debugKey: require_dependency.debugKey
2299
+ resolve: (address) => require_runtime.resolveAddress(input.schema, address, state.document),
2300
+ read: (address) => require_runtime.read(state.document, address),
2301
+ contains: require_runtime.contains,
2302
+ overlaps: require_runtime.overlaps,
2303
+ debugKey: require_runtime.debugKey
2582
2304
  },
2583
2305
  revision: () => revision,
2584
2306
  update: (run, options) => {
@@ -2659,7 +2381,7 @@ const createDocument = (input) => {
2659
2381
  operations: batch.operations,
2660
2382
  inverse: batch.inverse,
2661
2383
  impact,
2662
- footprint: commandFootprint(batch.operations),
2384
+ footprint: require_runtime.commandFootprint(batch.operations),
2663
2385
  reports
2664
2386
  };
2665
2387
  } catch (error) {
@@ -2687,7 +2409,7 @@ const createDocument = (input) => {
2687
2409
  const invalidTree = invalidDocument(input.schema, document);
2688
2410
  if (invalidTree) return {
2689
2411
  status: "rejected",
2690
- issues: [at$1(invalidTree, "invalid-tree", "Document replacement contains an invalid tree.")],
2412
+ issues: [at(invalidTree, "invalid-tree", "Document replacement contains an invalid tree.")],
2691
2413
  revision
2692
2414
  };
2693
2415
  if (require_ownership.deepEqual(state.document, document)) return {
@@ -2704,28 +2426,33 @@ const createDocument = (input) => {
2704
2426
  }, source, "replace", false);
2705
2427
  },
2706
2428
  snapshot: () => {
2707
- if (state.disposed) throw new require_dependency.DocumentDisposedError();
2429
+ if (state.disposed) throw new require_runtime.DocumentDisposedError();
2708
2430
  return require_ownership.cloneValue(state.document, "snapshot");
2709
2431
  },
2710
2432
  subscribe: ((targetOrListener, listener) => {
2711
- if (state.disposed) throw new require_dependency.DocumentDisposedError();
2712
- if (typeof targetOrListener === "function") return subscribeRoot(notification, targetOrListener);
2433
+ if (state.disposed) throw new require_runtime.DocumentDisposedError();
2434
+ if (typeof targetOrListener === "function") return require_runtime.subscribeRoot(notification, targetOrListener);
2713
2435
  const targets = Array.isArray(targetOrListener) ? targetOrListener : [targetOrListener];
2714
2436
  if (targets.length === 0 || !listener) throw new TypeError("Filtered subscribe requires at least one target and a listener.");
2715
- return subscribeTargets(notification, targets, listener);
2437
+ return require_runtime.subscribeTargets(notification, targets, listener);
2716
2438
  }),
2717
2439
  history: history.api,
2718
2440
  dispose: () => {
2719
2441
  if (state.disposed) return;
2442
+ if (busy || require_runtime.accessOf(runtime).projectionLocks) throw new require_runtime.DocumentReentrancyError();
2720
2443
  state.disposed = true;
2721
- disposeNotification(notification);
2722
- history.api.clear();
2723
- require_driver.disposeRuntimeDriver(runtime);
2444
+ try {
2445
+ require_runtime.disposeNotification(notification);
2446
+ } finally {
2447
+ history.dispose();
2448
+ require_driver.disposeRuntimeDriver(runtime);
2449
+ }
2724
2450
  }
2725
2451
  };
2726
- require_dependency.bindRuntimeAccess(runtime, state);
2452
+ require_runtime.bindRuntimeAccess(runtime, state);
2727
2453
  require_driver.bindRuntimeDriver(runtime);
2728
- notification = createNotification(runtime);
2454
+ notification = require_runtime.createNotification(runtime);
2455
+ require_runtime.bindDocumentReadable(history.api, runtime);
2729
2456
  return runtime;
2730
2457
  };
2731
2458
  //#endregion
@@ -2736,427 +2463,37 @@ const asReadable = (runtime) => {
2736
2463
  revision: runtime.revision,
2737
2464
  subscribe: runtime.subscribe
2738
2465
  };
2739
- require_dependency.bindRuntimeAccess(readable, require_dependency.accessOf(runtime));
2740
- shareNotification(runtime, readable);
2466
+ require_runtime.bindRuntimeAccess(readable, require_runtime.accessOf(runtime));
2467
+ require_runtime.shareNotification(runtime, readable);
2741
2468
  return Object.freeze(readable);
2742
2469
  };
2743
2470
  //#endregion
2744
2471
  //#region core/src/projection/select.ts
2745
- const select = (runtime, selector) => require_dependency.readWith(runtime, selector);
2746
- //#endregion
2747
- //#region core/src/projection/collection-view.ts
2748
- const createCollectionView = (input) => {
2749
- if (require_dependency.schemaOf(input.runtime) !== input.source.schema) throw new Error("Collection selector belongs to another schema.");
2750
- const idsListeners = /* @__PURE__ */ new Set();
2751
- const allListeners = /* @__PURE__ */ new Set();
2752
- const itemListeners = /* @__PURE__ */ new Map();
2753
- const itemReadables = /* @__PURE__ */ new Map();
2754
- let disposed = false;
2755
- const sourceNode = require_dependency.nodeAt(input.source.schema, input.source.address);
2756
- const entryNode = sourceNode?.kind === "table" || sourceNode?.kind === "map" ? sourceNode.value : {
2757
- kind: "object",
2758
- shape: {}
2759
- };
2760
- const mapEntry = (document, id) => {
2761
- require_ownership.profile.collectionView.mapped();
2762
- const address = [...input.source.address, String(id)];
2763
- let active = true;
2764
- try {
2765
- return input.map(id, require_dependency.readerFor(entryNode, {
2766
- root: () => document,
2767
- active: () => active
2768
- }, address));
2769
- } finally {
2770
- active = false;
2771
- }
2772
- };
2773
- const build = (previous, changed) => {
2774
- const document = require_dependency.documentOf(input.runtime);
2775
- const collection = require_dependency.read(document, input.source.address);
2776
- const ids = collection && typeof collection === "object" && "ids" in collection && Array.isArray(collection.ids) ? [...collection.ids] : Object.keys(collection ?? {});
2777
- require_ownership.profile.collectionView.idsScanned(ids.length);
2778
- const values = previous && changed ? previous.values : /* @__PURE__ */ new Map();
2779
- if (previous && changed) {
2780
- const nextIds = new Set(ids);
2781
- for (const id of previous.ids) if (!nextIds.has(id)) values.delete(id);
2782
- }
2783
- for (const id of ids) {
2784
- if (previous && changed && previous.values.has(id) && !changed.has(id)) continue;
2785
- const next = mapEntry(document, id);
2786
- if (previous && changed && previous.values.has(id)) {
2787
- const before = previous.values.get(id);
2788
- if (input.isEqual ? input.isEqual(before, next) : Object.is(before, next)) {
2789
- changed.delete(id);
2790
- continue;
2791
- }
2792
- }
2793
- values.set(id, next);
2794
- }
2795
- return {
2796
- ids: Object.freeze(ids),
2797
- values,
2798
- all: void 0,
2799
- allDirty: true
2800
- };
2801
- };
2802
- let state = build();
2803
- const ensureAll = () => {
2804
- if (!state.allDirty && state.all) return state.all;
2805
- require_ownership.profile.collectionView.arrayCopied();
2806
- const all = new Array(state.ids.length);
2807
- for (let index = 0; index < state.ids.length; index += 1) all[index] = state.values.get(state.ids[index]);
2808
- const stable = Object.freeze(all);
2809
- state.all = stable;
2810
- state.allDirty = false;
2811
- return stable;
2812
- };
2813
- const emit = (listeners) => {
2814
- Array.from(listeners).forEach((listener) => listener());
2815
- };
2816
- const unsubscribe = input.runtime.subscribe(input.source, (commit) => {
2817
- if (disposed) return;
2818
- const impact = commit.impact.collection(input.source);
2819
- if (impact.kind === "incremental" && impact.added.size === 0 && impact.removed.size === 0 && !impact.orderChanged) {
2820
- const previous = state;
2821
- const changes = [];
2822
- const document = require_dependency.documentOf(input.runtime);
2823
- impact.updated.forEach((id) => {
2824
- if (!previous.values.has(id)) return;
2825
- const before = previous.values.get(id);
2826
- const after = mapEntry(document, id);
2827
- if (input.isEqual ? input.isEqual(before, after) : Object.is(before, after)) return;
2828
- changes.push([id, after]);
2829
- });
2830
- if (changes.length === 0) return;
2831
- for (const [id, value] of changes) previous.values.set(id, value);
2832
- previous.all = void 0;
2833
- previous.allDirty = true;
2834
- emit(allListeners);
2835
- for (const [id] of changes) {
2836
- const listeners = itemListeners.get(id);
2837
- if (listeners) emit(listeners);
2838
- }
2839
- return;
2840
- }
2841
- const changed = impact.kind === "reset" ? void 0 : new Set([...impact.added, ...impact.updated]);
2842
- const previous = state;
2843
- const next = build(previous, changed);
2844
- const changedIds = /* @__PURE__ */ new Set();
2845
- if (changed) {
2846
- changed.forEach((id) => changedIds.add(id));
2847
- previous.ids.forEach((id) => {
2848
- if (!next.values.has(id)) changedIds.add(id);
2849
- });
2850
- next.ids.forEach((id) => {
2851
- if (!previous.values.has(id)) changedIds.add(id);
2852
- });
2853
- } else {
2854
- previous.ids.forEach((id) => {
2855
- if (!next.values.has(id)) changedIds.add(id);
2856
- else if (!(input.isEqual ? input.isEqual(previous.values.get(id), next.values.get(id)) : Object.is(previous.values.get(id), next.values.get(id)))) changedIds.add(id);
2857
- });
2858
- next.ids.forEach((id) => {
2859
- if (!previous.values.has(id)) changedIds.add(id);
2860
- });
2861
- }
2862
- const idsChanged = impact.kind === "incremental" ? impact.added.size > 0 || impact.removed.size > 0 || impact.orderChanged : previous.ids.length !== next.ids.length || previous.ids.some((id, index) => next.ids[index] !== id);
2863
- state = idsChanged || changedIds.size ? next : previous;
2864
- if (idsChanged) emit(idsListeners);
2865
- if (idsChanged || changedIds.size) emit(allListeners);
2866
- changedIds.forEach((id) => {
2867
- const listeners = itemListeners.get(id);
2868
- if (listeners) emit(listeners);
2869
- });
2870
- });
2871
- return {
2872
- ids: {
2873
- current: () => state.ids,
2874
- revision: () => input.runtime.revision(),
2875
- subscribe: (listener) => {
2876
- if (disposed) return () => void 0;
2877
- idsListeners.add(listener);
2878
- return () => idsListeners.delete(listener);
2879
- }
2880
- },
2881
- all: {
2882
- current: ensureAll,
2883
- revision: () => input.runtime.revision(),
2884
- subscribe: (listener) => {
2885
- if (disposed) return () => void 0;
2886
- allListeners.add(listener);
2887
- return () => allListeners.delete(listener);
2888
- }
2889
- },
2890
- item: (id) => {
2891
- const cached = itemReadables.get(id);
2892
- if (cached) return cached;
2893
- const readable = {
2894
- current: () => state.values.get(id),
2895
- revision: () => input.runtime.revision(),
2896
- subscribe: (listener) => {
2897
- if (disposed) return () => void 0;
2898
- const listeners = itemListeners.get(id) ?? /* @__PURE__ */ new Set();
2899
- itemListeners.set(id, listeners);
2900
- listeners.add(listener);
2901
- return () => {
2902
- listeners.delete(listener);
2903
- if (listeners.size === 0) itemListeners.delete(id);
2904
- };
2905
- }
2906
- };
2907
- itemReadables.set(id, readable);
2908
- return readable;
2909
- },
2910
- revision: () => input.runtime.revision(),
2911
- dispose: () => {
2912
- if (disposed) return;
2913
- disposed = true;
2914
- unsubscribe();
2915
- idsListeners.clear();
2916
- allListeners.clear();
2917
- itemListeners.clear();
2918
- itemReadables.clear();
2919
- state.values.clear();
2920
- state = {
2921
- ids: Object.freeze([]),
2922
- values: /* @__PURE__ */ new Map(),
2923
- all: Object.freeze([]),
2924
- allDirty: false
2925
- };
2926
- }
2927
- };
2928
- };
2929
- //#endregion
2930
- //#region core/src/projection/materialized-view.ts
2931
- const materializedMeta = /* @__PURE__ */ new WeakMap();
2932
- const createMaterializedView = (runtime, spec) => {
2933
- const listeners = /* @__PURE__ */ new Set();
2934
- let disposed = false;
2935
- let fault;
2936
- let revision = runtime.revision();
2937
- let instance;
2938
- let currentValue;
2939
- let pendingEmit = false;
2940
- const dependencies = require_dependency.createDependencyTracker();
2941
- const sources = spec.sources ?? {};
2942
- const sourceEntries = Object.keys(sources).map((key) => [key, sources[key]]);
2943
- sourceEntries.forEach(([, source]) => {
2944
- const meta = materializedMeta.get(source);
2945
- if (!meta || meta.runtime !== runtime) throw new Error("Materialized sources must belong to the same runtime and be created first.");
2946
- });
2947
- const sourceValuesRecord = {};
2948
- let sourceChanged = false;
2949
- for (const [key] of sourceEntries) sourceValuesRecord[key] = {
2950
- value: void 0,
2951
- change: void 0
2952
- };
2953
- const sourceValues = (commitRevision) => {
2954
- sourceChanged = false;
2955
- for (const [key, source] of sourceEntries) {
2956
- const meta = materializedMeta.get(source);
2957
- const entry = sourceValuesRecord[key];
2958
- entry.value = meta?.value ?? source.current();
2959
- entry.change = meta?.change;
2960
- if (commitRevision !== void 0 && meta?.changedRevision === commitRevision) {
2961
- sourceChanged = true;
2962
- require_ownership.profile.materialized.sourceChanged();
2963
- }
2964
- }
2965
- return sourceValuesRecord;
2966
- };
2967
- let activeImpact;
2968
- const assertImpact = () => {
2969
- if (!activeImpact) throw new Error("Materialized impact is no longer active.");
2970
- return activeImpact;
2971
- };
2972
- const trackedImpact = {
2973
- get kind() {
2974
- return assertImpact().kind;
2975
- },
2976
- affects: (target) => {
2977
- dependencies.record(target);
2978
- return assertImpact().affects(target);
2979
- },
2980
- collection: (selector) => {
2981
- dependencies.record(selector);
2982
- return assertImpact().collection(selector);
2983
- },
2984
- get operations() {
2985
- dependencies.record({
2986
- kind: "value",
2987
- at: []
2988
- });
2989
- return assertImpact().operations;
2990
- }
2991
- };
2992
- let view;
2993
- const writeMeta = (change, value, nextRevision, changed) => {
2994
- const meta = materializedMeta.get(view);
2995
- if (meta) {
2996
- meta.value = value;
2997
- meta.change = change;
2998
- meta.revision = nextRevision;
2999
- if (changed) meta.changedRevision = nextRevision;
3000
- } else materializedMeta.set(view, {
3001
- runtime,
3002
- value,
3003
- change,
3004
- revision: nextRevision,
3005
- changedRevision: changed ? nextRevision : -1
3006
- });
3007
- };
3008
- const build = () => {
3009
- require_ownership.profile.materialized.rebuilt();
3010
- dependencies.clear();
3011
- let active = true;
3012
- try {
3013
- instance = spec.build({
3014
- read: require_dependency.documentReader(require_dependency.schemaOf(runtime), () => require_dependency.documentOf(runtime), () => active),
3015
- sources: sourceValues()
3016
- });
3017
- currentValue = instance.value;
3018
- fault = void 0;
3019
- revision = runtime.revision();
3020
- } finally {
3021
- active = false;
3022
- }
3023
- };
3024
- build();
3025
- const emit = () => {
3026
- require_ownership.profile.materialized.notification();
3027
- Array.from(listeners).forEach((listener) => listener());
3028
- };
3029
- const process = (commit) => {
3030
- if (disposed) return;
3031
- try {
3032
- if (commit.kind === "replace" || fault) {
3033
- build();
3034
- writeMeta(void 0, currentValue, commit.revision, true);
3035
- pendingEmit = true;
3036
- return;
3037
- }
3038
- const sourceSnapshot = sourceValues(commit.revision);
3039
- if (!sourceChanged && dependencies.size() > 0 && !dependencies.some(commit.impact.affects)) {
3040
- require_ownership.profile.materialized.skipped();
3041
- revision = commit.revision;
3042
- writeMeta(void 0, currentValue, commit.revision, false);
3043
- return;
3044
- }
3045
- dependencies.clear();
3046
- require_ownership.profile.materialized.updated();
3047
- let active = true;
3048
- let result;
3049
- try {
3050
- activeImpact = commit.impact;
3051
- result = instance.update({
3052
- read: require_dependency.documentReader(require_dependency.schemaOf(runtime), () => require_dependency.documentOf(runtime), () => active),
3053
- revision: commit.revision,
3054
- source: commit.source,
3055
- impact: trackedImpact,
3056
- sources: sourceSnapshot
3057
- });
3058
- } finally {
3059
- activeImpact = void 0;
3060
- active = false;
3061
- }
3062
- if (result.kind === "rebuild") {
3063
- build();
3064
- writeMeta(void 0, currentValue, commit.revision, true);
3065
- pendingEmit = true;
3066
- } else if (result.kind === "changed") {
3067
- currentValue = result.value;
3068
- revision = commit.revision;
3069
- writeMeta(result.change, currentValue, commit.revision, true);
3070
- pendingEmit = true;
3071
- } else {
3072
- revision = commit.revision;
3073
- writeMeta(void 0, currentValue, commit.revision, false);
3074
- }
3075
- } catch (error) {
3076
- try {
3077
- build();
3078
- writeMeta(void 0, currentValue, commit.revision, true);
3079
- pendingEmit = true;
3080
- } catch (rebuildError) {
3081
- fault = rebuildError;
3082
- throw rebuildError;
3083
- }
3084
- throw error;
3085
- }
3086
- };
3087
- view = {
3088
- current: () => {
3089
- if (fault) throw fault;
3090
- return currentValue;
3091
- },
3092
- revision: () => revision,
3093
- subscribe: (listener) => {
3094
- listeners.add(listener);
3095
- return () => listeners.delete(listener);
3096
- },
3097
- rebuild: () => {
3098
- build();
3099
- writeMeta(void 0, currentValue, runtime.revision(), true);
3100
- emit();
3101
- },
3102
- dispose: () => {
3103
- if (disposed) return;
3104
- disposed = true;
3105
- unregister();
3106
- listeners.clear();
3107
- materializedMeta.delete(view);
3108
- }
3109
- };
3110
- materializedMeta.set(view, {
3111
- runtime,
3112
- value: currentValue,
3113
- change: void 0,
3114
- revision,
3115
- changedRevision: -1
3116
- });
3117
- const unregister = registerProcessor(runtime, {
3118
- process,
3119
- flush: () => {
3120
- if (!pendingEmit) return;
3121
- pendingEmit = false;
3122
- emit();
3123
- }
3124
- });
3125
- return view;
3126
- };
2472
+ const select = (runtime, selector) => require_runtime.readWith(runtime, selector);
3127
2473
  //#endregion
3128
- exports.DocumentDisposedError = require_dependency.DocumentDisposedError;
3129
- exports.DocumentReentrancyError = require_dependency.DocumentReentrancyError;
2474
+ exports.DocumentDisposedError = require_runtime.DocumentDisposedError;
2475
+ exports.DocumentReentrancyError = require_runtime.DocumentReentrancyError;
2476
+ exports.ProjectionDisposedError = require_runtime.ProjectionDisposedError;
2477
+ exports.ProjectionError = require_runtime.ProjectionError;
3130
2478
  exports.asReadable = asReadable;
3131
- exports.commandFootprint = commandFootprint;
3132
- exports.contains = require_dependency.contains;
3133
- exports.createCollectionView = createCollectionView;
3134
2479
  exports.createDocument = createDocument;
3135
- exports.createMaterializedView = createMaterializedView;
3136
- exports.debugKey = require_dependency.debugKey;
3137
- exports.decodeCommandFootprint = decodeCommandFootprint;
3138
- exports.dict = require_dependency.dict;
3139
- exports.field = require_dependency.field;
3140
- exports.footprintsOverlap = footprintsOverlap;
3141
- exports.list = require_dependency.list;
3142
- exports.map = require_dependency.map;
3143
- exports.object = require_dependency.object;
3144
- exports.optional = require_dependency.optional;
3145
- exports.overlaps = require_dependency.overlaps;
3146
- exports.readAddress = require_dependency.read;
3147
- exports.record = require_dependency.record;
3148
- exports.resolveAddress = require_dependency.resolveAddress;
3149
- exports.schema = require_dependency.schema;
2480
+ exports.createProjectionRuntime = require_runtime.createProjectionRuntime;
2481
+ exports.dict = require_runtime.dict;
2482
+ exports.field = require_runtime.field;
2483
+ exports.list = require_runtime.list;
2484
+ exports.map = require_runtime.map;
2485
+ exports.object = require_runtime.object;
2486
+ exports.optional = require_runtime.optional;
2487
+ exports.schema = require_runtime.schema;
3150
2488
  exports.select = select;
3151
- exports.single = require_dependency.single;
3152
- exports.table = require_dependency.table;
2489
+ exports.table = require_runtime.table;
3153
2490
  Object.defineProperty(exports, "target", {
3154
2491
  enumerable: true,
3155
2492
  get: function() {
3156
- return require_dependency.impact_target_exports;
2493
+ return require_runtime.impact_target_exports;
3157
2494
  }
3158
2495
  });
3159
- exports.tree = require_dependency.tree;
3160
- exports.variant = require_dependency.variant;
2496
+ exports.tree = require_runtime.tree;
2497
+ exports.variant = require_runtime.variant;
3161
2498
 
3162
2499
  //# sourceMappingURL=index.cjs.map