@optimystic/db-p2p-storage-fs 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 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;AAEnD,+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;IAM9C,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;CAGjB"}
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
- const filePath = this.keyToPath(key);
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":"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"}
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;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,+FAA+F;AAC/F,MAAM,OAAO,WAAW;IACM;IAA7B,YAA6B,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAEjD,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,IAAI,CAAC;YACJ,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAK,GAA6B,EAAE,IAAI,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAC;YACxE,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;QACnC,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACvB,IAAI,CAAC;YACJ,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAK,GAA6B,EAAE,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;QAClE,CAAC;IACF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc;QACxB,oEAAoE;QACpE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC;QACnD,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,OAAO,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,OAAe,EAAE,MAAc,EAAE,OAAiB;QAC7E,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAK,GAA6B,EAAE,IAAI,KAAK,QAAQ;gBAAE,OAAO;YAC9D,MAAM,GAAG,CAAC;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC;gBAC5C,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;YAC9E,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3D,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,CAAC;QACF,CAAC;IACF,CAAC;IAEO,SAAS,CAAC,GAAW;QAC5B,+EAA+E;QAC/E,4EAA4E;QAC5E,+EAA+E;QAC/E,+EAA+E;QAC/E,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;IAC9D,CAAC;CACD"}
@@ -1,31 +1,63 @@
1
- import type { BlockId, IBlock, Transform, ActionId, ActionRev } from "@optimystic/db-core";
2
- import type { BlockMetadata, IRawStorage } from "@optimystic/db-p2p";
3
- export declare class FileRawStorage implements IRawStorage {
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<BlockMetadata | undefined>;
7
- saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void>;
8
- getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined>;
9
- saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void>;
10
- getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
11
- savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
12
- deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
13
- listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId>;
14
- getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
15
- listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev>;
16
- saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
17
- getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined>;
18
- saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void>;
19
- getApproximateBytesUsed(): Promise<number>;
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 readIfExists;
29
- private ensureAndWriteFile;
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,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAerE,qBAAa,cAAe,YAAW,WAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAIvC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAIjE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAIzE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9E,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAI3F,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjG,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5E,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IAenE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAInF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;IAS5F,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1F,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIvF,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc1F,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;YAIlC,iBAAiB;IA2BzB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpF,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;YAIb,YAAY;YASZ,kBAAkB;CAIhC"}
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"}