js/file/memory

js/file/memory.ts

fino:file/memory — an in-memory filesystem provider.

MemoryFileSystem implements the same FileSystem contract as DiskFileSystem, so anything written against fino:file works against it unchanged: the same Stat objects, the same File handles, the same Entry/FileEntry/DirEntry listings, and the same errno-carrying errors. Nothing it stores ever reaches a disk.

That makes it useful wherever a real filesystem is inconvenient — tests that would otherwise need a temp directory, staging a tree before committing it, or any caller written against FileSystem that needs an isolated instance.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/etc/app.conf': 'debug=true' });
console.log(new TextDecoder().decode(await fs.readFile('/etc/app.conf')));

Parent directories of seeded files are created implicitly, which is what makes a one-line constructor a usable tree. Directories created later follow POSIX and require their parent to exist.

Timestamps come from Date.now() by default. Pass a now function to make them something else — a counter, for instance, so that two runs of the same sequence of operations produce identical metadata.

Interfaces

interface MemoryFileSystemOptions {

Settings for a MemoryFileSystem.

Properties

files?: Record<string, string | Uint8Array>

Files to seed the tree with, by absolute path. Parent directories are created as needed.

now?: () => number

Source of timestamps, in milliseconds. Defaults to Date.now.

A monotonic counter here makes metadata reproducible across repeated runs of the same operation sequence.

fileMode?: number

Permission bits reported for seeded and created files. Defaults to 0o644.

dirMode?: number

Permission bits reported for directories. Defaults to 0o755.

Classes

class MemoryFileSystem extends FileSystem {

A filesystem whose entire contents live in this isolate's heap.

Construction seeds the tree; everything after that goes through the ordinary provider methods. Instances are independent — two MemoryFileSystems share nothing, which is what makes one per test safe to use in parallel.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem();
await fs.mkdir('/work');
await fs.writeFile('/work/notes.txt', new TextEncoder().encode('hello'));
const dir = await fs.dir('/work');
for (const entry of await dir.entries()) console.log(entry.name);

Constructors

constructor( files: Record<string, string | Uint8Array> | MemoryFileSystemOptions = {}, options: MemoryFileSystemOptions = {}, )

Methods

async stat(path: Path | string): Promise<Stat>

Stat a path, following symlinks.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
console.log((await fs.stat('/a.txt')).size); // 2
statSync(path: Path | string): Stat

Synchronous stat. Always available: there is no I/O to await.

async lstat(path: Path | string): Promise<Stat>

Stat a path without following symlinks.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
console.log((await fs.lstat('/a.txt')).isSymlink()); // false
async open(path: Path | string, mode: string = 'r'): Promise<FileHandle>

Open a file and return a handle.

Supports the same modes as DiskFileSystem: r, r+, w, w+, a, a+, the internal c+, and an x suffix for exclusive creation.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem();
const file = await fs.open('/log.txt', 'w');
await file.pwrite(0, new TextEncoder().encode('ready\n'));
await file.close();
openSync(path: Path | string, mode: string = 'r'): FileHandle

Synchronous open, for callers such as the SQLite VFS that cannot await.

async dir(path: Path | string): Promise<DirEntry>

Open a directory handle.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/work/a.txt': '' });
const dir = await fs.dir('/work');
console.log((await dir.entries()).length); // 1
async entry(path: Path | string): Promise<Entry>

Build an Entry for any path, without following a trailing symlink.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
console.log((await fs.entry('/a.txt')).isFile()); // true
async readdir(path: Path | string): Promise<Entry[]>

List a directory's immediate children.

DirEntry iteration goes through this, which is how listings work without a real directory stream underneath.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/work/a.txt': '' });
console.log((await fs.readdir('/work')).map((e) => e.name)); // ['a.txt']
async mkdir(path: Path | string, mode: number = 0o755): Promise<void>

Create a directory. The parent must already exist.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem();
await fs.mkdir('/work', 0o700);
async rmdir(path: Path | string): Promise<void>

Remove an empty directory.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem();
await fs.mkdir('/scratch');
await fs.rmdir('/scratch');
unlinkSync(path: Path | string): void

Synchronous unlink.

async rename(oldPath: Path | string, newPath: Path | string): Promise<void>

Rename a file or directory, moving any subtree with it.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
await fs.rename('/a.txt', '/b.txt');
async realpath(path: Path | string): Promise<string>

Resolve a path to its canonical form, expanding every symlink.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a/b.txt': 'hi' });
console.log(await fs.realpath('/a/./b.txt')); // '/a/b.txt'
async access(path: Path | string, mode: number = F_OK): Promise<void>

Check a path exists, and optionally that its permission bits allow a mode.

import { MemoryFileSystem, F_OK } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
await fs.access('/a.txt', F_OK);
async chmod(path: Path | string, mode: number): Promise<void>

Change a path's permission bits.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
await fs.chmod('/a.txt', 0o600);
async chown(path: Path | string, uid: number, gid: number): Promise<void>

Change a path's owner and group.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
await fs.chown('/a.txt', 501, 20);
async utimes(path: Path | string, atime: Date | number, mtime: Date | number): Promise<void>

Set a path's access and modification times.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
await fs.utimes('/a.txt', 0, 0);
async truncate(path: Path | string, size = 0): Promise<void>

Set a file's size, zero-filling any extension.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hello' });
await fs.truncate('/a.txt', 2);
async copyFile(src: Path | string, dest: Path | string): Promise<void>

Copy a file's contents to another path.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
await fs.copyFile('/a.txt', '/b.txt');
snapshot(): Record<string, string>

Every file's contents as text, keyed by path.

Handy for asserting on what a run wrote without walking the tree.

import { MemoryFileSystem } from 'fino:file/memory';

const fs = new MemoryFileSystem({ '/a.txt': 'hi' });
console.log(fs.snapshot()); // { '/a.txt': 'hi' }