@xnetjs/data 0.12.0 → 1.0.0

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 (154) hide show
  1. package/dist/account-ZD3HMRY6.js +12 -0
  2. package/dist/{account-ledger-R2GAFJL6.js → account-ledger-ZHBNVREV.js} +3 -3
  3. package/dist/auth/index.d.ts +15 -5
  4. package/dist/auth/index.js +1 -1
  5. package/dist/budget-FTGZEXX6.js +12 -0
  6. package/dist/canvas-NQBTRNLS.js +10 -0
  7. package/dist/channel-474QAZGP.js +12 -0
  8. package/dist/chat-message-LGLCITLE.js +10 -0
  9. package/dist/{chunk-VQ7JHB67.js → chunk-22FKOLMX.js} +2 -2
  10. package/dist/{chunk-LYSWLCOI.js → chunk-26HTTVJN.js} +2 -2
  11. package/dist/{chunk-PH3PVAR6.js → chunk-3PV6QTKF.js} +2 -2
  12. package/dist/{chunk-KQUALW4O.js → chunk-4KJ4NAMC.js} +1 -1
  13. package/dist/{chunk-U64CW73O.js → chunk-52DR23IW.js} +2 -2
  14. package/dist/{chunk-SVNGSZZA.js → chunk-57UZYV3L.js} +2 -2
  15. package/dist/{chunk-ZMZSBJBC.js → chunk-5B47M2IG.js} +175 -175
  16. package/dist/{chunk-QVQVKZ3U.js → chunk-5UJMY3AH.js} +6 -5
  17. package/dist/{chunk-3J3HILXO.js → chunk-7PCMTV6J.js} +2 -2
  18. package/dist/{chunk-YSUIXD2Y.js → chunk-C4CQH7UD.js} +2 -2
  19. package/dist/{chunk-JFMLCFZJ.js → chunk-CARPMPXV.js} +285 -229
  20. package/dist/{chunk-PNESGUH5.js → chunk-D4EVJN2P.js} +2 -2
  21. package/dist/{chunk-RL64OJJ5.js → chunk-D4LUUZYD.js} +36 -9
  22. package/dist/{chunk-W7EBL7ZU.js → chunk-DP4Q2HMO.js} +2 -2
  23. package/dist/{chunk-5EXHNXPL.js → chunk-DPJ4T2LQ.js} +2 -2
  24. package/dist/{chunk-BQLQSWKZ.js → chunk-EN6XTVLZ.js} +2 -2
  25. package/dist/{chunk-Q67VT5XL.js → chunk-ENA6NEFY.js} +5 -5
  26. package/dist/{chunk-TORDOOJ5.js → chunk-EYRZ6ZUB.js} +1 -1
  27. package/dist/{chunk-NI4FHG2K.js → chunk-EZTBWWMS.js} +2 -2
  28. package/dist/{chunk-UQM3G5A2.js → chunk-FWCM2TKJ.js} +2 -2
  29. package/dist/{chunk-OSAWNZVM.js → chunk-GCTONBF6.js} +1 -1
  30. package/dist/{chunk-2ZNJWIQG.js → chunk-HPUXHSKR.js} +2 -2
  31. package/dist/{chunk-ZPZ7XDA6.js → chunk-HTZTBXKL.js} +2 -2
  32. package/dist/{chunk-MCKP4F72.js → chunk-I7JMGCV6.js} +26 -5
  33. package/dist/{chunk-OGJCRNGE.js → chunk-IA23CLGV.js} +2 -2
  34. package/dist/{chunk-YEFKQYKA.js → chunk-IKGUBSZP.js} +2 -2
  35. package/dist/{chunk-XMSKJ5PV.js → chunk-IKI5FBNO.js} +2 -2
  36. package/dist/{chunk-OCMSAKWV.js → chunk-JA56EQJO.js} +15 -3
  37. package/dist/{chunk-TCTZW4A6.js → chunk-JLUFPUIL.js} +2 -2
  38. package/dist/{chunk-WEPK7SZF.js → chunk-KTPSQJ5L.js} +2 -2
  39. package/dist/{chunk-T5AZAOG5.js → chunk-LK5ZIAIE.js} +1 -1
  40. package/dist/{chunk-XROI44ZP.js → chunk-M4KGY2PC.js} +2 -2
  41. package/dist/{chunk-QJW5LDP4.js → chunk-NSMSOIEW.js} +2 -2
  42. package/dist/{chunk-FB63TZVI.js → chunk-OF6455OV.js} +2 -2
  43. package/dist/{chunk-6OMG4I7M.js → chunk-OIWW5TG3.js} +2 -2
  44. package/dist/{chunk-6KLGA6IR.js → chunk-Q6VTTPG4.js} +2 -2
  45. package/dist/{chunk-J4ANIXIV.js → chunk-QDLWTOII.js} +2 -2
  46. package/dist/{chunk-EBNMV2VO.js → chunk-RRXJNZX5.js} +1 -1
  47. package/dist/{chunk-FVEACSKE.js → chunk-RXBLWQQI.js} +2 -2
  48. package/dist/{chunk-L7NRPLE5.js → chunk-S6JWYPVW.js} +2 -2
  49. package/dist/{chunk-JDFNMFOZ.js → chunk-SOXXBQ4P.js} +2 -2
  50. package/dist/{chunk-4EYKGTSQ.js → chunk-T45PWXNA.js} +2 -2
  51. package/dist/{chunk-MD553LDL.js → chunk-TRPXLCND.js} +2 -2
  52. package/dist/{chunk-ZCOFZY5M.js → chunk-UUQVNDBG.js} +2 -2
  53. package/dist/{chunk-53F4PRNC.js → chunk-W3QOEPUN.js} +2 -2
  54. package/dist/{chunk-S6U6TCMN.js → chunk-WC7EDJHC.js} +2 -2
  55. package/dist/{chunk-KQAT4XBL.js → chunk-WWQO7QKV.js} +2 -2
  56. package/dist/{chunk-BY5O6LNC.js → chunk-XJBDESCX.js} +1 -1
  57. package/dist/{chunk-ZZ6TWKGS.js → chunk-XQDXQ7WG.js} +2 -2
  58. package/dist/{chunk-MBTUO3ZL.js → chunk-YD2ZPFT7.js} +2 -2
  59. package/dist/{chunk-ZLU4Y3O2.js → chunk-YHO7GNZK.js} +2 -2
  60. package/dist/{chunk-Y3S5SRVM.js → chunk-YSD4NSCE.js} +2 -2
  61. package/dist/{chunk-IJ44LR7N.js → chunk-ZKJXGQCR.js} +2 -2
  62. package/dist/{chunk-GU6THOAB.js → chunk-ZKMWJNDQ.js} +2 -2
  63. package/dist/{chunk-OPPHF3TF.js → chunk-ZKVRGKLM.js} +3 -3
  64. package/dist/{clone-CbmoWwu4.d.ts → clone-Bj0nQyMc.d.ts} +2 -2
  65. package/dist/comment-IAZJHG6G.js +10 -0
  66. package/dist/{crm-L5RYAAF3.js → crm-AFJSSPHK.js} +5 -5
  67. package/dist/dashboard-2DLFZM2J.js +10 -0
  68. package/dist/database/index.d.ts +3 -3
  69. package/dist/database/index.js +10 -10
  70. package/dist/database-5M7ZA33U.js +10 -0
  71. package/dist/database-field-IFUWOK7X.js +10 -0
  72. package/dist/database-row-C52KCOPB.js +10 -0
  73. package/dist/database-select-option-AEAPKXEE.js +10 -0
  74. package/dist/database-view-GUGJHS7J.js +10 -0
  75. package/dist/experiment-JXHENWXV.js +10 -0
  76. package/dist/{external-item-P2E5X7BZ.js → external-item-ZVD33IXV.js} +5 -5
  77. package/dist/external-reference-LGFNVEKC.js +8 -0
  78. package/dist/feed-CLEHVA6Q.js +12 -0
  79. package/dist/feed-item-QXRIE6FU.js +12 -0
  80. package/dist/{folder-6KM3KYD5.js → folder-NU5TJFOT.js} +3 -3
  81. package/dist/{game-5AK5XWBR.js → game-7WW2JX6O.js} +5 -5
  82. package/dist/grant-W772T6FE.js +8 -0
  83. package/dist/{grant-expiration-cleaner-Bm1fSG42.d.ts → grant-expiration-cleaner-BrMW0ffX.d.ts} +7 -2
  84. package/dist/import-batch-ZRU4FY6Y.js +12 -0
  85. package/dist/{inbox-state-E5NSVOGP.js → inbox-state-IO5C7HVE.js} +3 -3
  86. package/dist/index.d.ts +7 -6
  87. package/dist/index.js +316 -314
  88. package/dist/map-BZAVGCR6.js +10 -0
  89. package/dist/media-asset-VHFL65HI.js +8 -0
  90. package/dist/{meeting-LMUYTZBF.js → meeting-PVNKBGTI.js} +5 -5
  91. package/dist/{memory-PNRTMBZO.js → memory-5PD3XCKB.js} +3 -3
  92. package/dist/metric-6IGGFJMB.js +10 -0
  93. package/dist/milestone-MZAZCWGR.js +12 -0
  94. package/dist/{moderation-FFHETMRB.js → moderation-CXQB77CP.js} +3 -3
  95. package/dist/observation-O3DLKVIH.js +10 -0
  96. package/dist/page-UE5NPYEF.js +10 -0
  97. package/dist/posting-5VZZMOKS.js +12 -0
  98. package/dist/{profile-XBPZQA3C.js → profile-USKJ6PN7.js} +3 -3
  99. package/dist/project-UHGGEFLP.js +10 -0
  100. package/dist/{query-ast-Bvg0Wolx.d.ts → query-ast-ESRn8U2H.d.ts} +1 -1
  101. package/dist/reaction-KD3G75UH.js +10 -0
  102. package/dist/{registry-_49rC6IG.d.ts → registry-BGD80QCd.d.ts} +1 -1
  103. package/dist/saved-view-AZTUINZA.js +10 -0
  104. package/dist/schema/index.d.ts +5 -5
  105. package/dist/schema/index.js +70 -70
  106. package/dist/{schema-extension-LNRF2YRD.js → schema-extension-DV5D7JCQ.js} +5 -5
  107. package/dist/{space-IJU24PBH.js → space-THD2MWHR.js} +6 -6
  108. package/dist/{space-membership-DEUMM7NV.js → space-membership-M3QFMONE.js} +3 -3
  109. package/dist/store/index.d.ts +13 -4
  110. package/dist/store/index.js +4 -4
  111. package/dist/{store-DEWnip2G.d.ts → store-B0fb6WdJ.d.ts} +1 -1
  112. package/dist/{system-ILHCZOVO.js → system-LFIMY3QQ.js} +3 -3
  113. package/dist/{tag-SKCHM3U5.js → tag-W7ZYPXHK.js} +3 -3
  114. package/dist/{task-7AOBP7WC.js → task-DHTPD66H.js} +5 -5
  115. package/dist/task-view-J2WRE62C.js +10 -0
  116. package/dist/transaction-ANNDXCJM.js +12 -0
  117. package/dist/transcription-DFTQ4FQ4.js +12 -0
  118. package/dist/{types-BB8-SLEa.d.ts → types-B5rrBwRI.d.ts} +17 -0
  119. package/dist/user-widget-5IRVORF3.js +8 -0
  120. package/dist/workspace-RC4V4IF3.js +8 -0
  121. package/package.json +7 -7
  122. package/dist/account-MWNCH4GJ.js +0 -12
  123. package/dist/budget-NN7P35XP.js +0 -12
  124. package/dist/canvas-6WYBNH53.js +0 -10
  125. package/dist/channel-AT3LQF4A.js +0 -12
  126. package/dist/chat-message-47LETTYF.js +0 -10
  127. package/dist/comment-NIMFL4XB.js +0 -10
  128. package/dist/dashboard-JMKTTRA7.js +0 -10
  129. package/dist/database-GIXSPZJW.js +0 -10
  130. package/dist/database-field-3EPAMZUR.js +0 -10
  131. package/dist/database-row-5MEIOJ52.js +0 -10
  132. package/dist/database-select-option-BBBJFLLH.js +0 -10
  133. package/dist/database-view-DM7YCTZT.js +0 -10
  134. package/dist/experiment-DOVOZ34Y.js +0 -10
  135. package/dist/external-reference-7BSFF6SA.js +0 -8
  136. package/dist/feed-2IXHHP6O.js +0 -12
  137. package/dist/feed-item-DPE6LN7I.js +0 -12
  138. package/dist/grant-WTQSNT5A.js +0 -8
  139. package/dist/import-batch-IXBJLQPJ.js +0 -12
  140. package/dist/map-RG5Y3CPC.js +0 -10
  141. package/dist/media-asset-QTCGLIZA.js +0 -8
  142. package/dist/metric-65SVOX5C.js +0 -10
  143. package/dist/milestone-XEO6ZGDG.js +0 -12
  144. package/dist/observation-2Y3QKUXM.js +0 -10
  145. package/dist/page-2V27JGKJ.js +0 -10
  146. package/dist/posting-GBOSZCMZ.js +0 -12
  147. package/dist/project-CQL5LN26.js +0 -10
  148. package/dist/reaction-T2DTJBBV.js +0 -10
  149. package/dist/saved-view-4R2DBCL3.js +0 -10
  150. package/dist/task-view-NUGKELJP.js +0 -10
  151. package/dist/transaction-ZDSEGDIR.js +0 -12
  152. package/dist/transcription-PSCLDAGX.js +0 -12
  153. package/dist/user-widget-RLY2N46A.js +0 -8
  154. package/dist/workspace-WDKSQMOK.js +0 -8
