mountx logomountx

Overview

#Transports

One driver interface, two ways of getting it in front of a kernel.

A transport is everything between the driver you wrote and the operating system: the protocol codec, the session that turns messages into driver calls, and the piece that attaches the result to a directory. mountx/auto picks one for you; both can be pinned directly.

#What it is choosing between

FUSENFSv3
mount…() runs onLinuxLinux, macOS
serves tothe local kernelanything with an NFSv3 client
root to mountno, with fusermount3Linux: yes · macOS: no
root to servenevernever
what you losenothinghandles — see below

FUSE is preferred wherever it works. You get real open/release state, direct control over kernel caching, and every errno passes through untouched.

NFSv3 covers everything else — macOS most obviously, which has no usable FUSE (macFUSE is a third-party kernel extension speaking its own protocol dialect, not the Linux fuse.h that src/fuse/ is written against) but does ship an NFSv3 client.

The trade-off is that NFSv3 is stateless. There is no open/release, so every request carries a handle built from the driver's (dev, ino) identity. In practice that costs exactly one behaviour: a file deleted while still open stays readable over FUSE, and answers ESTALE over NFS.

#How auto decides

The preference order is per-platform, and Linux is the only host where both can work — so it is the only host whose order decides anything.

hosttransportwhy
Linux, /dev/fuse, rootFUSEreal open/release, no ESTALE
Linux, /dev/fuse, non-rootFUSEfusermount3 + the addon do the mounting
Linux, no usable FUSE, rootNFSneeds no /dev/fuse and no helper
macOSNFSmacFUSE is a different protocol; no root needed here
Windowsnoneno FUSE, no NFS client worth the name

The probe reads facts in order of cheapness — the platform, then the device, then (only when unprivileged) the helper and the addon — and it deliberately loads nothing heavy: neither branch pulls in a protocol codec. probeTransports() hands you the whole answer, and is cheap enough to call before deciding whether to offer a mount at all.

Note

Both transports arrive through await import(), so choosing one never loads the other's codec. That is why mountx/auto is the recommended entry point rather than a convenience that costs you both stacks.

It also declines to do three things on purpose — no fallback after a failure, no probe when you name a transport, and no wrapping of the mount object it hands back. See mountx/auto for why each one is deliberate.

#Pinning a transport

mountx/fuse and mountx/nfs are the same transports without the probe, for when you know what you want:

import { mount } from "mountx/fuse"; // Linux only
import { mountNfs } from "mountx/nfs"; // Linux (root) and macOS (no root)

await using mounted = await mount(createMemoryDriver(), "/mnt/point");

Their options sit at the top level rather than under fuse: {…}/nfs: {…}; everything else is identical, because this is exactly what mountx/auto calls.

You can also name one through auto and keep the shared-option shape:

await mount(driver, "/mnt/point", { transport: "nfs", nfs: { exportPath: "/" } });

#No native code, with one exception

Serving needs none at all — both protocols are pure JavaScript, which is a design rule of this project. The one exception is a ~7 KB helper used only to receive the mount connection from fusermount3, because unprivileged FUSE mounting needs a file descriptor passed over a unix socket and Node cannot recvmsg one.

It is optional, lazy, and never on the root path: mounting as root opens /dev/fuse itself and touches no native code, so a host with no prebuilt for its platform loses unprivileged FUSE mounting and nothing else.

It also ships as compressed base64 inside a JavaScript module rather than as a .node file — a binary is loaded by path, and a path is the one thing a bundle does not have. The loader extracts it to a private temporary directory, dlopens it, and deletes it again. So it bundles: nothing to configure, nothing to mark external, no sibling file to copy into your output.

#Next

  • mountx/auto — the chooser itself: every option and type.
  • FUSE — the preferred transport, in detail.
  • NFSv3 — including serving without mounting at all.
mountx logo

mountx  Write a filesystem in JavaScript, mount it as a real folder.