@optimystic/db-p2p-storage-ns 1.15.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 CHANGED
@@ -6,9 +6,10 @@ Android, native — not React Native). Provides:
6
6
  - **`SqliteRawStorage`** — the `IRawStorage` a NativeScript node uses to
7
7
  persist block metadata, revisions, pending transactions, committed
8
8
  transactions, commit proofs, and materialized blocks across app restarts.
9
- It is a thin shell over the shared `KvRawStorage` kernel driven by
10
- `SqliteStoreDriver`: the kernel owns all JSON serialization, the driver
11
- maps the six logical stores onto the six SQLite tables.
9
+ It is the shared `KvRawStorage` kernel over a `WriteGroupDriver` over
10
+ `SqliteBatchBackend`: the kernel owns all JSON serialization, the driver
11
+ holds each group of writes in memory until it is flushed, and the backend
12
+ applies the group as one SQLite transaction over the six tables.
12
13
  - **`SqliteKVStore`** — implements `IKVStore` for the persistent transaction
13
14
  state used to recover crashed two-phase commits.
14
15
  - **`loadOrCreateNSPeerKey`** — generates an Ed25519 libp2p private key on
@@ -72,27 +73,40 @@ would each `BEGIN` on the shared connection; the second nests, throws, and its
72
73
  rollback discards the first's still-open writes. Reads (`get`/`all`) stay off
73
74
  the mutex to preserve read concurrency.
74
75
 
76
+ Close the storage before the handle, and the handle last:
77
+
78
+ ```ts
79
+ await rawStorage.close(); // flushes what it still holds, syncs it, disarms its timers
80
+ await kvStore.flush();
81
+ await db.close();
82
+ ```
83
+
84
+ `SqliteRawStorage.close()` does not close the shared handle. A write the
85
+ storage still holds when the handle is closed under it is lost, and a timer it
86
+ still has armed would fire on a closed connection.
87
+
75
88
  ## Persistence model
76
89
 
