@optimystic/db-p2p-storage-ns 0.13.4

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 ADDED
@@ -0,0 +1,118 @@
1
+ # @optimystic/db-p2p-storage-ns
2
+
3
+ SQLite-backed storage backend for Optimystic NativeScript peers (iOS and
4
+ Android, native — not React Native). Provides:
5
+
6
+ - **`SqliteRawStorage`** — implements `IRawStorage` so a NativeScript node
7
+ persists block metadata, revisions, pending transactions, committed
8
+ transactions, and materialized blocks across app restarts.
9
+ - **`SqliteKVStore`** — implements `IKVStore` for the persistent transaction
10
+ state used to recover crashed two-phase commits.
11
+ - **`loadOrCreateNSPeerKey`** — generates an Ed25519 libp2p private key on
12
+ first run and persists it as a BLOB in the same SQLite database, giving
13
+ the NativeScript peer a stable, restart-surviving identity.
14
+
15
+ This package targets NativeScript apps (iOS + Android via the
16
+ [`@nativescript-community/sqlite`](https://github.com/nativescript-community/sqlite)
17
+ plugin). It cannot run in plain Node, browsers, or React Native — use the
18
+ sibling adapter for each of those:
19
+
20
+ - `@optimystic/db-p2p-storage-fs` — Node filesystem
21
+ - `@optimystic/db-p2p-storage-rn` — React Native (MMKV)
22
+ - `@optimystic/db-p2p-storage-web` — Browser (IndexedDB)
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ yarn add @optimystic/db-p2p-storage-ns @optimystic/db-p2p @optimystic/db-core \
28
+ @nativescript-community/sqlite
29
+ ```
30
+
31
+ `@nativescript-community/sqlite` is a peer dependency — the host app pins the
32
+ version it needs (same approach as `react-native-mmkv` for the RN package).
33
+
34
+ Use the Node-free `/rn` entry point of `@optimystic/db-p2p` so the Node-only
35
+ TCP transport doesn't get bundled into the NativeScript app:
36
+
37
+ ```ts
38
+ import { createLibp2pNode } from '@optimystic/db-p2p/rn';
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```ts
44
+ import { createLibp2pNode } from '@optimystic/db-p2p/rn';
45
+ import {
46
+ openOptimysticNSDb,
47
+ SqliteRawStorage,
48
+ SqliteKVStore,
49
+ loadOrCreateNSPeerKey,
50
+ } from '@optimystic/db-p2p-storage-ns';
51
+
52
+ const db = await openOptimysticNSDb(); // single shared handle
53
+ const rawStorage = new SqliteRawStorage(db); // → IRawStorage
54
+ const kvStore = new SqliteKVStore(db); // → IKVStore (txn recovery)
55
+ const privateKey = await loadOrCreateNSPeerKey(db);
56
+
57
+ const libp2p = await createLibp2pNode({
58
+ bootstrapNodes: [/* … */],
59
+ networkName: 'my-network',
60
+ privateKey,
61
+ });
62
+ ```
63
+
64
+ The same handle is shared by all three consumers. SQLite serializes writes
65
+ inside a single connection; reads/writes are short-lived enough that
66
+ single-connection contention is a non-issue for a client peer.
67
+
68
+ ## Persistence model
69
+
70
+ The package opens a single SQLite database (`optimystic.sqlite` by default
71
+ in the NativeScript app's documents directory) with six tables:
72
+
73
+ | Table | Key | Value |
74
+ |----------------|----------------------------------|----------------------------------|
75
+ | `metadata` | `block_id` | `BlockMetadata` (JSON) |
76
+ | `revisions` | `(block_id, rev)` | `action_id` |
77
+ | `pending` | `(block_id, action_id)` | `Transform` (JSON) |
78
+ | `transactions` | `(block_id, action_id)` | `Transform` (JSON) |
79
+ | `materialized` | `(block_id, action_id)` | `IBlock` (JSON) |
80
+ | `kv` | `key` | `s_val` (TEXT) or `b_val` (BLOB) |
81
+
82
+ Pragmas: `journal_mode = WAL`, `synchronous = NORMAL`, `foreign_keys = OFF`.
83
+ Schema is versioned via `PRAGMA user_version`.
84
+
85
+ `listRevisions` and `listPendingTransactions` issue bounded
86
+ `SELECT … ORDER BY` queries — never `SELECT * FROM <table>` + JS filter —
87
+ so list latency stays bounded as the tables grow.
88
+ `promotePendingTransaction` runs as a single `BEGIN; INSERT…; DELETE…; COMMIT;`
89
+ inside `SqliteDb.transaction(fn)`, so the move is atomic across crashes —
90
+ unlike the MMKV adapter, which has to maintain a separate pending-index row.
91
+
92
+ `getApproximateBytesUsed()` returns `page_count × page_size` from PRAGMAs.
93
+ This is the SQLite database-file footprint; it is **not** a per-block figure.
94
+ That's adequate for `StorageMonitor` — which uses the value as an advisory
95
+ ring-selection input.
96
+
97
+ ## Identity
98
+
99
+ `loadOrCreateNSPeerKey(db, keyName?)` writes the libp2p private key as raw
100
+ protobuf bytes (`Uint8Array`) into the `kv` table's `b_val` column under
101
+ `keyName` (default `peer-private-key`). SQLite stores BLOBs natively — no
102
+ base64 round-trip. To rotate the identity, delete that one row:
103
+
104
+ ```ts
105
+ await db.prepare('DELETE FROM kv WHERE key = ?').run('peer-private-key');
106
+ ```
107
+
108
+ To wipe the entire backing store (debugging / dev reset), delete the
109
+ underlying SQLite file.
110
+
111
+ ## Tests
112
+
113
+ `yarn test` runs the spec suite under Node's built-in `node:sqlite` driver
114
+ (Node 22+ — default-enabled on Node 23+). Production code only depends on
115
+ the package-private `SqliteDb` interface, so the Node driver is a faithful
116
+ behavioural surrogate for the NativeScript plugin. The suite never imports
117
+ `@nativescript-community/sqlite`, so the plugin's native bindings don't need
118
+ to be installed to run tests.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Internal SQLite shim and Optimystic schema for the NativeScript storage backend.
3
+ *
4
+ * The storage classes (`SqliteRawStorage`, `SqliteKVStore`) and the identity
5
+ * helper (`loadOrCreateNSPeerKey`) only depend on the `SqliteDb` interface
6
+ * defined here — never on `@nativescript-community/sqlite` directly. That lets
7
+ * the suite run under Node mocha against a `node:sqlite` or `better-sqlite3`
8
+ * driver, matching the pattern `db-p2p-storage-web` uses with `fake-indexeddb`.
9
+ *
10
+ * The wrapper is **package-private**: only `openOptimysticNSDb` is exported to
11
+ * consumers; the `SqliteDb` interface is an internal seam for tests.
12
+ */
13
+ /** SQL parameter value — covers everything `IRawStorage` and `IKVStore` write. */
14
+ export type SqliteParam = string | number | Uint8Array | null;
15
+ /** A single row returned from a SQL query, keyed by column name. */
16
+ export type SqliteRow = Record<string, SqliteParam>;
17
+ /**
18
+ * A prepared statement bound to a single SQL string.
19
+ *
20
+ * Both `node:sqlite` and `@nativescript-community/sqlite` cache parsed SQL
21
+ * internally, so re-binding a prepared statement is cheaper than re-parsing a
22
+ * raw `execSQL`. The storage classes prepare each query once per instance.
23
+ */
24
+ export interface SqliteStatement {
25
+ /** Execute with the given bind parameters, ignoring any returned rows. */
26
+ run(...params: SqliteParam[]): Promise<void>;
27
+ /** Execute and return the first row, or `undefined` if none. */
28
+ get(...params: SqliteParam[]): Promise<SqliteRow | undefined>;
29
+ /** Execute and return all rows (already drained — safe to await between yields). */
30
+ all(...params: SqliteParam[]): Promise<SqliteRow[]>;
31
+ }
32
+ /**
33
+ * Minimal SQLite driver surface used by this package.
34
+ *
35
+ * Wraps either the NativeScript plugin (in production) or a Node SQLite
36
+ * driver (in tests). Async on every method so the NS plugin's I/O can be
37
+ * Promised — even where the underlying call is synchronous.
38
+ */
39
+ export interface SqliteDb {
40
+ /** Execute one or more semicolon-separated statements; no result rows. */
41
+ exec(sql: string): Promise<void>;
42
+ /** Prepare a parameterized statement for repeated execution. */
43
+ prepare(sql: string): SqliteStatement;
44
+ /** Run `fn` inside `BEGIN ... COMMIT` (rolls back on throw). */
45
+ transaction<T>(fn: () => Promise<T>): Promise<T>;
46
+ /** Release the underlying handle. */
47
+ close(): Promise<void>;
48
+ }
49
+ export declare const DEFAULT_DB_NAME = "optimystic.sqlite";
50
+ export declare const DEFAULT_DB_VERSION = 1;
51
+ /**
52
+ * Schema for the NativeScript storage backend.
53
+ *
54
+ * Mirrors the IndexedDB object stores 1:1:
55
+ * - `metadata` → per-block `BlockMetadata` (JSON).
56
+ * - `revisions` → revision lookup keyed by `(block_id, rev)`.
57
+ * - `pending` → uncommitted transforms keyed by `(block_id, action_id)`.
58
+ * - `transactions` → committed transforms keyed by `(block_id, action_id)`.
59
+ * - `materialized` → materialized blocks keyed by `(block_id, action_id)`.
60
+ * - `kv` → generic string keyspace shared by `IKVStore` (`s_val`) and the
61
+ * identity helper (`b_val`). The two columns avoid a base64 round-trip
62
+ * for the libp2p private key.
63
+ */
64
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS metadata (\n\tblock_id TEXT PRIMARY KEY,\n\tvalue TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS revisions (\n\tblock_id TEXT NOT NULL,\n\trev INTEGER NOT NULL,\n\taction_id TEXT 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 TEXT 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 TEXT NOT NULL,\n\tPRIMARY KEY (block_id, action_id)\n);\n\nCREATE TABLE IF NOT EXISTS materialized (\n\tblock_id TEXT NOT NULL,\n\taction_id TEXT NOT NULL,\n\tvalue TEXT 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";
65
+ /**
66
+ * Apply pragmas, run the schema DDL, and stamp `user_version` so future
67
+ * migrations can branch on it. Idempotent — safe to call on every open.
68
+ */
69
+ export declare function applySchema(db: SqliteDb, version?: number): Promise<void>;
70
+ /**
71
+ * Public handle returned by `openOptimysticNSDb`. Consumers pass it to
72
+ * `SqliteRawStorage`, `SqliteKVStore`, and `loadOrCreateNSPeerKey`.
73
+ */
74
+ export type OptimysticNSDBHandle = SqliteDb;
75
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +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;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACxB,0EAA0E;IAC1E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,gEAAgE;IAChE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,gEAAgE;IAChE,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,sBAAsB,CAAC;AACnD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,+2BAuCtB,CAAC;AAQF;;;GAGG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE,MAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAInG;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC"}
package/dist/src/db.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Internal SQLite shim and Optimystic schema for the NativeScript storage backend.
3
+ *
4
+ * The storage classes (`SqliteRawStorage`, `SqliteKVStore`) and the identity
5
+ * helper (`loadOrCreateNSPeerKey`) only depend on the `SqliteDb` interface
6
+ * defined here — never on `@nativescript-community/sqlite` directly. That lets
7
+ * the suite run under Node mocha against a `node:sqlite` or `better-sqlite3`
8
+ * driver, matching the pattern `db-p2p-storage-web` uses with `fake-indexeddb`.
9
+ *
10
+ * The wrapper is **package-private**: only `openOptimysticNSDb` is exported to
11
+ * consumers; the `SqliteDb` interface is an internal seam for tests.
12
+ */
13
+ export const DEFAULT_DB_NAME = 'optimystic.sqlite';
14
+ export const DEFAULT_DB_VERSION = 1;
15
+ /**
16
+ * Schema for the NativeScript storage backend.
17
+ *
18
+ * Mirrors the IndexedDB object stores 1:1:
19
+ * - `metadata` → per-block `BlockMetadata` (JSON).
20
+ * - `revisions` → revision lookup keyed by `(block_id, rev)`.
21
+ * - `pending` → uncommitted transforms keyed by `(block_id, action_id)`.
22
+ * - `transactions` → committed transforms keyed by `(block_id, action_id)`.
23
+ * - `materialized` → materialized blocks keyed by `(block_id, action_id)`.
24
+ * - `kv` → generic string keyspace shared by `IKVStore` (`s_val`) and the
25
+ * identity helper (`b_val`). The two columns avoid a base64 round-trip
26
+ * for the libp2p private key.
27
+ */
28
+ export const SCHEMA_SQL = `
29
+ CREATE TABLE IF NOT EXISTS metadata (
30
+ block_id TEXT PRIMARY KEY,
31
+ value TEXT NOT NULL
32
+ );
33
+
34
+ CREATE TABLE IF NOT EXISTS revisions (
35
+ block_id TEXT NOT NULL,
36
+ rev INTEGER NOT NULL,
37
+ action_id TEXT NOT NULL,
38
+ PRIMARY KEY (block_id, rev)
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS pending (
42
+ block_id TEXT NOT NULL,
43
+ action_id TEXT NOT NULL,
44
+ value TEXT NOT NULL,
45
+ PRIMARY KEY (block_id, action_id)
46
+ );
47
+
48
+ CREATE TABLE IF NOT EXISTS transactions (
49
+ block_id TEXT NOT NULL,
50
+ action_id TEXT NOT NULL,
51
+ value TEXT NOT NULL,
52
+ PRIMARY KEY (block_id, action_id)
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS materialized (
56
+ block_id TEXT NOT NULL,
57
+ action_id TEXT NOT NULL,
58
+ value TEXT NOT NULL,
59
+ PRIMARY KEY (block_id, action_id)
60
+ );
61
+
62
+ CREATE TABLE IF NOT EXISTS kv (
63
+ key TEXT PRIMARY KEY,
64
+ s_val TEXT,
65
+ b_val BLOB
66
+ );
67
+ `;
68
+ const PRAGMAS_SQL = `
69
+ PRAGMA journal_mode = WAL;
70
+ PRAGMA synchronous = NORMAL;
71
+ PRAGMA foreign_keys = OFF;
72
+ `;
73
+ /**
74
+ * Apply pragmas, run the schema DDL, and stamp `user_version` so future
75
+ * migrations can branch on it. Idempotent — safe to call on every open.
76
+ */
77
+ export async function applySchema(db, version = DEFAULT_DB_VERSION) {
78
+ await db.exec(PRAGMAS_SQL);
79
+ await db.exec(SCHEMA_SQL);
80
+ await db.exec(`PRAGMA user_version = ${version}`);
81
+ }
82
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0CH,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCzB,CAAC;AAEF,MAAM,WAAW,GAAG;;;;CAInB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAY,EAAE,UAAkB,kBAAkB;IACnF,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"}
@@ -0,0 +1,17 @@
1
+ import type { PrivateKey } from '@libp2p/interface';
2
+ import type { SqliteDb } from './db.js';
3
+ export declare const DEFAULT_PEER_KEY_NAME = "peer-private-key";
4
+ /**
5
+ * Loads the persisted NativeScript peer's libp2p private key, or generates a
6
+ * fresh Ed25519 key on first call and persists it.
7
+ *
8
+ * The key is stored as raw protobuf bytes in the `kv` table's `b_val` column
9
+ * under `keyName`. SQLite stores BLOB natively, so there is no base64
10
+ * round-trip — unlike text-only key/value stores.
11
+ *
12
+ * The returned `PrivateKey` can be passed directly to
13
+ * `createLibp2pNode({ privateKey })`, giving a NativeScript peer a stable,
14
+ * restart-surviving identity.
15
+ */
16
+ export declare function loadOrCreateNSPeerKey(db: SqliteDb, keyName?: string): Promise<PrivateKey>;
17
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,28 @@
1
+ import { generateKeyPair, privateKeyFromProtobuf, privateKeyToProtobuf } from '@libp2p/crypto/keys';
2
+ export const DEFAULT_PEER_KEY_NAME = 'peer-private-key';
3
+ /**
4
+ * Loads the persisted NativeScript peer's libp2p private key, or generates a
5
+ * fresh Ed25519 key on first call and persists it.
6
+ *
7
+ * The key is stored as raw protobuf bytes in the `kv` table's `b_val` column
8
+ * under `keyName`. SQLite stores BLOB natively, so there is no base64
9
+ * round-trip — unlike text-only key/value stores.
10
+ *
11
+ * The returned `PrivateKey` can be passed directly to
12
+ * `createLibp2pNode({ privateKey })`, giving a NativeScript peer a stable,
13
+ * restart-surviving identity.
14
+ */
15
+ export async function loadOrCreateNSPeerKey(db, keyName = DEFAULT_PEER_KEY_NAME) {
16
+ const row = await db.prepare('SELECT b_val FROM kv WHERE key = ?').get(keyName);
17
+ const stored = row?.b_val;
18
+ if (stored instanceof Uint8Array && stored.length > 0) {
19
+ return privateKeyFromProtobuf(stored);
20
+ }
21
+ const key = await generateKeyPair('Ed25519');
22
+ const bytes = privateKeyToProtobuf(key);
23
+ await db
24
+ .prepare('INSERT INTO kv (key, s_val, b_val) VALUES (?, NULL, ?) ON CONFLICT(key) DO UPDATE SET b_val = excluded.b_val')
25
+ .run(keyName, bytes);
26
+ return key;
27
+ }
28
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +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"}
@@ -0,0 +1,7 @@
1
+ export type { OptimysticNSDBHandle, SqliteDb, SqliteStatement, SqliteParam, SqliteRow } from './db.js';
2
+ export { DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
3
+ export { openOptimysticNSDb, wrapNSPluginDb } from './ns-opener.js';
4
+ export { SqliteRawStorage } from './sqlite-storage.js';
5
+ export { SqliteKVStore } from './sqlite-kv-store.js';
6
+ export { loadOrCreateNSPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
2
+ export { openOptimysticNSDb, wrapNSPluginDb } from './ns-opener.js';
3
+ export { SqliteRawStorage } from './sqlite-storage.js';
4
+ export { SqliteKVStore } from './sqlite-kv-store.js';
5
+ export { loadOrCreateNSPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ import debug from 'debug';
2
+ export declare function createLogger(subNamespace: string): debug.Debugger;
3
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAEjE"}
@@ -0,0 +1,6 @@
1
+ import debug from 'debug';
2
+ const BASE_NAMESPACE = 'optimystic:db-p2p-storage-ns';
3
+ export function createLogger(subNamespace) {
4
+ return debug(`${BASE_NAMESPACE}:${subNamespace}`);
5
+ }
6
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,cAAc,GAAG,8BAA8B,CAAC;AAEtD,MAAM,UAAU,YAAY,CAAC,YAAoB;IAChD,OAAO,KAAK,CAAC,GAAG,cAAc,IAAI,YAAY,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { type OptimysticNSDBHandle, type SqliteDb } from './db.js';
2
+ /**
3
+ * Minimal subset of `@nativescript-community/sqlite`'s `Db` we depend on.
4
+ * Re-declared locally so this module's *types* don't pull in the plugin's
5
+ * declarations (the plugin is a peer dependency that may not be installed
6
+ * at typecheck time on non-NativeScript consumers).
7
+ */
8
+ interface NSPluginDb {
9
+ execSQL(sql: string, params?: ReadonlyArray<unknown>): unknown;
10
+ get(sql: string, params?: ReadonlyArray<unknown>): Promise<Record<string, unknown> | null | undefined> | Record<string, unknown> | null | undefined;
11
+ select(sql: string, params?: ReadonlyArray<unknown>): Promise<Array<Record<string, unknown>>> | Array<Record<string, unknown>>;
12
+ close(): void | Promise<void>;
13
+ }
14
+ /**
15
+ * Opens (creating if needed) the Optimystic SQLite database at `name` under
16
+ * the NativeScript app's documents directory, applies WAL pragmas, and runs
17
+ * the migration to `version`.
18
+ *
19
+ * The returned `SqliteDb` handle is safe to share across `SqliteRawStorage`,
20
+ * `SqliteKVStore`, and `loadOrCreateNSPeerKey` — SQLite serializes writes
21
+ * inside the connection, and our reads/writes are short-lived enough that
22
+ * single-connection contention is a non-issue for a client peer.
23
+ *
24
+ * `path` may be passed in as the full filesystem path if the caller wants
25
+ * to control file placement; otherwise the plugin's documents-directory
26
+ * default is used.
27
+ */
28
+ export declare function openOptimysticNSDb(name?: string, version?: number): Promise<OptimysticNSDBHandle>;
29
+ /**
30
+ * Wraps a `@nativescript-community/sqlite` `Db` instance to satisfy the
31
+ * internal `SqliteDb` interface used by the storage classes. Exported only
32
+ * for callers that already hold an open NS-plugin handle and want to skip
33
+ * the opener (rare — typically users just call `openOptimysticNSDb`).
34
+ */
35
+ export declare function wrapNSPluginDb(raw: NSPluginDb): SqliteDb;
36
+ export {};
37
+ //# sourceMappingURL=ns-opener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ns-opener.d.ts","sourceRoot":"","sources":["../../src/ns-opener.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,oBAAoB,EAAE,KAAK,QAAQ,EAA0D,MAAM,SAAS,CAAC;AAE7K;;;;;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;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACvC,IAAI,GAAE,MAAwB,EAC9B,OAAO,GAAE,MAA2B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CAM/B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAExD"}
@@ -0,0 +1,93 @@
1
+ import { applySchema, DEFAULT_DB_NAME, DEFAULT_DB_VERSION } from './db.js';
2
+ /**
3
+ * Opens (creating if needed) the Optimystic SQLite database at `name` under
4
+ * the NativeScript app's documents directory, applies WAL pragmas, and runs
5
+ * the migration to `version`.
6
+ *
7
+ * The returned `SqliteDb` handle is safe to share across `SqliteRawStorage`,
8
+ * `SqliteKVStore`, and `loadOrCreateNSPeerKey` — SQLite serializes writes
9
+ * inside the connection, and our reads/writes are short-lived enough that
10
+ * single-connection contention is a non-issue for a client peer.
11
+ *
12
+ * `path` may be passed in as the full filesystem path if the caller wants
13
+ * to control file placement; otherwise the plugin's documents-directory
14
+ * default is used.
15
+ */
16
+ export async function openOptimysticNSDb(name = DEFAULT_DB_NAME, version = DEFAULT_DB_VERSION) {
17
+ const plugin = (await import('@nativescript-community/sqlite'));
18
+ const raw = await plugin.openOrCreate(name);
19
+ const wrapped = wrapNSPluginDb(raw);
20
+ await applySchema(wrapped, version);
21
+ return wrapped;
22
+ }
23
+ /**
24
+ * Wraps a `@nativescript-community/sqlite` `Db` instance to satisfy the
25
+ * internal `SqliteDb` interface used by the storage classes. Exported only
26
+ * for callers that already hold an open NS-plugin handle and want to skip
27
+ * the opener (rare — typically users just call `openOptimysticNSDb`).
28
+ */
29
+ export function wrapNSPluginDb(raw) {
30
+ return new NSPluginDbWrapper(raw);
31
+ }
32
+ class NSPluginDbWrapper {
33
+ raw;
34
+ constructor(raw) {
35
+ this.raw = raw;
36
+ }
37
+ async exec(sql) {
38
+ // The plugin's execSQL accepts a single statement; split semicolon-
39
+ // separated DDL so callers can pass the full schema in one go.
40
+ const statements = sql
41
+ .split(';')
42
+ .map(s => s.trim())
43
+ .filter(s => s.length > 0);
44
+ for (const statement of statements) {
45
+ await this.raw.execSQL(statement);
46
+ }
47
+ }
48
+ prepare(sql) {
49
+ return new NSPluginStatement(this.raw, sql);
50
+ }
51
+ async transaction(fn) {
52
+ await this.raw.execSQL('BEGIN');
53
+ try {
54
+ const result = await fn();
55
+ await this.raw.execSQL('COMMIT');
56
+ return result;
57
+ }
58
+ catch (err) {
59
+ try {
60
+ await this.raw.execSQL('ROLLBACK');
61
+ }
62
+ catch {
63
+ // Swallow rollback failures so we surface the original error.
64
+ }
65
+ throw err;
66
+ }
67
+ }
68
+ async close() {
69
+ await this.raw.close();
70
+ }
71
+ }
72
+ class NSPluginStatement {
73
+ raw;
74
+ sql;
75
+ constructor(raw, sql) {
76
+ this.raw = raw;
77
+ this.sql = sql;
78
+ }
79
+ async run(...params) {
80
+ await this.raw.execSQL(this.sql, params);
81
+ }
82
+ async get(...params) {
83
+ const row = await this.raw.get(this.sql, params);
84
+ if (row === null || row === undefined)
85
+ return undefined;
86
+ return row;
87
+ }
88
+ async all(...params) {
89
+ const rows = await this.raw.select(this.sql, params);
90
+ return rows;
91
+ }
92
+ }
93
+ //# sourceMappingURL=ns-opener.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ns-opener.js","sourceRoot":"","sources":["../../src/ns-opener.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { IKVStore } from '@optimystic/db-p2p';
2
+ import type { SqliteDb } from './db.js';
3
+ /**
4
+ * SQLite-backed `IKVStore` adapter for NativeScript peers.
5
+ *
6
+ * Stored keys are namespaced with `prefix` so the `kv` table can be shared
7
+ * with the identity helper (which uses the `b_val` column) without collisions.
8
+ * `list(prefix)` issues a bounded `SELECT key FROM kv WHERE key >= ? AND key < ?`
9
+ * — never `SELECT * FROM kv` + JS-side filter — so listing latency stays
10
+ * bounded as the table grows.
11
+ *
12
+ * Only the `s_val` column is read or written here; the `b_val` column is
13
+ * reserved for binary identity material.
14
+ */
15
+ export declare class SqliteKVStore implements IKVStore {
16
+ private readonly stmts;
17
+ private readonly prefix;
18
+ constructor(db: SqliteDb, prefix?: string);
19
+ get(key: string): Promise<string | undefined>;
20
+ set(key: string, value: string): Promise<void>;
21
+ delete(key: string): Promise<void>;
22
+ list(prefix: string): Promise<string[]>;
23
+ }
24
+ //# sourceMappingURL=sqlite-kv-store.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * SQLite-backed `IKVStore` adapter for NativeScript peers.
3
+ *
4
+ * Stored keys are namespaced with `prefix` so the `kv` table can be shared
5
+ * with the identity helper (which uses the `b_val` column) without collisions.
6
+ * `list(prefix)` issues a bounded `SELECT key FROM kv WHERE key >= ? AND key < ?`
7
+ * — never `SELECT * FROM kv` + JS-side filter — so listing latency stays
8
+ * bounded as the table grows.
9
+ *
10
+ * Only the `s_val` column is read or written here; the `b_val` column is
11
+ * reserved for binary identity material.
12
+ */
13
+ export class SqliteKVStore {
14
+ stmts;
15
+ prefix;
16
+ constructor(db, prefix = 'optimystic:txn:') {
17
+ this.prefix = prefix;
18
+ this.stmts = {
19
+ get: db.prepare('SELECT s_val FROM kv WHERE key = ?'),
20
+ set: db.prepare('INSERT INTO kv (key, s_val, b_val) VALUES (?, ?, NULL) ON CONFLICT(key) DO UPDATE SET s_val = excluded.s_val'),
21
+ delete: db.prepare('DELETE FROM kv WHERE key = ?'),
22
+ list: db.prepare('SELECT key FROM kv WHERE key >= ? AND key < ? ORDER BY key ASC'),
23
+ };
24
+ }
25
+ async get(key) {
26
+ const row = await this.stmts.get.get(this.prefix + key);
27
+ if (!row)
28
+ return undefined;
29
+ const value = row.s_val;
30
+ return typeof value === 'string' ? value : undefined;
31
+ }
32
+ async set(key, value) {
33
+ await this.stmts.set.run(this.prefix + key, value);
34
+ }
35
+ async delete(key) {
36
+ await this.stmts.delete.run(this.prefix + key);
37
+ }
38
+ async list(prefix) {
39
+ const fullPrefix = this.prefix + prefix;
40
+ // SQLite TEXT columns compare with BINARY collation by default — byte-by-byte
41
+ // over the UTF-8 encoding. U+10FFFF (`\u{10FFFF}`) is the highest Unicode
42
+ // code point, encoded as `F4 8F BF BF`; since the only valid UTF-8 leading
43
+ // bytes go up to 0xF4, any string starting with `fullPrefix` sorts strictly
44
+ // below `fullPrefix + '\u{10FFFF}'`, so the bound is exact for all valid keys.
45
+ const upper = fullPrefix + '\u{10FFFF}';
46
+ const rows = await this.stmts.list.all(fullPrefix, upper);
47
+ return rows.map(row => row.key.slice(this.prefix.length));
48
+ }
49
+ }
50
+ //# sourceMappingURL=sqlite-kv-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-kv-store.js","sourceRoot":"","sources":["../../src/sqlite-kv-store.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,aAAa;IACR,KAAK,CAKpB;IACe,MAAM,CAAS;IAEhC,YAAY,EAAY,EAAE,SAAiB,iBAAiB;QAC3D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG;YACZ,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC;YACrD,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,8GAA8G,CAAC;YAC/H,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,8BAA8B,CAAC;YAClD,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,gEAAgE,CAAC;SAClF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACxB,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;QACnC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACvB,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc;QACxB,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACxC,8EAA8E;QAC9E,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,+EAA+E;QAC/E,MAAM,KAAK,GAAG,UAAU,GAAG,YAAY,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAE,GAAG,CAAC,GAAc,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,CAAC;CACD"}