@optimystic/db-p2p-storage-ns 0.14.1 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,38 +1,61 @@
1
- import type { ActionId, ActionRev, BlockId, IBlock, Transform } from '@optimystic/db-core';
2
- import type { BlockMetadata, IRawStorage } from '@optimystic/db-p2p';
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-backed `IRawStorage` implementation for NativeScript peers.
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
- * Every CRUD operation goes through a prepared statement bound once in the
8
- * constructor. `listRevisions` and `listPendingTransactions` issue
9
- * `SELECT … ORDER BY` queries bounded by `block_id` (and `rev` for revisions)
10
- * and drain the results into an array before yielding — matching the
11
- * `IndexedDBRawStorage` pattern, where holding a cursor live across consumer
12
- * awaits would invalidate the underlying transaction.
13
- *
14
- * `promotePendingTransaction` runs as a single `BEGIN; INSERT…; DELETE…; COMMIT;`
15
- * via `SqliteDb.transaction(fn)`, so the move is atomic across crashes —
16
- * unlike the MMKV adapter, which has to maintain a separate pending-index row.
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 SqliteRawStorage implements IRawStorage {
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<BlockMetadata | undefined>;
23
- saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void>;
24
- getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined>;
25
- saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void>;
26
- listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev>;
27
- getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
28
- savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
29
- deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
30
- listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId>;
31
- getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
32
- saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
33
- getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined>;
34
- saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void>;
35
- getApproximateBytesUsed(): Promise<number>;
36
- promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
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,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAKzD;;;;;;;;;;;;;GAaG;AACH,qBAAa,gBAAiB,YAAW,WAAW;IAqBvC,OAAO,CAAC,QAAQ,CAAC,EAAE;IApB/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAkBpB;gBAE2B,EAAE,EAAE,QAAQ;IAsBnC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAMjE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAKzE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;IAW5F,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAM3F,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjG,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IAOnE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAMpF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAMvF,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1F,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAa1C,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAUpF"}
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-backed `IRawStorage` implementation for NativeScript peers.
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
- * Every CRUD operation goes through a prepared statement bound once in the
7
- * constructor. `listRevisions` and `listPendingTransactions` issue
8
- * `SELECT … ORDER BY` queries bounded by `block_id` (and `rev` for revisions)
9
- * and drain the results into an array before yielding — matching the
10
- * `IndexedDBRawStorage` pattern, where holding a cursor live across consumer
11
- * awaits would invalidate the underlying transaction.
12
- *
13
- * `promotePendingTransaction` runs as a single `BEGIN; INSERT…; DELETE…; COMMIT;`
14
- * via `SqliteDb.transaction(fn)`, so the move is atomic across crashes —
15
- * unlike the MMKV adapter, which has to maintain a separate pending-index row.
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 SqliteRawStorage {
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
- if (!row)
45
- return undefined;
46
- return JSON.parse(row.value);
55
+ return row ? row.value : undefined;
47
56
  }
48
- async saveMetadata(blockId, metadata) {
49
- await this.stmts.saveMetadata.run(blockId, JSON.stringify(metadata));
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 saveRevision(blockId, rev, actionId) {
56
- await this.stmts.saveRevision.run(blockId, rev, actionId);
65
+ async putRevision(blockId, rev, value) {
66
+ await this.stmts.saveRevision.run(blockId, rev, value);
57
67
  }
58
- async *listRevisions(blockId, startRev, endRev) {
59
- const ascending = startRev <= endRev;
60
- const lo = ascending ? startRev : endRev;
61
- const hi = ascending ? endRev : startRev;
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 { rev: row.rev, actionId: row.action_id };
74
+ yield [row.rev, row.action_id];
66
75
  }
67
76
  }
68
- async getPendingTransaction(blockId, actionId) {
77
+ // --- pending ---
78
+ async getPending(blockId, actionId) {
69
79
  const row = await this.stmts.getPending.get(blockId, actionId);
70
- if (!row)
71
- return undefined;
72
- return JSON.parse(row.value);
80
+ return row ? row.value : undefined;
73
81
  }
74
- async savePendingTransaction(blockId, actionId, transform) {
75
- await this.stmts.savePending.run(blockId, actionId, JSON.stringify(transform));
82
+ async putPending(blockId, actionId, value) {
83
+ await this.stmts.savePending.run(blockId, actionId, value);
76
84
  }
77
- async deletePendingTransaction(blockId, actionId) {
85
+ async deletePending(blockId, actionId) {
78
86
  await this.stmts.deletePending.run(blockId, actionId);
79
87
  }
80
- async *listPendingTransactions(blockId) {
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
- if (!row)
89
- return undefined;
90
- return JSON.parse(row.value);
98
+ return row ? row.value : undefined;
91
99
  }
92
- async saveTransaction(blockId, actionId, transform) {
93
- await this.stmts.saveTransaction.run(blockId, actionId, JSON.stringify(transform));
100
+ async putTransaction(blockId, actionId, value) {
101
+ await this.stmts.saveTransaction.run(blockId, actionId, value);
94
102
  }
95
- async getMaterializedBlock(blockId, actionId) {
103
+ // --- materialized ---
104
+ async getMaterialized(blockId, actionId) {
96
105
  const row = await this.stmts.getMaterialized.get(blockId, actionId);
97
- if (!row)
98
- return undefined;
99
- return JSON.parse(row.value);
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
- async saveMaterializedBlock(blockId, actionId, block) {
102
- if (block) {
103
- await this.stmts.saveMaterialized.run(blockId, actionId, JSON.stringify(block));
104
- }
105
- else {
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 getApproximateBytesUsed() {
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
- async promotePendingTransaction(blockId, actionId) {
123
- await this.db.transaction(async () => {
124
- const row = await this.stmts.getPending.get(blockId, actionId);
125
- if (!row) {
126
- throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
127
- }
128
- await this.stmts.saveTransaction.run(blockId, actionId, row.value);
129
- await this.stmts.deletePending.run(blockId, actionId);
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":"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"}
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.14.1",
3
+ "version": "0.16.2",
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.14.1",
57
- "@optimystic/db-p2p": "^0.14.1",
56
+ "@optimystic/db-core": "^0.16.2",
57
+ "@optimystic/db-p2p": "^0.16.2",
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 `execSQL`. The storage classes prepare each query once per instance.
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
- /** Run `fn` inside `BEGIN ... COMMIT` (rolls back on throw). */
49
- transaction<T>(fn: () => Promise<T>): Promise<T>;
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
- * - `metadata` → per-block `BlockMetadata` (JSON).
62
- * - `revisions` → revision lookup keyed by `(block_id, rev)`.
63
- * - `pending` → uncommitted transforms keyed by `(block_id, action_id)`.
64
- * - `transactions` → committed transforms keyed by `(block_id, action_id)`.
65
- * - `materialized` → materialized blocks keyed by `(block_id, action_id)`.
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 avoid a base64 round-trip
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 TEXT NOT NULL
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 TEXT NOT NULL,
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 TEXT NOT NULL,
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 TEXT NOT NULL,
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 TEXT NOT NULL,
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';