@bandf/fs
v2.0.0
Published
Application-level virtual filesystem over local, S3, and mirror storage.
Readme
@bandf/fs
@bandf/fs is an application-level virtual filesystem for Node.js. One
filesystem instance exposes a logical namespace of mount:relative/path
addresses. Each mount binds a virtual root to a local directory, an S3 prefix,
or a local-and-S3 mirror.
| Virtual address | Mounted backend root |
| --- | --- |
| work:notes/plan.md | /srv/work/notes/plan.md |
| archive:notes/plan.md | s3://documents/archive/notes/plan.md |
| durable:state.json | Local and S3 copies under one address |
Filesystem operations use virtual addresses, so the same read, write,
stat, ls, remove, copy, and move API works across backends. copy
and move can cross mount boundaries.
This is a library-level VFS. It does not mount an operating-system filesystem
and is not a drop-in replacement for node:fs. The package ships as ESM with
no CommonJS build and requires Node.js 24 or newer.
Install
npm install @bandf/fsQuick start
import { createFs } from '@bandf/fs';
const fs = await createFs({
s3: { region: `us-east-1` },
mounts: {
work: {
type: `local`,
path: `/absolute/existing/path`,
mode: `rw`
},
archive: {
type: `s3`,
bucket: `my-bucket`,
prefix: `documents`,
mode: `rw`
}
}
});
await fs.write({ address: `work:notes/plan.md`, value: `# Plan` });
const file = await fs.read({ address: `work:notes/plan.md` });
file.value = `# Revised plan`;
await fs.write({ file }); // guarded by the revision captured by read()
await fs.copy({
from: `work:notes/plan.md`,
to: `archive:notes/plan.md`
});Use createFs() for an explicit in-memory mount table. Use openFs() to load
and update the persisted table at fsConfigPath().
Operational contract
| Area | Contract |
| --- | --- |
| Namespace | Named virtual roots backed by local, S3, or mirror storage |
| Addresses | Lowercase mount name plus normalized relative path: docs:notes/a.md |
| Modes | Mounts default to ro; mutations require rw |
| Reads | Return a VFile with byte revision and mount provenance in file.data.fs |
| Local writes | Stage a sibling file and atomically rename it while holding a per-target interprocess lock |
| S3 writes | Use PutObject; guarded writes use IfMatch or IfNoneMatch |
| Mirror reads | Use the local side only; there is no S3 fallback or background reconciliation |
| Mirror writes | Mutate S3, then local, and restore the S3 snapshot if either step fails; this is not a distributed transaction |
| Removal | Can recursively remove a local directory or S3 prefix; mount roots are always refused |
| Move | Copy, then remove; it is not atomic and may leave the destination if removal fails |
| Enumeration | Hides dot-led names and never follows symlinks |
Documentation
- API reference — signatures, defaults, results, and errors.
- Mounts and backends — backend configuration, persistence, and mirror failure boundaries.
- Addresses and containment — grammar, normalization, symlink policy, and globs.
- Concurrency — revisions, lock mechanics, S3 preconditions, and consistency limits.
License
AGPL-3.0-or-later. See LICENSE.
