doxum 0.1.4 → 0.1.6

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 (48) hide show
  1. package/README.md +46 -5
  2. package/dist/{contract-BsY1XLiG.d.ts → contract-I99DUMbN.d.ts} +53 -40
  3. package/dist/{contract-D8kjdfqT.d.cts → contract-uYJTmT8o.d.cts} +53 -40
  4. package/dist/index.cjs +244 -630
  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 +182 -569
  9. package/dist/index.js.map +1 -1
  10. package/dist/integration-Bs3pOk65.cjs +35 -0
  11. package/dist/integration-Bs3pOk65.cjs.map +1 -0
  12. package/dist/integration-xupBIKjU.js +30 -0
  13. package/dist/integration-xupBIKjU.js.map +1 -0
  14. package/dist/integration.cjs +4 -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 +1 -1
  19. package/dist/local-sync.d.cts +1 -1
  20. package/dist/local-sync.d.ts +1 -1
  21. package/dist/local-sync.js +1 -1
  22. package/dist/{ownership-CU-9DKJQ.cjs → ownership-CY0nPXGF.cjs} +4 -1
  23. package/dist/ownership-CY0nPXGF.cjs.map +1 -0
  24. package/dist/{ownership-B-VAPcce.js → ownership-CduRygE7.js} +4 -1
  25. package/dist/ownership-CduRygE7.js.map +1 -0
  26. package/dist/react.cjs +1 -1
  27. package/dist/react.js +1 -1
  28. package/dist/runtime-BJRXF0gq.js +1735 -0
  29. package/dist/runtime-BJRXF0gq.js.map +1 -0
  30. package/dist/runtime-CRijXjYp.cjs +1991 -0
  31. package/dist/runtime-CRijXjYp.cjs.map +1 -0
  32. package/dist/runtime-CUcvhFNP.d.ts +152 -0
  33. package/dist/runtime-C_vKHmhy.d.cts +152 -0
  34. package/package.json +1 -1
  35. package/skills/doxum-runtime/references/guide.en.md +35 -18
  36. package/skills/doxum-runtime/references/guide.zh-CN.md +26 -14
  37. package/skills/doxum-runtime/references/invariants.en.md +12 -9
  38. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  39. package/skills/doxum-runtime/references/patterns.en.md +36 -56
  40. package/skills/doxum-runtime/references/patterns.zh-CN.md +27 -45
  41. package/dist/dependency-C5_R1tvQ.js +0 -496
  42. package/dist/dependency-C5_R1tvQ.js.map +0 -1
  43. package/dist/dependency-Dfzs3khk.cjs +0 -722
  44. package/dist/dependency-Dfzs3khk.cjs.map +0 -1
  45. package/dist/integration.cjs.map +0 -1
  46. package/dist/integration.js.map +0 -1
  47. package/dist/ownership-B-VAPcce.js.map +0 -1
  48. package/dist/ownership-CU-9DKJQ.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { A as optional, C as resolveLocated, D as list, E as field, F as tree, I as variant, M as schema, N as single, O as map, P as table, S as resolveAddress, T as dict, _ as debugKey, a as readWith, b as overlaps, c as DocumentReentrancyError, d as address$2, f as belongs, g as contains, h as impact_target_exports, i as documentOf, j as record, k as object, l as documentReader, m as id, n as accessOf, o as schemaOf, p as bucket, r as bindRuntimeAccess, s as DocumentDisposedError, t as createDependencyTracker, u as readerFor, v as locate, w as set$1, x as read, y as nodeAt$1 } from "./dependency-C5_R1tvQ.js";
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-B-VAPcce.js";
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";
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
5
5
  const emptyCollectionImpact = Object.freeze({
@@ -195,7 +195,11 @@ const writerFor = (node, inputAddress, sink) => {
195
195
  type: "dict.replace",
196
196
  at: address,
197
197
  value
198
- })
198
+ }),
199
+ ...node.optional ? { clear: () => emit(sink, {
200
+ type: "value.clear",
201
+ at: address
202
+ }) } : {}
199
203
  };
200
204
  if (node.kind === "list") return {
201
205
  insert: (value, anchor) => emit(sink, {
@@ -221,7 +225,11 @@ const writerFor = (node, inputAddress, sink) => {
221
225
  at: address,
222
226
  value,
223
227
  keys: value.map(node.keyOf)
224
- })
228
+ }),
229
+ ...node.optional ? { clear: () => emit(sink, {
230
+ type: "value.clear",
231
+ at: address
232
+ }) } : {}
225
233
  };
226
234
  if (node.kind === "tree") return {
227
235
  insert: (treeNodeId, value, parentId, index) => emit(sink, {
@@ -254,7 +262,11 @@ const writerFor = (node, inputAddress, sink) => {
254
262
  type: "tree.replace",
255
263
  at: address,
256
264
  value
257
- })
265
+ }),
266
+ ...node.optional ? { clear: () => emit(sink, {
267
+ type: "value.clear",
268
+ at: address
269
+ }) } : {}
258
270
  };
259
271
  if (node.kind === "table") {
260
272
  let items;
@@ -318,11 +330,17 @@ const writerFor = (node, inputAddress, sink) => {
318
330
  };
319
331
  }
320
332
  if (node.kind === "single") return writerFor(node.value, address, sink);