@@ -1,18 +1,18 @@
1
+ import {
2
+ SYSTEM_SCHEMA_BASE_IRIS,
3
+ isSystemNamespaceResource,
4
+ isSystemSchemaIri
5
+ } from "./chunk-XJBDESCX.js";
1
6
  import {
2
7
  applyNodeQueryDescriptor,
3
8
  getNodeQuerySearchTokens,
4
9
  withoutNodeQueryMaterializedView,
5
10
  withoutNodeQueryPagination
6
11
  } from "./chunk-S5RP5RKY.js";
7
- import {
8
- SYSTEM_SCHEMA_BASE_IRIS,
9
- isSystemNamespaceResource,
10
- isSystemSchemaIri
11
- } from "./chunk-BY5O6LNC.js";
12
12
  import {
13
13
  createNodeId,
14
14
  getBaseSchemaIRI
15
- } from "./chunk-EBNMV2VO.js";
15
+ } from "./chunk-RRXJNZX5.js";
16
16
 
17
17
  // src/store/query-ast.ts
18
18
  var QUERY_AST_VERSION = 1;
@@ -887,187 +887,23 @@ function evaluateQueryASTPlannerGate(value) {
887
887
  }
888
888
 
889
889
  // src/store/store.ts
890
- import { compareChangeApplicationOrder, lwwWins } from "@xnetjs/core";
891
-
892
- // src/store/transaction-executor.ts
893
- async function executeTransactionOperations(host, input) {
894
- const results = [];
895
- const changes = [];
896
- const events = [];
897
- for (let i = 0; i < input.operations.length; i++) {
898
- const op = input.operations[i];
899
- let change;
900
- let result = null;
901
- let previousNode = null;
902
- switch (op.type) {
903
- case "create": {
904
- const id = op.options.id ?? createNodeId();
905
- const payload = {
906
- nodeId: id,
907
- schemaId: op.options.schemaId,
908
- properties: op.options.properties
909
- };
910
- change = await host.createBatchedChange(
911
- "node-change",
912
- payload,
913
- input.lamport,
914
- input.now,
915
- input.batchId,
916
- i,
917
- input.batchSize,
918
- input.storage
919
- );
920
- await host.applyChange(change, input.storage);
921
- result = await input.storage.getNode(id);
922
- await host.persistEncryptedNodeSnapshot(result, input.storage);
923
- break;
924
- }
925
- case "update": {
926
- const existing = host.cloneNodeState(await input.storage.getNode(op.nodeId));
927
- if (!existing) {
928
- throw new Error(`Node not found: ${op.nodeId}`);
929
- }
930
- previousNode = existing;
931
- const payload = {
932
- nodeId: op.nodeId,
933
- properties: op.options.properties
934
- };
935
- change = await host.createBatchedChange(
936
- "node-change",
937
- payload,
938
- input.lamport,
939
- input.now,
940
- input.batchId,
941
- i,
942
- input.batchSize,
943
- input.storage
944
- );
945
- await host.applyChange(change, input.storage);
946
- result = await input.storage.getNode(op.nodeId);
947
- await host.persistEncryptedNodeSnapshot(result, input.storage);
948
- break;
949
- }
950
- case "delete": {
951
- const existing = host.cloneNodeState(await input.storage.getNode(op.nodeId));
952
- if (!existing) {
953
- throw new Error(`Node not found: ${op.nodeId}`);
954
- }
955
- previousNode = existing;
956
- const payload = {
957
- nodeId: op.nodeId,
958
- properties: {},
959
- deleted: true
960
- };
961
- change = await host.createBatchedChange(
962
- "node-change",
963
- payload,
964
- input.lamport,
965
- input.now,
966
- input.batchId,
967
- i,
968
- input.batchSize,
969
- input.storage
970
- );
971
- await host.applyChange(change, input.storage);
972
- result = null;
973
- break;
974
- }
975
- case "restore": {
976
- const existing = host.cloneNodeState(await input.storage.getNode(op.nodeId));
977
- if (!existing) {
978
- throw new Error(`Node not found: ${op.nodeId}`);
979
- }
980
- previousNode = existing;
981
- const payload = {
982
- nodeId: op.nodeId,
983
- properties: {},
984
- deleted: false
985
- };
986
- change = await host.createBatchedChange(
987
- "node-change",
988
- payload,
989
- input.lamport,
990
- input.now,
991
- input.batchId,
992
- i,
993
- input.batchSize,
994
- input.storage
995
- );
996
- await host.applyChange(change, input.storage);
997
- result = await input.storage.getNode(op.nodeId);
998
- await host.persistEncryptedNodeSnapshot(result, input.storage);
999
- break;
1000
- }
1001
- }
1002
- changes.push(change);
1003
- results.push(result);
1004
- events.push({ change, result, previousNode });
1005
- }
1006
- return { results, changes, events };
1007
- }
1008
- async function executeTransactionOperationsFast(host, input) {
1009
- const planned = input.operations.map((op) => ({
1010
- op,
1011
- id: op.type === "create" ? op.options.id ?? createNodeId() : op.nodeId
1012
- }));
1013
- const preflight = await host.getBatchPreflight(
1014
- planned.map((entry) => entry.id),
1015
- host.storage
1016
- );
1017
- const nodesById = host.cloneNodeMap(preflight.nodesById);
1018
- const lastChanges = new Map(preflight.lastChangesByNodeId);
1019
- const results = [];
1020
- const changes = [];
1021
- const events = [];
1022
- const affectedSchemaIds = /* @__PURE__ */ new Set();
1023
- for (let i = 0; i < planned.length; i++) {
1024
- const { op, id } = planned[i];
1025
- const existing = nodesById.get(id) ?? null;
1026
- if (op.type !== "create" && !existing) {
1027
- throw new Error(`Node not found: ${id}`);
1028
- }
1029
- const payload = op.type === "create" ? { nodeId: id, schemaId: op.options.schemaId, properties: op.options.properties } : op.type === "update" ? { nodeId: id, properties: op.options.properties } : { nodeId: id, properties: {}, deleted: op.type === "delete" };
1030
- const change = await host.createBatchedChangeWithParentHash(
1031
- "node-change",
1032
- payload,
1033
- lastChanges.get(id)?.hash ?? null,
1034
- input.lamport,
1035
- input.now,
1036
- input.batchId,
1037
- i,
1038
- input.batchSize
1039
- );
1040
- const schemaId = existing?.schemaId ?? (op.type === "create" ? op.options.schemaId : null);
1041
- if (!schemaId) {
1042
- throw new Error(`First change for node ${id} must include schemaId`);
1043
- }
1044
- const node = host.materializeNodeChange(
1045
- change,
1046
- existing ?? host.createInitialNodeFromChange(change, schemaId)
1047
- );
1048
- nodesById.set(id, node);
1049
- lastChanges.set(id, change);
1050
- affectedSchemaIds.add(schemaId);
1051
- changes.push(change);
1052
- const result = op.type === "delete" ? null : node;
1053
- results.push(result);
1054
- events.push({ change, result, previousNode: existing });
1055
- }
1056
- const touchedIds = Array.from(new Set(planned.map((entry) => entry.id)));
1057
- await host.storage.applyNodeBatch({
1058
- batchId: input.batchId,
1059
- nodes: touchedIds.flatMap((id) => {
1060
- const node = nodesById.get(id);
1061
- return node ? [node] : [];
1062
- }),
1063
- changes,
1064
- lastLamportTime: host.clockTime(),
1065
- affectedSchemaIds: Array.from(affectedSchemaIds),
1066
- indexMode: "touched",
1067
- indexProperties: true
1068
- });
1069
- return { results, changes, events };
1070
- }
890
+ import {
891
+ LWW_TIEBREAK_KEY_VERSION,
892
+ compareChangeApplicationOrder,
893
+ computeLwwTiebreakKey,
894
+ lwwWins
895
+ } from "@xnetjs/core";
896
+ import { base64ToBytes, bytesToBase64 } from "@xnetjs/crypto";
897
+ import { parseDID } from "@xnetjs/identity";
898
+ import {
899
+ createLamportClock,
900
+ tick,
901
+ receive,
902
+ signChange,
903
+ createUnsignedChange,
904
+ createBatchId
905
+ } from "@xnetjs/sync";
906
+ import { verifyChange, verifyChangeHash } from "@xnetjs/sync";
1071
907
 
