@optimystic/db-p2p-storage-rn 0.14.1 → 0.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/keys.d.ts +14 -0
- package/dist/src/keys.d.ts.map +1 -1
- package/dist/src/keys.js +17 -0
- package/dist/src/keys.js.map +1 -1
- package/dist/src/leveldb-storage.d.ts +48 -27
- package/dist/src/leveldb-storage.d.ts.map +1 -1
- package/dist/src/leveldb-storage.js +105 -82
- package/dist/src/leveldb-storage.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/keys.ts +19 -0
- package/src/leveldb-storage.ts +125 -77
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.
|
package/dist/src/index.d.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 { openOptimysticRNDb, wrapRNLevelDB, DEFAULT_DB_NAME, type OpenOptimysticRNDbOptions, type RNLevelDBNative, type RNLevelDBWriteBatchNative, type RNLevelDBIteratorNative, type RNLevelDBWriteBatchCtor, type RNLevelDBOpenFn, } from './rn-opener.js';
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,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';
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,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"}
|
package/dist/src/keys.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/keys.d.ts.map
CHANGED
|
@@ -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);
|
package/dist/src/keys.js.map
CHANGED
|
@@ -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,
|
|
2
|
-
import
|
|
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
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
18
|
+
export declare class LevelDBStoreDriver implements RawStoreDriver {
|
|
17
19
|
private readonly db;
|
|
18
20
|
constructor(db: LevelDBLike);
|
|
19
|
-
getMetadata(blockId: BlockId): Promise<
|
|
20
|
-
|
|
21
|
-
getRevision(blockId: BlockId, rev: number): Promise<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
getTransaction(blockId: BlockId, actionId: ActionId): Promise<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
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
|
-
|
|
26
|
-
if (!bytes)
|
|
27
|
-
return undefined;
|
|
28
|
-
return JSON.parse(textDecoder.decode(bytes));
|
|
27
|
+
return this.db.get(metadataKey(blockId));
|
|
29
28
|
}
|
|
30
|
-
async
|
|
31
|
-
await this.db.put(metadataKey(blockId),
|
|
29
|
+
async putMetadata(blockId, value) {
|
|
30
|
+
await this.db.put(metadataKey(blockId), value);
|
|
32
31
|
}
|
|
32
|
+
// --- revisions ---
|
|
33
33
|
async getRevision(blockId, rev) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
async
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
66
|
-
await this.db.put(pendingKey(blockId, actionId),
|
|
56
|
+
async putPending(blockId, actionId, value) {
|
|
57
|
+
await this.db.put(pendingKey(blockId, actionId), value);
|
|
67
58
|
}
|
|
68
|
-
async
|
|
59
|
+
async deletePending(blockId, actionId) {
|
|
69
60
|
await this.db.delete(pendingKey(blockId, actionId));
|
|
70
61
|
}
|
|
71
|
-
async *
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
async
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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('
|
|
139
|
+
log('approximateBytesUsed iterator failed: %o', err);
|
|
121
140
|
return 0;
|
|
122
141
|
}
|
|
123
142
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"leveldb-storage.js","sourceRoot":"","sources":["../../src/leveldb-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAuB,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,KAAK,EAAoB,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EACN,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,WAAW,EACX,aAAa,EACb,UAAU,EACV,eAAe,EACf,WAAW,EACX,cAAc,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,GAAG,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,kBAAkB;IACD;IAA7B,YAA6B,EAAe;QAAf,OAAE,GAAF,EAAE,CAAa;IAAG,CAAC;IAEhD,mBAAmB;IAEnB,KAAK,CAAC,WAAW,CAAC,OAAgB;QACjC,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAgB,EAAE,KAAiB;QACpD,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,oBAAoB;IAEpB,KAAK,CAAC,WAAW,CAAC,OAAgB,EAAE,GAAW;QAC9C,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAgB,EAAE,GAAW,EAAE,KAAiB;QACjE,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,CAAC,cAAc,CAAC,OAAgB,EAAE,EAAU,EAAE,EAAU,EAAE,OAAgB;QAC/E,+EAA+E;QAC/E,6EAA6E;QAC7E,gFAAgF;QAChF,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QACxC,8EAA8E;QAC9E,oEAAoE;QACpE,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACpE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QACrC,CAAC;IACF,CAAC;IAED,kBAAkB;IAElB,KAAK,CAAC,UAAU,CAAC,OAAgB,EAAE,QAAkB;QACpD,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAgB,EAAE,QAAkB,EAAE,KAAiB;QACvE,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAgB,EAAE,QAAkB;QACvD,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,CAAC,oBAAoB,CAAC,OAAgB;QAC3C,MAAM,KAAK,GAAG,kBAAkB,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACvD,2DAA2D;QAC3D,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC5F,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,eAAe,CAAC,GAAG,EAAE,OAAO,CAAa,CAAC;QACjD,CAAC;IACF,CAAC;IAED,uBAAuB;IAEvB,KAAK,CAAC,cAAc,CAAC,OAAgB,EAAE,QAAkB;QACxD,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAAgB,EAAE,QAAkB,EAAE,KAAiB;QAC3E,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IAC7D,CAAC;IAED,uBAAuB;IAEvB,KAAK,CAAC,eAAe,CAAC,OAAgB,EAAE,QAAkB;QACzD,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,OAAgB,EAAE,QAAkB,EAAE,KAAiB;QAC5E,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IAC9D,CAAC;IAED,8EAA8E;IAC9E,0CAA0C;IAC1C,KAAK,CAAC,kBAAkB,CAAC,OAAgB,EAAE,QAAkB;QAC5D,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,iDAAiD;IAEjD,KAAK,CAAC,OAAO,CAAC,OAAgB,EAAE,QAAkB;QACjD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,kBAAkB,QAAQ,wBAAwB,OAAO,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,wEAAwE;QACxE,sDAAsD;QACtD,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC/C,MAAM,IAAI,CAAC,EAAE;aACX,KAAK,EAAE;aACP,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC;aAChB,MAAM,CAAC,IAAI,CAAC;aACZ,KAAK,EAAE,CAAC;IACX,CAAC;IAED,gCAAgC;IAEhC,KAAK,CAAC,CAAC,YAAY;QAClB,0EAA0E;QAC1E,0EAA0E;QAC1E,yEAAyE;QACzE,sDAAsD;QACtD,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC5F,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,sBAAsB,CAAC,GAAG,CAAY,CAAC;QAC9C,CAAC;IACF,CAAC;IAED,0EAA0E;IAC1E,6EAA6E;IAC7E,0EAA0E;IAC1E,2DAA2D;IAC3D,KAAK,CAAC,oBAAoB;QACzB,IAAI,CAAC;YACJ,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;YAChC,IAAI,CAAC;gBACJ,OAAO,IAAI,EAAE,CAAC;oBACb,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;oBAChC,IAAI,CAAC,KAAK;wBAAE,MAAM;oBAClB,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACpD,CAAC;YACF,CAAC;oBAAS,CAAC;gBACV,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACpB,CAAC;YACD,OAAO,KAAK,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;YACrD,OAAO,CAAC,CAAC;QACV,CAAC;IACF,CAAC;CAOD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IAIlD,YAAY,EAAe;QAC1B,KAAK,CAAC,IAAI,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;CACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optimystic/db-p2p-storage-rn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.3",
|
|
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.
|
|
58
|
-
"@optimystic/db-p2p": "^0.
|
|
57
|
+
"@optimystic/db-core": "^0.16.3",
|
|
58
|
+
"@optimystic/db-p2p": "^0.16.3",
|
|
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);
|
package/src/leveldb-storage.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type { ActionId,
|
|
2
|
-
import
|
|
1
|
+
import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
2
|
+
import { KvRawStorage, type RawStoreDriver } from '@optimystic/db-p2p';
|
|
3
3
|
import { 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
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
-
* `
|
|
32
|
-
*
|
|
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
|
|
35
|
+
export class LevelDBStoreDriver implements RawStoreDriver {
|
|
35
36
|
constructor(private readonly db: LevelDBLike) {}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return
|
|
38
|
+
// --- metadata ---
|
|
39
|
+
|
|
40
|
+
async getMetadata(blockId: BlockId): Promise<Uint8Array | undefined> {
|
|
41
|
+
return this.db.get(metadataKey(blockId));
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
async
|
|
44
|
-
await this.db.put(metadataKey(blockId),
|
|
44
|
+
async putMetadata(blockId: BlockId, value: Uint8Array): Promise<void> {
|
|
45
|
+
await this.db.put(metadataKey(blockId), value);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
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
|
|
54
|
-
await this.db.put(revisionKey(blockId, rev),
|
|
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 *
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return
|
|
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
|
|
82
|
-
await this.db.put(pendingKey(blockId, actionId),
|
|
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
|
|
82
|
+
async deletePending(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
86
83
|
await this.db.delete(pendingKey(blockId, actionId));
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
async *
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
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
|
|
104
|
-
await this.db.put(
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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('
|
|
172
|
+
log('approximateBytesUsed iterator failed: %o', err);
|
|
138
173
|
return 0;
|
|
139
174
|
}
|
|
140
175
|
}
|
|
141
176
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
}
|