doxum 0.1.1 → 0.1.3

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 (44) hide show
  1. package/README.md +77 -2
  2. package/dist/{contract-DwNiKioc.d.ts → contract-BsY1XLiG.d.ts} +64 -5
  3. package/dist/{contract-Otb5W6cQ.d.cts → contract-D8kjdfqT.d.cts} +64 -5
  4. package/dist/{dependency-BdEMyquf.js → dependency-C5_R1tvQ.js} +24 -263
  5. package/dist/dependency-C5_R1tvQ.js.map +1 -0
  6. package/dist/{dependency-DLcCvNKq.cjs → dependency-Dfzs3khk.cjs} +48 -341
  7. package/dist/dependency-Dfzs3khk.cjs.map +1 -0
  8. package/dist/driver-CNxqMVFH.cjs +69 -0
  9. package/dist/driver-CNxqMVFH.cjs.map +1 -0
  10. package/dist/driver-CbzfW5MR.js +46 -0
  11. package/dist/driver-CbzfW5MR.js.map +1 -0
  12. package/dist/index.cjs +428 -133
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +8 -5
  15. package/dist/index.d.ts +8 -5
  16. package/dist/index.js +352 -61
  17. package/dist/index.js.map +1 -1
  18. package/dist/integration.cjs +1 -1
  19. package/dist/integration.cjs.map +1 -1
  20. package/dist/integration.d.cts +2 -2
  21. package/dist/integration.d.ts +2 -2
  22. package/dist/integration.js +1 -1
  23. package/dist/integration.js.map +1 -1
  24. package/dist/local-sync.cjs +485 -0
  25. package/dist/local-sync.cjs.map +1 -0
  26. package/dist/local-sync.d.cts +74 -0
  27. package/dist/local-sync.d.ts +74 -0
  28. package/dist/local-sync.js +476 -0
  29. package/dist/local-sync.js.map +1 -0
  30. package/dist/ownership-B-VAPcce.js +242 -0
  31. package/dist/ownership-B-VAPcce.js.map +1 -0
  32. package/dist/ownership-CU-9DKJQ.cjs +301 -0
  33. package/dist/ownership-CU-9DKJQ.cjs.map +1 -0
  34. package/dist/react.cjs.map +1 -1
  35. package/dist/react.d.cts +2 -2
  36. package/dist/react.d.ts +2 -2
  37. package/dist/react.js.map +1 -1
  38. package/package.json +14 -8
  39. package/skills/doxum-runtime/references/guide.en.md +60 -5
  40. package/skills/doxum-runtime/references/guide.zh-CN.md +48 -2
  41. package/skills/doxum-runtime/references/invariants.en.md +23 -8
  42. package/skills/doxum-runtime/references/invariants.zh-CN.md +2 -2
  43. package/dist/dependency-BdEMyquf.js.map +0 -1
  44. package/dist/dependency-DLcCvNKq.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- import { A as resolveAddress, B as record, C as impact_target_exports, D as nodeAt$1, E as locate, F as field, G as variant, H as single, I as list, L as map, M as set$1, N as profile, O as overlaps, P as dict, R as object, S as id, T as debugKey, U as table, V as schema, W as tree, _ as snapshotPayload, a as schemaOf, b as belongs, c as documentReader, d as deepEqual, f as isRecord, g as sameStructuralValue, h as ownPayload, i as readWith, j as resolveLocated, k as read, l as readerFor, m as operationOwnsStructuralPayload, n as bindRuntimeAccess, o as DocumentDisposedError, p as isStablePayload, r as documentOf, s as DocumentReentrancyError, t as createDependencyTracker, u as cloneValue, v as transferPayload, w as contains, x as bucket, y as address$1, z as optional } from "./dependency-BdEMyquf.js";
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";
3
+ import { n as bindRuntimeDriver, r as disposeRuntimeDriver, t as assertRuntimeWritable } from "./driver-CbzfW5MR.js";
2
4
  //#region core/src/impact.ts