77
90
  The package opens a single SQLite database (`optimystic.sqlite` by default
78
- in the NativeScript app's documents directory) with seven tables. The six
91
+ in the NativeScript app's documents directory) with eight tables. The six
79
92
  block-storage tables keep their original columns and keys, but their value
80
- columns are now **BLOB**: `SqliteStoreDriver` binds/reads the kernel's raw
93
+ columns are **BLOB**: `SqliteBatchBackend` binds/reads the kernel's raw
81
94
  `Uint8Array` bytes and the shared `KvRawStorage` kernel owns the JSON/UTF-8
82
95
  codec. SQLite stores a `Uint8Array` as a BLOB and returns it as a `Uint8Array`,
83
96
  so bytes round-trip exactly (a TEXT column would risk UTF-8-coercing non-ASCII
84
97
  JSON bytes). The "Decoded" column is the logical type the kernel decodes back
85
98
  into; keys stay TEXT/INTEGER.
86
99
 
87
- | Table | Key | Stored value | Decoded (logical) type |
88
- |----------------|----------------------------------|--------------|------------------------|
89
- | `metadata` | `block_id` | `BLOB` | `BlockMetadata` |
90
- | `revisions` | `(block_id, rev)` | `BLOB` (`action_id` col) | `ActionId` |
91
- | `pending` | `(block_id, action_id)` | `BLOB` | `Transform` |
92
- | `transactions` | `(block_id, action_id)` | `BLOB` | `Transform` |
93
- | `proofs` | `(block_id, rev)` | `BLOB` | `BlockCommitProof` |
94
- | `materialized` | `(block_id, action_id)` | `BLOB` | `IBlock` |
95
- | `kv` | `key` | `s_val` (TEXT) or `b_val` (BLOB) | — (not kernel-backed) |
100
+ | Table | Key | Stored value | Decoded (logical) type |
101
+ |-------------------|----------------------------------|--------------|------------------------|
102
+ | `metadata` | `block_id` | `BLOB` | `BlockMetadata` |
103
+ | `revisions` | `(block_id, rev)` | `BLOB` (`action_id` col) | `ActionId` |
104
+ | `pending` | `(block_id, action_id)` | `BLOB` | `Transform` |
105
+ | `transactions` | `(block_id, action_id)` | `BLOB` | `Transform` |
106
+ | `proofs` | `(block_id, rev)` | `BLOB` | `BlockCommitProof` |
107
+ | `materialized` | `(block_id, action_id)` | `BLOB` | `IBlock` |
108
+ | `kv` | `key` | `s_val` (TEXT) or `b_val` (BLOB) | — (not kernel-backed) |
109
+ | `durability_mark` | `id` (always 1) | `n` (INTEGER), a counter nothing reads | — (the row a sync commits when it has nothing else to write) |
96
110
 
97
111
  Pragmas: `journal_mode = WAL`, `synchronous = NORMAL`, `foreign_keys = OFF`.
98
112
  Schema is versioned via `PRAGMA user_version`.
@@ -100,21 +114,64 @@ Schema is versioned via `PRAGMA user_version`.
100
114
  `listRevisions` and `listPendingTransactions` issue bounded
101
115
  `SELECT … ORDER BY` queries — never `SELECT * FROM <table>` + JS filter —
102
116
  so list latency stays bounded as the tables grow.
103
- `promotePendingTransaction` runs as a single `BEGIN; INSERT…; DELETE…; COMMIT;`
104
- inside `SqliteDb.transaction(fn)`, so the move is atomic across crashes —
105
- unlike the MMKV adapter, which has to maintain a separate pending-index row.
117
+ `promotePendingTransaction` records two entries of one write group — the
118
+ committed row's insert and the pending row's delete — applied in that group's
119
+ transaction, so the move is atomic across crashes.
106
120
 
107
121
  `getApproximateBytesUsed()` returns `page_count × page_size` from PRAGMAs.
108
122
  This is the SQLite database-file footprint; it is **not** a per-block figure.
109
123
  That's adequate for `StorageMonitor` — which uses the value as an advisory
110
124
  ring-selection input.
111
125
 
126
+ ## Durability
127
+
128
+ `SqliteRawStorage` reports `durability: 'power-loss'`. A write resolves into
129
+ memory and is served from there; `flush()` applies every write made since the
130
+ last flush as **one SQLite transaction**, so a group lands whole or not at all.
131
+ In write-ahead-log mode a commit appends the group's frames to one append-only
132
+ log file, each frame checksummed over the frames before it, and recovery stops
133
+ at the first bad frame, so what a power cut leaves is a prefix of the committed
134
+ groups, in order. Under `synchronous = NORMAL` a commit does not wait for the
135
+ disk; a commit under `FULL` syncs the log, and with it every earlier commit on
136
+ the database. So:
137
+
138
+ - a strict `flush()` (no `withinMs`, or 0) commits the group under `FULL` and
139
+ resolves once it is on disk;
140
+ - a relaxed `flush({ withinMs })` — what a node uses for a write its cohort
141
+ also holds, see [storage.md §How long a write may wait for its sync](../db-p2p/docs/storage.md#how-long-a-write-may-wait-for-its-sync) —
142
+ commits under `NORMAL` and resolves at once; one `FULL` commit by the earliest
143
+ deadline, or a strict flush before then, syncs every group committed since the
144
+ last sync. `whenDurable()` resolves once the groups flushed so far are synced.
145
+
146
+ What a crash loses:
147
+
148
+ | Crash | Lost |
149
+ |---|---|
150
+ | the app's process | a group not yet flushed, still in memory; nothing a flush has resolved for |
151
+ | a power cut or operating-system crash | at most the groups still inside their window, always the most recent ones, each whole; nothing a strict flush has resolved for, and nothing once the window has passed |
152
+
153
+ `SqliteDb.transaction(fn, { synced: true })` is the primitive: inside the
154
+ connection mutex slot it sets `PRAGMA synchronous = FULL`, runs
155
+ `BEGIN IMMEDIATE … COMMIT`, and restores `NORMAL` afterwards, after a rollback
156
+ too. `syncSqliteDb(db)` is a synced transaction that bumps the one-row
157
+ `durability_mark` table, so a sync with no rows of its own still has a frame to
158
+ commit; `SqliteKVStore.flush()` runs it when the store wrote since its last
159
+ flush, and `loadOrCreateNSPeerKey` runs it after writing a newly generated key,
160
+ so a power cut right after first start cannot give the node a different
161
+ identity next time.
162
+
163
+ A failed commit or sync latches the storage: every later write, flush and
164
+ `whenDurable()` rejects with `StoreFailedError` until the app reopens the store
165
+ (rule 5 of the flush contract,
166
+ [storage.md §6](../db-p2p/docs/storage.md#6-a-write-survives-a-crash-once-a-flush-called-after-it-has-resolved)).
167
+
112
168
  ## Identity
113
169
 
114
170
  `loadOrCreateNSPeerKey(db, keyName?)` writes the libp2p private key as raw
115
171
  protobuf bytes (`Uint8Array`) into the `kv` table's `b_val` column under
116
- `keyName` (default `peer-private-key`). SQLite stores BLOBs natively — no
117
- base64 round-trip. To rotate the identity, delete that one row:
172
+ `keyName` (default `peer-private-key`), synced before it returns. SQLite stores
173
+ BLOBs natively — no base64 round-trip. To rotate the identity, delete that one
174
+ row:
118
175
 
119
176
  ```ts
120
177
  await db.prepare('DELETE FROM kv WHERE key = ?').run('peer-private-key');
@@ -130,4 +187,5 @@ underlying SQLite file.
130
187
  the package-private `SqliteDb` interface, so the Node driver is a faithful
131
188
  behavioural surrogate for the NativeScript plugin. The suite never imports
132
189
  `@nativescript-community/sqlite`, so the plugin's native bindings don't need
133
- to be installed to run tests.
190
+ to be installed to run tests. No test cuts the power: that a committed group
191
+ survives one rests on SQLite's write-ahead log, as stated above.
package/dist/src/db.d.ts CHANGED
@@ -44,6 +44,17 @@ export interface SqliteTransaction {
44
44
  /** Prepare a statement bound to the open transaction; bypasses the mutex. */
45
45
  prepare(sql: string): SqliteStatement;
46
46
  }
47
+ /** Options of {@link SqliteDb.transaction}. */
48
+ export interface SqliteTransactionOptions {
49
+ /**
50
+ * Commit under `PRAGMA synchronous = FULL`, which in write-ahead-log mode syncs the log file at
51
+ * this commit. The log is one append-only file, so that sync covers every frame before it: every
52
+ * earlier `NORMAL` commit on this database, whichever store or helper made it. The pragma is set
53
+ * inside the mutex slot the transaction holds and restored to `NORMAL` after the commit, or after
54
+ * the rollback, so no other write on the connection pays for the sync by accident. Default false.
55
+ */
56
+ readonly synced?: boolean;
57
+ }
47
58
  /**
48
59
  * Minimal SQLite driver surface used by this package.
49
60
  *
@@ -65,6 +76,10 @@ export interface SqliteDb {
65
76
  * Run `fn` inside `BEGIN IMMEDIATE ... COMMIT` (rolls back on throw), holding
66
77
  * the connection mutex for the whole body. `fn` receives a `SqliteTransaction`
67
78
  * whose statements run directly on the open transaction without re-locking.
79
+ * With `options.synced` the commit also syncs the write-ahead log, which makes
80
+ * this transaction and every earlier commit on the database survive a power
81
+ * cut (see {@link SqliteTransactionOptions.synced}); without it the commit
82
+ * survives a crash of this process only, until a later synced commit.
68
83
  *
69
84
  * NOTE: `fn` must issue its writes through the provided `tx`, never through a
70
85
  * class-level statement from `db.prepare(...)` (those re-acquire the mutex and
@@ -72,10 +87,27 @@ export interface SqliteDb {
72
87
  * `db.transaction(...)` again — nested calls deadlock for the same reason. No
73
88
  * current caller does either; guard here if that ever changes.
74
89
  */
75
- transaction<T>(fn: (tx: SqliteTransaction) => Promise<T>): Promise<T>;
90
+ transaction<T>(fn: (tx: SqliteTransaction) => Promise<T>, options?: SqliteTransactionOptions): Promise<T>;
76
91
  /** Release the underlying handle. */
77
92
  close(): Promise<void>;
78
93
  }
94
+ /** The raw connection surface {@link runTransaction} drives: one statement at a time, no rows back. */
95
+ export interface TransactionConnection {
96
+ execute(sql: string): Promise<void> | void;
97
+ }
98
+ /**
99
+ * The transaction protocol both `SqliteDb` implementations run inside their mutex slot: with
100
+ * `synced`, `PRAGMA synchronous = FULL` first; then `BEGIN IMMEDIATE`, the body, and `COMMIT`, or
101
+ * `ROLLBACK` when the body or the commit throws; then, synced, `PRAGMA synchronous = NORMAL`
102
+ * whichever way the transaction ended, so the connection's default is restored before the slot is
103
+ * released. `BEGIN IMMEDIATE` takes the write lock up front rather than deferring it to the first
104
+ * write, so contention surfaces here and not mid-body.
105
+ *
106
+ * A failure of the restore after a failed body is logged rather than thrown, so the body's error,
107
+ * the one that matters, is the one the caller sees; after a committed body it is thrown, since the
108
+ * connection would otherwise sync every later commit unasked.
109
+ */
110
+ export declare function runTransaction<T>(conn: TransactionConnection, tx: SqliteTransaction, fn: (tx: SqliteTransaction) => Promise<T>, options?: SqliteTransactionOptions): Promise<T>;
79
111
  export declare const DEFAULT_DB_NAME = "optimystic.sqlite";
80
112
  export declare const DEFAULT_DB_VERSION = 1;
81
113
  /**
@@ -83,11 +115,11 @@ export declare const DEFAULT_DB_VERSION = 1;
83
115
  *
84
116
  * Mirrors the IndexedDB object stores 1:1. The six block-storage stores keep
85
117
  * their original columns and keys, but their value columns are now BLOB: the
86
- * shared `KvRawStorage` kernel (driven by `SqliteStoreDriver`) owns all JSON /
87
- * UTF-8 (de)serialization, so the driver only ever binds/reads the kernel's raw
88
- * `Uint8Array` values. SQLite stores a `Uint8Array` as a BLOB and returns it as
89
- * a `Uint8Array`, so bytes round-trip exactly (a TEXT column would risk UTF-8
90
- * coercion corrupting non-ASCII JSON bytes). Keys stay TEXT/INTEGER.
118
+ * shared `KvRawStorage` kernel (over `WriteGroupDriver` over `SqliteBatchBackend`)
119
+ * owns all JSON / UTF-8 (de)serialization, so the backend only ever binds/reads
120
+ * the kernel's raw `Uint8Array` values. SQLite stores a `Uint8Array` as a BLOB
121
+ * and returns it as a `Uint8Array`, so bytes round-trip exactly (a TEXT column
122
+ * would risk UTF-8 coercion corrupting non-ASCII JSON bytes). Keys stay TEXT/INTEGER.
91
123
  * - `metadata` → per-block `BlockMetadata` bytes keyed by `block_id`.
92
124
  * - `revisions` → revision lookup keyed by `(block_id, rev)`; `action_id` column
93
125
  * holds the kernel's encoded `ActionId` bytes (BLOB).
@@ -100,6 +132,12 @@ export declare const DEFAULT_DB_VERSION = 1;
100
132
  * - `kv` → generic string keyspace shared by `IKVStore` (`s_val`) and the
101
133
  * identity helper (`b_val`). NOT kernel-backed; unchanged. The two columns
102
134
  * avoid a base64 round-trip for the libp2p private key.
135
+ * - `durability_mark` → one row, bumped by `syncSqliteDb` (`sqlite-sync.ts`) so a
136
+ * sync that has no rows of its own to write still has a frame to commit.
137
+ *
138
+ * NOTE: this package's SQL is uppercase throughout, predating the lowercase rule in
139
+ * AGENTS.md, and new statements have kept to it rather than mix cases within one file;
140
+ * convert the package in one pass rather than statement by statement.
103
141
  *
104
142
  * NOTE: value columns changed TEXT→BLOB with the kernel refactor, but every
105
143
  * statement is `CREATE TABLE IF NOT EXISTS` — so a database file created by a
@@ -110,7 +148,7 @@ export declare const DEFAULT_DB_VERSION = 1;
110
148
  * devices that later upgrade, add a `user_version` bump that drops+recreates
111
149
  * these six tables (or a real migration) before assuming BLOB rows.
112
150
  */
113
- export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS metadata (\n\tblock_id TEXT PRIMARY KEY,\n\tvalue BLOB NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS revisions (\n\tblock_id TEXT NOT NULL,\n\trev INTEGER NOT NULL,\n\taction_id BLOB NOT NULL,\n\tPRIMARY KEY (block_id, rev)\n);\n\nCREATE TABLE IF NOT EXISTS pending (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS transactions (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS proofs (\n\tblock_id TEXT NOT NULL,\n\trev INTEGER NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, rev)\n);\n\nCREATE TABLE IF NOT EXISTS materialized (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS kv (\n\tkey TEXT PRIMARY KEY,\n\ts_val TEXT,\n\tb_val BLOB\n);\n";
151
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS metadata (\n\tblock_id TEXT PRIMARY KEY,\n\tvalue BLOB NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS revisions (\n\tblock_id TEXT NOT NULL,\n\trev INTEGER NOT NULL,\n\taction_id BLOB NOT NULL,\n\tPRIMARY KEY (block_id, rev)\n);\n\nCREATE TABLE IF NOT EXISTS pending (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS transactions (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS proofs (\n\tblock_id TEXT NOT NULL,\n\trev INTEGER NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, rev)\n);\n\nCREATE TABLE IF NOT EXISTS materialized (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue BLOB NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS kv (\n\tkey TEXT PRIMARY KEY,\n\ts_val TEXT,\n\tb_val BLOB\n);\n\nCREATE TABLE IF NOT EXISTS durability_mark (\n\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\tn INTEGER NOT NULL\n);\n";
114
152
  /**
115
153
  * Apply pragmas, run the schema DDL, and stamp `user_version` so future
116
154
  * migrations can branch on it. Idempotent — safe to call on every open.
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;AAE9D,oEAAoE;AACpE,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,0EAA0E;IAC1E,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,gEAAgE;IAChE,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAC9D,oFAAoF;IACpF,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACpD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IACjC,6EAA6E;IAC7E,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,QAAQ;IACxB,yFAAyF;IACzF,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,2FAA2F;IAC3F,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtE,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,sBAAsB,CAAC;AACnD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,UAAU,mhCA8CtB,CAAC;AAQF;;;GAGG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE,MAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnG;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;AAE9D,oEAAoE;AACpE,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,0EAA0E;IAC1E,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,gEAAgE;IAChE,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAC9D,oFAAoF;IACpF,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACpD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IACjC,6EAA6E;IAC7E,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;CACtC;AAED,+CAA+C;AAC/C,MAAM,WAAW,wBAAwB;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,QAAQ;IACxB,yFAAyF;IACzF,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,2FAA2F;IAC3F,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1G,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,uGAAuG;AACvG,MAAM,WAAW,qBAAqB;IACrC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACrC,IAAI,EAAE,qBAAqB,EAC3B,EAAE,EAAE,iBAAiB,EACrB,EAAE,EAAE,CAAC,EAAE,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,OAAO,CAAC,EAAE,wBAAwB,GAChC,OAAO,CAAC,CAAC,CAAC,CAkBZ;AAmBD,eAAO,MAAM,eAAe,sBAAsB,CAAC;AACnD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,UAAU,woCAmDtB,CAAC;AAQF;;;GAGG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE,MAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnG;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC"}
package/dist/src/db.js CHANGED
@@ -10,6 +10,61 @@
10
10
  * The wrapper is **package-private**: only `openOptimysticNSDb` is exported to
11
11
  * consumers; the `SqliteDb` interface is an internal seam for tests.
12
12
  */
13
+ import { createLogger } from './logger.js';
14
+ const log = createLogger('db');
15
+ /**
16
+ * The transaction protocol both `SqliteDb` implementations run inside their mutex slot: with
17
+ * `synced`, `PRAGMA synchronous = FULL` first; then `BEGIN IMMEDIATE`, the body, and `COMMIT`, or
18
+ * `ROLLBACK` when the body or the commit throws; then, synced, `PRAGMA synchronous = NORMAL`
19
+ * whichever way the transaction ended, so the connection's default is restored before the slot is
20
+ * released. `BEGIN IMMEDIATE` takes the write lock up front rather than deferring it to the first
21
+ * write, so contention surfaces here and not mid-body.
22
+ *
23
+ * A failure of the restore after a failed body is logged rather than thrown, so the body's error,
24
+ * the one that matters, is the one the caller sees; after a committed body it is thrown, since the
25
+ * connection would otherwise sync every later commit unasked.
26
+ */
27
+ export async function runTransaction(conn, tx, fn, options) {
28
+ const synced = options?.synced === true;
29
+ if (synced)
30
+ await conn.execute('PRAGMA synchronous = FULL');
31
+ let result;
32
+ try {
33
+ result = await commitOrRollBack(conn, tx, fn);
34
+ }
35
+ catch (err) {
36
+ if (synced) {
37
+ try {
38
+ await conn.execute('PRAGMA synchronous = NORMAL');
39
+ }
40
+ catch (restoreErr) {
41
+ log('restoring synchronous = NORMAL failed after a failed transaction: %o', restoreErr);
42
+ }
43
+ }
44
+ throw err;
45
+ }
46
+ if (synced)
47
+ await conn.execute('PRAGMA synchronous = NORMAL');
48
+ return result;
49
+ }
50
+ async function commitOrRollBack(conn, tx, fn) {
51
+ await conn.execute('BEGIN IMMEDIATE');
52
+ try {
53
+ const result = await fn(tx);
54
+ await conn.execute('COMMIT');
55
+ return result;
56
+ }
57
+ catch (err) {
58
+ try {
59
+ await conn.execute('ROLLBACK');
60
+ }
61
+ catch (rollbackErr) {
62
+ // The original error is the one to surface; the rollback's is only logged.
63
+ log('rollback failed after a failed transaction: %o', rollbackErr);
64
+ }
65
+ throw err;
66
+ }
67
+ }
13
68
  export const DEFAULT_DB_NAME = 'optimystic.sqlite';
14
69
  export const DEFAULT_DB_VERSION = 1;
15
70
  /**
@@ -17,11 +72,11 @@ export const DEFAULT_DB_VERSION = 1;
17
72
  *
18
73
  * Mirrors the IndexedDB object stores 1:1. The six block-storage stores keep
19
74
  * their original columns and keys, but their value columns are now BLOB: the
20
- * shared `KvRawStorage` kernel (driven by `SqliteStoreDriver`) owns all JSON /
21
- * UTF-8 (de)serialization, so the driver only ever binds/reads the kernel's raw
22
- * `Uint8Array` values. SQLite stores a `Uint8Array` as a BLOB and returns it as
23
- * a `Uint8Array`, so bytes round-trip exactly (a TEXT column would risk UTF-8
24
- * coercion corrupting non-ASCII JSON bytes). Keys stay TEXT/INTEGER.
75
+ * shared `KvRawStorage` kernel (over `WriteGroupDriver` over `SqliteBatchBackend`)
76
+ * owns all JSON / UTF-8 (de)serialization, so the backend only ever binds/reads
77
+ * the kernel's raw `Uint8Array` values. SQLite stores a `Uint8Array` as a BLOB
78
+ * and returns it as a `Uint8Array`, so bytes round-trip exactly (a TEXT column
79
+ * would risk UTF-8 coercion corrupting non-ASCII JSON bytes). Keys stay TEXT/INTEGER.
25
80
  * - `metadata` → per-block `BlockMetadata` bytes keyed by `block_id`.
26
81
  * - `revisions` → revision lookup keyed by `(block_id, rev)`; `action_id` column
27
82
  * holds the kernel's encoded `ActionId` bytes (BLOB).
@@ -34,6 +89,12 @@ export const DEFAULT_DB_VERSION = 1;
34
89
  * - `kv` → generic string keyspace shared by `IKVStore` (`s_val`) and the
35
90
  * identity helper (`b_val`). NOT kernel-backed; unchanged. The two columns
36
91
  * avoid a base64 round-trip for the libp2p private key.
92
+ * - `durability_mark` → one row, bumped by `syncSqliteDb` (`sqlite-sync.ts`) so a
93
+ * sync that has no rows of its own to write still has a frame to commit.
94
+ *
95
+ * NOTE: this package's SQL is uppercase throughout, predating the lowercase rule in
96
+ * AGENTS.md, and new statements have kept to it rather than mix cases within one file;
97
+ * convert the package in one pass rather than statement by statement.
37
98
  *
38
99
  * NOTE: value columns changed TEXT→BLOB with the kernel refactor, but every
39
100
  * statement is `CREATE TABLE IF NOT EXISTS` — so a database file created by a
@@ -90,6 +151,11 @@ CREATE TABLE IF NOT EXISTS kv (
90
151
  s_val TEXT,
91
152
  b_val BLOB
92
153
  );
154
+
155
+ CREATE TABLE IF NOT EXISTS durability_mark (
156
+ id INTEGER PRIMARY KEY CHECK (id = 1),
157
+ n INTEGER NOT NULL
158
+ );
93
159
  `;
94
160
  // Assignment-form pragmas return no rows, so they go through exec()/execute().
95
161
  const PRAGMAS_SQL = `
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAyEH,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CzB,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,GAAG;;;CAGnB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAY,EAAE,UAAkB,kBAAkB;IACnF,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,iFAAiF;IACjF,MAAM,EAAE,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC,GAAG,EAAE,CAAC;IACpD,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3B,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1B,MAAM,EAAE,CAAC,IAAI,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;AA8F/B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,IAA2B,EAC3B,EAAqB,EACrB,EAAyC,EACzC,OAAkC;IAElC,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,IAAI,MAAM;QAAE,MAAM,IAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC5D,IAAI,MAAS,CAAC;IACd,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC;gBACJ,MAAM,IAAI,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;YACnD,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBACrB,GAAG,CAAC,sEAAsE,EAAE,UAAU,CAAC,CAAC;YACzF,CAAC;QACF,CAAC;QACD,MAAM,GAAG,CAAC;IACX,CAAC;IACD,IAAI,MAAM;QAAE,MAAM,IAAI,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC;AACf,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAI,IAA2B,EAAE,EAAqB,EAAE,EAAyC;IAC/H,MAAM,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACtC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,WAAW,EAAE,CAAC;YACtB,2EAA2E;YAC3E,GAAG,CAAC,gDAAgD,EAAE,WAAW,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,GAAG,CAAC;IACX,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDzB,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,GAAG;;;CAGnB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAY,EAAE,UAAkB,kBAAkB;IACnF,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,iFAAiF;IACjF,MAAM,EAAE,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC,GAAG,EAAE,CAAC;IACpD,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3B,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1B,MAAM,EAAE,CAAC,IAAI,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -3,7 +3,9 @@ import type { SqliteDb } from './db.js';
3
3
  export declare const DEFAULT_PEER_KEY_NAME = "peer-private-key";
4
4
  /**
5
5
  * Loads the persisted NativeScript peer's libp2p private key, or generates a
6
- * fresh Ed25519 key on first call and persists it.
6
+ * fresh Ed25519 key on first call, persists it, and syncs the database before
7
+ * returning it, so a power cut right after first start cannot give the node a
8
+ * different peer id next time. An existing key is read as it is.
7
9
  *
8
10
  * The key is stored as raw protobuf bytes in the `kv` table's `b_val` column
9
11
  * under `keyName`. SQLite stores BLOB natively, so there is no base64
@@ -1 +1 @@
1
- {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAC1C,EAAE,EAAE,QAAQ,EACZ,OAAO,GAAE,MAA8B,GACrC,OAAO,CAAC,UAAU,CAAC,CAYrB"}
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CAC1C,EAAE,EAAE,QAAQ,EACZ,OAAO,GAAE,MAA8B,GACrC,OAAO,CAAC,UAAU,CAAC,CAarB"}
@@ -1,8 +1,11 @@
1
1
  import { generateKeyPair, privateKeyFromProtobuf, privateKeyToProtobuf } from '@libp2p/crypto/keys';
2
+ import { syncSqliteDb } from './sqlite-sync.js';
2
3
  export const DEFAULT_PEER_KEY_NAME = 'peer-private-key';
3
4
  /**
4
5
  * Loads the persisted NativeScript peer's libp2p private key, or generates a
5
- * fresh Ed25519 key on first call and persists it.
6
+ * fresh Ed25519 key on first call, persists it, and syncs the database before
7
+ * returning it, so a power cut right after first start cannot give the node a
8
+ * different peer id next time. An existing key is read as it is.
6
9
  *
7
10
  * The key is stored as raw protobuf bytes in the `kv` table's `b_val` column
8
11
  * under `keyName`. SQLite stores BLOB natively, so there is no base64
@@ -23,6 +26,7 @@ export async function loadOrCreateNSPeerKey(db, keyName = DEFAULT_PEER_KEY_NAME)
23
26
  await db
24
27
  .prepare('INSERT INTO kv (key, s_val, b_val) VALUES (?, NULL, ?) ON CONFLICT(key) DO UPDATE SET b_val = excluded.b_val')
25
28
  .run(keyName, bytes);
29
+ await syncSqliteDb(db);
26
30
  return key;
27
31
  }
28
32
  //# sourceMappingURL=identity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIpG,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,EAAY,EACZ,UAAkB,qBAAqB;IAEvC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC;IAC1B,IAAI,MAAM,YAAY,UAAU,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,EAAE;SACN,OAAO,CAAC,8GAA8G,CAAC;SACvH,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtB,OAAO,GAAG,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGpG,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,EAAY,EACZ,UAAkB,qBAAqB;IAEvC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC;IAC1B,IAAI,MAAM,YAAY,UAAU,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,EAAE;SACN,OAAO,CAAC,8GAA8G,CAAC;SACvH,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtB,MAAM,YAAY,CAAC,EAAE,CAAC,CAAC;IACvB,OAAO,GAAG,CAAC;AACZ,CAAC"}
@@ -1,7 +1,9 @@
1
- export type { OptimysticNSDBHandle, SqliteDb, SqliteStatement, SqliteParam, SqliteRow } from './db.js';
1
+ export type { OptimysticNSDBHandle, SqliteDb, SqliteStatement, SqliteTransaction, SqliteTransactionOptions, 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, SqliteStoreDriver } from './sqlite-storage.js';
4
+ export { SqliteRawStorage, SqliteBatchBackend } from './sqlite-storage.js';
5
+ export type { SqliteRawStorageOptions, SqliteBatchBackendOptions } from './sqlite-storage.js';
6
+ export { syncSqliteDb } from './sqlite-sync.js';
5
7
  export { SqliteKVStore } from './sqlite-kv-store.js';
6
8
  export { loadOrCreateNSPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACvG,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,oBAAoB,EAAE,QAAQ,EAAE,eAAe,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpJ,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,YAAY,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
2
2
  export { openOptimysticNSDb, wrapNSPluginDb } from './ns-opener.js';
3
- export { SqliteRawStorage, SqliteStoreDriver } from './sqlite-storage.js';
3
+ export { SqliteRawStorage, SqliteBatchBackend } from './sqlite-storage.js';
4
+ export { syncSqliteDb } from './sqlite-sync.js';
4
5
  export { SqliteKVStore } from './sqlite-kv-store.js';
5
6
  export { loadOrCreateNSPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ns-opener.d.ts","sourceRoot":"","sources":["../../src/ns-opener.ts"],"names":[],"mappings":"AACA,OAAO,EAAoD,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAAkF,MAAM,SAAS,CAAC;AAErM;;;;;GAKG;AACH,UAAU,UAAU;IACnB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC/D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IACpJ,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/H,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,kBAAkB,CACvC,IAAI,GAAE,MAAwB,EAC9B,OAAO,GAAE,MAA2B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CAM/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAExD"}
1
+ {"version":3,"file":"ns-opener.d.ts","sourceRoot":"","sources":["../../src/ns-opener.ts"],"names":[],"mappings":"AACA,OAAO,EAAoE,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAAiH,MAAM,SAAS,CAAC;AAEpP;;;;;GAKG;AACH,UAAU,UAAU;IACnB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC/D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IACpJ,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/H,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,kBAAkB,CACvC,IAAI,GAAE,MAAwB,EAC9B,OAAO,GAAE,MAA2B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CAM/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAExD"}
@@ -1,5 +1,5 @@
1
1
  import { ConnectionMutex } from './connection-mutex.js';
2
- import { applySchema, DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
2
+ import { applySchema, runTransaction, DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
3
3
  /**
4
4
  * Opens (creating if needed) the Optimystic SQLite database at `name` under
5
5
  * the NativeScript app's documents directory, applies WAL pragmas, and runs
@@ -62,31 +62,11 @@ class NSPluginDbWrapper {
62
62
  // Outside-transaction statement: writes go through the mutex.
63
63
  return new NSPluginStatement(this.raw, sql, this.mutex);
64
64
  }
65
- async transaction(fn) {
66
- return this.mutex.serialize(async () => {
67
- // BEGIN IMMEDIATE takes the write lock up front rather than deferring
68
- // it to the first write, so contention surfaces here and not mid-body.
69
- await this.raw.execute('BEGIN IMMEDIATE');
70
- try {
71
- // Statements bound to the open transaction bypass the mutex — we
72
- // already hold the slot; re-locking would deadlock.
73
- const tx = {
74
- prepare: (sql) => new NSPluginStatement(this.raw, sql),
75
- };
76
- const result = await fn(tx);
77
- await this.raw.execute('COMMIT');
78
- return result;
79
- }
80
- catch (err) {
81
- try {
82
- await this.raw.execute('ROLLBACK');
83
- }
84
- catch {
85
- // Swallow rollback failures so we surface the original error.
86
- }
87
- throw err;
88
- }
89
- });
65
+ async transaction(fn, options) {
66
+ return this.mutex.serialize(() => runTransaction({ execute: async (sql) => { await this.raw.execute(sql); } },
67
+ // Statements bound to the open transaction bypass the mutex — we
68
+ // already hold the slot; re-locking would deadlock.
69
+ { prepare: (sql) => new NSPluginStatement(this.raw, sql) }, fn, options));
90
70
  }
91
71
  async close() {
92
72
  await this.raw.close();
@@ -119,11 +99,16 @@ class NSPluginStatement {
119
99
  // NOTE: reads run directly on the connection, unserialized, to preserve read
120
100
  // concurrency. A read issued while a write transaction is open on this same
121
101
  // connection observes that transaction's UNCOMMITTED rows (read-your-connection
122
- // semantics). Fine today: the only transaction writer is same-block promote
123
- // under the block's write latch, and cross-block reads are independent. If a future
124
- // caller reads a block on this connection while another op's transaction on the
125
- // same rows is mid-flight, it may see uncommitted state — serialize reads too if
126
- // that ever matters.
102
+ // semantics). Fine today: the one transaction writer of the block tables is
103
+ // `SqliteBatchBackend.commit`, and every row it is writing is held by the
104
+ // `WriteGroupDriver` overlay above it until the batch is durable. A point read of
105
+ // such a row is answered by the overlay and never reaches this connection; a
106
+ // listing does reach it, and may see the batch's uncommitted rows, but merges the
107
+ // overlay over them, so it answers the same whether the transaction has committed
108
+ // or not (and a rolled-back batch latches the store). The key-value store and the
109
+ // identity helper write other tables, by autocommit statements, and the sync's
110
+ // `durability_mark` row is read by nobody. If a reader of a batch's rows ever
111
+ // bypasses the overlay, serialize reads too.
127
112
  const row = await this.raw.get(this.sql, params);
128
113
  if (row === null || row === undefined)
129
114
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"ns-opener.js","sourceRoot":"","sources":["../../src/ns-opener.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ns-opener.js","sourceRoot":"","sources":["../../src/ns-opener.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { IKVStore } from '@optimystic/db-p2p';
1
+ import { type IKVStore } from '@optimystic/db-p2p';
2
2
  import type { SqliteDb } from './db.js';
3
3
  /**
4
4
  * SQLite-backed `IKVStore` adapter for NativeScript peers.
@@ -11,14 +11,28 @@ import type { SqliteDb } from './db.js';
11
11
  *
12
12
  * Only the `s_val` column is read or written here; the `b_val` column is
13
13
  * reserved for binary identity material.
14
+ *
15
+ * Writes are autocommit statements: each is visible, and survives a crash of this process, once it
16
+ * resolves, and the write-ahead log keeps them in order. `flush()` makes them survive a power cut:
17
+ * when anything was written since the last flush it runs `syncSqliteDb`, one synced commit that
18
+ * syncs the shared log and with it every earlier write on the connection; otherwise it resolves at
19
+ * once. A failed sync latches the store, after which every write and flush throws
20
+ * `StoreFailedError` (rule 5 of the flush contract on `RawStoreDriver.flush`); reads keep working.
14
21
  */
15
22
  export declare class SqliteKVStore implements IKVStore {
23
+ private readonly db;
16
24
  private readonly stmts;
17
25
  private readonly prefix;
26
+ /** Set once a write has resolved, cleared by the flush that syncs it. */
27
+ private dirty;
28
+ private fault;
18
29
  constructor(db: SqliteDb, prefix?: string);
19
30
  get(key: string): Promise<string | undefined>;
20
31
  set(key: string, value: string): Promise<void>;
21
32
  delete(key: string): Promise<void>;
22
33
  list(prefix: string): Promise<string[]>;
34
+ /** `IKVStore.flush`: one synced commit when this store wrote since its last flush, otherwise nothing. */
35
+ flush(): Promise<void>;
36
+ private throwIfFailed;
23
37
  }
24
38
  //# sourceMappingURL=sqlite-kv-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-kv-store.d.ts","sourceRoot":"","sources":["../../src/sqlite-kv-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAEzD;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAKpB;IACF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,MAA0B;IAUtD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAO7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAW7C"}
1
+ {"version":3,"file":"sqlite-kv-store.d.ts","sourceRoot":"","sources":["../../src/sqlite-kv-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAGzD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAYjC,OAAO,CAAC,QAAQ,CAAC,EAAE;IAX/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAKpB;IACF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,yEAAyE;IACzE,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAiC;gBAEjB,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,MAA0B;IAUvE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAO7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAY7C,yGAAyG;IACnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B,OAAO,CAAC,aAAa;CAGrB"}