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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @optimystic/db-p2p-storage-rn
2
+
3
+ React Native LevelDB storage backend for `@optimystic/db-p2p`. Lets a React
4
+ Native peer persist Optimystic block data across app restarts using an
5
+ on-device LevelDB. Provides:
6
+
7
+ - **`LevelDBRawStorage`** — implements `IRawStorage` so an RN node persists
8
+ block metadata, revisions, pending transactions, committed transactions,
9
+ and materialized blocks.
10
+ - **`LevelDBKVStore`** — implements `IKVStore` for the persistent
11
+ transaction state used to recover crashed two-phase commits.
12
+ - **`loadOrCreateRNPeerKey`** — generates an Ed25519 libp2p private key on
13
+ first run and persists it in the same LevelDB, giving the RN peer a
14
+ stable, restart-surviving identity.
15
+ - **`openOptimysticRNDb` / `wrapRNLevelDB`** — open/wrap helpers that adapt
16
+ the native `rn-leveldb` handle to the internal key/value interface.
17
+
18
+ ## Peer dependency
19
+
20
+ The native store is [`rn-leveldb`](https://www.npmjs.com/package/rn-leveldb),
21
+ declared as a **peer dependency** — the host app installs and pins the
22
+ version it needs (same approach as `@nativescript-community/sqlite` for the
23
+ `-storage-ns` package). This package never imports `rn-leveldb` directly;
24
+ the caller passes the `LevelDB` constructor (wrapped as an open function) in,
25
+ which keeps the package's unit tests runnable under plain Node (the tests use
26
+ `classic-level` as a Node LevelDB surrogate).
27
+
28
+ ## Usage
29
+
30
+ ```ts
31
+ import { createLibp2pNode } from '@optimystic/db-p2p/rn';
32
+ import {
33
+ openOptimysticRNDb,
34
+ LevelDBRawStorage,
35
+ LevelDBKVStore,
36
+ loadOrCreateRNPeerKey,
37
+ } from '@optimystic/db-p2p-storage-rn';
38
+ import { LevelDB, LevelDBWriteBatch } from 'rn-leveldb';
39
+
40
+ const db = openOptimysticRNDb({
41
+ openFn: (name, createIfMissing, errorIfExists) =>
42
+ new LevelDB(name, createIfMissing, errorIfExists),
43
+ WriteBatch: LevelDBWriteBatch,
44
+ });
45
+ const rawStorage = new LevelDBRawStorage(db); // → IRawStorage
46
+ const kvStore = new LevelDBKVStore(db); // → IKVStore (txn recovery)
47
+ const privateKey = await loadOrCreateRNPeerKey(db);
48
+
49
+ const libp2p = await createLibp2pNode({
50
+ bootstrapNodes: [/* … */],
51
+ networkName: 'my-network',
52
+ privateKey,
53
+ });
54
+ ```
55
+
56
+ Use the Node-free `/rn` entry point of `@optimystic/db-p2p` so the Node-only
57
+ TCP transport doesn't get bundled into the React Native app.
58
+
59
+ ## Related packages
60
+
61
+ - **[@optimystic/db-p2p](../db-p2p)** — the distributed layer this backend
62
+ plugs into (repo/cluster/coordination + storage interfaces).
63
+ - `@optimystic/db-p2p-storage-fs` — Node filesystem backend.
64
+ - `@optimystic/db-p2p-storage-ns` — NativeScript (SQLite) backend.
65
+ - `@optimystic/db-p2p-storage-web` — Browser (IndexedDB) backend.
@@ -1,4 +1,4 @@
1
- export { LevelDBRawStorage } from './leveldb-storage.js';
1
+ export { LevelDBRawStorage, LevelDBStoreDriver } from './leveldb-storage.js';
2
2
  export { LevelDBKVStore } from './leveldb-kv-store.js';
