@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
package/src/ns-opener.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConnectionMutex } from './connection-mutex.js';
|
|
2
|
+
import { applySchema, DEFAULT_DB_NAME, DEFAULT_DB_VERSION, type OptimysticNSDBHandle, type SqliteDb, type SqliteParam, type SqliteRow, type SqliteStatement, type SqliteTransaction } from './db.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Minimal subset of `@nativescript-community/sqlite`'s `Db` we depend on.
|
|
@@ -7,7 +8,7 @@ import { applySchema, DEFAULT_DB_NAME, DEFAULT_DB_VERSION, type OptimysticNSDBHa
|
|
|
7
8
|
* at typecheck time on non-NativeScript consumers).
|
|
8
9
|
*/
|
|
9
10
|
interface NSPluginDb {
|
|
10
|
-
|
|
11
|
+
execute(sql: string, params?: ReadonlyArray<unknown>): unknown;
|
|
11
12
|
get(sql: string, params?: ReadonlyArray<unknown>): Promise<Record<string, unknown> | null | undefined> | Record<string, unknown> | null | undefined;
|
|
12
13
|
select(sql: string, params?: ReadonlyArray<unknown>): Promise<Array<Record<string, unknown>>> | Array<Record<string, unknown>>;
|
|
13
14
|
close(): void | Promise<void>;
|
|
@@ -23,9 +24,13 @@ interface NSPluginModule {
|
|
|
23
24
|
* the migration to `version`.
|
|
24
25
|
*
|
|
25
26
|
* The returned `SqliteDb` handle is safe to share across `SqliteRawStorage`,
|
|
26
|
-
* `SqliteKVStore`, and `loadOrCreateNSPeerKey
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* `SqliteKVStore`, and `loadOrCreateNSPeerKey`. Because SQLite allows at most
|
|
28
|
+
* one open transaction per connection, the wrapper serializes every mutating
|
|
29
|
+
* operation — `exec`, statement `run`, and whole `transaction` bodies — through
|
|
30
|
+
* a per-connection FIFO mutex. Without it, two concurrent `transaction` bodies
|
|
31
|
+
* would each `BEGIN` on the shared connection; the second would nest and its
|
|
32
|
+
* rollback would silently discard the first's still-open writes. Reads
|
|
33
|
+
* (`get`/`all`) stay off the mutex to preserve read concurrency.
|
|
29
34
|
*
|
|
30
35
|
* `path` may be passed in as the full filesystem path if the caller wants
|
|
31
36
|
* to control file placement; otherwise the plugin's documents-directory
|
|
@@ -47,44 +52,63 @@ export async function openOptimysticNSDb(
|
|
|
47
52
|
* internal `SqliteDb` interface used by the storage classes. Exported only
|
|
48
53
|
* for callers that already hold an open NS-plugin handle and want to skip
|
|
49
54
|
* the opener (rare — typically users just call `openOptimysticNSDb`).
|
|
55
|
+
*
|
|
56
|
+
* NOTE: the serialization mutex lives on the wrapper, not the raw handle. Wrap
|
|
57
|
+
* a given raw connection exactly once — two wrappers over one handle each hold
|
|
58
|
+
* their own mutex and would not serialize against each other, reintroducing the
|
|
59
|
+
* cross-rollback this fix closes.
|
|
50
60
|
*/
|
|
51
61
|
export function wrapNSPluginDb(raw: NSPluginDb): SqliteDb {
|
|
52
62
|
return new NSPluginDbWrapper(raw);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
class NSPluginDbWrapper implements SqliteDb {
|
|
66
|
+
private readonly mutex = new ConnectionMutex();
|
|
67
|
+
|
|
56
68
|
constructor(private readonly raw: NSPluginDb) {}
|
|
57
69
|
|
|
58
70
|
async exec(sql: string): Promise<void> {
|
|
59
|
-
// The plugin's
|
|
71
|
+
// The plugin's execute accepts a single statement; split semicolon-
|
|
60
72
|
// separated DDL so callers can pass the full schema in one go.
|
|
61
73
|
const statements = sql
|
|
62
74
|
.split(';')
|
|
63
75
|
.map(s => s.trim())
|
|
64
76
|
.filter(s => s.length > 0);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
await this.mutex.serialize(async () => {
|
|
78
|
+
for (const statement of statements) {
|
|
79
|
+
await this.raw.execute(statement);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
prepare(sql: string): SqliteStatement {
|
|
71
|
-
|
|
85
|
+
// Outside-transaction statement: writes go through the mutex.
|
|
86
|
+
return new NSPluginStatement(this.raw, sql, this.mutex);
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
async transaction<T>(fn: () => Promise<T>): Promise<T> {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await this.raw.
|
|
79
|
-
return result;
|
|
80
|
-
} catch (err) {
|
|
89
|
+
async transaction<T>(fn: (tx: SqliteTransaction) => Promise<T>): Promise<T> {
|
|
90
|
+
return this.mutex.serialize(async () => {
|
|
91
|
+
// BEGIN IMMEDIATE takes the write lock up front rather than deferring
|
|
92
|
+
// it to the first write, so contention surfaces here and not mid-body.
|
|
93
|
+
await this.raw.execute('BEGIN IMMEDIATE');
|
|
81
94
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
// Statements bound to the open transaction bypass the mutex — we
|
|
96
|
+
// already hold the slot; re-locking would deadlock.
|
|
97
|
+
const tx: SqliteTransaction = {
|
|
98
|
+
prepare: (sql: string) => new NSPluginStatement(this.raw, sql),
|
|
99
|
+
};
|
|
100
|
+
const result = await fn(tx);
|
|
101
|
+
await this.raw.execute('COMMIT');
|
|
102
|
+
return result;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
try {
|
|
105
|
+
await this.raw.execute('ROLLBACK');
|
|
106
|
+
} catch {
|
|
107
|
+
// Swallow rollback failures so we surface the original error.
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
85
110
|
}
|
|
86
|
-
|
|
87
|
-
}
|
|
111
|
+
});
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
async close(): Promise<void> {
|
|
@@ -93,19 +117,42 @@ class NSPluginDbWrapper implements SqliteDb {
|
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
class NSPluginStatement implements SqliteStatement {
|
|
96
|
-
|
|
120
|
+
/**
|
|
121
|
+
* @param mutex When present, `run` is serialized on the connection mutex
|
|
122
|
+
* (outside-transaction writes). When absent, `run` executes directly on the
|
|
123
|
+
* raw connection — used for statements bound to an already-open transaction
|
|
124
|
+
* that already holds the mutex slot.
|
|
125
|
+
*/
|
|
126
|
+
constructor(
|
|
127
|
+
private readonly raw: NSPluginDb,
|
|
128
|
+
private readonly sql: string,
|
|
129
|
+
private readonly mutex?: ConnectionMutex,
|
|
130
|
+
) {}
|
|
97
131
|
|
|
98
132
|
async run(...params: SqliteParam[]): Promise<void> {
|
|
99
|
-
|
|
133
|
+
if (this.mutex) {
|
|
134
|
+
await this.mutex.serialize(() => this.raw.execute(this.sql, params));
|
|
135
|
+
} else {
|
|
136
|
+
await this.raw.execute(this.sql, params);
|
|
137
|
+
}
|
|
100
138
|
}
|
|
101
139
|
|
|
102
140
|
async get(...params: SqliteParam[]): Promise<SqliteRow | undefined> {
|
|
141
|
+
// NOTE: reads run directly on the connection, unserialized, to preserve read
|
|
142
|
+
// concurrency. A read issued while a write transaction is open on this same
|
|
143
|
+
// connection observes that transaction's UNCOMMITTED rows (read-your-connection
|
|
144
|
+
// semantics). Fine today: the only transaction writer is same-block promote
|
|
145
|
+
// under the commit latch, and cross-block reads are independent. If a future
|
|
146
|
+
// caller reads a block on this connection while another op's transaction on the
|
|
147
|
+
// same rows is mid-flight, it may see uncommitted state — serialize reads too if
|
|
148
|
+
// that ever matters.
|
|
103
149
|
const row = await this.raw.get(this.sql, params);
|
|
104
150
|
if (row === null || row === undefined) return undefined;
|
|
105
151
|
return row as SqliteRow;
|
|
106
152
|
}
|
|
107
153
|
|
|
108
154
|
async all(...params: SqliteParam[]): Promise<SqliteRow[]> {
|
|
155
|
+
// NOTE: unserialized read — see the note on `get` above re: uncommitted reads.
|
|
109
156
|
const rows = await this.raw.select(this.sql, params);
|
|
110
157
|
return rows as SqliteRow[];
|
|
111
158
|
}
|
package/src/sqlite-storage.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
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, SqliteStatement } from './db.js';
|
|
4
4
|
import { createLogger } from './logger.js';
|
|
5
5
|
|
|
6
6
|
const log = createLogger('storage:sqlite');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* SQLite
|
|
9
|
+
* SQLite {@link RawStoreDriver}: the five logical block-storage stores mapped to
|
|
10
|
+
* the five relational tables (`metadata`, `revisions`, `pending`, `transactions`,
|
|
11
|
+
* `materialized`) with their original columns and keys. This is a code refactor,
|
|
12
|
+
* not a storage-format change at the SQL level.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* `KvRawStorage` now owns all JSON serialization, so this driver only ever
|
|
15
|
+
* reads/writes `Uint8Array` values: the value columns are BLOB and SQLite binds
|
|
16
|
+
* a `Uint8Array` as a BLOB and returns it as a `Uint8Array`, so no codec lives
|
|
17
|
+
* here (a TEXT column would risk UTF-8 coercion corrupting non-ASCII JSON bytes).
|
|
18
|
+
* Everything SQLite-specific stays: each CRUD op goes through a prepared
|
|
19
|
+
* statement bound once in the constructor; range/list queries drain their rows
|
|
20
|
+
* (`.all(...)`) before yielding so no cursor straddles a consumer's `await`; and
|
|
21
|
+
* `promote` runs as a single `db.transaction(fn)` — `BEGIN IMMEDIATE; INSERT…;
|
|
22
|
+
* DELETE…; COMMIT;` — whose three statements are re-prepared against the OPEN
|
|
23
|
+
* transaction so they run on the held mutex slot without re-locking (which would
|
|
24
|
+
* deadlock — see `st-nativescript-sqlite-transaction-mutex`).
|
|
21
25
|
*/
|
|
22
|
-
export class
|
|
26
|
+
export class SqliteStoreDriver implements RawStoreDriver {
|
|
23
27
|
private readonly stmts: {
|
|
24
28
|
getMetadata: SqliteStatement;
|
|
25
29
|
saveMetadata: SqliteStatement;
|
|
@@ -31,6 +35,7 @@ export class SqliteRawStorage implements IRawStorage {
|
|
|
31
35
|
savePending: SqliteStatement;
|
|
32
36
|
deletePending: SqliteStatement;
|
|
33
37
|
listPending: SqliteStatement;
|
|
38
|
+
listBlockIds: SqliteStatement;
|
|
34
39
|
getTransaction: SqliteStatement;
|
|
35
40
|
saveTransaction: SqliteStatement;
|
|
36
41
|
getMaterialized: SqliteStatement;
|
|
@@ -52,6 +57,11 @@ export class SqliteRawStorage implements IRawStorage {
|
|
|
52
57
|
savePending: db.prepare('INSERT OR REPLACE INTO pending (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
53
58
|
deletePending: db.prepare('DELETE FROM pending WHERE block_id = ? AND action_id = ?'),
|
|
54
59
|
listPending: db.prepare('SELECT action_id FROM pending WHERE block_id = ? ORDER BY action_id ASC'),
|
|
60
|
+
// metadata.block_id is the PRIMARY KEY, so each row is a distinct block id —
|
|
61
|
+
// no dedup needed. NOTE: drains the whole metadata table up front; if a peer
|
|
62
|
+
// ever holds millions of blocks and this SELECT becomes a startup-latency
|
|
63
|
+
// problem, page it (LIMIT/OFFSET or keyset) — fine at current scale.
|
|
64
|
+
listBlockIds: db.prepare('SELECT block_id FROM metadata'),
|
|
55
65
|
getTransaction: db.prepare('SELECT value FROM transactions WHERE block_id = ? AND action_id = ?'),
|
|
56
66
|
saveTransaction: db.prepare('INSERT OR REPLACE INTO transactions (block_id, action_id, value) VALUES (?, ?, ?)'),
|
|
57
67
|
getMaterialized: db.prepare('SELECT value FROM materialized WHERE block_id = ? AND action_id = ?'),
|
|
@@ -62,82 +72,116 @@ export class SqliteRawStorage implements IRawStorage {
|
|
|
62
72
|
};
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
// --- metadata ---
|
|
76
|
+
|
|
77
|
+
async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
|
|
66
78
|
const row = await this.stmts.getMetadata.get(blockId);
|
|
67
|
-
|
|
68
|
-
return JSON.parse(row.value as string) as BlockMetadata;
|
|
79
|
+
return row ? (row.value as Uint8Array) : undefined;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
async
|
|
72
|
-
await this.stmts.saveMetadata.run(blockId,
|
|
82
|
+
async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
|
|
83
|
+
await this.stmts.saveMetadata.run(blockId, value);
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
|
|
86
|
+
// --- revisions ---
|
|
87
|
+
|
|
88
|
+
async getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
|
|
76
89
|
const row = await this.stmts.getRevision.get(blockId, rev);
|
|
77
|
-
return row ? (row.action_id as
|
|
90
|
+
return row ? (row.action_id as Uint8Array) : undefined;
|
|
78
91
|
}
|
|
79
92
|
|
|
80
|
-
async
|
|
81
|
-
await this.stmts.saveRevision.run(blockId, rev,
|
|
93
|
+
async putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
|
|
94
|
+
await this.stmts.saveRevision.run(blockId, rev, value);
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
async *
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const stmt = ascending ? this.stmts.listRevisionsAsc : this.stmts.listRevisionsDesc;
|
|
97
|
+
async *rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]> {
|
|
98
|
+
// `.all(...)` materializes every row before we yield, so no SQLite cursor
|
|
99
|
+
// straddles the consumer's awaits (the kernel's drain-before-yield contract).
|
|
100
|
+
const stmt = reverse ? this.stmts.listRevisionsDesc : this.stmts.listRevisionsAsc;
|
|
89
101
|
const rows = await stmt.all(blockId, lo, hi);
|
|
90
102
|
for (const row of rows) {
|
|
91
|
-
yield
|
|
103
|
+
yield [row.rev as number, row.action_id as Uint8Array];
|
|
92
104
|
}
|
|
93
105
|
}
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
// --- pending ---
|
|
108
|
+
|
|
109
|
+
async getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
96
110
|
const row = await this.stmts.getPending.get(blockId, actionId);
|
|
97
|
-
|
|
98
|
-
return JSON.parse(row.value as string) as Transform;
|
|
111
|
+
return row ? (row.value as Uint8Array) : undefined;
|
|
99
112
|
}
|
|
100
113
|
|
|
101
|
-
async
|
|
102
|
-
await this.stmts.savePending.run(blockId, actionId,
|
|
114
|
+
async putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
115
|
+
await this.stmts.savePending.run(blockId, actionId, value);
|
|
103
116
|
}
|
|
104
117
|
|
|
105
|
-
async
|
|
118
|
+
async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
106
119
|
await this.stmts.deletePending.run(blockId, actionId);
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
async *
|
|
122
|
+
async *listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId> {
|
|
123
|
+
// Drained by `.all(...)` before yielding — same rationale as rangeRevisions.
|
|
110
124
|
const rows = await this.stmts.listPending.all(blockId);
|
|
111
125
|
for (const row of rows) {
|
|
112
126
|
yield row.action_id as ActionId;
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
|
|
130
|
+
// --- transactions ---
|
|
131
|
+
|
|
132
|
+
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
117
133
|
const row = await this.stmts.getTransaction.get(blockId, actionId);
|
|
118
|
-
|
|
119
|
-
return JSON.parse(row.value as string) as Transform;
|
|
134
|
+
return row ? (row.value as Uint8Array) : undefined;
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
async
|
|
123
|
-
await this.stmts.saveTransaction.run(blockId, actionId,
|
|
137
|
+
async putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
138
|
+
await this.stmts.saveTransaction.run(blockId, actionId, value);
|
|
124
139
|
}
|
|
125
140
|
|
|
126
|
-
|
|
141
|
+
// --- materialized ---
|
|
142
|
+
|
|
143
|
+
async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
127
144
|
const row = await this.stmts.getMaterialized.get(blockId, actionId);
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
return row ? (row.value as Uint8Array) : undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
|
|
149
|
+
await this.stmts.saveMaterialized.run(blockId, actionId, value);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
153
|
+
await this.stmts.deleteMaterialized.run(blockId, actionId);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --- promote (the only cross-key atomic op) ---
|
|
157
|
+
|
|
158
|
+
async promote(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
159
|
+
await this.db.transaction(async (tx) => {
|
|
160
|
+
// Prepare against the open transaction so these three statements run on
|
|
161
|
+
// the held mutex slot without re-locking the connection (which would
|
|
162
|
+
// deadlock). Re-preparing is cheap — the driver caches by SQL text.
|
|
163
|
+
const getPending = tx.prepare('SELECT value FROM pending WHERE block_id = ? AND action_id = ?');
|
|
164
|
+
const saveTransaction = tx.prepare('INSERT OR REPLACE INTO transactions (block_id, action_id, value) VALUES (?, ?, ?)');
|
|
165
|
+
const deletePending = tx.prepare('DELETE FROM pending WHERE block_id = ? AND action_id = ?');
|
|
166
|
+
const row = await getPending.get(blockId, actionId);
|
|
167
|
+
if (!row) {
|
|
168
|
+
throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
|
|
169
|
+
}
|
|
170
|
+
await saveTransaction.run(blockId, actionId, row.value as Uint8Array);
|
|
171
|
+
await deletePending.run(blockId, actionId);
|
|
172
|
+
});
|
|
130
173
|
}
|
|
131
174
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
175
|
+
// --- optional passthroughs ---
|
|
176
|
+
|
|
177
|
+
async *listBlockIds(): AsyncIterable<BlockId> {
|
|
178
|
+
const rows = await this.stmts.listBlockIds.all();
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
yield row.block_id as BlockId;
|
|
137
181
|
}
|
|
138
182
|
}
|
|
139
183
|
|
|
140
|
-
async
|
|
184
|
+
async approximateBytesUsed(): Promise<number> {
|
|
141
185
|
try {
|
|
142
186
|
const pageCountRow = await this.stmts.pageCount.get();
|
|
143
187
|
const pageSizeRow = await this.stmts.pageSize.get();
|
|
@@ -149,15 +193,25 @@ export class SqliteRawStorage implements IRawStorage {
|
|
|
149
193
|
return 0;
|
|
150
194
|
}
|
|
151
195
|
}
|
|
196
|
+
}
|
|
152
197
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
/**
|
|
199
|
+
* SQLite-backed {@link IRawStorage} for NativeScript peers, now a thin shell over
|
|
200
|
+
* the shared {@link KvRawStorage} kernel driven by a {@link SqliteStoreDriver}.
|
|
201
|
+
* The public name/constructor (`new SqliteRawStorage(db)`) is unchanged so
|
|
202
|
+
* existing imports keep resolving; the kernel supplies the `IRawStorage` surface
|
|
203
|
+
* and the driver supplies SQLite behavior.
|
|
204
|
+
*
|
|
205
|
+
* `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
|
|
206
|
+
* (the SQLite driver always implements them, so the kernel constructor always
|
|
207
|
+
* wires them) — the base declares them optional, but every NS consumer relies on
|
|
208
|
+
* them.
|
|
209
|
+
*/
|
|
210
|
+
export class SqliteRawStorage extends KvRawStorage {
|
|
211
|
+
declare listBlockIds: () => AsyncIterable<BlockId>;
|
|
212
|
+
declare getApproximateBytesUsed: () => Promise<number>;
|
|
213
|
+
|
|
214
|
+
constructor(db: SqliteDb) {
|
|
215
|
+
super(new SqliteStoreDriver(db));
|
|
162
216
|
}
|
|
163
217
|
}
|