@korajs/store 1.0.0-beta.0 → 1.0.0-beta.10
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/adapters/better-sqlite3.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/better-sqlite3.js +1 -1
- package/dist/adapters/indexeddb.cjs +523 -148
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +4 -2
- package/dist/adapters/indexeddb.d.ts +4 -2
- package/dist/adapters/indexeddb.js +55 -11
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs +207 -187
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +208 -188
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +474 -140
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +45 -8
- package/dist/adapters/sqlite-wasm.d.ts +45 -8
- package/dist/adapters/sqlite-wasm.js +3 -3
- package/dist/{backup-5XZ7THNJ.js → backup-5S7K2Z35.js} +3 -3
- package/dist/blob-fs.cjs +166 -0
- package/dist/blob-fs.cjs.map +1 -0
- package/dist/blob-fs.d.cts +29 -0
- package/dist/blob-fs.d.ts +29 -0
- package/dist/blob-fs.js +128 -0
- package/dist/blob-fs.js.map +1 -0
- package/dist/{chunk-EIVXWF4B.js → chunk-CYO3YKFL.js} +4 -3
- package/dist/chunk-CYO3YKFL.js.map +1 -0
- package/dist/{chunk-ETDJEH2I.js → chunk-GSJ7EEUC.js} +12 -1
- package/dist/chunk-GSJ7EEUC.js.map +1 -0
- package/dist/{chunk-5R4QMY3Z.js → chunk-HS6DBMST.js} +13 -10
- package/dist/chunk-HS6DBMST.js.map +1 -0
- package/dist/chunk-RJIEKEN4.js +751 -0
- package/dist/chunk-RJIEKEN4.js.map +1 -0
- package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
- package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
- package/dist/{chunk-IOEFNHHP.js → chunk-VDBJQ75K.js} +2 -2
- package/dist/chunk-VDBJQ75K.js.map +1 -0
- package/dist/chunk-W4MTTNEZ.js +55 -0
- package/dist/chunk-W4MTTNEZ.js.map +1 -0
- package/dist/{chunk-OUQDC5Z6.js → chunk-YT7MRFCP.js} +55 -35
- package/dist/chunk-YT7MRFCP.js.map +1 -0
- package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
- package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
- package/dist/{export-audit-VPGQWNR6.js → export-audit-ODGQTQIK.js} +3 -3
- package/dist/index.cjs +923 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +443 -6
- package/dist/index.d.ts +443 -6
- package/dist/index.js +737 -49
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +69 -43
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +3 -3
- package/dist/sqlite-wasm-channel-GGHA2BXU.js +12 -0
- package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +1 -1
- package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +1 -1
- package/dist/{types-CyjXwrjW.d.cts → types-BOO9EPt1.d.ts} +10 -3
- package/dist/{types-CJOVKxLw.d.ts → types-BslDOTk0.d.cts} +32 -2
- package/dist/{types-CJOVKxLw.d.cts → types-BslDOTk0.d.ts} +32 -2
- package/dist/{types-DAxCladl.d.ts → types-C_GDavG-.d.cts} +10 -3
- package/package.json +13 -12
- package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -68
- package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
- package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -2
- package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -2
- package/dist/adapters/sqlite-wasm-shared-host.js +0 -66
- package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
- package/dist/chunk-5R4QMY3Z.js.map +0 -1
- package/dist/chunk-EIVXWF4B.js.map +0 -1
- package/dist/chunk-ETDJEH2I.js.map +0 -1
- package/dist/chunk-IOEFNHHP.js.map +0 -1
- package/dist/chunk-OUQDC5Z6.js.map +0 -1
- package/dist/chunk-RBPVEAVM.js +0 -425
- package/dist/chunk-RBPVEAVM.js.map +0 -1
- package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
- /package/dist/{backup-5XZ7THNJ.js.map → backup-5S7K2Z35.js.map} +0 -0
- /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-ODGQTQIK.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-GGHA2BXU.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -69,6 +69,7 @@ function encodeRichtext(value) {
|
|
|
69
69
|
}
|
|
70
70
|
if (typeof value === "string") {
|
|
71
71
|
const doc = new Y.Doc();
|
|
72
|
+
doc.clientID = 0;
|
|
72
73
|
doc.getText(TEXT_KEY).insert(0, value);
|
|
73
74
|
return Y.encodeStateAsUpdate(doc);
|
|
74
75
|
}
|
|
@@ -239,6 +240,9 @@ function serializeValue(value, descriptor) {
|
|
|
239
240
|
case "boolean":
|
|
240
241
|
return value ? 1 : 0;
|
|
241
242
|
case "array":
|
|
243
|
+
case "object":
|
|
244
|
+
case "json":
|
|
245
|
+
case "blob":
|
|
242
246
|
return JSON.stringify(value);
|
|
243
247
|
case "richtext":
|
|
244
248
|
return encodeRichtext(value);
|
|
@@ -255,6 +259,13 @@ function deserializeValue(value, descriptor) {
|
|
|
255
259
|
return JSON.parse(value);
|
|
256
260
|
}
|
|
257
261
|
return value;
|
|
262
|
+
case "object":
|
|
263
|
+
case "json":
|
|
264
|
+
case "blob":
|
|
265
|
+
if (typeof value === "string") {
|
|
266
|
+
return JSON.parse(value);
|
|
267
|
+
}
|
|
268
|
+
return value;
|
|
258
269
|
case "richtext":
|
|
259
270
|
return decodeRichtext(value);
|
|
260
271
|
default:
|
|
@@ -518,7 +529,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
518
529
|
schema_version: op.schemaVersion
|
|
519
530
|
};
|
|
520
531
|
await tx.execute(
|
|
521
|
-
`INSERT OR IGNORE INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
532
|
+
`INSERT OR IGNORE INTO ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
522
533
|
[
|
|
523
534
|
opRow.id,
|
|
524
535
|
opRow.node_id,
|
|
@@ -537,8 +548,8 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
537
548
|
} else {
|
|
538
549
|
await adapter.transaction(async (tx) => {
|
|
539
550
|
for (const collection of Object.keys(schema.collections)) {
|
|
540
|
-
await tx.execute(`DELETE FROM _kora_ops_${collection}`);
|
|
541
|
-
await tx.execute(`DELETE FROM ${collection}`);
|
|
551
|
+
await tx.execute(`DELETE FROM ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)}`);
|
|
552
|
+
await tx.execute(`DELETE FROM ${(0, import_core6.quoteIdent)(collection)}`);
|
|
542
553
|
}
|
|
543
554
|
await tx.execute("DELETE FROM _kora_version_vector");
|
|
544
555
|
await tx.execute("DELETE FROM _kora_meta");
|
|
@@ -558,7 +569,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
558
569
|
for (const op of operations) {
|
|
559
570
|
const collection = op.collection;
|
|
560
571
|
await tx.execute(
|
|
561
|
-
`INSERT INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
572
|
+
`INSERT INTO ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
562
573
|
[
|
|
563
574
|
op.id,
|
|
564
575
|
op.nodeId,
|
|
@@ -590,6 +601,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
590
601
|
for (const record of records) {
|
|
591
602
|
const keys = Object.keys(record);
|
|
592
603
|
const placeholders = keys.map(() => "?").join(", ");
|
|
604
|
+
const quotedKeys = keys.map((k) => (0, import_core6.quoteIdent)(k)).join(", ");
|
|
593
605
|
const values = keys.map((k) => {
|
|
594
606
|
const v = record[k];
|
|
595
607
|
if (typeof v === "boolean") return v ? 1 : 0;
|
|
@@ -597,7 +609,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
597
609
|
return v;
|
|
598
610
|
});
|
|
599
611
|
await tx.execute(
|
|
600
|
-
`INSERT OR REPLACE INTO ${col} (${
|
|
612
|
+
`INSERT OR REPLACE INTO ${(0, import_core6.quoteIdent)(col)} (${quotedKeys}) VALUES (${placeholders})`,
|
|
601
613
|
values
|
|
602
614
|
);
|
|
603
615
|
}
|
|
@@ -630,7 +642,7 @@ async function readAllOperations(adapter, schema) {
|
|
|
630
642
|
const allOps = [];
|
|
631
643
|
for (const collectionName of Object.keys(schema.collections)) {
|
|
632
644
|
const rows = await adapter.query(
|
|
633
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
645
|
+
`SELECT * FROM ${(0, import_core6.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
634
646
|
);
|
|
635
647
|
for (const row of rows) {
|
|
636
648
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -639,7 +651,9 @@ async function readAllOperations(adapter, schema) {
|
|
|
639
651
|
return allOps;
|
|
640
652
|
}
|
|
641
653
|
async function readCollectionRecords(adapter, collection) {
|
|
642
|
-
return adapter.query(
|
|
654
|
+
return adapter.query(
|
|
655
|
+
`SELECT * FROM ${(0, import_core6.quoteIdent)(collection)} WHERE _deleted = 0`
|
|
656
|
+
);
|
|
643
657
|
}
|
|
644
658
|
async function computeSha256(data) {
|
|
645
659
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -707,7 +721,7 @@ __export(audit_trace_store_exports, {
|
|
|
707
721
|
});
|
|
708
722
|
function persistedAuditTraceFromEvent(event) {
|
|
709
723
|
return {
|
|
710
|
-
id: (0,
|
|
724
|
+
id: (0, import_core21.generateUUIDv7)(),
|
|
711
725
|
recordedAt: Date.now(),
|
|
712
726
|
eventType: event.type,
|
|
713
727
|
constraint: event.type === "constraint:violated" ? event.constraint : void 0,
|
|
@@ -773,11 +787,11 @@ function auditTraceIndexFields(trace) {
|
|
|
773
787
|
recordId: trace.operationA.recordId
|
|
774
788
|
};
|
|
775
789
|
}
|
|
776
|
-
var
|
|
790
|
+
var import_core21;
|
|
777
791
|
var init_audit_trace_store = __esm({
|
|
778
792
|
"src/audit/audit-trace-store.ts"() {
|
|
779
793
|
"use strict";
|
|
780
|
-
|
|
794
|
+
import_core21 = require("@korajs/core");
|
|
781
795
|
init_audit_json();
|
|
782
796
|
}
|
|
783
797
|
});
|
|
@@ -849,7 +863,7 @@ async function readAllOperations2(adapter, schema, collectionFilter) {
|
|
|
849
863
|
const allOps = [];
|
|
850
864
|
for (const collectionName of collections) {
|
|
851
865
|
const rows = await adapter.query(
|
|
852
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
866
|
+
`SELECT * FROM ${(0, import_core22.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
853
867
|
);
|
|
854
868
|
for (const row of rows) {
|
|
855
869
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -960,9 +974,11 @@ function parseNdjsonSection(content) {
|
|
|
960
974
|
}
|
|
961
975
|
return text.split("\n").map((line) => deserializeAuditJson(line));
|
|
962
976
|
}
|
|
977
|
+
var import_core22;
|
|
963
978
|
var init_export_audit = __esm({
|
|
964
979
|
"src/audit/export-audit.ts"() {
|
|
965
980
|
"use strict";
|
|
981
|
+
import_core22 = require("@korajs/core");
|
|
966
982
|
init_serializer();
|
|
967
983
|
init_audit_json();
|
|
968
984
|
init_audit_trace_store();
|
|
@@ -973,10 +989,15 @@ var init_export_audit = __esm({
|
|
|
973
989
|
var index_exports = {};
|
|
974
990
|
__export(index_exports, {
|
|
975
991
|
AdapterError: () => AdapterError,
|
|
992
|
+
BlobIntegrityError: () => BlobIntegrityError,
|
|
976
993
|
COMPACTION_BASELINE_META_KEY: () => COMPACTION_BASELINE_META_KEY,
|
|
977
994
|
Collection: () => Collection,
|
|
995
|
+
DEFAULT_CHUNK_SIZE: () => DEFAULT_CHUNK_SIZE,
|
|
978
996
|
InvalidStateTransitionError: () => InvalidStateTransitionError,
|
|
979
997
|
LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
998
|
+
MemoryBlobStore: () => MemoryBlobStore,
|
|
999
|
+
NoLeaderError: () => NoLeaderError,
|
|
1000
|
+
OpfsBlobStore: () => OpfsBlobStore,
|
|
980
1001
|
OptimisticLockError: () => OptimisticLockError,
|
|
981
1002
|
PersistenceError: () => PersistenceError,
|
|
982
1003
|
QueryBuilder: () => QueryBuilder,
|
|
@@ -994,26 +1015,44 @@ __export(index_exports, {
|
|
|
994
1015
|
WorkerTimeoutError: () => WorkerTimeoutError,
|
|
995
1016
|
asRichTextSyncEngine: () => asRichTextSyncEngine,
|
|
996
1017
|
assertQueryReady: () => assertQueryReady,
|
|
1018
|
+
chunkBlob: () => chunkBlob,
|
|
1019
|
+
collectBlobGarbage: () => collectBlobGarbage,
|
|
997
1020
|
collectOperationsAheadOfServer: () => collectOperationsAheadOfServer,
|
|
998
1021
|
compactOperationLog: () => compactOperationLog,
|
|
999
1022
|
computeAckCompactionWatermark: () => computeAckCompactionWatermark,
|
|
1023
|
+
createChunkPortPair: () => createChunkPortPair,
|
|
1024
|
+
createMemoryServerBlobStore: () => createMemoryServerBlobStore,
|
|
1025
|
+
createOpfsBlobDirectory: () => createOpfsBlobDirectory,
|
|
1026
|
+
createOpfsBlobStore: () => createOpfsBlobStore,
|
|
1027
|
+
createRemoteChunkProvider: () => createRemoteChunkProvider,
|
|
1000
1028
|
createRichTextController: () => createRichTextController,
|
|
1001
1029
|
decodeAuditExport: () => decodeAuditExport,
|
|
1002
1030
|
decodeRichtext: () => decodeRichtext,
|
|
1003
1031
|
deserializeVersionVectorFromMeta: () => deserializeVersionVectorFromMeta,
|
|
1004
1032
|
encodeRichtext: () => encodeRichtext,
|
|
1005
1033
|
exportBackup: () => exportBackup,
|
|
1034
|
+
extractBlobRefs: () => extractBlobRefs,
|
|
1035
|
+
fetchBlobManifest: () => fetchBlobManifest,
|
|
1006
1036
|
getSharedQueryStoreCache: () => getSharedQueryStoreCache,
|
|
1007
1037
|
mergeVersionVectors: () => mergeVersionVectors,
|
|
1038
|
+
parseBlobManifest: () => parseBlobManifest,
|
|
1008
1039
|
persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
|
|
1009
1040
|
pluralize: () => pluralize,
|
|
1041
|
+
prepareBlobForSend: () => prepareBlobForSend,
|
|
1042
|
+
putBlobForTransfer: () => putBlobForTransfer,
|
|
1010
1043
|
readAuditExportManifest: () => readAuditExportManifest,
|
|
1011
1044
|
readBackupManifest: () => readBackupManifest,
|
|
1045
|
+
reassembleBlob: () => reassembleBlob,
|
|
1046
|
+
receiveBlob: () => receiveBlob,
|
|
1047
|
+
resolveBlobManifest: () => resolveBlobManifest,
|
|
1012
1048
|
restoreBackup: () => restoreBackup,
|
|
1013
1049
|
richtextStatesEqual: () => richtextStatesEqual,
|
|
1014
1050
|
richtextToPlainText: () => richtextToPlainText,
|
|
1051
|
+
serializeBlobManifest: () => serializeBlobManifest,
|
|
1015
1052
|
serializeVersionVectorToMeta: () => serializeVersionVectorToMeta,
|
|
1053
|
+
serveBlobChunks: () => serveBlobChunks,
|
|
1016
1054
|
singularize: () => singularize,
|
|
1055
|
+
toServerBlobCallbacks: () => toServerBlobCallbacks,
|
|
1017
1056
|
validateStateTransition: () => validateStateTransition,
|
|
1018
1057
|
validateUpdateStateMachine: () => validateUpdateStateMachine,
|
|
1019
1058
|
verifyAuditExportChecksum: () => verifyAuditExportChecksum,
|
|
@@ -1076,6 +1115,16 @@ var WorkerTimeoutError = class extends import_core.KoraError {
|
|
|
1076
1115
|
this.name = "WorkerTimeoutError";
|
|
1077
1116
|
}
|
|
1078
1117
|
};
|
|
1118
|
+
var NoLeaderError = class extends import_core.KoraError {
|
|
1119
|
+
constructor(operation) {
|
|
1120
|
+
super(
|
|
1121
|
+
`No live leader tab is answering multi-tab storage RPC for operation "${operation}"`,
|
|
1122
|
+
"NO_LEADER",
|
|
1123
|
+
{ operation }
|
|
1124
|
+
);
|
|
1125
|
+
this.name = "NoLeaderError";
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1079
1128
|
var PersistenceError = class extends import_core.KoraError {
|
|
1080
1129
|
constructor(message, context) {
|
|
1081
1130
|
super(`Persistence error: ${message}`, "PERSISTENCE_ERROR", context);
|
|
@@ -1115,7 +1164,7 @@ async function compactOperationLog(adapter, schema, strategy, serverVector) {
|
|
|
1115
1164
|
const collectionNames = Object.keys(schema.collections);
|
|
1116
1165
|
await adapter.transaction(async (tx) => {
|
|
1117
1166
|
for (const collectionName of collectionNames) {
|
|
1118
|
-
const table = `_kora_ops_${collectionName}
|
|
1167
|
+
const table = (0, import_core2.quoteIdent)(`_kora_ops_${collectionName}`);
|
|
1119
1168
|
for (const [nodeId, maxSeq] of watermark) {
|
|
1120
1169
|
if (maxSeq <= 0) continue;
|
|
1121
1170
|
const countSql = buildCountSql(table, ageCutoffSerialized !== null);
|
|
@@ -1150,6 +1199,7 @@ function buildDeleteSql(table, withAge) {
|
|
|
1150
1199
|
var import_core3 = require("@korajs/core");
|
|
1151
1200
|
var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
|
|
1152
1201
|
var DELTA_CURSOR_META_KEY = "delta_cursor";
|
|
1202
|
+
var DELIVERY_WATERMARK_META_KEY = "delivery_watermark";
|
|
1153
1203
|
function serializeVersionVectorToMeta(vector) {
|
|
1154
1204
|
const record = {};
|
|
1155
1205
|
for (const [nodeId, seq] of vector) {
|
|
@@ -1222,13 +1272,55 @@ async function saveDeltaCursor(adapter, cursor) {
|
|
|
1222
1272
|
cursor
|
|
1223
1273
|
]);
|
|
1224
1274
|
}
|
|
1275
|
+
function deliveryWatermarkKey(signature) {
|
|
1276
|
+
return signature === "" ? DELIVERY_WATERMARK_META_KEY : `${DELIVERY_WATERMARK_META_KEY}:${signature}`;
|
|
1277
|
+
}
|
|
1278
|
+
async function loadDeliveryWatermark(adapter, signature) {
|
|
1279
|
+
const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
|
|
1280
|
+
deliveryWatermarkKey(signature)
|
|
1281
|
+
]);
|
|
1282
|
+
const value = rows[0]?.value;
|
|
1283
|
+
if (value === void 0 || value === null) {
|
|
1284
|
+
return 0;
|
|
1285
|
+
}
|
|
1286
|
+
const parsed = Number(value);
|
|
1287
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
1288
|
+
}
|
|
1289
|
+
async function saveDeliveryWatermark(adapter, signature, watermark) {
|
|
1290
|
+
await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
|
|
1291
|
+
deliveryWatermarkKey(signature),
|
|
1292
|
+
String(watermark)
|
|
1293
|
+
]);
|
|
1294
|
+
}
|
|
1295
|
+
async function deleteDeliveryWatermark(adapter, signature) {
|
|
1296
|
+
await adapter.execute("DELETE FROM _kora_meta WHERE key = ?", [deliveryWatermarkKey(signature)]);
|
|
1297
|
+
}
|
|
1298
|
+
async function loadAllDeliveryWatermarks(adapter) {
|
|
1299
|
+
const rows = await adapter.query(
|
|
1300
|
+
"SELECT key, value FROM _kora_meta WHERE key = ? OR key LIKE ?",
|
|
1301
|
+
[DELIVERY_WATERMARK_META_KEY, `${DELIVERY_WATERMARK_META_KEY}:%`]
|
|
1302
|
+
);
|
|
1303
|
+
const result = {};
|
|
1304
|
+
const prefix = `${DELIVERY_WATERMARK_META_KEY}:`;
|
|
1305
|
+
for (const row of rows) {
|
|
1306
|
+
const signature = row.key === DELIVERY_WATERMARK_META_KEY ? "" : row.key.slice(prefix.length);
|
|
1307
|
+
const parsed = Number(row.value);
|
|
1308
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
1309
|
+
result[signature] = parsed;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return result;
|
|
1313
|
+
}
|
|
1225
1314
|
|
|
1226
1315
|
// src/store/store.ts
|
|
1227
|
-
var
|
|
1316
|
+
var import_core23 = require("@korajs/core");
|
|
1228
1317
|
init_backup();
|
|
1229
1318
|
|
|
1319
|
+
// src/collection/collection.ts
|
|
1320
|
+
var import_core13 = require("@korajs/core");
|
|
1321
|
+
|
|
1230
1322
|
// src/mutations/execute-delete.ts
|
|
1231
|
-
var
|
|
1323
|
+
var import_core9 = require("@korajs/core");
|
|
1232
1324
|
|
|
1233
1325
|
// src/lww/row-version.ts
|
|
1234
1326
|
var import_core7 = require("@korajs/core");
|
|
@@ -1254,9 +1346,10 @@ function lwwVersionWhereClause(remoteVersion) {
|
|
|
1254
1346
|
}
|
|
1255
1347
|
|
|
1256
1348
|
// src/query/sql-builder.ts
|
|
1349
|
+
var import_core8 = require("@korajs/core");
|
|
1257
1350
|
function buildSelectQuery(descriptor, fields) {
|
|
1258
1351
|
const params = [];
|
|
1259
|
-
const parts = [`SELECT * FROM ${descriptor.collection}`];
|
|
1352
|
+
const parts = [`SELECT * FROM ${(0, import_core8.quoteIdent)(descriptor.collection)}`];
|
|
1260
1353
|
const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
|
|
1261
1354
|
const deletedFilter = "_deleted = 0";
|
|
1262
1355
|
if (whereClause) {
|
|
@@ -1267,7 +1360,7 @@ function buildSelectQuery(descriptor, fields) {
|
|
|
1267
1360
|
if (descriptor.orderBy.length > 0) {
|
|
1268
1361
|
const orderParts = descriptor.orderBy.map((o) => {
|
|
1269
1362
|
validateFieldName(o.field, fields);
|
|
1270
|
-
return `${o.field} ${o.direction.toUpperCase()}`;
|
|
1363
|
+
return `${(0, import_core8.quoteIdent)(o.field)} ${o.direction.toUpperCase()}`;
|
|
1271
1364
|
});
|
|
1272
1365
|
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
1273
1366
|
}
|
|
@@ -1281,7 +1374,7 @@ function buildSelectQuery(descriptor, fields) {
|
|
|
1281
1374
|
}
|
|
1282
1375
|
function buildCountQuery(descriptor, fields) {
|
|
1283
1376
|
const params = [];
|
|
1284
|
-
const parts = [`SELECT COUNT(*) as count FROM ${descriptor.collection}`];
|
|
1377
|
+
const parts = [`SELECT COUNT(*) as count FROM ${(0, import_core8.quoteIdent)(descriptor.collection)}`];
|
|
1285
1378
|
const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
|
|
1286
1379
|
const deletedFilter = "_deleted = 0";
|
|
1287
1380
|
if (whereClause) {
|
|
@@ -1295,17 +1388,18 @@ function buildInsertQuery(collection, record) {
|
|
|
1295
1388
|
const columns = Object.keys(record);
|
|
1296
1389
|
const placeholders = columns.map(() => "?");
|
|
1297
1390
|
const params = Object.values(record);
|
|
1298
|
-
const
|
|
1391
|
+
const quotedColumns = columns.map((col) => (0, import_core8.quoteIdent)(col));
|
|
1392
|
+
const sql = `INSERT INTO ${(0, import_core8.quoteIdent)(collection)} (${quotedColumns.join(", ")}) VALUES (${placeholders.join(", ")})`;
|
|
1299
1393
|
return { sql, params };
|
|
1300
1394
|
}
|
|
1301
1395
|
function buildUpdateQuery(collection, id, changes) {
|
|
1302
|
-
const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
|
|
1396
|
+
const setClauses = Object.keys(changes).map((col) => `${(0, import_core8.quoteIdent)(col)} = ?`);
|
|
1303
1397
|
const params = [...Object.values(changes), id];
|
|
1304
|
-
const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1398
|
+
const sql = `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1305
1399
|
return { sql, params };
|
|
1306
1400
|
}
|
|
1307
1401
|
function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVersion, wallTime, options) {
|
|
1308
|
-
const fieldClauses = Object.keys(fieldChanges).map((col) => `${col} = ?`);
|
|
1402
|
+
const fieldClauses = Object.keys(fieldChanges).map((col) => `${(0, import_core8.quoteIdent)(col)} = ?`);
|
|
1309
1403
|
const setClauses = [
|
|
1310
1404
|
...fieldClauses,
|
|
1311
1405
|
"_version = CASE WHEN _version >= ? THEN _version ELSE ? END",
|
|
@@ -1323,25 +1417,25 @@ function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVe
|
|
|
1323
1417
|
params.push(options.maxCreatedAt, options.maxCreatedAt);
|
|
1324
1418
|
}
|
|
1325
1419
|
params.push(id);
|
|
1326
|
-
const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1420
|
+
const sql = `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1327
1421
|
return { sql, params };
|
|
1328
1422
|
}
|
|
1329
1423
|
function buildSoftDeleteQuery(collection, id, updatedAt, version) {
|
|
1330
1424
|
if (version !== void 0) {
|
|
1331
1425
|
return {
|
|
1332
|
-
sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
|
|
1426
|
+
sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
|
|
1333
1427
|
params: [updatedAt, version, id]
|
|
1334
1428
|
};
|
|
1335
1429
|
}
|
|
1336
1430
|
return {
|
|
1337
|
-
sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
|
|
1431
|
+
sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
|
|
1338
1432
|
params: [updatedAt, id]
|
|
1339
1433
|
};
|
|
1340
1434
|
}
|
|
1341
1435
|
function buildLwwSoftDeleteQuery(collection, id, updatedAt, version) {
|
|
1342
1436
|
const lww = lwwVersionWhereClause(version);
|
|
1343
1437
|
return {
|
|
1344
|
-
sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
|
|
1438
|
+
sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
|
|
1345
1439
|
params: [updatedAt, version, id, ...lww.params]
|
|
1346
1440
|
};
|
|
1347
1441
|
}
|
|
@@ -1372,31 +1466,32 @@ function buildWhereClauseParts(where, fields, params) {
|
|
|
1372
1466
|
}
|
|
1373
1467
|
function buildOperatorCondition(fieldName, operator, value, descriptor, params) {
|
|
1374
1468
|
const sqlValue = descriptor?.kind === "boolean" && typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
1469
|
+
const column = (0, import_core8.quoteIdent)(fieldName);
|
|
1375
1470
|
switch (operator) {
|
|
1376
1471
|
case "$eq":
|
|
1377
1472
|
if (sqlValue === null) {
|
|
1378
|
-
return `${
|
|
1473
|
+
return `${column} IS NULL`;
|
|
1379
1474
|
}
|
|
1380
1475
|
params.push(sqlValue);
|
|
1381
|
-
return `${
|
|
1476
|
+
return `${column} = ?`;
|
|
1382
1477
|
case "$ne":
|
|
1383
1478
|
if (sqlValue === null) {
|
|
1384
|
-
return `${
|
|
1479
|
+
return `${column} IS NOT NULL`;
|
|
1385
1480
|
}
|
|
1386
1481
|
params.push(sqlValue);
|
|
1387
|
-
return `${
|
|
1482
|
+
return `${column} != ?`;
|
|
1388
1483
|
case "$gt":
|
|
1389
1484
|
params.push(sqlValue);
|
|
1390
|
-
return `${
|
|
1485
|
+
return `${column} > ?`;
|
|
1391
1486
|
case "$gte":
|
|
1392
1487
|
params.push(sqlValue);
|
|
1393
|
-
return `${
|
|
1488
|
+
return `${column} >= ?`;
|
|
1394
1489
|
case "$lt":
|
|
1395
1490
|
params.push(sqlValue);
|
|
1396
|
-
return `${
|
|
1491
|
+
return `${column} < ?`;
|
|
1397
1492
|
case "$lte":
|
|
1398
1493
|
params.push(sqlValue);
|
|
1399
|
-
return `${
|
|
1494
|
+
return `${column} <= ?`;
|
|
1400
1495
|
case "$in": {
|
|
1401
1496
|
if (!Array.isArray(sqlValue)) {
|
|
1402
1497
|
throw new QueryError(`$in operator requires an array value for field "${fieldName}"`, {
|
|
@@ -1410,7 +1505,7 @@ function buildOperatorCondition(fieldName, operator, value, descriptor, params)
|
|
|
1410
1505
|
descriptor?.kind === "boolean" && typeof item === "boolean" ? item ? 1 : 0 : item
|
|
1411
1506
|
);
|
|
1412
1507
|
}
|
|
1413
|
-
return `${
|
|
1508
|
+
return `${column} IN (${placeholders.join(", ")})`;
|
|
1414
1509
|
}
|
|
1415
1510
|
default:
|
|
1416
1511
|
throw new QueryError(`Unknown operator "${operator}"`, { operator });
|
|
@@ -1490,7 +1585,7 @@ function resolveCausalDeps(ctx) {
|
|
|
1490
1585
|
// src/mutations/execute-delete.ts
|
|
1491
1586
|
async function executeDelete(ctx, id, options) {
|
|
1492
1587
|
const currentRows = await ctx.adapter.query(
|
|
1493
|
-
`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
|
|
1588
|
+
`SELECT * FROM ${(0, import_core9.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
|
|
1494
1589
|
[id]
|
|
1495
1590
|
);
|
|
1496
1591
|
if (!currentRows[0]) {
|
|
@@ -1502,7 +1597,7 @@ async function executeDelete(ctx, id, options) {
|
|
|
1502
1597
|
await ctx.adapter.transaction(async (tx) => {
|
|
1503
1598
|
if (!operation) {
|
|
1504
1599
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
|
|
1505
|
-
operation = await (0,
|
|
1600
|
+
operation = await (0, import_core9.createOperation)(
|
|
1506
1601
|
{
|
|
1507
1602
|
nodeId: ctx.nodeId,
|
|
1508
1603
|
type: "delete",
|
|
@@ -1549,7 +1644,7 @@ async function executeDelete(ctx, id, options) {
|
|
|
1549
1644
|
}
|
|
1550
1645
|
|
|
1551
1646
|
// src/mutations/execute-insert.ts
|
|
1552
|
-
var
|
|
1647
|
+
var import_core10 = require("@korajs/core");
|
|
1553
1648
|
|
|
1554
1649
|
// src/lww/field-versions.ts
|
|
1555
1650
|
function parseFieldVersions(raw) {
|
|
@@ -1606,19 +1701,24 @@ function resolvePerFieldLww(current, incomingFields, incomingVersion, rowVersion
|
|
|
1606
1701
|
init_op_data_encoding();
|
|
1607
1702
|
init_serializer();
|
|
1608
1703
|
async function executeInsert(ctx, data) {
|
|
1609
|
-
const validated = (0,
|
|
1610
|
-
const recordId = (0,
|
|
1704
|
+
const validated = (0, import_core10.validateRecord)(ctx.collection, ctx.definition, data, "insert");
|
|
1705
|
+
const recordId = (0, import_core10.generateUUIDv7)();
|
|
1611
1706
|
for (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {
|
|
1612
1707
|
if (descriptor.auto && descriptor.kind === "timestamp") {
|
|
1613
1708
|
validated[fieldName] = Date.now();
|
|
1614
1709
|
}
|
|
1615
1710
|
}
|
|
1711
|
+
const writeData = await (0, import_core10.transformSecretFieldsForWrite)(
|
|
1712
|
+
validated,
|
|
1713
|
+
ctx.definition,
|
|
1714
|
+
ctx.secretKeyProvider
|
|
1715
|
+
);
|
|
1616
1716
|
const causalDeps = resolveCausalDeps(ctx);
|
|
1617
1717
|
let operation;
|
|
1618
1718
|
let record;
|
|
1619
1719
|
await ctx.adapter.transaction(async (tx) => {
|
|
1620
1720
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
|
|
1621
|
-
operation = await (0,
|
|
1721
|
+
operation = await (0, import_core10.createOperation)(
|
|
1622
1722
|
{
|
|
1623
1723
|
nodeId: ctx.nodeId,
|
|
1624
1724
|
type: "insert",
|
|
@@ -1627,7 +1727,7 @@ async function executeInsert(ctx, data) {
|
|
|
1627
1727
|
// Binary richtext values are tagged as canonical JSON BEFORE the
|
|
1628
1728
|
// operation is content-hashed, so the hash input, persisted JSON,
|
|
1629
1729
|
// and wire payload are the identical value.
|
|
1630
|
-
data: encodeRichtextFieldsForOpData(
|
|
1730
|
+
data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
|
|
1631
1731
|
previousData: null,
|
|
1632
1732
|
sequenceNumber,
|
|
1633
1733
|
causalDeps,
|
|
@@ -1636,7 +1736,7 @@ async function executeInsert(ctx, data) {
|
|
|
1636
1736
|
ctx.clock
|
|
1637
1737
|
);
|
|
1638
1738
|
ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
|
|
1639
|
-
const serializedData = serializeRecord(
|
|
1739
|
+
const serializedData = serializeRecord(writeData, ctx.definition.fields);
|
|
1640
1740
|
const version = serializeRowVersion(operation.timestamp);
|
|
1641
1741
|
record = {
|
|
1642
1742
|
id: recordId,
|
|
@@ -1661,20 +1761,20 @@ async function executeInsert(ctx, data) {
|
|
|
1661
1761
|
ctx.onMutation(ctx.collection, operation);
|
|
1662
1762
|
return {
|
|
1663
1763
|
id: recordId,
|
|
1664
|
-
...
|
|
1764
|
+
...writeData,
|
|
1665
1765
|
createdAt: operation.timestamp.wallTime,
|
|
1666
1766
|
updatedAt: operation.timestamp.wallTime
|
|
1667
1767
|
};
|
|
1668
1768
|
}
|
|
1669
1769
|
|
|
1670
1770
|
// src/mutations/execute-update.ts
|
|
1671
|
-
var
|
|
1771
|
+
var import_core12 = require("@korajs/core");
|
|
1672
1772
|
init_op_data_encoding();
|
|
1673
1773
|
init_serializer();
|
|
1674
1774
|
|
|
1675
1775
|
// src/state-machine/state-validator.ts
|
|
1676
|
-
var
|
|
1677
|
-
var InvalidStateTransitionError = class extends
|
|
1776
|
+
var import_core11 = require("@korajs/core");
|
|
1777
|
+
var InvalidStateTransitionError = class extends import_core11.KoraError {
|
|
1678
1778
|
constructor(collection, recordId, field, fromState, toState, allowedStates) {
|
|
1679
1779
|
super(
|
|
1680
1780
|
`Invalid state transition in collection "${collection}": cannot transition field "${field}" from "${fromState}" to "${toState}". Allowed transitions from "${fromState}": ${allowedStates.length > 0 ? allowedStates.join(", ") : "(none -- terminal state)"}`,
|
|
@@ -1708,7 +1808,7 @@ function validateStateTransition(collectionName, recordId, stateMachine, current
|
|
|
1708
1808
|
collection: collectionName,
|
|
1709
1809
|
transitions: stateMachine.transitions
|
|
1710
1810
|
};
|
|
1711
|
-
const transitionResult = (0,
|
|
1811
|
+
const transitionResult = (0, import_core11.validateTransition)(constraint, currentState, newState);
|
|
1712
1812
|
if (transitionResult.valid) {
|
|
1713
1813
|
return { valid: true, allowedStates: transitionResult.allowedTargets };
|
|
1714
1814
|
}
|
|
@@ -1757,14 +1857,14 @@ function validateUpdateStateMachine(collectionName, recordId, collectionDef, cur
|
|
|
1757
1857
|
// src/mutations/execute-update.ts
|
|
1758
1858
|
async function executeUpdate(ctx, id, data) {
|
|
1759
1859
|
const currentRows = await ctx.adapter.query(
|
|
1760
|
-
`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
|
|
1860
|
+
`SELECT * FROM ${(0, import_core12.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
|
|
1761
1861
|
[id]
|
|
1762
1862
|
);
|
|
1763
1863
|
const currentRow = currentRows[0];
|
|
1764
1864
|
if (!currentRow) {
|
|
1765
1865
|
throw new RecordNotFoundError(ctx.collection, id);
|
|
1766
1866
|
}
|
|
1767
|
-
let validated = (0,
|
|
1867
|
+
let validated = (0, import_core12.validateRecord)(ctx.collection, ctx.definition, data, "update");
|
|
1768
1868
|
const currentRecord = deserializeRecord(currentRow, ctx.definition.fields);
|
|
1769
1869
|
validated = validateUpdateStateMachine(
|
|
1770
1870
|
ctx.collection,
|
|
@@ -1782,19 +1882,24 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1782
1882
|
for (const key of Object.keys(validated)) {
|
|
1783
1883
|
const value = validated[key];
|
|
1784
1884
|
previousData[key] = currentRecord[key];
|
|
1785
|
-
if ((0,
|
|
1786
|
-
resolvedData[key] = (0,
|
|
1787
|
-
atomicOps[key] = (0,
|
|
1885
|
+
if ((0, import_core12.isAtomicOp)(value)) {
|
|
1886
|
+
resolvedData[key] = (0, import_core12.resolveAtomicOp)(currentRecord[key], value);
|
|
1887
|
+
atomicOps[key] = (0, import_core12.toAtomicOp)(value);
|
|
1788
1888
|
} else {
|
|
1789
1889
|
resolvedData[key] = value;
|
|
1790
1890
|
}
|
|
1791
1891
|
}
|
|
1892
|
+
const writeData = await (0, import_core12.transformSecretFieldsForWrite)(
|
|
1893
|
+
resolvedData,
|
|
1894
|
+
ctx.definition,
|
|
1895
|
+
ctx.secretKeyProvider
|
|
1896
|
+
);
|
|
1792
1897
|
const hasAtomicOps = Object.keys(atomicOps).length > 0;
|
|
1793
1898
|
const causalDeps = resolveCausalDeps(ctx);
|
|
1794
1899
|
let operation;
|
|
1795
1900
|
await ctx.adapter.transaction(async (tx) => {
|
|
1796
1901
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
|
|
1797
|
-
operation = await (0,
|
|
1902
|
+
operation = await (0, import_core12.createOperation)(
|
|
1798
1903
|
{
|
|
1799
1904
|
nodeId: ctx.nodeId,
|
|
1800
1905
|
type: "update",
|
|
@@ -1803,7 +1908,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1803
1908
|
// Binary richtext values (new and previous) are tagged as canonical
|
|
1804
1909
|
// JSON BEFORE the operation is content-hashed, so the hash input,
|
|
1805
1910
|
// persisted JSON, and wire payload are the identical value.
|
|
1806
|
-
data: encodeRichtextFieldsForOpData(
|
|
1911
|
+
data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
|
|
1807
1912
|
previousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),
|
|
1808
1913
|
sequenceNumber,
|
|
1809
1914
|
causalDeps,
|
|
@@ -1813,7 +1918,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1813
1918
|
ctx.clock
|
|
1814
1919
|
);
|
|
1815
1920
|
ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
|
|
1816
|
-
const serializedChanges = serializeRecord(
|
|
1921
|
+
const serializedChanges = serializeRecord(writeData, ctx.definition.fields);
|
|
1817
1922
|
const version = serializeRowVersion(operation.timestamp);
|
|
1818
1923
|
const mergedFieldVersions = parseFieldVersions(currentRow._field_versions);
|
|
1819
1924
|
for (const changedField of Object.keys(serializedChanges)) {
|
|
@@ -1834,7 +1939,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1834
1939
|
});
|
|
1835
1940
|
ctx.onMutation(ctx.collection, operation);
|
|
1836
1941
|
const rows = await ctx.adapter.query(
|
|
1837
|
-
`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
|
|
1942
|
+
`SELECT * FROM ${(0, import_core12.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
|
|
1838
1943
|
[id]
|
|
1839
1944
|
);
|
|
1840
1945
|
const row = rows[0];
|
|
@@ -1847,7 +1952,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1847
1952
|
// src/collection/collection.ts
|
|
1848
1953
|
init_serializer();
|
|
1849
1954
|
var Collection = class {
|
|
1850
|
-
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
|
|
1955
|
+
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
|
|
1851
1956
|
this.name = name;
|
|
1852
1957
|
this.definition = definition;
|
|
1853
1958
|
this.schema = schema;
|
|
@@ -1859,6 +1964,7 @@ var Collection = class {
|
|
|
1859
1964
|
this.relationEnforcer = relationEnforcer;
|
|
1860
1965
|
this.mutationHandler = mutationHandler;
|
|
1861
1966
|
this.causalTracker = causalTracker;
|
|
1967
|
+
this.secretKeyProvider = secretKeyProvider;
|
|
1862
1968
|
}
|
|
1863
1969
|
name;
|
|
1864
1970
|
definition;
|
|
@@ -1871,6 +1977,7 @@ var Collection = class {
|
|
|
1871
1977
|
relationEnforcer;
|
|
1872
1978
|
mutationHandler;
|
|
1873
1979
|
causalTracker;
|
|
1980
|
+
secretKeyProvider;
|
|
1874
1981
|
mutationContext() {
|
|
1875
1982
|
return {
|
|
1876
1983
|
collection: this.name,
|
|
@@ -1883,7 +1990,8 @@ var Collection = class {
|
|
|
1883
1990
|
onMutation: this.onMutation,
|
|
1884
1991
|
relationEnforcer: this.relationEnforcer,
|
|
1885
1992
|
causalTracker: this.causalTracker,
|
|
1886
|
-
inTransaction: false
|
|
1993
|
+
inTransaction: false,
|
|
1994
|
+
secretKeyProvider: this.secretKeyProvider
|
|
1887
1995
|
};
|
|
1888
1996
|
}
|
|
1889
1997
|
async insert(data) {
|
|
@@ -1894,7 +2002,7 @@ var Collection = class {
|
|
|
1894
2002
|
}
|
|
1895
2003
|
async findById(id) {
|
|
1896
2004
|
const rows = await this.adapter.query(
|
|
1897
|
-
`SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
|
|
2005
|
+
`SELECT * FROM ${(0, import_core13.quoteIdent)(this.name)} WHERE id = ? AND _deleted = 0`,
|
|
1898
2006
|
[id]
|
|
1899
2007
|
);
|
|
1900
2008
|
const row = rows[0];
|
|
@@ -1927,6 +2035,7 @@ var Collection = class {
|
|
|
1927
2035
|
};
|
|
1928
2036
|
|
|
1929
2037
|
// src/query/query-builder.ts
|
|
2038
|
+
var import_core14 = require("@korajs/core");
|
|
1930
2039
|
init_serializer();
|
|
1931
2040
|
|
|
1932
2041
|
// src/query/pluralize.ts
|
|
@@ -2158,7 +2267,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
2158
2267
|
const relatedDef = this.schema?.collections[relatedCollection];
|
|
2159
2268
|
if (!relatedDef) return;
|
|
2160
2269
|
const placeholders = uniqueFks.map(() => "?").join(", ");
|
|
2161
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
2270
|
+
const sql = `SELECT * FROM ${(0, import_core14.quoteIdent)(relatedCollection)} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
2162
2271
|
const rows = await this.adapter.query(sql, uniqueFks);
|
|
2163
2272
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
2164
2273
|
const lookup = /* @__PURE__ */ new Map();
|
|
@@ -2181,7 +2290,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
2181
2290
|
if (!relatedDef) return;
|
|
2182
2291
|
const fkField = relation.field;
|
|
2183
2292
|
const placeholders = primaryIds.map(() => "?").join(", ");
|
|
2184
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE ${fkField} IN (${placeholders}) AND _deleted = 0`;
|
|
2293
|
+
const sql = `SELECT * FROM ${(0, import_core14.quoteIdent)(relatedCollection)} WHERE ${(0, import_core14.quoteIdent)(fkField)} IN (${placeholders}) AND _deleted = 0`;
|
|
2185
2294
|
const rows = await this.adapter.query(sql, primaryIds);
|
|
2186
2295
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
2187
2296
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -2223,7 +2332,7 @@ var QueryError2 = class extends Error {
|
|
|
2223
2332
|
};
|
|
2224
2333
|
|
|
2225
2334
|
// src/relations/relation-enforcer.ts
|
|
2226
|
-
var
|
|
2335
|
+
var import_core15 = require("@korajs/core");
|
|
2227
2336
|
init_serializer();
|
|
2228
2337
|
|
|
2229
2338
|
// src/relations/relation-lookup.ts
|
|
@@ -2248,7 +2357,7 @@ function getIncomingRelations(lookup, collection) {
|
|
|
2248
2357
|
}
|
|
2249
2358
|
|
|
2250
2359
|
// src/relations/relation-enforcer.ts
|
|
2251
|
-
var ReferentialIntegrityError = class extends
|
|
2360
|
+
var ReferentialIntegrityError = class extends import_core15.KoraError {
|
|
2252
2361
|
constructor(collection, recordId, referencingCollection, relationName, referencingCount) {
|
|
2253
2362
|
super(
|
|
2254
2363
|
`Cannot delete record "${recordId}" from "${collection}": ${referencingCount} record(s) in "${referencingCollection}" reference it via relation "${relationName}" with onDelete: 'restrict'. Delete or reassign the referencing records first.`,
|
|
@@ -2327,7 +2436,7 @@ var RelationEnforcer = class {
|
|
|
2327
2436
|
async enforceCascade(incoming, deletedRecordId, tx, causalDeps) {
|
|
2328
2437
|
const { sourceCollection, foreignKeyField } = incoming;
|
|
2329
2438
|
const referencingRows = await tx.query(
|
|
2330
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
2439
|
+
`SELECT id FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
|
|
2331
2440
|
[deletedRecordId]
|
|
2332
2441
|
);
|
|
2333
2442
|
if (referencingRows.length === 0) {
|
|
@@ -2337,7 +2446,7 @@ var RelationEnforcer = class {
|
|
|
2337
2446
|
for (const row of referencingRows) {
|
|
2338
2447
|
const now = Date.now();
|
|
2339
2448
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
|
|
2340
|
-
const operation = await (0,
|
|
2449
|
+
const operation = await (0, import_core15.createOperation)(
|
|
2341
2450
|
{
|
|
2342
2451
|
nodeId: this.nodeId,
|
|
2343
2452
|
type: "delete",
|
|
@@ -2374,7 +2483,7 @@ var RelationEnforcer = class {
|
|
|
2374
2483
|
return [];
|
|
2375
2484
|
}
|
|
2376
2485
|
const referencingRows = await tx.query(
|
|
2377
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
2486
|
+
`SELECT id FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
|
|
2378
2487
|
[deletedRecordId]
|
|
2379
2488
|
);
|
|
2380
2489
|
if (referencingRows.length === 0) {
|
|
@@ -2386,7 +2495,7 @@ var RelationEnforcer = class {
|
|
|
2386
2495
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
|
|
2387
2496
|
const updateData = { [foreignKeyField]: null };
|
|
2388
2497
|
const previousData = { [foreignKeyField]: deletedRecordId };
|
|
2389
|
-
const operation = await (0,
|
|
2498
|
+
const operation = await (0, import_core15.createOperation)(
|
|
2390
2499
|
{
|
|
2391
2500
|
nodeId: this.nodeId,
|
|
2392
2501
|
type: "update",
|
|
@@ -2422,7 +2531,7 @@ var RelationEnforcer = class {
|
|
|
2422
2531
|
async enforceRestrict(incoming, deletedRecordId, tx) {
|
|
2423
2532
|
const { sourceCollection, foreignKeyField, relationName } = incoming;
|
|
2424
2533
|
const countRows = await tx.query(
|
|
2425
|
-
`SELECT COUNT(*) as cnt FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
2534
|
+
`SELECT COUNT(*) as cnt FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
|
|
2426
2535
|
[deletedRecordId]
|
|
2427
2536
|
);
|
|
2428
2537
|
const count = countRows[0]?.cnt ?? 0;
|
|
@@ -2447,7 +2556,7 @@ var RelationEnforcer = class {
|
|
|
2447
2556
|
};
|
|
2448
2557
|
|
|
2449
2558
|
// src/replay/replay-to.ts
|
|
2450
|
-
var
|
|
2559
|
+
var import_core16 = require("@korajs/core");
|
|
2451
2560
|
var import_internal = require("@korajs/core/internal");
|
|
2452
2561
|
init_op_data_encoding();
|
|
2453
2562
|
function collectCausalClosure(allOps, targetOperationId) {
|
|
@@ -2457,7 +2566,7 @@ function collectCausalClosure(allOps, targetOperationId) {
|
|
|
2457
2566
|
}
|
|
2458
2567
|
const target = opMap.get(targetOperationId);
|
|
2459
2568
|
if (!target) {
|
|
2460
|
-
throw new
|
|
2569
|
+
throw new import_core16.OperationError(
|
|
2461
2570
|
`Operation "${targetOperationId}" not found in the local operation log`,
|
|
2462
2571
|
{
|
|
2463
2572
|
operationId: targetOperationId
|
|
@@ -2489,7 +2598,7 @@ function buildReplaySnapshot(schema, allOps, targetOperationId) {
|
|
|
2489
2598
|
const operationsApplied = collectCausalClosure(allOps, targetOperationId);
|
|
2490
2599
|
const targetOperation = operationsApplied.find((op) => op.id === targetOperationId);
|
|
2491
2600
|
if (!targetOperation) {
|
|
2492
|
-
throw new
|
|
2601
|
+
throw new import_core16.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
|
|
2493
2602
|
operationId: targetOperationId
|
|
2494
2603
|
});
|
|
2495
2604
|
}
|
|
@@ -2615,7 +2724,7 @@ function toCollectionRecord(record, definition) {
|
|
|
2615
2724
|
}
|
|
2616
2725
|
|
|
2617
2726
|
// src/sequences/sequence-manager.ts
|
|
2618
|
-
var
|
|
2727
|
+
var import_core17 = require("@korajs/core");
|
|
2619
2728
|
var SequenceManager = class {
|
|
2620
2729
|
adapter;
|
|
2621
2730
|
nodeId;
|
|
@@ -2633,7 +2742,7 @@ var SequenceManager = class {
|
|
|
2633
2742
|
async next(name, config) {
|
|
2634
2743
|
const scope = config?.scope ?? "";
|
|
2635
2744
|
const startAt = config?.startAt ?? 1;
|
|
2636
|
-
const format = config?.format ?? (0,
|
|
2745
|
+
const format = config?.format ?? (0, import_core17.defaultSequenceFormat)(name);
|
|
2637
2746
|
let counter = 0;
|
|
2638
2747
|
await this.adapter.transaction(async (tx) => {
|
|
2639
2748
|
const rows = await tx.query(
|
|
@@ -2655,7 +2764,7 @@ var SequenceManager = class {
|
|
|
2655
2764
|
);
|
|
2656
2765
|
}
|
|
2657
2766
|
});
|
|
2658
|
-
return (0,
|
|
2767
|
+
return (0, import_core17.formatSequenceValue)(format, counter, this.nodeId);
|
|
2659
2768
|
}
|
|
2660
2769
|
/**
|
|
2661
2770
|
* Get the current counter value without incrementing.
|
|
@@ -3114,10 +3223,10 @@ var SubscriptionManager = class {
|
|
|
3114
3223
|
};
|
|
3115
3224
|
|
|
3116
3225
|
// src/sync/rebase-unsynced-operations.ts
|
|
3117
|
-
var
|
|
3226
|
+
var import_core18 = require("@korajs/core");
|
|
3118
3227
|
var import_internal2 = require("@korajs/core/internal");
|
|
3119
3228
|
init_serializer();
|
|
3120
|
-
var LOGICAL_COUNTER_LIMIT =
|
|
3229
|
+
var LOGICAL_COUNTER_LIMIT = import_core18.MAX_LOGICAL + 1;
|
|
3121
3230
|
function emptyResult() {
|
|
3122
3231
|
return { operations: [], idMapping: {}, rebasedCount: 0, newMaxTimestamp: null };
|
|
3123
3232
|
}
|
|
@@ -3129,7 +3238,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3129
3238
|
const rebased = [];
|
|
3130
3239
|
let maxOtherSerializedTs = null;
|
|
3131
3240
|
for (const collectionName of Object.keys(schema.collections)) {
|
|
3132
|
-
const rows = await adapter.query(
|
|
3241
|
+
const rows = await adapter.query(
|
|
3242
|
+
`SELECT * FROM ${(0, import_core18.quoteIdent)(`_kora_ops_${collectionName}`)}`
|
|
3243
|
+
);
|
|
3133
3244
|
for (const row of rows) {
|
|
3134
3245
|
if (rebaseIds.has(row.id)) {
|
|
3135
3246
|
rebased.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -3141,9 +3252,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3141
3252
|
if (rebased.length === 0) {
|
|
3142
3253
|
return emptyResult();
|
|
3143
3254
|
}
|
|
3144
|
-
const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY :
|
|
3255
|
+
const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY : import_core18.HybridLogicalClock.deserialize(maxOtherSerializedTs).wallTime;
|
|
3145
3256
|
const baseWallTime = Math.max(correctedNowMs, maxOtherWallTime + 1);
|
|
3146
|
-
rebased.sort((a, b) =>
|
|
3257
|
+
rebased.sort((a, b) => import_core18.HybridLogicalClock.compare(a.timestamp, b.timestamp));
|
|
3147
3258
|
const newTimestamps = rebased.map((op, i) => ({
|
|
3148
3259
|
// Spill into wallTime past the 5-digit logical serialization limit so the
|
|
3149
3260
|
// serialized order stays correct even for enormous offline queues.
|
|
@@ -3171,7 +3282,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3171
3282
|
schemaVersion: op.schemaVersion,
|
|
3172
3283
|
...op.atomicOps !== void 0 ? { atomicOps: op.atomicOps } : {}
|
|
3173
3284
|
};
|
|
3174
|
-
const newId = await (0, import_internal2.computeOperationId)(input,
|
|
3285
|
+
const newId = await (0, import_internal2.computeOperationId)(input, import_core18.HybridLogicalClock.serialize(ts));
|
|
3175
3286
|
idMapping[op.id] = newId;
|
|
3176
3287
|
newIds.push(newId);
|
|
3177
3288
|
}
|
|
@@ -3198,7 +3309,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3198
3309
|
continue;
|
|
3199
3310
|
}
|
|
3200
3311
|
const opsTable = `_kora_ops_${oldOp.collection}`;
|
|
3201
|
-
await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
|
|
3312
|
+
await tx.execute(`DELETE FROM ${(0, import_core18.quoteIdent)(opsTable)} WHERE id = ?`, [oldOp.id]);
|
|
3202
3313
|
const opInsert = buildInsertQuery(
|
|
3203
3314
|
opsTable,
|
|
3204
3315
|
serializeOperation(newOp)
|
|
@@ -3207,11 +3318,11 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3207
3318
|
const oldVersion = serializeRowVersion(oldOp.timestamp);
|
|
3208
3319
|
const newVersion = serializeRowVersion(newOp.timestamp);
|
|
3209
3320
|
await tx.execute(
|
|
3210
|
-
`UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
3321
|
+
`UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
3211
3322
|
[newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
|
|
3212
3323
|
);
|
|
3213
3324
|
const rows = await tx.query(
|
|
3214
|
-
`SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
|
|
3325
|
+
`SELECT _field_versions FROM ${(0, import_core18.quoteIdent)(oldOp.collection)} WHERE id = ?`,
|
|
3215
3326
|
[oldOp.recordId]
|
|
3216
3327
|
);
|
|
3217
3328
|
const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
|
|
@@ -3223,14 +3334,14 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3223
3334
|
}
|
|
3224
3335
|
}
|
|
3225
3336
|
if (changed) {
|
|
3226
|
-
await tx.execute(
|
|
3227
|
-
|
|
3228
|
-
oldOp.recordId
|
|
3229
|
-
|
|
3337
|
+
await tx.execute(
|
|
3338
|
+
`UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _field_versions = ? WHERE id = ?`,
|
|
3339
|
+
[serializeFieldVersions(fieldVersions), oldOp.recordId]
|
|
3340
|
+
);
|
|
3230
3341
|
}
|
|
3231
3342
|
if (oldOp.type === "insert") {
|
|
3232
3343
|
await tx.execute(
|
|
3233
|
-
`UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
3344
|
+
`UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
3234
3345
|
[newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
|
|
3235
3346
|
);
|
|
3236
3347
|
}
|
|
@@ -3246,7 +3357,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3246
3357
|
}
|
|
3247
3358
|
|
|
3248
3359
|
// src/transaction/transaction-context.ts
|
|
3249
|
-
var
|
|
3360
|
+
var import_core19 = require("@korajs/core");
|
|
3250
3361
|
init_op_data_encoding();
|
|
3251
3362
|
init_serializer();
|
|
3252
3363
|
var TransactionContext = class {
|
|
@@ -3258,7 +3369,7 @@ var TransactionContext = class {
|
|
|
3258
3369
|
config;
|
|
3259
3370
|
constructor(config) {
|
|
3260
3371
|
this.config = config;
|
|
3261
|
-
this.transactionId = (0,
|
|
3372
|
+
this.transactionId = (0, import_core19.generateUUIDv7)();
|
|
3262
3373
|
}
|
|
3263
3374
|
/**
|
|
3264
3375
|
* Set a human-readable mutation name for this transaction.
|
|
@@ -3373,8 +3484,8 @@ var TransactionContext = class {
|
|
|
3373
3484
|
}
|
|
3374
3485
|
async insert(collectionName, definition, data) {
|
|
3375
3486
|
this.ensureActive();
|
|
3376
|
-
const validated = (0,
|
|
3377
|
-
const recordId = (0,
|
|
3487
|
+
const validated = (0, import_core19.validateRecord)(collectionName, definition, data, "insert");
|
|
3488
|
+
const recordId = (0, import_core19.generateUUIDv7)();
|
|
3378
3489
|
const now = Date.now();
|
|
3379
3490
|
for (const [fieldName, descriptor] of Object.entries(definition.fields)) {
|
|
3380
3491
|
if (descriptor.auto && descriptor.kind === "timestamp") {
|
|
@@ -3383,7 +3494,7 @@ var TransactionContext = class {
|
|
|
3383
3494
|
}
|
|
3384
3495
|
const sequenceNumber = await this.config.sequenceAllocator.allocate();
|
|
3385
3496
|
const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
|
|
3386
|
-
const operation = await (0,
|
|
3497
|
+
const operation = await (0, import_core19.createOperation)(
|
|
3387
3498
|
{
|
|
3388
3499
|
nodeId: this.config.nodeId,
|
|
3389
3500
|
type: "insert",
|
|
@@ -3438,7 +3549,7 @@ var TransactionContext = class {
|
|
|
3438
3549
|
if (!currentRecord) {
|
|
3439
3550
|
throw new RecordNotFoundError(collectionName, id);
|
|
3440
3551
|
}
|
|
3441
|
-
const validated = (0,
|
|
3552
|
+
const validated = (0, import_core19.validateRecord)(collectionName, definition, data, "update");
|
|
3442
3553
|
const now = Date.now();
|
|
3443
3554
|
const previousData = {};
|
|
3444
3555
|
const resolvedData = {};
|
|
@@ -3446,9 +3557,9 @@ var TransactionContext = class {
|
|
|
3446
3557
|
for (const key of Object.keys(validated)) {
|
|
3447
3558
|
const value = validated[key];
|
|
3448
3559
|
previousData[key] = currentRecord[key];
|
|
3449
|
-
if ((0,
|
|
3450
|
-
resolvedData[key] = (0,
|
|
3451
|
-
atomicOps[key] = (0,
|
|
3560
|
+
if ((0, import_core19.isAtomicOp)(value)) {
|
|
3561
|
+
resolvedData[key] = (0, import_core19.resolveAtomicOp)(currentRecord[key], value);
|
|
3562
|
+
atomicOps[key] = (0, import_core19.toAtomicOp)(value);
|
|
3452
3563
|
} else {
|
|
3453
3564
|
resolvedData[key] = value;
|
|
3454
3565
|
}
|
|
@@ -3456,7 +3567,7 @@ var TransactionContext = class {
|
|
|
3456
3567
|
const hasAtomicOps = Object.keys(atomicOps).length > 0;
|
|
3457
3568
|
const sequenceNumber = await this.config.sequenceAllocator.allocate();
|
|
3458
3569
|
const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
|
|
3459
|
-
const operation = await (0,
|
|
3570
|
+
const operation = await (0, import_core19.createOperation)(
|
|
3460
3571
|
{
|
|
3461
3572
|
nodeId: this.config.nodeId,
|
|
3462
3573
|
type: "update",
|
|
@@ -3511,7 +3622,7 @@ var TransactionContext = class {
|
|
|
3511
3622
|
const now = Date.now();
|
|
3512
3623
|
const sequenceNumber = await this.config.sequenceAllocator.allocate();
|
|
3513
3624
|
const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
|
|
3514
|
-
const operation = await (0,
|
|
3625
|
+
const operation = await (0, import_core19.createOperation)(
|
|
3515
3626
|
{
|
|
3516
3627
|
nodeId: this.config.nodeId,
|
|
3517
3628
|
type: "delete",
|
|
@@ -3561,7 +3672,7 @@ var TransactionContext = class {
|
|
|
3561
3672
|
}
|
|
3562
3673
|
}
|
|
3563
3674
|
const rows = await this.config.adapter.query(
|
|
3564
|
-
`SELECT * FROM ${collectionName} WHERE id = ? AND _deleted = 0`,
|
|
3675
|
+
`SELECT * FROM ${(0, import_core19.quoteIdent)(collectionName)} WHERE id = ? AND _deleted = 0`,
|
|
3565
3676
|
[id]
|
|
3566
3677
|
);
|
|
3567
3678
|
let record = rows[0] ? deserializeRecord(rows[0], definition.fields) : null;
|
|
@@ -3616,7 +3727,7 @@ var TransactionSequenceAllocator = class {
|
|
|
3616
3727
|
};
|
|
3617
3728
|
|
|
3618
3729
|
// src/store/tab-node-id.ts
|
|
3619
|
-
var
|
|
3730
|
+
var import_core20 = require("@korajs/core");
|
|
3620
3731
|
function perTabStorageKey(dbName) {
|
|
3621
3732
|
return `kora-tab-node-${dbName}`;
|
|
3622
3733
|
}
|
|
@@ -3637,7 +3748,7 @@ function resolvePerTabNodeId(dbName) {
|
|
|
3637
3748
|
if (existing) {
|
|
3638
3749
|
return existing;
|
|
3639
3750
|
}
|
|
3640
|
-
const nodeId = (0,
|
|
3751
|
+
const nodeId = (0, import_core20.generateUUIDv7)();
|
|
3641
3752
|
savePerTabNodeId(dbName, nodeId);
|
|
3642
3753
|
return nodeId;
|
|
3643
3754
|
}
|
|
@@ -3647,7 +3758,7 @@ var Store = class {
|
|
|
3647
3758
|
opened = false;
|
|
3648
3759
|
nodeId = "";
|
|
3649
3760
|
sequenceNumber = 0;
|
|
3650
|
-
versionVector = (0,
|
|
3761
|
+
versionVector = (0, import_core23.createVersionVector)();
|
|
3651
3762
|
clock = null;
|
|
3652
3763
|
collections = /* @__PURE__ */ new Map();
|
|
3653
3764
|
subscriptionManager;
|
|
@@ -3661,6 +3772,7 @@ var Store = class {
|
|
|
3661
3772
|
localMutationHandler;
|
|
3662
3773
|
relationEnforcer = null;
|
|
3663
3774
|
causalTracker = null;
|
|
3775
|
+
secretKeyProvider;
|
|
3664
3776
|
constructor(config) {
|
|
3665
3777
|
this.schema = config.schema;
|
|
3666
3778
|
this.adapter = config.adapter;
|
|
@@ -3669,6 +3781,7 @@ var Store = class {
|
|
|
3669
3781
|
this.isolation = config.isolation ?? "shared";
|
|
3670
3782
|
this.emitter = config.emitter ?? null;
|
|
3671
3783
|
this.localMutationHandler = config.localMutationHandler ?? null;
|
|
3784
|
+
this.secretKeyProvider = config.secretKeyProvider;
|
|
3672
3785
|
this.subscriptionManager = new SubscriptionManager({
|
|
3673
3786
|
onQuerySubscribed: config.onQuerySubscribed
|
|
3674
3787
|
});
|
|
@@ -3681,8 +3794,8 @@ var Store = class {
|
|
|
3681
3794
|
await this.adapter.open(this.schema);
|
|
3682
3795
|
await this.runMigrationsIfNeeded();
|
|
3683
3796
|
this.nodeId = await this.loadOrGenerateNodeId();
|
|
3684
|
-
this.clock = new
|
|
3685
|
-
this.causalTracker = new
|
|
3797
|
+
this.clock = new import_core23.HybridLogicalClock(this.nodeId);
|
|
3798
|
+
this.causalTracker = new import_core23.CausalTracker();
|
|
3686
3799
|
this.sequenceManager = new SequenceManager(this.adapter, this.nodeId);
|
|
3687
3800
|
this.sequenceNumber = await this.loadSequenceNumber();
|
|
3688
3801
|
this.versionVector = await this.loadVersionVector();
|
|
@@ -3711,7 +3824,8 @@ var Store = class {
|
|
|
3711
3824
|
},
|
|
3712
3825
|
this.relationEnforcer,
|
|
3713
3826
|
this.localMutationHandler,
|
|
3714
|
-
this.causalTracker
|
|
3827
|
+
this.causalTracker,
|
|
3828
|
+
this.secretKeyProvider
|
|
3715
3829
|
);
|
|
3716
3830
|
this.collections.set(name, col);
|
|
3717
3831
|
}
|
|
@@ -3793,7 +3907,7 @@ var Store = class {
|
|
|
3793
3907
|
return "skipped";
|
|
3794
3908
|
}
|
|
3795
3909
|
const existing = await this.adapter.query(
|
|
3796
|
-
`SELECT id FROM _kora_ops_${collection} WHERE id = ?`,
|
|
3910
|
+
`SELECT id FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE id = ?`,
|
|
3797
3911
|
[op.id]
|
|
3798
3912
|
);
|
|
3799
3913
|
if (existing.length > 0) {
|
|
@@ -3818,7 +3932,8 @@ var Store = class {
|
|
|
3818
3932
|
throw new OptimisticLockError(collection, op.recordId);
|
|
3819
3933
|
}
|
|
3820
3934
|
};
|
|
3821
|
-
if (
|
|
3935
|
+
if (options?.logOnly) {
|
|
3936
|
+
} else if (op.type === "insert" && materializeSource) {
|
|
3822
3937
|
const serializedData = serializeRecord(materializeSource, definition.fields);
|
|
3823
3938
|
const existing2 = await tx.query(
|
|
3824
3939
|
`SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
|
|
@@ -3840,7 +3955,7 @@ var Store = class {
|
|
|
3840
3955
|
};
|
|
3841
3956
|
const insertQuery = buildInsertQuery(collection, record);
|
|
3842
3957
|
await tx.execute(insertQuery.sql, insertQuery.params);
|
|
3843
|
-
await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
|
|
3958
|
+
await this.foldOrphanedOperations(tx, collection, definition, op.recordId, op);
|
|
3844
3959
|
} else if (row._deleted === 1) {
|
|
3845
3960
|
if (isIncomingNewerThanRow(materializeTimestamp, row)) {
|
|
3846
3961
|
const fieldChanges = {
|
|
@@ -3898,7 +4013,7 @@ var Store = class {
|
|
|
3898
4013
|
const serializedChanges = serializeRecord(materializeSource, definition.fields);
|
|
3899
4014
|
const changedFields = Object.keys(serializedChanges);
|
|
3900
4015
|
const currentRows = await tx.query(
|
|
3901
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
4016
|
+
`SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
3902
4017
|
[op.recordId]
|
|
3903
4018
|
);
|
|
3904
4019
|
const currentRow = currentRows[0];
|
|
@@ -3992,22 +4107,22 @@ var Store = class {
|
|
|
3992
4107
|
* necessarily an orphan (rows are soft-deleted, never removed, so a row
|
|
3993
4108
|
* that is absent now was never materialized).
|
|
3994
4109
|
*/
|
|
3995
|
-
async foldOrphanedOperations(tx, collection, definition, recordId) {
|
|
4110
|
+
async foldOrphanedOperations(tx, collection, definition, recordId, insertOp) {
|
|
3996
4111
|
const orphanRows = await tx.query(
|
|
3997
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
4112
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
3998
4113
|
[recordId]
|
|
3999
4114
|
);
|
|
4000
4115
|
if (orphanRows.length === 0) {
|
|
4001
4116
|
return;
|
|
4002
4117
|
}
|
|
4003
|
-
const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) =>
|
|
4118
|
+
const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
|
|
4004
4119
|
for (const orphan of orphans) {
|
|
4005
4120
|
const orphanVersion = serializeRowVersion(orphan.timestamp);
|
|
4006
4121
|
const orphanWall = orphan.timestamp.wallTime;
|
|
4007
4122
|
if (orphan.type === "update" && orphan.data) {
|
|
4008
4123
|
const orphanChanges = serializeRecord(orphan.data, definition.fields);
|
|
4009
4124
|
const rows = await tx.query(
|
|
4010
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
4125
|
+
`SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
4011
4126
|
[recordId]
|
|
4012
4127
|
);
|
|
4013
4128
|
const current = rows[0];
|
|
@@ -4041,6 +4156,37 @@ var Store = class {
|
|
|
4041
4156
|
await tx.execute(query.sql, query.params);
|
|
4042
4157
|
}
|
|
4043
4158
|
}
|
|
4159
|
+
const atomicFields = /* @__PURE__ */ new Set();
|
|
4160
|
+
for (const orphan of orphans) {
|
|
4161
|
+
if (orphan.type === "update" && orphan.atomicOps) {
|
|
4162
|
+
for (const field of Object.keys(orphan.atomicOps)) {
|
|
4163
|
+
atomicFields.add(field);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
if (atomicFields.size > 0) {
|
|
4168
|
+
const ordered = [insertOp, ...orphans].sort(
|
|
4169
|
+
(a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp)
|
|
4170
|
+
);
|
|
4171
|
+
const folded = (0, import_core23.replayOperationsForRecord)(ordered);
|
|
4172
|
+
if (folded) {
|
|
4173
|
+
const composed = {};
|
|
4174
|
+
for (const field of atomicFields) {
|
|
4175
|
+
if (field in folded) {
|
|
4176
|
+
composed[field] = folded[field];
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
const serialized = serializeRecord(composed, definition.fields);
|
|
4180
|
+
const cols = Object.keys(serialized);
|
|
4181
|
+
if (cols.length > 0) {
|
|
4182
|
+
const setClause = cols.map((c) => `${(0, import_core23.quoteIdent)(c)} = ?`).join(", ");
|
|
4183
|
+
await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET ${setClause} WHERE id = ?`, [
|
|
4184
|
+
...cols.map((c) => serialized[c]),
|
|
4185
|
+
recordId
|
|
4186
|
+
]);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4044
4190
|
}
|
|
4045
4191
|
/**
|
|
4046
4192
|
* Get operations from a node within a sequence number range.
|
|
@@ -4057,7 +4203,7 @@ var Store = class {
|
|
|
4057
4203
|
const allOps = [];
|
|
4058
4204
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
4059
4205
|
const rows = await this.adapter.query(
|
|
4060
|
-
`SELECT * FROM _kora_ops_${collectionName} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
4206
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collectionName}`)} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
4061
4207
|
[nodeId, fromSeq, toSeq]
|
|
4062
4208
|
);
|
|
4063
4209
|
for (const row of rows) {
|
|
@@ -4076,7 +4222,7 @@ var Store = class {
|
|
|
4076
4222
|
const allOps = [];
|
|
4077
4223
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
4078
4224
|
const rows = await this.adapter.query(
|
|
4079
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
4225
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
4080
4226
|
);
|
|
4081
4227
|
for (const row of rows) {
|
|
4082
4228
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -4145,6 +4291,22 @@ var Store = class {
|
|
|
4145
4291
|
col.setMutationHandler(handler);
|
|
4146
4292
|
}
|
|
4147
4293
|
}
|
|
4294
|
+
/**
|
|
4295
|
+
* Notify this store that another same-origin runtime committed an operation to
|
|
4296
|
+
* the shared local database. The operation has already been persisted and
|
|
4297
|
+
* materialized by the database owner; this method only advances in-memory causal
|
|
4298
|
+
* watermarks and invalidates local reactive queries so already-open tabs stay
|
|
4299
|
+
* live without reapplying or rewriting the operation.
|
|
4300
|
+
*/
|
|
4301
|
+
notifyExternalOperation(operation) {
|
|
4302
|
+
this.ensureOpen();
|
|
4303
|
+
const definition = this.schema.collections[operation.collection];
|
|
4304
|
+
if (!definition) {
|
|
4305
|
+
return;
|
|
4306
|
+
}
|
|
4307
|
+
this.recordOperationSequence(operation);
|
|
4308
|
+
this.subscriptionManager.notify(operation.collection, operation);
|
|
4309
|
+
}
|
|
4148
4310
|
/**
|
|
4149
4311
|
* Build mutation context for a collection (used by ApplyPipeline side effects).
|
|
4150
4312
|
*/
|
|
@@ -4172,7 +4334,8 @@ var Store = class {
|
|
|
4172
4334
|
relationEnforcer: this.relationEnforcer,
|
|
4173
4335
|
causalTracker: this.causalTracker,
|
|
4174
4336
|
inTransaction: options?.inTransaction ?? false,
|
|
4175
|
-
extraCausalDeps: options?.extraCausalDeps
|
|
4337
|
+
extraCausalDeps: options?.extraCausalDeps,
|
|
4338
|
+
secretKeyProvider: this.secretKeyProvider
|
|
4176
4339
|
};
|
|
4177
4340
|
}
|
|
4178
4341
|
/**
|
|
@@ -4185,7 +4348,7 @@ var Store = class {
|
|
|
4185
4348
|
return null;
|
|
4186
4349
|
}
|
|
4187
4350
|
const rows = await this.adapter.query(
|
|
4188
|
-
`SELECT * FROM ${collection} WHERE id = ?`,
|
|
4351
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
4189
4352
|
[recordId]
|
|
4190
4353
|
);
|
|
4191
4354
|
const row = rows[0];
|
|
@@ -4210,7 +4373,7 @@ var Store = class {
|
|
|
4210
4373
|
return null;
|
|
4211
4374
|
}
|
|
4212
4375
|
const rows = await this.adapter.query(
|
|
4213
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
4376
|
+
`SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
4214
4377
|
[recordId]
|
|
4215
4378
|
);
|
|
4216
4379
|
const row = rows[0];
|
|
@@ -4253,6 +4416,35 @@ var Store = class {
|
|
|
4253
4416
|
this.ensureOpen();
|
|
4254
4417
|
await saveDeltaCursor(this.adapter, cursor);
|
|
4255
4418
|
}
|
|
4419
|
+
/**
|
|
4420
|
+
* Load the persisted delivery watermark for a view signature (0 when none recorded).
|
|
4421
|
+
* Defaults to the unfiltered view so existing single-watermark reads keep working.
|
|
4422
|
+
*/
|
|
4423
|
+
async loadDeliveryWatermark(signature = "") {
|
|
4424
|
+
this.ensureOpen();
|
|
4425
|
+
return loadDeliveryWatermark(this.adapter, signature);
|
|
4426
|
+
}
|
|
4427
|
+
/**
|
|
4428
|
+
* Persist the delivery watermark for a view signature so it survives a client restart.
|
|
4429
|
+
*/
|
|
4430
|
+
async saveDeliveryWatermark(signature, watermark) {
|
|
4431
|
+
this.ensureOpen();
|
|
4432
|
+
await saveDeliveryWatermark(this.adapter, signature, watermark);
|
|
4433
|
+
}
|
|
4434
|
+
/**
|
|
4435
|
+
* Load every persisted view watermark, keyed by signature.
|
|
4436
|
+
*/
|
|
4437
|
+
async loadAllDeliveryWatermarks() {
|
|
4438
|
+
this.ensureOpen();
|
|
4439
|
+
return loadAllDeliveryWatermarks(this.adapter);
|
|
4440
|
+
}
|
|
4441
|
+
/**
|
|
4442
|
+
* Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
|
|
4443
|
+
*/
|
|
4444
|
+
async deleteDeliveryWatermark(signature) {
|
|
4445
|
+
this.ensureOpen();
|
|
4446
|
+
await deleteDeliveryWatermark(this.adapter, signature);
|
|
4447
|
+
}
|
|
4256
4448
|
/**
|
|
4257
4449
|
* Local operations not yet reflected on the server version vector.
|
|
4258
4450
|
*/
|
|
@@ -4302,7 +4494,7 @@ var Store = class {
|
|
|
4302
4494
|
async compact(strategy) {
|
|
4303
4495
|
this.ensureOpen();
|
|
4304
4496
|
if (strategy.mode === "never") {
|
|
4305
|
-
return compactOperationLog(this.adapter, this.schema, strategy, (0,
|
|
4497
|
+
return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core23.createVersionVector)());
|
|
4306
4498
|
}
|
|
4307
4499
|
const serverVector = strategy.serverVector ?? await loadLastAckedServerVector(this.adapter);
|
|
4308
4500
|
return compactOperationLog(this.adapter, this.schema, strategy, serverVector);
|
|
@@ -4316,7 +4508,7 @@ var Store = class {
|
|
|
4316
4508
|
async getLatestLocalOperationForRecord(collection, recordId) {
|
|
4317
4509
|
this.ensureOpen();
|
|
4318
4510
|
const rows = await this.adapter.query(
|
|
4319
|
-
`SELECT * FROM _kora_ops_${collection} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
4511
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
4320
4512
|
[this.nodeId, recordId]
|
|
4321
4513
|
);
|
|
4322
4514
|
const row = rows[0];
|
|
@@ -4331,18 +4523,33 @@ var Store = class {
|
|
|
4331
4523
|
async getLatestOperationForRecord(collection, recordId) {
|
|
4332
4524
|
this.ensureOpen();
|
|
4333
4525
|
const rows = await this.adapter.query(
|
|
4334
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
|
|
4526
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
|
|
4335
4527
|
[recordId]
|
|
4336
4528
|
);
|
|
4337
4529
|
let latest = null;
|
|
4338
4530
|
for (const row of rows) {
|
|
4339
4531
|
const op = deserializeOperationWithCollection(row, collection);
|
|
4340
|
-
if (!latest ||
|
|
4532
|
+
if (!latest || import_core23.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
|
|
4341
4533
|
latest = op;
|
|
4342
4534
|
}
|
|
4343
4535
|
}
|
|
4344
4536
|
return latest;
|
|
4345
4537
|
}
|
|
4538
|
+
/**
|
|
4539
|
+
* All operations for a record, sorted in HLC total order (wallTime, logical,
|
|
4540
|
+
* nodeId). Used by the apply pipeline to materialize atomic-op fields by folding
|
|
4541
|
+
* the record's log, matching the server's materialization exactly.
|
|
4542
|
+
*/
|
|
4543
|
+
async getOperationsForRecord(collection, recordId) {
|
|
4544
|
+
this.ensureOpen();
|
|
4545
|
+
const rows = await this.adapter.query(
|
|
4546
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
|
|
4547
|
+
[recordId]
|
|
4548
|
+
);
|
|
4549
|
+
const ops = rows.map((row) => deserializeOperationWithCollection(row, collection));
|
|
4550
|
+
ops.sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
|
|
4551
|
+
return ops;
|
|
4552
|
+
}
|
|
4346
4553
|
/** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
|
|
4347
4554
|
getSubscriptionManager() {
|
|
4348
4555
|
return this.subscriptionManager;
|
|
@@ -4480,7 +4687,7 @@ var Store = class {
|
|
|
4480
4687
|
for (let v = storedVersion + 1; v <= targetVersion; v++) {
|
|
4481
4688
|
const migration = migrations[v];
|
|
4482
4689
|
if (!migration) continue;
|
|
4483
|
-
const sqlStatements = (0,
|
|
4690
|
+
const sqlStatements = (0, import_core23.migrationStepsToSQL)(migration.steps);
|
|
4484
4691
|
for (const sql of sqlStatements) {
|
|
4485
4692
|
try {
|
|
4486
4693
|
await this.adapter.execute(sql);
|
|
@@ -4518,14 +4725,14 @@ var Store = class {
|
|
|
4518
4725
|
*/
|
|
4519
4726
|
async runBackfill(collection, transform) {
|
|
4520
4727
|
const rows = await this.adapter.query(
|
|
4521
|
-
`SELECT * FROM ${collection} WHERE _deleted = 0`
|
|
4728
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE _deleted = 0`
|
|
4522
4729
|
);
|
|
4523
4730
|
await this.adapter.transaction(async (tx) => {
|
|
4524
4731
|
for (const row of rows) {
|
|
4525
4732
|
const updates = transform(row);
|
|
4526
4733
|
const fields = Object.keys(updates);
|
|
4527
4734
|
if (fields.length === 0) continue;
|
|
4528
|
-
const setClauses = fields.map((f) => `${f} = ?`).join(", ");
|
|
4735
|
+
const setClauses = fields.map((f) => `${(0, import_core23.quoteIdent)(f)} = ?`).join(", ");
|
|
4529
4736
|
const values = fields.map((f) => {
|
|
4530
4737
|
const val = updates[f];
|
|
4531
4738
|
if (typeof val === "boolean") return val ? 1 : 0;
|
|
@@ -4535,7 +4742,7 @@ var Store = class {
|
|
|
4535
4742
|
return val;
|
|
4536
4743
|
});
|
|
4537
4744
|
values.push(row.id);
|
|
4538
|
-
await tx.execute(`UPDATE ${collection} SET ${setClauses} WHERE id = ?`, values);
|
|
4745
|
+
await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET ${setClauses} WHERE id = ?`, values);
|
|
4539
4746
|
}
|
|
4540
4747
|
});
|
|
4541
4748
|
}
|
|
@@ -4558,7 +4765,7 @@ var Store = class {
|
|
|
4558
4765
|
if (rows[0]) {
|
|
4559
4766
|
return rows[0].value;
|
|
4560
4767
|
}
|
|
4561
|
-
const newNodeId = (0,
|
|
4768
|
+
const newNodeId = (0, import_core23.generateUUIDv7)();
|
|
4562
4769
|
await this.adapter.execute("INSERT INTO _kora_meta (key, value) VALUES ('node_id', ?)", [
|
|
4563
4770
|
newNodeId
|
|
4564
4771
|
]);
|
|
@@ -4575,7 +4782,7 @@ var Store = class {
|
|
|
4575
4782
|
const rows = await this.adapter.query(
|
|
4576
4783
|
"SELECT node_id, sequence_number FROM _kora_version_vector"
|
|
4577
4784
|
);
|
|
4578
|
-
const vector = (0,
|
|
4785
|
+
const vector = (0, import_core23.createVersionVector)();
|
|
4579
4786
|
for (const row of rows) {
|
|
4580
4787
|
vector.set(row.node_id, row.sequence_number);
|
|
4581
4788
|
}
|
|
@@ -4595,6 +4802,7 @@ var QueryStore = class {
|
|
|
4595
4802
|
listeners = /* @__PURE__ */ new Set();
|
|
4596
4803
|
unsubscribeQuery = null;
|
|
4597
4804
|
active = false;
|
|
4805
|
+
hasEmittedSnapshot = false;
|
|
4598
4806
|
queryBuilder;
|
|
4599
4807
|
constructor(queryBuilder) {
|
|
4600
4808
|
this.queryBuilder = queryBuilder;
|
|
@@ -4620,16 +4828,25 @@ var QueryStore = class {
|
|
|
4620
4828
|
getSnapshot = () => {
|
|
4621
4829
|
return this.snapshot;
|
|
4622
4830
|
};
|
|
4831
|
+
/**
|
|
4832
|
+
* Whether the underlying query subscription has delivered at least one
|
|
4833
|
+
* authoritative result for the current subscription lifecycle.
|
|
4834
|
+
*/
|
|
4835
|
+
hasSnapshot = () => {
|
|
4836
|
+
return this.hasEmittedSnapshot;
|
|
4837
|
+
};
|
|
4623
4838
|
/** Tear down listeners and the underlying query subscription. */
|
|
4624
4839
|
destroy() {
|
|
4625
4840
|
this.stopSubscription();
|
|
4626
4841
|
this.listeners.clear();
|
|
4627
4842
|
this.snapshot = EMPTY_ARRAY;
|
|
4843
|
+
this.hasEmittedSnapshot = false;
|
|
4628
4844
|
}
|
|
4629
4845
|
startSubscription() {
|
|
4630
4846
|
this.active = true;
|
|
4631
4847
|
this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
|
|
4632
4848
|
if (!this.active) return;
|
|
4849
|
+
this.hasEmittedSnapshot = true;
|
|
4633
4850
|
this.snapshot = Object.freeze([...results]);
|
|
4634
4851
|
this.notifyListeners();
|
|
4635
4852
|
});
|
|
@@ -4649,12 +4866,12 @@ var QueryStore = class {
|
|
|
4649
4866
|
};
|
|
4650
4867
|
|
|
4651
4868
|
// src/reactivity/assert-query-ready.ts
|
|
4652
|
-
var
|
|
4869
|
+
var import_core24 = require("@korajs/core");
|
|
4653
4870
|
var LEGACY_PENDING_COLLECTION = "__pending__";
|
|
4654
4871
|
function assertQueryReady(query) {
|
|
4655
4872
|
const descriptor = query.getDescriptor();
|
|
4656
4873
|
if (descriptor.collection === LEGACY_PENDING_COLLECTION) {
|
|
4657
|
-
throw new
|
|
4874
|
+
throw new import_core24.AppNotReadyError(
|
|
4658
4875
|
"Cannot use useQuery() before app.ready. Await app.ready or wrap your UI in <KoraProvider app={app}>."
|
|
4659
4876
|
);
|
|
4660
4877
|
}
|
|
@@ -5075,13 +5292,570 @@ init_backup();
|
|
|
5075
5292
|
init_audit_trace_store();
|
|
5076
5293
|
init_audit_json();
|
|
5077
5294
|
init_export_audit();
|
|
5295
|
+
|
|
5296
|
+
// src/blob/content-addressed-blob-store.ts
|
|
5297
|
+
var import_core25 = require("@korajs/core");
|
|
5298
|
+
var BlobIntegrityError = class extends Error {
|
|
5299
|
+
constructor(expectedHash, actualHash) {
|
|
5300
|
+
super(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`);
|
|
5301
|
+
this.expectedHash = expectedHash;
|
|
5302
|
+
this.actualHash = actualHash;
|
|
5303
|
+
this.name = "BlobIntegrityError";
|
|
5304
|
+
}
|
|
5305
|
+
expectedHash;
|
|
5306
|
+
actualHash;
|
|
5307
|
+
};
|
|
5308
|
+
var MemoryBlobStore = class {
|
|
5309
|
+
blobs = /* @__PURE__ */ new Map();
|
|
5310
|
+
async put(bytes, metadata = {}) {
|
|
5311
|
+
const ref = await (0, import_core25.createBlobRef)(bytes, metadata);
|
|
5312
|
+
if (!this.blobs.has(ref.hash)) {
|
|
5313
|
+
const stored = new Uint8Array(bytes.byteLength);
|
|
5314
|
+
stored.set(bytes);
|
|
5315
|
+
this.blobs.set(ref.hash, stored);
|
|
5316
|
+
}
|
|
5317
|
+
return ref;
|
|
5318
|
+
}
|
|
5319
|
+
async get(hash) {
|
|
5320
|
+
const stored = this.blobs.get(hash);
|
|
5321
|
+
if (stored === void 0) {
|
|
5322
|
+
return null;
|
|
5323
|
+
}
|
|
5324
|
+
const actualHash = await (0, import_core25.hashBlob)(stored);
|
|
5325
|
+
if (actualHash !== hash) {
|
|
5326
|
+
throw new BlobIntegrityError(hash, actualHash);
|
|
5327
|
+
}
|
|
5328
|
+
const out = new Uint8Array(stored.byteLength);
|
|
5329
|
+
out.set(stored);
|
|
5330
|
+
return out;
|
|
5331
|
+
}
|
|
5332
|
+
async has(hash) {
|
|
5333
|
+
return this.blobs.has(hash);
|
|
5334
|
+
}
|
|
5335
|
+
async delete(hash) {
|
|
5336
|
+
return this.blobs.delete(hash);
|
|
5337
|
+
}
|
|
5338
|
+
async size() {
|
|
5339
|
+
return this.blobs.size;
|
|
5340
|
+
}
|
|
5341
|
+
async list() {
|
|
5342
|
+
return [...this.blobs.keys()];
|
|
5343
|
+
}
|
|
5344
|
+
};
|
|
5345
|
+
|
|
5346
|
+
// src/blob/opfs-blob-store.ts
|
|
5347
|
+
var import_core26 = require("@korajs/core");
|
|
5348
|
+
var OpfsBlobStore = class {
|
|
5349
|
+
constructor(dir) {
|
|
5350
|
+
this.dir = dir;
|
|
5351
|
+
}
|
|
5352
|
+
dir;
|
|
5353
|
+
shardFor(hash) {
|
|
5354
|
+
return hash.slice(0, 2);
|
|
5355
|
+
}
|
|
5356
|
+
async put(bytes, metadata = {}) {
|
|
5357
|
+
const ref = await (0, import_core26.createBlobRef)(bytes, metadata);
|
|
5358
|
+
const shard = this.shardFor(ref.hash);
|
|
5359
|
+
if (await this.dir.has(shard, ref.hash)) {
|
|
5360
|
+
return ref;
|
|
5361
|
+
}
|
|
5362
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
5363
|
+
out.set(bytes);
|
|
5364
|
+
await this.dir.write(shard, ref.hash, out);
|
|
5365
|
+
return ref;
|
|
5366
|
+
}
|
|
5367
|
+
async get(hash) {
|
|
5368
|
+
const bytes = await this.dir.read(this.shardFor(hash), hash);
|
|
5369
|
+
if (bytes === null) {
|
|
5370
|
+
return null;
|
|
5371
|
+
}
|
|
5372
|
+
const actual = await (0, import_core26.hashBlob)(bytes);
|
|
5373
|
+
if (actual !== hash) {
|
|
5374
|
+
throw new BlobIntegrityError(hash, actual);
|
|
5375
|
+
}
|
|
5376
|
+
return bytes;
|
|
5377
|
+
}
|
|
5378
|
+
async has(hash) {
|
|
5379
|
+
return this.dir.has(this.shardFor(hash), hash);
|
|
5380
|
+
}
|
|
5381
|
+
async delete(hash) {
|
|
5382
|
+
return this.dir.remove(this.shardFor(hash), hash);
|
|
5383
|
+
}
|
|
5384
|
+
async size() {
|
|
5385
|
+
return this.dir.count();
|
|
5386
|
+
}
|
|
5387
|
+
async list() {
|
|
5388
|
+
return this.dir.keys();
|
|
5389
|
+
}
|
|
5390
|
+
};
|
|
5391
|
+
function getStorageManager() {
|
|
5392
|
+
const nav = globalThis.navigator;
|
|
5393
|
+
if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
|
|
5394
|
+
throw new Error(
|
|
5395
|
+
"OPFS is unavailable in this environment. The OPFS blob store requires a browser with Origin Private File System support; use MemoryBlobStore or a server-side store elsewhere."
|
|
5396
|
+
);
|
|
5397
|
+
}
|
|
5398
|
+
return nav.storage;
|
|
5399
|
+
}
|
|
5400
|
+
function isNotFoundError(error) {
|
|
5401
|
+
return typeof error === "object" && error !== null && error.name === "NotFoundError";
|
|
5402
|
+
}
|
|
5403
|
+
async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
|
|
5404
|
+
const storage = getStorageManager();
|
|
5405
|
+
if (typeof storage.persist === "function") {
|
|
5406
|
+
try {
|
|
5407
|
+
await storage.persist();
|
|
5408
|
+
} catch {
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
const opfsRoot = await storage.getDirectory();
|
|
5412
|
+
const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
|
|
5413
|
+
async function shardHandle(shard, create) {
|
|
5414
|
+
try {
|
|
5415
|
+
return await root.getDirectoryHandle(shard, { create });
|
|
5416
|
+
} catch (error) {
|
|
5417
|
+
if (isNotFoundError(error)) {
|
|
5418
|
+
return null;
|
|
5419
|
+
}
|
|
5420
|
+
throw error;
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
return {
|
|
5424
|
+
async read(shard, name) {
|
|
5425
|
+
const dir = await shardHandle(shard, false);
|
|
5426
|
+
if (!dir) {
|
|
5427
|
+
return null;
|
|
5428
|
+
}
|
|
5429
|
+
let handle;
|
|
5430
|
+
try {
|
|
5431
|
+
handle = await dir.getFileHandle(name, { create: false });
|
|
5432
|
+
} catch (error) {
|
|
5433
|
+
if (isNotFoundError(error)) {
|
|
5434
|
+
return null;
|
|
5435
|
+
}
|
|
5436
|
+
throw error;
|
|
5437
|
+
}
|
|
5438
|
+
const file = await handle.getFile();
|
|
5439
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
5440
|
+
},
|
|
5441
|
+
async write(shard, name, bytes) {
|
|
5442
|
+
const dir = await root.getDirectoryHandle(shard, { create: true });
|
|
5443
|
+
const handle = await dir.getFileHandle(name, { create: true });
|
|
5444
|
+
const writable = await handle.createWritable({ keepExistingData: false });
|
|
5445
|
+
try {
|
|
5446
|
+
await writable.write(bytes);
|
|
5447
|
+
} finally {
|
|
5448
|
+
await writable.close();
|
|
5449
|
+
}
|
|
5450
|
+
},
|
|
5451
|
+
async remove(shard, name) {
|
|
5452
|
+
const dir = await shardHandle(shard, false);
|
|
5453
|
+
if (!dir) {
|
|
5454
|
+
return false;
|
|
5455
|
+
}
|
|
5456
|
+
try {
|
|
5457
|
+
await dir.removeEntry(name);
|
|
5458
|
+
return true;
|
|
5459
|
+
} catch (error) {
|
|
5460
|
+
if (isNotFoundError(error)) {
|
|
5461
|
+
return false;
|
|
5462
|
+
}
|
|
5463
|
+
throw error;
|
|
5464
|
+
}
|
|
5465
|
+
},
|
|
5466
|
+
async has(shard, name) {
|
|
5467
|
+
const dir = await shardHandle(shard, false);
|
|
5468
|
+
if (!dir) {
|
|
5469
|
+
return false;
|
|
5470
|
+
}
|
|
5471
|
+
try {
|
|
5472
|
+
await dir.getFileHandle(name, { create: false });
|
|
5473
|
+
return true;
|
|
5474
|
+
} catch (error) {
|
|
5475
|
+
if (isNotFoundError(error)) {
|
|
5476
|
+
return false;
|
|
5477
|
+
}
|
|
5478
|
+
throw error;
|
|
5479
|
+
}
|
|
5480
|
+
},
|
|
5481
|
+
async count() {
|
|
5482
|
+
let total = 0;
|
|
5483
|
+
for await (const shard of root.keys()) {
|
|
5484
|
+
const dir = await shardHandle(shard, false);
|
|
5485
|
+
if (!dir) {
|
|
5486
|
+
continue;
|
|
5487
|
+
}
|
|
5488
|
+
for await (const name of dir.keys()) {
|
|
5489
|
+
if (!name.endsWith(".tmp")) {
|
|
5490
|
+
total++;
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
return total;
|
|
5495
|
+
},
|
|
5496
|
+
async keys() {
|
|
5497
|
+
const names = [];
|
|
5498
|
+
for await (const shard of root.keys()) {
|
|
5499
|
+
const dir = await shardHandle(shard, false);
|
|
5500
|
+
if (!dir) {
|
|
5501
|
+
continue;
|
|
5502
|
+
}
|
|
5503
|
+
for await (const name of dir.keys()) {
|
|
5504
|
+
if (!name.endsWith(".tmp")) {
|
|
5505
|
+
names.push(name);
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
return names;
|
|
5510
|
+
}
|
|
5511
|
+
};
|
|
5512
|
+
}
|
|
5513
|
+
async function createOpfsBlobStore(rootDirName = "kora-blobs") {
|
|
5514
|
+
return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
|
|
5515
|
+
}
|
|
5516
|
+
|
|
5517
|
+
// src/blob/blob-chunking.ts
|
|
5518
|
+
var import_core27 = require("@korajs/core");
|
|
5519
|
+
var DEFAULT_CHUNK_SIZE = 256 * 1024;
|
|
5520
|
+
async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
|
|
5521
|
+
if (chunkSize <= 0) {
|
|
5522
|
+
throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
|
|
5523
|
+
}
|
|
5524
|
+
const chunkHashes = [];
|
|
5525
|
+
const chunks = /* @__PURE__ */ new Map();
|
|
5526
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
5527
|
+
const end = Math.min(offset + chunkSize, bytes.byteLength);
|
|
5528
|
+
const chunk = new Uint8Array(end - offset);
|
|
5529
|
+
chunk.set(bytes.subarray(offset, end));
|
|
5530
|
+
const hash = await (0, import_core27.hashBlob)(chunk);
|
|
5531
|
+
chunkHashes.push(hash);
|
|
5532
|
+
if (!chunks.has(hash)) {
|
|
5533
|
+
chunks.set(hash, chunk);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
const blobHash = await (0, import_core27.hashBlob)(bytes);
|
|
5537
|
+
const manifest = {
|
|
5538
|
+
blobHash,
|
|
5539
|
+
size: bytes.byteLength,
|
|
5540
|
+
chunkSize,
|
|
5541
|
+
chunkHashes
|
|
5542
|
+
};
|
|
5543
|
+
if (metadata.mimeType !== void 0) {
|
|
5544
|
+
manifest.mimeType = metadata.mimeType;
|
|
5545
|
+
}
|
|
5546
|
+
if (metadata.filename !== void 0) {
|
|
5547
|
+
manifest.filename = metadata.filename;
|
|
5548
|
+
}
|
|
5549
|
+
return { manifest, chunks };
|
|
5550
|
+
}
|
|
5551
|
+
async function reassembleBlob(manifest, chunkStore) {
|
|
5552
|
+
const parts = [];
|
|
5553
|
+
let total = 0;
|
|
5554
|
+
for (const hash of manifest.chunkHashes) {
|
|
5555
|
+
const chunk = await chunkStore.get(hash);
|
|
5556
|
+
if (chunk === null) {
|
|
5557
|
+
throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
|
|
5558
|
+
}
|
|
5559
|
+
parts.push(chunk);
|
|
5560
|
+
total += chunk.byteLength;
|
|
5561
|
+
}
|
|
5562
|
+
const out = new Uint8Array(total);
|
|
5563
|
+
let offset = 0;
|
|
5564
|
+
for (const part of parts) {
|
|
5565
|
+
out.set(part, offset);
|
|
5566
|
+
offset += part.byteLength;
|
|
5567
|
+
}
|
|
5568
|
+
const actual = await (0, import_core27.hashBlob)(out);
|
|
5569
|
+
if (actual !== manifest.blobHash) {
|
|
5570
|
+
throw new BlobIntegrityError(manifest.blobHash, actual);
|
|
5571
|
+
}
|
|
5572
|
+
return out;
|
|
5573
|
+
}
|
|
5574
|
+
|
|
5575
|
+
// src/blob/blob-transfer.ts
|
|
5576
|
+
var import_core28 = require("@korajs/core");
|
|
5577
|
+
async function receiveBlob(manifest, provider, stores) {
|
|
5578
|
+
let chunksFetched = 0;
|
|
5579
|
+
let chunksSkipped = 0;
|
|
5580
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5581
|
+
for (const hash of manifest.chunkHashes) {
|
|
5582
|
+
if (seen.has(hash)) {
|
|
5583
|
+
continue;
|
|
5584
|
+
}
|
|
5585
|
+
seen.add(hash);
|
|
5586
|
+
if (await stores.chunkStore.has(hash)) {
|
|
5587
|
+
chunksSkipped++;
|
|
5588
|
+
continue;
|
|
5589
|
+
}
|
|
5590
|
+
const bytes = await provider.getChunk(hash);
|
|
5591
|
+
if (bytes === null) {
|
|
5592
|
+
throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
|
|
5593
|
+
}
|
|
5594
|
+
const actual = await (0, import_core28.hashBlob)(bytes);
|
|
5595
|
+
if (actual !== hash) {
|
|
5596
|
+
throw new BlobIntegrityError(hash, actual);
|
|
5597
|
+
}
|
|
5598
|
+
await stores.chunkStore.put(bytes);
|
|
5599
|
+
chunksFetched++;
|
|
5600
|
+
}
|
|
5601
|
+
const full = await reassembleBlob(manifest, stores.chunkStore);
|
|
5602
|
+
const ref = await stores.blobStore.put(full, {
|
|
5603
|
+
mimeType: manifest.mimeType,
|
|
5604
|
+
filename: manifest.filename
|
|
5605
|
+
});
|
|
5606
|
+
return { ref, chunksFetched, chunksSkipped };
|
|
5607
|
+
}
|
|
5608
|
+
async function prepareBlobForSend(bytes, chunkStore, options = {}) {
|
|
5609
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
5610
|
+
mimeType: options.mimeType,
|
|
5611
|
+
filename: options.filename
|
|
5612
|
+
});
|
|
5613
|
+
for (const chunk of chunks.values()) {
|
|
5614
|
+
await chunkStore.put(chunk);
|
|
5615
|
+
}
|
|
5616
|
+
const provider = {
|
|
5617
|
+
getChunk: (hash) => chunkStore.get(hash)
|
|
5618
|
+
};
|
|
5619
|
+
return { manifest, provider };
|
|
5620
|
+
}
|
|
5621
|
+
|
|
5622
|
+
// src/blob/blob-manifest-transfer.ts
|
|
5623
|
+
var import_core29 = require("@korajs/core");
|
|
5624
|
+
function serializeBlobManifest(manifest) {
|
|
5625
|
+
const canonical = {
|
|
5626
|
+
blobHash: manifest.blobHash,
|
|
5627
|
+
size: manifest.size,
|
|
5628
|
+
chunkSize: manifest.chunkSize,
|
|
5629
|
+
chunkHashes: manifest.chunkHashes
|
|
5630
|
+
};
|
|
5631
|
+
if (manifest.mimeType !== void 0) {
|
|
5632
|
+
canonical.mimeType = manifest.mimeType;
|
|
5633
|
+
}
|
|
5634
|
+
if (manifest.filename !== void 0) {
|
|
5635
|
+
canonical.filename = manifest.filename;
|
|
5636
|
+
}
|
|
5637
|
+
return new TextEncoder().encode(JSON.stringify(canonical));
|
|
5638
|
+
}
|
|
5639
|
+
function parseBlobManifest(bytes) {
|
|
5640
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
5641
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
5642
|
+
throw new Error("Invalid blob manifest: not an object");
|
|
5643
|
+
}
|
|
5644
|
+
const obj = parsed;
|
|
5645
|
+
if (typeof obj.blobHash !== "string") {
|
|
5646
|
+
throw new Error("Invalid blob manifest: missing blobHash");
|
|
5647
|
+
}
|
|
5648
|
+
if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
|
|
5649
|
+
throw new Error("Invalid blob manifest: missing size or chunkSize");
|
|
5650
|
+
}
|
|
5651
|
+
if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
|
|
5652
|
+
throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
|
|
5653
|
+
}
|
|
5654
|
+
const manifest = {
|
|
5655
|
+
blobHash: obj.blobHash,
|
|
5656
|
+
size: obj.size,
|
|
5657
|
+
chunkSize: obj.chunkSize,
|
|
5658
|
+
chunkHashes: obj.chunkHashes
|
|
5659
|
+
};
|
|
5660
|
+
if (typeof obj.mimeType === "string") {
|
|
5661
|
+
manifest.mimeType = obj.mimeType;
|
|
5662
|
+
}
|
|
5663
|
+
if (typeof obj.filename === "string") {
|
|
5664
|
+
manifest.filename = obj.filename;
|
|
5665
|
+
}
|
|
5666
|
+
return manifest;
|
|
5667
|
+
}
|
|
5668
|
+
async function putBlobForTransfer(store, bytes, options = {}) {
|
|
5669
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
5670
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
5671
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
5672
|
+
});
|
|
5673
|
+
for (const chunk of chunks.values()) {
|
|
5674
|
+
await store.put(chunk);
|
|
5675
|
+
}
|
|
5676
|
+
const baseRef = await store.put(bytes, {
|
|
5677
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
5678
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
5679
|
+
});
|
|
5680
|
+
const manifestRef = await store.put(serializeBlobManifest(manifest));
|
|
5681
|
+
return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
|
|
5682
|
+
}
|
|
5683
|
+
async function fetchBlobManifest(provider, manifestHash) {
|
|
5684
|
+
const bytes = await provider.getChunk(manifestHash);
|
|
5685
|
+
if (bytes === null) {
|
|
5686
|
+
throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
|
|
5687
|
+
}
|
|
5688
|
+
const actual = await (0, import_core29.hashBlob)(bytes);
|
|
5689
|
+
if (actual !== manifestHash) {
|
|
5690
|
+
throw new BlobIntegrityError(manifestHash, actual);
|
|
5691
|
+
}
|
|
5692
|
+
return parseBlobManifest(bytes);
|
|
5693
|
+
}
|
|
5694
|
+
async function resolveBlobManifest(provider, ref) {
|
|
5695
|
+
if (ref.manifestHash === void 0) {
|
|
5696
|
+
throw new Error(
|
|
5697
|
+
`Blob ${ref.hash} has no manifest to resolve. Store it with putBlobForTransfer (or app.blobs.put) so its reference carries a manifestHash, or pull with an explicit manifest.`
|
|
5698
|
+
);
|
|
5699
|
+
}
|
|
5700
|
+
return fetchBlobManifest(provider, ref.manifestHash);
|
|
5701
|
+
}
|
|
5702
|
+
|
|
5703
|
+
// src/blob/server-blob-callbacks.ts
|
|
5704
|
+
var import_core30 = require("@korajs/core");
|
|
5705
|
+
function toServerBlobCallbacks(store) {
|
|
5706
|
+
return {
|
|
5707
|
+
resolveBlobChunk: (hash) => store.get(hash),
|
|
5708
|
+
async persistBlobChunk(hash, bytes) {
|
|
5709
|
+
const actual = await (0, import_core30.hashBlob)(bytes);
|
|
5710
|
+
if (actual !== hash) {
|
|
5711
|
+
return;
|
|
5712
|
+
}
|
|
5713
|
+
await store.put(bytes);
|
|
5714
|
+
}
|
|
5715
|
+
};
|
|
5716
|
+
}
|
|
5717
|
+
function createMemoryServerBlobStore() {
|
|
5718
|
+
const store = new MemoryBlobStore();
|
|
5719
|
+
return { store, callbacks: toServerBlobCallbacks(store) };
|
|
5720
|
+
}
|
|
5721
|
+
|
|
5722
|
+
// src/blob/blob-gc.ts
|
|
5723
|
+
var import_core31 = require("@korajs/core");
|
|
5724
|
+
function extractBlobRefs(record) {
|
|
5725
|
+
const refs = [];
|
|
5726
|
+
for (const value of Object.values(record)) {
|
|
5727
|
+
if ((0, import_core31.isBlobRef)(value)) {
|
|
5728
|
+
refs.push(value);
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
return refs;
|
|
5732
|
+
}
|
|
5733
|
+
async function collectBlobGarbage(store, liveRefs, options = {}) {
|
|
5734
|
+
const live = /* @__PURE__ */ new Set();
|
|
5735
|
+
for (const ref of liveRefs) {
|
|
5736
|
+
live.add(ref.hash);
|
|
5737
|
+
if (ref.manifestHash === void 0) {
|
|
5738
|
+
continue;
|
|
5739
|
+
}
|
|
5740
|
+
live.add(ref.manifestHash);
|
|
5741
|
+
try {
|
|
5742
|
+
const manifestBytes = await store.get(ref.manifestHash);
|
|
5743
|
+
if (manifestBytes !== null) {
|
|
5744
|
+
const manifest = parseBlobManifest(manifestBytes);
|
|
5745
|
+
for (const chunkHash of manifest.chunkHashes) {
|
|
5746
|
+
live.add(chunkHash);
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
} catch {
|
|
5750
|
+
}
|
|
5751
|
+
}
|
|
5752
|
+
const all = await store.list();
|
|
5753
|
+
const collectedHashes = [];
|
|
5754
|
+
for (const hash of all) {
|
|
5755
|
+
if (live.has(hash)) {
|
|
5756
|
+
continue;
|
|
5757
|
+
}
|
|
5758
|
+
collectedHashes.push(hash);
|
|
5759
|
+
if (!options.dryRun) {
|
|
5760
|
+
await store.delete(hash);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
return {
|
|
5764
|
+
scanned: all.length,
|
|
5765
|
+
live: live.size,
|
|
5766
|
+
collected: collectedHashes.length,
|
|
5767
|
+
collectedHashes
|
|
5768
|
+
};
|
|
5769
|
+
}
|
|
5770
|
+
|
|
5771
|
+
// src/blob/blob-chunk-transport.ts
|
|
5772
|
+
var import_core32 = require("@korajs/core");
|
|
5773
|
+
function createRemoteChunkProvider(port, options = {}) {
|
|
5774
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
5775
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5776
|
+
port.onMessage((message) => {
|
|
5777
|
+
if (message.type !== "blob-chunk-response") {
|
|
5778
|
+
return;
|
|
5779
|
+
}
|
|
5780
|
+
const entry = pending.get(message.requestId);
|
|
5781
|
+
if (!entry) {
|
|
5782
|
+
return;
|
|
5783
|
+
}
|
|
5784
|
+
pending.delete(message.requestId);
|
|
5785
|
+
clearTimeout(entry.timer);
|
|
5786
|
+
entry.resolve(message.bytes);
|
|
5787
|
+
});
|
|
5788
|
+
return {
|
|
5789
|
+
pendingCount: () => pending.size,
|
|
5790
|
+
getChunk(hash) {
|
|
5791
|
+
const requestId = (0, import_core32.generateUUIDv7)();
|
|
5792
|
+
return new Promise((resolve, reject) => {
|
|
5793
|
+
const timer = setTimeout(() => {
|
|
5794
|
+
pending.delete(requestId);
|
|
5795
|
+
reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
|
|
5796
|
+
}, timeoutMs);
|
|
5797
|
+
pending.set(requestId, { resolve, reject, timer });
|
|
5798
|
+
port.send({ type: "blob-chunk-request", requestId, hash });
|
|
5799
|
+
});
|
|
5800
|
+
}
|
|
5801
|
+
};
|
|
5802
|
+
}
|
|
5803
|
+
function serveBlobChunks(port, blobStore) {
|
|
5804
|
+
port.onMessage((message) => {
|
|
5805
|
+
if (message.type !== "blob-chunk-request") {
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
void blobStore.get(message.hash).then(
|
|
5809
|
+
(bytes) => {
|
|
5810
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
|
|
5811
|
+
},
|
|
5812
|
+
() => {
|
|
5813
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
|
|
5814
|
+
}
|
|
5815
|
+
);
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
function createChunkPortPair() {
|
|
5819
|
+
const handlersA = [];
|
|
5820
|
+
const handlersB = [];
|
|
5821
|
+
const a = {
|
|
5822
|
+
send(message) {
|
|
5823
|
+
queueMicrotask(() => {
|
|
5824
|
+
for (const handler of handlersB) {
|
|
5825
|
+
handler(message);
|
|
5826
|
+
}
|
|
5827
|
+
});
|
|
5828
|
+
},
|
|
5829
|
+
onMessage(handler) {
|
|
5830
|
+
handlersA.push(handler);
|
|
5831
|
+
}
|
|
5832
|
+
};
|
|
5833
|
+
const b = {
|
|
5834
|
+
send(message) {
|
|
5835
|
+
queueMicrotask(() => {
|
|
5836
|
+
for (const handler of handlersA) {
|
|
5837
|
+
handler(message);
|
|
5838
|
+
}
|
|
5839
|
+
});
|
|
5840
|
+
},
|
|
5841
|
+
onMessage(handler) {
|
|
5842
|
+
handlersB.push(handler);
|
|
5843
|
+
}
|
|
5844
|
+
};
|
|
5845
|
+
return { a, b };
|
|
5846
|
+
}
|
|
5078
5847
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5079
5848
|
0 && (module.exports = {
|
|
5080
5849
|
AdapterError,
|
|
5850
|
+
BlobIntegrityError,
|
|
5081
5851
|
COMPACTION_BASELINE_META_KEY,
|
|
5082
5852
|
Collection,
|
|
5853
|
+
DEFAULT_CHUNK_SIZE,
|
|
5083
5854
|
InvalidStateTransitionError,
|
|
5084
5855
|
LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
5856
|
+
MemoryBlobStore,
|
|
5857
|
+
NoLeaderError,
|
|
5858
|
+
OpfsBlobStore,
|
|
5085
5859
|
OptimisticLockError,
|
|
5086
5860
|
PersistenceError,
|
|
5087
5861
|
QueryBuilder,
|
|
@@ -5099,26 +5873,44 @@ init_export_audit();
|
|
|
5099
5873
|
WorkerTimeoutError,
|
|
5100
5874
|
asRichTextSyncEngine,
|
|
5101
5875
|
assertQueryReady,
|
|
5876
|
+
chunkBlob,
|
|
5877
|
+
collectBlobGarbage,
|
|
5102
5878
|
collectOperationsAheadOfServer,
|
|
5103
5879
|
compactOperationLog,
|
|
5104
5880
|
computeAckCompactionWatermark,
|
|
5881
|
+
createChunkPortPair,
|
|
5882
|
+
createMemoryServerBlobStore,
|
|
5883
|
+
createOpfsBlobDirectory,
|
|
5884
|
+
createOpfsBlobStore,
|
|
5885
|
+
createRemoteChunkProvider,
|
|
5105
5886
|
createRichTextController,
|
|
5106
5887
|
decodeAuditExport,
|
|
5107
5888
|
decodeRichtext,
|
|
5108
5889
|
deserializeVersionVectorFromMeta,
|
|
5109
5890
|
encodeRichtext,
|
|
5110
5891
|
exportBackup,
|
|
5892
|
+
extractBlobRefs,
|
|
5893
|
+
fetchBlobManifest,
|
|
5111
5894
|
getSharedQueryStoreCache,
|
|
5112
5895
|
mergeVersionVectors,
|
|
5896
|
+
parseBlobManifest,
|
|
5113
5897
|
persistedAuditTraceFromEvent,
|
|
5114
5898
|
pluralize,
|
|
5899
|
+
prepareBlobForSend,
|
|
5900
|
+
putBlobForTransfer,
|
|
5115
5901
|
readAuditExportManifest,
|
|
5116
5902
|
readBackupManifest,
|
|
5903
|
+
reassembleBlob,
|
|
5904
|
+
receiveBlob,
|
|
5905
|
+
resolveBlobManifest,
|
|
5117
5906
|
restoreBackup,
|
|
5118
5907
|
richtextStatesEqual,
|
|
5119
5908
|
richtextToPlainText,
|
|
5909
|
+
serializeBlobManifest,
|
|
5120
5910
|
serializeVersionVectorToMeta,
|
|
5911
|
+
serveBlobChunks,
|
|
5121
5912
|
singularize,
|
|
5913
|
+
toServerBlobCallbacks,
|
|
5122
5914
|
validateStateTransition,
|
|
5123
5915
|
validateUpdateStateMachine,
|
|
5124
5916
|
verifyAuditExportChecksum,
|