3
3
  export { loadOrCreateRNPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
4
4
  export { openOptimysticRNDb, wrapRNLevelDB, DEFAULT_DB_NAME, type OpenOptimysticRNDbOptions, type RNLevelDBNative, type RNLevelDBWriteBatchNative, type RNLevelDBIteratorNative, type RNLevelDBWriteBatchCtor, type RNLevelDBOpenFn, } from './rn-opener.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACN,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACpB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACN,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { LevelDBRawStorage } from './leveldb-storage.js';
1
+ export { LevelDBRawStorage, LevelDBStoreDriver } from './leveldb-storage.js';
2
2
  export { LevelDBKVStore } from './leveldb-kv-store.js';
3
3
  export { loadOrCreateRNPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
4
4
  export { openOptimysticRNDb, wrapRNLevelDB, DEFAULT_DB_NAME, } from './rn-opener.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACN,kBAAkB,EAClB,aAAa,EACb,eAAe,GAOf,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACN,kBAAkB,EAClB,aAAa,EACb,eAAe,GAOf,MAAM,gBAAgB,CAAC"}
@@ -30,6 +30,20 @@ export declare const TAG_MATERIALIZED = 5;
30
30
  export declare const TAG_KV = 16;
31
31
  export declare const TAG_IDENTITY = 32;
32
32
  export declare function metadataKey(blockId: string): Uint8Array;
33
+ /**
34
+ * Returns the inclusive lower / exclusive upper range covering every metadata
35
+ * key. Metadata keys are `TAG_METADATA || len(blockId) || blockId` with an empty
36
+ * suffix, so every metadata key sorts in `[TAG_METADATA, TAG_METADATA+1)`. The
37
+ * tag bytes are deliberately spaced (0x01, 0x02, ...) so `TAG_METADATA + 1` is
38
+ * the next tag byte and a clean exclusive upper bound — a revision / pending /
39
+ * transaction key for the same block (higher tag) falls outside this range.
40
+ */
41
+ export declare function metadataRange(): {
42
+ gte: Uint8Array;
43
+ lt: Uint8Array;
44
+ };
45
+ /** Decode the `blockId` from a `metadataKey`-encoded key (empty suffix). */
46
+ export declare function blockIdFromMetadataKey(key: Uint8Array): string;
33
47
  export declare function revisionKey(blockId: string, rev: number): Uint8Array;
34
48
  /** Decode the trailing 8-byte big-endian rev from a `revisionKey`-encoded key. */
35
49
  export declare function revisionFromKey(key: Uint8Array): number;
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,YAAY,IAAO,CAAC;AACjC,eAAO,MAAM,aAAa,IAAO,CAAC;AAClC,eAAO,MAAM,WAAW,IAAO,CAAC;AAChC,eAAO,MAAM,gBAAgB,IAAO,CAAC;AACrC,eAAO,MAAM,gBAAgB,IAAO,CAAC;AACrC,eAAO,MAAM,MAAM,KAAO,CAAC;AAC3B,eAAO,MAAM,YAAY,KAAO,CAAC;AA0BjC,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAEvD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAMpE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAGvD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAExE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAE5E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAE7E;AAED,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAapG;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxE;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAE7C;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAcjF;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAErD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAEvD"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,YAAY,IAAO,CAAC;AACjC,eAAO,MAAM,aAAa,IAAO,CAAC;AAClC,eAAO,MAAM,WAAW,IAAO,CAAC;AAChC,eAAO,MAAM,gBAAgB,IAAO,CAAC;AACrC,eAAO,MAAM,gBAAgB,IAAO,CAAC;AACrC,eAAO,MAAM,MAAM,KAAO,CAAC;AAC3B,eAAO,MAAM,YAAY,KAAO,CAAC;AA0BjC,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAEnE;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAI9D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAMpE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAGvD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAExE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAE5E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAE7E;AAED,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAapG;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxE;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAE7C;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAcjF;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAErD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAEvD"}
package/dist/src/keys.js CHANGED
@@ -54,6 +54,23 @@ function concat(...parts) {
54
54
  export function metadataKey(blockId) {
55
55
  return encodeBlockEnvelope(TAG_METADATA, blockId);
56
56
  }
57
+ /**
58
+ * Returns the inclusive lower / exclusive upper range covering every metadata
59
+ * key. Metadata keys are `TAG_METADATA || len(blockId) || blockId` with an empty
60
+ * suffix, so every metadata key sorts in `[TAG_METADATA, TAG_METADATA+1)`. The
61
+ * tag bytes are deliberately spaced (0x01, 0x02, ...) so `TAG_METADATA + 1` is
62
+ * the next tag byte and a clean exclusive upper bound — a revision / pending /
63
+ * transaction key for the same block (higher tag) falls outside this range.
64
+ */
65
+ export function metadataRange() {
66
+ return { gte: Uint8Array.of(TAG_METADATA), lt: Uint8Array.of(TAG_METADATA + 1) };
67
+ }
68
+ /** Decode the `blockId` from a `metadataKey`-encoded key (empty suffix). */
69
+ export function blockIdFromMetadataKey(key) {
70
+ const view = new DataView(key.buffer, key.byteOffset, key.byteLength);
71
+ const blockIdLen = view.getUint32(1, false);
72
+ return textDecoder.decode(key.subarray(5, 5 + blockIdLen));
73
+ }
57
74
  export function revisionKey(blockId, rev) {
58
75
  const envelope = encodeBlockEnvelope(TAG_REVISIONS, blockId);
59
76
  const out = new Uint8Array(envelope.length + 8);
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/keys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/keys.ts"],"names":[],"mappings":"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"}
@@ -1,35 +1,56 @@
1
- import type { ActionId, ActionRev, BlockId, IBlock, Transform } from '@optimystic/db-core';
2
- import type { BlockMetadata, IRawStorage } from '@optimystic/db-p2p';
1
+ import type { ActionId, BlockId } from '@optimystic/db-core';
2
+ import { KvRawStorage, type RawStoreDriver } from '@optimystic/db-p2p';
3
3
  import { type LevelDBLike } from './leveldb-like.js';
4
4
  /**
5
- * LevelDB-backed `IRawStorage` implementation for React Native peers.
5
+ * LevelDB {@link RawStoreDriver}: the five logical block-storage stores mapped
6
+ * into a single ordered byte keyspace, partitioned by a leading tag byte (see
7
+ * `./keys.ts`). LevelDB is already one ordered byte-keyspace, so this driver is
8
+ * mostly key encoding — the tag-prefixed byte scheme in `keys.ts` stays in this
9
+ * package; the kernel deals in logical keys.
6
10
  *
7
- * All data lives in a single LevelDB database; per-store partitioning is by a
8
- * leading tag byte (see `./keys.ts`). `listRevisions` and
9
- * `listPendingTransactions` use range iterators with explicit bounds, drained
10
- * into an array before yielding (same rationale as the IndexedDB / SQLite
11
- * backends — a native iterator must not stay open across consumer awaits).
12
- *
13
- * `promotePendingTransaction` runs as a single `WriteBatch`, making the
14
- * pending → committed move atomic against crashes.
11
+ * `KvRawStorage` now owns all JSON/UTF-8 serialization, so this driver only ever
12
+ * reads/writes raw `Uint8Array` values — no `TextEncoder`/`TextDecoder` for
13
+ * values, and no `JSON.parse`/`stringify`. A value round-trips as the exact bytes
14
+ * the kernel wrote. Everything LevelDB-specific stays here: the tag-range scans
15
+ * (drained snapshot-first before yielding, so a native iterator never straddles a
16
+ * consumer's `await`), and the single `WriteBatch` that makes `promote` atomic.
15
17
  */
16
- export declare class LevelDBRawStorage implements IRawStorage {
18
+ export declare class LevelDBStoreDriver implements RawStoreDriver {
17
19
  private readonly db;
18
20
  constructor(db: LevelDBLike);
19
- getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined>;
20
- saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void>;
21
- getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined>;
22
- saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void>;
23
- listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev>;
24
- getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
25
- savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
26
- deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
27
- listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId>;
28
- getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
29
- saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
30
- getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined>;
31
- saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void>;
32
- getApproximateBytesUsed(): Promise<number>;
33
- promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
21
+ getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
22
+ putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
23
+ getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
24
+ putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
25
+ rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]>;
26
+ getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
27
+ putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
28
+ deletePending(blockId: BlockId, actionId: ActionId): Promise<void>;
29
+ listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
30
+ getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
31
+ putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
32
+ getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
33
+ putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
34
+ deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
35
+ promote(blockId: BlockId, actionId: ActionId): Promise<void>;
36
+ listBlockIds(): AsyncIterable<BlockId>;
37
+ approximateBytesUsed(): Promise<number>;
38
+ }
39
+ /**
40
+ * LevelDB-backed {@link IRawStorage} for React Native peers, now a thin shell
41
+ * over the shared {@link KvRawStorage} kernel driven by a {@link LevelDBStoreDriver}.
42
+ * The public name/constructor (`new LevelDBRawStorage(db)`) is unchanged so
43
+ * existing imports keep resolving; the kernel supplies the `IRawStorage` surface
44
+ * and the driver supplies LevelDB behavior.
45
+ *
46
+ * `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
47
+ * (the LevelDB driver always implements them, so the kernel constructor always
48
+ * wires them) — the base declares them optional, but every RN consumer relies on
49
+ * them.
50
+ */
51
+ export declare class LevelDBRawStorage extends KvRawStorage {
52
+ listBlockIds: () => AsyncIterable<BlockId>;
53
+ getApproximateBytesUsed: () => Promise<number>;
54
+ constructor(db: LevelDBLike);
34
55
  }
35
56
  //# sourceMappingURL=leveldb-storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"leveldb-storage.d.ts","sourceRoot":"","sources":["../../src/leveldb-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAmB5D;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,YAAW,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,WAAW;IAEtC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAMjE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAMzE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;IAkB5F,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAM3F,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjG,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IAQnE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAMpF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAMvF,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1F,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAoB1C,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAapF"}
1
+ {"version":3,"file":"leveldb-storage.d.ts","sourceRoot":"","sources":["../../src/leveldb-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAkB5D;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,WAAW;IAItC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAI9D,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/D,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAI3E,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAgBhH,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAIjF,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjE,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IAWhE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAIrF,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAItF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvF,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3D,YAAY,IAAI,aAAa,CAAC,OAAO,CAAC;IAgBvC,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;CAyB7C;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IAC1C,YAAY,EAAE,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,uBAAuB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBAE3C,EAAE,EAAE,WAAW;CAG3B"}
@@ -1,105 +1,124 @@
1
+ import { KvRawStorage } from '@optimystic/db-p2p';
1
2
  import { drain } from './leveldb-like.js';
2
- import { TAG_PENDING, actionIdFromKey, blockEnvelopeRange, materializedKey, metadataKey, pendingKey, revisionFromKey, revisionKey, transactionKey, } from './keys.js';
3
+ import { TAG_PENDING, actionIdFromKey, blockEnvelopeRange, blockIdFromMetadataKey, materializedKey, metadataKey, metadataRange, pendingKey, revisionFromKey, revisionKey, transactionKey, } from './keys.js';
3
4
  import { createLogger } from './logger.js';
4
5
  const log = createLogger('storage:leveldb');
5
- const textEncoder = new TextEncoder();
6
- const textDecoder = new TextDecoder();
7
6
  /**
8
- * LevelDB-backed `IRawStorage` implementation for React Native peers.
7
+ * LevelDB {@link RawStoreDriver}: the five logical block-storage stores mapped
8
+ * into a single ordered byte keyspace, partitioned by a leading tag byte (see
9
+ * `./keys.ts`). LevelDB is already one ordered byte-keyspace, so this driver is
10
+ * mostly key encoding — the tag-prefixed byte scheme in `keys.ts` stays in this
11
+ * package; the kernel deals in logical keys.
9
12
  *
10
- * All data lives in a single LevelDB database; per-store partitioning is by a
11
- * leading tag byte (see `./keys.ts`). `listRevisions` and
12
- * `listPendingTransactions` use range iterators with explicit bounds, drained
13
- * into an array before yielding (same rationale as the IndexedDB / SQLite
14
- * backends — a native iterator must not stay open across consumer awaits).
15
- *
16
- * `promotePendingTransaction` runs as a single `WriteBatch`, making the
17
- * pending → committed move atomic against crashes.
13
+ * `KvRawStorage` now owns all JSON/UTF-8 serialization, so this driver only ever
14
+ * reads/writes raw `Uint8Array` values — no `TextEncoder`/`TextDecoder` for
15
+ * values, and no `JSON.parse`/`stringify`. A value round-trips as the exact bytes
16
+ * the kernel wrote. Everything LevelDB-specific stays here: the tag-range scans
17
+ * (drained snapshot-first before yielding, so a native iterator never straddles a
18
+ * consumer's `await`), and the single `WriteBatch` that makes `promote` atomic.
18
19
  */
19
- export class LevelDBRawStorage {
20
+ export class LevelDBStoreDriver {
20
21
  db;
21
22
  constructor(db) {
22
23
  this.db = db;
23
24
  }
25
+ // --- metadata ---
24
26
  async getMetadata(blockId) {
25
- const bytes = await this.db.get(metadataKey(blockId));
26
- if (!bytes)
27
- return undefined;
28
- return JSON.parse(textDecoder.decode(bytes));
27
+ return this.db.get(metadataKey(blockId));
29
28
  }
30
- async saveMetadata(blockId, metadata) {
31
- await this.db.put(metadataKey(blockId), textEncoder.encode(JSON.stringify(metadata)));
29
+ async putMetadata(blockId, value) {
30
+ await this.db.put(metadataKey(blockId), value);
32
31
  }
32
+ // --- revisions ---
33
33
  async getRevision(blockId, rev) {
34
- const bytes = await this.db.get(revisionKey(blockId, rev));
35
- if (!bytes)
36
- return undefined;
37
- return textDecoder.decode(bytes);
38
- }
39
- async saveRevision(blockId, rev, actionId) {
40
- await this.db.put(revisionKey(blockId, rev), textEncoder.encode(actionId));
41
- }
42
- async *listRevisions(blockId, startRev, endRev) {
43
- const ascending = startRev <= endRev;
44
- const lo = ascending ? startRev : endRev;
45
- const hi = ascending ? endRev : startRev;
46
- // `revisionKey(blockId, hi)` is exactly the inclusive upper bound; LevelDB
47
- // uses exclusive `lt`, so request `lte` via `lt = key(hi)+0x01` would
48
- // require an extra byte. Easier: use `lt = revisionKey(blockId, hi+1)`.
34
+ return this.db.get(revisionKey(blockId, rev));
35
+ }
36
+ async putRevision(blockId, rev, value) {
37
+ await this.db.put(revisionKey(blockId, rev), value);
38
+ }
39
+ async *rangeRevisions(blockId, lo, hi, reverse) {
40
+ // Revs are encoded 8-byte big-endian (`revisionKey`), so byte order == numeric
41
+ // order and `revisionKey(blockId, hi)` is exactly the inclusive upper bound;
42
+ // LevelDB's upper bound is exclusive (`lt`), so use `lt = revisionKey(hi + 1)`.
49
43
  const gte = revisionKey(blockId, lo);
50
44
  const lt = revisionKey(blockId, hi + 1);
51
- const entries = await drain(this.db.iterator({ gte, lt, reverse: !ascending }));
45
+ // Drain before yielding — a native LevelDB iterator must not stay open across
46
+ // the consumer's awaits (the kernel's drain-before-yield contract).
47
+ const entries = await drain(this.db.iterator({ gte, lt, reverse }));
52
48
  for (const [key, value] of entries) {
53
- yield {
54
- rev: revisionFromKey(key),
55
- actionId: textDecoder.decode(value),
56
- };
49
+ yield [revisionFromKey(key), value];
57
50
  }
58
51
  }
59
- async getPendingTransaction(blockId, actionId) {
60
- const bytes = await this.db.get(pendingKey(blockId, actionId));
61
- if (!bytes)
62
- return undefined;
63
- return JSON.parse(textDecoder.decode(bytes));
52
+ // --- pending ---
53
+ async getPending(blockId, actionId) {
54
+ return this.db.get(pendingKey(blockId, actionId));
64
55
  }
65
- async savePendingTransaction(blockId, actionId, transform) {
66
- await this.db.put(pendingKey(blockId, actionId), textEncoder.encode(JSON.stringify(transform)));
56
+ async putPending(blockId, actionId, value) {
57
+ await this.db.put(pendingKey(blockId, actionId), value);
67
58
  }
68
- async deletePendingTransaction(blockId, actionId) {
59
+ async deletePending(blockId, actionId) {
69
60
  await this.db.delete(pendingKey(blockId, actionId));
70
61
  }
71
- async *listPendingTransactions(blockId) {
62
+ async *listPendingActionIds(blockId) {
72
63
  const range = blockEnvelopeRange(TAG_PENDING, blockId);
64
+ // Drain before yielding, same rationale as rangeRevisions.
73
65
  const entries = await drain(this.db.iterator({ gte: range.gte, lt: range.lt, keys: true }));
74
66
  for (const [key] of entries) {
75
67
  yield actionIdFromKey(key, blockId);
76
68
  }
77
69
  }
70
+ // --- transactions ---
78
71
  async getTransaction(blockId, actionId) {
79
- const bytes = await this.db.get(transactionKey(blockId, actionId));
80
- if (!bytes)
81
- return undefined;
82
- return JSON.parse(textDecoder.decode(bytes));
83
- }
84
- async saveTransaction(blockId, actionId, transform) {
85
- await this.db.put(transactionKey(blockId, actionId), textEncoder.encode(JSON.stringify(transform)));
86
- }
87
- async getMaterializedBlock(blockId, actionId) {
88
- const bytes = await this.db.get(materializedKey(blockId, actionId));
89
- if (!bytes)
90
- return undefined;
91
- return JSON.parse(textDecoder.decode(bytes));
92
- }
93
- async saveMaterializedBlock(blockId, actionId, block) {
94
- const key = materializedKey(blockId, actionId);
95
- if (block) {
96
- await this.db.put(key, textEncoder.encode(JSON.stringify(block)));
72
+ return this.db.get(transactionKey(blockId, actionId));
73
+ }
74
+ async putTransaction(blockId, actionId, value) {
75
+ await this.db.put(transactionKey(blockId, actionId), value);
76
+ }
77
+ // --- materialized ---
78
+ async getMaterialized(blockId, actionId) {
79
+ return this.db.get(materializedKey(blockId, actionId));
80
+ }
81
+ async putMaterialized(blockId, actionId, value) {
82
+ await this.db.put(materializedKey(blockId, actionId), value);
83
+ }
84
+ // The kernel owns the put-or-delete branch of `saveMaterializedBlock`, so the
85
+ // driver exposes delete as a separate op.
86
+ async deleteMaterialized(blockId, actionId) {
87
+ await this.db.delete(materializedKey(blockId, actionId));
88
+ }
89
+ // --- promote (the only cross-key atomic op) ---
90
+ async promote(blockId, actionId) {
91
+ const pKey = pendingKey(blockId, actionId);
92
+ const value = await this.db.get(pKey);
93
+ if (!value) {
94
+ throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
97
95
  }
98
- else {
99
- await this.db.delete(key);
96
+ // The single `WriteBatch` IS the atomic move: a crash leaves either the
97
+ // pending or the committed entry, never both/neither.
98
+ const tKey = transactionKey(blockId, actionId);
99
+ await this.db
100
+ .batch()
101
+ .put(tKey, value)
102
+ .delete(pKey)
103
+ .write();
104
+ }
105
+ // --- optional passthroughs ---
106
+ async *listBlockIds() {
107
+ // Scan the whole TAG_METADATA keyspace; each metadata key is one distinct
108
+ // block id (empty suffix). keys:true skips values — we only need the ids.
109
+ // Drained before yielding (same discipline as the range scans): a native
110
+ // iterator must not stay open across consumer awaits.
111
+ const range = metadataRange();
112
+ const entries = await drain(this.db.iterator({ gte: range.gte, lt: range.lt, keys: true }));
113
+ for (const [key] of entries) {
114
+ yield blockIdFromMetadataKey(key);
100
115
  }
101
116
  }
102
- async getApproximateBytesUsed() {
117
+ // NOTE: full-scan byte sum — O(total bytes) per call. Fine as an advisory
118
+ // StorageMonitor input at current sizes; if a StorageMonitor ever polls this
119
+ // on a large db, replace with the kernel's incremental write-path counter
120
+ // (see the seam NOTE in kv-raw-storage.ts's saveMetadata).
121
+ async approximateBytesUsed() {
103
122
  try {
104
123
  let total = 0;
105
124
  const iter = this.db.iterator();
@@ -117,22 +136,26 @@ export class LevelDBRawStorage {
117
136
  return total;
118
137
  }
119
138
  catch (err) {
120
- log('getApproximateBytesUsed iterator failed: %o', err);
139
+ log('approximateBytesUsed iterator failed: %o', err);
121
140
  return 0;
122
141
  }
123
142
  }
124
- async promotePendingTransaction(blockId, actionId) {
125
- const pKey = pendingKey(blockId, actionId);
126
- const value = await this.db.get(pKey);
127
- if (!value) {
128
- throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
129
- }
130
- const tKey = transactionKey(blockId, actionId);
131
- await this.db
132
- .batch()
133
- .put(tKey, value)
134
- .delete(pKey)
135
- .write();
143
+ }
144
+ /**
145
+ * LevelDB-backed {@link IRawStorage} for React Native peers, now a thin shell
146
+ * over the shared {@link KvRawStorage} kernel driven by a {@link LevelDBStoreDriver}.
147
+ * The public name/constructor (`new LevelDBRawStorage(db)`) is unchanged so
148
+ * existing imports keep resolving; the kernel supplies the `IRawStorage` surface
149
+ * and the driver supplies LevelDB behavior.
150
+ *
151
+ * `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
152
+ * (the LevelDB driver always implements them, so the kernel constructor always
153
+ * wires them) — the base declares them optional, but every RN consumer relies on
154
+ * them.
155
+ */
156
+ export class LevelDBRawStorage extends KvRawStorage {
157
+ constructor(db) {
158
+ super(new LevelDBStoreDriver(db));
136
159
  }
137
160
  }
138
161
  //# sourceMappingURL=leveldb-storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"leveldb-storage.js","sourceRoot":"","sources":["../../src/leveldb-storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"leveldb-storage.js","sourceRoot":"","sources":["../../src/leveldb-storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optimystic/db-p2p-storage-rn",
3
- "version": "0.14.1",
3
+ "version": "0.16.2",
4
4
  "type": "module",
5
5
  "description": "React Native LevelDB storage backend for @optimystic/db-p2p",
6
6
  "main": "dist/src/index.js",
@@ -54,8 +54,8 @@
54
54
  "typescript": "^5.9.3"
55
55
  },
56
56
  "dependencies": {
57
- "@optimystic/db-core": "^0.14.1",
58
- "@optimystic/db-p2p": "^0.14.1",
57
+ "@optimystic/db-core": "^0.16.2",
58
+ "@optimystic/db-p2p": "^0.16.2",
59
59
  "debug": "^4.4.3"
60
60
  },
61
61
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { LevelDBRawStorage } from './leveldb-storage.js';
1
+ export { LevelDBRawStorage, LevelDBStoreDriver } from './leveldb-storage.js';
2
2
  export { LevelDBKVStore } from './leveldb-kv-store.js';
3
3
  export { loadOrCreateRNPeerKey, DEFAULT_PEER_KEY_NAME } from './identity.js';
4
4
  export {
package/src/keys.ts CHANGED
@@ -59,6 +59,25 @@ export function metadataKey(blockId: string): Uint8Array {
59
59
  return encodeBlockEnvelope(TAG_METADATA, blockId);
60
60
  }
61
61
 
62
+ /**
63
+ * Returns the inclusive lower / exclusive upper range covering every metadata
64
+ * key. Metadata keys are `TAG_METADATA || len(blockId) || blockId` with an empty
65
+ * suffix, so every metadata key sorts in `[TAG_METADATA, TAG_METADATA+1)`. The
66
+ * tag bytes are deliberately spaced (0x01, 0x02, ...) so `TAG_METADATA + 1` is
67
+ * the next tag byte and a clean exclusive upper bound — a revision / pending /
68
+ * transaction key for the same block (higher tag) falls outside this range.
69
+ */
70
+ export function metadataRange(): { gte: Uint8Array; lt: Uint8Array } {
71
+ return { gte: Uint8Array.of(TAG_METADATA), lt: Uint8Array.of(TAG_METADATA + 1) };
72
+ }
73
+
74
+ /** Decode the `blockId` from a `metadataKey`-encoded key (empty suffix). */
75
+ export function blockIdFromMetadataKey(key: Uint8Array): string {
76
+ const view = new DataView(key.buffer, key.byteOffset, key.byteLength);
77
+ const blockIdLen = view.getUint32(1, false);
78
+ return textDecoder.decode(key.subarray(5, 5 + blockIdLen));
79
+ }
80
+
62
81
  export function revisionKey(blockId: string, rev: number): Uint8Array {
63
82
  const envelope = encodeBlockEnvelope(TAG_REVISIONS, blockId);
64
83
  const out = new Uint8Array(envelope.length + 8);
@@ -1,12 +1,14 @@
1
- import type { ActionId, ActionRev, BlockId, IBlock, Transform } from '@optimystic/db-core';
2
- import type { BlockMetadata, IRawStorage } from '@optimystic/db-p2p';
1
+ import type { ActionId, BlockId } from '@optimystic/db-core';
2
+ import { KvRawStorage, type RawStoreDriver } from '@optimystic/db-p2p';
3
3
  import { drain, type LevelDBLike } from './leveldb-like.js';
4
4
  import {
5
5
  TAG_PENDING,
6
6
  actionIdFromKey,
7
7
  blockEnvelopeRange,
8
+ blockIdFromMetadataKey,
8
9
  materializedKey,
9
10
  metadataKey,
11
+ metadataRange,
10
12
  pendingKey,
11
13
  revisionFromKey,
12
14
  revisionKey,
@@ -16,110 +18,143 @@ import { createLogger } from './logger.js';
16
18
 
17
19
  const log = createLogger('storage:leveldb');
18
20
 
19
- const textEncoder = new TextEncoder();
20
- const textDecoder = new TextDecoder();
21
-
22
21
  /**
23
- * LevelDB-backed `IRawStorage` implementation for React Native peers.
24
- *
25
- * All data lives in a single LevelDB database; per-store partitioning is by a
26
- * leading tag byte (see `./keys.ts`). `listRevisions` and
27
- * `listPendingTransactions` use range iterators with explicit bounds, drained
28
- * into an array before yielding (same rationale as the IndexedDB / SQLite
29
- * backends — a native iterator must not stay open across consumer awaits).
22
+ * LevelDB {@link RawStoreDriver}: the five logical block-storage stores mapped
23
+ * into a single ordered byte keyspace, partitioned by a leading tag byte (see
24
+ * `./keys.ts`). LevelDB is already one ordered byte-keyspace, so this driver is
25
+ * mostly key encoding — the tag-prefixed byte scheme in `keys.ts` stays in this
26
+ * package; the kernel deals in logical keys.
30
27
  *
31
- * `promotePendingTransaction` runs as a single `WriteBatch`, making the
32
- * pending → committed move atomic against crashes.
28
+ * `KvRawStorage` now owns all JSON/UTF-8 serialization, so this driver only ever
29
+ * reads/writes raw `Uint8Array` values — no `TextEncoder`/`TextDecoder` for
30
+ * values, and no `JSON.parse`/`stringify`. A value round-trips as the exact bytes
31
+ * the kernel wrote. Everything LevelDB-specific stays here: the tag-range scans
32
+ * (drained snapshot-first before yielding, so a native iterator never straddles a
33
+ * consumer's `await`), and the single `WriteBatch` that makes `promote` atomic.
33
34
  */
34
- export class LevelDBRawStorage implements IRawStorage {
35
+ export class LevelDBStoreDriver implements RawStoreDriver {
35
36
  constructor(private readonly db: LevelDBLike) {}
36
37
 
37
- async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
38
- const bytes = await this.db.get(metadataKey(blockId));
39
- if (!bytes) return undefined;
40
- return JSON.parse(textDecoder.decode(bytes)) as BlockMetadata;
38
+ // --- metadata ---
39
+
40
+ async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
41
+ return this.db.get(metadataKey(blockId));
41
42
  }
42
43
 
43
- async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
44
- await this.db.put(metadataKey(blockId), textEncoder.encode(JSON.stringify(metadata)));
44
+ async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
45
+ await this.db.put(metadataKey(blockId), value);
45
46
  }
46
47
 
47
- async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
48
- const bytes = await this.db.get(revisionKey(blockId, rev));
49
- if (!bytes) return undefined;
50
- return textDecoder.decode(bytes) as ActionId;
48
+ // --- revisions ---
49
+
50
+ async getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
51
+ return this.db.get(revisionKey(blockId, rev));
51
52
  }
52
53
 
53
- async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
54
- await this.db.put(revisionKey(blockId, rev), textEncoder.encode(actionId));
54
+ async putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
55
+ await this.db.put(revisionKey(blockId, rev), value);
55
56
  }
56
57
 
57
- async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
58
- const ascending = startRev <= endRev;
59
- const lo = ascending ? startRev : endRev;
60
- const hi = ascending ? endRev : startRev;
61
- // `revisionKey(blockId, hi)` is exactly the inclusive upper bound; LevelDB
62
- // uses exclusive `lt`, so request `lte` via `lt = key(hi)+0x01` would
63
- // require an extra byte. Easier: use `lt = revisionKey(blockId, hi+1)`.
58
+ async *rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]> {
59
+ // Revs are encoded 8-byte big-endian (`revisionKey`), so byte order == numeric
60
+ // order and `revisionKey(blockId, hi)` is exactly the inclusive upper bound;
61
+ // LevelDB's upper bound is exclusive (`lt`), so use `lt = revisionKey(hi + 1)`.
64
62
  const gte = revisionKey(blockId, lo);
65
63
  const lt = revisionKey(blockId, hi + 1);
66
- const entries = await drain(this.db.iterator({ gte, lt, reverse: !ascending }));
64
+ // Drain before yielding — a native LevelDB iterator must not stay open across
65
+ // the consumer's awaits (the kernel's drain-before-yield contract).
66
+ const entries = await drain(this.db.iterator({ gte, lt, reverse }));
67
67
  for (const [key, value] of entries) {
68
- yield {
69
- rev: revisionFromKey(key),
70
- actionId: textDecoder.decode(value) as ActionId,
71
- };
68
+ yield [revisionFromKey(key), value];
72
69
  }
73
70
  }
74
71
 
75
- async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
76
- const bytes = await this.db.get(pendingKey(blockId, actionId));
77
- if (!bytes) return undefined;
78
- return JSON.parse(textDecoder.decode(bytes)) as Transform;
72
+ // --- pending ---
73
+
74
+ async getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
75
+ return this.db.get(pendingKey(blockId, actionId));
79
76
  }
80
77
 
81
- async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
82
- await this.db.put(pendingKey(blockId, actionId), textEncoder.encode(JSON.stringify(transform)));
78
+ async putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
79
+ await this.db.put(pendingKey(blockId, actionId), value);
83
80
  }
84
81
 
85
- async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
82
+ async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
86
83
  await this.db.delete(pendingKey(blockId, actionId));
87
84
  }
88
85
 
89
- async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
86
+ async *listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId> {
90
87
  const range = blockEnvelopeRange(TAG_PENDING, blockId);
88
+ // Drain before yielding, same rationale as rangeRevisions.
91
89
  const entries = await drain(this.db.iterator({ gte: range.gte, lt: range.lt, keys: true }));
92
90
  for (const [key] of entries) {
93
91
  yield actionIdFromKey(key, blockId) as ActionId;
94
92
  }
95
93
  }
96
94
 
97
- async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
98
- const bytes = await this.db.get(transactionKey(blockId, actionId));
99
- if (!bytes) return undefined;
100
- return JSON.parse(textDecoder.decode(bytes)) as Transform;
95
+ // --- transactions ---
96
+
97
+ async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
98
+ return this.db.get(transactionKey(blockId, actionId));
99
+ }
100
+
101
+ async putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
102
+ await this.db.put(transactionKey(blockId, actionId), value);
103
+ }
104
+
105
+ // --- materialized ---
106
+
107
+ async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
108
+ return this.db.get(materializedKey(blockId, actionId));
101
109
  }
102
110
 
103
- async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
104
- await this.db.put(transactionKey(blockId, actionId), textEncoder.encode(JSON.stringify(transform)));
111
+ async putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void> {
112
+ await this.db.put(materializedKey(blockId, actionId), value);
105
113
  }
106
114
 
107
- async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
108
- const bytes = await this.db.get(materializedKey(blockId, actionId));
109
- if (!bytes) return undefined;
110
- return JSON.parse(textDecoder.decode(bytes)) as IBlock;
115
+ // The kernel owns the put-or-delete branch of `saveMaterializedBlock`, so the
116
+ // driver exposes delete as a separate op.
117
+ async deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void> {
118
+ await this.db.delete(materializedKey(blockId, actionId));
111
119
  }
112
120
 
113
- async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
114
- const key = materializedKey(blockId, actionId);
115
- if (block) {
116
- await this.db.put(key, textEncoder.encode(JSON.stringify(block)));
117
- } else {
118
- await this.db.delete(key);
121
+ // --- promote (the only cross-key atomic op) ---
122
+
123
+ async promote(blockId: BlockId, actionId: ActionId): Promise<void> {
124
+ const pKey = pendingKey(blockId, actionId);
125
+ const value = await this.db.get(pKey);
126
+ if (!value) {
127
+ throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
119
128
  }
129
+ // The single `WriteBatch` IS the atomic move: a crash leaves either the
130
+ // pending or the committed entry, never both/neither.
131
+ const tKey = transactionKey(blockId, actionId);
132
+ await this.db
133
+ .batch()
134
+ .put(tKey, value)
135
+ .delete(pKey)
136
+ .write();
120
137
  }
121
138
 
122
- async getApproximateBytesUsed(): Promise<number> {
139
+ // --- optional passthroughs ---
140
+
141
+ async *listBlockIds(): AsyncIterable<BlockId> {
142
+ // Scan the whole TAG_METADATA keyspace; each metadata key is one distinct
143
+ // block id (empty suffix). keys:true skips values — we only need the ids.
144
+ // Drained before yielding (same discipline as the range scans): a native
145
+ // iterator must not stay open across consumer awaits.
146
+ const range = metadataRange();
147
+ const entries = await drain(this.db.iterator({ gte: range.gte, lt: range.lt, keys: true }));
148
+ for (const [key] of entries) {
149
+ yield blockIdFromMetadataKey(key) as BlockId;
150
+ }
151
+ }
152
+
153
+ // NOTE: full-scan byte sum — O(total bytes) per call. Fine as an advisory
154
+ // StorageMonitor input at current sizes; if a StorageMonitor ever polls this
155
+ // on a large db, replace with the kernel's incremental write-path counter
156
+ // (see the seam NOTE in kv-raw-storage.ts's saveMetadata).
157
+ async approximateBytesUsed(): Promise<number> {
123
158
  try {
124
159
  let total = 0;
125
160
  const iter = this.db.iterator();
@@ -134,22 +169,35 @@ export class LevelDBRawStorage implements IRawStorage {
134
169
  }
135
170
  return total;
136
171
  } catch (err) {
137
- log('getApproximateBytesUsed iterator failed: %o', err);
172
+ log('approximateBytesUsed iterator failed: %o', err);
138
173
  return 0;
139
174
  }
140
175
  }
141
176
 
142
- async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
143
- const pKey = pendingKey(blockId, actionId);
144
- const value = await this.db.get(pKey);
145
- if (!value) {
146
- throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
147
- }
148
- const tKey = transactionKey(blockId, actionId);
149
- await this.db
150
- .batch()
151
- .put(tKey, value)
152
- .delete(pKey)
153
- .write();
177
+ // NOTE: intentionally NO close(). The `LevelDBLike` handle is shared with
178
+ // `LevelDBKVStore` and `loadOrCreateRNPeerKey` (one db per RN peer — see the
179
+ // README usage), so the driver must not own its lifecycle; closing it here
180
+ // would break the other two. Same rationale as the IndexedDB driver. The
181
+ // kernel never wires the optional close() regardless.
182
+ }
183
+
184
+ /**
185
+ * LevelDB-backed {@link IRawStorage} for React Native peers, now a thin shell
186
+ * over the shared {@link KvRawStorage} kernel driven by a {@link LevelDBStoreDriver}.
187
+ * The public name/constructor (`new LevelDBRawStorage(db)`) is unchanged so
188
+ * existing imports keep resolving; the kernel supplies the `IRawStorage` surface
189
+ * and the driver supplies LevelDB behavior.
190
+ *
191
+ * `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
192
+ * (the LevelDB driver always implements them, so the kernel constructor always
193
+ * wires them) — the base declares them optional, but every RN consumer relies on
194
+ * them.
195
+ */
196
+ export class LevelDBRawStorage extends KvRawStorage {
197
+ declare listBlockIds: () => AsyncIterable<BlockId>;
198
+ declare getApproximateBytesUsed: () => Promise<number>;
199
+
200
+ constructor(db: LevelDBLike) {
201
+ super(new LevelDBStoreDriver(db));
154
202
  }
155
203
  }