@optimystic/db-p2p-storage-web 0.24.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -108
- package/dist/src/db.d.ts +9 -2
- package/dist/src/db.d.ts.map +1 -1
- package/dist/src/db.js +6 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/indexeddb-storage.d.ts +9 -5
- package/dist/src/indexeddb-storage.d.ts.map +1 -1
- package/dist/src/indexeddb-storage.js +20 -5
- package/dist/src/indexeddb-storage.js.map +1 -1
- package/package.json +3 -3
- package/src/db.ts +104 -92
- package/src/indexeddb-storage.ts +28 -6
package/README.md
CHANGED
|
@@ -1,108 +1,110 @@
|
|
|
1
|
-
# @optimystic/db-p2p-storage-web
|
|
2
|
-
|
|
3
|
-
IndexedDB-backed storage backend for Optimystic browser peers. Provides:
|
|
4
|
-
|
|
5
|
-
- **`IndexedDBRawStorage`** — implements `IRawStorage` so a browser node persists
|
|
6
|
-
block metadata, revisions, pending transactions, committed transactions,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- `@optimystic/db-p2p-storage-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
|
|
77
|
-
`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
1
|
+
# @optimystic/db-p2p-storage-web
|
|
2
|
+
|
|
3
|
+
IndexedDB-backed storage backend for Optimystic browser peers. Provides:
|
|
4
|
+
|
|
5
|
+
- **`IndexedDBRawStorage`** — implements `IRawStorage` so a browser node persists
|
|
6
|
+
block metadata, revisions, pending transactions, committed transactions,
|
|
7
|
+
commit proofs, and
|
|
8
|
+
materialized blocks across page reloads.
|
|
9
|
+
- **`IndexedDBKVStore`** — implements `IKVStore` for the persistent transaction
|
|
10
|
+
state used to recover crashed two-phase commits.
|
|
11
|
+
- **`loadOrCreateBrowserPeerKey`** — generates an Ed25519 libp2p private key on
|
|
12
|
+
first run and persists it in the same IndexedDB database, giving the browser
|
|
13
|
+
peer a stable, reload-surviving identity.
|
|
14
|
+
|
|
15
|
+
This package is browser-only — it imports no Node built-ins. Counterparts:
|
|
16
|
+
|
|
17
|
+
- `@optimystic/db-p2p-storage-rn` — React Native (MMKV)
|
|
18
|
+
- `@optimystic/db-p2p-storage-fs` — Node filesystem
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
yarn add @optimystic/db-p2p-storage-web @optimystic/db-p2p @optimystic/db-core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`@optimystic/db-p2p` is consumed via its `react-native` / `./rn` entry point so
|
|
27
|
+
the Node-only filesystem code does not get bundled into the browser:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createLibp2pNode } from '@optimystic/db-p2p/rn';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createLibp2pNode } from '@optimystic/db-p2p/rn';
|
|
37
|
+
import {
|
|
38
|
+
openOptimysticWebDb,
|
|
39
|
+
IndexedDBRawStorage,
|
|
40
|
+
IndexedDBKVStore,
|
|
41
|
+
loadOrCreateBrowserPeerKey,
|
|
42
|
+
} from '@optimystic/db-p2p-storage-web';
|
|
43
|
+
|
|
44
|
+
const db = await openOptimysticWebDb(); // single shared handle
|
|
45
|
+
const rawStorage = new IndexedDBRawStorage(db); // → IRawStorage
|
|
46
|
+
const kvStore = new IndexedDBKVStore(db); // → IKVStore (txn recovery)
|
|
47
|
+
const privateKey = await loadOrCreateBrowserPeerKey(db);
|
|
48
|
+
|
|
49
|
+
const libp2p = await createLibp2pNode({
|
|
50
|
+
bootstrapNodes: [/* … */],
|
|
51
|
+
networkName: 'my-network',
|
|
52
|
+
privateKey,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The same `IDBPDatabase` handle is shared by all three consumers — IndexedDB
|
|
57
|
+
permits concurrent transactions across disjoint object stores, so the storage
|
|
58
|
+
layer parallelises naturally.
|
|
59
|
+
|
|
60
|
+
## Persistence semantics
|
|
61
|
+
|
|
62
|
+
The package opens a single IndexedDB database (`optimystic` by default) with
|
|
63
|
+
seven object stores. `IndexedDBRawStorage` is a thin shell over the shared
|
|
64
|
+
`KvRawStorage` kernel driven by an `IndexedDBStoreDriver`: the kernel owns all
|
|
65
|
+
JSON/UTF-8 serialization, so the six block-storage stores now hold opaque
|
|
66
|
+
kernel-encoded `Uint8Array` bytes rather than live objects. The keys and the
|
|
67
|
+
logical types the kernel decodes them back into are unchanged:
|
|
68
|
+
|
|
69
|
+
| Store | Key | Stored value | Decoded (logical) type |
|
|
70
|
+
|----------------|----------------------|--------------|------------------------|
|
|
71
|
+
| `metadata` | `blockId` | `Uint8Array` | `BlockMetadata` |
|
|
72
|
+
| `revisions` | `[blockId, rev]` | `Uint8Array` | `ActionId` |
|
|
73
|
+
| `pending` | `[blockId, actionId]`| `Uint8Array` | `Transform` |
|
|
74
|
+
| `transactions` | `[blockId, actionId]`| `Uint8Array` | `Transform` |
|
|
75
|
+
| `proofs` | `[blockId, rev]` | `Uint8Array` | `BlockCommitProof` |
|
|
76
|
+
| `materialized` | `[blockId, actionId]`| `Uint8Array` | `IBlock` |
|
|
77
|
+
| `kv` | `key` | `string` or `Uint8Array` | — (not kernel-backed) |
|
|
78
|
+
|
|
79
|
+
`listRevisions` and `listPendingTransactions` use real IndexedDB range cursors
|
|
80
|
+
— never `getAllKeys()` + JS filter — so list latency stays bounded as the store
|
|
81
|
+
grows. `promotePendingTransaction` runs as a single `readwrite` transaction
|
|
82
|
+
spanning `pending` and `transactions`, so the move is atomic across crashes.
|
|
83
|
+
|
|
84
|
+
`getApproximateBytesUsed()` returns `(await navigator.storage.estimate()).usage`,
|
|
85
|
+
which is **per-origin**, not per-database. That is adequate for `StorageMonitor`
|
|
86
|
+
— which uses the figure as an advisory ring-selection input — but is not a
|
|
87
|
+
precise per-database accounting.
|
|
88
|
+
|
|
89
|
+
## Identity
|
|
90
|
+
|
|
91
|
+
`loadOrCreateBrowserPeerKey(db, keyName?)` writes the libp2p private key as
|
|
92
|
+
raw protobuf bytes (`Uint8Array`) under `keyName` in the `kv` store
|
|
93
|
+
(default `peer-private-key`). IndexedDB stores typed arrays natively, so no
|
|
94
|
+
base64 round-trip is needed. To rotate the identity, delete that one key:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
await db.delete('kv', 'peer-private-key');
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
To wipe the entire backing store (debugging / dev reset):
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
indexedDB.deleteDatabase('optimystic');
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Tests
|
|
107
|
+
|
|
108
|
+
`yarn test` runs the spec suite under [`fake-indexeddb`](https://www.npmjs.com/package/fake-indexeddb)
|
|
109
|
+
in Node so CI doesn't need a real browser. Production code only uses the
|
|
110
|
+
IndexedDB W3C API, so fake-indexeddb is a faithful behavioural surrogate.
|
package/dist/src/db.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
|
3
3
|
/**
|
|
4
4
|
* IndexedDB schema for the Optimystic browser storage backend.
|
|
5
5
|
*
|
|
6
|
-
* The
|
|
6
|
+
* The six block-storage stores keep their original object stores and compound
|
|
7
7
|
* array keys, but their values are now opaque `Uint8Array` blobs: the shared
|
|
8
8
|
* `KvRawStorage` kernel owns JSON (de)serialization, so `IndexedDBStoreDriver`
|
|
9
9
|
* only ever hands IndexedDB kernel-encoded bytes. IndexedDB stores a
|
|
@@ -16,6 +16,9 @@ import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
|
16
16
|
* - `pending`: pending transform bytes keyed by `[blockId, actionId]`. Listing pending
|
|
17
17
|
* transactions for a block uses a key cursor over `[blockId, ...]`.
|
|
18
18
|
* - `transactions`: committed transform bytes keyed by `[blockId, actionId]`.
|
|
19
|
+
* - `proofs`: `BlockCommitProof` bytes keyed by `[blockId, rev]` — the same key shape
|
|
20
|
+
* as `revisions`, deliberately NOT the action keyspace (an action id is caller-chosen,
|
|
21
|
+
* so no reserved id could be relied on).
|
|
19
22
|
* - `materialized`: materialized block bytes keyed by `[blockId, actionId]`. Deleting
|
|
20
23
|
* the row is a driver `delete`, not a stored `undefined`.
|
|
21
24
|
* - `kv`: a generic string keyspace shared by `IndexedDBKVStore` and the
|
|
@@ -40,6 +43,10 @@ export interface OptimysticWebDB extends DBSchema {
|
|
|
40
43
|
key: [BlockId, ActionId];
|
|
41
44
|
value: Uint8Array;
|
|
42
45
|
};
|
|
46
|
+
proofs: {
|
|
47
|
+
key: [BlockId, number];
|
|
48
|
+
value: Uint8Array;
|
|
49
|
+
};
|
|
43
50
|
materialized: {
|
|
44
51
|
key: [BlockId, ActionId];
|
|
45
52
|
value: Uint8Array;
|
|
@@ -50,7 +57,7 @@ export interface OptimysticWebDB extends DBSchema {
|
|
|
50
57
|
};
|
|
51
58
|
}
|
|
52
59
|
export declare const DEFAULT_DB_NAME = "optimystic";
|
|
53
|
-
export declare const DEFAULT_DB_VERSION =
|
|
60
|
+
export declare const DEFAULT_DB_VERSION = 2;
|
|
54
61
|
export type OptimysticWebDBHandle = IDBPDatabase<OptimysticWebDB>;
|
|
55
62
|
/**
|
|
56
63
|
* Opens (and creates if necessary) the Optimystic IndexedDB database used by
|
package/dist/src/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,KAAK,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE7D
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,KAAK,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAChD,QAAQ,EAAE;QACT,GAAG,EAAE,OAAO,CAAC;QACb,KAAK,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,SAAS,EAAE;QACV,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,KAAK,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,OAAO,EAAE;QACR,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzB,KAAK,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,YAAY,EAAE;QACb,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzB,KAAK,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QACP,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvB,KAAK,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,YAAY,EAAE;QACb,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzB,KAAK,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,EAAE,EAAE;QACH,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;KAC3B,CAAC;CACF;AAED,eAAO,MAAM,eAAe,eAAe,CAAC;AAG5C,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACxC,IAAI,GAAE,MAAwB,EAC9B,OAAO,GAAE,MAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CA0BhC"}
|
package/dist/src/db.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { openDB } from 'idb';
|
|
2
2
|
export const DEFAULT_DB_NAME = 'optimystic';
|
|
3
|
-
|
|
3
|
+
// Bumped to 2 when the `proofs` object store was added. The `upgrade` hook is guarded
|
|
4
|
+
// by `objectStoreNames.contains`, so an existing v1 database gains only the new store.
|
|
5
|
+
export const DEFAULT_DB_VERSION = 2;
|
|
4
6
|
/**
|
|
5
7
|
* Opens (and creates if necessary) the Optimystic IndexedDB database used by
|
|
6
8
|
* `IndexedDBRawStorage`, `IndexedDBKVStore`, and `loadOrCreateBrowserPeerKey`.
|
|
@@ -23,6 +25,9 @@ export async function openOptimysticWebDb(name = DEFAULT_DB_NAME, version = DEFA
|
|
|
23
25
|
if (!db.objectStoreNames.contains('transactions')) {
|
|
24
26
|
db.createObjectStore('transactions');
|
|
25
27
|
}
|
|
28
|
+
if (!db.objectStoreNames.contains('proofs')) {
|
|
29
|
+
db.createObjectStore('proofs');
|
|
30
|
+
}
|
|
26
31
|
if (!db.objectStoreNames.contains('materialized')) {
|
|
27
32
|
db.createObjectStore('materialized');
|
|
28
33
|
}
|
package/dist/src/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoC,MAAM,KAAK,CAAC;AA4D/D,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC;AAC5C,sFAAsF;AACtF,uFAAuF;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAIpC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,OAAe,eAAe,EAC9B,UAAkB,kBAAkB;IAEpC,OAAO,MAAM,CAAkB,IAAI,EAAE,OAAO,EAAE;QAC7C,OAAO,CAAC,EAAE;YACT,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC/C,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YAClC,CAAC;YACD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAChD,EAAE,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;YACnC,CAAC;YACD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC9C,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACjC,CAAC;YACD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBACnD,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YACtC,CAAC;YACD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7C,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBACnD,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YACtC,CAAC;YACD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACF,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
2
|
-
import { KvRawStorage, type RawStoreDriver } from '@optimystic/db-p2p';
|
|
2
|
+
import { KvRawStorage, type RawStoreDriver, type StoreIdentity } from '@optimystic/db-p2p';
|
|
3
3
|
import type { OptimysticWebDBHandle } from './db.js';
|
|
4
4
|
/**
|
|
5
|
-
* IndexedDB {@link RawStoreDriver}: the
|
|
6
|
-
* to the
|
|
7
|
-
* `transactions`, `materialized`) with their
|
|
8
|
-
* is a code refactor, not a storage-format change at the key level.
|
|
5
|
+
* IndexedDB {@link RawStoreDriver}: the six logical block-storage stores mapped
|
|
6
|
+
* to the six IndexedDB object stores (`metadata`, `revisions`, `pending`,
|
|
7
|
+
* `transactions`, `proofs`, `materialized`) with their compound array keys.
|
|
9
8
|
*
|
|
10
9
|
* `KvRawStorage` now owns all JSON serialization, so this driver only ever
|
|
11
10
|
* reads/writes `Uint8Array` values — IndexedDB stores a typed array natively via
|
|
@@ -15,7 +14,9 @@ import type { OptimysticWebDBHandle } from './db.js';
|
|
|
15
14
|
*/
|
|
16
15
|
export declare class IndexedDBStoreDriver implements RawStoreDriver {
|
|
17
16
|
private readonly db;
|
|
17
|
+
private readonly identity;
|
|
18
18
|
constructor(db: OptimysticWebDBHandle);
|
|
19
|
+
storeIdentity(): StoreIdentity;
|
|
19
20
|
getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
|
|
20
21
|
putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
|
|
21
22
|
getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
|
|
@@ -27,6 +28,8 @@ export declare class IndexedDBStoreDriver implements RawStoreDriver {
|
|
|
27
28
|
listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
|
|
28
29
|
getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
29
30
|
putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
31
|
+
getProof(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
|
|
32
|
+
putProof(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
|
|
30
33
|
getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
31
34
|
putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
32
35
|
deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
@@ -49,6 +52,7 @@ export declare class IndexedDBStoreDriver implements RawStoreDriver {
|
|
|
49
52
|
export declare class IndexedDBRawStorage extends KvRawStorage {
|
|
50
53
|
listBlockIds: () => AsyncIterable<BlockId>;
|
|
51
54
|
getApproximateBytesUsed: () => Promise<number>;
|
|
55
|
+
getStoreIdentity: () => StoreIdentity;
|
|
52
56
|
constructor(db: OptimysticWebDBHandle);
|
|
53
57
|
}
|
|
54
58
|
//# sourceMappingURL=indexeddb-storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb-storage.d.ts","sourceRoot":"","sources":["../../src/indexeddb-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"indexeddb-storage.d.ts","sourceRoot":"","sources":["../../src/indexeddb-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { KvRawStorage } from '@optimystic/db-p2p';
|
|
1
|
+
import { KvRawStorage, identityForHandle } from '@optimystic/db-p2p';
|
|
2
2
|
import { createLogger } from './logger.js';
|
|
3
3
|
const log = createLogger('storage:indexeddb');
|
|
4
4
|
/**
|
|
5
|
-
* IndexedDB {@link RawStoreDriver}: the
|
|
6
|
-
* to the
|
|
7
|
-
* `transactions`, `materialized`) with their
|
|
8
|
-
* is a code refactor, not a storage-format change at the key level.
|
|
5
|
+
* IndexedDB {@link RawStoreDriver}: the six logical block-storage stores mapped
|
|
6
|
+
* to the six IndexedDB object stores (`metadata`, `revisions`, `pending`,
|
|
7
|
+
* `transactions`, `proofs`, `materialized`) with their compound array keys.
|
|
9
8
|
*
|
|
10
9
|
* `KvRawStorage` now owns all JSON serialization, so this driver only ever
|
|
11
10
|
* reads/writes `Uint8Array` values — IndexedDB stores a typed array natively via
|
|
@@ -15,8 +14,17 @@ const log = createLogger('storage:indexeddb');
|
|
|
15
14
|
*/
|
|
16
15
|
export class IndexedDBStoreDriver {
|
|
17
16
|
db;
|
|
17
|
+
// NOTE: identity is the HANDLE object, so two handles opened over the same IndexedDB database
|
|
18
|
+
// NAME read as two identities. Resolving that would mean naming the underlying database,
|
|
19
|
+
// which `OptimysticWebDBHandle` does not expose. The package opener (`db.ts`) hands out one
|
|
20
|
+
// handle per database name in practice, so this is the reachable case, not the complete one.
|
|
21
|
+
identity;
|
|
18
22
|
constructor(db) {
|
|
19
23
|
this.db = db;
|
|
24
|
+
this.identity = identityForHandle('idb-handle', db);
|
|
25
|
+
}
|
|
26
|
+
storeIdentity() {
|
|
27
|
+
return this.identity;
|
|
20
28
|
}
|
|
21
29
|
// --- metadata ---
|
|
22
30
|
async getMetadata(blockId) {
|
|
@@ -88,6 +96,13 @@ export class IndexedDBStoreDriver {
|
|
|
88
96
|
async putTransaction(blockId, actionId, value) {
|
|
89
97
|
await this.db.put('transactions', value, [blockId, actionId]);
|
|
90
98
|
}
|
|
99
|
+
// --- proofs (keyed [blockId, rev] like revisions; no delete — see RawStoreDriver.getProof) ---
|
|
100
|
+
async getProof(blockId, rev) {
|
|
101
|
+
return this.db.get('proofs', [blockId, rev]);
|
|
102
|
+
}
|
|
103
|
+
async putProof(blockId, rev, value) {
|
|
104
|
+
await this.db.put('proofs', value, [blockId, rev]);
|
|
105
|
+
}
|
|
91
106
|
// --- materialized ---
|
|
92
107
|
async getMaterialized(blockId, actionId) {
|
|
93
108
|
return this.db.get('materialized', [blockId, actionId]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb-storage.js","sourceRoot":"","sources":["../../src/indexeddb-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"indexeddb-storage.js","sourceRoot":"","sources":["../../src/indexeddb-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-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browser IndexedDB 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.25.0",
|
|
58
|
+
"@optimystic/db-p2p": "^0.25.0",
|
|
59
59
|
"debug": "^4.4.3",
|
|
60
60
|
"idb": "^8.0.3"
|
|
61
61
|
},
|
package/src/db.ts
CHANGED
|
@@ -1,92 +1,104 @@
|
|
|
1
|
-
import { openDB, type DBSchema, type IDBPDatabase } from 'idb';
|
|
2
|
-
import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* IndexedDB schema for the Optimystic browser storage backend.
|
|
6
|
-
*
|
|
7
|
-
* The
|
|
8
|
-
* array keys, but their values are now opaque `Uint8Array` blobs: the shared
|
|
9
|
-
* `KvRawStorage` kernel owns JSON (de)serialization, so `IndexedDBStoreDriver`
|
|
10
|
-
* only ever hands IndexedDB kernel-encoded bytes. IndexedDB stores a
|
|
11
|
-
* `Uint8Array` natively via structured clone and returns a `Uint8Array`, so the
|
|
12
|
-
* bytes round-trip without a codec here.
|
|
13
|
-
*
|
|
14
|
-
* - `metadata`: per-block metadata bytes keyed by `blockId`.
|
|
15
|
-
* - `revisions`: revision lookup keyed by `[blockId, rev]` mapping to actionId bytes.
|
|
16
|
-
* Range scans use `IDBKeyRange.bound([blockId, startRev], [blockId, endRev])`.
|
|
17
|
-
* - `pending`: pending transform bytes keyed by `[blockId, actionId]`. Listing pending
|
|
18
|
-
* transactions for a block uses a key cursor over `[blockId, ...]`.
|
|
19
|
-
* - `transactions`: committed transform bytes keyed by `[blockId, actionId]`.
|
|
20
|
-
* - `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!db.objectStoreNames.contains('
|
|
82
|
-
db.createObjectStore('
|
|
83
|
-
}
|
|
84
|
-
if (!db.objectStoreNames.contains('
|
|
85
|
-
db.createObjectStore('
|
|
86
|
-
}
|
|
87
|
-
if (!db.objectStoreNames.contains('
|
|
88
|
-
db.createObjectStore('
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
1
|
+
import { openDB, type DBSchema, type IDBPDatabase } from 'idb';
|
|
2
|
+
import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IndexedDB schema for the Optimystic browser storage backend.
|
|
6
|
+
*
|
|
7
|
+
* The six block-storage stores keep their original object stores and compound
|
|
8
|
+
* array keys, but their values are now opaque `Uint8Array` blobs: the shared
|
|
9
|
+
* `KvRawStorage` kernel owns JSON (de)serialization, so `IndexedDBStoreDriver`
|
|
10
|
+
* only ever hands IndexedDB kernel-encoded bytes. IndexedDB stores a
|
|
11
|
+
* `Uint8Array` natively via structured clone and returns a `Uint8Array`, so the
|
|
12
|
+
* bytes round-trip without a codec here.
|
|
13
|
+
*
|
|
14
|
+
* - `metadata`: per-block metadata bytes keyed by `blockId`.
|
|
15
|
+
* - `revisions`: revision lookup keyed by `[blockId, rev]` mapping to actionId bytes.
|
|
16
|
+
* Range scans use `IDBKeyRange.bound([blockId, startRev], [blockId, endRev])`.
|
|
17
|
+
* - `pending`: pending transform bytes keyed by `[blockId, actionId]`. Listing pending
|
|
18
|
+
* transactions for a block uses a key cursor over `[blockId, ...]`.
|
|
19
|
+
* - `transactions`: committed transform bytes keyed by `[blockId, actionId]`.
|
|
20
|
+
* - `proofs`: `BlockCommitProof` bytes keyed by `[blockId, rev]` — the same key shape
|
|
21
|
+
* as `revisions`, deliberately NOT the action keyspace (an action id is caller-chosen,
|
|
22
|
+
* so no reserved id could be relied on).
|
|
23
|
+
* - `materialized`: materialized block bytes keyed by `[blockId, actionId]`. Deleting
|
|
24
|
+
* the row is a driver `delete`, not a stored `undefined`.
|
|
25
|
+
* - `kv`: a generic string keyspace shared by `IndexedDBKVStore` and the
|
|
26
|
+
* identity helper. Identity keys are stored as raw `Uint8Array` blobs (under
|
|
27
|
+
* a separate logical store from string-only `IKVStore` data, but the same
|
|
28
|
+
* IndexedDB object store — IndexedDB stores typed arrays natively).
|
|
29
|
+
*/
|
|
30
|
+
export interface OptimysticWebDB extends DBSchema {
|
|
31
|
+
metadata: {
|
|
32
|
+
key: BlockId;
|
|
33
|
+
value: Uint8Array;
|
|
34
|
+
};
|
|
35
|
+
revisions: {
|
|
36
|
+
key: [BlockId, number];
|
|
37
|
+
value: Uint8Array;
|
|
38
|
+
};
|
|
39
|
+
pending: {
|
|
40
|
+
key: [BlockId, ActionId];
|
|
41
|
+
value: Uint8Array;
|
|
42
|
+
};
|
|
43
|
+
transactions: {
|
|
44
|
+
key: [BlockId, ActionId];
|
|
45
|
+
value: Uint8Array;
|
|
46
|
+
};
|
|
47
|
+
proofs: {
|
|
48
|
+
key: [BlockId, number];
|
|
49
|
+
value: Uint8Array;
|
|
50
|
+
};
|
|
51
|
+
materialized: {
|
|
52
|
+
key: [BlockId, ActionId];
|
|
53
|
+
value: Uint8Array;
|
|
54
|
+
};
|
|
55
|
+
kv: {
|
|
56
|
+
key: string;
|
|
57
|
+
value: string | Uint8Array;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_DB_NAME = 'optimystic';
|
|
62
|
+
// Bumped to 2 when the `proofs` object store was added. The `upgrade` hook is guarded
|
|
63
|
+
// by `objectStoreNames.contains`, so an existing v1 database gains only the new store.
|
|
64
|
+
export const DEFAULT_DB_VERSION = 2;
|
|
65
|
+
|
|
66
|
+
export type OptimysticWebDBHandle = IDBPDatabase<OptimysticWebDB>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Opens (and creates if necessary) the Optimystic IndexedDB database used by
|
|
70
|
+
* `IndexedDBRawStorage`, `IndexedDBKVStore`, and `loadOrCreateBrowserPeerKey`.
|
|
71
|
+
*
|
|
72
|
+
* The same handle can be safely shared across all three — IndexedDB connections
|
|
73
|
+
* support concurrent transactions across disjoint object stores.
|
|
74
|
+
*/
|
|
75
|
+
export async function openOptimysticWebDb(
|
|
76
|
+
name: string = DEFAULT_DB_NAME,
|
|
77
|
+
version: number = DEFAULT_DB_VERSION,
|
|
78
|
+
): Promise<OptimysticWebDBHandle> {
|
|
79
|
+
return openDB<OptimysticWebDB>(name, version, {
|
|
80
|
+
upgrade(db) {
|
|
81
|
+
if (!db.objectStoreNames.contains('metadata')) {
|
|
82
|
+
db.createObjectStore('metadata');
|
|
83
|
+
}
|
|
84
|
+
if (!db.objectStoreNames.contains('revisions')) {
|
|
85
|
+
db.createObjectStore('revisions');
|
|
86
|
+
}
|
|
87
|
+
if (!db.objectStoreNames.contains('pending')) {
|
|
88
|
+
db.createObjectStore('pending');
|
|
89
|
+
}
|
|
90
|
+
if (!db.objectStoreNames.contains('transactions')) {
|
|
91
|
+
db.createObjectStore('transactions');
|
|
92
|
+
}
|
|
93
|
+
if (!db.objectStoreNames.contains('proofs')) {
|
|
94
|
+
db.createObjectStore('proofs');
|
|
95
|
+
}
|
|
96
|
+
if (!db.objectStoreNames.contains('materialized')) {
|
|
97
|
+
db.createObjectStore('materialized');
|
|
98
|
+
}
|
|
99
|
+
if (!db.objectStoreNames.contains('kv')) {
|
|
100
|
+
db.createObjectStore('kv');
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
package/src/indexeddb-storage.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { ActionId, BlockId } from '@optimystic/db-core';
|
|
2
|
-
import { KvRawStorage, type RawStoreDriver } from '@optimystic/db-p2p';
|
|
2
|
+
import { KvRawStorage, identityForHandle, type RawStoreDriver, type StoreIdentity } from '@optimystic/db-p2p';
|
|
3
3
|
import type { OptimysticWebDBHandle } from './db.js';
|
|
4
4
|
import { createLogger } from './logger.js';
|
|
5
5
|
|
|
6
6
|
const log = createLogger('storage:indexeddb');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* IndexedDB {@link RawStoreDriver}: the
|
|
10
|
-
* to the
|
|
11
|
-
* `transactions`, `materialized`) with their
|
|
12
|
-
* is a code refactor, not a storage-format change at the key level.
|
|
9
|
+
* IndexedDB {@link RawStoreDriver}: the six logical block-storage stores mapped
|
|
10
|
+
* to the six IndexedDB object stores (`metadata`, `revisions`, `pending`,
|
|
11
|
+
* `transactions`, `proofs`, `materialized`) with their compound array keys.
|
|
13
12
|
*
|
|
14
13
|
* `KvRawStorage` now owns all JSON serialization, so this driver only ever
|
|
15
14
|
* reads/writes `Uint8Array` values — IndexedDB stores a typed array natively via
|
|
@@ -18,7 +17,19 @@ const log = createLogger('storage:indexeddb');
|
|
|
18
17
|
* yielding), and the single `readwrite` transaction that makes `promote` atomic.
|
|
19
18
|
*/
|
|
20
19
|
export class IndexedDBStoreDriver implements RawStoreDriver {
|
|
21
|
-
|
|
20
|
+
// NOTE: identity is the HANDLE object, so two handles opened over the same IndexedDB database
|
|
21
|
+
// NAME read as two identities. Resolving that would mean naming the underlying database,
|
|
22
|
+
// which `OptimysticWebDBHandle` does not expose. The package opener (`db.ts`) hands out one
|
|
23
|
+
// handle per database name in practice, so this is the reachable case, not the complete one.
|
|
24
|
+
private readonly identity: StoreIdentity;
|
|
25
|
+
|
|
26
|
+
constructor(private readonly db: OptimysticWebDBHandle) {
|
|
27
|
+
this.identity = identityForHandle('idb-handle', db);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
storeIdentity(): StoreIdentity {
|
|
31
|
+
return this.identity;
|
|
32
|
+
}
|
|
22
33
|
|
|
23
34
|
// --- metadata ---
|
|
24
35
|
|
|
@@ -105,6 +116,16 @@ export class IndexedDBStoreDriver implements RawStoreDriver {
|
|
|
105
116
|
await this.db.put('transactions', value, [blockId, actionId]);
|
|
106
117
|
}
|
|
107
118
|
|
|
119
|
+
// --- proofs (keyed [blockId, rev] like revisions; no delete — see RawStoreDriver.getProof) ---
|
|
120
|
+
|
|
121
|
+
async getProof(blockId: BlockId, rev: number): Promise<Uint8Array | undefined> {
|
|
122
|
+
return this.db.get('proofs', [blockId, rev]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async putProof(blockId: BlockId, rev: number, value: Uint8Array): Promise<void> {
|
|
126
|
+
await this.db.put('proofs', value, [blockId, rev]);
|
|
127
|
+
}
|
|
128
|
+
|
|
108
129
|
// --- materialized ---
|
|
109
130
|
|
|
110
131
|
async getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined> {
|
|
@@ -181,6 +202,7 @@ export class IndexedDBStoreDriver implements RawStoreDriver {
|
|
|
181
202
|
export class IndexedDBRawStorage extends KvRawStorage {
|
|
182
203
|
declare listBlockIds: () => AsyncIterable<BlockId>;
|
|
183
204
|
declare getApproximateBytesUsed: () => Promise<number>;
|
|
205
|
+
declare getStoreIdentity: () => StoreIdentity;
|
|
184
206
|
|
|
185
207
|
constructor(db: OptimysticWebDBHandle) {
|
|
186
208
|
super(new IndexedDBStoreDriver(db));
|