@xnetjs/data 0.11.1 → 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.
- package/dist/account-ZD3HMRY6.js +12 -0
- package/dist/{account-ledger-R2GAFJL6.js → account-ledger-ZHBNVREV.js} +3 -3
- package/dist/auth/index.d.ts +15 -5
- package/dist/auth/index.js +1 -1
- package/dist/budget-FTGZEXX6.js +12 -0
- package/dist/canvas-NQBTRNLS.js +10 -0
- package/dist/channel-474QAZGP.js +12 -0
- package/dist/chat-message-LGLCITLE.js +10 -0
- package/dist/{chunk-VQ7JHB67.js → chunk-22FKOLMX.js} +2 -2
- package/dist/{chunk-LYSWLCOI.js → chunk-26HTTVJN.js} +2 -2
- package/dist/{chunk-PH3PVAR6.js → chunk-3PV6QTKF.js} +2 -2
- package/dist/{chunk-KQUALW4O.js → chunk-4KJ4NAMC.js} +1 -1
- package/dist/{chunk-U64CW73O.js → chunk-52DR23IW.js} +2 -2
- package/dist/{chunk-SVNGSZZA.js → chunk-57UZYV3L.js} +2 -2
- package/dist/{chunk-3L73ZALR.js → chunk-5B47M2IG.js} +196 -201
- package/dist/{chunk-QVQVKZ3U.js → chunk-5UJMY3AH.js} +6 -5
- package/dist/{chunk-3J3HILXO.js → chunk-7PCMTV6J.js} +2 -2
- package/dist/{chunk-YSUIXD2Y.js → chunk-C4CQH7UD.js} +2 -2
- package/dist/{chunk-QN334DU7.js → chunk-CARPMPXV.js} +301 -245
- package/dist/{chunk-PNESGUH5.js → chunk-D4EVJN2P.js} +2 -2
- package/dist/{chunk-RL64OJJ5.js → chunk-D4LUUZYD.js} +36 -9
- package/dist/{chunk-W7EBL7ZU.js → chunk-DP4Q2HMO.js} +2 -2
- package/dist/{chunk-5EXHNXPL.js → chunk-DPJ4T2LQ.js} +2 -2
- package/dist/{chunk-BQLQSWKZ.js → chunk-EN6XTVLZ.js} +2 -2
- package/dist/{chunk-Q67VT5XL.js → chunk-ENA6NEFY.js} +5 -5
- package/dist/{chunk-TORDOOJ5.js → chunk-EYRZ6ZUB.js} +1 -1
- package/dist/{chunk-NI4FHG2K.js → chunk-EZTBWWMS.js} +2 -2
- package/dist/{chunk-UQM3G5A2.js → chunk-FWCM2TKJ.js} +2 -2
- package/dist/{chunk-OSAWNZVM.js → chunk-GCTONBF6.js} +1 -1
- package/dist/{chunk-2ZNJWIQG.js → chunk-HPUXHSKR.js} +2 -2
- package/dist/{chunk-ZPZ7XDA6.js → chunk-HTZTBXKL.js} +2 -2
- package/dist/{chunk-MCKP4F72.js → chunk-I7JMGCV6.js} +26 -5
- package/dist/{chunk-OGJCRNGE.js → chunk-IA23CLGV.js} +2 -2
- package/dist/{chunk-YEFKQYKA.js → chunk-IKGUBSZP.js} +2 -2
- package/dist/{chunk-XMSKJ5PV.js → chunk-IKI5FBNO.js} +2 -2
- package/dist/{chunk-OCMSAKWV.js → chunk-JA56EQJO.js} +15 -3
- package/dist/{chunk-TCTZW4A6.js → chunk-JLUFPUIL.js} +2 -2
- package/dist/{chunk-WEPK7SZF.js → chunk-KTPSQJ5L.js} +2 -2
- package/dist/{chunk-T5AZAOG5.js → chunk-LK5ZIAIE.js} +1 -1
- package/dist/{chunk-XROI44ZP.js → chunk-M4KGY2PC.js} +2 -2
- package/dist/{chunk-QJW5LDP4.js → chunk-NSMSOIEW.js} +2 -2
- package/dist/{chunk-FB63TZVI.js → chunk-OF6455OV.js} +2 -2
- package/dist/{chunk-6OMG4I7M.js → chunk-OIWW5TG3.js} +2 -2
- package/dist/{chunk-6KLGA6IR.js → chunk-Q6VTTPG4.js} +2 -2
- package/dist/{chunk-J4ANIXIV.js → chunk-QDLWTOII.js} +2 -2
- package/dist/{chunk-EBNMV2VO.js → chunk-RRXJNZX5.js} +1 -1
- package/dist/{chunk-FVEACSKE.js → chunk-RXBLWQQI.js} +2 -2
- package/dist/{chunk-L7NRPLE5.js → chunk-S6JWYPVW.js} +2 -2
- package/dist/{chunk-JDFNMFOZ.js → chunk-SOXXBQ4P.js} +2 -2
- package/dist/{chunk-4EYKGTSQ.js → chunk-T45PWXNA.js} +2 -2
- package/dist/{chunk-MD553LDL.js → chunk-TRPXLCND.js} +2 -2
- package/dist/{chunk-ZCOFZY5M.js → chunk-UUQVNDBG.js} +2 -2
- package/dist/{chunk-53F4PRNC.js → chunk-W3QOEPUN.js} +2 -2
- package/dist/{chunk-S6U6TCMN.js → chunk-WC7EDJHC.js} +2 -2
- package/dist/{chunk-KQAT4XBL.js → chunk-WWQO7QKV.js} +2 -2
- package/dist/{chunk-BY5O6LNC.js → chunk-XJBDESCX.js} +1 -1
- package/dist/{chunk-ZZ6TWKGS.js → chunk-XQDXQ7WG.js} +2 -2
- package/dist/{chunk-MBTUO3ZL.js → chunk-YD2ZPFT7.js} +2 -2
- package/dist/{chunk-ZLU4Y3O2.js → chunk-YHO7GNZK.js} +2 -2
- package/dist/{chunk-Y3S5SRVM.js → chunk-YSD4NSCE.js} +2 -2
- package/dist/{chunk-IJ44LR7N.js → chunk-ZKJXGQCR.js} +2 -2
- package/dist/{chunk-GU6THOAB.js → chunk-ZKMWJNDQ.js} +2 -2
- package/dist/{chunk-OPPHF3TF.js → chunk-ZKVRGKLM.js} +3 -3
- package/dist/{clone-CbmoWwu4.d.ts → clone-Bj0nQyMc.d.ts} +2 -2
- package/dist/comment-IAZJHG6G.js +10 -0
- package/dist/{crm-L5RYAAF3.js → crm-AFJSSPHK.js} +5 -5
- package/dist/dashboard-2DLFZM2J.js +10 -0
- package/dist/database/index.d.ts +3 -3
- package/dist/database/index.js +10 -10
- package/dist/database-5M7ZA33U.js +10 -0
- package/dist/database-field-IFUWOK7X.js +10 -0
- package/dist/database-row-C52KCOPB.js +10 -0
- package/dist/database-select-option-AEAPKXEE.js +10 -0
- package/dist/database-view-GUGJHS7J.js +10 -0
- package/dist/experiment-JXHENWXV.js +10 -0
- package/dist/{external-item-P2E5X7BZ.js → external-item-ZVD33IXV.js} +5 -5
- package/dist/external-reference-LGFNVEKC.js +8 -0
- package/dist/feed-CLEHVA6Q.js +12 -0
- package/dist/feed-item-QXRIE6FU.js +12 -0
- package/dist/{folder-6KM3KYD5.js → folder-NU5TJFOT.js} +3 -3
- package/dist/{game-5AK5XWBR.js → game-7WW2JX6O.js} +5 -5
- package/dist/grant-W772T6FE.js +8 -0
- package/dist/{grant-expiration-cleaner-Bm1fSG42.d.ts → grant-expiration-cleaner-BrMW0ffX.d.ts} +7 -2
- package/dist/import-batch-ZRU4FY6Y.js +12 -0
- package/dist/{inbox-state-E5NSVOGP.js → inbox-state-IO5C7HVE.js} +3 -3
- package/dist/index.d.ts +7 -6
- package/dist/index.js +316 -314
- package/dist/map-BZAVGCR6.js +10 -0
- package/dist/media-asset-VHFL65HI.js +8 -0
- package/dist/{meeting-LMUYTZBF.js → meeting-PVNKBGTI.js} +5 -5
- package/dist/{memory-PNRTMBZO.js → memory-5PD3XCKB.js} +3 -3
- package/dist/metric-6IGGFJMB.js +10 -0
- package/dist/milestone-MZAZCWGR.js +12 -0
- package/dist/{moderation-FFHETMRB.js → moderation-CXQB77CP.js} +3 -3
- package/dist/observation-O3DLKVIH.js +10 -0
- package/dist/page-UE5NPYEF.js +10 -0
- package/dist/posting-5VZZMOKS.js +12 -0
- package/dist/{profile-XBPZQA3C.js → profile-USKJ6PN7.js} +3 -3
- package/dist/project-UHGGEFLP.js +10 -0
- package/dist/{query-ast-Bvg0Wolx.d.ts → query-ast-ESRn8U2H.d.ts} +1 -1
- package/dist/reaction-KD3G75UH.js +10 -0
- package/dist/{registry-_49rC6IG.d.ts → registry-BGD80QCd.d.ts} +1 -1
- package/dist/saved-view-AZTUINZA.js +10 -0
- package/dist/schema/index.d.ts +5 -5
- package/dist/schema/index.js +70 -70
- package/dist/{schema-extension-LNRF2YRD.js → schema-extension-DV5D7JCQ.js} +5 -5
- package/dist/{space-IJU24PBH.js → space-THD2MWHR.js} +6 -6
- package/dist/{space-membership-DEUMM7NV.js → space-membership-M3QFMONE.js} +3 -3
- package/dist/store/index.d.ts +16 -6
- package/dist/store/index.js +4 -4
- package/dist/{store-DEWnip2G.d.ts → store-B0fb6WdJ.d.ts} +1 -1
- package/dist/{system-ILHCZOVO.js → system-LFIMY3QQ.js} +3 -3
- package/dist/{tag-SKCHM3U5.js → tag-W7ZYPXHK.js} +3 -3
- package/dist/{task-7AOBP7WC.js → task-DHTPD66H.js} +5 -5
- package/dist/task-view-J2WRE62C.js +10 -0
- package/dist/transaction-ANNDXCJM.js +12 -0
- package/dist/transcription-DFTQ4FQ4.js +12 -0
- package/dist/{types-BB8-SLEa.d.ts → types-B5rrBwRI.d.ts} +17 -0
- package/dist/user-widget-5IRVORF3.js +8 -0
- package/dist/workspace-RC4V4IF3.js +8 -0
- package/package.json +7 -7
- package/dist/account-MWNCH4GJ.js +0 -12
- package/dist/budget-NN7P35XP.js +0 -12
- package/dist/canvas-6WYBNH53.js +0 -10
- package/dist/channel-AT3LQF4A.js +0 -12
- package/dist/chat-message-47LETTYF.js +0 -10
- package/dist/comment-NIMFL4XB.js +0 -10
- package/dist/dashboard-JMKTTRA7.js +0 -10
- package/dist/database-GIXSPZJW.js +0 -10
- package/dist/database-field-3EPAMZUR.js +0 -10
- package/dist/database-row-5MEIOJ52.js +0 -10
- package/dist/database-select-option-BBBJFLLH.js +0 -10
- package/dist/database-view-DM7YCTZT.js +0 -10
- package/dist/experiment-DOVOZ34Y.js +0 -10
- package/dist/external-reference-7BSFF6SA.js +0 -8
- package/dist/feed-2IXHHP6O.js +0 -12
- package/dist/feed-item-DPE6LN7I.js +0 -12
- package/dist/grant-WTQSNT5A.js +0 -8
- package/dist/import-batch-IXBJLQPJ.js +0 -12
- package/dist/map-RG5Y3CPC.js +0 -10
- package/dist/media-asset-QTCGLIZA.js +0 -8
- package/dist/metric-65SVOX5C.js +0 -10
- package/dist/milestone-XEO6ZGDG.js +0 -12
- package/dist/observation-2Y3QKUXM.js +0 -10
- package/dist/page-2V27JGKJ.js +0 -10
- package/dist/posting-GBOSZCMZ.js +0 -12
- package/dist/project-CQL5LN26.js +0 -10
- package/dist/reaction-T2DTJBBV.js +0 -10
- package/dist/saved-view-4R2DBCL3.js +0 -10
- package/dist/task-view-NUGKELJP.js +0 -10
- package/dist/transaction-ZDSEGDIR.js +0 -12
- package/dist/transcription-PSCLDAGX.js +0 -12
- package/dist/user-widget-RLY2N46A.js +0 -8
- 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-
|
|
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 {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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,21 +995,10 @@ 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";
|
|
1000
|
+
var PermissionError = class extends TaggedError {
|
|
1001
|
+
_tag = "PermissionError";
|
|
1177
1002
|
code = "PERMISSION_DENIED";
|
|
1178
1003
|
action;
|
|
1179
1004
|
nodeId;
|
|
@@ -1186,7 +1011,6 @@ var PermissionError = class extends Error {
|
|
|
1186
1011
|
super(
|
|
1187
1012
|
`Permission denied: ${roleInfo} but action '${decision.action}' is not permitted. Reasons: ${decision.reasons.join(", ")}`
|
|
1188
1013
|
);
|
|
1189
|
-
this.name = "PermissionError";
|
|
1190
1014
|
this.action = decision.action;
|
|
1191
1015
|
this.nodeId = decision.resource;
|
|
1192
1016
|
this.subject = decision.subject;
|
|
@@ -1363,6 +1187,186 @@ function findSchemaIdForNode(operations, nodeId, tempIds) {
|
|
|
1363
1187
|
return void 0;
|
|
1364
1188
|
}
|
|
1365
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
|
+
|
|
1366
1370
|
// src/store/store.ts
|
|
1367
1371
|
var MAX_CONFLICTS = 200;
|
|
1368
1372
|
var ENCRYPTED_NODE_MARKER = "xnet:node-encrypted:v1";
|
|
@@ -1462,7 +1466,7 @@ var NodeStore = class {
|
|
|
1462
1466
|
schemaId: options.schemaId,
|
|
1463
1467
|
properties: options.properties
|
|
1464
1468
|
},
|
|
1465
|
-
authAction: "
|
|
1469
|
+
authAction: "create",
|
|
1466
1470
|
requireExisting: false,
|
|
1467
1471
|
createSchemaId: options.schemaId
|
|
1468
1472
|
});
|
|
@@ -1476,7 +1480,7 @@ var NodeStore = class {
|
|
|
1476
1480
|
}
|
|
1477
1481
|
await this.assertAuthorized({
|
|
1478
1482
|
subject: this.authorDID,
|
|
1479
|
-
action: "
|
|
1483
|
+
action: "create",
|
|
1480
1484
|
nodeId: id,
|
|
1481
1485
|
patch: options.properties,
|
|
1482
1486
|
node: {
|
|
@@ -1610,7 +1614,7 @@ var NodeStore = class {
|
|
|
1610
1614
|
nodeId: id,
|
|
1611
1615
|
properties: options.properties
|
|
1612
1616
|
},
|
|
1613
|
-
authAction: "
|
|
1617
|
+
authAction: "update",
|
|
1614
1618
|
requireExisting: true
|
|
1615
1619
|
});
|
|
1616
1620
|
this.emit(change2, node2, previousNode, false);
|
|
@@ -1634,7 +1638,7 @@ var NodeStore = class {
|
|
|
1634
1638
|
}
|
|
1635
1639
|
await this.assertAuthorized({
|
|
1636
1640
|
subject: this.authorDID,
|
|
1637
|
-
action: "
|
|
1641
|
+
action: "update",
|
|
1638
1642
|
nodeId: id,
|
|
1639
1643
|
patch: options.properties,
|
|
1640
1644
|
node: {
|
|
@@ -1757,7 +1761,7 @@ var NodeStore = class {
|
|
|
1757
1761
|
properties: {},
|
|
1758
1762
|
deleted: false
|
|
1759
1763
|
},
|
|
1760
|
-
authAction: "
|
|
1764
|
+
authAction: "update",
|
|
1761
1765
|
requireExisting: true
|
|
1762
1766
|
});
|
|
1763
1767
|
this.emit(change2, node2, previousNode, false);
|
|
@@ -1770,7 +1774,7 @@ var NodeStore = class {
|
|
|
1770
1774
|
}
|
|
1771
1775
|
await this.assertAuthorized({
|
|
1772
1776
|
subject: this.authorDID,
|
|
1773
|
-
action: "
|
|
1777
|
+
action: "update",
|
|
1774
1778
|
nodeId: id,
|
|
1775
1779
|
node: {
|
|
1776
1780
|
schemaId: existing.schemaId,
|
|
@@ -2471,12 +2475,21 @@ var NodeStore = class {
|
|
|
2471
2475
|
);
|
|
2472
2476
|
}
|
|
2473
2477
|
if (this.authEvaluator) {
|
|
2474
|
-
const
|
|
2478
|
+
const stored = await this.storage.getNode(change.payload.nodeId);
|
|
2479
|
+
const action = this.inferActionFromChange(change, stored);
|
|
2475
2480
|
const decision = await this.authEvaluator.can({
|
|
2476
2481
|
subject: change.authorDID,
|
|
2477
2482
|
action,
|
|
2478
2483
|
nodeId: change.payload.nodeId,
|
|
2479
|
-
patch: action === "
|
|
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
|
|
2480
2493
|
});
|
|
2481
2494
|
if (!decision.allowed) {
|
|
2482
2495
|
this.telemetry?.reportSecurityEvent("data.unauthorized_remote_change", "medium");
|
|
@@ -2921,7 +2934,11 @@ var NodeStore = class {
|
|
|
2921
2934
|
const newTs = {
|
|
2922
2935
|
lamport: change.lamport,
|
|
2923
2936
|
author: change.authorDID,
|
|
2924
|
-
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) } : {}
|
|
2925
2942
|
};
|
|
2926
2943
|
const incomingWins = !existingTs || this.shouldReplace(existingTs, newTs);
|
|
2927
2944
|
const target = isUnknownProperty ? node._unknown ??= {} : node.properties;
|
|
@@ -3056,7 +3073,7 @@ var NodeStore = class {
|
|
|
3056
3073
|
if (operation.type === "create") {
|
|
3057
3074
|
await this.assertAuthorized({
|
|
3058
3075
|
subject: this.authorDID,
|
|
3059
|
-
action: "
|
|
3076
|
+
action: "create",
|
|
3060
3077
|
nodeId: operation.options.id ?? "",
|
|
3061
3078
|
node: {
|
|
3062
3079
|
schemaId: operation.options.schemaId,
|
|
@@ -3076,17 +3093,20 @@ var NodeStore = class {
|
|
|
3076
3093
|
}
|
|
3077
3094
|
await this.assertAuthorized({
|
|
3078
3095
|
subject: this.authorDID,
|
|
3079
|
-
action: "
|
|
3096
|
+
action: "update",
|
|
3080
3097
|
nodeId: operation.nodeId,
|
|
3081
3098
|
patch: operation.type === "update" ? operation.options.properties : void 0
|
|
3082
3099
|
});
|
|
3083
3100
|
}
|
|
3084
3101
|
}
|
|
3085
|
-
inferActionFromChange(change) {
|
|
3102
|
+
inferActionFromChange(change, stored) {
|
|
3086
3103
|
if (change.payload.deleted === true) {
|
|
3087
3104
|
return "delete";
|
|
3088
3105
|
}
|
|
3089
|
-
|
|
3106
|
+
if (!stored && change.payload.schemaId) {
|
|
3107
|
+
return "create";
|
|
3108
|
+
}
|
|
3109
|
+
return "update";
|
|
3090
3110
|
}
|
|
3091
3111
|
shouldRecomputeRecipients(schemaId, patch) {
|
|
3092
3112
|
const authRelevant = this.authRelevantPropertyLookup?.(schemaId);
|
|
@@ -3459,7 +3479,7 @@ var MemoryNodeStorageAdapter = class {
|
|
|
3459
3479
|
};
|
|
3460
3480
|
|
|
3461
3481
|
// src/store/sqlite-adapter.ts
|
|
3462
|
-
import { lwwUpdateGuardSql } from "@xnetjs/core";
|
|
3482
|
+
import { lwwUpdateGuardSql, singleFlight } from "@xnetjs/core";
|
|
3463
3483
|
import {
|
|
3464
3484
|
extractSearchableContent as extractSearchableContent2,
|
|
3465
3485
|
analyzeQuery,
|
|
@@ -3519,7 +3539,10 @@ function hydrateJoinedRows(rows) {
|
|
|
3519
3539
|
node.timestamps[row.property_key] = {
|
|
3520
3540
|
lamport: row.lamport_time ?? 0,
|
|
3521
3541
|
author: row.updated_by ?? "",
|
|
3522
|
-
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 } : {}
|
|
3523
3546
|
};
|
|
3524
3547
|
if ((row.prop_updated_at ?? 0) >= node.updatedAt) {
|
|
3525
3548
|
node.updatedBy = row.updated_by ?? node.createdBy;
|
|
@@ -3539,7 +3562,10 @@ function hydrateAggregatedRows(rows) {
|
|
|
3539
3562
|
timestamps[key] = {
|
|
3540
3563
|
lamport: entry.l ?? 0,
|
|
3541
3564
|
author: entry.b ?? "",
|
|
3542
|
-
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 } : {}
|
|
3543
3569
|
};
|
|
3544
3570
|
if ((entry.w ?? 0) >= row.updated_at) {
|
|
3545
3571
|
node.updatedBy = entry.b ?? row.created_by;
|
|
@@ -3570,6 +3596,7 @@ function buildHydrateChunkQuery(ids) {
|
|
|
3570
3596
|
p.lamport_time,
|
|
3571
3597
|
p.updated_by,
|
|
3572
3598
|
p.updated_at AS prop_updated_at,
|
|
3599
|
+
p.tiebreak_key,
|
|
3573
3600
|
wanted.ordinal
|
|
3574
3601
|
FROM wanted
|
|
3575
3602
|
JOIN nodes n ON n.id = wanted.id
|
|
@@ -3598,7 +3625,7 @@ function buildAggregatedHydrateChunkQuery(ids) {
|
|
|
3598
3625
|
FILTER (WHERE p.property_key IS NOT NULL) AS props_json,
|
|
3599
3626
|
json_group_object(
|
|
3600
3627
|
p.property_key,
|
|
3601
|
-
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)
|
|
3602
3629
|
) FILTER (WHERE p.property_key IS NOT NULL) AS meta_json
|
|
3603
3630
|
FROM wanted
|
|
3604
3631
|
JOIN nodes n ON n.id = wanted.id
|
|
@@ -4521,7 +4548,12 @@ var NODE_PROPERTIES_LWW_GUARD = lwwUpdateGuardSql({
|
|
|
4521
4548
|
table: "node_properties",
|
|
4522
4549
|
lamportColumn: "lamport_time",
|
|
4523
4550
|
wallTimeColumn: "updated_at",
|
|
4524
|
-
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"
|
|
4525
4557
|
});
|
|
4526
4558
|
var DEFAULT_ADAPTIVE_INDEXING = {
|
|
4527
4559
|
enabled: false,
|
|
@@ -4621,6 +4653,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
4621
4653
|
nodeReadAuthorizer;
|
|
4622
4654
|
/** One-time guard: ensure the `auth_fingerprint` column exists on upgraded DBs. */
|
|
4623
4655
|
materializationColumnsReady = false;
|
|
4656
|
+
nodePropertyColumnsReady = false;
|
|
4624
4657
|
/**
|
|
4625
4658
|
* Descriptor→SQL compilation lives in `query-compiler.ts` (exploration
|
|
4626
4659
|
* 0276). Flags are read per-compile so the compiler never captures stale
|
|
@@ -4965,6 +4998,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
4965
4998
|
* Used by importNodes to avoid nested transactions.
|
|
4966
4999
|
*/
|
|
4967
5000
|
async _setNodeInternal(node, options) {
|
|
5001
|
+
await this.ensureNodePropertyColumns();
|
|
4968
5002
|
await this.db.run(
|
|
4969
5003
|
`INSERT INTO nodes (id, schema_id, created_at, updated_at, created_by, deleted_at)
|
|
4970
5004
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -4986,13 +5020,14 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
4986
5020
|
const timestamp = node.timestamps[key];
|
|
4987
5021
|
if (!timestamp) continue;
|
|
4988
5022
|
await this.db.run(
|
|
4989
|
-
`INSERT INTO node_properties (node_id, property_key, value, lamport_time, updated_by, updated_at)
|
|
4990
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
5023
|
+
`INSERT INTO node_properties (node_id, property_key, value, lamport_time, updated_by, updated_at, tiebreak_key)
|
|
5024
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
4991
5025
|
ON CONFLICT(node_id, property_key) DO UPDATE SET
|
|
4992
5026
|
value = excluded.value,
|
|
4993
5027
|
lamport_time = excluded.lamport_time,
|
|
4994
5028
|
updated_by = excluded.updated_by,
|
|
4995
|
-
updated_at = excluded.updated_at
|
|
5029
|
+
updated_at = excluded.updated_at,
|
|
5030
|
+
tiebreak_key = excluded.tiebreak_key
|
|
4996
5031
|
WHERE ${NODE_PROPERTIES_LWW_GUARD}`,
|
|
4997
5032
|
[
|
|
4998
5033
|
node.id,
|
|
@@ -5000,7 +5035,8 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5000
5035
|
this.serializeValue(value),
|
|
5001
5036
|
timestamp.lamport,
|
|
5002
5037
|
timestamp.author,
|
|
5003
|
-
timestamp.wallTime
|
|
5038
|
+
timestamp.wallTime,
|
|
5039
|
+
timestamp.tiebreakKey ?? null
|
|
5004
5040
|
]
|
|
5005
5041
|
);
|
|
5006
5042
|
}
|
|
@@ -5062,7 +5098,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5062
5098
|
)
|
|
5063
5099
|
SELECT
|
|
5064
5100
|
ln.id, ln.schema_id, ln.created_at, ln.updated_at, ln.created_by, ln.deleted_at,
|
|
5065
|
-
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,
|
|
5066
5102
|
NULL as ordinal
|
|
5067
5103
|
FROM limited_nodes ln
|
|
5068
5104
|
LEFT JOIN node_properties p ON ln.id = p.node_id
|
|
@@ -5074,7 +5110,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5074
5110
|
sql = `
|
|
5075
5111
|
SELECT
|
|
5076
5112
|
n.id, n.schema_id, n.created_at, n.updated_at, n.created_by, n.deleted_at,
|
|
5077
|
-
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,
|
|
5078
5114
|
NULL as ordinal
|
|
5079
5115
|
FROM nodes n
|
|
5080
5116
|
LEFT JOIN node_properties p ON n.id = p.node_id
|
|
@@ -5587,6 +5623,7 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5587
5623
|
if (!this.db.transactionBatch) {
|
|
5588
5624
|
throw new Error("SQLite transactionBatch is not available.");
|
|
5589
5625
|
}
|
|
5626
|
+
await this.ensureNodePropertyColumns();
|
|
5590
5627
|
const operations = [];
|
|
5591
5628
|
const indexProperties = options?.indexProperties ?? true;
|
|
5592
5629
|
const hasFullTextSearch = await this.fullTextIndexing.hasTable();
|
|
@@ -5637,16 +5674,17 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5637
5674
|
const timestamp = node.timestamps[key];
|
|
5638
5675
|
if (!timestamp) continue;
|
|
5639
5676
|
operations.push({
|
|
5640
|
-
// Full LWW ordering
|
|
5641
|
-
// 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).
|
|
5642
5679
|
sql: `INSERT INTO node_properties
|
|
5643
|
-
(node_id, property_key, value, lamport_time, updated_by, updated_at)
|
|
5644
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
5680
|
+
(node_id, property_key, value, lamport_time, updated_by, updated_at, tiebreak_key)
|
|
5681
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
5645
5682
|
ON CONFLICT(node_id, property_key) DO UPDATE SET
|
|
5646
5683
|
value = excluded.value,
|
|
5647
5684
|
lamport_time = excluded.lamport_time,
|
|
5648
5685
|
updated_by = excluded.updated_by,
|
|
5649
|
-
updated_at = excluded.updated_at
|
|
5686
|
+
updated_at = excluded.updated_at,
|
|
5687
|
+
tiebreak_key = excluded.tiebreak_key
|
|
5650
5688
|
WHERE ${NODE_PROPERTIES_LWW_GUARD}`,
|
|
5651
5689
|
params: [
|
|
5652
5690
|
node.id,
|
|
@@ -5654,7 +5692,8 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5654
5692
|
this.serializeValue(value),
|
|
5655
5693
|
timestamp.lamport,
|
|
5656
5694
|
timestamp.author,
|
|
5657
|
-
timestamp.wallTime
|
|
5695
|
+
timestamp.wallTime,
|
|
5696
|
+
timestamp.tiebreakKey ?? null
|
|
5658
5697
|
]
|
|
5659
5698
|
});
|
|
5660
5699
|
}
|
|
@@ -5906,6 +5945,24 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
5906
5945
|
}
|
|
5907
5946
|
this.materializationColumnsReady = true;
|
|
5908
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
|
+
}
|
|
5909
5966
|
setNodeReadAuthorizer(authorizer) {
|
|
5910
5967
|
this.nodeReadAuthorizer = authorizer;
|
|
5911
5968
|
}
|
|
@@ -6185,24 +6242,23 @@ var SQLiteNodeStorageAdapter = class {
|
|
|
6185
6242
|
*/
|
|
6186
6243
|
collectCompiledQueryDiagnostics(compiled) {
|
|
6187
6244
|
const memoKey = compiled.sql;
|
|
6188
|
-
|
|
6189
|
-
if (memoized) {
|
|
6190
|
-
return memoized;
|
|
6191
|
-
}
|
|
6192
|
-
const pending = this.computeCompiledQueryDiagnostics(compiled).then((diagnostics) => {
|
|
6193
|
-
if (diagnostics.diagnosticsError) {
|
|
6194
|
-
this.compiledQueryDiagnosticsMemo.delete(memoKey);
|
|
6195
|
-
}
|
|
6196
|
-
return diagnostics;
|
|
6197
|
-
});
|
|
6198
|
-
if (this.compiledQueryDiagnosticsMemo.size >= COMPILED_QUERY_DIAGNOSTICS_MEMO_LIMIT) {
|
|
6245
|
+
if (!this.compiledQueryDiagnosticsMemo.has(memoKey) && this.compiledQueryDiagnosticsMemo.size >= COMPILED_QUERY_DIAGNOSTICS_MEMO_LIMIT) {
|
|
6199
6246
|
const oldest = this.compiledQueryDiagnosticsMemo.keys().next().value;
|
|
6200
6247
|
if (oldest !== void 0) {
|
|
6201
6248
|
this.compiledQueryDiagnosticsMemo.delete(oldest);
|
|
6202
6249
|
}
|
|
6203
6250
|
}
|
|
6204
|
-
|
|
6205
|
-
|
|
6251
|
+
return singleFlight(
|
|
6252
|
+
this.compiledQueryDiagnosticsMemo,
|
|
6253
|
+
memoKey,
|
|
6254
|
+
() => this.computeCompiledQueryDiagnostics(compiled).then((diagnostics) => {
|
|
6255
|
+
if (diagnostics.diagnosticsError) {
|
|
6256
|
+
this.compiledQueryDiagnosticsMemo.delete(memoKey);
|
|
6257
|
+
}
|
|
6258
|
+
return diagnostics;
|
|
6259
|
+
}),
|
|
6260
|
+
{ retain: "keep" }
|
|
6261
|
+
);
|
|
6206
6262
|
}
|
|
6207
6263
|
async computeCompiledQueryDiagnostics(compiled) {
|
|
6208
6264
|
try {
|