@optimystic/db-p2p-storage-ns 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -20
- package/dist/src/db.d.ts +45 -7
- package/dist/src/db.d.ts.map +1 -1
- package/dist/src/db.js +71 -5
- package/dist/src/db.js.map +1 -1
- package/dist/src/identity.d.ts +3 -1
- package/dist/src/identity.d.ts.map +1 -1
- package/dist/src/identity.js +5 -1
- package/dist/src/identity.js.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/ns-opener.d.ts.map +1 -1
- package/dist/src/ns-opener.js +16 -31
- package/dist/src/ns-opener.js.map +1 -1
- package/dist/src/sqlite-kv-store.d.ts +15 -1
- package/dist/src/sqlite-kv-store.d.ts.map +1 -1
- package/dist/src/sqlite-kv-store.js +38 -0
- package/dist/src/sqlite-kv-store.js.map +1 -1
- package/dist/src/sqlite-storage.d.ts +126 -45
- package/dist/src/sqlite-storage.d.ts.map +1 -1
- package/dist/src/sqlite-storage.js +246 -129
- package/dist/src/sqlite-storage.js.map +1 -1
- package/dist/src/sqlite-sync.d.ts +17 -0
- package/dist/src/sqlite-sync.d.ts.map +1 -0
- package/dist/src/sqlite-sync.js +21 -0
- package/dist/src/sqlite-sync.js.map +1 -0
- package/package.json +3 -3
- package/src/db.ts +96 -6
- package/src/identity.ts +5 -1
- package/src/index.ts +4 -2
- package/src/ns-opener.ts +20 -29
- package/src/sqlite-kv-store.ts +36 -2
- package/src/sqlite-storage.ts +291 -164
- package/src/sqlite-sync.ts +24 -0
|
@@ -1,153 +1,146 @@
|
|
|
1
|
-
import { KvRawStorage, identityForHandle } from '@optimystic/db-p2p';
|
|
1
|
+
import { KvRawStorage, StoreFailedError, SyncDeadlines, WriteGroupDriver, identityForHandle, } from '@optimystic/db-p2p';
|
|
2
2
|
import { createLogger } from './logger.js';
|
|
3
|
+
import { bumpDurabilityMark } from './sqlite-sync.js';
|
|
3
4
|
const log = createLogger('storage:sqlite');
|
|
5
|
+
const LAYOUTS = {
|
|
6
|
+
metadata: { keyColumns: ['block_id'], valueColumn: 'value' },
|
|
7
|
+
revisions: { keyColumns: ['block_id', 'rev'], valueColumn: 'action_id' },
|
|
8
|
+
proofs: { keyColumns: ['block_id', 'rev'], valueColumn: 'value' },
|
|
9
|
+
pending: { keyColumns: ['block_id', 'action_id'], valueColumn: 'value' },
|
|
10
|
+
transactions: { keyColumns: ['block_id', 'action_id'], valueColumn: 'value' },
|
|
11
|
+
materialized: { keyColumns: ['block_id', 'action_id'], valueColumn: 'value' },
|
|
12
|
+
};
|
|
13
|
+
const STORES = Object.keys(LAYOUTS);
|
|
14
|
+
const whereKey = (store) => LAYOUTS[store].keyColumns.map(column => `${column} = ?`).join(' AND ');
|
|
15
|
+
const sqlByStore = (build) => Object.fromEntries(STORES.map(store => [store, build(store)]));
|
|
16
|
+
const GET_SQL = sqlByStore(store => `SELECT ${LAYOUTS[store].valueColumn} FROM ${store} WHERE ${whereKey(store)}`);
|
|
17
|
+
const PUT_SQL = sqlByStore(store => {
|
|
18
|
+
const columns = [...LAYOUTS[store].keyColumns, LAYOUTS[store].valueColumn];
|
|
19
|
+
return `INSERT OR REPLACE INTO ${store} (${columns.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
20
|
+
});
|
|
21
|
+
const DELETE_SQL = sqlByStore(store => `DELETE FROM ${store} WHERE ${whereKey(store)}`);
|
|
22
|
+
/** The key's bind parameters, in the order of its store's key columns. */
|
|
23
|
+
function keyParams(key) {
|
|
24
|
+
switch (key.store) {
|
|
25
|
+
case 'metadata':
|
|
26
|
+
return [key.blockId];
|
|
27
|
+
case 'revisions':
|
|
28
|
+
case 'proofs':
|
|
29
|
+
return [key.blockId, key.rev];
|
|
30
|
+
case 'pending':
|
|
31
|
+
case 'transactions':
|
|
32
|
+
case 'materialized':
|
|
33
|
+
return [key.blockId, key.actionId];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
4
36
|
/**
|
|
5
|
-
* SQLite {@link
|
|
6
|
-
*
|
|
7
|
-
*
|
|
37
|
+
* SQLite {@link BatchBackend}: the six logical block-storage stores mapped to the six relational
|
|
38
|
+
* tables (`metadata`, `revisions`, `pending`, `transactions`, `proofs`, `materialized`) with their
|
|
39
|
+
* original columns and keys, every batch applied as ONE SQLite transaction. The reads are the
|
|
40
|
+
* former per-write driver's statements unchanged, so the on-disk format did not change with the
|
|
41
|
+
* move to batches.
|
|
42
|
+
*
|
|
43
|
+
* `KvRawStorage` owns all JSON serialization, so this backend only ever reads/writes `Uint8Array`
|
|
44
|
+
* values: the value columns are BLOB and SQLite binds a `Uint8Array` as a BLOB and returns it as a
|
|
45
|
+
* `Uint8Array`, so no codec lives here (a TEXT column would risk UTF-8 coercion corrupting non-ASCII
|
|
46
|
+
* JSON bytes).
|
|
47
|
+
*
|
|
48
|
+
* ### Why a committed batch survives a power cut, and in order
|
|
49
|
+
*
|
|
50
|
+
* `applySchema` (`db.ts`) opens every database in write-ahead-log (WAL) mode under
|
|
51
|
+
* `synchronous = NORMAL`. A commit appends the batch's frames to the one WAL file, each frame
|
|
52
|
+
* checksummed over the frames before it, and recovery stops at the first bad frame; before every
|
|
53
|
+
* checkpoint SQLite syncs the WAL, so the database file never holds later content ahead of earlier
|
|
54
|
+
* content. What a power cut leaves is therefore a prefix of the committed transactions, in commit
|
|
55
|
+
* order — rule 3 of the flush contract on `RawStoreDriver.flush` — and a batch is one transaction,
|
|
56
|
+
* so it survives whole or not at all. A commit under `synchronous = FULL`
|
|
57
|
+
* (`SqliteTransactionOptions.synced`) syncs the WAL at that commit, and the WAL being one
|
|
58
|
+
* append-only file, that sync covers every earlier commit on the database, the key-value store's
|
|
59
|
+
* and the identity helper's included, since all three share one connection. So the sync of a batch
|
|
60
|
+
* is one `FULL` commit — this batch's own when it must be synced at once, a later batch's, or the
|
|
61
|
+
* `durability_mark` bump the deadline timer commits — which is what `durability: 'power-loss'`
|
|
62
|
+
* states.
|
|
63
|
+
*
|
|
64
|
+
* ### How a commit decides
|
|
8
65
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
66
|
+
* The deadlines, the timer and the backpressure test are the rules of `SyncDeadlines` from
|
|
67
|
+
* `@optimystic/db-p2p`, shared with the file backend. `commit` takes the batch's deadline as it is
|
|
68
|
+
* called and decides whether the transaction is synced BEFORE taking the connection mutex, since
|
|
69
|
+
* the pragma is set as the transaction starts; the bookkeeping — `defer` of a deferred batch,
|
|
70
|
+
* `take` of the cover a synced one settles — runs INSIDE the transaction body, under the mutex, so
|
|
71
|
+
* it is ordered against every other commit and sync: a cover taken there is exactly the batches
|
|
72
|
+
* whose transactions committed earlier, every one of which this `FULL` commit syncs. A deadline
|
|
73
|
+
* that passes between the decision and the mutex only delays that commit's backpressure to the
|
|
74
|
+
* timer, which is due anyway.
|
|
75
|
+
*
|
|
76
|
+
* A read sees a batch as soon as its transaction commits, before it is synced, which `BatchBackend`
|
|
77
|
+
* allows; the overlay above serves the batch until it is durable. The handle is shared with
|
|
78
|
+
* `SqliteKVStore` and `loadOrCreateNSPeerKey`, so `close()` never closes it. Every write of a batch
|
|
79
|
+
* goes through `tx.prepare`, never a class-level statement, which would re-take the mutex the
|
|
80
|
+
* transaction holds and deadlock (see `SqliteDb.transaction`).
|
|
20
81
|
*/
|
|
21
|
-
export class
|
|
82
|
+
export class SqliteBatchBackend {
|
|
22
83
|
db;
|
|
23
|
-
|
|
84
|
+
durability = 'power-loss';
|
|
24
85
|
// NOTE: identity is the HANDLE object, so two handles opened over the same SQLite FILE read
|
|
25
86
|
// as two identities. Resolving that would mean naming the underlying database file, which
|
|
26
87
|
// `SqliteDb` does not expose. The package opener (`ns-opener.ts`) hands out one handle per
|
|
27
88
|
// file in practice, so this is the reachable case, not the complete one.
|
|
28
89
|
identity;
|
|
29
|
-
|
|
90
|
+
/** One point read per store. Reads run off the mutex, so a class-level statement is right here. */
|
|
91
|
+
reads;
|
|
92
|
+
stmts;
|
|
93
|
+
/**
|
|
94
|
+
* Batches committed and not yet synced, in order, each with the `durable` the next sync settles;
|
|
95
|
+
* the earliest deadline among them, with a timer armed for it; and the `window-overrun` episode.
|
|
96
|
+
* A batch's payload is nothing: its transaction has already applied it.
|
|
97
|
+
*/
|
|
98
|
+
deadlines;
|
|
99
|
+
fault;
|
|
100
|
+
constructor(db, options = {}) {
|
|
30
101
|
this.db = db;
|
|
31
102
|
this.identity = identityForHandle('sqlite-handle', db);
|
|
103
|
+
this.reads = Object.fromEntries(STORES.map(store => [store, db.prepare(GET_SQL[store])]));
|
|
32
104
|
this.stmts = {
|
|
33
|
-
|
|
34
|
-
saveMetadata: db.prepare('INSERT OR REPLACE INTO metadata (block_id, value) VALUES (?, ?)'),
|
|
35
|
-
getRevision: db.prepare('SELECT action_id FROM revisions WHERE block_id = ? AND rev = ?'),
|
|
36
|
-
saveRevision: db.prepare('INSERT OR REPLACE INTO revisions (block_id, rev, action_id) VALUES (?, ?, ?)'),
|
|
37
|
-
listRevisionsAsc: db.prepare('SELECT rev, action_id FROM revisions WHERE block_id = ? AND rev BETWEEN ? AND ? ORDER BY rev ASC'),
|
|
38
|
-
listRevisionsDesc: db.prepare('SELECT rev, action_id FROM revisions WHERE block_id = ? AND rev BETWEEN ? AND ? ORDER BY rev DESC'),
|
|
39
|
-
getPending: db.prepare('SELECT value FROM pending WHERE block_id = ? AND action_id = ?'),
|
|
40
|
-
savePending: db.prepare('INSERT OR REPLACE INTO pending (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
41
|
-
deletePending: db.prepare('DELETE FROM pending WHERE block_id = ? AND action_id = ?'),
|
|
105
|
+
listRevisions: db.prepare('SELECT rev, action_id FROM revisions WHERE block_id = ? AND rev BETWEEN ? AND ? ORDER BY rev ASC'),
|
|
42
106
|
listPending: db.prepare('SELECT action_id FROM pending WHERE block_id = ? ORDER BY action_id ASC'),
|
|
43
107
|
// metadata.block_id is the PRIMARY KEY, so each row is a distinct block id —
|
|
44
108
|
// no dedup needed. NOTE: drains the whole metadata table up front; if a peer
|
|
45
109
|
// ever holds millions of blocks and this SELECT becomes a startup-latency
|
|
46
110
|
// problem, page it (LIMIT/OFFSET or keyset) — fine at current scale.
|
|
47
111
|
listBlockIds: db.prepare('SELECT block_id FROM metadata'),
|
|
48
|
-
getTransaction: db.prepare('SELECT value FROM transactions WHERE block_id = ? AND action_id = ?'),
|
|
49
|
-
saveTransaction: db.prepare('INSERT OR REPLACE INTO transactions (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
50
|
-
getProof: db.prepare('SELECT value FROM proofs WHERE block_id = ? AND rev = ?'),
|
|
51
|
-
saveProof: db.prepare('INSERT OR REPLACE INTO proofs (block_id, rev, value) VALUES (?, ?, ?)'),
|
|
52
|
-
getMaterialized: db.prepare('SELECT value FROM materialized WHERE block_id = ? AND action_id = ?'),
|
|
53
|
-
saveMaterialized: db.prepare('INSERT OR REPLACE INTO materialized (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
54
|
-
deleteMaterialized: db.prepare('DELETE FROM materialized WHERE block_id = ? AND action_id = ?'),
|
|
55
112
|
pageCount: db.prepare('PRAGMA page_count'),
|
|
56
113
|
pageSize: db.prepare('PRAGMA page_size'),
|
|
57
114
|
};
|
|
115
|
+
this.deadlines = new SyncDeadlines({
|
|
116
|
+
onDue: firedAt => this.syncInBackground(firedAt),
|
|
117
|
+
setTimer: options.setTimer,
|
|
118
|
+
log,
|
|
119
|
+
subject: this.identity,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** True once a transaction or a sync failed; every later `commit` and `sync` then throws {@link StoreFailedError}. */
|
|
123
|
+
get failed() {
|
|
124
|
+
return this.fault !== undefined;
|
|
58
125
|
}
|
|
59
126
|
storeIdentity() {
|
|
60
127
|
return this.identity;
|
|
61
128
|
}
|
|
62
|
-
// ---
|
|
63
|
-
async
|
|
64
|
-
const row = await this.
|
|
65
|
-
return row ? row.
|
|
66
|
-
}
|
|
67
|
-
async putMetadata(blockId, value) {
|
|
68
|
-
await this.stmts.saveMetadata.run(blockId, value);
|
|
129
|
+
// --- reads ---
|
|
130
|
+
async get(key) {
|
|
131
|
+
const row = await this.reads[key.store].get(...keyParams(key));
|
|
132
|
+
return row ? row[LAYOUTS[key.store].valueColumn] : undefined;
|
|
69
133
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return row ? row.action_id : undefined;
|
|
134
|
+
async rangeRevisions(blockId, lo, hi) {
|
|
135
|
+
const rows = await this.stmts.listRevisions.all(blockId, lo, hi);
|
|
136
|
+
return rows.map(row => [row.rev, row.action_id]);
|
|
74
137
|
}
|
|
75
|
-
async
|
|
76
|
-
await this.stmts.saveRevision.run(blockId, rev, value);
|
|
77
|
-
}
|
|
78
|
-
async *rangeRevisions(blockId, lo, hi, reverse) {
|
|
79
|
-
// `.all(...)` materializes every row before we yield, so no SQLite cursor
|
|
80
|
-
// straddles the consumer's awaits (the kernel's drain-before-yield contract).
|
|
81
|
-
const stmt = reverse ? this.stmts.listRevisionsDesc : this.stmts.listRevisionsAsc;
|
|
82
|
-
const rows = await stmt.all(blockId, lo, hi);
|
|
83
|
-
for (const row of rows) {
|
|
84
|
-
yield [row.rev, row.action_id];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
// --- pending ---
|
|
88
|
-
async getPending(blockId, actionId) {
|
|
89
|
-
const row = await this.stmts.getPending.get(blockId, actionId);
|
|
90
|
-
return row ? row.value : undefined;
|
|
91
|
-
}
|
|
92
|
-
async putPending(blockId, actionId, value) {
|
|
93
|
-
await this.stmts.savePending.run(blockId, actionId, value);
|
|
94
|
-
}
|
|
95
|
-
async deletePending(blockId, actionId) {
|
|
96
|
-
await this.stmts.deletePending.run(blockId, actionId);
|
|
97
|
-
}
|
|
98
|
-
async *listPendingActionIds(blockId) {
|
|
99
|
-
// Drained by `.all(...)` before yielding — same rationale as rangeRevisions.
|
|
138
|
+
async listPendingActionIds(blockId) {
|
|
100
139
|
const rows = await this.stmts.listPending.all(blockId);
|
|
101
|
-
|
|
102
|
-
yield row.action_id;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// --- transactions ---
|
|
106
|
-
async getTransaction(blockId, actionId) {
|
|
107
|
-
const row = await this.stmts.getTransaction.get(blockId, actionId);
|
|
108
|
-
return row ? row.value : undefined;
|
|
109
|
-
}
|
|
110
|
-
async putTransaction(blockId, actionId, value) {
|
|
111
|
-
await this.stmts.saveTransaction.run(blockId, actionId, value);
|
|
112
|
-
}
|
|
113
|
-
// --- proofs (keyed (block_id, rev) like revisions; no delete — see RawStoreDriver.getProof) ---
|
|
114
|
-
async getProof(blockId, rev) {
|
|
115
|
-
const row = await this.stmts.getProof.get(blockId, rev);
|
|
116
|
-
return row ? row.value : undefined;
|
|
117
|
-
}
|
|
118
|
-
async putProof(blockId, rev, value) {
|
|
119
|
-
await this.stmts.saveProof.run(blockId, rev, value);
|
|
120
|
-
}
|
|
121
|
-
// --- materialized ---
|
|
122
|
-
async getMaterialized(blockId, actionId) {
|
|
123
|
-
const row = await this.stmts.getMaterialized.get(blockId, actionId);
|
|
124
|
-
return row ? row.value : undefined;
|
|
125
|
-
}
|
|
126
|
-
async putMaterialized(blockId, actionId, value) {
|
|
127
|
-
await this.stmts.saveMaterialized.run(blockId, actionId, value);
|
|
128
|
-
}
|
|
129
|
-
async deleteMaterialized(blockId, actionId) {
|
|
130
|
-
await this.stmts.deleteMaterialized.run(blockId, actionId);
|
|
131
|
-
}
|
|
132
|
-
// --- promote (the only cross-key atomic op) ---
|
|
133
|
-
async promote(blockId, actionId) {
|
|
134
|
-
await this.db.transaction(async (tx) => {
|
|
135
|
-
// Prepare against the open transaction so these three statements run on
|
|
136
|
-
// the held mutex slot without re-locking the connection (which would
|
|
137
|
-
// deadlock). Re-preparing is cheap — the driver caches by SQL text.
|
|
138
|
-
const getPending = tx.prepare('SELECT value FROM pending WHERE block_id = ? AND action_id = ?');
|
|
139
|
-
const saveTransaction = tx.prepare('INSERT OR REPLACE INTO transactions (block_id, action_id, value) VALUES (?, ?, ?)');
|
|
140
|
-
const deletePending = tx.prepare('DELETE FROM pending WHERE block_id = ? AND action_id = ?');
|
|
141
|
-
const row = await getPending.get(blockId, actionId);
|
|
142
|
-
if (!row) {
|
|
143
|
-
throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
|
|
144
|
-
}
|
|
145
|
-
await saveTransaction.run(blockId, actionId, row.value);
|
|
146
|
-
await deletePending.run(blockId, actionId);
|
|
147
|
-
});
|
|
140
|
+
return rows.map(row => row.action_id);
|
|
148
141
|
}
|
|
149
|
-
// --- optional passthroughs ---
|
|
150
142
|
async *listBlockIds() {
|
|
143
|
+
// Drained by `.all(...)` before yielding, so no cursor straddles the consumer's awaits.
|
|
151
144
|
const rows = await this.stmts.listBlockIds.all();
|
|
152
145
|
for (const row of rows) {
|
|
153
146
|
yield row.block_id;
|
|
@@ -166,22 +159,146 @@ export class SqliteStoreDriver {
|
|
|
166
159
|
return 0;
|
|
167
160
|
}
|
|
168
161
|
}
|
|
162
|
+
// --- the write group's sink ---
|
|
163
|
+
/**
|
|
164
|
+
* Apply the batch as one transaction and resolve with its `durable`. `WriteGroupSink.commit`'s
|
|
165
|
+
* contract: with `syncWithinMs` 0, or a deadline already passed (`SyncDeadlines.mustSyncNow`),
|
|
166
|
+
* the transaction commits under `synchronous = FULL`, which syncs this batch and every one held,
|
|
167
|
+
* and `durable` is already settled; otherwise it commits under `NORMAL` and `durable` settles at
|
|
168
|
+
* the next sync, which the timer makes by the earliest deadline held. A rejection has latched the
|
|
169
|
+
* backend.
|
|
170
|
+
*/
|
|
171
|
+
commit(entries, { syncWithinMs }) {
|
|
172
|
+
if (!(syncWithinMs >= 0) || !Number.isFinite(syncWithinMs)) {
|
|
173
|
+
return Promise.reject(new RangeError(`syncWithinMs must be a finite number of milliseconds at or above 0, got ${syncWithinMs}`));
|
|
174
|
+
}
|
|
175
|
+
if (this.fault)
|
|
176
|
+
return Promise.reject(new StoreFailedError(this.fault.cause));
|
|
177
|
+
const deadline = this.deadlines.deadlineFor(syncWithinMs);
|
|
178
|
+
return this.deadlines.mustSyncNow(deadline) ? this.commitSynced(entries) : this.commitDeferred(entries, deadline);
|
|
179
|
+
}
|
|
180
|
+
/** Hasten: one synced commit covering every batch held when it runs; with nothing held, nothing is synced. `WriteGroupSink.sync`'s contract. */
|
|
181
|
+
sync() {
|
|
182
|
+
return this.syncHeld();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The handle is shared, so it is not closed here: only the deadline timer must not outlive the
|
|
186
|
+
* backend. `WriteGroupDriver.close` flushes strictly first, which took and settled everything
|
|
187
|
+
* held and so disarmed the timer; a caller that did not flush gets the same here. After a latch
|
|
188
|
+
* nothing is held, since the latch took and rejected it.
|
|
189
|
+
*/
|
|
190
|
+
async close() {
|
|
191
|
+
if (this.fault)
|
|
192
|
+
return;
|
|
193
|
+
await this.syncHeld();
|
|
194
|
+
}
|
|
195
|
+
async commitDeferred(entries, deadline) {
|
|
196
|
+
try {
|
|
197
|
+
return await this.db.transaction(async (tx) => {
|
|
198
|
+
this.throwIfFailed();
|
|
199
|
+
await this.applyEntries(tx, entries);
|
|
200
|
+
// Deferred under the mutex, so the next sync's cover, also taken under it, includes this
|
|
201
|
+
// batch only once its transaction has committed.
|
|
202
|
+
return { durable: this.deadlines.defer(undefined, deadline) };
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
throw this.latch(err);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async commitSynced(entries) {
|
|
210
|
+
await this.syncHeld(undefined, tx => this.applyEntries(tx, entries));
|
|
211
|
+
return { durable: Promise.resolve() };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The sync step: one synced transaction whose body runs `write`, if given, then takes the cover
|
|
215
|
+
* of every batch held — under the mutex, so the cover is exactly the batches whose transactions
|
|
216
|
+
* committed before this one — and, when there is a cover to settle and nothing of its own was
|
|
217
|
+
* written, bumps `durability_mark` so the commit has a frame to sync. The cover settles once the
|
|
218
|
+
* commit has resolved. A failure anywhere latches the backend and rejects every covered
|
|
219
|
+
* `durable`, so no flush waits for a sync the store will not make.
|
|
220
|
+
*
|
|
221
|
+
* `owedSince` is the moment the timer fired, when the timer's task runs this: `SyncDeadlines.take`
|
|
222
|
+
* counts the sync's lateness from it rather than from a deadline the timer's own slop missed.
|
|
223
|
+
*/
|
|
224
|
+
async syncHeld(owedSince, write) {
|
|
225
|
+
// Assigned inside the body, so a failure after the take still reaches the cover it took.
|
|
226
|
+
let cover;
|
|
227
|
+
try {
|
|
228
|
+
await this.db.transaction(async (tx) => {
|
|
229
|
+
this.throwIfFailed();
|
|
230
|
+
const written = write ? await write(tx) : 0;
|
|
231
|
+
cover = this.deadlines.take(owedSince);
|
|
232
|
+
if (written === 0 && cover.items.length > 0)
|
|
233
|
+
await bumpDurabilityMark(tx);
|
|
234
|
+
}, { synced: true });
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
const failure = this.latch(err);
|
|
238
|
+
cover?.fail(failure.cause);
|
|
239
|
+
throw failure;
|
|
240
|
+
}
|
|
241
|
+
cover?.settle();
|
|
242
|
+
}
|
|
243
|
+
/** The timer fired: run the sync step. Its failure has latched the backend, so it is only logged here. */
|
|
244
|
+
syncInBackground(firedAt) {
|
|
245
|
+
this.syncHeld(firedAt).catch((err) => {
|
|
246
|
+
log('deferred sync of %s failed; the store refuses every later write: %o', this.identity, err);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Every entry in order — a put as `INSERT OR REPLACE`, a delete as `DELETE` — each statement
|
|
251
|
+
* prepared once per batch against the open transaction. Returns how many were applied.
|
|
252
|
+
*/
|
|
253
|
+
async applyEntries(tx, entries) {
|
|
254
|
+
const prepared = new Map();
|
|
255
|
+
const statement = (sql) => {
|
|
256
|
+
let stmt = prepared.get(sql);
|
|
257
|
+
if (!stmt) {
|
|
258
|
+
stmt = tx.prepare(sql);
|
|
259
|
+
prepared.set(sql, stmt);
|
|
260
|
+
}
|
|
261
|
+
return stmt;
|
|
262
|
+
};
|
|
263
|
+
for (const { key, value } of entries) {
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
await statement(DELETE_SQL[key.store]).run(...keyParams(key));
|
|
266
|
+
else
|
|
267
|
+
await statement(PUT_SQL[key.store]).run(...keyParams(key), value);
|
|
268
|
+
}
|
|
269
|
+
return entries.length;
|
|
270
|
+
}
|
|
271
|
+
throwIfFailed() {
|
|
272
|
+
if (this.fault)
|
|
273
|
+
throw new StoreFailedError(this.fault.cause);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Latch the backend failed on its first fault and reject every batch waiting for a sync that will
|
|
277
|
+
* now never be made, so no caller waits on a `durable` for ever. Returns the error to throw.
|
|
278
|
+
*/
|
|
279
|
+
latch(err) {
|
|
280
|
+
this.fault ??= { cause: err };
|
|
281
|
+
this.deadlines.failAll(err);
|
|
282
|
+
return new StoreFailedError(this.fault.cause);
|
|
283
|
+
}
|
|
169
284
|
}
|
|
170
285
|
/**
|
|
171
|
-
* SQLite-backed {@link IRawStorage} for NativeScript peers
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
286
|
+
* SQLite-backed {@link IRawStorage} for NativeScript peers: the shared {@link KvRawStorage} kernel
|
|
287
|
+
* over a {@link WriteGroupDriver} over a {@link SqliteBatchBackend}. Every write resolves into the
|
|
288
|
+
* driver's in-memory overlay; `flush()` hands the group to the backend as one batch, which applies
|
|
289
|
+
* it as one SQLite transaction, synced before the flush resolves or within the window the flush
|
|
290
|
+
* allows. The public name/constructor (`new SqliteRawStorage(db)`) is unchanged so existing imports
|
|
291
|
+
* keep resolving.
|
|
176
292
|
*
|
|
177
|
-
* `listBlockIds`/`getApproximateBytesUsed` are re-declared here as
|
|
178
|
-
* (the
|
|
179
|
-
*
|
|
180
|
-
* them.
|
|
293
|
+
* `listBlockIds`/`getApproximateBytesUsed`/`getStoreIdentity`/`close` are re-declared here as
|
|
294
|
+
* always-present (the backend always implements the first three, so the driver and kernel always
|
|
295
|
+
* wire them, and the driver always has a `close`) — the base declares them optional, but every NS
|
|
296
|
+
* consumer relies on them. `close()` flushes what is still held, syncs it, and disarms the timers;
|
|
297
|
+
* it does not close the shared handle, which the host closes afterwards.
|
|
181
298
|
*/
|
|
182
299
|
export class SqliteRawStorage extends KvRawStorage {
|
|
183
|
-
constructor(db) {
|
|
184
|
-
super(new
|
|
300
|
+
constructor(db, options = {}) {
|
|
301
|
+
super(new WriteGroupDriver(new SqliteBatchBackend(db, options), options));
|
|
185
302
|
}
|
|
186
303
|
}
|
|
187
304
|
//# sourceMappingURL=sqlite-storage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-storage.js","sourceRoot":"","sources":["../../src/sqlite-storage.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"sqlite-storage.js","sourceRoot":"","sources":["../../src/sqlite-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SqliteDb, SqliteTransaction } from './db.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bump the one `durability_mark` row inside an open transaction, so that the transaction has a
|
|
4
|
+
* frame to commit whatever else it wrote. The row's count means nothing; a sync needs a change to
|
|
5
|
+
* commit, because a transaction that changed nothing writes no frame and syncs nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function bumpDurabilityMark(tx: SqliteTransaction): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Sync the database: one synced transaction (`SqliteTransactionOptions.synced`) that bumps the
|
|
10
|
+
* `durability_mark` row. Its commit syncs the write-ahead log, which is one append-only file, so
|
|
11
|
+
* every earlier commit on this database — the block store's, the key-value store's and the identity
|
|
12
|
+
* helper's, all on one connection — survives a power cut once this resolves. Every sync in the
|
|
13
|
+
* package goes through this helper or through a synced transaction that writes rows of its own
|
|
14
|
+
* (`SqliteBatchBackend`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function syncSqliteDb(db: SqliteDb): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=sqlite-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-sync.d.ts","sourceRoot":"","sources":["../../src/sqlite-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAI3D;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const BUMP_MARK_SQL = 'INSERT INTO durability_mark (id, n) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET n = n + 1';
|
|
2
|
+
/**
|
|
3
|
+
* Bump the one `durability_mark` row inside an open transaction, so that the transaction has a
|
|
4
|
+
* frame to commit whatever else it wrote. The row's count means nothing; a sync needs a change to
|
|
5
|
+
* commit, because a transaction that changed nothing writes no frame and syncs nothing.
|
|
6
|
+
*/
|
|
7
|
+
export async function bumpDurabilityMark(tx) {
|
|
8
|
+
await tx.prepare(BUMP_MARK_SQL).run();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sync the database: one synced transaction (`SqliteTransactionOptions.synced`) that bumps the
|
|
12
|
+
* `durability_mark` row. Its commit syncs the write-ahead log, which is one append-only file, so
|
|
13
|
+
* every earlier commit on this database — the block store's, the key-value store's and the identity
|
|
14
|
+
* helper's, all on one connection — survives a power cut once this resolves. Every sync in the
|
|
15
|
+
* package goes through this helper or through a synced transaction that writes rows of its own
|
|
16
|
+
* (`SqliteBatchBackend`).
|
|
17
|
+
*/
|
|
18
|
+
export async function syncSqliteDb(db) {
|
|
19
|
+
await db.transaction(bumpDurabilityMark, { synced: true });
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=sqlite-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-sync.js","sourceRoot":"","sources":["../../src/sqlite-sync.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,2FAA2F,CAAC;AAElH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EAAqB;IAC7D,MAAM,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAY;IAC9C,MAAM,EAAE,CAAC,WAAW,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optimystic/db-p2p-storage-ns",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "NativeScript SQLite storage backend for @optimystic/db-p2p",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"typescript": "^5.9.3"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@optimystic/db-core": "^1.
|
|
57
|
-
"@optimystic/db-p2p": "^1.
|
|
56
|
+
"@optimystic/db-core": "^1.16.0",
|
|
57
|
+
"@optimystic/db-p2p": "^1.16.0",
|
|
58
58
|
"debug": "^4.4.3"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|