doxum 0.1.3 → 0.1.5

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.
package/dist/index.cjs CHANGED
@@ -196,7 +196,11 @@ const writerFor = (node, inputAddress, sink) => {
196
196
  type: "dict.replace",
197
197
  at: address,
198
198
  value
199
- })
199
+ }),
200
+ ...node.optional ? { clear: () => emit(sink, {
201
+ type: "value.clear",
202
+ at: address
203
+ }) } : {}
200
204
  };
201
205
  if (node.kind === "list") return {
202
206
  insert: (value, anchor) => emit(sink, {
@@ -222,7 +226,11 @@ const writerFor = (node, inputAddress, sink) => {
222
226
  at: address,
223
227
  value,
224
228
  keys: value.map(node.keyOf)
225
- })
229
+ }),
230
+ ...node.optional ? { clear: () => emit(sink, {
231
+ type: "value.clear",
232
+ at: address
233
+ }) } : {}
226
234
  };
227
235
  if (node.kind === "tree") return {
228
236
  insert: (treeNodeId, value, parentId, index) => emit(sink, {
@@ -255,7 +263,11 @@ const writerFor = (node, inputAddress, sink) => {
255
263
  type: "tree.replace",
256
264
  at: address,
257
265
  value
258
- })
266
+ }),
267
+ ...node.optional ? { clear: () => emit(sink, {
268
+ type: "value.clear",
269
+ at: address
270
+ }) } : {}
259
271
  };
260
272
  if (node.kind === "table") {
261
273
  let items;
@@ -319,11 +331,17 @@ const writerFor = (node, inputAddress, sink) => {
319
331
  };
320
332
  }
321
333
  if (node.kind === "single") return writerFor(node.value, address, sink);
322
- if (node.kind === "variant") return { replace: (value) => emit(sink, {
323
- type: "variant.replace",
324
- at: address,
325
- value
326
- }) };
334
+ if (node.kind === "variant") return {
335
+ replace: (value) => emit(sink, {
336
+ type: "variant.replace",
337
+ at: address,
338
+ value
339
+ }),
340
+ ...node.optional ? { clear: () => emit(sink, {
341
+ type: "value.clear",
342
+ at: address
343
+ }) } : {}
344
+ };
327
345
  let children;