3
5
  const emptyCollectionImpact = Object.freeze({
4
6
  kind: "incremental",
@@ -36,9 +38,9 @@ const createImpact = (input) => {
36
38
  profile.impact.affects();
37
39
  if (kind === "reset") return true;
38
40
  if (!belongs(value, input.schema)) return false;
39
- if (pathsAffect(address$1(value))) return true;
41
+ if (pathsAffect(address$2(value))) return true;
40
42
  if (value.kind === "collection") {
41
- const collection = findCollection(address$1(value));
43
+ const collection = findCollection(address$2(value));
42
44
  if (!collection) return false;
43
45
  const id$1 = id(value);
44
46
  if (id$1 !== void 0) return collection.added.has(id$1) || collection.removed.has(id$1) || collection.updated.has(id$1);
@@ -1156,7 +1158,7 @@ const execute = (root, operation, target, copyPayload) => {
1156
1158
  //#region core/src/mutation/journal.ts
1157
1159
  const indexNode = () => ({ children: /* @__PURE__ */ new Map() });
1158
1160
  const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1159
- const sameAddress = (left, right) => {
1161
+ const sameAddress$1 = (left, right) => {
1160
1162
  if (left.length !== right.length) return false;
1161
1163
  for (let index = 0; index < left.length; index += 1) if (left[index] !== right[index]) return false;
1162
1164
  return true;
@@ -1271,7 +1273,7 @@ const restoreRootSubject = (value, subject) => {
1271
1273
  return restoreChange(value, subject.state);
1272
1274
  };
1273
1275
  const restoreSubject = (value, prefix, subject) => {
1274
- if (sameAddress(prefix, subject.address)) return restoreRootSubject(value, subject);
1276
+ if (sameAddress$1(prefix, subject.address)) return restoreRootSubject(value, subject);
1275
1277
  const address = subject.address.slice(prefix.length);
1276
1278
  if (subject.kind === "value") {
1277
1279
  writePresence(value, address, subject.before);
@@ -1498,7 +1500,7 @@ const createChangeJournal = (root) => {
1498
1500
  const candidates = buckets.get(addressHash) ?? [];
1499
1501
  for (const subject of candidates) {
1500
1502
  profile.journal.comparison();
1501
- if (subject.kind === kind && sameAddress(subject.address, address)) return subject;
1503
+ if (subject.kind === kind && sameAddress$1(subject.address, address)) return subject;
1502
1504
  }
1503
1505
  }
1504
1506
  const ownerOf = (address) => {
@@ -1720,7 +1722,7 @@ const createChangeJournal = (root) => {
1720
1722
  const changeAt = (address, addressHash) => {
1721
1723
  const candidates = collectionBuckets.get(addressHash) ?? [];
1722
1724
  collectionBuckets.set(addressHash, candidates);
1723
- let change = candidates.find((entry) => sameAddress(entry.address, address));
1725
+ let change = candidates.find((entry) => sameAddress$1(entry.address, address));
1724
1726
  if (!change) {
1725
1727
  change = {
1726
1728
  address,
@@ -1764,7 +1766,7 @@ const createChangeJournal = (root) => {
1764
1766
  const has = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1765
1767
  const stringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
1766
1768
  const positions = (value) => Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0);
1767
- const address = (value) => stringArray(value);
1769
+ const address$1 = (value) => stringArray(value);
1768
1770
  const anchor = (value) => {
1769
1771
  if (!isRecord(value)) return false;
1770
1772
  if (Number(has(value, "at")) + Number(has(value, "before")) + Number(has(value, "after")) !== 1) return false;
@@ -1785,7 +1787,7 @@ const rejected = (code, message) => ({
1785
1787
  });
1786
1788
  const decode = (input) => {
1787
1789
  if (!isRecord(input)) return rejected("invalid-operation", "Operation must be an object.");
1788
- if (!address(input.at)) return rejected("invalid-address", "Operation address is malformed.");
1790
+ if (!address$1(input.at)) return rejected("invalid-address", "Operation address is malformed.");
1789
1791
  if (typeof input.type !== "string") return rejected("invalid-operation", "Operation type must be a string.");
1790
1792
  switch (input.type) {
1791
1793
  case "field.set":
@@ -1821,6 +1823,7 @@ const ownAddress = (value) => {
1821
1823
  profile.address.arrayCopied();
1822
1824
  return Object.freeze(value.slice());
1823
1825
  };
1826
+ const structuralPayload = (value) => Array.isArray(value) || isPlainObject(value);
1824
1827
  const normalize = (operation) => {
1825
1828
  profile.mutation.normalized();
1826
1829
  const copy = {
@@ -1835,23 +1838,34 @@ const normalize = (operation) => {
1835
1838
  return Object.freeze(copy);
1836
1839
  };
1837
1840
  const publish = (operation) => {
1838
- if (!operationOwnsStructuralPayload(operation)) return operation;
1839
- if (operation.type === "entity.create" && operation.entries.every((entry) => isStablePayload(entry.value))) return operation;
1841
+ if (operation.type === "entity.create" && operation.entries.every((entry) => !structuralPayload(entry.value) || isStablePayload(entry.value))) return operation;
1842
+ if ("value" in operation && (!structuralPayload(operation.value) || isStablePayload(operation.value))) return operation;
1840
1843
  const copy = { ...operation };
1841
1844
  if (copy.entries) copy.entries = Object.freeze(copy.entries.map((entry) => Object.freeze({
1842
1845
  id: entry.id,
1843
- value: isStablePayload(entry.value) ? entry.value : snapshotPayload(entry.value, "commit")
1846
+ value: structuralPayload(entry.value) ? isStablePayload(entry.value) ? entry.value : snapshotPayload(entry.value, "commit") : entry.value
1844
1847
  })));
1845
- else if ("value" in copy) copy.value = isStablePayload(copy.value) ? copy.value : snapshotPayload(copy.value, "commit");
1848
+ else if ("value" in copy && structuralPayload(copy.value)) copy.value = isStablePayload(copy.value) ? copy.value : snapshotPayload(copy.value, "commit");
1846
1849
  return Object.freeze(copy);
1847
1850
  };
1851
+ const requiresPayloadCopy = (operation) => {
1852
+ if (operation.type === "entity.create") return operation.entries.some((entry) => isStablePayload(entry.value));
1853
+ return "value" in operation && isStablePayload(operation.value);
1854
+ };
1848
1855
  const inverse = (operation) => {
1849
1856
  const copy = { ...operation };
1850
1857
  if (copy.anchor) copy.anchor = Object.freeze({ ...copy.anchor });
1851
1858
  if (copy.keys) copy.keys = Object.freeze([...copy.keys]);
1852
- if (copy.entries) copy.entries = Object.freeze(copy.entries.map((entry) => Object.freeze({ ...entry })));
1859
+ if (copy.entries) copy.entries = Object.freeze(copy.entries.map((entry) => Object.freeze({
1860
+ id: entry.id,
1861
+ value: structuralPayload(entry.value) ? isStablePayload(entry.value) ? entry.value : snapshotPayload(entry.value, "inverse") : entry.value
1862
+ })));
1853
1863
  if (copy.ids) copy.ids = Object.freeze([...copy.ids]);
1854
1864
  if (copy.positions) copy.positions = Object.freeze([...copy.positions]);
1865
+ if ("value" in copy && structuralPayload(copy.value)) {
1866
+ const payload = copy;
1867
+ payload.value = isStablePayload(payload.value) ? payload.value : snapshotPayload(payload.value, "inverse");
1868
+ }
1855
1869
  return Object.freeze(copy);
1856
1870
  };
1857
1871
  //#endregion
@@ -1859,18 +1873,18 @@ const inverse = (operation) => {
1859
1873
  const createMutationSession = (root, schema, options = {}) => {
1860
1874
  const steps = [];
1861
1875
  const journal = createChangeJournal(root);
1862
- let closed = false;
1876
+ let state = "active";
1863
1877
  const rollback = () => {
1864
- if (closed) return;
1878
+ if (state === "rolled-back") return;
1865
1879
  for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) for (const inverse of steps[stepIndex].inverse) {
1866
1880
  const result = execute(root, inverse, resolveLocated(schema, root, inverse.at), true);
1867
1881
  if (result.status === "rejected") throw new Error(`Document rollback failed: ${result.issue.message}`);
1868
1882
  }
1869
- closed = true;
1883
+ state = "rolled-back";
1870
1884
  };
1871
1885
  return {
1872
1886
  apply: (input) => {
1873
- if (closed) throw new Error("Mutation session is closed.");
1887
+ if (state !== "active") throw new Error("Mutation session is closed.");
1874
1888
  profile.batch.operation();
1875
1889
  const decoded = decode(input);
1876
1890
  if (decoded.status === "rejected") {
@@ -1882,7 +1896,7 @@ const createMutationSession = (root, schema, options = {}) => {
1882
1896
  const resolved = resolveLocated(schema, root, normalized.at);
1883
1897
  if (resolved?.collection) profile.batch.collectionResolved();
1884
1898
  profile.mutation.executed();
1885
- const result = execute(root, normalized, resolved, options.copyPayload === true);
1899
+ const result = execute(root, normalized, resolved, options.copyPayload === true || requiresPayloadCopy(normalized));
1886
1900
  if (result.status === "rejected") {
1887
1901
  profile.batch.rejected();
1888
1902
  return result.issue;
@@ -1909,9 +1923,9 @@ const createMutationSession = (root, schema, options = {}) => {
1909
1923
  }
1910
1924
  },
1911
1925
  finish: () => {
1912
- if (closed) throw new Error("Mutation session is closed.");
1913
- closed = true;
1926
+ if (state !== "active") throw new Error("Mutation session is closed.");
1914
1927
  const changes = journal.finish();
1928
+ state = "finished";
1915
1929
  if (changes.status === "unchanged") return { status: "unchanged" };
1916
1930
  let inverseCount = 0;
1917
1931
  for (const step of steps) inverseCount += step.inverse.length;
@@ -1948,6 +1962,191 @@ const mutateOperations = (root, schema, operations, options = {}) => {
1948
1962
  return session.finish();
1949
1963
  };
1950
1964
  //#endregion
1965
+ //#region core/src/mutation/footprint.ts
1966
+ const sameAddress = (left, right) => left.length === right.length && left.every((segment, index) => segment === right[index]);
1967
+ const containsAddress = (parent, child) => parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
1968
+ const address = (value) => Array.isArray(value) && value.every((segment) => typeof segment === "string") ? Object.freeze(value.slice()) : void 0;
1969
+ const targetKey = (target) => {
1970
+ const at = target.at.join("\0");
1971
+ if (target.kind === "dictionary-key" || target.kind === "list-item") return `${target.kind}:${at}:${target.key}`;
1972
+ if (target.kind === "entity" || target.kind === "tree-node") return `${target.kind}:${at}:${target.id}`;
1973
+ return `${target.kind}:${at}`;
1974
+ };
1975
+ const freezeTarget = (target) => Object.freeze({
1976
+ ...target,
1977
+ at: Object.freeze(target.at.slice())
1978
+ });
1979
+ const add = (targets, target) => {
1980
+ const stable = freezeTarget(target);
1981
+ targets.set(targetKey(stable), stable);
1982
+ };
1983
+ const commandFootprint = (operations) => {
1984
+ const targets = /* @__PURE__ */ new Map();
1985
+ for (const operation of operations) switch (operation.type) {
1986
+ case "field.set":
1987
+ case "field.clear":
1988
+ case "variant.replace":
1989
+ add(targets, {
1990
+ kind: "value",
1991
+ at: operation.at
1992
+ });
1993
+ break;
1994
+ case "dict.replace":
1995
+ add(targets, {
1996
+ kind: "dictionary",
1997
+ at: operation.at
1998
+ });
1999
+ break;
2000
+ case "dict.set":
2001
+ case "dict.delete":
2002
+ add(targets, {
2003
+ kind: "dictionary-key",
2004
+ at: operation.at,
2005
+ key: operation.key
2006
+ });
2007
+ break;
2008
+ case "entity.create":
2009
+ add(targets, {
2010
+ kind: "collection",
2011
+ at: operation.at
2012
+ });
2013
+ for (const entry of operation.entries) add(targets, {
2014
+ kind: "entity",
2015
+ at: operation.at,
2016
+ id: entry.id
2017
+ });
2018
+ break;
2019
+ case "entity.remove":
2020
+ add(targets, {
2021
+ kind: "collection",
2022
+ at: operation.at
2023
+ });
2024
+ for (const id of operation.ids) add(targets, {
2025
+ kind: "entity",
2026
+ at: operation.at,
2027
+ id
2028
+ });
2029
+ break;
2030
+ case "entity.move":
2031
+ add(targets, {
2032
+ kind: "collection",
2033
+ at: operation.at
2034
+ });
2035
+ add(targets, {
2036
+ kind: "entity",
2037
+ at: operation.at,
2038
+ id: operation.id
2039
+ });
2040
+ break;
2041
+ case "list.insert":
2042
+ case "list.move":
2043
+ case "list.remove":
2044
+ add(targets, {
2045
+ kind: "list",
2046
+ at: operation.at
2047
+ });
2048
+ add(targets, {
2049
+ kind: "list-item",
2050
+ at: operation.at,
2051
+ key: operation.key
2052
+ });
2053
+ break;
2054
+ case "list.replace":
2055
+ add(targets, {
2056
+ kind: "list",
2057
+ at: operation.at
2058
+ });
2059
+ for (const key of operation.keys) add(targets, {
2060
+ kind: "list-item",
2061
+ at: operation.at,
2062
+ key
2063
+ });
2064
+ break;
2065
+ case "tree.insert":
2066
+ case "tree.move":
2067
+ case "tree.remove":
2068
+ case "tree.set":
2069
+ add(targets, {
2070
+ kind: "tree",
2071
+ at: operation.at
2072
+ });
2073
+ add(targets, {
2074
+ kind: "tree-node",
2075
+ at: operation.at,
2076
+ id: operation.treeNodeId
2077
+ });
2078
+ break;
2079
+ case "tree.replace":
2080
+ add(targets, {
2081
+ kind: "tree",
2082
+ at: operation.at
2083
+ });
2084
+ break;
2085
+ }
2086
+ return Object.freeze(Array.from(targets.values()));
2087
+ };
2088
+ const overlapsValueAndEntity = (value, entity) => containsAddress([...entity.at, entity.id], value.at) || containsAddress(value.at, [...entity.at, entity.id]);
2089
+ const targetsOverlap = (left, right) => {
2090
+ if (left.kind === "value" && right.kind === "value") return containsAddress(left.at, right.at) || containsAddress(right.at, left.at);
2091
+ if (left.kind === "value" && right.kind === "entity") return overlapsValueAndEntity(left, right);
2092
+ if (left.kind === "entity" && right.kind === "value") return overlapsValueAndEntity(right, left);
2093
+ if (left.kind === "dictionary" && right.kind === "dictionary") return sameAddress(left.at, right.at);
2094
+ if (left.kind === "dictionary" && right.kind === "dictionary-key") return sameAddress(left.at, right.at);
2095
+ if (left.kind === "dictionary-key" && right.kind === "dictionary") return sameAddress(left.at, right.at);
2096
+ if (left.kind === "dictionary-key" && right.kind === "dictionary-key") return sameAddress(left.at, right.at) && left.key === right.key;
2097
+ if (left.kind === "collection" && right.kind === "collection") return sameAddress(left.at, right.at);
2098
+ if (left.kind === "collection" && right.kind === "entity") return sameAddress(left.at, right.at);
2099
+ if (left.kind === "entity" && right.kind === "collection") return sameAddress(left.at, right.at);
2100
+ if (left.kind === "entity" && right.kind === "entity") return sameAddress(left.at, right.at) && left.id === right.id;
2101
+ if (left.kind === "list" && (right.kind === "list" || right.kind === "list-item")) return sameAddress(left.at, right.at);
2102
+ if (right.kind === "list" && left.kind === "list-item") return sameAddress(left.at, right.at);
2103
+ if (left.kind === "list-item" && right.kind === "list-item") return sameAddress(left.at, right.at) && left.key === right.key;
2104
+ if (left.kind === "tree" && (right.kind === "tree" || right.kind === "tree-node")) return sameAddress(left.at, right.at);
2105
+ if (right.kind === "tree" && left.kind === "tree-node") return sameAddress(left.at, right.at);
2106
+ if (left.kind === "tree-node" && right.kind === "tree-node") return sameAddress(left.at, right.at) && left.id === right.id;
2107
+ return false;
2108
+ };
2109
+ const footprintsOverlap = (left, right) => left.some((leftTarget) => right.some((rightTarget) => targetsOverlap(leftTarget, rightTarget)));
2110
+ const decodedTarget = (value) => {
2111
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
2112
+ const record = value;
2113
+ const at = address(record.at);
2114
+ if (!at || typeof record.kind !== "string") return void 0;
2115
+ switch (record.kind) {
2116
+ case "value":
2117
+ case "dictionary":
2118
+ case "collection":
2119
+ case "list":
2120
+ case "tree": return freezeTarget({
2121
+ kind: record.kind,
2122
+ at
2123
+ });
2124
+ case "dictionary-key":
2125
+ case "list-item": return typeof record.key === "string" ? freezeTarget({
2126
+ kind: record.kind,
2127
+ at,
2128
+ key: record.key
2129
+ }) : void 0;
2130
+ case "entity":
2131
+ case "tree-node": return typeof record.id === "string" ? freezeTarget({
2132
+ kind: record.kind,
2133
+ at,
2134
+ id: record.id
2135
+ }) : void 0;
2136
+ default: return;
2137
+ }
2138
+ };
2139
+ const decodeCommandFootprint = (value) => {
2140
+ if (!Array.isArray(value)) return void 0;
2141
+ const targets = /* @__PURE__ */ new Map();
2142
+ for (const entry of value) {
2143
+ const target = decodedTarget(entry);
2144
+ if (!target) return void 0;
2145
+ targets.set(targetKey(target), target);
2146
+ }
2147
+ return Object.freeze(Array.from(targets.values()));
2148
+ };
2149
+ //#endregion
1951
2150
  //#region core/src/runtime/notification.ts
1952
2151
  const ROOT_BUCKET = Symbol("document-root");
1953
2152
  const notifications = /* @__PURE__ */ new WeakMap();
@@ -1970,6 +2169,9 @@ const notificationOf = (runtime) => {
1970
2169
  if (!value) throw new Error("Unknown Doxum runtime.");
1971
2170
  return value;
1972
2171
  };
2172
+ const shareNotification = (source, target) => {
2173
+ notifications.set(target, notificationOf(source));
2174
+ };
1973
2175
  const registerProcessor = (runtime, processor) => {
1974
2176
  const notification = notificationOf(runtime);
1975
2177
  const entry = {
@@ -2131,10 +2333,47 @@ const createDocument = (input) => {
2131
2333
  let busy = false;
2132
2334
  let runtime;
2133
2335
  let notification;
2134
- const assertWritable = () => {
2336
+ const assertWritable = (intent) => {
2135
2337
  if (state.disposed) throw new DocumentDisposedError();
2136
2338
  if (busy) throw new DocumentReentrancyError();
2339
+ assertRuntimeWritable(runtime, intent);
2340
+ };
2341
+ const runTransaction = (session, run) => {
2342
+ let reports;
2343
+ let active = true;
2344
+ const transaction = {
2345
+ read: documentReader(input.schema, () => state.document, () => active),
2346
+ write: documentWriter(input.schema, (operation) => {
2347
+ if (!active) throw new Error("Document writer is no longer active.");
2348
+ const rejected = session.apply(operation);
2349
+ if (rejected) throw new RejectedUpdate([rejected]);
2350
+ }),
2351
+ reject: (diagnostic) => {
2352
+ throw new RejectedUpdate((Array.isArray(diagnostic) ? diagnostic : [diagnostic]).map((entry) => publishDiagnostic(entry)));
2353
+ },
2354
+ report: (diagnostic) => {
2355
+ if (!active) throw new Error("Document transaction is no longer active.");
2356
+ (reports ??= []).push(publishDiagnostic(diagnostic));
2357
+ }
2358
+ };
2359
+ try {
2360
+ const value = run(transaction);
2361
+ if (value !== null && typeof value === "object" && typeof value.then === "function") throw new TypeError("Document update callback must be synchronous.");
2362
+ return {
2363
+ value,
2364
+ reports: Object.freeze(reports ? reports.slice() : EMPTY)
2365
+ };
2366
+ } finally {
2367
+ active = false;
2368
+ }
2137
2369
  };
2370
+ const impactFor = (batch, kind) => createImpact({
2371
+ schema: input.schema,
2372
+ operations: batch.operations,
2373
+ paths: batch.paths,
2374
+ collections: batch.collections,
2375
+ reset: kind === "replace"
2376
+ });
2138
2377
  const history = createHistory({
2139
2378
  capacity: input.history === false ? 0 : Math.max(0, input.history?.capacity ?? 100),
2140
2379
  revision: () => revision,
@@ -2148,13 +2387,7 @@ const createDocument = (input) => {
2148
2387
  source,
2149
2388
  operations: batch.operations,
2150
2389
  inverse: batch.inverse,
2151
- impact: createImpact({
2152
- schema: input.schema,
2153
- operations: batch.operations,
2154
- paths: batch.paths,
2155
- collections: batch.collections,
2156
- reset: kind === "replace"
2157
- })
2390
+ impact: impactFor(batch, kind)
2158
2391
  });
2159
2392
  if (kind === "replace" || source === "remote") history.invalidate();
2160
2393
  else if (recordHistory && (source === "local" || source === "system")) history.record(batch.operations, batch.inverse);
@@ -2172,7 +2405,10 @@ const createDocument = (input) => {
2172
2405
  };
2173
2406
  };
2174
2407
  function applyBatch(operations, source, recordHistory) {
2175
- assertWritable();
2408
+ assertWritable({
2409
+ kind: "apply",
2410
+ source
2411
+ });
2176
2412
  const batch = mutateOperations(state.document, input.schema, operations, { copyPayload: source === "history" });
2177
2413
  if (batch.status === "rejected") return {
2178
2414
  status: "rejected",
@@ -2186,6 +2422,7 @@ const createDocument = (input) => {
2186
2422
  return publish(batch, source, "operations", recordHistory);
2187
2423
  }
2188
2424
  runtime = {
2425
+ schema: input.schema,
2189
2426
  address: {
2190
2427
  resolve: (address) => resolveAddress(input.schema, address, state.document),
2191
2428
  read: (address) => read(state.document, address),
@@ -2195,55 +2432,40 @@ const createDocument = (input) => {
2195
2432
  },
2196
2433
  revision: () => revision,
2197
2434
  update: (run, options) => {
2198
- assertWritable();
2435
+ const source = options?.source ?? "local";
2436
+ assertWritable({
2437
+ kind: "update",
2438
+ source
2439
+ });
2199
2440
  busy = true;
2200
2441
  const session = createMutationSession(state.document, input.schema);
2201
- let reports;
2202
- let active = true;
2203
- const transaction = {
2204
- read: documentReader(input.schema, () => state.document, () => active),
2205
- write: documentWriter(input.schema, (operation) => {
2206
- if (!active) throw new Error("Document writer is no longer active.");
2207
- const rejected = session.apply(operation);
2208
- if (rejected) throw new RejectedUpdate([rejected]);
2209
- }),
2210
- reject: (diagnostic) => {
2211
- throw new RejectedUpdate((Array.isArray(diagnostic) ? diagnostic : [diagnostic]).map((entry) => publishDiagnostic(entry)));
2212
- },
2213
- report: (diagnostic) => {
2214
- if (!active) throw new Error("Document transaction is no longer active.");
2215
- (reports ??= []).push(publishDiagnostic(diagnostic));
2216
- }
2217
- };
2442
+ let committed = false;
2218
2443
  try {
2219
- const value = run(transaction);
2220
- if (value !== null && typeof value === "object" && typeof value.then === "function") throw new TypeError("Document update callback must be synchronous.");
2221
- active = false;
2222
- busy = false;
2444
+ const { value, reports } = runTransaction(session, run);
2223
2445
  const batch = session.finish();
2224
2446
  if (batch.status === "unchanged") return {
2225
2447
  status: "unchanged",
2226
2448
  value,
2227
2449
  revision,
2228
- reports: Object.freeze(reports ?? EMPTY)
2450
+ reports
2229
2451
  };
2230
2452
  if (batch.status === "rejected") return {
2231
2453
  status: "rejected",
2232
2454
  issues: batch.issues,
2233
2455
  revision
2234
2456
  };
2235
- const result = publish(batch, options?.source ?? "local", "operations", options?.history ?? true);
2457
+ const result = publish(batch, source, "operations", options?.history ?? true);
2458
+ committed = true;
2236
2459
  return {
2237
2460
  status: "committed",
2238
2461
  value,
2239
2462
  commit: result.commit,
2240
- reports: Object.freeze(reports ?? EMPTY),
2463
+ reports,
2241
2464
  observerErrors: result.observerErrors
2242
2465
  };
2243
2466
  } catch (error) {
2244
- active = false;
2245
2467
  try {
2246
- session.rollback();
2468
+ if (!committed) session.rollback();
2247
2469
  } finally {
2248
2470
  busy = false;
2249
2471
  }
@@ -2254,13 +2476,64 @@ const createDocument = (input) => {
2254
2476
  };
2255
2477
  throw error;
2256
2478
  } finally {
2257
- active = false;
2479
+ busy = false;
2480
+ }
2481
+ },
2482
+ prepare: (run) => {
2483
+ assertWritable({ kind: "prepare" });
2484
+ busy = true;
2485
+ const session = createMutationSession(state.document, input.schema);
2486
+ try {
2487
+ const { value, reports } = runTransaction(session, run);
2488
+ const batch = session.finish();
2489
+ if (batch.status === "unchanged") {
2490
+ session.rollback();
2491
+ return {
2492
+ status: "unchanged",
2493
+ value,
2494
+ reports
2495
+ };
2496
+ }
2497
+ if (batch.status === "rejected") {
2498
+ session.rollback();
2499
+ return {
2500
+ status: "rejected",
2501
+ issues: batch.issues
2502
+ };
2503
+ }
2504
+ const impact = impactFor(batch, "operations");
2505
+ session.rollback();
2506
+ return {
2507
+ status: "prepared",
2508
+ value,
2509
+ operations: batch.operations,
2510
+ inverse: batch.inverse,
2511
+ impact,
2512
+ footprint: commandFootprint(batch.operations),
2513
+ reports
2514
+ };
2515
+ } catch (error) {
2516
+ try {
2517
+ session.rollback();
2518
+ } finally {
2519
+ busy = false;
2520
+ }
2521
+ if (error instanceof RejectedUpdate) return {
2522
+ status: "rejected",
2523
+ issues: error.issues
2524
+ };
2525
+ throw error;
2526
+ } finally {
2258
2527
  busy = false;
2259
2528
  }
2260
2529
  },
2261
2530
  apply: (operations, options) => applyBatch(operations, options?.source ?? "local", options?.history ?? true),
2262
2531
  replace: (document, options) => {
2263
- assertWritable();
2532
+ const source = options?.source ?? "system";
2533
+ assertWritable({
2534
+ kind: "replace",
2535
+ source
2536
+ });
2264
2537
  const invalidTree = invalidDocument(input.schema, document);
2265
2538
  if (invalidTree) return {
2266
2539
  status: "rejected",
@@ -2278,7 +2551,11 @@ const createDocument = (input) => {
2278
2551
  inverse: EMPTY,
2279
2552
  paths: EMPTY,
2280
2553
  collections: EMPTY
2281
- }, options?.source ?? "system", "replace", false);
2554
+ }, source, "replace", false);
2555
+ },
2556
+ snapshot: () => {
2557
+ if (state.disposed) throw new DocumentDisposedError();
2558
+ return cloneValue(state.document, "snapshot");
2282
2559
  },
2283
2560
  subscribe: ((targetOrListener, listener) => {
2284
2561
  if (state.disposed) throw new DocumentDisposedError();
@@ -2293,13 +2570,27 @@ const createDocument = (input) => {
2293
2570
  state.disposed = true;
2294
2571
  disposeNotification(notification);
2295
2572
  history.api.clear();
2573
+ disposeRuntimeDriver(runtime);
2296
2574
  }
2297
2575
  };
2298
2576
  bindRuntimeAccess(runtime, state);
2577
+ bindRuntimeDriver(runtime);
2299
2578
  notification = createNotification(runtime);
2300
2579
  return runtime;
2301
2580
  };
2302
2581
  //#endregion
2582
+ //#region core/src/runtime/readable.ts
2583
+ const asReadable = (runtime) => {
2584
+ const readable = {
2585
+ address: runtime.address,
2586
+ revision: runtime.revision,
2587
+ subscribe: runtime.subscribe
2588
+ };
2589
+ bindRuntimeAccess(readable, accessOf(runtime));
2590
+ shareNotification(runtime, readable);
2591
+ return Object.freeze(readable);
2592
+ };
2593
+ //#endregion
2303
2594
  //#region core/src/projection/select.ts
2304
2595
  const select = (runtime, selector) => readWith(runtime, selector);
2305
2596
  //#endregion
@@ -2684,6 +2975,6 @@ const createMaterializedView = (runtime, spec) => {
2684
2975
  return view;
2685
2976
  };
2686
2977
  //#endregion
2687
- export { DocumentDisposedError, DocumentReentrancyError, contains, createCollectionView, createDocument, createMaterializedView, debugKey, dict, field, list, map, object, optional, overlaps, read as readAddress, record, resolveAddress, schema, select, single, table, impact_target_exports as target, tree, variant };
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 };
2688
2979
 
2689
2980
  //# sourceMappingURL=index.js.map