@korajs/store 1.0.0-beta.1 → 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-ZV4Y424B.js → backup-5S7K2Z35.js} +3 -3
- package/dist/{chunk-FFYA7J5H.js → chunk-CYO3YKFL.js} +4 -3
- package/dist/chunk-CYO3YKFL.js.map +1 -0
- package/dist/{chunk-6NKLDBTY.js → chunk-GSJ7EEUC.js} +2 -1
- package/dist/chunk-GSJ7EEUC.js.map +1 -0
- package/dist/{chunk-X57MWW35.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-ZLWAYQEQ.js → chunk-YT7MRFCP.js} +36 -32
- package/dist/chunk-YT7MRFCP.js.map +1 -0
- package/dist/{export-audit-AJ7RORNK.js → export-audit-ODGQTQIK.js} +3 -3
- package/dist/index.cjs +313 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -5
- package/dist/index.d.ts +51 -5
- package/dist/index.js +212 -59
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +46 -40
- 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-5h4Hqcwc.d.ts → types-BOO9EPt1.d.ts} +1 -1
- package/dist/{types-k6AZgiH4.d.ts → types-BslDOTk0.d.cts} +26 -1
- package/dist/{types-k6AZgiH4.d.cts → types-BslDOTk0.d.ts} +26 -1
- package/dist/{types-DfPiPwKr.d.cts → types-C_GDavG-.d.cts} +1 -1
- package/package.json +2 -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-6NKLDBTY.js.map +0 -1
- package/dist/chunk-FFYA7J5H.js.map +0 -1
- package/dist/chunk-IOEFNHHP.js.map +0 -1
- package/dist/chunk-RBPVEAVM.js +0 -425
- package/dist/chunk-RBPVEAVM.js.map +0 -1
- package/dist/chunk-X57MWW35.js.map +0 -1
- package/dist/chunk-ZLWAYQEQ.js.map +0 -1
- package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
- /package/dist/{backup-ZV4Y424B.js.map → backup-5S7K2Z35.js.map} +0 -0
- /package/dist/{export-audit-AJ7RORNK.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.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
exportBackup,
|
|
3
|
-
readBackupManifest,
|
|
4
|
-
restoreBackup,
|
|
5
|
-
verifyBackupChecksum
|
|
6
|
-
} from "./chunk-X57MWW35.js";
|
|
7
1
|
import {
|
|
8
2
|
decodeAuditExport,
|
|
9
3
|
readAuditExportManifest,
|
|
10
4
|
verifyAuditExportChecksum
|
|
11
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CYO3YKFL.js";
|
|
12
6
|
import {
|
|
13
7
|
persistedAuditTraceFromEvent
|
|
14
8
|
} from "./chunk-MYSJN3DF.js";
|
|
@@ -39,7 +33,24 @@ import {
|
|
|
39
33
|
serializeRowVersion,
|
|
40
34
|
validateStateTransition,
|
|
41
35
|
validateUpdateStateMachine
|
|
42
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-YT7MRFCP.js";
|
|
37
|
+
import {
|
|
38
|
+
AdapterError,
|
|
39
|
+
NoLeaderError,
|
|
40
|
+
OptimisticLockError,
|
|
41
|
+
PersistenceError,
|
|
42
|
+
QueryError,
|
|
43
|
+
RecordNotFoundError,
|
|
44
|
+
StoreNotOpenError,
|
|
45
|
+
WorkerInitError,
|
|
46
|
+
WorkerTimeoutError
|
|
47
|
+
} from "./chunk-TUR7RGEP.js";
|
|
48
|
+
import {
|
|
49
|
+
exportBackup,
|
|
50
|
+
readBackupManifest,
|
|
51
|
+
restoreBackup,
|
|
52
|
+
verifyBackupChecksum
|
|
53
|
+
} from "./chunk-HS6DBMST.js";
|
|
43
54
|
import {
|
|
44
55
|
decodeRichtext,
|
|
45
56
|
decodeRichtextFieldsFromOpData,
|
|
@@ -51,23 +62,13 @@ import {
|
|
|
51
62
|
richtextToPlainText,
|
|
52
63
|
serializeOperation,
|
|
53
64
|
serializeRecord
|
|
54
|
-
} from "./chunk-
|
|
55
|
-
import {
|
|
56
|
-
AdapterError,
|
|
57
|
-
OptimisticLockError,
|
|
58
|
-
PersistenceError,
|
|
59
|
-
QueryError,
|
|
60
|
-
RecordNotFoundError,
|
|
61
|
-
StoreNotOpenError,
|
|
62
|
-
WorkerInitError,
|
|
63
|
-
WorkerTimeoutError
|
|
64
|
-
} from "./chunk-QL2XVZRN.js";
|
|
65
|
+
} from "./chunk-GSJ7EEUC.js";
|
|
65
66
|
|
|
66
67
|
// src/compaction/types.ts
|
|
67
68
|
var COMPACTION_BASELINE_META_KEY = "compaction_baseline_at";
|
|
68
69
|
|
|
69
70
|
// src/compaction/compact-operation-log.ts
|
|
70
|
-
import { HybridLogicalClock, createVersionVector } from "@korajs/core";
|
|
71
|
+
import { HybridLogicalClock, createVersionVector, quoteIdent } from "@korajs/core";
|
|
71
72
|
function computeAckCompactionWatermark(serverVector) {
|
|
72
73
|
const watermark = createVersionVector();
|
|
73
74
|
for (const [nodeId, seq] of serverVector) {
|
|
@@ -95,7 +96,7 @@ async function compactOperationLog(adapter, schema, strategy, serverVector) {
|
|
|
95
96
|
const collectionNames = Object.keys(schema.collections);
|
|
96
97
|
await adapter.transaction(async (tx) => {
|
|
97
98
|
for (const collectionName of collectionNames) {
|
|
98
|
-
const table = `_kora_ops_${collectionName}
|
|
99
|
+
const table = quoteIdent(`_kora_ops_${collectionName}`);
|
|
99
100
|
for (const [nodeId, maxSeq] of watermark) {
|
|
100
101
|
if (maxSeq <= 0) continue;
|
|
101
102
|
const countSql = buildCountSql(table, ageCutoffSerialized !== null);
|
|
@@ -130,6 +131,7 @@ function buildDeleteSql(table, withAge) {
|
|
|
130
131
|
import { createVersionVector as createVersionVector2 } from "@korajs/core";
|
|
131
132
|
var LAST_ACKED_SERVER_VECTOR_META_KEY = "last_acked_server_vector";
|
|
132
133
|
var DELTA_CURSOR_META_KEY = "delta_cursor";
|
|
134
|
+
var DELIVERY_WATERMARK_META_KEY = "delivery_watermark";
|
|
133
135
|
function serializeVersionVectorToMeta(vector) {
|
|
134
136
|
const record = {};
|
|
135
137
|
for (const [nodeId, seq] of vector) {
|
|
@@ -202,6 +204,45 @@ async function saveDeltaCursor(adapter, cursor) {
|
|
|
202
204
|
cursor
|
|
203
205
|
]);
|
|
204
206
|
}
|
|
207
|
+
function deliveryWatermarkKey(signature) {
|
|
208
|
+
return signature === "" ? DELIVERY_WATERMARK_META_KEY : `${DELIVERY_WATERMARK_META_KEY}:${signature}`;
|
|
209
|
+
}
|
|
210
|
+
async function loadDeliveryWatermark(adapter, signature) {
|
|
211
|
+
const rows = await adapter.query("SELECT value FROM _kora_meta WHERE key = ?", [
|
|
212
|
+
deliveryWatermarkKey(signature)
|
|
213
|
+
]);
|
|
214
|
+
const value = rows[0]?.value;
|
|
215
|
+
if (value === void 0 || value === null) {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
const parsed = Number(value);
|
|
219
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
220
|
+
}
|
|
221
|
+
async function saveDeliveryWatermark(adapter, signature, watermark) {
|
|
222
|
+
await adapter.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
|
|
223
|
+
deliveryWatermarkKey(signature),
|
|
224
|
+
String(watermark)
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
async function deleteDeliveryWatermark(adapter, signature) {
|
|
228
|
+
await adapter.execute("DELETE FROM _kora_meta WHERE key = ?", [deliveryWatermarkKey(signature)]);
|
|
229
|
+
}
|
|
230
|
+
async function loadAllDeliveryWatermarks(adapter) {
|
|
231
|
+
const rows = await adapter.query(
|
|
232
|
+
"SELECT key, value FROM _kora_meta WHERE key = ? OR key LIKE ?",
|
|
233
|
+
[DELIVERY_WATERMARK_META_KEY, `${DELIVERY_WATERMARK_META_KEY}:%`]
|
|
234
|
+
);
|
|
235
|
+
const result = {};
|
|
236
|
+
const prefix = `${DELIVERY_WATERMARK_META_KEY}:`;
|
|
237
|
+
for (const row of rows) {
|
|
238
|
+
const signature = row.key === DELIVERY_WATERMARK_META_KEY ? "" : row.key.slice(prefix.length);
|
|
239
|
+
const parsed = Number(row.value);
|
|
240
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
241
|
+
result[signature] = parsed;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
205
246
|
|
|
206
247
|
// src/store/store.ts
|
|
207
248
|
import {
|
|
@@ -209,10 +250,13 @@ import {
|
|
|
209
250
|
HybridLogicalClock as HybridLogicalClock3,
|
|
210
251
|
createVersionVector as createVersionVector3,
|
|
211
252
|
generateUUIDv7 as generateUUIDv73,
|
|
212
|
-
migrationStepsToSQL
|
|
253
|
+
migrationStepsToSQL,
|
|
254
|
+
quoteIdent as quoteIdent7,
|
|
255
|
+
replayOperationsForRecord
|
|
213
256
|
} from "@korajs/core";
|
|
214
257
|
|
|
215
258
|
// src/collection/collection.ts
|
|
259
|
+
import { quoteIdent as quoteIdent2 } from "@korajs/core";
|
|
216
260
|
var Collection = class {
|
|
217
261
|
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
|
|
218
262
|
this.name = name;
|
|
@@ -264,7 +308,7 @@ var Collection = class {
|
|
|
264
308
|
}
|
|
265
309
|
async findById(id) {
|
|
266
310
|
const rows = await this.adapter.query(
|
|
267
|
-
`SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
|
|
311
|
+
`SELECT * FROM ${quoteIdent2(this.name)} WHERE id = ? AND _deleted = 0`,
|
|
268
312
|
[id]
|
|
269
313
|
);
|
|
270
314
|
const row = rows[0];
|
|
@@ -296,6 +340,9 @@ var Collection = class {
|
|
|
296
340
|
}
|
|
297
341
|
};
|
|
298
342
|
|
|
343
|
+
// src/query/query-builder.ts
|
|
344
|
+
import { quoteIdent as quoteIdent3 } from "@korajs/core";
|
|
345
|
+
|
|
299
346
|
// src/query/pluralize.ts
|
|
300
347
|
function isVowel(char) {
|
|
301
348
|
if (!char) return false;
|
|
@@ -525,7 +572,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
525
572
|
const relatedDef = this.schema?.collections[relatedCollection];
|
|
526
573
|
if (!relatedDef) return;
|
|
527
574
|
const placeholders = uniqueFks.map(() => "?").join(", ");
|
|
528
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
575
|
+
const sql = `SELECT * FROM ${quoteIdent3(relatedCollection)} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
529
576
|
const rows = await this.adapter.query(sql, uniqueFks);
|
|
530
577
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
531
578
|
const lookup = /* @__PURE__ */ new Map();
|
|
@@ -548,7 +595,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
548
595
|
if (!relatedDef) return;
|
|
549
596
|
const fkField = relation.field;
|
|
550
597
|
const placeholders = primaryIds.map(() => "?").join(", ");
|
|
551
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE ${fkField} IN (${placeholders}) AND _deleted = 0`;
|
|
598
|
+
const sql = `SELECT * FROM ${quoteIdent3(relatedCollection)} WHERE ${quoteIdent3(fkField)} IN (${placeholders}) AND _deleted = 0`;
|
|
552
599
|
const rows = await this.adapter.query(sql, primaryIds);
|
|
553
600
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
554
601
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -590,7 +637,7 @@ var QueryError2 = class extends Error {
|
|
|
590
637
|
};
|
|
591
638
|
|
|
592
639
|
// src/relations/relation-enforcer.ts
|
|
593
|
-
import { KoraError, createOperation } from "@korajs/core";
|
|
640
|
+
import { KoraError, createOperation, quoteIdent as quoteIdent4 } from "@korajs/core";
|
|
594
641
|
|
|
595
642
|
// src/relations/relation-lookup.ts
|
|
596
643
|
function buildRelationLookup(schema) {
|
|
@@ -693,7 +740,7 @@ var RelationEnforcer = class {
|
|
|
693
740
|
async enforceCascade(incoming, deletedRecordId, tx, causalDeps) {
|
|
694
741
|
const { sourceCollection, foreignKeyField } = incoming;
|
|
695
742
|
const referencingRows = await tx.query(
|
|
696
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
743
|
+
`SELECT id FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
|
|
697
744
|
[deletedRecordId]
|
|
698
745
|
);
|
|
699
746
|
if (referencingRows.length === 0) {
|
|
@@ -740,7 +787,7 @@ var RelationEnforcer = class {
|
|
|
740
787
|
return [];
|
|
741
788
|
}
|
|
742
789
|
const referencingRows = await tx.query(
|
|
743
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
790
|
+
`SELECT id FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
|
|
744
791
|
[deletedRecordId]
|
|
745
792
|
);
|
|
746
793
|
if (referencingRows.length === 0) {
|
|
@@ -788,7 +835,7 @@ var RelationEnforcer = class {
|
|
|
788
835
|
async enforceRestrict(incoming, deletedRecordId, tx) {
|
|
789
836
|
const { sourceCollection, foreignKeyField, relationName } = incoming;
|
|
790
837
|
const countRows = await tx.query(
|
|
791
|
-
`SELECT COUNT(*) as cnt FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
838
|
+
`SELECT COUNT(*) as cnt FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
|
|
792
839
|
[deletedRecordId]
|
|
793
840
|
);
|
|
794
841
|
const count = countRows[0]?.cnt ?? 0;
|
|
@@ -1476,7 +1523,7 @@ var SubscriptionManager = class {
|
|
|
1476
1523
|
};
|
|
1477
1524
|
|
|
1478
1525
|
// src/sync/rebase-unsynced-operations.ts
|
|
1479
|
-
import { HybridLogicalClock as HybridLogicalClock2, MAX_LOGICAL } from "@korajs/core";
|
|
1526
|
+
import { HybridLogicalClock as HybridLogicalClock2, MAX_LOGICAL, quoteIdent as quoteIdent5 } from "@korajs/core";
|
|
1480
1527
|
import { computeOperationId } from "@korajs/core/internal";
|
|
1481
1528
|
var LOGICAL_COUNTER_LIMIT = MAX_LOGICAL + 1;
|
|
1482
1529
|
function emptyResult() {
|
|
@@ -1490,7 +1537,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1490
1537
|
const rebased = [];
|
|
1491
1538
|
let maxOtherSerializedTs = null;
|
|
1492
1539
|
for (const collectionName of Object.keys(schema.collections)) {
|
|
1493
|
-
const rows = await adapter.query(
|
|
1540
|
+
const rows = await adapter.query(
|
|
1541
|
+
`SELECT * FROM ${quoteIdent5(`_kora_ops_${collectionName}`)}`
|
|
1542
|
+
);
|
|
1494
1543
|
for (const row of rows) {
|
|
1495
1544
|
if (rebaseIds.has(row.id)) {
|
|
1496
1545
|
rebased.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -1559,7 +1608,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1559
1608
|
continue;
|
|
1560
1609
|
}
|
|
1561
1610
|
const opsTable = `_kora_ops_${oldOp.collection}`;
|
|
1562
|
-
await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
|
|
1611
|
+
await tx.execute(`DELETE FROM ${quoteIdent5(opsTable)} WHERE id = ?`, [oldOp.id]);
|
|
1563
1612
|
const opInsert = buildInsertQuery(
|
|
1564
1613
|
opsTable,
|
|
1565
1614
|
serializeOperation(newOp)
|
|
@@ -1568,11 +1617,11 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1568
1617
|
const oldVersion = serializeRowVersion(oldOp.timestamp);
|
|
1569
1618
|
const newVersion = serializeRowVersion(newOp.timestamp);
|
|
1570
1619
|
await tx.execute(
|
|
1571
|
-
`UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
1620
|
+
`UPDATE ${quoteIdent5(oldOp.collection)} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
1572
1621
|
[newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
|
|
1573
1622
|
);
|
|
1574
1623
|
const rows = await tx.query(
|
|
1575
|
-
`SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
|
|
1624
|
+
`SELECT _field_versions FROM ${quoteIdent5(oldOp.collection)} WHERE id = ?`,
|
|
1576
1625
|
[oldOp.recordId]
|
|
1577
1626
|
);
|
|
1578
1627
|
const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
|
|
@@ -1584,14 +1633,14 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1584
1633
|
}
|
|
1585
1634
|
}
|
|
1586
1635
|
if (changed) {
|
|
1587
|
-
await tx.execute(
|
|
1588
|
-
|
|
1589
|
-
oldOp.recordId
|
|
1590
|
-
|
|
1636
|
+
await tx.execute(
|
|
1637
|
+
`UPDATE ${quoteIdent5(oldOp.collection)} SET _field_versions = ? WHERE id = ?`,
|
|
1638
|
+
[serializeFieldVersions(fieldVersions), oldOp.recordId]
|
|
1639
|
+
);
|
|
1591
1640
|
}
|
|
1592
1641
|
if (oldOp.type === "insert") {
|
|
1593
1642
|
await tx.execute(
|
|
1594
|
-
`UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
1643
|
+
`UPDATE ${quoteIdent5(oldOp.collection)} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
1595
1644
|
[newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
|
|
1596
1645
|
);
|
|
1597
1646
|
}
|
|
@@ -1611,6 +1660,7 @@ import {
|
|
|
1611
1660
|
createOperation as createOperation2,
|
|
1612
1661
|
generateUUIDv7,
|
|
1613
1662
|
isAtomicOp,
|
|
1663
|
+
quoteIdent as quoteIdent6,
|
|
1614
1664
|
resolveAtomicOp,
|
|
1615
1665
|
toAtomicOp,
|
|
1616
1666
|
validateRecord
|
|
@@ -1927,7 +1977,7 @@ var TransactionContext = class {
|
|
|
1927
1977
|
}
|
|
1928
1978
|
}
|
|
1929
1979
|
const rows = await this.config.adapter.query(
|
|
1930
|
-
`SELECT * FROM ${collectionName} WHERE id = ? AND _deleted = 0`,
|
|
1980
|
+
`SELECT * FROM ${quoteIdent6(collectionName)} WHERE id = ? AND _deleted = 0`,
|
|
1931
1981
|
[id]
|
|
1932
1982
|
);
|
|
1933
1983
|
let record = rows[0] ? deserializeRecord(rows[0], definition.fields) : null;
|
|
@@ -2162,7 +2212,7 @@ var Store = class {
|
|
|
2162
2212
|
return "skipped";
|
|
2163
2213
|
}
|
|
2164
2214
|
const existing = await this.adapter.query(
|
|
2165
|
-
`SELECT id FROM _kora_ops_${collection} WHERE id = ?`,
|
|
2215
|
+
`SELECT id FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE id = ?`,
|
|
2166
2216
|
[op.id]
|
|
2167
2217
|
);
|
|
2168
2218
|
if (existing.length > 0) {
|
|
@@ -2187,7 +2237,8 @@ var Store = class {
|
|
|
2187
2237
|
throw new OptimisticLockError(collection, op.recordId);
|
|
2188
2238
|
}
|
|
2189
2239
|
};
|
|
2190
|
-
if (
|
|
2240
|
+
if (options?.logOnly) {
|
|
2241
|
+
} else if (op.type === "insert" && materializeSource) {
|
|
2191
2242
|
const serializedData = serializeRecord(materializeSource, definition.fields);
|
|
2192
2243
|
const existing2 = await tx.query(
|
|
2193
2244
|
`SELECT _updated_at, _version, _field_versions, _deleted FROM ${collection} WHERE id = ?`,
|
|
@@ -2209,7 +2260,7 @@ var Store = class {
|
|
|
2209
2260
|
};
|
|
2210
2261
|
const insertQuery = buildInsertQuery(collection, record);
|
|
2211
2262
|
await tx.execute(insertQuery.sql, insertQuery.params);
|
|
2212
|
-
await this.foldOrphanedOperations(tx, collection, definition, op.recordId);
|
|
2263
|
+
await this.foldOrphanedOperations(tx, collection, definition, op.recordId, op);
|
|
2213
2264
|
} else if (row._deleted === 1) {
|
|
2214
2265
|
if (isIncomingNewerThanRow(materializeTimestamp, row)) {
|
|
2215
2266
|
const fieldChanges = {
|
|
@@ -2267,7 +2318,7 @@ var Store = class {
|
|
|
2267
2318
|
const serializedChanges = serializeRecord(materializeSource, definition.fields);
|
|
2268
2319
|
const changedFields = Object.keys(serializedChanges);
|
|
2269
2320
|
const currentRows = await tx.query(
|
|
2270
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
2321
|
+
`SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2271
2322
|
[op.recordId]
|
|
2272
2323
|
);
|
|
2273
2324
|
const currentRow = currentRows[0];
|
|
@@ -2361,9 +2412,9 @@ var Store = class {
|
|
|
2361
2412
|
* necessarily an orphan (rows are soft-deleted, never removed, so a row
|
|
2362
2413
|
* that is absent now was never materialized).
|
|
2363
2414
|
*/
|
|
2364
|
-
async foldOrphanedOperations(tx, collection, definition, recordId) {
|
|
2415
|
+
async foldOrphanedOperations(tx, collection, definition, recordId, insertOp) {
|
|
2365
2416
|
const orphanRows = await tx.query(
|
|
2366
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
2417
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
2367
2418
|
[recordId]
|
|
2368
2419
|
);
|
|
2369
2420
|
if (orphanRows.length === 0) {
|
|
@@ -2376,7 +2427,7 @@ var Store = class {
|
|
|
2376
2427
|
if (orphan.type === "update" && orphan.data) {
|
|
2377
2428
|
const orphanChanges = serializeRecord(orphan.data, definition.fields);
|
|
2378
2429
|
const rows = await tx.query(
|
|
2379
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
2430
|
+
`SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2380
2431
|
[recordId]
|
|
2381
2432
|
);
|
|
2382
2433
|
const current = rows[0];
|
|
@@ -2410,6 +2461,37 @@ var Store = class {
|
|
|
2410
2461
|
await tx.execute(query.sql, query.params);
|
|
2411
2462
|
}
|
|
2412
2463
|
}
|
|
2464
|
+
const atomicFields = /* @__PURE__ */ new Set();
|
|
2465
|
+
for (const orphan of orphans) {
|
|
2466
|
+
if (orphan.type === "update" && orphan.atomicOps) {
|
|
2467
|
+
for (const field of Object.keys(orphan.atomicOps)) {
|
|
2468
|
+
atomicFields.add(field);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
if (atomicFields.size > 0) {
|
|
2473
|
+
const ordered = [insertOp, ...orphans].sort(
|
|
2474
|
+
(a, b) => HybridLogicalClock3.compare(a.timestamp, b.timestamp)
|
|
2475
|
+
);
|
|
2476
|
+
const folded = replayOperationsForRecord(ordered);
|
|
2477
|
+
if (folded) {
|
|
2478
|
+
const composed = {};
|
|
2479
|
+
for (const field of atomicFields) {
|
|
2480
|
+
if (field in folded) {
|
|
2481
|
+
composed[field] = folded[field];
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
const serialized = serializeRecord(composed, definition.fields);
|
|
2485
|
+
const cols = Object.keys(serialized);
|
|
2486
|
+
if (cols.length > 0) {
|
|
2487
|
+
const setClause = cols.map((c) => `${quoteIdent7(c)} = ?`).join(", ");
|
|
2488
|
+
await tx.execute(`UPDATE ${quoteIdent7(collection)} SET ${setClause} WHERE id = ?`, [
|
|
2489
|
+
...cols.map((c) => serialized[c]),
|
|
2490
|
+
recordId
|
|
2491
|
+
]);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2413
2495
|
}
|
|
2414
2496
|
/**
|
|
2415
2497
|
* Get operations from a node within a sequence number range.
|
|
@@ -2426,7 +2508,7 @@ var Store = class {
|
|
|
2426
2508
|
const allOps = [];
|
|
2427
2509
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
2428
2510
|
const rows = await this.adapter.query(
|
|
2429
|
-
`SELECT * FROM _kora_ops_${collectionName} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
2511
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collectionName}`)} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
2430
2512
|
[nodeId, fromSeq, toSeq]
|
|
2431
2513
|
);
|
|
2432
2514
|
for (const row of rows) {
|
|
@@ -2445,7 +2527,7 @@ var Store = class {
|
|
|
2445
2527
|
const allOps = [];
|
|
2446
2528
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
2447
2529
|
const rows = await this.adapter.query(
|
|
2448
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
2530
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
2449
2531
|
);
|
|
2450
2532
|
for (const row of rows) {
|
|
2451
2533
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -2496,7 +2578,7 @@ var Store = class {
|
|
|
2496
2578
|
*/
|
|
2497
2579
|
async exportAudit(options) {
|
|
2498
2580
|
this.ensureOpen();
|
|
2499
|
-
const { exportAudit: doExport } = await import("./export-audit-
|
|
2581
|
+
const { exportAudit: doExport } = await import("./export-audit-ODGQTQIK.js");
|
|
2500
2582
|
return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
|
|
2501
2583
|
}
|
|
2502
2584
|
/**
|
|
@@ -2514,6 +2596,22 @@ var Store = class {
|
|
|
2514
2596
|
col.setMutationHandler(handler);
|
|
2515
2597
|
}
|
|
2516
2598
|
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Notify this store that another same-origin runtime committed an operation to
|
|
2601
|
+
* the shared local database. The operation has already been persisted and
|
|
2602
|
+
* materialized by the database owner; this method only advances in-memory causal
|
|
2603
|
+
* watermarks and invalidates local reactive queries so already-open tabs stay
|
|
2604
|
+
* live without reapplying or rewriting the operation.
|
|
2605
|
+
*/
|
|
2606
|
+
notifyExternalOperation(operation) {
|
|
2607
|
+
this.ensureOpen();
|
|
2608
|
+
const definition = this.schema.collections[operation.collection];
|
|
2609
|
+
if (!definition) {
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
this.recordOperationSequence(operation);
|
|
2613
|
+
this.subscriptionManager.notify(operation.collection, operation);
|
|
2614
|
+
}
|
|
2517
2615
|
/**
|
|
2518
2616
|
* Build mutation context for a collection (used by ApplyPipeline side effects).
|
|
2519
2617
|
*/
|
|
@@ -2555,7 +2653,7 @@ var Store = class {
|
|
|
2555
2653
|
return null;
|
|
2556
2654
|
}
|
|
2557
2655
|
const rows = await this.adapter.query(
|
|
2558
|
-
`SELECT * FROM ${collection} WHERE id = ?`,
|
|
2656
|
+
`SELECT * FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2559
2657
|
[recordId]
|
|
2560
2658
|
);
|
|
2561
2659
|
const row = rows[0];
|
|
@@ -2580,7 +2678,7 @@ var Store = class {
|
|
|
2580
2678
|
return null;
|
|
2581
2679
|
}
|
|
2582
2680
|
const rows = await this.adapter.query(
|
|
2583
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
2681
|
+
`SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2584
2682
|
[recordId]
|
|
2585
2683
|
);
|
|
2586
2684
|
const row = rows[0];
|
|
@@ -2623,6 +2721,35 @@ var Store = class {
|
|
|
2623
2721
|
this.ensureOpen();
|
|
2624
2722
|
await saveDeltaCursor(this.adapter, cursor);
|
|
2625
2723
|
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Load the persisted delivery watermark for a view signature (0 when none recorded).
|
|
2726
|
+
* Defaults to the unfiltered view so existing single-watermark reads keep working.
|
|
2727
|
+
*/
|
|
2728
|
+
async loadDeliveryWatermark(signature = "") {
|
|
2729
|
+
this.ensureOpen();
|
|
2730
|
+
return loadDeliveryWatermark(this.adapter, signature);
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Persist the delivery watermark for a view signature so it survives a client restart.
|
|
2734
|
+
*/
|
|
2735
|
+
async saveDeliveryWatermark(signature, watermark) {
|
|
2736
|
+
this.ensureOpen();
|
|
2737
|
+
await saveDeliveryWatermark(this.adapter, signature, watermark);
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Load every persisted view watermark, keyed by signature.
|
|
2741
|
+
*/
|
|
2742
|
+
async loadAllDeliveryWatermarks() {
|
|
2743
|
+
this.ensureOpen();
|
|
2744
|
+
return loadAllDeliveryWatermarks(this.adapter);
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Delete a persisted view watermark (safe: the view back-fills from 0 when next visited).
|
|
2748
|
+
*/
|
|
2749
|
+
async deleteDeliveryWatermark(signature) {
|
|
2750
|
+
this.ensureOpen();
|
|
2751
|
+
await deleteDeliveryWatermark(this.adapter, signature);
|
|
2752
|
+
}
|
|
2626
2753
|
/**
|
|
2627
2754
|
* Local operations not yet reflected on the server version vector.
|
|
2628
2755
|
*/
|
|
@@ -2686,7 +2813,7 @@ var Store = class {
|
|
|
2686
2813
|
async getLatestLocalOperationForRecord(collection, recordId) {
|
|
2687
2814
|
this.ensureOpen();
|
|
2688
2815
|
const rows = await this.adapter.query(
|
|
2689
|
-
`SELECT * FROM _kora_ops_${collection} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
2816
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
2690
2817
|
[this.nodeId, recordId]
|
|
2691
2818
|
);
|
|
2692
2819
|
const row = rows[0];
|
|
@@ -2701,7 +2828,7 @@ var Store = class {
|
|
|
2701
2828
|
async getLatestOperationForRecord(collection, recordId) {
|
|
2702
2829
|
this.ensureOpen();
|
|
2703
2830
|
const rows = await this.adapter.query(
|
|
2704
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
|
|
2831
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ?`,
|
|
2705
2832
|
[recordId]
|
|
2706
2833
|
);
|
|
2707
2834
|
let latest = null;
|
|
@@ -2713,6 +2840,21 @@ var Store = class {
|
|
|
2713
2840
|
}
|
|
2714
2841
|
return latest;
|
|
2715
2842
|
}
|
|
2843
|
+
/**
|
|
2844
|
+
* All operations for a record, sorted in HLC total order (wallTime, logical,
|
|
2845
|
+
* nodeId). Used by the apply pipeline to materialize atomic-op fields by folding
|
|
2846
|
+
* the record's log, matching the server's materialization exactly.
|
|
2847
|
+
*/
|
|
2848
|
+
async getOperationsForRecord(collection, recordId) {
|
|
2849
|
+
this.ensureOpen();
|
|
2850
|
+
const rows = await this.adapter.query(
|
|
2851
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ?`,
|
|
2852
|
+
[recordId]
|
|
2853
|
+
);
|
|
2854
|
+
const ops = rows.map((row) => deserializeOperationWithCollection(row, collection));
|
|
2855
|
+
ops.sort((a, b) => HybridLogicalClock3.compare(a.timestamp, b.timestamp));
|
|
2856
|
+
return ops;
|
|
2857
|
+
}
|
|
2716
2858
|
/** Expose the subscription manager for direct access (e.g., by QueryBuilder) */
|
|
2717
2859
|
getSubscriptionManager() {
|
|
2718
2860
|
return this.subscriptionManager;
|
|
@@ -2791,7 +2933,7 @@ var Store = class {
|
|
|
2791
2933
|
*/
|
|
2792
2934
|
async exportBackup(options) {
|
|
2793
2935
|
this.ensureOpen();
|
|
2794
|
-
const { exportBackup: doExport } = await import("./backup-
|
|
2936
|
+
const { exportBackup: doExport } = await import("./backup-5S7K2Z35.js");
|
|
2795
2937
|
return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
|
|
2796
2938
|
}
|
|
2797
2939
|
/**
|
|
@@ -2803,7 +2945,7 @@ var Store = class {
|
|
|
2803
2945
|
*/
|
|
2804
2946
|
async importBackup(data, options) {
|
|
2805
2947
|
this.ensureOpen();
|
|
2806
|
-
const { restoreBackup: doRestore } = await import("./backup-
|
|
2948
|
+
const { restoreBackup: doRestore } = await import("./backup-5S7K2Z35.js");
|
|
2807
2949
|
return doRestore(this.adapter, this.schema, data, options);
|
|
2808
2950
|
}
|
|
2809
2951
|
/**
|
|
@@ -2888,14 +3030,14 @@ var Store = class {
|
|
|
2888
3030
|
*/
|
|
2889
3031
|
async runBackfill(collection, transform) {
|
|
2890
3032
|
const rows = await this.adapter.query(
|
|
2891
|
-
`SELECT * FROM ${collection} WHERE _deleted = 0`
|
|
3033
|
+
`SELECT * FROM ${quoteIdent7(collection)} WHERE _deleted = 0`
|
|
2892
3034
|
);
|
|
2893
3035
|
await this.adapter.transaction(async (tx) => {
|
|
2894
3036
|
for (const row of rows) {
|
|
2895
3037
|
const updates = transform(row);
|
|
2896
3038
|
const fields = Object.keys(updates);
|
|
2897
3039
|
if (fields.length === 0) continue;
|
|
2898
|
-
const setClauses = fields.map((f) => `${f} = ?`).join(", ");
|
|
3040
|
+
const setClauses = fields.map((f) => `${quoteIdent7(f)} = ?`).join(", ");
|
|
2899
3041
|
const values = fields.map((f) => {
|
|
2900
3042
|
const val = updates[f];
|
|
2901
3043
|
if (typeof val === "boolean") return val ? 1 : 0;
|
|
@@ -2905,7 +3047,7 @@ var Store = class {
|
|
|
2905
3047
|
return val;
|
|
2906
3048
|
});
|
|
2907
3049
|
values.push(row.id);
|
|
2908
|
-
await tx.execute(`UPDATE ${collection} SET ${setClauses} WHERE id = ?`, values);
|
|
3050
|
+
await tx.execute(`UPDATE ${quoteIdent7(collection)} SET ${setClauses} WHERE id = ?`, values);
|
|
2909
3051
|
}
|
|
2910
3052
|
});
|
|
2911
3053
|
}
|
|
@@ -2965,6 +3107,7 @@ var QueryStore = class {
|
|
|
2965
3107
|
listeners = /* @__PURE__ */ new Set();
|
|
2966
3108
|
unsubscribeQuery = null;
|
|
2967
3109
|
active = false;
|
|
3110
|
+
hasEmittedSnapshot = false;
|
|
2968
3111
|
queryBuilder;
|
|
2969
3112
|
constructor(queryBuilder) {
|
|
2970
3113
|
this.queryBuilder = queryBuilder;
|
|
@@ -2990,16 +3133,25 @@ var QueryStore = class {
|
|
|
2990
3133
|
getSnapshot = () => {
|
|
2991
3134
|
return this.snapshot;
|
|
2992
3135
|
};
|
|
3136
|
+
/**
|
|
3137
|
+
* Whether the underlying query subscription has delivered at least one
|
|
3138
|
+
* authoritative result for the current subscription lifecycle.
|
|
3139
|
+
*/
|
|
3140
|
+
hasSnapshot = () => {
|
|
3141
|
+
return this.hasEmittedSnapshot;
|
|
3142
|
+
};
|
|
2993
3143
|
/** Tear down listeners and the underlying query subscription. */
|
|
2994
3144
|
destroy() {
|
|
2995
3145
|
this.stopSubscription();
|
|
2996
3146
|
this.listeners.clear();
|
|
2997
3147
|
this.snapshot = EMPTY_ARRAY;
|
|
3148
|
+
this.hasEmittedSnapshot = false;
|
|
2998
3149
|
}
|
|
2999
3150
|
startSubscription() {
|
|
3000
3151
|
this.active = true;
|
|
3001
3152
|
this.unsubscribeQuery = this.queryBuilder.subscribe((results) => {
|
|
3002
3153
|
if (!this.active) return;
|
|
3154
|
+
this.hasEmittedSnapshot = true;
|
|
3003
3155
|
this.snapshot = Object.freeze([...results]);
|
|
3004
3156
|
this.notifyListeners();
|
|
3005
3157
|
});
|
|
@@ -3944,6 +4096,7 @@ export {
|
|
|
3944
4096
|
InvalidStateTransitionError,
|
|
3945
4097
|
LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
3946
4098
|
MemoryBlobStore,
|
|
4099
|
+
NoLeaderError,
|
|
3947
4100
|
OpfsBlobStore,
|
|
3948
4101
|
OptimisticLockError,
|
|
3949
4102
|
PersistenceError,
|