mountx logomountx

Driver Interface

FsDriver and the four structural types it is built from — the whole shape a driver has to have.

import { S_IFDIR, S_IFMT, S_IFREG } from "mountx";
import type { DirentLike, FileHandleLike, FsDriver, StatsLike } from "mountx";

Prose version, with a complete driver you can run: Writing a driver.

#FsDriver

Three required methods; everything else optional, and missing means the capability is absent.

interface FsDriver {
  readonly capabilities?: FsCapabilities;
  readonly mountx?: MountxExtensions;

  // required
  stat(path: string): Promise<StatsLike>;
  readdir(path: string, options: { withFileTypes: true }): Promise<DirentLike[]>;
  open(path: string, flags?: string | number, mode?: number): Promise<FileHandleLike>;

  // optional
  lstat?(path: string): Promise<StatsLike>;
  statfs?(path: string): Promise<StatsFsLike>;
  mkdir?(path: string, options?: MkdirOptions): Promise<string | undefined>;
  rmdir?(path: string): Promise<void>;
  unlink?(path: string): Promise<void>;
  rename?(oldPath: string, newPath: string): Promise<void>;
  link?(existingPath: string, newPath: string): Promise<void>;
  symlink?(target: string, path: string, type?: string | null): Promise<void>;
  readlink?(path: string): Promise<string>;
  chmod?(path: string, mode: number): Promise<void>;
  chown?(path: string, uid: number, gid: number): Promise<void>;
  lchown?(path: string, uid: number, gid: number): Promise<void>;
  truncate?(path: string, length?: number): Promise<void>;
  utimes?(path: string, atime: TimeLike, mtime: TimeLike): Promise<void>;
  lutimes?(path: string, atime: TimeLike, mtime: TimeLike): Promise<void>;
}

Every path is absolute, POSIX-style and normalized before your driver sees it.

FullFsDriver is the same with every optional method required — the memory and node-fs drivers satisfy it, and so does node:fs/promises. FsDriverMethod is the union of the optional method names.

The two readonly members are on their own page: Capabilities.

#Structural types

All four are the smallest shapes the transports need, so Node's own Stats, StatsFs, Dirent and FileHandle satisfy them unmodified.

#StatsLike

interface StatsLike {
  dev: number;
  ino: number;
  mode: number; // S_IFMT type bits | permission bits
  nlink: number;
  uid: number;
  gid: number;
  rdev: number;
  size: number;
  blksize: number;
  blocks: number;
  atimeMs: number;
  mtimeMs: number;
  ctimeMs: number;
  birthtimeMs: number;
  isFile(): boolean;
  isDirectory(): boolean;
  isSymbolicLink(): boolean;
  isBlockDevice(): boolean;
  isCharacterDevice(): boolean;
  isFIFO(): boolean;
  isSocket(): boolean;
}

#DirentLike

{ name: string; parentPath?: string } plus the same seven type predicates.

#StatsFsLike

{ type, bsize, blocks, bfree, bavail, files, ffree } — for statfs(2) and df.

#FileHandleLike

interface FileHandleLike {
  readonly fd?: number;
  read(
    buffer: Uint8Array,
    offset?: number | null,
    length?: number | null,
    position?: number | null,
  ): Promise<{ bytesRead: number; buffer: Uint8Array }>;
  write(
    buffer: Uint8Array,
    offset?: number | null,
    length?: number | null,
    position?: number | null,
  ): Promise<{ bytesWritten: number; buffer: Uint8Array }>;
  stat(): Promise<StatsLike>;
  truncate(length?: number): Promise<void>;
  close(): Promise<void>;
  sync?(): Promise<void>; // absent = nothing to flush
  datasync?(): Promise<void>;
}

Whether that handle is real per-open state is the handles capability, one of the two that cannot be inferred.

#File-type constants

S_IFMT, S_IFREG, S_IFDIR, S_IFLNK, S_IFBLK, S_IFCHR, S_IFIFO, S_IFSOCK — the POSIX mode bits, for building and interrogating StatsLike.mode.

import { S_IFDIR, S_IFMT } from "mountx";

const isDirectory = (mode: number) => (mode & S_IFMT) === S_IFDIR;

#Next

  • Capabilities — what a driver declares, and what is inferred from the methods above.
  • Errors — what to throw out of them.
mountx logo

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