mountx
Write a filesystem in JavaScript, mount it as a real folder.
import { mount } from "mountx/auto";
import { createMemoryDriver } from "mountx/drivers/memory";
// A driver is any object with stat(), readdir() and open().
const driver = createMemoryDriver();
// FUSE (no root needed) then 9P on Linux, NFSv3 on macOS.
await using mounted = await mount(driver, "/mnt/point");
mounted.transport; // "fuse" | "9p" | "nfs"
// $ echo hi > /mnt/point/hello.txt
// $ cat /mnt/point/hello.txt
Features
No API to learn
A driver is a subset of node:fs/promises — stat, readdir, open. Node's own fs/promises is a valid driver, unchanged.
A real folder
Every program on the machine sees an ordinary path. Shell tools, editors, build systems and agents need to know nothing about it.
One API, three transports
FUSE where FUSE works, then 9P, then NFS everywhere else. mountx/auto probes the host, picks one, and hands back that transport's own mount object.
No root on Linux
Unprivileged mounting through fusermount3, the setuid helper FUSE already ships. Serving never needs privileges on any transport.
Pure JavaScript
Zero runtime dependencies, all three protocols implemented from the specs. The one native piece is a ~7 KB helper that only unprivileged FUSE mounts load.
Develop without mounting
createLoopback(driver) runs your driver like fs/promises in-process — no kernel, no privileges, every platform. Where drivers get written and tested.
Errors pass through
Throw what node:fs throws. fsError("ENOENT") builds an error byte-identical to Node's, and it reaches the kernel untranslated.
Capabilities, never faked
What your driver supports is declared or inferred from its shape. Anything unsupported answers ENOSYS/ENOTSUP instead of pretending.
Kernel caching you control
Attribute and entry timeouts are worth 10–15× on a real workload, and notifyInvalInode() drops a cache the moment your storage changes.