Capabilities

Capabilities state what a driver supports. Declare facts that mountx cannot infer. Mountx infers the other facts from driver methods. It does not simulate missing features.

import { resolveCapabilities } from "mountx";
import type { FsCapabilities, MountxExtensions, ResolvedCapabilities } from "mountx";

#FsCapabilities

interface FsCapabilities {
  handles?: boolean; // open() returns real per-open state that survives unlink
  hardlinks?: boolean; // link() works and nlink is counted
  symlinks?: boolean; // symlink()/readlink() work; lstat differs from stat
  permissions?: boolean; // mode and ownership bits are stored and returned
  times?: boolean; // utimes() is stored and returned
  truncate?: boolean; // truncate() and FileHandle.truncate() work
  atomicRename?: boolean; // rename() replaces the destination atomically
  caseSensitive?: boolean; // names differing only in case are distinct
  statfs?: boolean; // statfs() returns meaningful numbers
  readOnly?: boolean; // every mutating operation answers EROFS
  durableWrites?: boolean; // a write is durable once its promise resolves
  extensions?: readonly (keyof MountxExtensions)[];
}

An unset value means "infer this value." It does not mean false. See Writing a driver for each inference rule. Mountx cannot infer handles, atomicRename, readOnly, or durableWrites. These four values default to false.

Declare readOnly when every operation that can change data returns EROFS. The absence of methods such as unlink, mkdir, and rename does not prove that a driver is read-only. The driver can still open files for writing. Therefore, mountx cannot infer this capability.

Declare durableWrites only when no data remains buffered after write() resolves. The memory driver has durable writes. A driver that batches work or uploads in the background does not have durable writes, even if write() resolves quickly.

When durableWrites is true, FUSE does not answer FLUSH.

The kernel sends FLUSH for each close(2).

It is the third most common opcode in an install workload.

A false declaration can silently lose a write error.

The test suite does not cover delayed close(2) errors, so you must verify this declaration from the driver's design.

If a capability is not available, mountx returns ENOSYS or ENOTSUP. Mountx must not report support for a missing capability.

#resolveCapabilities(driver)

function resolveCapabilities(driver: FsDriver): ResolvedCapabilities;

Explicit declarations take priority. When no declaration exists, the presence of a method determines the result. ResolvedCapabilities contains a value for every member. createLoopback() exposes this object as fs.capabilities.

#MountxExtensions

Use the optional driver.mountx namespace for operations that node:fs does not provide.

interface MountxExtensions {
  // Nanosecond timestamps; fs.utimes takes float seconds and loses them.
  utimens?(
    path: string,
    atimeNs: bigint,
    mtimeNs: bigint,
    options?: { followSymlinks?: boolean },
  ): Promise<void>;
  // FIFOs, sockets and device nodes.
  mknod?(path: string, mode: number, dev: number): Promise<void>;
}

Transports check each extension separately. They reduce their feature set when an extension is absent. Both extensions are currently used:

  • mknod is used by all three mount transports. This is four sessions when NFSv3 and NFSv4.1 are counted separately. The memory driver implements it. Without mknod, MKNOD, Tmknod, and NFSv4.1 CREATE return ENOSYS or NOTSUPP for all types except regular files. S3 and WebDAV cannot represent a first-in, first-out (FIFO) file or device node, so they do not use it.
  • utimens is used by FUSE and 9P. Their protocols carry nanoseconds that fs.utimes would round. NFS SETATTR also carries nanoseconds, but the NFS sessions do not yet use this extension.

This namespace contains only path-based gaps in node:fs. Locks, fallocate, lseek, and cache invalidation apply to an open file or a session. They must be designed in the session layer. The four xattr types had no consumer and were removed. Add them again only with session work that handles the related opcodes.

#Next

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