Auto
One mount() that picks a transport, kept off the root export so importing mountx for the driver types does not drag two protocol stacks in with it.
import { mount, probeTransports, liveMounts, unmountAll } from "mountx/auto";#mount(driver, mountpoint, options?)
function mount(
driver: FsDriver,
mountpoint: string,
options?: AutoMountOptions,
): Promise<AutoMount>;Serves driver at mountpoint over whatever this host can mount, and resolves once the mountpoint is usable. The result is the transport's own mount object with a transport property defined on it — tagged, not wrapped — so await using, unmount() and every transport-specific member work exactly as they do when the transport is imported directly.
await using mounted = await mount(createMemoryDriver(), "/mnt/point");Throws when the probe finds nothing usable, with a message naming what each transport is missing.
#AutoMountOptions
Deliberately not the union of both transports' option types: they have same-named options with genuinely different shapes (onError hands FUSE a request and NFS an RPC call), and a merged type would either lie about that or collapse to something unusable.
So the top level is the options that mean the same thing in both, and anything transport-specific goes in fuse: {…} or nfs: {…} — applied after the shared ones, and therefore winning.
interface AutoMountOptions {
/** "auto" (default), or a name — which skips the probe entirely. */
transport?: "auto" | "fuse" | "nfs";
/** Mount read-only. */
readOnly?: boolean;
/** Unmount on SIGINT/SIGTERM. Default true. */
signals?: boolean;
/** Milliseconds an unmount may spend before it is forced. Default 10_000. */
unmountTimeout?: number;
/** Report the driver's own ino values instead of synthesising them. */
useDriverIno?: boolean;
/** Called for errors raised while answering a request. */
onError?: (error: unknown) => void;
/** Called for transport-level failures, and for a forced teardown. */
onTransportError?: (error: unknown) => void;
/** FUSE-only options. Applied after the shared ones. */
fuse?: MountOptions;
/** NFS-only options. Applied after the shared ones. */
nfs?: MountNfsOptions;
}A block for the transport you did not get is simply ignored — which is what makes one call portable.
See MountOptions and MountNfsOptions for the two nested blocks.
#AutoMount
type AutoMount =
(Mount & { readonly transport: "fuse" }) | (NfsMount & { readonly transport: "nfs" });A discriminated union. Narrowing on the tag reaches the full transport type, with no cast:
if (mounted.transport === "fuse") {
mounted.session; // FuseSession
mounted.fd; // the /dev/fuse descriptor
mounted.closed; // Promise<void>, never rejects
mounted.notifyInvalInode(2n);
mounted.notifyInvalEntry(1n, "hello.txt");
} else {
mounted.server; // NfsServer
mounted.port; // number
}Both share mountpoint, source, active, unmount() and [Symbol.asyncDispose].
#probeTransports(platform?)
function probeTransports(platform?: NodeJS.Platform): Promise<AutoProbe>;interface AutoProbe {
platform: NodeJS.Platform;
chosen: Transport | undefined; // what mount() would use
preference: readonly Transport[]; // the list `chosen` was picked from
fuse: TransportProbe; // { usable: boolean; reason: string | undefined }
nfs: TransportProbe;
reason: string | undefined; // when nothing can mount, naming both
}Cheap enough to call before deciding whether to offer a mount at all, and specific enough to print. It reads facts in order of cheapness — the platform, then /dev/fuse, then (only when unprivileged) the fusermount3 helper and the native addon — and it loads no protocol codec on either branch.
The preference order is ["fuse", "nfs"] on Linux and ["nfs", "fuse"] elsewhere. Linux is the only host where both can work, so it is the only host whose order decides anything.
Note
platform exists to be overridden in tests — it is how the darwin and win32 answers are checked from any host. Leave it alone otherwise.
#liveMounts() / unmountAll()
function liveMounts(): Promise<AutoMount[]>;
function unmountAll(): Promise<unknown[]>;Every live mount in this process, on both transports, tagged; and unmount them all — never rejects, returning whatever went wrong. Only transports that were actually used are asked, so neither loads anything that was not already loaded.
#Re-exported types
Transport, TransportProbe, AutoProbe, AutoMountOptions, AutoMount, plus Mount/MountOptions from the FUSE transport and NfsMount/MountNfsOptions from the NFS one — so a caller needs one import either way.
#Three things auto deliberately does not do
- No fallback after a failure. The probe decides once, from host facts. If the chosen transport then fails to mount, that error is what you get — silently mounting the other one would hand back a filesystem with different semantics than the error you never saw.
- No probing when you name a transport.
transport: "fuse"calls the FUSE transport directly, whose own errors are more specific than anything the chooser could say. - No wrapping. The result is the transport's own mount object with a
transportproperty defined on it. Narrowing on that tag reachessession,fdand thenotifyInval*pair for FUSE, orserverandportfor NFS — andawait usingworks either way.
And one it does do quietly: it loads nothing it does not use. Both transports arrive through await import(), so choosing one never pulls in the other's codec.