@optimystic/db-p2p-storage-ns 0.14.1 → 0.16.3
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 +30 -16
- package/dist/src/connection-mutex.d.ts +26 -0
- package/dist/src/connection-mutex.d.ts.map +1 -0
- package/dist/src/connection-mutex.js +33 -0
- package/dist/src/connection-mutex.js.map +1 -0
- package/dist/src/db.d.ts +60 -14
- package/dist/src/db.d.ts.map +1 -1
- package/dist/src/db.js +36 -14
- package/dist/src/db.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/ns-opener.d.ts +13 -4
- package/dist/src/ns-opener.d.ts.map +1 -1
- package/dist/src/ns-opener.js +67 -22
- package/dist/src/ns-opener.js.map +1 -1
- package/dist/src/sqlite-storage.d.ts +52 -29
- package/dist/src/sqlite-storage.d.ts.map +1 -1
- package/dist/src/sqlite-storage.js +94 -58
- package/dist/src/sqlite-storage.js.map +1 -1
- package/package.json +3 -3
- package/src/connection-mutex.ts +33 -0
- package/src/db.ts +72 -19
- package/src/index.ts +1 -1
- package/src/ns-opener.ts +71 -24
- package/src/sqlite-storage.ts +113 -59
|
@@ -1,38 +1,61 @@
|
|
|
1
|
-
import type { ActionId,
|
|
2
|
-
import
|
|
1
|
+
import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
2
|
+
import { KvRawStorage, type RawStoreDriver } from '@optimystic/db-p2p';
|
|
3
3
|
import type { SqliteDb } from './db.js';
|
|
4
4
|
/**
|
|
5
|
-
* SQLite
|
|
5
|
+
* SQLite {@link RawStoreDriver}: the five logical block-storage stores mapped to
|
|
6
|
+
* the five relational tables (`metadata`, `revisions`, `pending`, `transactions`,
|
|
7
|
+
* `materialized`) with their original columns and keys. This is a code refactor,
|
|
8
|
+
* not a storage-format change at the SQL level.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* `KvRawStorage` now owns all JSON serialization, so this driver only ever
|
|
11
|
+
* reads/writes `Uint8Array` values: the value columns are BLOB and SQLite binds
|
|
12
|
+
* a `Uint8Array` as a BLOB and returns it as a `Uint8Array`, so no codec lives
|
|
13
|
+
* here (a TEXT column would risk UTF-8 coercion corrupting non-ASCII JSON bytes).
|
|
14
|
+
* Everything SQLite-specific stays: each CRUD op goes through a prepared
|
|
15
|
+
* statement bound once in the constructor; range/list queries drain their rows
|
|
16
|
+
* (`.all(...)`) before yielding so no cursor straddles a consumer's `await`; and
|
|
17
|
+
* `promote` runs as a single `db.transaction(fn)` — `BEGIN IMMEDIATE; INSERT…;
|
|
18
|
+
* DELETE…; COMMIT;` — whose three statements are re-prepared against the OPEN
|
|
19
|
+
* transaction so they run on the held mutex slot without re-locking (which would
|
|
20
|
+
* deadlock — see `st-nativescript-sqlite-transaction-mutex`).
|
|
17
21
|
*/
|
|
18
|
-
export declare class
|
|
22
|
+
export declare class SqliteStoreDriver implements RawStoreDriver {
|
|
19
23
|
private readonly db;
|
|
20
24
|
private readonly stmts;
|
|
21
25
|
constructor(db: SqliteDb);
|
|
22
|
-
getMetadata(blockId: BlockId): Promise<
|
|
23
|
-
|
|
24
|
-
getRevision(blockId: BlockId, rev: number): Promise<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
getTransaction(blockId: BlockId, actionId: ActionId): Promise<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
|
|
27
|
+
putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
|
|
28
|
+
getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
|
|
29
|
+
putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
|
|
30
|
+
rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]>;
|
|
31
|
+
getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
32
|
+
putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
33
|
+
deletePending(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
34
|
+
listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
|
|
35
|
+
getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
36
|
+
putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
37
|
+
getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
38
|
+
putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
39
|
+
deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
40
|
+
promote(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
41
|
+
listBlockIds(): AsyncIterable<BlockId>;
|
|
42
|
+
approximateBytesUsed(): Promise<number>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* SQLite-backed {@link IRawStorage} for NativeScript peers, now a thin shell over
|
|
46
|
+
* the shared {@link KvRawStorage} kernel driven by a {@link SqliteStoreDriver}.
|
|
47
|
+
* The public name/constructor (`new SqliteRawStorage(db)`) is unchanged so
|
|
48
|
+
* existing imports keep resolving; the kernel supplies the `IRawStorage` surface
|
|
49
|
+
* and the driver supplies SQLite behavior.
|
|
50
|
+
*
|
|
51
|
+
* `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
|
|
52
|
+
* (the SQLite driver always implements them, so the kernel constructor always
|
|
53
|
+
* wires them) — the base declares them optional, but every NS consumer relies on
|
|
54
|
+
* them.
|
|
55
|
+
*/
|
|
56
|
+
export declare class SqliteRawStorage extends KvRawStorage {
|
|
57
|
+
listBlockIds: () => AsyncIterable<BlockId>;
|
|
58
|
+
getApproximateBytesUsed: () => Promise<number>;
|
|
59
|
+
constructor(db: SqliteDb);
|
|
37
60
|
}
|
|
38
61
|
//# sourceMappingURL=sqlite-storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-storage.d.ts","sourceRoot":"","sources":["../../src/sqlite-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"sqlite-storage.d.ts","sourceRoot":"","sources":["../../src/sqlite-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAKzD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAsB3C,OAAO,CAAC,QAAQ,CAAC,EAAE;IArB/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAmBpB;gBAE2B,EAAE,EAAE,QAAQ;IA6BnC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAK9D,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/D,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAK3E,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAYhH,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKjF,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjE,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IAUhE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKrF,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKtF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB3D,YAAY,IAAI,aAAa,CAAC,OAAO,CAAC;IAOvC,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;CAY7C;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IACzC,YAAY,EAAE,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,uBAAuB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBAE3C,EAAE,EAAE,QAAQ;CAGxB"}
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
+
import { KvRawStorage } from '@optimystic/db-p2p';
|
|
1
2
|
import { createLogger } from './logger.js';
|
|
2
3
|
const log = createLogger('storage:sqlite');
|
|
3
4
|
/**
|
|
4
|
-
* SQLite
|
|
5
|
+
* SQLite {@link RawStoreDriver}: the five logical block-storage stores mapped to
|
|
6
|
+
* the five relational tables (`metadata`, `revisions`, `pending`, `transactions`,
|
|
7
|
+
* `materialized`) with their original columns and keys. This is a code refactor,
|
|
8
|
+
* not a storage-format change at the SQL level.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* `KvRawStorage` now owns all JSON serialization, so this driver only ever
|
|
11
|
+
* reads/writes `Uint8Array` values: the value columns are BLOB and SQLite binds
|
|
12
|
+
* a `Uint8Array` as a BLOB and returns it as a `Uint8Array`, so no codec lives
|
|
13
|
+
* here (a TEXT column would risk UTF-8 coercion corrupting non-ASCII JSON bytes).
|
|
14
|
+
* Everything SQLite-specific stays: each CRUD op goes through a prepared
|
|
15
|
+
* statement bound once in the constructor; range/list queries drain their rows
|
|
16
|
+
* (`.all(...)`) before yielding so no cursor straddles a consumer's `await`; and
|
|
17
|
+
* `promote` runs as a single `db.transaction(fn)` — `BEGIN IMMEDIATE; INSERT…;
|
|
18
|
+
* DELETE…; COMMIT;` — whose three statements are re-prepared against the OPEN
|
|
19
|
+
* transaction so they run on the held mutex slot without re-locking (which would
|
|
20
|
+
* deadlock — see `st-nativescript-sqlite-transaction-mutex`).
|
|
16
21
|
*/
|
|
17
|
-
export class
|
|
22
|
+
export class SqliteStoreDriver {
|
|
18
23
|
db;
|
|
19
24
|
stmts;
|
|
20
25
|
constructor(db) {
|
|
@@ -30,6 +35,11 @@ export class SqliteRawStorage {
|
|
|
30
35
|
savePending: db.prepare('INSERT OR REPLACE INTO pending (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
31
36
|
deletePending: db.prepare('DELETE FROM pending WHERE block_id = ? AND action_id = ?'),
|
|
32
37
|
listPending: db.prepare('SELECT action_id FROM pending WHERE block_id = ? ORDER BY action_id ASC'),
|
|
38
|
+
// metadata.block_id is the PRIMARY KEY, so each row is a distinct block id —
|
|
39
|
+
// no dedup needed. NOTE: drains the whole metadata table up front; if a peer
|
|
40
|
+
// ever holds millions of blocks and this SELECT becomes a startup-latency
|
|
41
|
+
// problem, page it (LIMIT/OFFSET or keyset) — fine at current scale.
|
|
42
|
+
listBlockIds: db.prepare('SELECT block_id FROM metadata'),
|
|
33
43
|
getTransaction: db.prepare('SELECT value FROM transactions WHERE block_id = ? AND action_id = ?'),
|
|
34
44
|
saveTransaction: db.prepare('INSERT OR REPLACE INTO transactions (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
35
45
|
getMaterialized: db.prepare('SELECT value FROM materialized WHERE block_id = ? AND action_id = ?'),
|
|
@@ -39,74 +49,93 @@ export class SqliteRawStorage {
|
|
|
39
49
|
pageSize: db.prepare('PRAGMA page_size'),
|
|
40
50
|
};
|
|
41
51
|
}
|
|
52
|
+
// --- metadata ---
|
|
42
53
|
async getMetadata(blockId) {
|
|
43
54
|
const row = await this.stmts.getMetadata.get(blockId);
|
|
44
|
-
|
|
45
|
-
return undefined;
|
|
46
|
-
return JSON.parse(row.value);
|
|
55
|
+
return row ? row.value : undefined;
|
|
47
56
|
}
|
|
48
|
-
async
|
|
49
|
-
await this.stmts.saveMetadata.run(blockId,
|
|
57
|
+
async putMetadata(blockId, value) {
|
|
58
|
+
await this.stmts.saveMetadata.run(blockId, value);
|
|
50
59
|
}
|
|
60
|
+
// --- revisions ---
|
|
51
61
|
async getRevision(blockId, rev) {
|
|
52
62
|
const row = await this.stmts.getRevision.get(blockId, rev);
|
|
53
63
|
return row ? row.action_id : undefined;
|
|
54
64
|
}
|
|
55
|
-
async
|
|
56
|
-
await this.stmts.saveRevision.run(blockId, rev,
|
|
65
|
+
async putRevision(blockId, rev, value) {
|
|
66
|
+
await this.stmts.saveRevision.run(blockId, rev, value);
|
|
57
67
|
}
|
|
58
|
-
async *
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const stmt = ascending ? this.stmts.listRevisionsAsc : this.stmts.listRevisionsDesc;
|
|
68
|
+
async *rangeRevisions(blockId, lo, hi, reverse) {
|
|
69
|
+
// `.all(...)` materializes every row before we yield, so no SQLite cursor
|
|
70
|
+
// straddles the consumer's awaits (the kernel's drain-before-yield contract).
|
|
71
|
+
const stmt = reverse ? this.stmts.listRevisionsDesc : this.stmts.listRevisionsAsc;
|
|
63
72
|
const rows = await stmt.all(blockId, lo, hi);
|
|
64
73
|
for (const row of rows) {
|
|
65
|
-
yield
|
|
74
|
+
yield [row.rev, row.action_id];
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
|
-
|
|
77
|
+
// --- pending ---
|
|
78
|
+
async getPending(blockId, actionId) {
|
|
69
79
|
const row = await this.stmts.getPending.get(blockId, actionId);
|
|
70
|
-
|
|
71
|
-
return undefined;
|
|
72
|
-
return JSON.parse(row.value);
|
|
80
|
+
return row ? row.value : undefined;
|
|
73
81
|
}
|
|
74
|
-
async
|
|
75
|
-
await this.stmts.savePending.run(blockId, actionId,
|
|
82
|
+
async putPending(blockId, actionId, value) {
|
|
83
|
+
await this.stmts.savePending.run(blockId, actionId, value);
|
|
76
84
|
}
|
|
77
|
-
async
|
|
85
|
+
async deletePending(blockId, actionId) {
|
|
78
86
|
await this.stmts.deletePending.run(blockId, actionId);
|
|
79
87
|
}
|
|
80
|
-
async *
|
|
88
|
+
async *listPendingActionIds(blockId) {
|
|
89
|
+
// Drained by `.all(...)` before yielding — same rationale as rangeRevisions.
|
|
81
90
|
const rows = await this.stmts.listPending.all(blockId);
|
|
82
91
|
for (const row of rows) {
|
|
83
92
|
yield row.action_id;
|
|
84
93
|
}
|
|
85
94
|
}
|
|
95
|
+
// --- transactions ---
|
|
86
96
|
async getTransaction(blockId, actionId) {
|
|
87
97
|
const row = await this.stmts.getTransaction.get(blockId, actionId);
|
|
88
|
-
|
|
89
|
-
return undefined;
|
|
90
|
-
return JSON.parse(row.value);
|
|
98
|
+
return row ? row.value : undefined;
|
|
91
99
|
}
|
|
92
|
-
async
|
|
93
|
-
await this.stmts.saveTransaction.run(blockId, actionId,
|
|
100
|
+
async putTransaction(blockId, actionId, value) {
|
|
101
|
+
await this.stmts.saveTransaction.run(blockId, actionId, value);
|
|
94
102
|
}
|
|
95
|
-
|
|
103
|
+
// --- materialized ---
|
|
104
|
+
async getMaterialized(blockId, actionId) {
|
|
96
105
|
const row = await this.stmts.getMaterialized.get(blockId, actionId);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
return row ? row.value : undefined;
|
|
107
|
+
}
|
|
108
|
+
async putMaterialized(blockId, actionId, value) {
|
|
109
|
+
await this.stmts.saveMaterialized.run(blockId, actionId, value);
|
|
110
|
+
}
|
|
111
|
+
async deleteMaterialized(blockId, actionId) {
|
|
112
|
+
await this.stmts.deleteMaterialized.run(blockId, actionId);
|
|
113
|
+
}
|
|
114
|
+
// --- promote (the only cross-key atomic op) ---
|
|
115
|
+
async promote(blockId, actionId) {
|
|
116
|
+
await this.db.transaction(async (tx) => {
|
|
117
|
+
// Prepare against the open transaction so these three statements run on
|
|
118
|
+
// the held mutex slot without re-locking the connection (which would
|
|
119
|
+
// deadlock). Re-preparing is cheap — the driver caches by SQL text.
|
|
120
|
+
const getPending = tx.prepare('SELECT value FROM pending WHERE block_id = ? AND action_id = ?');
|
|
121
|
+
const saveTransaction = tx.prepare('INSERT OR REPLACE INTO transactions (block_id, action_id, value) VALUES (?, ?, ?)');
|
|
122
|
+
const deletePending = tx.prepare('DELETE FROM pending WHERE block_id = ? AND action_id = ?');
|
|
123
|
+
const row = await getPending.get(blockId, actionId);
|
|
124
|
+
if (!row) {
|
|
125
|
+
throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
|
|
126
|
+
}
|
|
127
|
+
await saveTransaction.run(blockId, actionId, row.value);
|
|
128
|
+
await deletePending.run(blockId, actionId);
|
|
129
|
+
});
|
|
100
130
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await this.stmts.deleteMaterialized.run(blockId, actionId);
|
|
131
|
+
// --- optional passthroughs ---
|
|
132
|
+
async *listBlockIds() {
|
|
133
|
+
const rows = await this.stmts.listBlockIds.all();
|
|
134
|
+
for (const row of rows) {
|
|
135
|
+
yield row.block_id;
|
|
107
136
|
}
|
|
108
137
|
}
|
|
109
|
-
async
|
|
138
|
+
async approximateBytesUsed() {
|
|
110
139
|
try {
|
|
111
140
|
const pageCountRow = await this.stmts.pageCount.get();
|
|
112
141
|
const pageSizeRow = await this.stmts.pageSize.get();
|
|
@@ -119,15 +148,22 @@ export class SqliteRawStorage {
|
|
|
119
148
|
return 0;
|
|
120
149
|
}
|
|
121
150
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* SQLite-backed {@link IRawStorage} for NativeScript peers, now a thin shell over
|
|
154
|
+
* the shared {@link KvRawStorage} kernel driven by a {@link SqliteStoreDriver}.
|
|
155
|
+
* The public name/constructor (`new SqliteRawStorage(db)`) is unchanged so
|
|
156
|
+
* existing imports keep resolving; the kernel supplies the `IRawStorage` surface
|
|
157
|
+
* and the driver supplies SQLite behavior.
|
|
158
|
+
*
|
|
159
|
+
* `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
|
|
160
|
+
* (the SQLite driver always implements them, so the kernel constructor always
|
|
161
|
+
* wires them) — the base declares them optional, but every NS consumer relies on
|
|
162
|
+
* them.
|
|
163
|
+
*/
|
|
164
|
+
export class SqliteRawStorage extends KvRawStorage {
|
|
165
|
+
constructor(db) {
|
|
166
|
+
super(new SqliteStoreDriver(db));
|
|
131
167
|
}
|
|
132
168
|
}
|
|
133
169
|
//# sourceMappingURL=sqlite-storage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-storage.js","sourceRoot":"","sources":["../../src/sqlite-storage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sqlite-storage.js","sourceRoot":"","sources":["../../src/sqlite-storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optimystic/db-p2p-storage-ns",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.3",
|
|
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": "^0.
|
|
57
|
-
"@optimystic/db-p2p": "^0.
|
|
56
|
+
"@optimystic/db-core": "^0.16.3",
|
|
57
|
+
"@optimystic/db-p2p": "^0.16.3",
|
|
58
58
|
"debug": "^4.4.3"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-connection FIFO mutex for the shared SQLite handle.
|
|
3
|
+
*
|
|
4
|
+
* SQLite allows at most one open transaction per connection, so every mutating
|
|
5
|
+
* operation on a shared connection (plain writes AND transaction bodies) must be
|
|
6
|
+
* serialized or a concurrent `BEGIN` will nest and cross-rollback another
|
|
7
|
+
* operation's still-open writes.
|
|
8
|
+
*
|
|
9
|
+
* This is deliberately NOT the global `Latches` keyed map from
|
|
10
|
+
* `@optimystic/db-core` — that is process-wide and keyed by string. Here we want
|
|
11
|
+
* one mutex bound to one connection instance, so the wrapper holds its own.
|
|
12
|
+
*
|
|
13
|
+
* The chain tail is kept non-rejecting: a failing task must not poison the queue
|
|
14
|
+
* for the operations behind it, so `serialize` continues the chain regardless of
|
|
15
|
+
* the prior task's outcome while still surfacing each task's own result/error to
|
|
16
|
+
* its own caller.
|
|
17
|
+
*/
|
|
18
|
+
export class ConnectionMutex {
|
|
19
|
+
private tail: Promise<unknown> = Promise.resolve();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Queue `task` behind all previously-queued tasks and run it once they settle.
|
|
23
|
+
* Resolves/rejects with `task`'s own outcome; never rejects the shared tail.
|
|
24
|
+
*/
|
|
25
|
+
serialize<T>(task: () => Promise<T> | T): Promise<T> {
|
|
26
|
+
// Run `task` whether the prior task fulfilled or rejected.
|
|
27
|
+
const run = this.tail.then(task, task);
|
|
28
|
+
// Advance the tail with a settled-either-way promise so a rejection here
|
|
29
|
+
// does not reject the next task's `.then(task, task)` prematurely.
|
|
30
|
+
this.tail = run.then(() => undefined, () => undefined);
|
|
31
|
+
return run;
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/db.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type SqliteRow = Record<string, SqliteParam>;
|
|
|
22
22
|
*
|
|
23
23
|
* Both `node:sqlite` and `@nativescript-community/sqlite` cache parsed SQL
|
|
24
24
|
* internally, so re-binding a prepared statement is cheaper than re-parsing a
|
|
25
|
-
* raw `
|
|
25
|
+
* raw `execute`. The storage classes prepare each query once per instance.
|
|
26
26
|
*/
|
|
27
27
|
export interface SqliteStatement {
|
|
28
28
|
/** Execute with the given bind parameters, ignoring any returned rows. */
|
|
@@ -33,20 +33,51 @@ export interface SqliteStatement {
|
|
|
33
33
|
all(...params: SqliteParam[]): Promise<SqliteRow[]>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Statements that execute directly on an already-OPEN transaction.
|
|
38
|
+
*
|
|
39
|
+
* Handed to the `transaction(fn)` callback. Its prepared statements run on the
|
|
40
|
+
* raw connection *inside* the mutex slot the transaction already holds, so they
|
|
41
|
+
* must NOT re-acquire the connection mutex — doing so would deadlock (the
|
|
42
|
+
* transaction body holds the only slot). This is the explicit-context seam the
|
|
43
|
+
* fix threads through instead of a shared `inTransaction` flag, which cannot
|
|
44
|
+
* tell an inner statement (bypass the lock) from a concurrent external write
|
|
45
|
+
* (block on it).
|
|
46
|
+
*/
|
|
47
|
+
export interface SqliteTransaction {
|
|
48
|
+
/** Prepare a statement bound to the open transaction; bypasses the mutex. */
|
|
49
|
+
prepare(sql: string): SqliteStatement;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
/**
|
|
37
53
|
* Minimal SQLite driver surface used by this package.
|
|
38
54
|
*
|
|
39
55
|
* Wraps either the NativeScript plugin (in production) or a Node SQLite
|
|
40
56
|
* driver (in tests). Async on every method so the NS plugin's I/O can be
|
|
41
57
|
* Promised — even where the underlying call is synchronous.
|
|
58
|
+
*
|
|
59
|
+
* A single connection is shared across the storage classes, so every mutating
|
|
60
|
+
* operation (`exec`, statement `run`, and `transaction` bodies) is serialized on
|
|
61
|
+
* a per-connection FIFO mutex. Reads (`get`/`all`) are intentionally left
|
|
62
|
+
* unserialized to preserve read concurrency.
|
|
42
63
|
*/
|
|
43
64
|
export interface SqliteDb {
|
|
44
|
-
/** Execute one or more semicolon-separated statements; no result rows. */
|
|
65
|
+
/** Execute one or more semicolon-separated statements; no result rows. Mutex-guarded. */
|
|
45
66
|
exec(sql: string): Promise<void>;
|
|
46
|
-
/** Prepare a parameterized statement for repeated execution. */
|
|
67
|
+
/** Prepare a parameterized statement for repeated execution (writes are mutex-guarded). */
|
|
47
68
|
prepare(sql: string): SqliteStatement;
|
|
48
|
-
/**
|
|
49
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Run `fn` inside `BEGIN IMMEDIATE ... COMMIT` (rolls back on throw), holding
|
|
71
|
+
* the connection mutex for the whole body. `fn` receives a `SqliteTransaction`
|
|
72
|
+
* whose statements run directly on the open transaction without re-locking.
|
|
73
|
+
*
|
|
74
|
+
* NOTE: `fn` must issue its writes through the provided `tx`, never through a
|
|
75
|
+
* class-level statement from `db.prepare(...)` (those re-acquire the mutex and
|
|
76
|
+
* deadlock behind the slot this transaction holds), and must not call
|
|
77
|
+
* `db.transaction(...)` again — nested calls deadlock for the same reason. No
|
|
78
|
+
* current caller does either; guard here if that ever changes.
|
|
79
|
+
*/
|
|
80
|
+
transaction<T>(fn: (tx: SqliteTransaction) => Promise<T>): Promise<T>;
|
|
50
81
|
/** Release the underlying handle. */
|
|
51
82
|
close(): Promise<void>;
|
|
52
83
|
}
|
|
@@ -57,47 +88,63 @@ export const DEFAULT_DB_VERSION = 1;
|
|
|
57
88
|
/**
|
|
58
89
|
* Schema for the NativeScript storage backend.
|
|
59
90
|
*
|
|
60
|
-
* Mirrors the IndexedDB object stores 1:1
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* -
|
|
64
|
-
*
|
|
65
|
-
*
|
|
91
|
+
* Mirrors the IndexedDB object stores 1:1. The five block-storage stores keep
|
|
92
|
+
* their original columns and keys, but their value columns are now BLOB: the
|
|
93
|
+
* shared `KvRawStorage` kernel (driven by `SqliteStoreDriver`) owns all JSON /
|
|
94
|
+
* UTF-8 (de)serialization, so the driver only ever binds/reads the kernel's raw
|
|
95
|
+
* `Uint8Array` values. SQLite stores a `Uint8Array` as a BLOB and returns it as
|
|
96
|
+
* a `Uint8Array`, so bytes round-trip exactly (a TEXT column would risk UTF-8
|
|
97
|
+
* coercion corrupting non-ASCII JSON bytes). Keys stay TEXT/INTEGER.
|
|
98
|
+
* - `metadata` → per-block `BlockMetadata` bytes keyed by `block_id`.
|
|
99
|
+
* - `revisions` → revision lookup keyed by `(block_id, rev)`; `action_id` column
|
|
100
|
+
* holds the kernel's encoded `ActionId` bytes (BLOB).
|
|
101
|
+
* - `pending` → uncommitted transform bytes keyed by `(block_id, action_id)`.
|
|
102
|
+
* - `transactions` → committed transform bytes keyed by `(block_id, action_id)`.
|
|
103
|
+
* - `materialized` → materialized block bytes keyed by `(block_id, action_id)`.
|
|
66
104
|
* - `kv` → generic string keyspace shared by `IKVStore` (`s_val`) and the
|
|
67
|
-
* identity helper (`b_val`). The two columns
|
|
68
|
-
* for the libp2p private key.
|
|
105
|
+
* identity helper (`b_val`). NOT kernel-backed; unchanged. The two columns
|
|
106
|
+
* avoid a base64 round-trip for the libp2p private key.
|
|
107
|
+
*
|
|
108
|
+
* NOTE: value columns changed TEXT→BLOB with the kernel refactor, but every
|
|
109
|
+
* statement is `CREATE TABLE IF NOT EXISTS` — so a database file created by a
|
|
110
|
+
* pre-refactor build keeps its old TEXT-column schema, and its rows hold JSON
|
|
111
|
+
* *strings* the kernel now reads back and `TextDecoder.decode`s (garbling /
|
|
112
|
+
* throwing). There is no migration. Safe today because this backend is
|
|
113
|
+
* NativeScript-only with no shipped production data; if a build ever ships to
|
|
114
|
+
* devices that later upgrade, add a `user_version` bump that drops+recreates
|
|
115
|
+
* these five tables (or a real migration) before assuming BLOB rows.
|
|
69
116
|
*/
|
|
70
117
|
export const SCHEMA_SQL = `
|
|
71
118
|
CREATE TABLE IF NOT EXISTS metadata (
|
|
72
119
|
block_id TEXT PRIMARY KEY,
|
|
73
|
-
value
|
|
120
|
+
value BLOB NOT NULL
|
|
74
121
|
);
|
|
75
122
|
|
|
76
123
|
CREATE TABLE IF NOT EXISTS revisions (
|
|
77
124
|
block_id TEXT NOT NULL,
|
|
78
125
|
rev INTEGER NOT NULL,
|
|
79
|
-
action_id
|
|
126
|
+
action_id BLOB NOT NULL,
|
|
80
127
|
PRIMARY KEY (block_id, rev)
|
|
81
128
|
);
|
|
82
129
|
|
|
83
130
|
CREATE TABLE IF NOT EXISTS pending (
|
|
84
131
|
block_id TEXT NOT NULL,
|
|
85
132
|
action_id TEXT NOT NULL,
|
|
86
|
-
value
|
|
133
|
+
value BLOB NOT NULL,
|
|
87
134
|
PRIMARY KEY (block_id, action_id)
|
|
88
135
|
);
|
|
89
136
|
|
|
90
137
|
CREATE TABLE IF NOT EXISTS transactions (
|
|
91
138
|
block_id TEXT NOT NULL,
|
|
92
139
|
action_id TEXT NOT NULL,
|
|
93
|
-
value
|
|
140
|
+
value BLOB NOT NULL,
|
|
94
141
|
PRIMARY KEY (block_id, action_id)
|
|
95
142
|
);
|
|
96
143
|
|
|
97
144
|
CREATE TABLE IF NOT EXISTS materialized (
|
|
98
145
|
block_id TEXT NOT NULL,
|
|
99
146
|
action_id TEXT NOT NULL,
|
|
100
|
-
value
|
|
147
|
+
value BLOB NOT NULL,
|
|
101
148
|
PRIMARY KEY (block_id, action_id)
|
|
102
149
|
);
|
|
103
150
|
|
|
@@ -108,8 +155,8 @@ CREATE TABLE IF NOT EXISTS kv (
|
|
|
108
155
|
);
|
|
109
156
|
`;
|
|
110
157
|
|
|
158
|
+
// Assignment-form pragmas return no rows, so they go through exec()/execute().
|
|
111
159
|
const PRAGMAS_SQL = `
|
|
112
|
-
PRAGMA journal_mode = WAL;
|
|
113
160
|
PRAGMA synchronous = NORMAL;
|
|
114
161
|
PRAGMA foreign_keys = OFF;
|
|
115
162
|
`;
|
|
@@ -119,6 +166,12 @@ PRAGMA foreign_keys = OFF;
|
|
|
119
166
|
* migrations can branch on it. Idempotent — safe to call on every open.
|
|
120
167
|
*/
|
|
121
168
|
export async function applySchema(db: SqliteDb, version: number = DEFAULT_DB_VERSION): Promise<void> {
|
|
169
|
+
// `PRAGMA journal_mode = WAL` returns the resulting mode as a row. The NS
|
|
170
|
+
// Android plugin maps exec()→execSQL, which rejects row-returning statements
|
|
171
|
+
// ("Queries can be performed using ... query or rawQuery methods only"), so
|
|
172
|
+
// this pragma must run through a query method. prepare().get() does that on
|
|
173
|
+
// every driver (NS plugin → get()/rawQuery; node:sqlite/better-sqlite3 → get()).
|
|
174
|
+
await db.prepare('PRAGMA journal_mode = WAL').get();
|
|
122
175
|
await db.exec(PRAGMAS_SQL);
|
|
123
176
|
await db.exec(SCHEMA_SQL);
|
|
124
177
|
await db.exec(`PRAGMA user_version = ${version}`);
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { OptimysticNSDBHandle, SqliteDb, SqliteStatement, SqliteParam, SqliteRow } from './db.js';
|
|
2
2
|
export { DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
|
|
3
3
|
export { openOptimysticNSDb, wrapNSPluginDb } from './ns-opener.js';
|
|
4
|
-
export { SqliteRawStorage } from './sqlite-storage.js';
|
|
4
|
+
export { SqliteRawStorage, SqliteStoreDriver } from './sqlite-storage.js';
|
|
5
5
|
export { SqliteKVStore } from './sqlite-kv-store.js';
|
|
6
6
|
export { loadOrCreateNSPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
|