@tanstack/db-sqlite-persistence-core 0.4.7 → 0.4.8
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/README.md +22 -2
- package/dist/cjs/broadcast-coordinator.cjs +169 -18
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -1
- package/dist/cjs/broadcast-coordinator.d.cts +11 -2
- package/dist/cjs/committed-tx-anchor.cjs +9 -0
- package/dist/cjs/committed-tx-anchor.cjs.map +1 -0
- package/dist/cjs/committed-tx-anchor.d.cts +1 -0
- package/dist/cjs/persisted.cjs +199 -43
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +46 -0
- package/dist/cjs/sqlite-core-adapter.cjs +94 -3
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +10 -1
- package/dist/esm/broadcast-coordinator.d.ts +11 -2
- package/dist/esm/broadcast-coordinator.js +170 -19
- package/dist/esm/broadcast-coordinator.js.map +1 -1
- package/dist/esm/committed-tx-anchor.d.ts +1 -0
- package/dist/esm/committed-tx-anchor.js +9 -0
- package/dist/esm/committed-tx-anchor.js.map +1 -0
- package/dist/esm/persisted.d.ts +46 -0
- package/dist/esm/persisted.js +201 -45
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.d.ts +10 -1
- package/dist/esm/sqlite-core-adapter.js +94 -3
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/package.json +2 -2
- package/src/broadcast-coordinator.ts +267 -44
- package/src/committed-tx-anchor.ts +13 -0
- package/src/persisted.ts +315 -53
- package/src/sqlite-core-adapter.ts +143 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LoadSubsetOptions } from '@tanstack/db';
|
|
2
|
-
import { HydrationPersistenceAdapter, PersistedIndexSpec, PersistedKeySetEvidence, PersistedRowScanOptions, PersistedScannedRow, PersistedTx, PersistenceAdapter, ReplayableTxDelta, SQLiteDriver } from './persisted.js';
|
|
2
|
+
import { CommittedTxAnchor, HydrationPersistenceAdapter, PersistedIndexSpec, PersistedKeySetEvidence, PersistedRowScanOptions, PersistedScannedRow, PersistedTx, PersistenceAdapter, ReconciledCommittedTx, ReplayableTxDelta, SQLiteDriver } from './persisted.js';
|
|
3
3
|
type SQLiteCoreAdapterSchemaMismatchPolicy = `sync-present-reset` | `sync-absent-error` | `reset`;
|
|
4
4
|
export type SQLiteCoreAdapterOptions = {
|
|
5
5
|
driver: SQLiteDriver;
|
|
@@ -83,7 +83,10 @@ export declare class SQLiteCorePersistenceAdapter implements PersistenceAdapter
|
|
|
83
83
|
}>;
|
|
84
84
|
private loadResumeSnapshotUnscheduled;
|
|
85
85
|
applyCommittedTx(collectionId: string, tx: PersistedTx): Promise<void>;
|
|
86
|
+
reconcileCommittedTx(collectionId: string, tx: PersistedTx, anchor: CommittedTxAnchor): Promise<ReconciledCommittedTx>;
|
|
86
87
|
private applyCommittedTxUnscheduled;
|
|
88
|
+
private reconcileCommittedTxUnscheduled;
|
|
89
|
+
private applyCommittedTxInternal;
|
|
87
90
|
loadCollectionMetadata(collectionId: string): Promise<Array<{
|
|
88
91
|
key: string;
|
|
89
92
|
value: unknown;
|
|
@@ -95,6 +98,12 @@ export declare class SQLiteCorePersistenceAdapter implements PersistenceAdapter
|
|
|
95
98
|
private ensureIndexUnscheduled;
|
|
96
99
|
markIndexRemoved(collectionId: string, signature: string): Promise<void>;
|
|
97
100
|
private markIndexRemovedUnscheduled;
|
|
101
|
+
reserveLeadershipTerm(collectionId: string, observedTerm: number): Promise<{
|
|
102
|
+
latestTerm: number;
|
|
103
|
+
latestSeq: number;
|
|
104
|
+
latestRowVersion: number;
|
|
105
|
+
}>;
|
|
106
|
+
private reserveLeadershipTermUnscheduled;
|
|
98
107
|
getStreamPosition(collectionId: string): Promise<{
|
|
99
108
|
latestTerm: number;
|
|
100
109
|
latestSeq: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IR, toBooleanPredicate, compileSingleRowExpression, compareTemporalValues } from "@tanstack/db";
|
|
2
2
|
import { InvalidPersistedCollectionConfigError, InvalidPersistedStorageKeyEncodingError } from "./errors.js";
|
|
3
|
+
import { isValidCommittedTxAnchor } from "./committed-tx-anchor.js";
|
|
3
4
|
import { encodePersistedStorageKey, createPersistedTableName, decodePersistedStorageKey, SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY } from "./persisted.js";
|
|
4
5
|
import { serializeSQLiteTemporal, sqliteTemporalIdentity, assertSQLiteBigIntInRange, sqliteTemporalKind, PERSISTED_TYPE_TAG, PERSISTED_VALUE_TAG, reviveSQLiteTemporal, serializeSQLiteBigInt } from "./sqlite-value.js";
|
|
5
6
|
const REPLACEMENT_BATCH_SIZE = 125;
|
|
@@ -1088,6 +1089,7 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1088
1089
|
loadSubset: (collectionId, loadOptions, context) => this.loadSubsetUnscheduled(collectionId, loadOptions, context),
|
|
1089
1090
|
loadResumeSnapshot: (collectionId, context) => this.loadResumeSnapshotUnscheduled(collectionId, context),
|
|
1090
1091
|
applyCommittedTx: (collectionId, tx) => this.applyCommittedTxUnscheduled(collectionId, tx),
|
|
1092
|
+
reconcileCommittedTx: (collectionId, tx, anchor) => this.reconcileCommittedTxUnscheduled(collectionId, tx, anchor),
|
|
1091
1093
|
loadCollectionMetadata: (collectionId) => this.loadCollectionMetadataUnscheduled(collectionId),
|
|
1092
1094
|
scanRows: (collectionId, scanOptions) => this.scanRowsUnscheduled(collectionId, scanOptions),
|
|
1093
1095
|
ensureIndex: (collectionId, signature, spec) => this.ensureIndexUnscheduled(collectionId, signature, spec),
|
|
@@ -1283,15 +1285,33 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1283
1285
|
() => this.applyCommittedTxUnscheduled(collectionId, tx)
|
|
1284
1286
|
);
|
|
1285
1287
|
}
|
|
1288
|
+
reconcileCommittedTx(collectionId, tx, anchor) {
|
|
1289
|
+
return this.runRegular(
|
|
1290
|
+
() => this.reconcileCommittedTxUnscheduled(collectionId, tx, anchor)
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1286
1293
|
async applyCommittedTxUnscheduled(collectionId, tx) {
|
|
1294
|
+
await this.applyCommittedTxInternal(collectionId, tx);
|
|
1295
|
+
}
|
|
1296
|
+
reconcileCommittedTxUnscheduled(collectionId, tx, anchor) {
|
|
1297
|
+
if (!isValidCommittedTxAnchor(anchor)) {
|
|
1298
|
+
throw new Error(
|
|
1299
|
+
`Cannot reconcile a committed transaction without a valid durable anchor`
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
return this.applyCommittedTxInternal(collectionId, tx, anchor);
|
|
1303
|
+
}
|
|
1304
|
+
async applyCommittedTxInternal(collectionId, tx, anchor) {
|
|
1287
1305
|
const tableMapping = await this.ensureCollectionReady(collectionId);
|
|
1288
1306
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1289
1307
|
const tombstoneTableSql = quoteIdentifier(tableMapping.tombstoneTableName);
|
|
1290
|
-
|
|
1308
|
+
return this.runInTransaction(async (transactionDriver) => {
|
|
1291
1309
|
const versionRows = await transactionDriver.query(
|
|
1292
1310
|
`SELECT
|
|
1293
1311
|
latest_row_version,
|
|
1294
1312
|
key_set_evidence_available,
|
|
1313
|
+
(SELECT reset_epoch FROM collection_reset_epoch
|
|
1314
|
+
WHERE collection_id = ? LIMIT 1) AS reset_epoch,
|
|
1295
1315
|
(
|
|
1296
1316
|
SELECT schema_version
|
|
1297
1317
|
FROM collection_registry
|
|
@@ -1306,7 +1326,14 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1306
1326
|
FROM collection_version
|
|
1307
1327
|
WHERE collection_id = ?
|
|
1308
1328
|
LIMIT 1`,
|
|
1309
|
-
[
|
|
1329
|
+
[
|
|
1330
|
+
collectionId,
|
|
1331
|
+
collectionId,
|
|
1332
|
+
collectionId,
|
|
1333
|
+
tx.term,
|
|
1334
|
+
tx.seq,
|
|
1335
|
+
collectionId
|
|
1336
|
+
]
|
|
1310
1337
|
);
|
|
1311
1338
|
const version = versionRows[0];
|
|
1312
1339
|
if (!version) {
|
|
@@ -1319,8 +1346,34 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1319
1346
|
`Schema version mismatch for collection "${collectionId}": found ${version.schema_version}, expected ${this.schemaVersion}. Refusing to apply a committed transaction through a stale cached adapter.`
|
|
1320
1347
|
);
|
|
1321
1348
|
}
|
|
1349
|
+
if (anchor) {
|
|
1350
|
+
const sameResetEpoch = (version.reset_epoch ?? 0) === anchor.resetEpoch;
|
|
1351
|
+
const matching = await transactionDriver.query(
|
|
1352
|
+
`SELECT term, seq, row_version FROM applied_tx
|
|
1353
|
+
WHERE collection_id = ? AND tx_id = ? LIMIT 2`,
|
|
1354
|
+
[collectionId, tx.txId]
|
|
1355
|
+
);
|
|
1356
|
+
if (matching.length > 1) return { kind: `unknown` };
|
|
1357
|
+
if (matching[0]) {
|
|
1358
|
+
if (matching[0].row_version <= anchor.latestRowVersion || !sameResetEpoch) {
|
|
1359
|
+
return { kind: `unknown` };
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
kind: `already-applied`,
|
|
1363
|
+
committed: {
|
|
1364
|
+
term: matching[0].term,
|
|
1365
|
+
seq: matching[0].seq,
|
|
1366
|
+
rowVersion: matching[0].row_version
|
|
1367
|
+
},
|
|
1368
|
+
latestRowVersion: version.latest_row_version
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
if (version.latest_row_version !== anchor.latestRowVersion || !sameResetEpoch) {
|
|
1372
|
+
return { kind: `unknown` };
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1322
1375
|
if (version.already_applied === 1) {
|
|
1323
|
-
return;
|
|
1376
|
+
return { kind: `unknown` };
|
|
1324
1377
|
}
|
|
1325
1378
|
const currentRowVersion = version.latest_row_version;
|
|
1326
1379
|
const nextRowVersion = Math.max(currentRowVersion + 1, tx.rowVersion);
|
|
@@ -1568,6 +1621,15 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1568
1621
|
]
|
|
1569
1622
|
);
|
|
1570
1623
|
await this.pruneAppliedTxRows(collectionId, transactionDriver);
|
|
1624
|
+
return {
|
|
1625
|
+
kind: `applied-now`,
|
|
1626
|
+
committed: {
|
|
1627
|
+
term: tx.term,
|
|
1628
|
+
seq: tx.seq,
|
|
1629
|
+
rowVersion: nextRowVersion
|
|
1630
|
+
},
|
|
1631
|
+
latestRowVersion: nextRowVersion
|
|
1632
|
+
};
|
|
1571
1633
|
});
|
|
1572
1634
|
}
|
|
1573
1635
|
loadCollectionMetadata(collectionId) {
|
|
@@ -1732,6 +1794,31 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1732
1794
|
}
|
|
1733
1795
|
});
|
|
1734
1796
|
}
|
|
1797
|
+
reserveLeadershipTerm(collectionId, observedTerm) {
|
|
1798
|
+
return this.reserveLeadershipTermUnscheduled(collectionId, observedTerm);
|
|
1799
|
+
}
|
|
1800
|
+
async reserveLeadershipTermUnscheduled(collectionId, observedTerm) {
|
|
1801
|
+
await this.ensureCollectionReady(collectionId);
|
|
1802
|
+
return this.runInTransaction(async (transactionDriver) => {
|
|
1803
|
+
await this.assertCurrentSchemaVersion(
|
|
1804
|
+
collectionId,
|
|
1805
|
+
transactionDriver,
|
|
1806
|
+
`reserve a leadership term`
|
|
1807
|
+
);
|
|
1808
|
+
await transactionDriver.run(
|
|
1809
|
+
`INSERT INTO leader_term (collection_id, latest_term)
|
|
1810
|
+
VALUES (?, ? + 1)
|
|
1811
|
+
ON CONFLICT(collection_id) DO UPDATE SET
|
|
1812
|
+
latest_term = MAX(leader_term.latest_term, ?) + 1`,
|
|
1813
|
+
[collectionId, observedTerm, observedTerm]
|
|
1814
|
+
);
|
|
1815
|
+
const [position, latestRowVersion] = await Promise.all([
|
|
1816
|
+
this.readStreamPosition(collectionId, transactionDriver),
|
|
1817
|
+
this.readLatestRowVersion(collectionId, transactionDriver)
|
|
1818
|
+
]);
|
|
1819
|
+
return { ...position, latestRowVersion };
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1735
1822
|
getStreamPosition(collectionId) {
|
|
1736
1823
|
return this.getStreamPositionUnscheduled(collectionId);
|
|
1737
1824
|
}
|
|
@@ -2355,6 +2442,10 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2355
2442
|
PRIMARY KEY (collection_id, term, seq)
|
|
2356
2443
|
)`
|
|
2357
2444
|
);
|
|
2445
|
+
await this.driver.exec(
|
|
2446
|
+
`CREATE INDEX IF NOT EXISTS idx_applied_tx_collection_tx_id
|
|
2447
|
+
ON applied_tx (collection_id, tx_id)`
|
|
2448
|
+
);
|
|
2358
2449
|
try {
|
|
2359
2450
|
await this.driver.exec(
|
|
2360
2451
|
`ALTER TABLE applied_tx ADD COLUMN replay_json TEXT`
|