@korajs/store 1.0.0-beta.0 → 1.0.0-beta.2
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.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/indexeddb.cjs +39 -5
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/indexeddb.js +13 -6
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs +25 -3
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +25 -3
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +29 -0
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +17 -2
- package/dist/adapters/sqlite-wasm.d.ts +17 -2
- package/dist/adapters/sqlite-wasm.js +1 -1
- package/dist/{backup-5XZ7THNJ.js → backup-IQE4QC7Q.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-OUQDC5Z6.js → chunk-2VRPF25R.js} +58 -38
- package/dist/chunk-2VRPF25R.js.map +1 -0
- package/dist/{chunk-ETDJEH2I.js → chunk-6NKLDBTY.js} +11 -1
- package/dist/{chunk-ETDJEH2I.js.map → chunk-6NKLDBTY.js.map} +1 -1
- package/dist/{chunk-5R4QMY3Z.js → chunk-OCOGQRHW.js} +13 -10
- package/dist/chunk-OCOGQRHW.js.map +1 -0
- package/dist/{chunk-RBPVEAVM.js → chunk-QUJGXSZ6.js} +30 -1
- package/dist/chunk-QUJGXSZ6.js.map +1 -0
- package/dist/{chunk-EIVXWF4B.js → chunk-RCTG3IJG.js} +4 -3
- package/dist/chunk-RCTG3IJG.js.map +1 -0
- package/dist/chunk-W4MTTNEZ.js +55 -0
- package/dist/chunk-W4MTTNEZ.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-MT2LTMIN.js} +3 -3
- package/dist/index.cjs +765 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +397 -6
- package/dist/index.d.ts +397 -6
- package/dist/index.js +603 -60
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +68 -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/{types-CyjXwrjW.d.cts → types-5h4Hqcwc.d.ts} +10 -3
- package/dist/{types-DAxCladl.d.ts → types-DfPiPwKr.d.cts} +10 -3
- package/dist/{types-CJOVKxLw.d.cts → types-k6AZgiH4.d.cts} +6 -1
- package/dist/{types-CJOVKxLw.d.ts → types-k6AZgiH4.d.ts} +6 -1
- package/package.json +13 -2
- package/dist/chunk-5R4QMY3Z.js.map +0 -1
- package/dist/chunk-EIVXWF4B.js.map +0 -1
- package/dist/chunk-OUQDC5Z6.js.map +0 -1
- package/dist/chunk-RBPVEAVM.js.map +0 -1
- /package/dist/{backup-5XZ7THNJ.js.map → backup-IQE4QC7Q.js.map} +0 -0
- /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-MT2LTMIN.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -239,6 +239,9 @@ function serializeValue(value, descriptor) {
|
|
|
239
239
|
case "boolean":
|
|
240
240
|
return value ? 1 : 0;
|
|
241
241
|
case "array":
|
|
242
|
+
case "object":
|
|
243
|
+
case "json":
|
|
244
|
+
case "blob":
|
|
242
245
|
return JSON.stringify(value);
|
|
243
246
|
case "richtext":
|
|
244
247
|
return encodeRichtext(value);
|
|
@@ -255,6 +258,13 @@ function deserializeValue(value, descriptor) {
|
|
|
255
258
|
return JSON.parse(value);
|
|
256
259
|
}
|
|
257
260
|
return value;
|
|
261
|
+
case "object":
|
|
262
|
+
case "json":
|
|
263
|
+
case "blob":
|
|
264
|
+
if (typeof value === "string") {
|
|
265
|
+
return JSON.parse(value);
|
|
266
|
+
}
|
|
267
|
+
return value;
|
|
258
268
|
case "richtext":
|
|
259
269
|
return decodeRichtext(value);
|
|
260
270
|
default:
|
|
@@ -518,7 +528,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
518
528
|
schema_version: op.schemaVersion
|
|
519
529
|
};
|
|
520
530
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
531
|
+
`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
532
|
[
|
|
523
533
|
opRow.id,
|
|
524
534
|
opRow.node_id,
|
|
@@ -537,8 +547,8 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
537
547
|
} else {
|
|
538
548
|
await adapter.transaction(async (tx) => {
|
|
539
549
|
for (const collection of Object.keys(schema.collections)) {
|
|
540
|
-
await tx.execute(`DELETE FROM _kora_ops_${collection}`);
|
|
541
|
-
await tx.execute(`DELETE FROM ${collection}`);
|
|
550
|
+
await tx.execute(`DELETE FROM ${(0, import_core6.quoteIdent)(`_kora_ops_${collection}`)}`);
|
|
551
|
+
await tx.execute(`DELETE FROM ${(0, import_core6.quoteIdent)(collection)}`);
|
|
542
552
|
}
|
|
543
553
|
await tx.execute("DELETE FROM _kora_version_vector");
|
|
544
554
|
await tx.execute("DELETE FROM _kora_meta");
|
|
@@ -558,7 +568,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
558
568
|
for (const op of operations) {
|
|
559
569
|
const collection = op.collection;
|
|
560
570
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
571
|
+
`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
572
|
[
|
|
563
573
|
op.id,
|
|
564
574
|
op.nodeId,
|
|
@@ -590,6 +600,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
590
600
|
for (const record of records) {
|
|
591
601
|
const keys = Object.keys(record);
|
|
592
602
|
const placeholders = keys.map(() => "?").join(", ");
|
|
603
|
+
const quotedKeys = keys.map((k) => (0, import_core6.quoteIdent)(k)).join(", ");
|
|
593
604
|
const values = keys.map((k) => {
|
|
594
605
|
const v = record[k];
|
|
595
606
|
if (typeof v === "boolean") return v ? 1 : 0;
|
|
@@ -597,7 +608,7 @@ async function restoreBackup(adapter, schema, data, options) {
|
|
|
597
608
|
return v;
|
|
598
609
|
});
|
|
599
610
|
await tx.execute(
|
|
600
|
-
`INSERT OR REPLACE INTO ${col} (${
|
|
611
|
+
`INSERT OR REPLACE INTO ${(0, import_core6.quoteIdent)(col)} (${quotedKeys}) VALUES (${placeholders})`,
|
|
601
612
|
values
|
|
602
613
|
);
|
|
603
614
|
}
|
|
@@ -630,7 +641,7 @@ async function readAllOperations(adapter, schema) {
|
|
|
630
641
|
const allOps = [];
|
|
631
642
|
for (const collectionName of Object.keys(schema.collections)) {
|
|
632
643
|
const rows = await adapter.query(
|
|
633
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
644
|
+
`SELECT * FROM ${(0, import_core6.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
634
645
|
);
|
|
635
646
|
for (const row of rows) {
|
|
636
647
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -639,7 +650,9 @@ async function readAllOperations(adapter, schema) {
|
|
|
639
650
|
return allOps;
|
|
640
651
|
}
|
|
641
652
|
async function readCollectionRecords(adapter, collection) {
|
|
642
|
-
return adapter.query(
|
|
653
|
+
return adapter.query(
|
|
654
|
+
`SELECT * FROM ${(0, import_core6.quoteIdent)(collection)} WHERE _deleted = 0`
|
|
655
|
+
);
|
|
643
656
|
}
|
|
644
657
|
async function computeSha256(data) {
|
|
645
658
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -707,7 +720,7 @@ __export(audit_trace_store_exports, {
|
|
|
707
720
|
});
|
|
708
721
|
function persistedAuditTraceFromEvent(event) {
|
|
709
722
|
return {
|
|
710
|
-
id: (0,
|
|
723
|
+
id: (0, import_core21.generateUUIDv7)(),
|
|
711
724
|
recordedAt: Date.now(),
|
|
712
725
|
eventType: event.type,
|
|
713
726
|
constraint: event.type === "constraint:violated" ? event.constraint : void 0,
|
|
@@ -773,11 +786,11 @@ function auditTraceIndexFields(trace) {
|
|
|
773
786
|
recordId: trace.operationA.recordId
|
|
774
787
|
};
|
|
775
788
|
}
|
|
776
|
-
var
|
|
789
|
+
var import_core21;
|
|
777
790
|
var init_audit_trace_store = __esm({
|
|
778
791
|
"src/audit/audit-trace-store.ts"() {
|
|
779
792
|
"use strict";
|
|
780
|
-
|
|
793
|
+
import_core21 = require("@korajs/core");
|
|
781
794
|
init_audit_json();
|
|
782
795
|
}
|
|
783
796
|
});
|
|
@@ -849,7 +862,7 @@ async function readAllOperations2(adapter, schema, collectionFilter) {
|
|
|
849
862
|
const allOps = [];
|
|
850
863
|
for (const collectionName of collections) {
|
|
851
864
|
const rows = await adapter.query(
|
|
852
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
865
|
+
`SELECT * FROM ${(0, import_core22.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
853
866
|
);
|
|
854
867
|
for (const row of rows) {
|
|
855
868
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -960,9 +973,11 @@ function parseNdjsonSection(content) {
|
|
|
960
973
|
}
|
|
961
974
|
return text.split("\n").map((line) => deserializeAuditJson(line));
|
|
962
975
|
}
|
|
976
|
+
var import_core22;
|
|
963
977
|
var init_export_audit = __esm({
|
|
964
978
|
"src/audit/export-audit.ts"() {
|
|
965
979
|
"use strict";
|
|
980
|
+
import_core22 = require("@korajs/core");
|
|
966
981
|
init_serializer();
|
|
967
982
|
init_audit_json();
|
|
968
983
|
init_audit_trace_store();
|
|
@@ -973,10 +988,14 @@ var init_export_audit = __esm({
|
|
|
973
988
|
var index_exports = {};
|
|
974
989
|
__export(index_exports, {
|
|
975
990
|
AdapterError: () => AdapterError,
|
|
991
|
+
BlobIntegrityError: () => BlobIntegrityError,
|
|
976
992
|
COMPACTION_BASELINE_META_KEY: () => COMPACTION_BASELINE_META_KEY,
|
|
977
993
|
Collection: () => Collection,
|
|
994
|
+
DEFAULT_CHUNK_SIZE: () => DEFAULT_CHUNK_SIZE,
|
|
978
995
|
InvalidStateTransitionError: () => InvalidStateTransitionError,
|
|
979
996
|
LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
997
|
+
MemoryBlobStore: () => MemoryBlobStore,
|
|
998
|
+
OpfsBlobStore: () => OpfsBlobStore,
|
|
980
999
|
OptimisticLockError: () => OptimisticLockError,
|
|
981
1000
|
PersistenceError: () => PersistenceError,
|
|
982
1001
|
QueryBuilder: () => QueryBuilder,
|
|
@@ -994,26 +1013,44 @@ __export(index_exports, {
|
|
|
994
1013
|
WorkerTimeoutError: () => WorkerTimeoutError,
|
|
995
1014
|
asRichTextSyncEngine: () => asRichTextSyncEngine,
|
|
996
1015
|
assertQueryReady: () => assertQueryReady,
|
|
1016
|
+
chunkBlob: () => chunkBlob,
|
|
1017
|
+
collectBlobGarbage: () => collectBlobGarbage,
|
|
997
1018
|
collectOperationsAheadOfServer: () => collectOperationsAheadOfServer,
|
|
998
1019
|
compactOperationLog: () => compactOperationLog,
|
|
999
1020
|
computeAckCompactionWatermark: () => computeAckCompactionWatermark,
|
|
1021
|
+
createChunkPortPair: () => createChunkPortPair,
|
|
1022
|
+
createMemoryServerBlobStore: () => createMemoryServerBlobStore,
|
|
1023
|
+
createOpfsBlobDirectory: () => createOpfsBlobDirectory,
|
|
1024
|
+
createOpfsBlobStore: () => createOpfsBlobStore,
|
|
1025
|
+
createRemoteChunkProvider: () => createRemoteChunkProvider,
|
|
1000
1026
|
createRichTextController: () => createRichTextController,
|
|
1001
1027
|
decodeAuditExport: () => decodeAuditExport,
|
|
1002
1028
|
decodeRichtext: () => decodeRichtext,
|
|
1003
1029
|
deserializeVersionVectorFromMeta: () => deserializeVersionVectorFromMeta,
|
|
1004
1030
|
encodeRichtext: () => encodeRichtext,
|
|
1005
1031
|
exportBackup: () => exportBackup,
|
|
1032
|
+
extractBlobRefs: () => extractBlobRefs,
|
|
1033
|
+
fetchBlobManifest: () => fetchBlobManifest,
|
|
1006
1034
|
getSharedQueryStoreCache: () => getSharedQueryStoreCache,
|
|
1007
1035
|
mergeVersionVectors: () => mergeVersionVectors,
|
|
1036
|
+
parseBlobManifest: () => parseBlobManifest,
|
|
1008
1037
|
persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
|
|
1009
1038
|
pluralize: () => pluralize,
|
|
1039
|
+
prepareBlobForSend: () => prepareBlobForSend,
|
|
1040
|
+
putBlobForTransfer: () => putBlobForTransfer,
|
|
1010
1041
|
readAuditExportManifest: () => readAuditExportManifest,
|
|
1011
1042
|
readBackupManifest: () => readBackupManifest,
|
|
1043
|
+
reassembleBlob: () => reassembleBlob,
|
|
1044
|
+
receiveBlob: () => receiveBlob,
|
|
1045
|
+
resolveBlobManifest: () => resolveBlobManifest,
|
|
1012
1046
|
restoreBackup: () => restoreBackup,
|
|
1013
1047
|
richtextStatesEqual: () => richtextStatesEqual,
|
|
1014
1048
|
richtextToPlainText: () => richtextToPlainText,
|
|
1049
|
+
serializeBlobManifest: () => serializeBlobManifest,
|
|
1015
1050
|
serializeVersionVectorToMeta: () => serializeVersionVectorToMeta,
|
|
1051
|
+
serveBlobChunks: () => serveBlobChunks,
|
|
1016
1052
|
singularize: () => singularize,
|
|
1053
|
+
toServerBlobCallbacks: () => toServerBlobCallbacks,
|
|
1017
1054
|
validateStateTransition: () => validateStateTransition,
|
|
1018
1055
|
validateUpdateStateMachine: () => validateUpdateStateMachine,
|
|
1019
1056
|
verifyAuditExportChecksum: () => verifyAuditExportChecksum,
|
|
@@ -1115,7 +1152,7 @@ async function compactOperationLog(adapter, schema, strategy, serverVector) {
|
|
|
1115
1152
|
const collectionNames = Object.keys(schema.collections);
|
|
1116
1153
|
await adapter.transaction(async (tx) => {
|
|
1117
1154
|
for (const collectionName of collectionNames) {
|
|
1118
|
-
const table = `_kora_ops_${collectionName}
|
|
1155
|
+
const table = (0, import_core2.quoteIdent)(`_kora_ops_${collectionName}`);
|
|
1119
1156
|
for (const [nodeId, maxSeq] of watermark) {
|
|
1120
1157
|
if (maxSeq <= 0) continue;
|
|
1121
1158
|
const countSql = buildCountSql(table, ageCutoffSerialized !== null);
|
|
@@ -1224,11 +1261,14 @@ async function saveDeltaCursor(adapter, cursor) {
|
|
|
1224
1261
|
}
|
|
1225
1262
|
|
|
1226
1263
|
// src/store/store.ts
|
|
1227
|
-
var
|
|
1264
|
+
var import_core23 = require("@korajs/core");
|
|
1228
1265
|
init_backup();
|
|
1229
1266
|
|
|
1267
|
+
// src/collection/collection.ts
|
|
1268
|
+
var import_core13 = require("@korajs/core");
|
|
1269
|
+
|
|
1230
1270
|
// src/mutations/execute-delete.ts
|
|
1231
|
-
var
|
|
1271
|
+
var import_core9 = require("@korajs/core");
|
|
1232
1272
|
|
|
1233
1273
|
// src/lww/row-version.ts
|
|
1234
1274
|
var import_core7 = require("@korajs/core");
|
|
@@ -1254,9 +1294,10 @@ function lwwVersionWhereClause(remoteVersion) {
|
|
|
1254
1294
|
}
|
|
1255
1295
|
|
|
1256
1296
|
// src/query/sql-builder.ts
|
|
1297
|
+
var import_core8 = require("@korajs/core");
|
|
1257
1298
|
function buildSelectQuery(descriptor, fields) {
|
|
1258
1299
|
const params = [];
|
|
1259
|
-
const parts = [`SELECT * FROM ${descriptor.collection}`];
|
|
1300
|
+
const parts = [`SELECT * FROM ${(0, import_core8.quoteIdent)(descriptor.collection)}`];
|
|
1260
1301
|
const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
|
|
1261
1302
|
const deletedFilter = "_deleted = 0";
|
|
1262
1303
|
if (whereClause) {
|
|
@@ -1267,7 +1308,7 @@ function buildSelectQuery(descriptor, fields) {
|
|
|
1267
1308
|
if (descriptor.orderBy.length > 0) {
|
|
1268
1309
|
const orderParts = descriptor.orderBy.map((o) => {
|
|
1269
1310
|
validateFieldName(o.field, fields);
|
|
1270
|
-
return `${o.field} ${o.direction.toUpperCase()}`;
|
|
1311
|
+
return `${(0, import_core8.quoteIdent)(o.field)} ${o.direction.toUpperCase()}`;
|
|
1271
1312
|
});
|
|
1272
1313
|
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
1273
1314
|
}
|
|
@@ -1281,7 +1322,7 @@ function buildSelectQuery(descriptor, fields) {
|
|
|
1281
1322
|
}
|
|
1282
1323
|
function buildCountQuery(descriptor, fields) {
|
|
1283
1324
|
const params = [];
|
|
1284
|
-
const parts = [`SELECT COUNT(*) as count FROM ${descriptor.collection}`];
|
|
1325
|
+
const parts = [`SELECT COUNT(*) as count FROM ${(0, import_core8.quoteIdent)(descriptor.collection)}`];
|
|
1285
1326
|
const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
|
|
1286
1327
|
const deletedFilter = "_deleted = 0";
|
|
1287
1328
|
if (whereClause) {
|
|
@@ -1295,17 +1336,18 @@ function buildInsertQuery(collection, record) {
|
|
|
1295
1336
|
const columns = Object.keys(record);
|
|
1296
1337
|
const placeholders = columns.map(() => "?");
|
|
1297
1338
|
const params = Object.values(record);
|
|
1298
|
-
const
|
|
1339
|
+
const quotedColumns = columns.map((col) => (0, import_core8.quoteIdent)(col));
|
|
1340
|
+
const sql = `INSERT INTO ${(0, import_core8.quoteIdent)(collection)} (${quotedColumns.join(", ")}) VALUES (${placeholders.join(", ")})`;
|
|
1299
1341
|
return { sql, params };
|
|
1300
1342
|
}
|
|
1301
1343
|
function buildUpdateQuery(collection, id, changes) {
|
|
1302
|
-
const setClauses = Object.keys(changes).map((col) => `${col} = ?`);
|
|
1344
|
+
const setClauses = Object.keys(changes).map((col) => `${(0, import_core8.quoteIdent)(col)} = ?`);
|
|
1303
1345
|
const params = [...Object.values(changes), id];
|
|
1304
|
-
const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1346
|
+
const sql = `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1305
1347
|
return { sql, params };
|
|
1306
1348
|
}
|
|
1307
1349
|
function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVersion, wallTime, options) {
|
|
1308
|
-
const fieldClauses = Object.keys(fieldChanges).map((col) => `${col} = ?`);
|
|
1350
|
+
const fieldClauses = Object.keys(fieldChanges).map((col) => `${(0, import_core8.quoteIdent)(col)} = ?`);
|
|
1309
1351
|
const setClauses = [
|
|
1310
1352
|
...fieldClauses,
|
|
1311
1353
|
"_version = CASE WHEN _version >= ? THEN _version ELSE ? END",
|
|
@@ -1323,25 +1365,25 @@ function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVe
|
|
|
1323
1365
|
params.push(options.maxCreatedAt, options.maxCreatedAt);
|
|
1324
1366
|
}
|
|
1325
1367
|
params.push(id);
|
|
1326
|
-
const sql = `UPDATE ${collection} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1368
|
+
const sql = `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1327
1369
|
return { sql, params };
|
|
1328
1370
|
}
|
|
1329
1371
|
function buildSoftDeleteQuery(collection, id, updatedAt, version) {
|
|
1330
1372
|
if (version !== void 0) {
|
|
1331
1373
|
return {
|
|
1332
|
-
sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
|
|
1374
|
+
sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
|
|
1333
1375
|
params: [updatedAt, version, id]
|
|
1334
1376
|
};
|
|
1335
1377
|
}
|
|
1336
1378
|
return {
|
|
1337
|
-
sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
|
|
1379
|
+
sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
|
|
1338
1380
|
params: [updatedAt, id]
|
|
1339
1381
|
};
|
|
1340
1382
|
}
|
|
1341
1383
|
function buildLwwSoftDeleteQuery(collection, id, updatedAt, version) {
|
|
1342
1384
|
const lww = lwwVersionWhereClause(version);
|
|
1343
1385
|
return {
|
|
1344
|
-
sql: `UPDATE ${collection} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
|
|
1386
|
+
sql: `UPDATE ${(0, import_core8.quoteIdent)(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
|
|
1345
1387
|
params: [updatedAt, version, id, ...lww.params]
|
|
1346
1388
|
};
|
|
1347
1389
|
}
|
|
@@ -1372,31 +1414,32 @@ function buildWhereClauseParts(where, fields, params) {
|
|
|
1372
1414
|
}
|
|
1373
1415
|
function buildOperatorCondition(fieldName, operator, value, descriptor, params) {
|
|
1374
1416
|
const sqlValue = descriptor?.kind === "boolean" && typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
1417
|
+
const column = (0, import_core8.quoteIdent)(fieldName);
|
|
1375
1418
|
switch (operator) {
|
|
1376
1419
|
case "$eq":
|
|
1377
1420
|
if (sqlValue === null) {
|
|
1378
|
-
return `${
|
|
1421
|
+
return `${column} IS NULL`;
|
|
1379
1422
|
}
|
|
1380
1423
|
params.push(sqlValue);
|
|
1381
|
-
return `${
|
|
1424
|
+
return `${column} = ?`;
|
|
1382
1425
|
case "$ne":
|
|
1383
1426
|
if (sqlValue === null) {
|
|
1384
|
-
return `${
|
|
1427
|
+
return `${column} IS NOT NULL`;
|
|
1385
1428
|
}
|
|
1386
1429
|
params.push(sqlValue);
|
|
1387
|
-
return `${
|
|
1430
|
+
return `${column} != ?`;
|
|
1388
1431
|
case "$gt":
|
|
1389
1432
|
params.push(sqlValue);
|
|
1390
|
-
return `${
|
|
1433
|
+
return `${column} > ?`;
|
|
1391
1434
|
case "$gte":
|
|
1392
1435
|
params.push(sqlValue);
|
|
1393
|
-
return `${
|
|
1436
|
+
return `${column} >= ?`;
|
|
1394
1437
|
case "$lt":
|
|
1395
1438
|
params.push(sqlValue);
|
|
1396
|
-
return `${
|
|
1439
|
+
return `${column} < ?`;
|
|
1397
1440
|
case "$lte":
|
|
1398
1441
|
params.push(sqlValue);
|
|
1399
|
-
return `${
|
|
1442
|
+
return `${column} <= ?`;
|
|
1400
1443
|
case "$in": {
|
|
1401
1444
|
if (!Array.isArray(sqlValue)) {
|
|
1402
1445
|
throw new QueryError(`$in operator requires an array value for field "${fieldName}"`, {
|
|
@@ -1410,7 +1453,7 @@ function buildOperatorCondition(fieldName, operator, value, descriptor, params)
|
|
|
1410
1453
|
descriptor?.kind === "boolean" && typeof item === "boolean" ? item ? 1 : 0 : item
|
|
1411
1454
|
);
|
|
1412
1455
|
}
|
|
1413
|
-
return `${
|
|
1456
|
+
return `${column} IN (${placeholders.join(", ")})`;
|
|
1414
1457
|
}
|
|
1415
1458
|
default:
|
|
1416
1459
|
throw new QueryError(`Unknown operator "${operator}"`, { operator });
|
|
@@ -1490,7 +1533,7 @@ function resolveCausalDeps(ctx) {
|
|
|
1490
1533
|
// src/mutations/execute-delete.ts
|
|
1491
1534
|
async function executeDelete(ctx, id, options) {
|
|
1492
1535
|
const currentRows = await ctx.adapter.query(
|
|
1493
|
-
`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
|
|
1536
|
+
`SELECT * FROM ${(0, import_core9.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
|
|
1494
1537
|
[id]
|
|
1495
1538
|
);
|
|
1496
1539
|
if (!currentRows[0]) {
|
|
@@ -1502,7 +1545,7 @@ async function executeDelete(ctx, id, options) {
|
|
|
1502
1545
|
await ctx.adapter.transaction(async (tx) => {
|
|
1503
1546
|
if (!operation) {
|
|
1504
1547
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
|
|
1505
|
-
operation = await (0,
|
|
1548
|
+
operation = await (0, import_core9.createOperation)(
|
|
1506
1549
|
{
|
|
1507
1550
|
nodeId: ctx.nodeId,
|
|
1508
1551
|
type: "delete",
|
|
@@ -1549,7 +1592,7 @@ async function executeDelete(ctx, id, options) {
|
|
|
1549
1592
|
}
|
|
1550
1593
|
|
|
1551
1594
|
// src/mutations/execute-insert.ts
|
|
1552
|
-
var
|
|
1595
|
+
var import_core10 = require("@korajs/core");
|
|
1553
1596
|
|
|
1554
1597
|
// src/lww/field-versions.ts
|
|
1555
1598
|
function parseFieldVersions(raw) {
|
|
@@ -1606,19 +1649,24 @@ function resolvePerFieldLww(current, incomingFields, incomingVersion, rowVersion
|
|
|
1606
1649
|
init_op_data_encoding();
|
|
1607
1650
|
init_serializer();
|
|
1608
1651
|
async function executeInsert(ctx, data) {
|
|
1609
|
-
const validated = (0,
|
|
1610
|
-
const recordId = (0,
|
|
1652
|
+
const validated = (0, import_core10.validateRecord)(ctx.collection, ctx.definition, data, "insert");
|
|
1653
|
+
const recordId = (0, import_core10.generateUUIDv7)();
|
|
1611
1654
|
for (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {
|
|
1612
1655
|
if (descriptor.auto && descriptor.kind === "timestamp") {
|
|
1613
1656
|
validated[fieldName] = Date.now();
|
|
1614
1657
|
}
|
|
1615
1658
|
}
|
|
1659
|
+
const writeData = await (0, import_core10.transformSecretFieldsForWrite)(
|
|
1660
|
+
validated,
|
|
1661
|
+
ctx.definition,
|
|
1662
|
+
ctx.secretKeyProvider
|
|
1663
|
+
);
|
|
1616
1664
|
const causalDeps = resolveCausalDeps(ctx);
|
|
1617
1665
|
let operation;
|
|
1618
1666
|
let record;
|
|
1619
1667
|
await ctx.adapter.transaction(async (tx) => {
|
|
1620
1668
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
|
|
1621
|
-
operation = await (0,
|
|
1669
|
+
operation = await (0, import_core10.createOperation)(
|
|
1622
1670
|
{
|
|
1623
1671
|
nodeId: ctx.nodeId,
|
|
1624
1672
|
type: "insert",
|
|
@@ -1627,7 +1675,7 @@ async function executeInsert(ctx, data) {
|
|
|
1627
1675
|
// Binary richtext values are tagged as canonical JSON BEFORE the
|
|
1628
1676
|
// operation is content-hashed, so the hash input, persisted JSON,
|
|
1629
1677
|
// and wire payload are the identical value.
|
|
1630
|
-
data: encodeRichtextFieldsForOpData(
|
|
1678
|
+
data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
|
|
1631
1679
|
previousData: null,
|
|
1632
1680
|
sequenceNumber,
|
|
1633
1681
|
causalDeps,
|
|
@@ -1636,7 +1684,7 @@ async function executeInsert(ctx, data) {
|
|
|
1636
1684
|
ctx.clock
|
|
1637
1685
|
);
|
|
1638
1686
|
ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
|
|
1639
|
-
const serializedData = serializeRecord(
|
|
1687
|
+
const serializedData = serializeRecord(writeData, ctx.definition.fields);
|
|
1640
1688
|
const version = serializeRowVersion(operation.timestamp);
|
|
1641
1689
|
record = {
|
|
1642
1690
|
id: recordId,
|
|
@@ -1661,20 +1709,20 @@ async function executeInsert(ctx, data) {
|
|
|
1661
1709
|
ctx.onMutation(ctx.collection, operation);
|
|
1662
1710
|
return {
|
|
1663
1711
|
id: recordId,
|
|
1664
|
-
...
|
|
1712
|
+
...writeData,
|
|
1665
1713
|
createdAt: operation.timestamp.wallTime,
|
|
1666
1714
|
updatedAt: operation.timestamp.wallTime
|
|
1667
1715
|
};
|
|
1668
1716
|
}
|
|
1669
1717
|
|
|
1670
1718
|
// src/mutations/execute-update.ts
|
|
1671
|
-
var
|
|
1719
|
+
var import_core12 = require("@korajs/core");
|
|
1672
1720
|
init_op_data_encoding();
|
|
1673
1721
|
init_serializer();
|
|
1674
1722
|
|
|
1675
1723
|
// src/state-machine/state-validator.ts
|
|
1676
|
-
var
|
|
1677
|
-
var InvalidStateTransitionError = class extends
|
|
1724
|
+
var import_core11 = require("@korajs/core");
|
|
1725
|
+
var InvalidStateTransitionError = class extends import_core11.KoraError {
|
|
1678
1726
|
constructor(collection, recordId, field, fromState, toState, allowedStates) {
|
|
1679
1727
|
super(
|
|
1680
1728
|
`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 +1756,7 @@ function validateStateTransition(collectionName, recordId, stateMachine, current
|
|
|
1708
1756
|
collection: collectionName,
|
|
1709
1757
|
transitions: stateMachine.transitions
|
|
1710
1758
|
};
|
|
1711
|
-
const transitionResult = (0,
|
|
1759
|
+
const transitionResult = (0, import_core11.validateTransition)(constraint, currentState, newState);
|
|
1712
1760
|
if (transitionResult.valid) {
|
|
1713
1761
|
return { valid: true, allowedStates: transitionResult.allowedTargets };
|
|
1714
1762
|
}
|
|
@@ -1757,14 +1805,14 @@ function validateUpdateStateMachine(collectionName, recordId, collectionDef, cur
|
|
|
1757
1805
|
// src/mutations/execute-update.ts
|
|
1758
1806
|
async function executeUpdate(ctx, id, data) {
|
|
1759
1807
|
const currentRows = await ctx.adapter.query(
|
|
1760
|
-
`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
|
|
1808
|
+
`SELECT * FROM ${(0, import_core12.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
|
|
1761
1809
|
[id]
|
|
1762
1810
|
);
|
|
1763
1811
|
const currentRow = currentRows[0];
|
|
1764
1812
|
if (!currentRow) {
|
|
1765
1813
|
throw new RecordNotFoundError(ctx.collection, id);
|
|
1766
1814
|
}
|
|
1767
|
-
let validated = (0,
|
|
1815
|
+
let validated = (0, import_core12.validateRecord)(ctx.collection, ctx.definition, data, "update");
|
|
1768
1816
|
const currentRecord = deserializeRecord(currentRow, ctx.definition.fields);
|
|
1769
1817
|
validated = validateUpdateStateMachine(
|
|
1770
1818
|
ctx.collection,
|
|
@@ -1782,19 +1830,24 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1782
1830
|
for (const key of Object.keys(validated)) {
|
|
1783
1831
|
const value = validated[key];
|
|
1784
1832
|
previousData[key] = currentRecord[key];
|
|
1785
|
-
if ((0,
|
|
1786
|
-
resolvedData[key] = (0,
|
|
1787
|
-
atomicOps[key] = (0,
|
|
1833
|
+
if ((0, import_core12.isAtomicOp)(value)) {
|
|
1834
|
+
resolvedData[key] = (0, import_core12.resolveAtomicOp)(currentRecord[key], value);
|
|
1835
|
+
atomicOps[key] = (0, import_core12.toAtomicOp)(value);
|
|
1788
1836
|
} else {
|
|
1789
1837
|
resolvedData[key] = value;
|
|
1790
1838
|
}
|
|
1791
1839
|
}
|
|
1840
|
+
const writeData = await (0, import_core12.transformSecretFieldsForWrite)(
|
|
1841
|
+
resolvedData,
|
|
1842
|
+
ctx.definition,
|
|
1843
|
+
ctx.secretKeyProvider
|
|
1844
|
+
);
|
|
1792
1845
|
const hasAtomicOps = Object.keys(atomicOps).length > 0;
|
|
1793
1846
|
const causalDeps = resolveCausalDeps(ctx);
|
|
1794
1847
|
let operation;
|
|
1795
1848
|
await ctx.adapter.transaction(async (tx) => {
|
|
1796
1849
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
|
|
1797
|
-
operation = await (0,
|
|
1850
|
+
operation = await (0, import_core12.createOperation)(
|
|
1798
1851
|
{
|
|
1799
1852
|
nodeId: ctx.nodeId,
|
|
1800
1853
|
type: "update",
|
|
@@ -1803,7 +1856,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1803
1856
|
// Binary richtext values (new and previous) are tagged as canonical
|
|
1804
1857
|
// JSON BEFORE the operation is content-hashed, so the hash input,
|
|
1805
1858
|
// persisted JSON, and wire payload are the identical value.
|
|
1806
|
-
data: encodeRichtextFieldsForOpData(
|
|
1859
|
+
data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
|
|
1807
1860
|
previousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),
|
|
1808
1861
|
sequenceNumber,
|
|
1809
1862
|
causalDeps,
|
|
@@ -1813,7 +1866,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1813
1866
|
ctx.clock
|
|
1814
1867
|
);
|
|
1815
1868
|
ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
|
|
1816
|
-
const serializedChanges = serializeRecord(
|
|
1869
|
+
const serializedChanges = serializeRecord(writeData, ctx.definition.fields);
|
|
1817
1870
|
const version = serializeRowVersion(operation.timestamp);
|
|
1818
1871
|
const mergedFieldVersions = parseFieldVersions(currentRow._field_versions);
|
|
1819
1872
|
for (const changedField of Object.keys(serializedChanges)) {
|
|
@@ -1834,7 +1887,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1834
1887
|
});
|
|
1835
1888
|
ctx.onMutation(ctx.collection, operation);
|
|
1836
1889
|
const rows = await ctx.adapter.query(
|
|
1837
|
-
`SELECT * FROM ${ctx.collection} WHERE id = ? AND _deleted = 0`,
|
|
1890
|
+
`SELECT * FROM ${(0, import_core12.quoteIdent)(ctx.collection)} WHERE id = ? AND _deleted = 0`,
|
|
1838
1891
|
[id]
|
|
1839
1892
|
);
|
|
1840
1893
|
const row = rows[0];
|
|
@@ -1847,7 +1900,7 @@ async function executeUpdate(ctx, id, data) {
|
|
|
1847
1900
|
// src/collection/collection.ts
|
|
1848
1901
|
init_serializer();
|
|
1849
1902
|
var Collection = class {
|
|
1850
|
-
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
|
|
1903
|
+
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
|
|
1851
1904
|
this.name = name;
|
|
1852
1905
|
this.definition = definition;
|
|
1853
1906
|
this.schema = schema;
|
|
@@ -1859,6 +1912,7 @@ var Collection = class {
|
|
|
1859
1912
|
this.relationEnforcer = relationEnforcer;
|
|
1860
1913
|
this.mutationHandler = mutationHandler;
|
|
1861
1914
|
this.causalTracker = causalTracker;
|
|
1915
|
+
this.secretKeyProvider = secretKeyProvider;
|
|
1862
1916
|
}
|
|
1863
1917
|
name;
|
|
1864
1918
|
definition;
|
|
@@ -1871,6 +1925,7 @@ var Collection = class {
|
|
|
1871
1925
|
relationEnforcer;
|
|
1872
1926
|
mutationHandler;
|
|
1873
1927
|
causalTracker;
|
|
1928
|
+
secretKeyProvider;
|
|
1874
1929
|
mutationContext() {
|
|
1875
1930
|
return {
|
|
1876
1931
|
collection: this.name,
|
|
@@ -1883,7 +1938,8 @@ var Collection = class {
|
|
|
1883
1938
|
onMutation: this.onMutation,
|
|
1884
1939
|
relationEnforcer: this.relationEnforcer,
|
|
1885
1940
|
causalTracker: this.causalTracker,
|
|
1886
|
-
inTransaction: false
|
|
1941
|
+
inTransaction: false,
|
|
1942
|
+
secretKeyProvider: this.secretKeyProvider
|
|
1887
1943
|
};
|
|
1888
1944
|
}
|
|
1889
1945
|
async insert(data) {
|
|
@@ -1894,7 +1950,7 @@ var Collection = class {
|
|
|
1894
1950
|
}
|
|
1895
1951
|
async findById(id) {
|
|
1896
1952
|
const rows = await this.adapter.query(
|
|
1897
|
-
`SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
|
|
1953
|
+
`SELECT * FROM ${(0, import_core13.quoteIdent)(this.name)} WHERE id = ? AND _deleted = 0`,
|
|
1898
1954
|
[id]
|
|
1899
1955
|
);
|
|
1900
1956
|
const row = rows[0];
|
|
@@ -1927,6 +1983,7 @@ var Collection = class {
|
|
|
1927
1983
|
};
|
|
1928
1984
|
|
|
1929
1985
|
// src/query/query-builder.ts
|
|
1986
|
+
var import_core14 = require("@korajs/core");
|
|
1930
1987
|
init_serializer();
|
|
1931
1988
|
|
|
1932
1989
|
// src/query/pluralize.ts
|
|
@@ -2158,7 +2215,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
2158
2215
|
const relatedDef = this.schema?.collections[relatedCollection];
|
|
2159
2216
|
if (!relatedDef) return;
|
|
2160
2217
|
const placeholders = uniqueFks.map(() => "?").join(", ");
|
|
2161
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
2218
|
+
const sql = `SELECT * FROM ${(0, import_core14.quoteIdent)(relatedCollection)} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
2162
2219
|
const rows = await this.adapter.query(sql, uniqueFks);
|
|
2163
2220
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
2164
2221
|
const lookup = /* @__PURE__ */ new Map();
|
|
@@ -2181,7 +2238,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
2181
2238
|
if (!relatedDef) return;
|
|
2182
2239
|
const fkField = relation.field;
|
|
2183
2240
|
const placeholders = primaryIds.map(() => "?").join(", ");
|
|
2184
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE ${fkField} IN (${placeholders}) AND _deleted = 0`;
|
|
2241
|
+
const sql = `SELECT * FROM ${(0, import_core14.quoteIdent)(relatedCollection)} WHERE ${(0, import_core14.quoteIdent)(fkField)} IN (${placeholders}) AND _deleted = 0`;
|
|
2185
2242
|
const rows = await this.adapter.query(sql, primaryIds);
|
|
2186
2243
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
2187
2244
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -2223,7 +2280,7 @@ var QueryError2 = class extends Error {
|
|
|
2223
2280
|
};
|
|
2224
2281
|
|
|
2225
2282
|
// src/relations/relation-enforcer.ts
|
|
2226
|
-
var
|
|
2283
|
+
var import_core15 = require("@korajs/core");
|
|
2227
2284
|
init_serializer();
|
|
2228
2285
|
|
|
2229
2286
|
// src/relations/relation-lookup.ts
|
|
@@ -2248,7 +2305,7 @@ function getIncomingRelations(lookup, collection) {
|
|
|
2248
2305
|
}
|
|
2249
2306
|
|
|
2250
2307
|
// src/relations/relation-enforcer.ts
|
|
2251
|
-
var ReferentialIntegrityError = class extends
|
|
2308
|
+
var ReferentialIntegrityError = class extends import_core15.KoraError {
|
|
2252
2309
|
constructor(collection, recordId, referencingCollection, relationName, referencingCount) {
|
|
2253
2310
|
super(
|
|
2254
2311
|
`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 +2384,7 @@ var RelationEnforcer = class {
|
|
|
2327
2384
|
async enforceCascade(incoming, deletedRecordId, tx, causalDeps) {
|
|
2328
2385
|
const { sourceCollection, foreignKeyField } = incoming;
|
|
2329
2386
|
const referencingRows = await tx.query(
|
|
2330
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
2387
|
+
`SELECT id FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
|
|
2331
2388
|
[deletedRecordId]
|
|
2332
2389
|
);
|
|
2333
2390
|
if (referencingRows.length === 0) {
|
|
@@ -2337,7 +2394,7 @@ var RelationEnforcer = class {
|
|
|
2337
2394
|
for (const row of referencingRows) {
|
|
2338
2395
|
const now = Date.now();
|
|
2339
2396
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
|
|
2340
|
-
const operation = await (0,
|
|
2397
|
+
const operation = await (0, import_core15.createOperation)(
|
|
2341
2398
|
{
|
|
2342
2399
|
nodeId: this.nodeId,
|
|
2343
2400
|
type: "delete",
|
|
@@ -2374,7 +2431,7 @@ var RelationEnforcer = class {
|
|
|
2374
2431
|
return [];
|
|
2375
2432
|
}
|
|
2376
2433
|
const referencingRows = await tx.query(
|
|
2377
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
2434
|
+
`SELECT id FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
|
|
2378
2435
|
[deletedRecordId]
|
|
2379
2436
|
);
|
|
2380
2437
|
if (referencingRows.length === 0) {
|
|
@@ -2386,7 +2443,7 @@ var RelationEnforcer = class {
|
|
|
2386
2443
|
const sequenceNumber = await allocateNextSequenceInTransaction(tx, this.nodeId);
|
|
2387
2444
|
const updateData = { [foreignKeyField]: null };
|
|
2388
2445
|
const previousData = { [foreignKeyField]: deletedRecordId };
|
|
2389
|
-
const operation = await (0,
|
|
2446
|
+
const operation = await (0, import_core15.createOperation)(
|
|
2390
2447
|
{
|
|
2391
2448
|
nodeId: this.nodeId,
|
|
2392
2449
|
type: "update",
|
|
@@ -2422,7 +2479,7 @@ var RelationEnforcer = class {
|
|
|
2422
2479
|
async enforceRestrict(incoming, deletedRecordId, tx) {
|
|
2423
2480
|
const { sourceCollection, foreignKeyField, relationName } = incoming;
|
|
2424
2481
|
const countRows = await tx.query(
|
|
2425
|
-
`SELECT COUNT(*) as cnt FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
2482
|
+
`SELECT COUNT(*) as cnt FROM ${(0, import_core15.quoteIdent)(sourceCollection)} WHERE ${(0, import_core15.quoteIdent)(foreignKeyField)} = ? AND _deleted = 0`,
|
|
2426
2483
|
[deletedRecordId]
|
|
2427
2484
|
);
|
|
2428
2485
|
const count = countRows[0]?.cnt ?? 0;
|
|
@@ -2447,7 +2504,7 @@ var RelationEnforcer = class {
|
|
|
2447
2504
|
};
|
|
2448
2505
|
|
|
2449
2506
|
// src/replay/replay-to.ts
|
|
2450
|
-
var
|
|
2507
|
+
var import_core16 = require("@korajs/core");
|
|
2451
2508
|
var import_internal = require("@korajs/core/internal");
|
|
2452
2509
|
init_op_data_encoding();
|
|
2453
2510
|
function collectCausalClosure(allOps, targetOperationId) {
|
|
@@ -2457,7 +2514,7 @@ function collectCausalClosure(allOps, targetOperationId) {
|
|
|
2457
2514
|
}
|
|
2458
2515
|
const target = opMap.get(targetOperationId);
|
|
2459
2516
|
if (!target) {
|
|
2460
|
-
throw new
|
|
2517
|
+
throw new import_core16.OperationError(
|
|
2461
2518
|
`Operation "${targetOperationId}" not found in the local operation log`,
|
|
2462
2519
|
{
|
|
2463
2520
|
operationId: targetOperationId
|
|
@@ -2489,7 +2546,7 @@ function buildReplaySnapshot(schema, allOps, targetOperationId) {
|
|
|
2489
2546
|
const operationsApplied = collectCausalClosure(allOps, targetOperationId);
|
|
2490
2547
|
const targetOperation = operationsApplied.find((op) => op.id === targetOperationId);
|
|
2491
2548
|
if (!targetOperation) {
|
|
2492
|
-
throw new
|
|
2549
|
+
throw new import_core16.OperationError(`Operation "${targetOperationId}" not found after causal sort`, {
|
|
2493
2550
|
operationId: targetOperationId
|
|
2494
2551
|
});
|
|
2495
2552
|
}
|
|
@@ -2615,7 +2672,7 @@ function toCollectionRecord(record, definition) {
|
|
|
2615
2672
|
}
|
|
2616
2673
|
|
|
2617
2674
|
// src/sequences/sequence-manager.ts
|
|
2618
|
-
var
|
|
2675
|
+
var import_core17 = require("@korajs/core");
|
|
2619
2676
|
var SequenceManager = class {
|
|
2620
2677
|
adapter;
|
|
2621
2678
|
nodeId;
|
|
@@ -2633,7 +2690,7 @@ var SequenceManager = class {
|
|
|
2633
2690
|
async next(name, config) {
|
|
2634
2691
|
const scope = config?.scope ?? "";
|
|
2635
2692
|
const startAt = config?.startAt ?? 1;
|
|
2636
|
-
const format = config?.format ?? (0,
|
|
2693
|
+
const format = config?.format ?? (0, import_core17.defaultSequenceFormat)(name);
|
|
2637
2694
|
let counter = 0;
|
|
2638
2695
|
await this.adapter.transaction(async (tx) => {
|
|
2639
2696
|
const rows = await tx.query(
|
|
@@ -2655,7 +2712,7 @@ var SequenceManager = class {
|
|
|
2655
2712
|
);
|
|
2656
2713
|
}
|
|
2657
2714
|
});
|
|
2658
|
-
return (0,
|
|
2715
|
+
return (0, import_core17.formatSequenceValue)(format, counter, this.nodeId);
|
|
2659
2716
|
}
|
|
2660
2717
|
/**
|
|
2661
2718
|
* Get the current counter value without incrementing.
|
|
@@ -3114,10 +3171,10 @@ var SubscriptionManager = class {
|
|
|
3114
3171
|
};
|
|
3115
3172
|
|
|
3116
3173
|
// src/sync/rebase-unsynced-operations.ts
|
|
3117
|
-
var
|
|
3174
|
+
var import_core18 = require("@korajs/core");
|
|
3118
3175
|
var import_internal2 = require("@korajs/core/internal");
|
|
3119
3176
|
init_serializer();
|
|
3120
|
-
var LOGICAL_COUNTER_LIMIT =
|
|
3177
|
+
var LOGICAL_COUNTER_LIMIT = import_core18.MAX_LOGICAL + 1;
|
|
3121
3178
|
function emptyResult() {
|
|
3122
3179
|
return { operations: [], idMapping: {}, rebasedCount: 0, newMaxTimestamp: null };
|
|
3123
3180
|
}
|
|
@@ -3129,7 +3186,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3129
3186
|
const rebased = [];
|
|
3130
3187
|
let maxOtherSerializedTs = null;
|
|
3131
3188
|
for (const collectionName of Object.keys(schema.collections)) {
|
|
3132
|
-
const rows = await adapter.query(
|
|
3189
|
+
const rows = await adapter.query(
|
|
3190
|
+
`SELECT * FROM ${(0, import_core18.quoteIdent)(`_kora_ops_${collectionName}`)}`
|
|
3191
|
+
);
|
|
3133
3192
|
for (const row of rows) {
|
|
3134
3193
|
if (rebaseIds.has(row.id)) {
|
|
3135
3194
|
rebased.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -3141,9 +3200,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3141
3200
|
if (rebased.length === 0) {
|
|
3142
3201
|
return emptyResult();
|
|
3143
3202
|
}
|
|
3144
|
-
const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY :
|
|
3203
|
+
const maxOtherWallTime = maxOtherSerializedTs === null ? Number.NEGATIVE_INFINITY : import_core18.HybridLogicalClock.deserialize(maxOtherSerializedTs).wallTime;
|
|
3145
3204
|
const baseWallTime = Math.max(correctedNowMs, maxOtherWallTime + 1);
|
|
3146
|
-
rebased.sort((a, b) =>
|
|
3205
|
+
rebased.sort((a, b) => import_core18.HybridLogicalClock.compare(a.timestamp, b.timestamp));
|
|
3147
3206
|
const newTimestamps = rebased.map((op, i) => ({
|
|
3148
3207
|
// Spill into wallTime past the 5-digit logical serialization limit so the
|
|
3149
3208
|
// serialized order stays correct even for enormous offline queues.
|
|
@@ -3171,7 +3230,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3171
3230
|
schemaVersion: op.schemaVersion,
|
|
3172
3231
|
...op.atomicOps !== void 0 ? { atomicOps: op.atomicOps } : {}
|
|
3173
3232
|
};
|
|
3174
|
-
const newId = await (0, import_internal2.computeOperationId)(input,
|
|
3233
|
+
const newId = await (0, import_internal2.computeOperationId)(input, import_core18.HybridLogicalClock.serialize(ts));
|
|
3175
3234
|
idMapping[op.id] = newId;
|
|
3176
3235
|
newIds.push(newId);
|
|
3177
3236
|
}
|
|
@@ -3198,7 +3257,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3198
3257
|
continue;
|
|
3199
3258
|
}
|
|
3200
3259
|
const opsTable = `_kora_ops_${oldOp.collection}`;
|
|
3201
|
-
await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
|
|
3260
|
+
await tx.execute(`DELETE FROM ${(0, import_core18.quoteIdent)(opsTable)} WHERE id = ?`, [oldOp.id]);
|
|
3202
3261
|
const opInsert = buildInsertQuery(
|
|
3203
3262
|
opsTable,
|
|
3204
3263
|
serializeOperation(newOp)
|
|
@@ -3207,11 +3266,11 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3207
3266
|
const oldVersion = serializeRowVersion(oldOp.timestamp);
|
|
3208
3267
|
const newVersion = serializeRowVersion(newOp.timestamp);
|
|
3209
3268
|
await tx.execute(
|
|
3210
|
-
`UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
3269
|
+
`UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
3211
3270
|
[newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
|
|
3212
3271
|
);
|
|
3213
3272
|
const rows = await tx.query(
|
|
3214
|
-
`SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
|
|
3273
|
+
`SELECT _field_versions FROM ${(0, import_core18.quoteIdent)(oldOp.collection)} WHERE id = ?`,
|
|
3215
3274
|
[oldOp.recordId]
|
|
3216
3275
|
);
|
|
3217
3276
|
const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
|
|
@@ -3223,14 +3282,14 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3223
3282
|
}
|
|
3224
3283
|
}
|
|
3225
3284
|
if (changed) {
|
|
3226
|
-
await tx.execute(
|
|
3227
|
-
|
|
3228
|
-
oldOp.recordId
|
|
3229
|
-
|
|
3285
|
+
await tx.execute(
|
|
3286
|
+
`UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _field_versions = ? WHERE id = ?`,
|
|
3287
|
+
[serializeFieldVersions(fieldVersions), oldOp.recordId]
|
|
3288
|
+
);
|
|
3230
3289
|
}
|
|
3231
3290
|
if (oldOp.type === "insert") {
|
|
3232
3291
|
await tx.execute(
|
|
3233
|
-
`UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
3292
|
+
`UPDATE ${(0, import_core18.quoteIdent)(oldOp.collection)} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
3234
3293
|
[newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
|
|
3235
3294
|
);
|
|
3236
3295
|
}
|
|
@@ -3246,7 +3305,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
3246
3305
|
}
|
|
3247
3306
|
|
|
3248
3307
|
// src/transaction/transaction-context.ts
|
|
3249
|
-
var
|
|
3308
|
+
var import_core19 = require("@korajs/core");
|
|
3250
3309
|
init_op_data_encoding();
|
|
3251
3310
|
init_serializer();
|
|
3252
3311
|
var TransactionContext = class {
|
|
@@ -3258,7 +3317,7 @@ var TransactionContext = class {
|
|
|
3258
3317
|
config;
|
|
3259
3318
|
constructor(config) {
|
|
3260
3319
|
this.config = config;
|
|
3261
|
-
this.transactionId = (0,
|
|
3320
|
+
this.transactionId = (0, import_core19.generateUUIDv7)();
|
|
3262
3321
|
}
|
|
3263
3322
|
/**
|
|
3264
3323
|
* Set a human-readable mutation name for this transaction.
|
|
@@ -3373,8 +3432,8 @@ var TransactionContext = class {
|
|
|
3373
3432
|
}
|
|
3374
3433
|
async insert(collectionName, definition, data) {
|
|
3375
3434
|
this.ensureActive();
|
|
3376
|
-
const validated = (0,
|
|
3377
|
-
const recordId = (0,
|
|
3435
|
+
const validated = (0, import_core19.validateRecord)(collectionName, definition, data, "insert");
|
|
3436
|
+
const recordId = (0, import_core19.generateUUIDv7)();
|
|
3378
3437
|
const now = Date.now();
|
|
3379
3438
|
for (const [fieldName, descriptor] of Object.entries(definition.fields)) {
|
|
3380
3439
|
if (descriptor.auto && descriptor.kind === "timestamp") {
|
|
@@ -3383,7 +3442,7 @@ var TransactionContext = class {
|
|
|
3383
3442
|
}
|
|
3384
3443
|
const sequenceNumber = await this.config.sequenceAllocator.allocate();
|
|
3385
3444
|
const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
|
|
3386
|
-
const operation = await (0,
|
|
3445
|
+
const operation = await (0, import_core19.createOperation)(
|
|
3387
3446
|
{
|
|
3388
3447
|
nodeId: this.config.nodeId,
|
|
3389
3448
|
type: "insert",
|
|
@@ -3438,7 +3497,7 @@ var TransactionContext = class {
|
|
|
3438
3497
|
if (!currentRecord) {
|
|
3439
3498
|
throw new RecordNotFoundError(collectionName, id);
|
|
3440
3499
|
}
|
|
3441
|
-
const validated = (0,
|
|
3500
|
+
const validated = (0, import_core19.validateRecord)(collectionName, definition, data, "update");
|
|
3442
3501
|
const now = Date.now();
|
|
3443
3502
|
const previousData = {};
|
|
3444
3503
|
const resolvedData = {};
|
|
@@ -3446,9 +3505,9 @@ var TransactionContext = class {
|
|
|
3446
3505
|
for (const key of Object.keys(validated)) {
|
|
3447
3506
|
const value = validated[key];
|
|
3448
3507
|
previousData[key] = currentRecord[key];
|
|
3449
|
-
if ((0,
|
|
3450
|
-
resolvedData[key] = (0,
|
|
3451
|
-
atomicOps[key] = (0,
|
|
3508
|
+
if ((0, import_core19.isAtomicOp)(value)) {
|
|
3509
|
+
resolvedData[key] = (0, import_core19.resolveAtomicOp)(currentRecord[key], value);
|
|
3510
|
+
atomicOps[key] = (0, import_core19.toAtomicOp)(value);
|
|
3452
3511
|
} else {
|
|
3453
3512
|
resolvedData[key] = value;
|
|
3454
3513
|
}
|
|
@@ -3456,7 +3515,7 @@ var TransactionContext = class {
|
|
|
3456
3515
|
const hasAtomicOps = Object.keys(atomicOps).length > 0;
|
|
3457
3516
|
const sequenceNumber = await this.config.sequenceAllocator.allocate();
|
|
3458
3517
|
const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
|
|
3459
|
-
const operation = await (0,
|
|
3518
|
+
const operation = await (0, import_core19.createOperation)(
|
|
3460
3519
|
{
|
|
3461
3520
|
nodeId: this.config.nodeId,
|
|
3462
3521
|
type: "update",
|
|
@@ -3511,7 +3570,7 @@ var TransactionContext = class {
|
|
|
3511
3570
|
const now = Date.now();
|
|
3512
3571
|
const sequenceNumber = await this.config.sequenceAllocator.allocate();
|
|
3513
3572
|
const causalDeps = this.config.causalTracker?.nextCausalDeps(collectionName, true) ?? [];
|
|
3514
|
-
const operation = await (0,
|
|
3573
|
+
const operation = await (0, import_core19.createOperation)(
|
|
3515
3574
|
{
|
|
3516
3575
|
nodeId: this.config.nodeId,
|
|
3517
3576
|
type: "delete",
|
|
@@ -3561,7 +3620,7 @@ var TransactionContext = class {
|
|
|
3561
3620
|
}
|
|
3562
3621
|
}
|
|
3563
3622
|
const rows = await this.config.adapter.query(
|
|
3564
|
-
`SELECT * FROM ${collectionName} WHERE id = ? AND _deleted = 0`,
|
|
3623
|
+
`SELECT * FROM ${(0, import_core19.quoteIdent)(collectionName)} WHERE id = ? AND _deleted = 0`,
|
|
3565
3624
|
[id]
|
|
3566
3625
|
);
|
|
3567
3626
|
let record = rows[0] ? deserializeRecord(rows[0], definition.fields) : null;
|
|
@@ -3616,7 +3675,7 @@ var TransactionSequenceAllocator = class {
|
|
|
3616
3675
|
};
|
|
3617
3676
|
|
|
3618
3677
|
// src/store/tab-node-id.ts
|
|
3619
|
-
var
|
|
3678
|
+
var import_core20 = require("@korajs/core");
|
|
3620
3679
|
function perTabStorageKey(dbName) {
|
|
3621
3680
|
return `kora-tab-node-${dbName}`;
|
|
3622
3681
|
}
|
|
@@ -3637,7 +3696,7 @@ function resolvePerTabNodeId(dbName) {
|
|
|
3637
3696
|
if (existing) {
|
|
3638
3697
|
return existing;
|
|
3639
3698
|
}
|
|
3640
|
-
const nodeId = (0,
|
|
3699
|
+
const nodeId = (0, import_core20.generateUUIDv7)();
|
|
3641
3700
|
savePerTabNodeId(dbName, nodeId);
|
|
3642
3701
|
return nodeId;
|
|
3643
3702
|
}
|
|
@@ -3647,7 +3706,7 @@ var Store = class {
|
|
|
3647
3706
|
opened = false;
|
|
3648
3707
|
nodeId = "";
|
|
3649
3708
|
sequenceNumber = 0;
|
|
3650
|
-
versionVector = (0,
|
|
3709
|
+
versionVector = (0, import_core23.createVersionVector)();
|
|
3651
3710
|
clock = null;
|
|
3652
3711
|
collections = /* @__PURE__ */ new Map();
|
|
3653
3712
|
subscriptionManager;
|
|
@@ -3661,6 +3720,7 @@ var Store = class {
|
|
|
3661
3720
|
localMutationHandler;
|
|
3662
3721
|
relationEnforcer = null;
|
|
3663
3722
|
causalTracker = null;
|
|
3723
|
+
secretKeyProvider;
|
|
3664
3724
|
constructor(config) {
|
|
3665
3725
|
this.schema = config.schema;
|
|
3666
3726
|
this.adapter = config.adapter;
|
|
@@ -3669,6 +3729,7 @@ var Store = class {
|
|
|
3669
3729
|
this.isolation = config.isolation ?? "shared";
|
|
3670
3730
|
this.emitter = config.emitter ?? null;
|
|
3671
3731
|
this.localMutationHandler = config.localMutationHandler ?? null;
|
|
3732
|
+
this.secretKeyProvider = config.secretKeyProvider;
|
|
3672
3733
|
this.subscriptionManager = new SubscriptionManager({
|
|
3673
3734
|
onQuerySubscribed: config.onQuerySubscribed
|
|
3674
3735
|
});
|
|
@@ -3681,8 +3742,8 @@ var Store = class {
|
|
|
3681
3742
|
await this.adapter.open(this.schema);
|
|
3682
3743
|
await this.runMigrationsIfNeeded();
|
|
3683
3744
|
this.nodeId = await this.loadOrGenerateNodeId();
|
|
3684
|
-
this.clock = new
|
|
3685
|
-
this.causalTracker = new
|
|
3745
|
+
this.clock = new import_core23.HybridLogicalClock(this.nodeId);
|
|
3746
|
+
this.causalTracker = new import_core23.CausalTracker();
|
|
3686
3747
|
this.sequenceManager = new SequenceManager(this.adapter, this.nodeId);
|
|
3687
3748
|
this.sequenceNumber = await this.loadSequenceNumber();
|
|
3688
3749
|
this.versionVector = await this.loadVersionVector();
|
|
@@ -3711,7 +3772,8 @@ var Store = class {
|
|
|
3711
3772
|
},
|
|
3712
3773
|
this.relationEnforcer,
|
|
3713
3774
|
this.localMutationHandler,
|
|
3714
|
-
this.causalTracker
|
|
3775
|
+
this.causalTracker,
|
|
3776
|
+
this.secretKeyProvider
|
|
3715
3777
|
);
|
|
3716
3778
|
this.collections.set(name, col);
|
|
3717
3779
|
}
|
|
@@ -3793,7 +3855,7 @@ var Store = class {
|
|
|
3793
3855
|
return "skipped";
|
|
3794
3856
|
}
|
|
3795
3857
|
const existing = await this.adapter.query(
|
|
3796
|
-
`SELECT id FROM _kora_ops_${collection} WHERE id = ?`,
|
|
3858
|
+
`SELECT id FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE id = ?`,
|
|
3797
3859
|
[op.id]
|
|
3798
3860
|
);
|
|
3799
3861
|
if (existing.length > 0) {
|
|
@@ -3898,7 +3960,7 @@ var Store = class {
|
|
|
3898
3960
|
const serializedChanges = serializeRecord(materializeSource, definition.fields);
|
|
3899
3961
|
const changedFields = Object.keys(serializedChanges);
|
|
3900
3962
|
const currentRows = await tx.query(
|
|
3901
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
3963
|
+
`SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
3902
3964
|
[op.recordId]
|
|
3903
3965
|
);
|
|
3904
3966
|
const currentRow = currentRows[0];
|
|
@@ -3994,20 +4056,20 @@ var Store = class {
|
|
|
3994
4056
|
*/
|
|
3995
4057
|
async foldOrphanedOperations(tx, collection, definition, recordId) {
|
|
3996
4058
|
const orphanRows = await tx.query(
|
|
3997
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
4059
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
3998
4060
|
[recordId]
|
|
3999
4061
|
);
|
|
4000
4062
|
if (orphanRows.length === 0) {
|
|
4001
4063
|
return;
|
|
4002
4064
|
}
|
|
4003
|
-
const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) =>
|
|
4065
|
+
const orphans = orphanRows.map((r) => deserializeOperationWithCollection(r, collection)).sort((a, b) => import_core23.HybridLogicalClock.compare(a.timestamp, b.timestamp));
|
|
4004
4066
|
for (const orphan of orphans) {
|
|
4005
4067
|
const orphanVersion = serializeRowVersion(orphan.timestamp);
|
|
4006
4068
|
const orphanWall = orphan.timestamp.wallTime;
|
|
4007
4069
|
if (orphan.type === "update" && orphan.data) {
|
|
4008
4070
|
const orphanChanges = serializeRecord(orphan.data, definition.fields);
|
|
4009
4071
|
const rows = await tx.query(
|
|
4010
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
4072
|
+
`SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
4011
4073
|
[recordId]
|
|
4012
4074
|
);
|
|
4013
4075
|
const current = rows[0];
|
|
@@ -4057,7 +4119,7 @@ var Store = class {
|
|
|
4057
4119
|
const allOps = [];
|
|
4058
4120
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
4059
4121
|
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`,
|
|
4122
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collectionName}`)} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
4061
4123
|
[nodeId, fromSeq, toSeq]
|
|
4062
4124
|
);
|
|
4063
4125
|
for (const row of rows) {
|
|
@@ -4076,7 +4138,7 @@ var Store = class {
|
|
|
4076
4138
|
const allOps = [];
|
|
4077
4139
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
4078
4140
|
const rows = await this.adapter.query(
|
|
4079
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
4141
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
4080
4142
|
);
|
|
4081
4143
|
for (const row of rows) {
|
|
4082
4144
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -4172,7 +4234,8 @@ var Store = class {
|
|
|
4172
4234
|
relationEnforcer: this.relationEnforcer,
|
|
4173
4235
|
causalTracker: this.causalTracker,
|
|
4174
4236
|
inTransaction: options?.inTransaction ?? false,
|
|
4175
|
-
extraCausalDeps: options?.extraCausalDeps
|
|
4237
|
+
extraCausalDeps: options?.extraCausalDeps,
|
|
4238
|
+
secretKeyProvider: this.secretKeyProvider
|
|
4176
4239
|
};
|
|
4177
4240
|
}
|
|
4178
4241
|
/**
|
|
@@ -4185,7 +4248,7 @@ var Store = class {
|
|
|
4185
4248
|
return null;
|
|
4186
4249
|
}
|
|
4187
4250
|
const rows = await this.adapter.query(
|
|
4188
|
-
`SELECT * FROM ${collection} WHERE id = ?`,
|
|
4251
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
4189
4252
|
[recordId]
|
|
4190
4253
|
);
|
|
4191
4254
|
const row = rows[0];
|
|
@@ -4210,7 +4273,7 @@ var Store = class {
|
|
|
4210
4273
|
return null;
|
|
4211
4274
|
}
|
|
4212
4275
|
const rows = await this.adapter.query(
|
|
4213
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
4276
|
+
`SELECT _version, _field_versions FROM ${(0, import_core23.quoteIdent)(collection)} WHERE id = ?`,
|
|
4214
4277
|
[recordId]
|
|
4215
4278
|
);
|
|
4216
4279
|
const row = rows[0];
|
|
@@ -4302,7 +4365,7 @@ var Store = class {
|
|
|
4302
4365
|
async compact(strategy) {
|
|
4303
4366
|
this.ensureOpen();
|
|
4304
4367
|
if (strategy.mode === "never") {
|
|
4305
|
-
return compactOperationLog(this.adapter, this.schema, strategy, (0,
|
|
4368
|
+
return compactOperationLog(this.adapter, this.schema, strategy, (0, import_core23.createVersionVector)());
|
|
4306
4369
|
}
|
|
4307
4370
|
const serverVector = strategy.serverVector ?? await loadLastAckedServerVector(this.adapter);
|
|
4308
4371
|
return compactOperationLog(this.adapter, this.schema, strategy, serverVector);
|
|
@@ -4316,7 +4379,7 @@ var Store = class {
|
|
|
4316
4379
|
async getLatestLocalOperationForRecord(collection, recordId) {
|
|
4317
4380
|
this.ensureOpen();
|
|
4318
4381
|
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`,
|
|
4382
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
4320
4383
|
[this.nodeId, recordId]
|
|
4321
4384
|
);
|
|
4322
4385
|
const row = rows[0];
|
|
@@ -4331,13 +4394,13 @@ var Store = class {
|
|
|
4331
4394
|
async getLatestOperationForRecord(collection, recordId) {
|
|
4332
4395
|
this.ensureOpen();
|
|
4333
4396
|
const rows = await this.adapter.query(
|
|
4334
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
|
|
4397
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(`_kora_ops_${collection}`)} WHERE record_id = ?`,
|
|
4335
4398
|
[recordId]
|
|
4336
4399
|
);
|
|
4337
4400
|
let latest = null;
|
|
4338
4401
|
for (const row of rows) {
|
|
4339
4402
|
const op = deserializeOperationWithCollection(row, collection);
|
|
4340
|
-
if (!latest ||
|
|
4403
|
+
if (!latest || import_core23.HybridLogicalClock.compare(op.timestamp, latest.timestamp) > 0) {
|
|
4341
4404
|
latest = op;
|
|
4342
4405
|
}
|
|
4343
4406
|
}
|
|
@@ -4480,7 +4543,7 @@ var Store = class {
|
|
|
4480
4543
|
for (let v = storedVersion + 1; v <= targetVersion; v++) {
|
|
4481
4544
|
const migration = migrations[v];
|
|
4482
4545
|
if (!migration) continue;
|
|
4483
|
-
const sqlStatements = (0,
|
|
4546
|
+
const sqlStatements = (0, import_core23.migrationStepsToSQL)(migration.steps);
|
|
4484
4547
|
for (const sql of sqlStatements) {
|
|
4485
4548
|
try {
|
|
4486
4549
|
await this.adapter.execute(sql);
|
|
@@ -4518,14 +4581,14 @@ var Store = class {
|
|
|
4518
4581
|
*/
|
|
4519
4582
|
async runBackfill(collection, transform) {
|
|
4520
4583
|
const rows = await this.adapter.query(
|
|
4521
|
-
`SELECT * FROM ${collection} WHERE _deleted = 0`
|
|
4584
|
+
`SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE _deleted = 0`
|
|
4522
4585
|
);
|
|
4523
4586
|
await this.adapter.transaction(async (tx) => {
|
|
4524
4587
|
for (const row of rows) {
|
|
4525
4588
|
const updates = transform(row);
|
|
4526
4589
|
const fields = Object.keys(updates);
|
|
4527
4590
|
if (fields.length === 0) continue;
|
|
4528
|
-
const setClauses = fields.map((f) => `${f} = ?`).join(", ");
|
|
4591
|
+
const setClauses = fields.map((f) => `${(0, import_core23.quoteIdent)(f)} = ?`).join(", ");
|
|
4529
4592
|
const values = fields.map((f) => {
|
|
4530
4593
|
const val = updates[f];
|
|
4531
4594
|
if (typeof val === "boolean") return val ? 1 : 0;
|
|
@@ -4535,7 +4598,7 @@ var Store = class {
|
|
|
4535
4598
|
return val;
|
|
4536
4599
|
});
|
|
4537
4600
|
values.push(row.id);
|
|
4538
|
-
await tx.execute(`UPDATE ${collection} SET ${setClauses} WHERE id = ?`, values);
|
|
4601
|
+
await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET ${setClauses} WHERE id = ?`, values);
|
|
4539
4602
|
}
|
|
4540
4603
|
});
|
|
4541
4604
|
}
|
|
@@ -4558,7 +4621,7 @@ var Store = class {
|
|
|
4558
4621
|
if (rows[0]) {
|
|
4559
4622
|
return rows[0].value;
|
|
4560
4623
|
}
|
|
4561
|
-
const newNodeId = (0,
|
|
4624
|
+
const newNodeId = (0, import_core23.generateUUIDv7)();
|
|
4562
4625
|
await this.adapter.execute("INSERT INTO _kora_meta (key, value) VALUES ('node_id', ?)", [
|
|
4563
4626
|
newNodeId
|
|
4564
4627
|
]);
|
|
@@ -4575,7 +4638,7 @@ var Store = class {
|
|
|
4575
4638
|
const rows = await this.adapter.query(
|
|
4576
4639
|
"SELECT node_id, sequence_number FROM _kora_version_vector"
|
|
4577
4640
|
);
|
|
4578
|
-
const vector = (0,
|
|
4641
|
+
const vector = (0, import_core23.createVersionVector)();
|
|
4579
4642
|
for (const row of rows) {
|
|
4580
4643
|
vector.set(row.node_id, row.sequence_number);
|
|
4581
4644
|
}
|
|
@@ -4649,12 +4712,12 @@ var QueryStore = class {
|
|
|
4649
4712
|
};
|
|
4650
4713
|
|
|
4651
4714
|
// src/reactivity/assert-query-ready.ts
|
|
4652
|
-
var
|
|
4715
|
+
var import_core24 = require("@korajs/core");
|
|
4653
4716
|
var LEGACY_PENDING_COLLECTION = "__pending__";
|
|
4654
4717
|
function assertQueryReady(query) {
|
|
4655
4718
|
const descriptor = query.getDescriptor();
|
|
4656
4719
|
if (descriptor.collection === LEGACY_PENDING_COLLECTION) {
|
|
4657
|
-
throw new
|
|
4720
|
+
throw new import_core24.AppNotReadyError(
|
|
4658
4721
|
"Cannot use useQuery() before app.ready. Await app.ready or wrap your UI in <KoraProvider app={app}>."
|
|
4659
4722
|
);
|
|
4660
4723
|
}
|
|
@@ -5075,13 +5138,569 @@ init_backup();
|
|
|
5075
5138
|
init_audit_trace_store();
|
|
5076
5139
|
init_audit_json();
|
|
5077
5140
|
init_export_audit();
|
|
5141
|
+
|
|
5142
|
+
// src/blob/content-addressed-blob-store.ts
|
|
5143
|
+
var import_core25 = require("@korajs/core");
|
|
5144
|
+
var BlobIntegrityError = class extends Error {
|
|
5145
|
+
constructor(expectedHash, actualHash) {
|
|
5146
|
+
super(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`);
|
|
5147
|
+
this.expectedHash = expectedHash;
|
|
5148
|
+
this.actualHash = actualHash;
|
|
5149
|
+
this.name = "BlobIntegrityError";
|
|
5150
|
+
}
|
|
5151
|
+
expectedHash;
|
|
5152
|
+
actualHash;
|
|
5153
|
+
};
|
|
5154
|
+
var MemoryBlobStore = class {
|
|
5155
|
+
blobs = /* @__PURE__ */ new Map();
|
|
5156
|
+
async put(bytes, metadata = {}) {
|
|
5157
|
+
const ref = await (0, import_core25.createBlobRef)(bytes, metadata);
|
|
5158
|
+
if (!this.blobs.has(ref.hash)) {
|
|
5159
|
+
const stored = new Uint8Array(bytes.byteLength);
|
|
5160
|
+
stored.set(bytes);
|
|
5161
|
+
this.blobs.set(ref.hash, stored);
|
|
5162
|
+
}
|
|
5163
|
+
return ref;
|
|
5164
|
+
}
|
|
5165
|
+
async get(hash) {
|
|
5166
|
+
const stored = this.blobs.get(hash);
|
|
5167
|
+
if (stored === void 0) {
|
|
5168
|
+
return null;
|
|
5169
|
+
}
|
|
5170
|
+
const actualHash = await (0, import_core25.hashBlob)(stored);
|
|
5171
|
+
if (actualHash !== hash) {
|
|
5172
|
+
throw new BlobIntegrityError(hash, actualHash);
|
|
5173
|
+
}
|
|
5174
|
+
const out = new Uint8Array(stored.byteLength);
|
|
5175
|
+
out.set(stored);
|
|
5176
|
+
return out;
|
|
5177
|
+
}
|
|
5178
|
+
async has(hash) {
|
|
5179
|
+
return this.blobs.has(hash);
|
|
5180
|
+
}
|
|
5181
|
+
async delete(hash) {
|
|
5182
|
+
return this.blobs.delete(hash);
|
|
5183
|
+
}
|
|
5184
|
+
async size() {
|
|
5185
|
+
return this.blobs.size;
|
|
5186
|
+
}
|
|
5187
|
+
async list() {
|
|
5188
|
+
return [...this.blobs.keys()];
|
|
5189
|
+
}
|
|
5190
|
+
};
|
|
5191
|
+
|
|
5192
|
+
// src/blob/opfs-blob-store.ts
|
|
5193
|
+
var import_core26 = require("@korajs/core");
|
|
5194
|
+
var OpfsBlobStore = class {
|
|
5195
|
+
constructor(dir) {
|
|
5196
|
+
this.dir = dir;
|
|
5197
|
+
}
|
|
5198
|
+
dir;
|
|
5199
|
+
shardFor(hash) {
|
|
5200
|
+
return hash.slice(0, 2);
|
|
5201
|
+
}
|
|
5202
|
+
async put(bytes, metadata = {}) {
|
|
5203
|
+
const ref = await (0, import_core26.createBlobRef)(bytes, metadata);
|
|
5204
|
+
const shard = this.shardFor(ref.hash);
|
|
5205
|
+
if (await this.dir.has(shard, ref.hash)) {
|
|
5206
|
+
return ref;
|
|
5207
|
+
}
|
|
5208
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
5209
|
+
out.set(bytes);
|
|
5210
|
+
await this.dir.write(shard, ref.hash, out);
|
|
5211
|
+
return ref;
|
|
5212
|
+
}
|
|
5213
|
+
async get(hash) {
|
|
5214
|
+
const bytes = await this.dir.read(this.shardFor(hash), hash);
|
|
5215
|
+
if (bytes === null) {
|
|
5216
|
+
return null;
|
|
5217
|
+
}
|
|
5218
|
+
const actual = await (0, import_core26.hashBlob)(bytes);
|
|
5219
|
+
if (actual !== hash) {
|
|
5220
|
+
throw new BlobIntegrityError(hash, actual);
|
|
5221
|
+
}
|
|
5222
|
+
return bytes;
|
|
5223
|
+
}
|
|
5224
|
+
async has(hash) {
|
|
5225
|
+
return this.dir.has(this.shardFor(hash), hash);
|
|
5226
|
+
}
|
|
5227
|
+
async delete(hash) {
|
|
5228
|
+
return this.dir.remove(this.shardFor(hash), hash);
|
|
5229
|
+
}
|
|
5230
|
+
async size() {
|
|
5231
|
+
return this.dir.count();
|
|
5232
|
+
}
|
|
5233
|
+
async list() {
|
|
5234
|
+
return this.dir.keys();
|
|
5235
|
+
}
|
|
5236
|
+
};
|
|
5237
|
+
function getStorageManager() {
|
|
5238
|
+
const nav = globalThis.navigator;
|
|
5239
|
+
if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
|
|
5240
|
+
throw new Error(
|
|
5241
|
+
"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."
|
|
5242
|
+
);
|
|
5243
|
+
}
|
|
5244
|
+
return nav.storage;
|
|
5245
|
+
}
|
|
5246
|
+
function isNotFoundError(error) {
|
|
5247
|
+
return typeof error === "object" && error !== null && error.name === "NotFoundError";
|
|
5248
|
+
}
|
|
5249
|
+
async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
|
|
5250
|
+
const storage = getStorageManager();
|
|
5251
|
+
if (typeof storage.persist === "function") {
|
|
5252
|
+
try {
|
|
5253
|
+
await storage.persist();
|
|
5254
|
+
} catch {
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
const opfsRoot = await storage.getDirectory();
|
|
5258
|
+
const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
|
|
5259
|
+
async function shardHandle(shard, create) {
|
|
5260
|
+
try {
|
|
5261
|
+
return await root.getDirectoryHandle(shard, { create });
|
|
5262
|
+
} catch (error) {
|
|
5263
|
+
if (isNotFoundError(error)) {
|
|
5264
|
+
return null;
|
|
5265
|
+
}
|
|
5266
|
+
throw error;
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
return {
|
|
5270
|
+
async read(shard, name) {
|
|
5271
|
+
const dir = await shardHandle(shard, false);
|
|
5272
|
+
if (!dir) {
|
|
5273
|
+
return null;
|
|
5274
|
+
}
|
|
5275
|
+
let handle;
|
|
5276
|
+
try {
|
|
5277
|
+
handle = await dir.getFileHandle(name, { create: false });
|
|
5278
|
+
} catch (error) {
|
|
5279
|
+
if (isNotFoundError(error)) {
|
|
5280
|
+
return null;
|
|
5281
|
+
}
|
|
5282
|
+
throw error;
|
|
5283
|
+
}
|
|
5284
|
+
const file = await handle.getFile();
|
|
5285
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
5286
|
+
},
|
|
5287
|
+
async write(shard, name, bytes) {
|
|
5288
|
+
const dir = await root.getDirectoryHandle(shard, { create: true });
|
|
5289
|
+
const handle = await dir.getFileHandle(name, { create: true });
|
|
5290
|
+
const writable = await handle.createWritable({ keepExistingData: false });
|
|
5291
|
+
try {
|
|
5292
|
+
await writable.write(bytes);
|
|
5293
|
+
} finally {
|
|
5294
|
+
await writable.close();
|
|
5295
|
+
}
|
|
5296
|
+
},
|
|
5297
|
+
async remove(shard, name) {
|
|
5298
|
+
const dir = await shardHandle(shard, false);
|
|
5299
|
+
if (!dir) {
|
|
5300
|
+
return false;
|
|
5301
|
+
}
|
|
5302
|
+
try {
|
|
5303
|
+
await dir.removeEntry(name);
|
|
5304
|
+
return true;
|
|
5305
|
+
} catch (error) {
|
|
5306
|
+
if (isNotFoundError(error)) {
|
|
5307
|
+
return false;
|
|
5308
|
+
}
|
|
5309
|
+
throw error;
|
|
5310
|
+
}
|
|
5311
|
+
},
|
|
5312
|
+
async has(shard, name) {
|
|
5313
|
+
const dir = await shardHandle(shard, false);
|
|
5314
|
+
if (!dir) {
|
|
5315
|
+
return false;
|
|
5316
|
+
}
|
|
5317
|
+
try {
|
|
5318
|
+
await dir.getFileHandle(name, { create: false });
|
|
5319
|
+
return true;
|
|
5320
|
+
} catch (error) {
|
|
5321
|
+
if (isNotFoundError(error)) {
|
|
5322
|
+
return false;
|
|
5323
|
+
}
|
|
5324
|
+
throw error;
|
|
5325
|
+
}
|
|
5326
|
+
},
|
|
5327
|
+
async count() {
|
|
5328
|
+
let total = 0;
|
|
5329
|
+
for await (const shard of root.keys()) {
|
|
5330
|
+
const dir = await shardHandle(shard, false);
|
|
5331
|
+
if (!dir) {
|
|
5332
|
+
continue;
|
|
5333
|
+
}
|
|
5334
|
+
for await (const name of dir.keys()) {
|
|
5335
|
+
if (!name.endsWith(".tmp")) {
|
|
5336
|
+
total++;
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
return total;
|
|
5341
|
+
},
|
|
5342
|
+
async keys() {
|
|
5343
|
+
const names = [];
|
|
5344
|
+
for await (const shard of root.keys()) {
|
|
5345
|
+
const dir = await shardHandle(shard, false);
|
|
5346
|
+
if (!dir) {
|
|
5347
|
+
continue;
|
|
5348
|
+
}
|
|
5349
|
+
for await (const name of dir.keys()) {
|
|
5350
|
+
if (!name.endsWith(".tmp")) {
|
|
5351
|
+
names.push(name);
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
return names;
|
|
5356
|
+
}
|
|
5357
|
+
};
|
|
5358
|
+
}
|
|
5359
|
+
async function createOpfsBlobStore(rootDirName = "kora-blobs") {
|
|
5360
|
+
return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
|
|
5361
|
+
}
|
|
5362
|
+
|
|
5363
|
+
// src/blob/blob-chunking.ts
|
|
5364
|
+
var import_core27 = require("@korajs/core");
|
|
5365
|
+
var DEFAULT_CHUNK_SIZE = 256 * 1024;
|
|
5366
|
+
async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
|
|
5367
|
+
if (chunkSize <= 0) {
|
|
5368
|
+
throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
|
|
5369
|
+
}
|
|
5370
|
+
const chunkHashes = [];
|
|
5371
|
+
const chunks = /* @__PURE__ */ new Map();
|
|
5372
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
5373
|
+
const end = Math.min(offset + chunkSize, bytes.byteLength);
|
|
5374
|
+
const chunk = new Uint8Array(end - offset);
|
|
5375
|
+
chunk.set(bytes.subarray(offset, end));
|
|
5376
|
+
const hash = await (0, import_core27.hashBlob)(chunk);
|
|
5377
|
+
chunkHashes.push(hash);
|
|
5378
|
+
if (!chunks.has(hash)) {
|
|
5379
|
+
chunks.set(hash, chunk);
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
const blobHash = await (0, import_core27.hashBlob)(bytes);
|
|
5383
|
+
const manifest = {
|
|
5384
|
+
blobHash,
|
|
5385
|
+
size: bytes.byteLength,
|
|
5386
|
+
chunkSize,
|
|
5387
|
+
chunkHashes
|
|
5388
|
+
};
|
|
5389
|
+
if (metadata.mimeType !== void 0) {
|
|
5390
|
+
manifest.mimeType = metadata.mimeType;
|
|
5391
|
+
}
|
|
5392
|
+
if (metadata.filename !== void 0) {
|
|
5393
|
+
manifest.filename = metadata.filename;
|
|
5394
|
+
}
|
|
5395
|
+
return { manifest, chunks };
|
|
5396
|
+
}
|
|
5397
|
+
async function reassembleBlob(manifest, chunkStore) {
|
|
5398
|
+
const parts = [];
|
|
5399
|
+
let total = 0;
|
|
5400
|
+
for (const hash of manifest.chunkHashes) {
|
|
5401
|
+
const chunk = await chunkStore.get(hash);
|
|
5402
|
+
if (chunk === null) {
|
|
5403
|
+
throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
|
|
5404
|
+
}
|
|
5405
|
+
parts.push(chunk);
|
|
5406
|
+
total += chunk.byteLength;
|
|
5407
|
+
}
|
|
5408
|
+
const out = new Uint8Array(total);
|
|
5409
|
+
let offset = 0;
|
|
5410
|
+
for (const part of parts) {
|
|
5411
|
+
out.set(part, offset);
|
|
5412
|
+
offset += part.byteLength;
|
|
5413
|
+
}
|
|
5414
|
+
const actual = await (0, import_core27.hashBlob)(out);
|
|
5415
|
+
if (actual !== manifest.blobHash) {
|
|
5416
|
+
throw new BlobIntegrityError(manifest.blobHash, actual);
|
|
5417
|
+
}
|
|
5418
|
+
return out;
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
// src/blob/blob-transfer.ts
|
|
5422
|
+
var import_core28 = require("@korajs/core");
|
|
5423
|
+
async function receiveBlob(manifest, provider, stores) {
|
|
5424
|
+
let chunksFetched = 0;
|
|
5425
|
+
let chunksSkipped = 0;
|
|
5426
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5427
|
+
for (const hash of manifest.chunkHashes) {
|
|
5428
|
+
if (seen.has(hash)) {
|
|
5429
|
+
continue;
|
|
5430
|
+
}
|
|
5431
|
+
seen.add(hash);
|
|
5432
|
+
if (await stores.chunkStore.has(hash)) {
|
|
5433
|
+
chunksSkipped++;
|
|
5434
|
+
continue;
|
|
5435
|
+
}
|
|
5436
|
+
const bytes = await provider.getChunk(hash);
|
|
5437
|
+
if (bytes === null) {
|
|
5438
|
+
throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
|
|
5439
|
+
}
|
|
5440
|
+
const actual = await (0, import_core28.hashBlob)(bytes);
|
|
5441
|
+
if (actual !== hash) {
|
|
5442
|
+
throw new BlobIntegrityError(hash, actual);
|
|
5443
|
+
}
|
|
5444
|
+
await stores.chunkStore.put(bytes);
|
|
5445
|
+
chunksFetched++;
|
|
5446
|
+
}
|
|
5447
|
+
const full = await reassembleBlob(manifest, stores.chunkStore);
|
|
5448
|
+
const ref = await stores.blobStore.put(full, {
|
|
5449
|
+
mimeType: manifest.mimeType,
|
|
5450
|
+
filename: manifest.filename
|
|
5451
|
+
});
|
|
5452
|
+
return { ref, chunksFetched, chunksSkipped };
|
|
5453
|
+
}
|
|
5454
|
+
async function prepareBlobForSend(bytes, chunkStore, options = {}) {
|
|
5455
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
5456
|
+
mimeType: options.mimeType,
|
|
5457
|
+
filename: options.filename
|
|
5458
|
+
});
|
|
5459
|
+
for (const chunk of chunks.values()) {
|
|
5460
|
+
await chunkStore.put(chunk);
|
|
5461
|
+
}
|
|
5462
|
+
const provider = {
|
|
5463
|
+
getChunk: (hash) => chunkStore.get(hash)
|
|
5464
|
+
};
|
|
5465
|
+
return { manifest, provider };
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
// src/blob/blob-manifest-transfer.ts
|
|
5469
|
+
var import_core29 = require("@korajs/core");
|
|
5470
|
+
function serializeBlobManifest(manifest) {
|
|
5471
|
+
const canonical = {
|
|
5472
|
+
blobHash: manifest.blobHash,
|
|
5473
|
+
size: manifest.size,
|
|
5474
|
+
chunkSize: manifest.chunkSize,
|
|
5475
|
+
chunkHashes: manifest.chunkHashes
|
|
5476
|
+
};
|
|
5477
|
+
if (manifest.mimeType !== void 0) {
|
|
5478
|
+
canonical.mimeType = manifest.mimeType;
|
|
5479
|
+
}
|
|
5480
|
+
if (manifest.filename !== void 0) {
|
|
5481
|
+
canonical.filename = manifest.filename;
|
|
5482
|
+
}
|
|
5483
|
+
return new TextEncoder().encode(JSON.stringify(canonical));
|
|
5484
|
+
}
|
|
5485
|
+
function parseBlobManifest(bytes) {
|
|
5486
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
5487
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
5488
|
+
throw new Error("Invalid blob manifest: not an object");
|
|
5489
|
+
}
|
|
5490
|
+
const obj = parsed;
|
|
5491
|
+
if (typeof obj.blobHash !== "string") {
|
|
5492
|
+
throw new Error("Invalid blob manifest: missing blobHash");
|
|
5493
|
+
}
|
|
5494
|
+
if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
|
|
5495
|
+
throw new Error("Invalid blob manifest: missing size or chunkSize");
|
|
5496
|
+
}
|
|
5497
|
+
if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
|
|
5498
|
+
throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
|
|
5499
|
+
}
|
|
5500
|
+
const manifest = {
|
|
5501
|
+
blobHash: obj.blobHash,
|
|
5502
|
+
size: obj.size,
|
|
5503
|
+
chunkSize: obj.chunkSize,
|
|
5504
|
+
chunkHashes: obj.chunkHashes
|
|
5505
|
+
};
|
|
5506
|
+
if (typeof obj.mimeType === "string") {
|
|
5507
|
+
manifest.mimeType = obj.mimeType;
|
|
5508
|
+
}
|
|
5509
|
+
if (typeof obj.filename === "string") {
|
|
5510
|
+
manifest.filename = obj.filename;
|
|
5511
|
+
}
|
|
5512
|
+
return manifest;
|
|
5513
|
+
}
|
|
5514
|
+
async function putBlobForTransfer(store, bytes, options = {}) {
|
|
5515
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
5516
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
5517
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
5518
|
+
});
|
|
5519
|
+
for (const chunk of chunks.values()) {
|
|
5520
|
+
await store.put(chunk);
|
|
5521
|
+
}
|
|
5522
|
+
const baseRef = await store.put(bytes, {
|
|
5523
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
5524
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
5525
|
+
});
|
|
5526
|
+
const manifestRef = await store.put(serializeBlobManifest(manifest));
|
|
5527
|
+
return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
|
|
5528
|
+
}
|
|
5529
|
+
async function fetchBlobManifest(provider, manifestHash) {
|
|
5530
|
+
const bytes = await provider.getChunk(manifestHash);
|
|
5531
|
+
if (bytes === null) {
|
|
5532
|
+
throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
|
|
5533
|
+
}
|
|
5534
|
+
const actual = await (0, import_core29.hashBlob)(bytes);
|
|
5535
|
+
if (actual !== manifestHash) {
|
|
5536
|
+
throw new BlobIntegrityError(manifestHash, actual);
|
|
5537
|
+
}
|
|
5538
|
+
return parseBlobManifest(bytes);
|
|
5539
|
+
}
|
|
5540
|
+
async function resolveBlobManifest(provider, ref) {
|
|
5541
|
+
if (ref.manifestHash === void 0) {
|
|
5542
|
+
throw new Error(
|
|
5543
|
+
`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.`
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5546
|
+
return fetchBlobManifest(provider, ref.manifestHash);
|
|
5547
|
+
}
|
|
5548
|
+
|
|
5549
|
+
// src/blob/server-blob-callbacks.ts
|
|
5550
|
+
var import_core30 = require("@korajs/core");
|
|
5551
|
+
function toServerBlobCallbacks(store) {
|
|
5552
|
+
return {
|
|
5553
|
+
resolveBlobChunk: (hash) => store.get(hash),
|
|
5554
|
+
async persistBlobChunk(hash, bytes) {
|
|
5555
|
+
const actual = await (0, import_core30.hashBlob)(bytes);
|
|
5556
|
+
if (actual !== hash) {
|
|
5557
|
+
return;
|
|
5558
|
+
}
|
|
5559
|
+
await store.put(bytes);
|
|
5560
|
+
}
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
function createMemoryServerBlobStore() {
|
|
5564
|
+
const store = new MemoryBlobStore();
|
|
5565
|
+
return { store, callbacks: toServerBlobCallbacks(store) };
|
|
5566
|
+
}
|
|
5567
|
+
|
|
5568
|
+
// src/blob/blob-gc.ts
|
|
5569
|
+
var import_core31 = require("@korajs/core");
|
|
5570
|
+
function extractBlobRefs(record) {
|
|
5571
|
+
const refs = [];
|
|
5572
|
+
for (const value of Object.values(record)) {
|
|
5573
|
+
if ((0, import_core31.isBlobRef)(value)) {
|
|
5574
|
+
refs.push(value);
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
return refs;
|
|
5578
|
+
}
|
|
5579
|
+
async function collectBlobGarbage(store, liveRefs, options = {}) {
|
|
5580
|
+
const live = /* @__PURE__ */ new Set();
|
|
5581
|
+
for (const ref of liveRefs) {
|
|
5582
|
+
live.add(ref.hash);
|
|
5583
|
+
if (ref.manifestHash === void 0) {
|
|
5584
|
+
continue;
|
|
5585
|
+
}
|
|
5586
|
+
live.add(ref.manifestHash);
|
|
5587
|
+
try {
|
|
5588
|
+
const manifestBytes = await store.get(ref.manifestHash);
|
|
5589
|
+
if (manifestBytes !== null) {
|
|
5590
|
+
const manifest = parseBlobManifest(manifestBytes);
|
|
5591
|
+
for (const chunkHash of manifest.chunkHashes) {
|
|
5592
|
+
live.add(chunkHash);
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
} catch {
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
const all = await store.list();
|
|
5599
|
+
const collectedHashes = [];
|
|
5600
|
+
for (const hash of all) {
|
|
5601
|
+
if (live.has(hash)) {
|
|
5602
|
+
continue;
|
|
5603
|
+
}
|
|
5604
|
+
collectedHashes.push(hash);
|
|
5605
|
+
if (!options.dryRun) {
|
|
5606
|
+
await store.delete(hash);
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
return {
|
|
5610
|
+
scanned: all.length,
|
|
5611
|
+
live: live.size,
|
|
5612
|
+
collected: collectedHashes.length,
|
|
5613
|
+
collectedHashes
|
|
5614
|
+
};
|
|
5615
|
+
}
|
|
5616
|
+
|
|
5617
|
+
// src/blob/blob-chunk-transport.ts
|
|
5618
|
+
var import_core32 = require("@korajs/core");
|
|
5619
|
+
function createRemoteChunkProvider(port, options = {}) {
|
|
5620
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
5621
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5622
|
+
port.onMessage((message) => {
|
|
5623
|
+
if (message.type !== "blob-chunk-response") {
|
|
5624
|
+
return;
|
|
5625
|
+
}
|
|
5626
|
+
const entry = pending.get(message.requestId);
|
|
5627
|
+
if (!entry) {
|
|
5628
|
+
return;
|
|
5629
|
+
}
|
|
5630
|
+
pending.delete(message.requestId);
|
|
5631
|
+
clearTimeout(entry.timer);
|
|
5632
|
+
entry.resolve(message.bytes);
|
|
5633
|
+
});
|
|
5634
|
+
return {
|
|
5635
|
+
pendingCount: () => pending.size,
|
|
5636
|
+
getChunk(hash) {
|
|
5637
|
+
const requestId = (0, import_core32.generateUUIDv7)();
|
|
5638
|
+
return new Promise((resolve, reject) => {
|
|
5639
|
+
const timer = setTimeout(() => {
|
|
5640
|
+
pending.delete(requestId);
|
|
5641
|
+
reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
|
|
5642
|
+
}, timeoutMs);
|
|
5643
|
+
pending.set(requestId, { resolve, reject, timer });
|
|
5644
|
+
port.send({ type: "blob-chunk-request", requestId, hash });
|
|
5645
|
+
});
|
|
5646
|
+
}
|
|
5647
|
+
};
|
|
5648
|
+
}
|
|
5649
|
+
function serveBlobChunks(port, blobStore) {
|
|
5650
|
+
port.onMessage((message) => {
|
|
5651
|
+
if (message.type !== "blob-chunk-request") {
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
void blobStore.get(message.hash).then(
|
|
5655
|
+
(bytes) => {
|
|
5656
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
|
|
5657
|
+
},
|
|
5658
|
+
() => {
|
|
5659
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
|
|
5660
|
+
}
|
|
5661
|
+
);
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
function createChunkPortPair() {
|
|
5665
|
+
const handlersA = [];
|
|
5666
|
+
const handlersB = [];
|
|
5667
|
+
const a = {
|
|
5668
|
+
send(message) {
|
|
5669
|
+
queueMicrotask(() => {
|
|
5670
|
+
for (const handler of handlersB) {
|
|
5671
|
+
handler(message);
|
|
5672
|
+
}
|
|
5673
|
+
});
|
|
5674
|
+
},
|
|
5675
|
+
onMessage(handler) {
|
|
5676
|
+
handlersA.push(handler);
|
|
5677
|
+
}
|
|
5678
|
+
};
|
|
5679
|
+
const b = {
|
|
5680
|
+
send(message) {
|
|
5681
|
+
queueMicrotask(() => {
|
|
5682
|
+
for (const handler of handlersA) {
|
|
5683
|
+
handler(message);
|
|
5684
|
+
}
|
|
5685
|
+
});
|
|
5686
|
+
},
|
|
5687
|
+
onMessage(handler) {
|
|
5688
|
+
handlersB.push(handler);
|
|
5689
|
+
}
|
|
5690
|
+
};
|
|
5691
|
+
return { a, b };
|
|
5692
|
+
}
|
|
5078
5693
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5079
5694
|
0 && (module.exports = {
|
|
5080
5695
|
AdapterError,
|
|
5696
|
+
BlobIntegrityError,
|
|
5081
5697
|
COMPACTION_BASELINE_META_KEY,
|
|
5082
5698
|
Collection,
|
|
5699
|
+
DEFAULT_CHUNK_SIZE,
|
|
5083
5700
|
InvalidStateTransitionError,
|
|
5084
5701
|
LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
5702
|
+
MemoryBlobStore,
|
|
5703
|
+
OpfsBlobStore,
|
|
5085
5704
|
OptimisticLockError,
|
|
5086
5705
|
PersistenceError,
|
|
5087
5706
|
QueryBuilder,
|
|
@@ -5099,26 +5718,44 @@ init_export_audit();
|
|
|
5099
5718
|
WorkerTimeoutError,
|
|
5100
5719
|
asRichTextSyncEngine,
|
|
5101
5720
|
assertQueryReady,
|
|
5721
|
+
chunkBlob,
|
|
5722
|
+
collectBlobGarbage,
|
|
5102
5723
|
collectOperationsAheadOfServer,
|
|
5103
5724
|
compactOperationLog,
|
|
5104
5725
|
computeAckCompactionWatermark,
|
|
5726
|
+
createChunkPortPair,
|
|
5727
|
+
createMemoryServerBlobStore,
|
|
5728
|
+
createOpfsBlobDirectory,
|
|
5729
|
+
createOpfsBlobStore,
|
|
5730
|
+
createRemoteChunkProvider,
|
|
5105
5731
|
createRichTextController,
|
|
5106
5732
|
decodeAuditExport,
|
|
5107
5733
|
decodeRichtext,
|
|
5108
5734
|
deserializeVersionVectorFromMeta,
|
|
5109
5735
|
encodeRichtext,
|
|
5110
5736
|
exportBackup,
|
|
5737
|
+
extractBlobRefs,
|
|
5738
|
+
fetchBlobManifest,
|
|
5111
5739
|
getSharedQueryStoreCache,
|
|
5112
5740
|
mergeVersionVectors,
|
|
5741
|
+
parseBlobManifest,
|
|
5113
5742
|
persistedAuditTraceFromEvent,
|
|
5114
5743
|
pluralize,
|
|
5744
|
+
prepareBlobForSend,
|
|
5745
|
+
putBlobForTransfer,
|
|
5115
5746
|
readAuditExportManifest,
|
|
5116
5747
|
readBackupManifest,
|
|
5748
|
+
reassembleBlob,
|
|
5749
|
+
receiveBlob,
|
|
5750
|
+
resolveBlobManifest,
|
|
5117
5751
|
restoreBackup,
|
|
5118
5752
|
richtextStatesEqual,
|
|
5119
5753
|
richtextToPlainText,
|
|
5754
|
+
serializeBlobManifest,
|
|
5120
5755
|
serializeVersionVectorToMeta,
|
|
5756
|
+
serveBlobChunks,
|
|
5121
5757
|
singularize,
|
|
5758
|
+
toServerBlobCallbacks,
|
|
5122
5759
|
validateStateTransition,
|
|
5123
5760
|
validateUpdateStateMachine,
|
|
5124
5761
|
verifyAuditExportChecksum,
|