@optimystic/db-p2p-storage-fs 0.14.0 → 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 +118 -0
- package/dist/src/atomic-write.d.ts +22 -0
- package/dist/src/atomic-write.d.ts.map +1 -0
- package/dist/src/atomic-write.js +82 -0
- package/dist/src/atomic-write.js.map +1 -0
- package/dist/src/file-kv-store.d.ts.map +1 -1
- package/dist/src/file-kv-store.js +6 -3
- package/dist/src/file-kv-store.js.map +1 -1
- package/dist/src/file-storage.d.ts +52 -20
- package/dist/src/file-storage.d.ts.map +1 -1
- package/dist/src/file-storage.js +285 -69
- package/dist/src/file-storage.js.map +1 -1
- package/package.json +8 -4
- package/src/atomic-write.ts +82 -0
- package/src/file-kv-store.ts +6 -3
- package/src/file-storage.ts +304 -88
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @optimystic/db-p2p-storage-fs
|
|
2
|
+
|
|
3
|
+
Node.js filesystem storage backend for Optimystic peers. Provides:
|
|
4
|
+
|
|
5
|
+
- **`FileRawStorage`** — implements `IRawStorage` so a Node peer persists block
|
|
6
|
+
metadata, revisions, pending transactions, committed transactions, and
|
|
7
|
+
materialized blocks durably across restarts.
|
|
8
|
+
- **`FileKVStore`** — implements `IKVStore` for the persistent transaction state
|
|
9
|
+
used to recover crashed two-phase commits.
|
|
10
|
+
|
|
11
|
+
This package targets plain Node.js. Use the sibling adapter for other
|
|
12
|
+
environments:
|
|
13
|
+
|
|
14
|
+
- `@optimystic/db-p2p-storage-ns` — NativeScript (iOS/Android, SQLite)
|
|
15
|
+
- `@optimystic/db-p2p-storage-rn` — React Native (MMKV)
|
|
16
|
+
- `@optimystic/db-p2p-storage-web` — Browser (IndexedDB)
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
yarn add @optimystic/db-p2p-storage-fs @optimystic/db-p2p @optimystic/db-core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { FileRawStorage, FileKVStore } from '@optimystic/db-p2p-storage-fs';
|
|
28
|
+
import { createLibp2pNode } from '@optimystic/db-p2p';
|
|
29
|
+
|
|
30
|
+
const rawStorage = new FileRawStorage('/var/lib/my-peer/data');
|
|
31
|
+
const kvStore = new FileKVStore('/var/lib/my-peer/data');
|
|
32
|
+
|
|
33
|
+
const libp2p = await createLibp2pNode({
|
|
34
|
+
bootstrapNodes: [/* … */],
|
|
35
|
+
networkName: 'my-network',
|
|
36
|
+
rawStorage,
|
|
37
|
+
kvStore,
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Both constructors take a single `basePath` — the directory under which all
|
|
42
|
+
data is stored. They can share the same `basePath` in practice because their
|
|
43
|
+
top-level names do not collide: block data lives under `<blockId>/`
|
|
44
|
+
subdirectories (block ids are content-address hashes), while KV data lives
|
|
45
|
+
under directories named by the first segment of each key (a `/`-separated key
|
|
46
|
+
becomes nested subdirectories — e.g. `coordinator/key1` → `coordinator/key1.json`).
|
|
47
|
+
Sharing is safe only as long as no block id equals a KV key's first segment;
|
|
48
|
+
give them separate `basePath`s if you cannot guarantee that.
|
|
49
|
+
|
|
50
|
+
## On-disk layout
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
<basePath>/
|
|
54
|
+
<blockId>/
|
|
55
|
+
meta.json — BlockMetadata (JSON)
|
|
56
|
+
revs/<rev>.json — ActionId for that revision number
|
|
57
|
+
pend/<id>.json — pending Transform (two-phase commit, before promotion)
|
|
58
|
+
actions/<id>.json — committed Transform
|
|
59
|
+
blocks/<id>.json — materialized IBlock snapshot
|
|
60
|
+
<key-segment>/
|
|
61
|
+
<key-segment>.json — FileKVStore value; key "/" separators become subdirectories
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Action ids that contain a colon (e.g. `tx:abcd1234`) are percent-encoded in
|
|
65
|
+
filenames (`tx%3Aabcd1234.json`) for Windows compatibility; the storage layer
|
|
66
|
+
encodes and decodes transparently so callers always work with the canonical
|
|
67
|
+
id form.
|
|
68
|
+
|
|
69
|
+
## Atomic writes
|
|
70
|
+
|
|
71
|
+
Every write goes through `atomic-write.ts`: the new content is written to a
|
|
72
|
+
`.tmp` sibling, then renamed over the canonical path. A crash mid-write
|
|
73
|
+
therefore leaves either the complete old file or the complete new file — never
|
|
74
|
+
a partial/torn one.
|
|
75
|
+
|
|
76
|
+
## Identity
|
|
77
|
+
|
|
78
|
+
Unlike the `ns`/`rn`/`web` adapters, this package ships **no** identity module
|
|
79
|
+
and no `loadOrCreateFSPeerKey` helper. The reference peer that uses this adapter
|
|
80
|
+
does not persist its libp2p private key, so an fs-backed node gets a fresh
|
|
81
|
+
ephemeral peer id on every restart. Adding durable identity is a separate
|
|
82
|
+
feature — see Known limitations below.
|
|
83
|
+
|
|
84
|
+
## Tests
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
yarn test # mocha, minimal reporter
|
|
88
|
+
yarn test:verbose # mocha, spec reporter
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Two layers run here, both over a per-test `fs.mkdtemp` fixture with `afterEach`
|
|
92
|
+
cleanup:
|
|
93
|
+
|
|
94
|
+
- **Shared conformance suite** — `runRawStorageConformance('FileSystem', …)` from
|
|
95
|
+
`@optimystic/db-p2p/testing`, the one cross-backend parity target. It proves the
|
|
96
|
+
fs backend (now `KvRawStorage` over a `FileStoreDriver`) behaves identically to
|
|
97
|
+
every other backend: round-trips, `listRevisions` ordering, promote atomicity +
|
|
98
|
+
the exact missing-pend error, clone-on-store/read (structural via the byte
|
|
99
|
+
boundary), drain-before-yield iteration, and a `BlockStorage` parity slice.
|
|
100
|
+
- **fs-only tests** (`node:assert`) — the behaviors the shared suite can't cover:
|
|
101
|
+
atomic writes + torn-file corruption tolerance, `readdir` error discrimination,
|
|
102
|
+
colon-encoded filenames + the POSIX legacy raw-colon fallback, the directory-based
|
|
103
|
+
`listBlockIds` meta-gate, and `FileKVStore.list`/`delete`.
|
|
104
|
+
|
|
105
|
+
## Known limitations
|
|
106
|
+
|
|
107
|
+
**No cross-process lock.** Two separate Node processes pointing at the same
|
|
108
|
+
`basePath` can interleave writes with no coordination. The constructor carries
|
|
109
|
+
a TODO (`file-storage.ts:52`) to integrate
|
|
110
|
+
[`proper-lockfile`](https://www.npmjs.com/package/proper-lockfile) along with
|
|
111
|
+
an explicit `dispose()` pattern. Until that lands, `FileRawStorage` is
|
|
112
|
+
single-process only.
|
|
113
|
+
|
|
114
|
+
**Ephemeral peer identity.** There is no `loadOrCreateFSPeerKey` equivalent.
|
|
115
|
+
A Node peer backed by `FileRawStorage` gets a new libp2p peer id each restart.
|
|
116
|
+
If your use case requires a stable, restart-surviving identity, a future
|
|
117
|
+
`feat-fs-peer-identity` feature should add the key-persistence helper — it is
|
|
118
|
+
deliberately out of scope here.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomically write `content` to `filePath`.
|
|
3
|
+
*
|
|
4
|
+
* Writes to a unique `*.tmp` sibling, fsyncs the data, then renames it into
|
|
5
|
+
* place. `rename` over an existing file is atomic on POSIX and NTFS, so a
|
|
6
|
+
* concurrent reader only ever sees the complete old file or the complete new
|
|
7
|
+
* file — never a torn/half-written one. After the rename we best-effort fsync
|
|
8
|
+
* the containing directory so the rename itself survives power loss on POSIX;
|
|
9
|
+
* that directory fsync is unsupported on win32 and its error is swallowed.
|
|
10
|
+
*
|
|
11
|
+
* On any failure the temp file is removed (best-effort) so a crashed write does
|
|
12
|
+
* not leave the canonical path damaged. A crash *between* the temp write and the
|
|
13
|
+
* rename leaves only an inert `*.tmp` sibling — never read (reads target
|
|
14
|
+
* canonical paths) and skipped by `.json` directory scans.
|
|
15
|
+
*
|
|
16
|
+
* `content` is `string | Uint8Array`: `FileKVStore` writes strings, while the
|
|
17
|
+
* `KvRawStorage`-backed `FileStoreDriver` writes the kernel's raw value bytes.
|
|
18
|
+
* `FileHandle.writeFile` writes either losslessly — a `Uint8Array` is written
|
|
19
|
+
* byte-for-byte, so non-ASCII JSON round-trips exactly.
|
|
20
|
+
*/
|
|
21
|
+
export declare function atomicWriteFile(filePath: string, content: string | Uint8Array): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=atomic-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../../src/atomic-write.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCnG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
// Per-process monotonic counter for unique temp names. Combined with the pid it
|
|
4
|
+
// makes each temp path unique across concurrent writers to the same logical
|
|
5
|
+
// target — even two FileRawStorage/FileKVStore instances in one process, which
|
|
6
|
+
// do not lock (see the proper-lockfile TODO in file-storage.ts). Deterministic
|
|
7
|
+
// and collision-safe, unlike Math.random()/Date.now().
|
|
8
|
+
let tempCounter = 0;
|
|
9
|
+
/**
|
|
10
|
+
* Atomically write `content` to `filePath`.
|
|
11
|
+
*
|
|
12
|
+
* Writes to a unique `*.tmp` sibling, fsyncs the data, then renames it into
|
|
13
|
+
* place. `rename` over an existing file is atomic on POSIX and NTFS, so a
|
|
14
|
+
* concurrent reader only ever sees the complete old file or the complete new
|
|
15
|
+
* file — never a torn/half-written one. After the rename we best-effort fsync
|
|
16
|
+
* the containing directory so the rename itself survives power loss on POSIX;
|
|
17
|
+
* that directory fsync is unsupported on win32 and its error is swallowed.
|
|
18
|
+
*
|
|
19
|
+
* On any failure the temp file is removed (best-effort) so a crashed write does
|
|
20
|
+
* not leave the canonical path damaged. A crash *between* the temp write and the
|
|
21
|
+
* rename leaves only an inert `*.tmp` sibling — never read (reads target
|
|
22
|
+
* canonical paths) and skipped by `.json` directory scans.
|
|
23
|
+
*
|
|
24
|
+
* `content` is `string | Uint8Array`: `FileKVStore` writes strings, while the
|
|
25
|
+
* `KvRawStorage`-backed `FileStoreDriver` writes the kernel's raw value bytes.
|
|
26
|
+
* `FileHandle.writeFile` writes either losslessly — a `Uint8Array` is written
|
|
27
|
+
* byte-for-byte, so non-ASCII JSON round-trips exactly.
|
|
28
|
+
*/
|
|
29
|
+
export async function atomicWriteFile(filePath, content) {
|
|
30
|
+
const dir = path.dirname(filePath);
|
|
31
|
+
await fs.mkdir(dir, { recursive: true });
|
|
32
|
+
// Suffix ends in `.tmp` (not `.json`) so listPendingTransactions / FileKVStore.list,
|
|
33
|
+
// which filter on `.json`, never surface an in-flight temp file.
|
|
34
|
+
// NOTE: a crash between open and rename leaves an inert `*.tmp` orphan (never read,
|
|
35
|
+
// skipped by `.json` scans). No cleanup sweep exists; if a crash-looping writer ever
|
|
36
|
+
// accumulates many, add a startup sweep of stale `*.tmp` siblings.
|
|
37
|
+
const tmpPath = path.join(dir, `${path.basename(filePath)}.${process.pid}.${tempCounter++}.tmp`);
|
|
38
|
+
let handle;
|
|
39
|
+
try {
|
|
40
|
+
handle = await fs.open(tmpPath, 'w');
|
|
41
|
+
await handle.writeFile(content);
|
|
42
|
+
await handle.sync();
|
|
43
|
+
await handle.close();
|
|
44
|
+
handle = undefined;
|
|
45
|
+
// NOTE: on win32, rename-over-existing can throw EPERM/EACCES/EBUSY if a
|
|
46
|
+
// concurrent reader holds the target open without FILE_SHARE_DELETE (Node
|
|
47
|
+
// readIfExists/get open→read→close in a tiny window, so it's rare). Modern
|
|
48
|
+
// libuv retries some cases; if this ever surfaces as spurious write failures
|
|
49
|
+
// under concurrent read+write on Windows, add a bounded retry loop here (the
|
|
50
|
+
// write-file-atomic package does exactly this). Conditional — the adapter is
|
|
51
|
+
// already last-writer-wins with no cross-process lock (proper-lockfile TODO
|
|
52
|
+
// in file-storage.ts), so it is not reachable under current single-writer use.
|
|
53
|
+
await fs.rename(tmpPath, filePath);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (handle)
|
|
57
|
+
await handle.close().catch(() => { });
|
|
58
|
+
await fs.unlink(tmpPath).catch(() => { });
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
await fsyncDir(dir);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Best-effort fsync of a directory so a completed rename is durable. POSIX needs
|
|
65
|
+
* this; win32 (and platforms that reject opening a directory for fsync) throw,
|
|
66
|
+
* and we ignore that rather than failing the write.
|
|
67
|
+
*/
|
|
68
|
+
async function fsyncDir(dir) {
|
|
69
|
+
let handle;
|
|
70
|
+
try {
|
|
71
|
+
handle = await fs.open(dir, 'r');
|
|
72
|
+
await handle.sync();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Directory fsync unsupported here (e.g. win32) — nothing to do.
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
if (handle)
|
|
79
|
+
await handle.close().catch(() => { });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=atomic-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,gFAAgF;AAChF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,uDAAuD;AACvD,IAAI,WAAW,GAAG,CAAC,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,OAA4B;IACnF,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,qFAAqF;IACrF,iEAAiE;IACjE,oFAAoF;IACpF,qFAAqF;IACrF,mEAAmE;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,GAAG,IAAI,WAAW,EAAE,MAAM,CAAC,CAAC;IAEjG,IAAI,MAAiC,CAAC;IACtC,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,GAAG,SAAS,CAAC;QACnB,yEAAyE;QACzE,0EAA0E;QAC1E,2EAA2E;QAC3E,6EAA6E;QAC7E,6EAA6E;QAC7E,6EAA6E;QAC7E,4EAA4E;QAC5E,+EAA+E;QAC/E,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAqB,CAAC,CAAC,CAAC;QACpE,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAyC,CAAC,CAAC,CAAC;QAChF,MAAM,GAAG,CAAC;IACX,CAAC;IAED,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,QAAQ,CAAC,GAAW;IAClC,IAAI,MAAiC,CAAC;IACtC,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACR,iEAAiE;IAClE,CAAC;YAAS,CAAC;QACV,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAqB,CAAC,CAAC,CAAC;IACrE,CAAC;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-kv-store.d.ts","sourceRoot":"","sources":["../../src/file-kv-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-kv-store.d.ts","sourceRoot":"","sources":["../../src/file-kv-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD,+FAA+F;AAC/F,qBAAa,WAAY,YAAW,QAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAS7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAS/B,aAAa;IAkB3B,OAAO,CAAC,SAAS;CAOjB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import { atomicWriteFile } from './atomic-write.js';
|
|
3
4
|
/** Filesystem-backed IKVStore. Keys may contain `/` separators which become subdirectories. */
|
|
4
5
|
export class FileKVStore {
|
|
5
6
|
basePath;
|
|
@@ -17,9 +18,7 @@ export class FileKVStore {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
async set(key, value) {
|
|
20
|
-
|
|
21
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
22
|
-
await fs.writeFile(filePath, value);
|
|
21
|
+
await atomicWriteFile(this.keyToPath(key), value);
|
|
23
22
|
}
|
|
24
23
|
async delete(key) {
|
|
25
24
|
try {
|
|
@@ -59,6 +58,10 @@ export class FileKVStore {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
keyToPath(key) {
|
|
61
|
+
// NOTE: `/`-separated keys become nested dirs, so a key's first segment shares
|
|
62
|
+
// the top-level namespace with FileRawStorage's <blockId>/ dirs. Safe today
|
|
63
|
+
// because block ids are content hashes; if a KV key's first segment could ever
|
|
64
|
+
// equal a block id, give the two stores separate basePaths (see README Usage).
|
|
62
65
|
return path.join(this.basePath, ...key.split('/')) + '.json';
|
|
63
66
|
}
|
|
64
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-kv-store.js","sourceRoot":"","sources":["../../src/file-kv-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"file-kv-store.js","sourceRoot":"","sources":["../../src/file-kv-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,63 @@
|
|
|
1
|
-
import type { BlockId,
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import type { BlockId, ActionId } from "@optimystic/db-core";
|
|
2
|
+
import { KvRawStorage, type RawStoreDriver } from "@optimystic/db-p2p";
|
|
3
|
+
/**
|
|
4
|
+
* Filesystem {@link RawStoreDriver}: the five logical block-storage stores mapped
|
|
5
|
+
* to five subdirectories under `basePath/<blockId>/`
|
|
6
|
+
* (`{meta.json,revs/,pend/,actions/,blocks/}`). The directory tree is a
|
|
7
|
+
* deliberate, human-inspectable/debuggable layout — it is NOT flattened into
|
|
8
|
+
* encoded-filename KV keys.
|
|
9
|
+
*
|
|
10
|
+
* `KvRawStorage` now owns all JSON serialization, so this driver reads/writes raw
|
|
11
|
+
* `Uint8Array` bytes and never does `JSON.stringify/parse` on values. Everything
|
|
12
|
+
* else fs-specific lives here: atomic (temp-file + rename) writes, the
|
|
13
|
+
* corrupt-content-as-missing read guard, colon-encoded action-id filenames with
|
|
14
|
+
* the legacy raw-colon read fallback + win32 guards, and rename-based promote.
|
|
15
|
+
*/
|
|
16
|
+
export declare class FileStoreDriver implements RawStoreDriver {
|
|
4
17
|
private readonly basePath;
|
|
5
18
|
constructor(basePath: string);
|
|
6
|
-
getMetadata(blockId: BlockId): Promise<
|
|
7
|
-
|
|
8
|
-
getRevision(blockId: BlockId, rev: number): Promise<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
getMetadata(blockId: BlockId): Promise<Uint8Array | undefined>;
|
|
20
|
+
putMetadata(blockId: BlockId, value: Uint8Array): Promise<void>;
|
|
21
|
+
getRevision(blockId: BlockId, rev: number): Promise<Uint8Array | undefined>;
|
|
22
|
+
putRevision(blockId: BlockId, rev: number, value: Uint8Array): Promise<void>;
|
|
23
|
+
rangeRevisions(blockId: BlockId, lo: number, hi: number, reverse: boolean): AsyncIterable<[number, Uint8Array]>;
|
|
24
|
+
getPending(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
25
|
+
putPending(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
26
|
+
deletePending(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
27
|
+
listPendingActionIds(blockId: BlockId): AsyncIterable<ActionId>;
|
|
28
|
+
getTransaction(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
29
|
+
putTransaction(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
30
|
+
getMaterialized(blockId: BlockId, actionId: ActionId): Promise<Uint8Array | undefined>;
|
|
31
|
+
putMaterialized(blockId: BlockId, actionId: ActionId, value: Uint8Array): Promise<void>;
|
|
32
|
+
deleteMaterialized(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
33
|
+
promote(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
34
|
+
listBlockIds(): AsyncIterable<BlockId>;
|
|
35
|
+
approximateBytesUsed(): Promise<number>;
|
|
20
36
|
private directoryByteSize;
|
|
21
|
-
promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
22
37
|
private getBlockPath;
|
|
23
38
|
private getMetadataPath;
|
|
24
39
|
private getRevisionPath;
|
|
25
40
|
private getPendingActionPath;
|
|
26
41
|
private getActionPath;
|
|
27
42
|
private getMaterializedPath;
|
|
28
|
-
private
|
|
29
|
-
private
|
|
43
|
+
private unlinkRawColon;
|
|
44
|
+
private readActionScopedBytes;
|
|
45
|
+
private readBytesIfExists;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Filesystem-backed {@link IRawStorage}, now a thin shell over the shared
|
|
49
|
+
* {@link KvRawStorage} kernel driven by a {@link FileStoreDriver}. The public
|
|
50
|
+
* name/constructor (`new FileRawStorage(basePath)`) is unchanged so existing
|
|
51
|
+
* imports keep resolving; the kernel supplies the `IRawStorage` surface and the
|
|
52
|
+
* driver supplies fs behavior.
|
|
53
|
+
*
|
|
54
|
+
* `listBlockIds`/`getApproximateBytesUsed` are re-declared here as always-present
|
|
55
|
+
* (the fs driver always implements them, so the kernel constructor always wires
|
|
56
|
+
* them) — the base declares them optional, but every fs consumer relies on them.
|
|
57
|
+
*/
|
|
58
|
+
export declare class FileRawStorage extends KvRawStorage {
|
|
59
|
+
listBlockIds: () => AsyncIterable<BlockId>;
|
|
60
|
+
getApproximateBytesUsed: () => Promise<number>;
|
|
61
|
+
constructor(basePath: string);
|
|
30
62
|
}
|
|
31
63
|
//# sourceMappingURL=file-storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/file-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/file-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsCvE;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,YAAW,cAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAMvC,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;IAe/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;IAuBhH,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAOjF,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;IASjE,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IA8ChE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAOrF,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;IAOtF,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;IAWvE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB3D,YAAY,IAAI,aAAa,CAAC,OAAO,CAAC;IA4CvC,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;YAI/B,iBAAiB;IA6B/B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,mBAAmB;YASb,cAAc;YAsBd,qBAAqB;YAcrB,iBAAiB;CAc/B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,SAAQ,YAAY;IACvC,YAAY,EAAE,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,uBAAuB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;gBAE3C,QAAQ,EAAE,MAAM;CAG5B"}
|