321
- if (node.kind === "variant") return { replace: (value) => emit(sink, {
322
- type: "variant.replace",
323
- at: address,
324
- value
325
- }) };
333
+ if (node.kind === "variant") return {
334
+ replace: (value) => emit(sink, {
335
+ type: "variant.replace",
336
+ at: address,
337
+ value
338
+ }),
339
+ ...node.optional ? { clear: () => emit(sink, {
340
+ type: "value.clear",
341
+ at: address
342
+ }) } : {}
343
+ };
326
344
  let children;
327
345
  return new Proxy({}, { get: (_target, property) => {
328
346
  if (typeof property !== "string" || !node.shape[property]) return void 0;
@@ -540,8 +558,11 @@ const rejected$3 = (operation, code, message) => ({
540
558
  issue: from(operation, code, message)
541
559
  });
542
560
  const executeList = (target, operation, copyPayload) => {
543
- if (!Array.isArray(target.value) || target.node.kind !== "list") return rejected$3(operation, "invalid-address", "List target is invalid.");
544
- const list = target.value;
561
+ if (target.node.kind !== "list") return rejected$3(operation, "invalid-address", "List target is invalid.");
562
+ const absent = target.value === void 0 && "optional" in target.node && target.node.optional === true;
563
+ if (!Array.isArray(target.value) && !absent) return rejected$3(operation, "invalid-address", "List target is invalid.");
564
+ if (absent && operation.type !== "list.replace") return rejected$3(operation, "invalid-address", "Only list replacement can initialize an optional list.");
565
+ const list = Array.isArray(target.value) ? target.value : [];
545
566
  const orderedKeys = keys(list, target.node.keyOf);
546
567
  const own = (value) => copyPayload ? ownPayload(value) : value;
547
568
  if (operation.type === "list.replace") {
@@ -552,18 +573,23 @@ const executeList = (target, operation, copyPayload) => {
552
573
  if (seen.has(key) || target.node.keyOf(operation.value[index]) !== key) return rejected$3(operation, "invalid-list-keys", "List replacement keys are invalid.");
553
574
  seen.add(key);
554
575
  }
555
- if (sameStructuralValue(list, operation.value)) return { status: "unchanged" };
556
- const keys = new Array(list.length);
557
- for (let index = 0; index < list.length; index += 1) keys[index] = target.node.keyOf(list[index]);
558
- const inverse = {
576
+ if (!absent && sameStructuralValue(list, operation.value)) return { status: "unchanged" };
577
+ const keys = list.map(target.node.keyOf);
578
+ const inverse = absent ? {
579
+ type: "value.clear",
580
+ at: operation.at
581
+ } : {
559
582
  type: "list.replace",
560
583
  at: operation.at,
561
584
  value: cloneValue(list, "inverse"),
562
585
  keys
563
586
  };
564
587
  const owned = own(operation.value);
565
- list.length = owned.length;
566
- for (let index = 0; index < owned.length; index += 1) list[index] = owned[index];
588
+ if (absent) target.parent[target.key] = owned;
589
+ else {
590
+ list.length = owned.length;
591
+ for (let index = 0; index < owned.length; index += 1) list[index] = owned[index];
592
+ }
567
593
  return {
568
594
  status: "changed",
569
595
  inverse: [inverse]
@@ -1023,16 +1049,39 @@ const outcome = (operation, value) => value.status === "rejected" ? {
1023
1049
  issue: from(operation, value.code, value.message)
1024
1050
  } : value;
1025
1051
  const executeTree = (root, target, operation, copyPayload) => {
1026
- if (!is(target.value)) return {
1052
+ if (target.node.kind !== "tree") return {
1027
1053
  status: "rejected",
1028
1054
  issue: from(operation, "invalid-address", "Tree target is invalid.")
1029
1055
  };
1030
1056
  const own = (value) => copyPayload ? ownPayload(value) : value;
1057
+ const absent = target.value === void 0 && "optional" in target.node && target.node.optional === true;
1031
1058
  if (operation.type === "tree.replace") {
1059
+ if (absent) {
1060
+ if (!validate(operation.value)) return {
1061
+ status: "rejected",
1062
+ issue: from(operation, "invalid-tree", "Tree replacement is malformed.")
1063
+ };
1064
+ target.parent[target.key] = own(operation.value);
1065
+ return {
1066
+ status: "changed",
1067
+ inverse: [{
1068
+ type: "value.clear",
1069
+ at: operation.at
1070
+ }]
1071
+ };
1072
+ }
1073
+ if (!is(target.value)) return {
1074
+ status: "rejected",
1075
+ issue: from(operation, "invalid-address", "Tree target is invalid.")
1076
+ };
1032
1077
  const result = replace(target.value, operation);
1033
1078
  if (result.status === "changed") set$1(root, operation.at, ownPayload(operation.value));
1034
1079
  return outcome(operation, result);
1035
1080
  }
1081
+ if (!is(target.value)) return {
1082
+ status: "rejected",
1083
+ issue: from(operation, "invalid-address", "Tree target is invalid.")
1084
+ };
1036
1085
  if (operation.type === "tree.insert") return outcome(operation, insert(target.value, operation, own(operation.value)));
1037
1086
  if (operation.type === "tree.set") return outcome(operation, set(target.value, operation, own(operation.value)));
1038
1087
  if (operation.type === "tree.remove") return outcome(operation, remove(target.value, operation));
@@ -1090,6 +1139,18 @@ const executeValue = (target, operation, copyPayload) => {
1090
1139
  inverse: [inverse]
1091
1140
  };
1092
1141
  }
1142
+ if (operation.type === "dict.replace" && target.value === void 0) {
1143
+ if (!("optional" in target.node) || !target.node.optional) return rejected$1(operation, "invalid-address", "Dictionary target is invalid.");
1144
+ const inverse = {
1145
+ type: "value.clear",
1146
+ at: operation.at
1147
+ };
1148
+ target.parent[target.key] = own(operation.value);
1149
+ return {
1150
+ status: "changed",
1151
+ inverse: [inverse]
1152
+ };
1153
+ }
1093
1154
  if (!isRecord(target.value)) return rejected$1(operation, "invalid-address", "Dictionary target is invalid.");
1094
1155
  if (operation.type === "dict.replace") {
1095
1156
  if (sameStructuralValue(target.value, operation.value)) return { status: "unchanged" };
@@ -1138,11 +1199,67 @@ const executeValue = (target, operation, copyPayload) => {
1138
1199
  };
1139
1200
  //#endregion
1140
1201
  //#region core/src/mutation/execute.ts
1202
+ const hasOwn$1 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1203
+ const executeClear = (target, operation) => {
1204
+ if (!("optional" in target.node) || !target.node.optional) return {
1205
+ status: "rejected",
1206
+ issue: from(operation, "required-field", "Only optional values can be cleared.")
1207
+ };
1208
+ 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 {
1210
+ status: "rejected",
1211
+ issue: from(operation, "invalid-operation", "This optional value has no clear operation.")
1212
+ };
1213
+ const inverse = (() => {
1214
+ switch (target.node.kind) {
1215
+ case "field": return {
1216
+ type: "field.set",
1217
+ at: operation.at,
1218
+ value: target.value
1219
+ };
1220
+ case "variant": return {
1221
+ type: "variant.replace",
1222
+ at: operation.at,
1223
+ value: cloneValue(target.value, "inverse")
1224
+ };
1225
+ case "dict":
1226
+ case "record": return {
1227
+ type: "dict.replace",
1228
+ at: operation.at,
1229
+ value: cloneValue(target.value, "inverse")
1230
+ };
1231
+ case "list":
1232
+ if (!Array.isArray(target.value)) return {
1233
+ type: "list.replace",
1234
+ at: operation.at,
1235
+ value: [],
1236
+ keys: []
1237
+ };
1238
+ return {
1239
+ type: "list.replace",
1240
+ at: operation.at,
1241
+ value: cloneValue(target.value, "inverse"),
1242
+ keys: target.value.map(target.node.keyOf)
1243
+ };
1244
+ case "tree": return {
1245
+ type: "tree.replace",
1246
+ at: operation.at,
1247
+ value: cloneValue(target.value, "inverse")
1248
+ };
1249
+ }
1250
+ })();
1251
+ delete target.parent[target.key];
1252
+ return {
1253
+ status: "changed",
1254
+ inverse: [inverse]
1255
+ };
1256
+ };
1141
1257
  const invalidTarget = (operation) => ({
1142
1258
  status: "rejected",
1143
1259
  issue: from(operation, "invalid-address", `Operation '${operation.type}' does not match the schema address.`)
1144
1260
  });
1145
1261
  const execute = (root, operation, target, copyPayload) => {
1262
+ if (operation.type === "value.clear") return !target ? invalidTarget(operation) : executeClear(target, operation);
1146
1263
  if (operation.type === "field.set" || operation.type === "field.clear") return !target || target.node.kind !== "field" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1147
1264
  if (operation.type === "variant.replace") return !target || target.node.kind !== "variant" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1148
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);
@@ -1532,11 +1649,12 @@ const createChangeJournal = (root) => {
1532
1649
  const recordValue = (resolved, operation, inverse) => {
1533
1650
  if (find("value", operation.at, resolved.addressHash)) return;
1534
1651
  const first = inverse[0];
1535
- if (first?.type !== "field.set" && first?.type !== "field.clear") return inverseMismatch(operation);
1652
+ if (first?.type !== "field.set" && first?.type !== "field.clear" && first?.type !== "value.clear" && !("value" in (first ?? {}))) return inverseMismatch(operation);
1653
+ const before = first.type === "field.clear" || first.type === "value.clear" ? absent : "value" in first ? present(first.value) : inverseMismatch(operation);
1536
1654
  add({
1537
1655
  ...base(resolved, operation),
1538
1656
  kind: "value",
1539
- before: first.type === "field.set" ? present(first.value) : absent
1657
+ before
1540
1658
  });
1541
1659
  };
1542
1660
  const recordVariant = (resolved, operation, inverse) => {
@@ -1579,6 +1697,18 @@ const createChangeJournal = (root) => {
1579
1697
  if (!first) return inverseMismatch(operation);
1580
1698
  let subject = find("list", operation.at, resolved.addressHash);
1581
1699
  if (!subject) {
1700
+ if (first.type === "value.clear") {
1701
+ add({
1702
+ ...base(resolved, operation),
1703
+ kind: "list",
1704
+ node: resolved.node,
1705
+ state: {
1706
+ mode: "whole",
1707
+ value: void 0
1708
+ }
1709
+ });
1710
+ return;
1711
+ }
1582
1712
  if (first.type === "list.replace") {
1583
1713
  add({
1584
1714
  ...base(resolved, operation),
@@ -1666,6 +1796,22 @@ const createChangeJournal = (root) => {
1666
1796
  };
1667
1797
  const recordTree = (resolved, operation, inverse) => {
1668
1798
  const current = read(root, operation.at);
1799
+ if (inverse.length === 1 && inverse[0].type === "value.clear" && operation.type === "tree.replace") {
1800
+ const subject = find("tree", operation.at, resolved.addressHash);
1801
+ if (subject) subject.state = {
1802
+ mode: "whole",
1803
+ value: void 0
1804
+ };
1805
+ else add({
1806
+ ...base(resolved, operation),
1807
+ kind: "tree",
1808
+ state: {
1809
+ mode: "whole",
1810
+ value: void 0
1811
+ }
1812
+ });
1813
+ return;
1814
+ }
1669
1815
  if (!is(current)) return inverseMismatch(operation);
1670
1816
  const inverses = inverse.filter(treeOperation);
1671
1817
  if (inverses.length === 0 || inverses.length !== inverse.length) return inverseMismatch(operation);
@@ -1682,7 +1828,7 @@ const createChangeJournal = (root) => {
1682
1828
  record: (resolved, operation, inverse) => {
1683
1829
  profile.batch.journalRecord();
1684
1830
  if (ownerOf(operation.at)) return;
1685
- if (operation.type === "field.set" || operation.type === "field.clear") {
1831
+ if (operation.type === "field.set" || operation.type === "field.clear" || operation.type === "value.clear") {
1686
1832
  recordValue(resolved, operation, inverse);
1687
1833
  return;
1688
1834
  }
@@ -1691,7 +1837,8 @@ const createChangeJournal = (root) => {
1691
1837
  return;
1692
1838
  }
1693
1839
  if (operation.type === "dict.set" || operation.type === "dict.delete" || operation.type === "dict.replace") {
1694
- recordDict(resolved, operation, inverse);
1840
+ if (operation.type === "dict.replace" && inverse[0]?.type === "value.clear") recordValue(resolved, operation, inverse);
1841
+ else recordDict(resolved, operation, inverse);
1695
1842
  return;
1696
1843
  }
1697
1844
  if (operation.type === "entity.create" || operation.type === "entity.remove" || operation.type === "entity.move") {
@@ -1792,7 +1939,8 @@ const decode = (input) => {
1792
1939
  switch (input.type) {
1793
1940
  case "field.set":
1794
1941
  case "variant.replace": return has(input, "value") ? decoded(input) : rejected("invalid-operation", "Operation value is required.");
1795
- case "field.clear": return decoded(input);
1942
+ case "field.clear":
1943
+ case "value.clear": return decoded(input);
1796
1944
  case "dict.set": return typeof input.key === "string" && has(input, "value") ? decoded(input) : rejected("invalid-operation", "Dictionary set requires a string key and value.");
1797
1945
  case "dict.delete": return typeof input.key === "string" ? decoded(input) : rejected("invalid-operation", "Dictionary delete requires a string key.");
1798
1946
  case "dict.replace": return isRecord(input.value) ? decoded(input) : rejected("invalid-operation", "Dictionary replacement must be an object.");
@@ -1985,6 +2133,7 @@ const commandFootprint = (operations) => {
1985
2133
  for (const operation of operations) switch (operation.type) {
1986
2134
  case "field.set":
1987
2135
  case "field.clear":
2136
+ case "value.clear":
1988
2137
  case "variant.replace":
1989
2138
  add(targets, {
1990
2139
  kind: "value",
@@ -2147,166 +2296,6 @@ const decodeCommandFootprint = (value) => {
2147
2296
  return Object.freeze(Array.from(targets.values()));
2148
2297
  };
2149
2298
  //#endregion
2150
- //#region core/src/runtime/notification.ts
2151
- const ROOT_BUCKET = Symbol("document-root");
2152
- const notifications = /* @__PURE__ */ new WeakMap();
2153
- const key = (value) => bucket(value) ?? ROOT_BUCKET;
2154
- const createNotification = (runtime) => {
2155
- const notification = {
2156
- root: /* @__PURE__ */ new Set(),
2157
- filtered: /* @__PURE__ */ new Set(),
2158
- buckets: /* @__PURE__ */ new Map(),
2159
- processors: [],
2160
- candidates: /* @__PURE__ */ new Set(),
2161
- rootSnapshot: [],
2162
- notifying: false
2163
- };
2164
- notifications.set(runtime, notification);
2165
- return notification;
2166
- };
2167
- const notificationOf = (runtime) => {
2168
- const value = notifications.get(runtime);
2169
- if (!value) throw new Error("Unknown Doxum runtime.");
2170
- return value;
2171
- };
2172
- const shareNotification = (source, target) => {
2173
- notifications.set(target, notificationOf(source));
2174
- };
2175
- const registerProcessor = (runtime, processor) => {
2176
- const notification = notificationOf(runtime);
2177
- const entry = {
2178
- processor,
2179
- active: true
2180
- };
2181
- notification.processors.push(entry);
2182
- return () => {
2183
- if (!entry.active) return;
2184
- entry.active = false;
2185
- if (!notification.notifying) {
2186
- const index = notification.processors.indexOf(entry);
2187
- if (index >= 0) notification.processors.splice(index, 1);
2188
- }
2189
- };
2190
- };
2191
- const subscribeRoot = (notification, listener) => {
2192
- const entry = {
2193
- listener,
2194
- active: true
2195
- };
2196
- notification.root.add(entry);
2197
- return () => {
2198
- entry.active = false;
2199
- if (!notification.notifying) notification.root.delete(entry);
2200
- };
2201
- };
2202
- const subscribeTargets = (notification, targets, listener) => {
2203
- const entry = {
2204
- targets: Object.freeze(targets.slice()),
2205
- listener,
2206
- active: true
2207
- };
2208
- notification.filtered.add(entry);
2209
- const keys = new Set(targets.map(key));
2210
- keys.forEach((key) => {
2211
- const bucket = notification.buckets.get(key) ?? /* @__PURE__ */ new Set();
2212
- bucket.add(entry);
2213
- notification.buckets.set(key, bucket);
2214
- });
2215
- return () => {
2216
- if (!entry.active) return;
2217
- entry.active = false;
2218
- if (notification.notifying) return;
2219
- notification.filtered.delete(entry);
2220
- keys.forEach((key) => {
2221
- const bucket = notification.buckets.get(key);
2222
- bucket?.delete(entry);
2223
- if (bucket?.size === 0) notification.buckets.delete(key);
2224
- });
2225
- };
2226
- };
2227
- const notify = (notification, commit) => {
2228
- notification.notifying = true;
2229
- const errors = [];
2230
- const call = (listener) => {
2231
- try {
2232
- listener(commit);
2233
- } catch (error) {
2234
- errors.push(Object.freeze({
2235
- phase: "listener",
2236
- error
2237
- }));
2238
- }
2239
- };
2240
- try {
2241
- const processorCount = notification.processors.length;
2242
- for (let index = 0; index < processorCount; index += 1) {
2243
- const entry = notification.processors[index];
2244
- if (!entry?.active) continue;
2245
- try {
2246
- entry.processor.process(commit);
2247
- } catch (error) {
2248
- errors.push(Object.freeze({
2249
- phase: "processor",
2250
- error
2251
- }));
2252
- }
2253
- }
2254
- for (let index = 0; index < processorCount; index += 1) {
2255
- const entry = notification.processors[index];
2256
- if (!entry?.active) continue;
2257
- try {
2258
- entry.processor.flush();
2259
- } catch (error) {
2260
- errors.push(Object.freeze({
2261
- phase: "flush",
2262
- error
2263
- }));
2264
- }
2265
- }
2266
- const candidates = notification.candidates;
2267
- candidates.clear();
2268
- if (commit.impact.kind === "reset") notification.filtered.forEach((entry) => candidates.add(entry));
2269
- else for (const operation of commit.operations) {
2270
- const key = operation.at[0] ?? ROOT_BUCKET;
2271
- notification.buckets.get(key)?.forEach((entry) => candidates.add(entry));
2272
- notification.buckets.get(ROOT_BUCKET)?.forEach((entry) => candidates.add(entry));
2273
- }
2274
- candidates.forEach((entry) => {
2275
- if (entry.active && entry.targets.some(commit.impact.affects)) call(entry.listener);
2276
- });
2277
- const rootSnapshot = notification.rootSnapshot;
2278
- rootSnapshot.length = 0;
2279
- notification.root.forEach((entry) => rootSnapshot.push(entry));
2280
- for (const entry of rootSnapshot) if (entry.active) call(entry.listener);
2281
- } finally {
2282
- notification.notifying = false;
2283
- let writeIndex = 0;
2284
- for (const entry of notification.processors) if (entry.active) notification.processors[writeIndex++] = entry;
2285
- notification.processors.length = writeIndex;
2286
- notification.root.forEach((entry) => {
2287
- if (!entry.active) notification.root.delete(entry);
2288
- });
2289
- notification.filtered.forEach((entry) => {
2290
- if (!entry.active) notification.filtered.delete(entry);
2291
- });
2292
- notification.buckets.forEach((bucket, key) => {
2293
- bucket.forEach((entry) => {
2294
- if (!entry.active) bucket.delete(entry);
2295
- });
2296
- if (bucket.size === 0) notification.buckets.delete(key);
2297
- });
2298
- }
2299
- return Object.freeze(errors);
2300
- };
2301
- const disposeNotification = (notification) => {
2302
- notification.root.clear();
2303
- notification.filtered.clear();
2304
- notification.buckets.clear();
2305
- notification.processors.length = 0;
2306
- notification.candidates.clear();
2307
- notification.rootSnapshot.length = 0;
2308
- };
2309
- //#endregion
2310
2299
  //#region core/src/runtime.ts
2311
2300
  var RejectedUpdate = class extends Error {
2312
2301
  issues;
@@ -2336,6 +2325,7 @@ const createDocument = (input) => {
2336
2325
  const assertWritable = (intent) => {
2337
2326
  if (state.disposed) throw new DocumentDisposedError();
2338
2327
  if (busy) throw new DocumentReentrancyError();
2328
+ if (accessOf(runtime).projectionLocks) throw new DocumentReentrancyError();
2339
2329
  assertRuntimeWritable(runtime, intent);
2340
2330
  };
2341
2331
  const runTransaction = (session, run) => {
@@ -2567,10 +2557,14 @@ const createDocument = (input) => {
2567
2557
  history: history.api,
2568
2558
  dispose: () => {
2569
2559
  if (state.disposed) return;
2560
+ if (busy || accessOf(runtime).projectionLocks) throw new DocumentReentrancyError();
2570
2561
  state.disposed = true;
2571
- disposeNotification(notification);
2572
- history.api.clear();
2573
- disposeRuntimeDriver(runtime);
2562
+ try {
2563
+ disposeNotification(notification);
2564
+ } finally {
2565
+ history.api.clear();
2566
+ disposeRuntimeDriver(runtime);
2567
+ }
2574
2568
  }
2575
2569
  };
2576
2570
  bindRuntimeAccess(runtime, state);
@@ -2594,387 +2588,6 @@ const asReadable = (runtime) => {
2594
2588
  //#region core/src/projection/select.ts
2595
2589
  const select = (runtime, selector) => readWith(runtime, selector);
2596
2590
  //#endregion
2597
- //#region core/src/projection/collection-view.ts
2598
- const createCollectionView = (input) => {
2599
- if (schemaOf(input.runtime) !== input.source.schema) throw new Error("Collection selector belongs to another schema.");
2600
- const idsListeners = /* @__PURE__ */ new Set();
2601
- const allListeners = /* @__PURE__ */ new Set();
2602
- const itemListeners = /* @__PURE__ */ new Map();
2603
- const itemReadables = /* @__PURE__ */ new Map();
2604
- let disposed = false;
2605
- const sourceNode = nodeAt$1(input.source.schema, input.source.address);
2606
- const entryNode = sourceNode?.kind === "table" || sourceNode?.kind === "map" ? sourceNode.value : {
2607
- kind: "object",
2608
- shape: {}
2609
- };
2610
- const mapEntry = (document, id) => {
2611
- profile.collectionView.mapped();
2612
- const address = [...input.source.address, String(id)];
2613
- let active = true;
2614
- try {
2615
- return input.map(id, readerFor(entryNode, {
2616
- root: () => document,
2617
- active: () => active
2618
- }, address));
2619
- } finally {
2620
- active = false;
2621
- }
2622
- };
2623
- const build = (previous, changed) => {
2624
- const document = documentOf(input.runtime);
2625
- const collection = read(document, input.source.address);
2626
- const ids = collection && typeof collection === "object" && "ids" in collection && Array.isArray(collection.ids) ? [...collection.ids] : Object.keys(collection ?? {});
2627
- profile.collectionView.idsScanned(ids.length);
2628
- const values = previous && changed ? previous.values : /* @__PURE__ */ new Map();
2629
- if (previous && changed) {
2630
- const nextIds = new Set(ids);
2631
- for (const id of previous.ids) if (!nextIds.has(id)) values.delete(id);
2632
- }
2633
- for (const id of ids) {
2634
- if (previous && changed && previous.values.has(id) && !changed.has(id)) continue;
2635
- const next = mapEntry(document, id);
2636
- if (previous && changed && previous.values.has(id)) {
2637
- const before = previous.values.get(id);
2638
- if (input.isEqual ? input.isEqual(before, next) : Object.is(before, next)) {
2639
- changed.delete(id);
2640
- continue;
2641
- }
2642
- }
2643
- values.set(id, next);
2644
- }
2645
- return {
2646
- ids: Object.freeze(ids),
2647
- values,
2648
- all: void 0,
2649
- allDirty: true
2650
- };
2651
- };
2652
- let state = build();
2653
- const ensureAll = () => {
2654
- if (!state.allDirty && state.all) return state.all;
2655
- profile.collectionView.arrayCopied();
2656
- const all = new Array(state.ids.length);
2657
- for (let index = 0; index < state.ids.length; index += 1) all[index] = state.values.get(state.ids[index]);
2658
- const stable = Object.freeze(all);
2659
- state.all = stable;
2660
- state.allDirty = false;
2661
- return stable;
2662
- };
2663
- const emit = (listeners) => {
2664
- Array.from(listeners).forEach((listener) => listener());
2665
- };
2666
- const unsubscribe = input.runtime.subscribe(input.source, (commit) => {
2667
- if (disposed) return;
2668
- const impact = commit.impact.collection(input.source);
2669
- if (impact.kind === "incremental" && impact.added.size === 0 && impact.removed.size === 0 && !impact.orderChanged) {
2670
- const previous = state;
2671
- const changes = [];
2672
- const document = documentOf(input.runtime);
2673
- impact.updated.forEach((id) => {
2674
- if (!previous.values.has(id)) return;
2675
- const before = previous.values.get(id);
2676
- const after = mapEntry(document, id);
2677
- if (input.isEqual ? input.isEqual(before, after) : Object.is(before, after)) return;
2678
- changes.push([id, after]);
2679
- });
2680
- if (changes.length === 0) return;
2681
- for (const [id, value] of changes) previous.values.set(id, value);
2682
- previous.all = void 0;
2683
- previous.allDirty = true;
2684
- emit(allListeners);
2685
- for (const [id] of changes) {
2686
- const listeners = itemListeners.get(id);
2687
- if (listeners) emit(listeners);
2688
- }
2689
- return;
2690
- }
2691
- const changed = impact.kind === "reset" ? void 0 : new Set([...impact.added, ...impact.updated]);
2692
- const previous = state;
2693
- const next = build(previous, changed);
2694
- const changedIds = /* @__PURE__ */ new Set();
2695
- if (changed) {
2696
- changed.forEach((id) => changedIds.add(id));
2697
- previous.ids.forEach((id) => {
2698
- if (!next.values.has(id)) changedIds.add(id);
2699
- });
2700
- next.ids.forEach((id) => {
2701
- if (!previous.values.has(id)) changedIds.add(id);
2702
- });
2703
- } else {
2704
- previous.ids.forEach((id) => {
2705
- if (!next.values.has(id)) changedIds.add(id);
2706
- 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);
2707
- });
2708
- next.ids.forEach((id) => {
2709
- if (!previous.values.has(id)) changedIds.add(id);
2710
- });
2711
- }
2712
- 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);
2713
- state = idsChanged || changedIds.size ? next : previous;
2714
- if (idsChanged) emit(idsListeners);
2715
- if (idsChanged || changedIds.size) emit(allListeners);
2716
- changedIds.forEach((id) => {
2717
- const listeners = itemListeners.get(id);
2718
- if (listeners) emit(listeners);
2719
- });
2720
- });
2721
- return {
2722
- ids: {
2723
- current: () => state.ids,
2724
- revision: () => input.runtime.revision(),
2725
- subscribe: (listener) => {
2726
- if (disposed) return () => void 0;
2727
- idsListeners.add(listener);
2728
- return () => idsListeners.delete(listener);
2729
- }
2730
- },
2731
- all: {
2732
- current: ensureAll,
2733
- revision: () => input.runtime.revision(),
2734
- subscribe: (listener) => {
2735
- if (disposed) return () => void 0;
2736
- allListeners.add(listener);
2737
- return () => allListeners.delete(listener);
2738
- }
2739
- },
2740
- item: (id) => {
2741
- const cached = itemReadables.get(id);
2742
- if (cached) return cached;
2743
- const readable = {
2744
- current: () => state.values.get(id),
2745
- revision: () => input.runtime.revision(),
2746
- subscribe: (listener) => {
2747
- if (disposed) return () => void 0;
2748
- const listeners = itemListeners.get(id) ?? /* @__PURE__ */ new Set();
2749
- itemListeners.set(id, listeners);
2750
- listeners.add(listener);
2751
- return () => {
2752
- listeners.delete(listener);
2753
- if (listeners.size === 0) itemListeners.delete(id);
2754
- };
2755
- }
2756
- };
2757
- itemReadables.set(id, readable);
2758
- return readable;
2759
- },
2760
- revision: () => input.runtime.revision(),
2761
- dispose: () => {
2762
- if (disposed) return;
2763
- disposed = true;
2764
- unsubscribe();
2765
- idsListeners.clear();
2766
- allListeners.clear();
2767
- itemListeners.clear();
2768
- itemReadables.clear();
2769
- state.values.clear();
2770
- state = {
2771
- ids: Object.freeze([]),
2772
- values: /* @__PURE__ */ new Map(),
2773
- all: Object.freeze([]),
2774
- allDirty: false
2775
- };
2776
- }
2777
- };
2778
- };
2779
- //#endregion
2780
- //#region core/src/projection/materialized-view.ts
2781
- const materializedMeta = /* @__PURE__ */ new WeakMap();
2782
- const createMaterializedView = (runtime, spec) => {
2783
- const listeners = /* @__PURE__ */ new Set();
2784
- let disposed = false;
2785
- let fault;
2786
- let revision = runtime.revision();
2787
- let instance;
2788
- let currentValue;
2789
- let pendingEmit = false;
2790
- const dependencies = createDependencyTracker();
2791
- const sources = spec.sources ?? {};
2792
- const sourceEntries = Object.keys(sources).map((key) => [key, sources[key]]);
2793
- sourceEntries.forEach(([, source]) => {
2794
- const meta = materializedMeta.get(source);
2795
- if (!meta || meta.runtime !== runtime) throw new Error("Materialized sources must belong to the same runtime and be created first.");
2796
- });
2797
- const sourceValuesRecord = {};
2798
- let sourceChanged = false;
2799
- for (const [key] of sourceEntries) sourceValuesRecord[key] = {
2800
- value: void 0,
2801
- change: void 0
2802
- };
2803
- const sourceValues = (commitRevision) => {
2804
- sourceChanged = false;
2805
- for (const [key, source] of sourceEntries) {
2806
- const meta = materializedMeta.get(source);
2807
- const entry = sourceValuesRecord[key];
2808
- entry.value = meta?.value ?? source.current();
2809
- entry.change = meta?.change;
2810
- if (commitRevision !== void 0 && meta?.changedRevision === commitRevision) {
2811
- sourceChanged = true;
2812
- profile.materialized.sourceChanged();
2813
- }
2814
- }
2815
- return sourceValuesRecord;
2816
- };
2817
- let activeImpact;
2818
- const assertImpact = () => {
2819
- if (!activeImpact) throw new Error("Materialized impact is no longer active.");
2820
- return activeImpact;
2821
- };
2822
- const trackedImpact = {
2823
- get kind() {
2824
- return assertImpact().kind;
2825
- },
2826
- affects: (target) => {
2827
- dependencies.record(target);
2828
- return assertImpact().affects(target);
2829
- },
2830
- collection: (selector) => {
2831
- dependencies.record(selector);
2832
- return assertImpact().collection(selector);
2833
- },
2834
- get operations() {
2835
- dependencies.record({
2836
- kind: "value",
2837
- at: []
2838
- });
2839
- return assertImpact().operations;
2840
- }
2841
- };
2842
- let view;
2843
- const writeMeta = (change, value, nextRevision, changed) => {
2844
- const meta = materializedMeta.get(view);
2845
- if (meta) {
2846
- meta.value = value;
2847
- meta.change = change;
2848
- meta.revision = nextRevision;
2849
- if (changed) meta.changedRevision = nextRevision;
2850
- } else materializedMeta.set(view, {
2851
- runtime,
2852
- value,
2853
- change,
2854
- revision: nextRevision,
2855
- changedRevision: changed ? nextRevision : -1
2856
- });
2857
- };
2858
- const build = () => {
2859
- profile.materialized.rebuilt();
2860
- dependencies.clear();
2861
- let active = true;
2862
- try {
2863
- instance = spec.build({
2864
- read: documentReader(schemaOf(runtime), () => documentOf(runtime), () => active),
2865
- sources: sourceValues()
2866
- });
2867
- currentValue = instance.value;
2868
- fault = void 0;
2869
- revision = runtime.revision();
2870
- } finally {
2871
- active = false;
2872
- }
2873
- };
2874
- build();
2875
- const emit = () => {
2876
- profile.materialized.notification();
2877
- Array.from(listeners).forEach((listener) => listener());
2878
- };
2879
- const process = (commit) => {
2880
- if (disposed) return;
2881
- try {
2882
- if (commit.kind === "replace" || fault) {
2883
- build();
2884
- writeMeta(void 0, currentValue, commit.revision, true);
2885
- pendingEmit = true;
2886
- return;
2887
- }
2888
- const sourceSnapshot = sourceValues(commit.revision);
2889
- if (!sourceChanged && dependencies.size() > 0 && !dependencies.some(commit.impact.affects)) {
2890
- profile.materialized.skipped();
2891
- revision = commit.revision;
2892
- writeMeta(void 0, currentValue, commit.revision, false);
2893
- return;
2894
- }
2895
- dependencies.clear();
2896
- profile.materialized.updated();
2897
- let active = true;
2898
- let result;
2899
- try {
2900
- activeImpact = commit.impact;
2901
- result = instance.update({
2902
- read: documentReader(schemaOf(runtime), () => documentOf(runtime), () => active),
2903
- revision: commit.revision,
2904
- source: commit.source,
2905
- impact: trackedImpact,
2906
- sources: sourceSnapshot
2907
- });
2908
- } finally {
2909
- activeImpact = void 0;
2910
- active = false;
2911
- }
2912
- if (result.kind === "rebuild") {
2913
- build();
2914
- writeMeta(void 0, currentValue, commit.revision, true);
2915
- pendingEmit = true;
2916
- } else if (result.kind === "changed") {
2917
- currentValue = result.value;
2918
- revision = commit.revision;
2919
- writeMeta(result.change, currentValue, commit.revision, true);
2920
- pendingEmit = true;
2921
- } else {
2922
- revision = commit.revision;
2923
- writeMeta(void 0, currentValue, commit.revision, false);
2924
- }
2925
- } catch (error) {
2926
- try {
2927
- build();
2928
- writeMeta(void 0, currentValue, commit.revision, true);
2929
- pendingEmit = true;
2930
- } catch (rebuildError) {
2931
- fault = rebuildError;
2932
- throw rebuildError;
2933
- }
2934
- throw error;
2935
- }
2936
- };
2937
- view = {
2938
- current: () => {
2939
- if (fault) throw fault;
2940
- return currentValue;
2941
- },
2942
- revision: () => revision,
2943
- subscribe: (listener) => {
2944
- listeners.add(listener);
2945
- return () => listeners.delete(listener);
2946
- },
2947
- rebuild: () => {
2948
- build();
2949
- writeMeta(void 0, currentValue, runtime.revision(), true);
2950
- emit();
2951
- },
2952
- dispose: () => {
2953
- if (disposed) return;
2954
- disposed = true;
2955
- unregister();
2956
- listeners.clear();
2957
- materializedMeta.delete(view);
2958
- }
2959
- };
2960
- materializedMeta.set(view, {
2961
- runtime,
2962
- value: currentValue,
2963
- change: void 0,
2964
- revision,
2965
- changedRevision: -1
2966
- });
2967
- const unregister = registerProcessor(runtime, {
2968
- process,
2969
- flush: () => {
2970
- if (!pendingEmit) return;
2971
- pendingEmit = false;
2972
- emit();
2973
- }
2974
- });
2975
- return view;
2976
- };
2977
- //#endregion
2978
- export { DocumentDisposedError, DocumentReentrancyError, asReadable, commandFootprint, contains, createCollectionView, createDocument, createMaterializedView, 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 };
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 };
2979
2592
 
2980
2593
  //# sourceMappingURL=index.js.map