Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 1x 1x 1x 1x 1x 1x 1x 28x 1x 28x 28x 28x 1x 28x 28x 28x 1x 337x 337x 337x 1x 42x 42x 42x 42x 42x 2x 2x 40x 72x 72x 72x 40x 42x 1x 63x 63x 1x 75x 75x 75x 75x 1x 3x 3x 1x 16x 16x 16x 16x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 5x 5x 5x 5x 7x 7x 7x 7x 7x 1x 3x 3x 2x 3x 2x 2x 3x 1x 3x 3x 3x 3x 1x 4x 4x 1x 5x 5x 5x 5x 1x 107x 107x 76x 107x 107x 107x 107x 107x 31x 31x 107x 1x | import { createReadStream, createWriteStream, promises as fs } from 'node:fs';
import * as nodePath from 'node:path';
import { Readable, Writable } from 'node:stream';
import { dirname, normalizePath } from '../path.js';
import { chunked } from '../stream.js';
import type { ByteRange, VFSAdapter, VFSListEntry, VFSStat } from '../types.js';
/**
* Node.js filesystem backend. Useful for CLI tooling and server-side peers,
* and it is what makes the engine testable against a real filesystem rather
* than only against the in-memory adapter.
*/
export class NodeFsAdapter implements VFSAdapter {
readonly name: string;
readonly root: string;
constructor(root: string, name = nodePath.basename(root)) {
this.root = nodePath.resolve(root);
this.name = name;
}
/** Creates the folder if it does not exist yet. */
static async open(root: string, name?: string): Promise<NodeFsAdapter> {
await fs.mkdir(root, { recursive: true });
return new NodeFsAdapter(root, name);
}
private resolve(path: string): string {
const relative = normalizePath(path);
return relative ? nodePath.join(this.root, relative) : this.root;
}
async list(path: string): Promise<VFSListEntry[]> {
const base = normalizePath(path);
let entries;
try {
entries = await fs.readdir(this.resolve(base), { withFileTypes: true });
} catch {
return [];
}
return entries.map((entry) => ({
name: entry.name,
path: base ? `${base}/${entry.name}` : entry.name,
kind: entry.isDirectory() ? ('directory' as const) : ('file' as const),
}));
}
async read(path: string): Promise<Uint8Array> {
return new Uint8Array(await fs.readFile(this.resolve(path)));
}
async write(path: string, data: Uint8Array): Promise<void> {
const target = this.resolve(path);
await fs.mkdir(nodePath.dirname(target), { recursive: true });
await fs.writeFile(target, data);
}
async mkdir(path: string): Promise<void> {
await fs.mkdir(this.resolve(path), { recursive: true });
}
/** Real append: the commit log grows by the bytes added, not by its size. */
async append(path: string, data: Uint8Array): Promise<void> {
const target = this.resolve(path);
await fs.mkdir(nodePath.dirname(target), { recursive: true });
await fs.appendFile(target, data);
}
/** Positional read: seeks straight to `start`, never reads the rest. */
async readRange(path: string, range: ByteRange = {}): Promise<Uint8Array> {
const handle = await fs.open(this.resolve(path), 'r');
try {
const { size } = await handle.stat();
const start = Math.min(Math.max(range.start ?? 0, 0), size);
const end = Math.min(range.end ?? size, size);
const length = Math.max(0, end - start);
const out = new Uint8Array(length);
let filled = 0;
// A single read() can come up short on some filesystems.
while (filled < length) {
const { bytesRead } = await handle.read(out, filled, length - filled, start + filled);
if (bytesRead === 0) break;
filled += bytesRead;
}
return filled === length ? out : out.subarray(0, filled);
} finally {
await handle.close();
}
}
async readStream(path: string, range: ByteRange = {}): Promise<ReadableStream<Uint8Array>> {
const start = range.start ?? 0;
// node's `end` is inclusive, ours is not
if (range.end !== undefined && range.end <= start) return chunked(new Uint8Array());
const options: { start: number; end?: number } = { start };
if (range.end !== undefined) options.end = range.end - 1;
const stream = createReadStream(this.resolve(path), options);
return Readable.toWeb(stream) as unknown as ReadableStream<Uint8Array>;
}
async writeStream(path: string): Promise<WritableStream<Uint8Array>> {
const target = this.resolve(path);
await fs.mkdir(nodePath.dirname(target), { recursive: true });
return Writable.toWeb(createWriteStream(target)) as unknown as WritableStream<Uint8Array>;
}
async delete(path: string): Promise<void> {
await fs.rm(this.resolve(path), { recursive: true, force: true });
}
async rename(oldPath: string, newPath: string): Promise<void> {
const target = normalizePath(newPath);
await fs.mkdir(this.resolve(dirname(target)), { recursive: true });
await fs.rename(this.resolve(oldPath), this.resolve(target));
}
async stat(path: string): Promise<VFSStat | null> {
try {
const stat = await fs.stat(this.resolve(path));
return {
kind: stat.isDirectory() ? 'directory' : 'file',
size: stat.size,
// ms precision: sub-millisecond values would not survive JSON round
// trips identically on every platform
mtime: Math.floor(stat.mtimeMs),
};
} catch {
return null;
}
}
}
|