1072
908
  // src/store/batch-write-orchestrator.ts
1073
909
  var elapsedMs = (startedAt) => Math.max(0, Date.now() - startedAt);
@@ -1159,19 +995,6 @@ async function executeDeterministicNodeImport(host, input) {
1159
995
  };
1160
996
  }
1161
997
 
1162
- // src/store/store.ts
1163
- import { base64ToBytes, bytesToBase64 } from "@xnetjs/crypto";
1164
- import { parseDID } from "@xnetjs/identity";
1165
- import {
1166
- createLamportClock,
1167
- tick,
1168
- receive,
1169
- signChange,
1170
- createUnsignedChange,
1171
- createBatchId
1172
- } from "@xnetjs/sync";
1173
- import { verifyChange, verifyChangeHash } from "@xnetjs/sync";
1174
-
1175
998
  // src/store/permission-error.ts
1176
999
  import { TaggedError } from "@xnetjs/core";
1177
1000
  var PermissionError = class extends TaggedError {
@@ -1364,6 +1187,186 @@ function findSchemaIdForNode(operations, nodeId, tempIds) {
1364
1187
  return void 0;
1365
1188
  }
1366
1189
 
1190
+ // src/store/transaction-executor.ts
1191
+ async function executeTransactionOperations(host, input) {
1192
+ const results = [];
1193
+ const changes = [];
1194
+ const events = [];
1195
+ for (let i = 0; i < input.operations.length; i++) {
1196
+ const op = input.operations[i];
1197
+ let change;
1198
+ let result = null;
1199
+ let previousNode = null;
1200
+ switch (op.type) {
1201
+ case "create": {
1202
+ const id = op.options.id ?? createNodeId();
1203
+ const payload = {
1204
+ nodeId: id,
1205
+ schemaId: op.options.schemaId,
1206
+ properties: op.options.properties
1207
+ };
1208
+ change = await host.createBatchedChange(
1209
+ "node-change",
1210
+ payload,
1211
+ input.lamport,
1212
+ input.now,
1213
+ input.batchId,
1214
+ i,
1215
+ input.batchSize,
1216
+ input.storage
1217
+ );
1218
+ await host.applyChange(change, input.storage);
1219
+ result = await input.storage.getNode(id);
1220
+ await host.persistEncryptedNodeSnapshot(result, input.storage);
1221
+ break;
1222
+ }
1223
+ case "update": {
1224
+ const existing = host.cloneNodeState(await input.storage.getNode(op.nodeId));
1225
+ if (!existing) {
1226
+ throw new Error(`Node not found: ${op.nodeId}`);
1227
+ }
1228
+ previousNode = existing;
1229
+ const payload = {
1230
+ nodeId: op.nodeId,
1231
+ properties: op.options.properties
1232
+ };
1233
+ change = await host.createBatchedChange(
1234
+ "node-change",
1235
+ payload,
1236
+ input.lamport,
1237
+ input.now,
1238
+ input.batchId,
1239
+ i,
1240
+ input.batchSize,
1241
+ input.storage
1242
+ );
1243
+ await host.applyChange(change, input.storage);
1244
+ result = await input.storage.getNode(op.nodeId);
1245
+ await host.persistEncryptedNodeSnapshot(result, input.storage);
1246
+ break;
1247
+ }
1248
+ case "delete": {
1249
+ const existing = host.cloneNodeState(await input.storage.getNode(op.nodeId));
1250
+ if (!existing) {
1251
+ throw new Error(`Node not found: ${op.nodeId}`);
1252
+ }
1253
+ previousNode = existing;
1254
+ const payload = {
1255
+ nodeId: op.nodeId,
1256
+ properties: {},
1257
+ deleted: true
1258
+ };
1259
+ change = await host.createBatchedChange(
1260
+ "node-change",
1261
+ payload,
1262
+ input.lamport,
1263
+ input.now,
1264
+ input.batchId,
1265
+ i,
1266
+ input.batchSize,
1267
+ input.storage
1268
+ );
1269
+ await host.applyChange(change, input.storage);
1270
+ result = null;
1271
+ break;
1272
+ }
1273
+ case "restore": {
1274
+ const existing = host.cloneNodeState(await input.storage.getNode(op.nodeId));
1275
+ if (!existing) {
1276
+ throw new Error(`Node not found: ${op.nodeId}`);
1277
+ }
1278
+ previousNode = existing;
1279
+ const payload = {
1280
+ nodeId: op.nodeId,
1281
+ properties: {},
1282
+ deleted: false
1283
+ };
1284
+ change = await host.createBatchedChange(
1285
+ "node-change",
1286
+ payload,
1287
+ input.lamport,
1288
+ input.now,
1289
+ input.batchId,
1290
+ i,
1291
+ input.batchSize,
1292
+ input.storage
1293
+ );
1294
+ await host.applyChange(change, input.storage);
1295
+ result = await input.storage.getNode(op.nodeId);
1296
+ await host.persistEncryptedNodeSnapshot(result, input.storage);
1297
+ break;
1298
+ }
1299
+ }
1300
+ changes.push(change);
1301
+ results.push(result);
1302
+ events.push({ change, result, previousNode });
1303
+ }
1304
+ return { results, changes, events };
1305
+ }
1306
+ async function executeTransactionOperationsFast(host, input) {
1307
+ const planned = input.operations.map((op) => ({
1308
+ op,
1309
+ id: op.type === "create" ? op.options.id ?? createNodeId() : op.nodeId
1310
+ }));
1311
+ const preflight = await host.getBatchPreflight(
1312
+ planned.map((entry) => entry.id),
1313
+ host.storage
1314
+ );
1315
+ const nodesById = host.cloneNodeMap(preflight.nodesById);
1316
+ const lastChanges = new Map(preflight.lastChangesByNodeId);
1317
+ const results = [];
1318
+ const changes = [];
1319
+ const events = [];
1320
+ const affectedSchemaIds = /* @__PURE__ */ new Set();
1321
+ for (let i = 0; i < planned.length; i++) {
1322
+ const { op, id } = planned[i];
1323
+ const existing = nodesById.get(id) ?? null;
1324
+ if (op.type !== "create" && !existing) {
1325
+ throw new Error(`Node not found: ${id}`);
1326
+ }
1327
+ const payload = op.type === "create" ? { nodeId: id, schemaId: op.options.schemaId, properties: op.options.properties } : op.type === "update" ? { nodeId: id, properties: op.options.properties } : { nodeId: id, properties: {}, deleted: op.type === "delete" };
1328
+ const change = await host.createBatchedChangeWithParentHash(
1329
+ "node-change",
1330
+ payload,
1331
+ lastChanges.get(id)?.hash ?? null,
1332
+ input.lamport,
1333
+ input.now,
1334
+ input.batchId,
1335
+ i,
1336
+ input.batchSize
1337
+ );
1338
+ const schemaId = existing?.schemaId ?? (op.type === "create" ? op.options.schemaId : null);
1339
+ if (!schemaId) {
1340
+ throw new Error(`First change for node ${id} must include schemaId`);
1341
+ }
1342
+ const node = host.materializeNodeChange(
1343
+ change,
1344
+ existing ?? host.createInitialNodeFromChange(change, schemaId)
1345
+ );
1346
+ nodesById.set(id, node);
1347
+ lastChanges.set(id, change);
1348
+ affectedSchemaIds.add(schemaId);
1349
+ changes.push(change);
1350
+ const result = op.type === "delete" ? null : node;
1351
+ results.push(result);
1352
+ events.push({ change, result, previousNode: existing });
1353
+ }
1354
+ const touchedIds = Array.from(new Set(planned.map((entry) => entry.id)));
1355
+ await host.storage.applyNodeBatch({
1356
+ batchId: input.batchId,
1357
+ nodes: touchedIds.flatMap((id) => {
1358
+ const node = nodesById.get(id);
1359
+ return node ? [node] : [];
1360
+ }),
1361
+ changes,
1362
+ lastLamportTime: host.clockTime(),
1363
+ affectedSchemaIds: Array.from(affectedSchemaIds),
1364
+ indexMode: "touched",
1365
+ indexProperties: true
1366
+ });
1367
+ return { results, changes, events };
1368
+ }
1369
+
1367
1370
  // src/store/store.ts
1368
1371
  var MAX_CONFLICTS = 200;
1369
1372
  var ENCRYPTED_NODE_MARKER = "xnet:node-encrypted:v1";
@@ -1463,7 +1466,7 @@ var NodeStore = class {
1463
1466
  schemaId: options.schemaId,
1464
1467
  properties: options.properties
1465
1468
  },
1466
- authAction: "write",
1469
+ authAction: "create",
1467
1470
  requireExisting: false,
1468
1471
  createSchemaId: options.schemaId
1469
1472
  });