328
346
  return new Proxy({}, { get: (_target, property) => {
329
347
  if (typeof property !== "string" || !node.shape[property]) return void 0;
@@ -541,8 +559,11 @@ const rejected$3 = (operation, code, message) => ({
541
559
  issue: from(operation, code, message)
542
560
  });
543
561
  const executeList = (target, operation, copyPayload) => {
544
- if (!Array.isArray(target.value) || target.node.kind !== "list") return rejected$3(operation, "invalid-address", "List target is invalid.");
545
- const list = target.value;
562
+ if (target.node.kind !== "list") return rejected$3(operation, "invalid-address", "List target is invalid.");
563
+ const absent = target.value === void 0 && "optional" in target.node && target.node.optional === true;
564
+ if (!Array.isArray(target.value) && !absent) return rejected$3(operation, "invalid-address", "List target is invalid.");
565
+ if (absent && operation.type !== "list.replace") return rejected$3(operation, "invalid-address", "Only list replacement can initialize an optional list.");
566
+ const list = Array.isArray(target.value) ? target.value : [];
546
567
  const orderedKeys = keys(list, target.node.keyOf);
547
568
  const own = (value) => copyPayload ? require_ownership.ownPayload(value) : value;
548
569
  if (operation.type === "list.replace") {
@@ -553,18 +574,23 @@ const executeList = (target, operation, copyPayload) => {
553
574
  if (seen.has(key) || target.node.keyOf(operation.value[index]) !== key) return rejected$3(operation, "invalid-list-keys", "List replacement keys are invalid.");
554
575
  seen.add(key);
555
576
  }
556
- if (require_ownership.sameStructuralValue(list, operation.value)) return { status: "unchanged" };
557
- const keys = new Array(list.length);
558
- for (let index = 0; index < list.length; index += 1) keys[index] = target.node.keyOf(list[index]);
559
- const inverse = {
577
+ if (!absent && require_ownership.sameStructuralValue(list, operation.value)) return { status: "unchanged" };
578
+ const keys = list.map(target.node.keyOf);
579
+ const inverse = absent ? {
580
+ type: "value.clear",
581
+ at: operation.at
582
+ } : {
560
583
  type: "list.replace",
561
584
  at: operation.at,
562
585
  value: require_ownership.cloneValue(list, "inverse"),
563
586
  keys
564
587
  };
565
588
  const owned = own(operation.value);
566
- list.length = owned.length;
567
- for (let index = 0; index < owned.length; index += 1) list[index] = owned[index];
589
+ if (absent) target.parent[target.key] = owned;
590
+ else {
591
+ list.length = owned.length;
592
+ for (let index = 0; index < owned.length; index += 1) list[index] = owned[index];
593
+ }
568
594
  return {
569
595
  status: "changed",
570
596
  inverse: [inverse]
@@ -1024,16 +1050,39 @@ const outcome = (operation, value) => value.status === "rejected" ? {
1024
1050
  issue: from(operation, value.code, value.message)
1025
1051
  } : value;
1026
1052
  const executeTree = (root, target, operation, copyPayload) => {
1027
- if (!is(target.value)) return {
1053
+ if (target.node.kind !== "tree") return {
1028
1054
  status: "rejected",
1029
1055
  issue: from(operation, "invalid-address", "Tree target is invalid.")
1030
1056
  };
1031
1057
  const own = (value) => copyPayload ? require_ownership.ownPayload(value) : value;
1058
+ const absent = target.value === void 0 && "optional" in target.node && target.node.optional === true;
1032
1059
  if (operation.type === "tree.replace") {
1060
+ if (absent) {
1061
+ if (!validate(operation.value)) return {
1062
+ status: "rejected",
1063
+ issue: from(operation, "invalid-tree", "Tree replacement is malformed.")
1064
+ };
1065
+ target.parent[target.key] = own(operation.value);
1066
+ return {
1067
+ status: "changed",
1068
+ inverse: [{
1069
+ type: "value.clear",
1070
+ at: operation.at
1071
+ }]
1072
+ };
1073
+ }
1074
+ if (!is(target.value)) return {
1075
+ status: "rejected",
1076
+ issue: from(operation, "invalid-address", "Tree target is invalid.")
1077
+ };
1033
1078
  const result = replace(target.value, operation);
1034
1079
  if (result.status === "changed") require_dependency.set(root, operation.at, require_ownership.ownPayload(operation.value));
1035
1080
  return outcome(operation, result);
1036
1081
  }
1082
+ if (!is(target.value)) return {
1083
+ status: "rejected",
1084
+ issue: from(operation, "invalid-address", "Tree target is invalid.")
1085
+ };
1037
1086
  if (operation.type === "tree.insert") return outcome(operation, insert(target.value, operation, own(operation.value)));
1038
1087
  if (operation.type === "tree.set") return outcome(operation, set(target.value, operation, own(operation.value)));
1039
1088
  if (operation.type === "tree.remove") return outcome(operation, remove(target.value, operation));
@@ -1091,6 +1140,18 @@ const executeValue = (target, operation, copyPayload) => {
1091
1140
  inverse: [inverse]
1092
1141
  };
1093
1142
  }
1143
+ if (operation.type === "dict.replace" && target.value === void 0) {
1144
+ if (!("optional" in target.node) || !target.node.optional) return rejected$1(operation, "invalid-address", "Dictionary target is invalid.");
1145
+ const inverse = {
1146
+ type: "value.clear",
1147
+ at: operation.at
1148
+ };
1149
+ target.parent[target.key] = own(operation.value);
1150
+ return {
1151
+ status: "changed",
1152
+ inverse: [inverse]
1153
+ };
1154
+ }
1094
1155
  if (!require_ownership.isRecord(target.value)) return rejected$1(operation, "invalid-address", "Dictionary target is invalid.");
1095
1156
  if (operation.type === "dict.replace") {
1096
1157
  if (require_ownership.sameStructuralValue(target.value, operation.value)) return { status: "unchanged" };
@@ -1139,11 +1200,67 @@ const executeValue = (target, operation, copyPayload) => {
1139
1200
  };
1140
1201
  //#endregion
1141
1202
  //#region core/src/mutation/execute.ts
1203
+ const hasOwn$1 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
1204
+ const executeClear = (target, operation) => {
1205
+ if (!("optional" in target.node) || !target.node.optional) return {
1206
+ status: "rejected",
1207
+ issue: from(operation, "required-field", "Only optional values can be cleared.")
1208
+ };
1209
+ 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 {
1211
+ status: "rejected",
1212
+ issue: from(operation, "invalid-operation", "This optional value has no clear operation.")
1213
+ };
1214
+ const inverse = (() => {
1215
+ switch (target.node.kind) {
1216
+ case "field": return {
1217
+ type: "field.set",
1218
+ at: operation.at,
1219
+ value: target.value
1220
+ };
1221
+ case "variant": return {
1222
+ type: "variant.replace",
1223
+ at: operation.at,
1224
+ value: require_ownership.cloneValue(target.value, "inverse")
1225
+ };
1226
+ case "dict":
1227
+ case "record": return {
1228
+ type: "dict.replace",
1229
+ at: operation.at,
1230
+ value: require_ownership.cloneValue(target.value, "inverse")
1231
+ };
1232
+ case "list":
1233
+ if (!Array.isArray(target.value)) return {
1234
+ type: "list.replace",
1235
+ at: operation.at,
1236
+ value: [],
1237
+ keys: []
1238
+ };
1239
+ return {
1240
+ type: "list.replace",
1241
+ at: operation.at,
1242
+ value: require_ownership.cloneValue(target.value, "inverse"),
1243
+ keys: target.value.map(target.node.keyOf)
1244
+ };
1245
+ case "tree": return {
1246
+ type: "tree.replace",
1247
+ at: operation.at,
1248
+ value: require_ownership.cloneValue(target.value, "inverse")
1249
+ };
1250
+ }
1251
+ })();
1252
+ delete target.parent[target.key];
1253
+ return {
1254
+ status: "changed",
1255
+ inverse: [inverse]
1256
+ };
1257
+ };
1142
1258
  const invalidTarget = (operation) => ({
1143
1259
  status: "rejected",
1144
1260
  issue: from(operation, "invalid-address", `Operation '${operation.type}' does not match the schema address.`)
1145
1261
  });
1146
1262
  const execute = (root, operation, target, copyPayload) => {
1263
+ if (operation.type === "value.clear") return !target ? invalidTarget(operation) : executeClear(target, operation);
1147
1264
  if (operation.type === "field.set" || operation.type === "field.clear") return !target || target.node.kind !== "field" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1148
1265
  if (operation.type === "variant.replace") return !target || target.node.kind !== "variant" ? invalidTarget(operation) : executeValue(target, operation, copyPayload);
1149
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);
@@ -1533,11 +1650,12 @@ const createChangeJournal = (root) => {
1533
1650
  const recordValue = (resolved, operation, inverse) => {
1534
1651
  if (find("value", operation.at, resolved.addressHash)) return;
1535
1652
  const first = inverse[0];
1536
- if (first?.type !== "field.set" && first?.type !== "field.clear") return inverseMismatch(operation);
1653
+ if (first?.type !== "field.set" && first?.type !== "field.clear" && first?.type !== "value.clear" && !("value" in (first ?? {}))) return inverseMismatch(operation);
1654
+ const before = first.type === "field.clear" || first.type === "value.clear" ? absent : "value" in first ? present(first.value) : inverseMismatch(operation);
1537
1655
  add({
1538
1656
  ...base(resolved, operation),
1539
1657
  kind: "value",
1540
- before: first.type === "field.set" ? present(first.value) : absent
1658
+ before
1541
1659
  });
1542
1660
  };
1543
1661
  const recordVariant = (resolved, operation, inverse) => {
@@ -1580,6 +1698,18 @@ const createChangeJournal = (root) => {
1580
1698
  if (!first) return inverseMismatch(operation);
1581
1699
  let subject = find("list", operation.at, resolved.addressHash);
1582
1700
  if (!subject) {
1701
+ if (first.type === "value.clear") {
1702
+ add({
1703
+ ...base(resolved, operation),
1704
+ kind: "list",
1705
+ node: resolved.node,
1706
+ state: {
1707
+ mode: "whole",
1708
+ value: void 0
1709
+ }
1710
+ });
1711
+ return;
1712
+ }
1583
1713
  if (first.type === "list.replace") {
1584
1714
  add({
1585
1715
  ...base(resolved, operation),
@@ -1667,6 +1797,22 @@ const createChangeJournal = (root) => {
1667
1797
  };
1668
1798
  const recordTree = (resolved, operation, inverse) => {
1669
1799
  const current = require_dependency.read(root, operation.at);
1800
+ if (inverse.length === 1 && inverse[0].type === "value.clear" && operation.type === "tree.replace") {
1801
+ const subject = find("tree", operation.at, resolved.addressHash);
1802
+ if (subject) subject.state = {
1803
+ mode: "whole",
1804
+ value: void 0
1805
+ };
1806
+ else add({
1807
+ ...base(resolved, operation),
1808
+ kind: "tree",
1809
+ state: {
1810
+ mode: "whole",
1811
+ value: void 0
1812
+ }
1813
+ });
1814
+ return;
1815
+ }
1670
1816
  if (!is(current)) return inverseMismatch(operation);
1671
1817
  const inverses = inverse.filter(treeOperation);
1672
1818
  if (inverses.length === 0 || inverses.length !== inverse.length) return inverseMismatch(operation);
@@ -1683,7 +1829,7 @@ const createChangeJournal = (root) => {
1683
1829
  record: (resolved, operation, inverse) => {
1684
1830
  require_ownership.profile.batch.journalRecord();
1685
1831
  if (ownerOf(operation.at)) return;
1686
- if (operation.type === "field.set" || operation.type === "field.clear") {
1832
+ if (operation.type === "field.set" || operation.type === "field.clear" || operation.type === "value.clear") {
1687
1833
  recordValue(resolved, operation, inverse);
1688
1834
  return;
1689
1835
  }
@@ -1692,7 +1838,8 @@ const createChangeJournal = (root) => {
1692
1838
  return;
1693
1839
  }
1694
1840
  if (operation.type === "dict.set" || operation.type === "dict.delete" || operation.type === "dict.replace") {
1695
- recordDict(resolved, operation, inverse);
1841
+ if (operation.type === "dict.replace" && inverse[0]?.type === "value.clear") recordValue(resolved, operation, inverse);
1842
+ else recordDict(resolved, operation, inverse);
1696
1843
  return;
1697
1844
  }
1698
1845
  if (operation.type === "entity.create" || operation.type === "entity.remove" || operation.type === "entity.move") {
@@ -1793,7 +1940,8 @@ const decode = (input) => {
1793
1940
  switch (input.type) {
1794
1941
  case "field.set":
1795
1942
  case "variant.replace": return has(input, "value") ? decoded(input) : rejected("invalid-operation", "Operation value is required.");
1796
- case "field.clear": return decoded(input);
1943
+ case "field.clear":
1944
+ case "value.clear": return decoded(input);
1797
1945
  case "dict.set": return typeof input.key === "string" && has(input, "value") ? decoded(input) : rejected("invalid-operation", "Dictionary set requires a string key and value.");
1798
1946
  case "dict.delete": return typeof input.key === "string" ? decoded(input) : rejected("invalid-operation", "Dictionary delete requires a string key.");
1799
1947
  case "dict.replace": return require_ownership.isRecord(input.value) ? decoded(input) : rejected("invalid-operation", "Dictionary replacement must be an object.");
@@ -1986,6 +2134,7 @@ const commandFootprint = (operations) => {
1986
2134
  for (const operation of operations) switch (operation.type) {
1987
2135
  case "field.set":
1988
2136
  case "field.clear":
2137
+ case "value.clear":
1989
2138
  case "variant.replace":
1990
2139
  add(targets, {
1991
2140
  kind: "value",