@optimystic/db-p2p-storage-fs 0.22.0 → 0.24.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 +118 -118
- package/dist/src/file-storage.d.ts.map +1 -1
- package/dist/src/file-storage.js +13 -9
- package/dist/src/file-storage.js.map +1 -1
- package/package.json +3 -3
- package/src/atomic-write.ts +82 -82
- package/src/file-kv-store.ts +65 -65
- package/src/file-storage.ts +422 -418
- package/src/logger.ts +7 -7
package/src/file-kv-store.ts
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import type { IKVStore } from '@optimystic/db-p2p';
|
|
4
|
-
import { atomicWriteFile } from './atomic-write.js';
|
|
5
|
-
|
|
6
|
-
/** Filesystem-backed IKVStore. Keys may contain `/` separators which become subdirectories. */
|
|
7
|
-
export class FileKVStore implements IKVStore {
|
|
8
|
-
constructor(private readonly basePath: string) {}
|
|
9
|
-
|
|
10
|
-
async get(key: string): Promise<string | undefined> {
|
|
11
|
-
try {
|
|
12
|
-
return await fs.readFile(this.keyToPath(key), 'utf-8');
|
|
13
|
-
} catch (err) {
|
|
14
|
-
if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return undefined;
|
|
15
|
-
throw err;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async set(key: string, value: string): Promise<void> {
|
|
20
|
-
await atomicWriteFile(this.keyToPath(key), value);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async delete(key: string): Promise<void> {
|
|
24
|
-
try {
|
|
25
|
-
await fs.unlink(this.keyToPath(key));
|
|
26
|
-
} catch (err) {
|
|
27
|
-
if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async list(prefix: string): Promise<string[]> {
|
|
32
|
-
// prefix like "coordinator/" → scan basePath/coordinator/ directory
|
|
33
|
-
const parts = prefix.split('/').filter(Boolean);
|
|
34
|
-
const dirPath = path.join(this.basePath, ...parts);
|
|
35
|
-
const results: string[] = [];
|
|
36
|
-
await this.listRecursive(dirPath, prefix, results);
|
|
37
|
-
return results;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private async listRecursive(dirPath: string, prefix: string, results: string[]): Promise<void> {
|
|
41
|
-
let entries;
|
|
42
|
-
try {
|
|
43
|
-
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
44
|
-
} catch (err) {
|
|
45
|
-
if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return;
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
-
if (entry.isDirectory()) {
|
|
50
|
-
const subPrefix = prefix + entry.name + '/';
|
|
51
|
-
await this.listRecursive(path.join(dirPath, entry.name), subPrefix, results);
|
|
52
|
-
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
53
|
-
results.push(prefix + entry.name.slice(0, -5));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private keyToPath(key: string): string {
|
|
59
|
-
// NOTE: `/`-separated keys become nested dirs, so a key's first segment shares
|
|
60
|
-
// the top-level namespace with FileRawStorage's <blockId>/ dirs. Safe today
|
|
61
|
-
// because block ids are content hashes; if a KV key's first segment could ever
|
|
62
|
-
// equal a block id, give the two stores separate basePaths (see README Usage).
|
|
63
|
-
return path.join(this.basePath, ...key.split('/')) + '.json';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { IKVStore } from '@optimystic/db-p2p';
|
|
4
|
+
import { atomicWriteFile } from './atomic-write.js';
|
|
5
|
+
|
|
6
|
+
/** Filesystem-backed IKVStore. Keys may contain `/` separators which become subdirectories. */
|
|
7
|
+
export class FileKVStore implements IKVStore {
|
|
8
|
+
constructor(private readonly basePath: string) {}
|
|
9
|
+
|
|
10
|
+
async get(key: string): Promise<string | undefined> {
|
|
11
|
+
try {
|
|
12
|
+
return await fs.readFile(this.keyToPath(key), 'utf-8');
|
|
13
|
+
} catch (err) {
|
|
14
|
+
if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return undefined;
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async set(key: string, value: string): Promise<void> {
|
|
20
|
+
await atomicWriteFile(this.keyToPath(key), value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async delete(key: string): Promise<void> {
|
|
24
|
+
try {
|
|
25
|
+
await fs.unlink(this.keyToPath(key));
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async list(prefix: string): Promise<string[]> {
|
|
32
|
+
// prefix like "coordinator/" → scan basePath/coordinator/ directory
|
|
33
|
+
const parts = prefix.split('/').filter(Boolean);
|
|
34
|
+
const dirPath = path.join(this.basePath, ...parts);
|
|
35
|
+
const results: string[] = [];
|
|
36
|
+
await this.listRecursive(dirPath, prefix, results);
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async listRecursive(dirPath: string, prefix: string, results: string[]): Promise<void> {
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
const subPrefix = prefix + entry.name + '/';
|
|
51
|
+
await this.listRecursive(path.join(dirPath, entry.name), subPrefix, results);
|
|
52
|
+
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
53
|
+
results.push(prefix + entry.name.slice(0, -5));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private keyToPath(key: string): string {
|
|
59
|
+
// NOTE: `/`-separated keys become nested dirs, so a key's first segment shares
|
|
60
|
+
// the top-level namespace with FileRawStorage's <blockId>/ dirs. Safe today
|
|
61
|
+
// because block ids are content hashes; if a KV key's first segment could ever
|
|
62
|
+
// equal a block id, give the two stores separate basePaths (see README Usage).
|
|
63
|
+
return path.join(this.basePath, ...key.split('/')) + '.json';
|
|
64
|
+
}
|
|
65
|
+
}
|