@@ -1477,7 +1480,7 @@ var NodeStore = class {
1477
1480
  }
1478
1481
  await this.assertAuthorized({
1479
1482
  subject: this.authorDID,
1480
- action: "write",
1483
+ action: "create",
1481
1484
  nodeId: id,
1482
1485
  patch: options.properties,
1483
1486
  node: {
@@ -1611,7 +1614,7 @@ var NodeStore = class {
1611
1614
  nodeId: id,
1612
1615
  properties: options.properties
1613
1616
  },
1614
- authAction: "write",
1617
+ authAction: "update",
1615
1618
  requireExisting: true
1616
1619
  });
1617
1620
  this.emit(change2, node2, previousNode, false);
@@ -1635,7 +1638,7 @@ var NodeStore = class {
1635
1638
  }
1636
1639
  await this.assertAuthorized({
1637
1640
  subject: this.authorDID,
1638
- action: "write",
1641
+ action: "update",
1639
1642
  nodeId: id,
1640
1643
  patch: options.properties,
1641
1644
  node: {
@@ -1758,7 +1761,7 @@ var NodeStore = class {
1758
1761
  properties: {},
1759
1762
  deleted: false
1760
1763
  },
1761
- authAction: "write",
1764
+ authAction: "update",
1762
1765
  requireExisting: true
1763
1766
  });
1764
1767
  this.emit(change2, node2, previousNode, false);
@@ -1771,7 +1774,7 @@ var NodeStore = class {
1771
1774
  }
1772
1775
  await this.assertAuthorized({
1773
1776
  subject: this.authorDID,
1774
- action: "write",
1777
+ action: "update",
1775
1778
  nodeId: id,
1776
1779
  node: {
1777
1780
  schemaId: existing.schemaId,
@@ -2472,12 +2475,21 @@ var NodeStore = class {
2472
2475
  );
2473
2476
  }
2474
2477
  if (this.authEvaluator) {
2475
- const action = this.inferActionFromChange(change);
2478
+ const stored = await this.storage.getNode(change.payload.nodeId);
2479
+ const action = this.inferActionFromChange(change, stored);
2476
2480
  const decision = await this.authEvaluator.can({
2477
2481
  subject: change.authorDID,
2478
2482
  action,
2479
2483
  nodeId: change.payload.nodeId,
2480
- patch: action === "write" ? change.payload.properties : void 0
2484
+ patch: action === "create" || action === "update" ? change.payload.properties : void 0,
2485
+ // A remote create has no stored node to evaluate against — build the
2486
+ // draft from the payload so container-relation roles resolve
2487
+ // (exploration 0304). Update/delete load the stored node as before.
2488
+ node: action === "create" && change.payload.schemaId ? {
2489
+ schemaId: change.payload.schemaId,
2490
+ createdBy: change.authorDID,
2491
+ properties: change.payload.properties
2492
+ } : void 0
2481
2493
  });
2482
2494
  if (!decision.allowed) {
2483
2495
  this.telemetry?.reportSecurityEvent("data.unauthorized_remote_change", "medium");
@@ -2922,7 +2934,11 @@ var NodeStore = class {
2922
2934
  const newTs = {
2923
2935
  lamport: change.lamport,
2924
2936
  author: change.authorDID,
2925
- wallTime: change.wallTime
2937
+ wallTime: change.wallTime,
2938
+ // Grinding-resistant final tiebreak (exploration 0305): only v4+ changes
2939
+ // carry a key, so a v4-vs-legacy comparison degrades to the author DID
2940
+ // and mixed fleets still agree.
2941
+ ...(change.protocolVersion ?? 0) >= LWW_TIEBREAK_KEY_VERSION ? { tiebreakKey: computeLwwTiebreakKey(change.authorDID, key, value) } : {}
2926
2942
  };
2927
2943
  const incomingWins = !existingTs || this.shouldReplace(existingTs, newTs);
2928
2944
  const target = isUnknownProperty ? node._unknown ??= {} : node.properties;
@@ -3057,7 +3073,7 @@ var NodeStore = class {
3057
3073
  if (operation.type === "create") {
3058
3074
  await this.assertAuthorized({
3059
3075
  subject: this.authorDID,
3060
- action: "write",
3076
+ action: "create",
3061
3077
  nodeId: operation.options.id ?? "",
3062
3078
  node: {
3063
3079
  schemaId: operation.options.schemaId,
@@ -3077,17 +3093,20 @@ var NodeStore = class {
3077
3093
  }
3078
3094
  await this.assertAuthorized({
3079
3095
  subject: this.authorDID,
3080
- action: "write",
3096
+ action: "update",
3081
3097
  nodeId: operation.nodeId,
3082
3098
  patch: operation.type === "update" ? operation.options.properties : void 0
3083
3099
  });
3084
3100
  }
3085
3101
  }
3086
- inferActionFromChange(change) {
3102
+ inferActionFromChange(change, stored) {
3087
3103
  if (change.payload.deleted === true) {
3088
3104
  return "delete";
3089
3105
  }
3090
- return "write";
3106
+ if (!stored && change.payload.schemaId) {
3107
+ return "create";
3108
+ }
3109
+ return "update";
3091
3110
  }
3092
3111
  shouldRecomputeRecipients(schemaId, patch) {
3093
3112
  const authRelevant = this.authRelevantPropertyLookup?.(schemaId);
@@ -3520,7 +3539,10 @@ function hydrateJoinedRows(rows) {
3520
3539
  node.timestamps[row.property_key] = {
3521
3540
  lamport: row.lamport_time ?? 0,
3522
3541
  author: row.updated_by ?? "",
3523
- wallTime: row.prop_updated_at ?? 0
3542
+ wallTime: row.prop_updated_at ?? 0,
3543
+ // Round-trip the stored key so the in-memory LWW comparison matches the
3544
+ // SQL guard exactly (0305); NULL legacy rows keep the author tiebreak.
3545
+ ...row.tiebreak_key != null ? { tiebreakKey: row.tiebreak_key } : {}
3524
3546
  };
3525
3547
  if ((row.prop_updated_at ?? 0) >= node.updatedAt) {
3526
3548
  node.updatedBy = row.updated_by ?? node.createdBy;
@@ -3540,7 +3562,10 @@ function hydrateAggregatedRows(rows) {
3540
3562
  timestamps[key] = {
3541
3563
  lamport: entry.l ?? 0,
3542
3564
  author: entry.b ?? "",
3543
- wallTime: entry.w ?? 0
3565
+ wallTime: entry.w ?? 0,
3566
+ // Round-trip the stored tiebreak key (0305) so aggregated hydrate
3567
+ // matches the SQL guard; NULL/absent → author tiebreak.
3568
+ ...entry.k != null ? { tiebreakKey: entry.k } : {}
3544
3569
  };
3545
3570
  if ((entry.w ?? 0) >= row.updated_at) {
3546
3571
  node.updatedBy = entry.b ?? row.created_by;
@@ -3571,6 +3596,7 @@ function buildHydrateChunkQuery(ids) {
3571
3596
  p.lamport_time,
3572
3597
  p.updated_by,
3573
3598
  p.updated_at AS prop_updated_at,
3599
+ p.tiebreak_key,
3574
3600
  wanted.ordinal
3575
3601
  FROM wanted
3576
3602
  JOIN nodes n ON n.id = wanted.id
@@ -3599,7 +3625,7 @@ function buildAggregatedHydrateChunkQuery(ids) {
3599
3625
  FILTER (WHERE p.property_key IS NOT NULL) AS props_json,
3600
3626
  json_group_object(
3601
3627
  p.property_key,
3602
- json_object('l', p.lamport_time, 'b', p.updated_by, 'w', p.updated_at)
3628
+ json_object('l', p.lamport_time, 'b', p.updated_by, 'w', p.updated_at, 'k', p.tiebreak_key)
3603
3629
  ) FILTER (WHERE p.property_key IS NOT NULL) AS meta_json
3604
3630
  FROM wanted
3605
3631
  JOIN nodes n ON n.id = wanted.id
@@ -4522,7 +4548,12 @@ var NODE_PROPERTIES_LWW_GUARD = lwwUpdateGuardSql({
4522
4548
  table: "node_properties",
4523
4549
  lamportColumn: "lamport_time",
4524
4550
  wallTimeColumn: "updated_at",
4525
- authorColumn: "updated_by"
4551
+ authorColumn: "updated_by",
4552
+ // Grinding-resistant final tiebreak (exploration 0305): larger key wins when
4553
+ // both rows carry one (v4+), else the author DID. The key is precomputed in
4554
+ // application code and stored, so SQL only compares the opaque hex — byte
4555
+ // identical to `shouldReplace` in ./store.ts, no user-defined function.
4556
+ tiebreakKeyColumn: "tiebreak_key"
4526
4557
  });
4527
4558
  var DEFAULT_ADAPTIVE_INDEXING = {
4528
4559
  enabled: false,
@@ -4622,6 +4653,7 @@ var SQLiteNodeStorageAdapter = class {
4622
4653
  nodeReadAuthorizer;
4623
4654
  /** One-time guard: ensure the `auth_fingerprint` column exists on upgraded DBs. */
4624
4655
  materializationColumnsReady = false;
4656
+ nodePropertyColumnsReady = false;
4625
4657
  /**
4626
4658
  * Descriptor→SQL compilation lives in `query-compiler.ts` (exploration
4627
4659
  * 0276). Flags are read per-compile so the compiler never captures stale
@@ -4966,6 +4998,7 @@ var SQLiteNodeStorageAdapter = class {
4966
4998
  * Used by importNodes to avoid nested transactions.
4967
4999
  */
4968
5000
  async _setNodeInternal(node, options) {
5001
+ await this.ensureNodePropertyColumns();
4969
5002
  await this.db.run(
4970
5003
  `INSERT INTO nodes (id, schema_id, created_at, updated_at, created_by, deleted_at)
4971
5004
  VALUES (?, ?, ?, ?, ?, ?)
@@ -4987,13 +5020,14 @@ var SQLiteNodeStorageAdapter = class {
4987
5020
  const timestamp = node.timestamps[key];
4988
5021
  if (!timestamp) continue;
4989
5022
  await this.db.run(
4990
- `INSERT INTO node_properties (node_id, property_key, value, lamport_time, updated_by, updated_at)
4991
- VALUES (?, ?, ?, ?, ?, ?)
5023
+ `INSERT INTO node_properties (node_id, property_key, value, lamport_time, updated_by, updated_at, tiebreak_key)
5024
+ VALUES (?, ?, ?, ?, ?, ?, ?)
4992
5025
  ON CONFLICT(node_id, property_key) DO UPDATE SET
4993
5026
  value = excluded.value,
4994
5027
  lamport_time = excluded.lamport_time,
4995
5028
  updated_by = excluded.updated_by,
4996
- updated_at = excluded.updated_at
5029
+ updated_at = excluded.updated_at,
5030
+ tiebreak_key = excluded.tiebreak_key
4997
5031
  WHERE ${NODE_PROPERTIES_LWW_GUARD}`,
4998
5032
  [
4999
5033
  node.id,
@@ -5001,7 +5035,8 @@ var SQLiteNodeStorageAdapter = class {
5001
5035
  this.serializeValue(value),
5002
5036
  timestamp.lamport,
5003
5037
  timestamp.author,
5004
- timestamp.wallTime
5038
+ timestamp.wallTime,
5039
+ timestamp.tiebreakKey ?? null
5005
5040
  ]
5006
5041
  );
5007
5042
  }
@@ -5063,7 +5098,7 @@ var SQLiteNodeStorageAdapter = class {
5063
5098
  )
5064
5099
  SELECT
5065
5100
  ln.id, ln.schema_id, ln.created_at, ln.updated_at, ln.created_by, ln.deleted_at,
5066
- p.property_key, p.value, p.lamport_time, p.updated_by, p.updated_at as prop_updated_at,
5101
+ p.property_key, p.value, p.lamport_time, p.updated_by, p.updated_at as prop_updated_at, p.tiebreak_key,
5067
5102
  NULL as ordinal
5068
5103
  FROM limited_nodes ln
5069
5104
  LEFT JOIN node_properties p ON ln.id = p.node_id
@@ -5075,7 +5110,7 @@ var SQLiteNodeStorageAdapter = class {
5075
5110
  sql = `
5076
5111
  SELECT
5077
5112
  n.id, n.schema_id, n.created_at, n.updated_at, n.created_by, n.deleted_at,
5078
- p.property_key, p.value, p.lamport_time, p.updated_by, p.updated_at as prop_updated_at,
5113
+ p.property_key, p.value, p.lamport_time, p.updated_by, p.updated_at as prop_updated_at, p.tiebreak_key,
5079
5114
  NULL as ordinal
5080
5115
  FROM nodes n
5081
5116
  LEFT JOIN node_properties p ON n.id = p.node_id
@@ -5588,6 +5623,7 @@ var SQLiteNodeStorageAdapter = class {
5588
5623
  if (!this.db.transactionBatch) {
5589
5624
  throw new Error("SQLite transactionBatch is not available.");
5590
5625
  }
5626
+ await this.ensureNodePropertyColumns();
5591
5627
  const operations = [];
5592
5628
  const indexProperties = options?.indexProperties ?? true;
5593
5629
  const hasFullTextSearch = await this.fullTextIndexing.hasTable();
@@ -5638,16 +5674,17 @@ var SQLiteNodeStorageAdapter = class {
5638
5674
  const timestamp = node.timestamps[key];
5639
5675
  if (!timestamp) continue;
5640
5676
  operations.push({
5641
- // Full LWW ordering triple — keep in lockstep with the setNode
5642
- // upsert above and `shouldReplace` in ./store.ts (0272).
5677
+ // Full LWW ordering chain — keep in lockstep with the setNode
5678
+ // upsert above and `shouldReplace` in ./store.ts (0272/0305).
5643
5679
  sql: `INSERT INTO node_properties
5644
- (node_id, property_key, value, lamport_time, updated_by, updated_at)
5645
- VALUES (?, ?, ?, ?, ?, ?)
5680
+ (node_id, property_key, value, lamport_time, updated_by, updated_at, tiebreak_key)
5681
+ VALUES (?, ?, ?, ?, ?, ?, ?)
5646
5682
  ON CONFLICT(node_id, property_key) DO UPDATE SET
5647
5683
  value = excluded.value,
5648
5684
  lamport_time = excluded.lamport_time,
5649
5685
  updated_by = excluded.updated_by,
5650
- updated_at = excluded.updated_at
5686
+ updated_at = excluded.updated_at,
5687
+ tiebreak_key = excluded.tiebreak_key
5651
5688
  WHERE ${NODE_PROPERTIES_LWW_GUARD}`,
5652
5689
  params: [
5653
5690
  node.id,
@@ -5655,7 +5692,8 @@ var SQLiteNodeStorageAdapter = class {
5655
5692
  this.serializeValue(value),
5656
5693
  timestamp.lamport,
5657
5694
  timestamp.author,
5658
- timestamp.wallTime
5695
+ timestamp.wallTime,
5696
+ timestamp.tiebreakKey ?? null
5659
5697
  ]
5660
5698
  });
5661
5699
  }
@@ -5907,6 +5945,24 @@ var SQLiteNodeStorageAdapter = class {
5907
5945
  }
5908
5946
  this.materializationColumnsReady = true;
5909
5947
  }
5948
+ /**
5949
+ * Ensure the `node_properties.tiebreak_key` column exists (exploration 0305).
5950
+ * Fresh databases get it from the DDL; databases created before schema v8 need
5951
+ * this in-place add so the grinding-resistant LWW guard has a column to
5952
+ * compare. Idempotent, memoized, and non-fatal on races — a legacy NULL key
5953
+ * just falls back to the author-DID tiebreak.
5954
+ */
5955
+ async ensureNodePropertyColumns() {
5956
+ if (this.nodePropertyColumnsReady) return;
5957
+ try {
5958
+ const columns = await this.db.query(`PRAGMA table_info(node_properties)`);
5959
+ if (!columns.some((column) => column.name === "tiebreak_key")) {
5960
+ await this.db.run("ALTER TABLE node_properties ADD COLUMN tiebreak_key TEXT");
5961
+ }
5962
+ } catch {
5963
+ }
5964
+ this.nodePropertyColumnsReady = true;
5965
+ }
5910
5966
  setNodeReadAuthorizer(authorizer) {
5911
5967
  this.nodeReadAuthorizer = authorizer;
5912
5968
  }