Reference

Types

Types

The types most callers reach for. Shared data shapes are exported from @openclaw/fs-safe/types; method-specific option/result types live next to their subpath.

import type {
  BasePathOptions,
  DirEntry,
  FastPathMode,
  PathStat,
  SafeEncoding,
} from "@openclaw/fs-safe/types";

#PathStat

type PathStat = {
  kind: "file" | "directory" | "symlink" | "fifo" | "socket" | "blockDevice" | "characterDevice" | "unknown";
  size: number;       // bytes
  mtimeMs: number;    // milliseconds since epoch
  mode: number;       // POSIX mode bits
  nlink: number;      // hardlink count
};

The shape returned by Root.stat(). A trimmed view of node:fs.Stats — only the fields the boundary cares about. Use kind instead of inspecting the various is* methods on a Node Stats object; it covers every case in one switchable string.

#DirEntry

type DirEntry = PathStat & {
  name: string;       // base name within the listed directory
};

Returned by Root.list(rel, { withFileTypes: true }). Includes the same kind/size/etc as PathStat, plus the entry's name.

#BasePathOptions

type BasePathOptions = {
  fastPathMode?: FastPathMode;
};

type FastPathMode = "auto" | "never" | "require";

Options shared by helpers that can take a "fast path" (use cheaper syscalls when the input is already absolute and clearly inside scope). The default is "auto" — let the helper pick. Force "never" in tests if you want to exercise the slow path. Force "require" if you need to assert that the fast path is taken (the helper throws if it can't).

Most callers don't need to touch this.

#SafeEncoding

type SafeEncoding = BufferEncoding | null;

Used by helpers that accept either an encoding (returning a string) or null (returning a Buffer). The Node BufferEncoding type is widened to include null for "give me bytes."

#OpenResult / ReadResult

Returned by Root.open() and Root.read():

type OpenResult = {
  handle: import("node:fs/promises").FileHandle;
  realPath: string;
  stat: import("node:fs").Stats;
};

type ReadResult = {
  buffer: Buffer;
  realPath: string;
  stat: import("node:fs").Stats;
};

realPath is the canonical real path the read or open landed on, after symlink resolution; stat is the verified fstat result.

#RootDefaults / RootOptions

type RootDefaults = {
  hardlinks?: "reject" | "allow";
  maxBytes?: number;
  mkdir?: boolean;
  mode?: number;
  nonBlockingRead?: boolean;
  symlinks?: "reject" | "follow-within-root";
};

type RootOptions = {
  rootDir: string;
  defaults?: RootDefaults;
};

RootDefaults is what root(rootDir, defaults) accepts. See root() for the per-method options that override these.

#RootReadOptions / RootWriteOptions / RootCopyOptions

type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
type RootWriteOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
  encoding?: BufferEncoding;
  overwrite?: boolean;
};
type RootCopyOptions = Pick<RootDefaults, "maxBytes" | "mkdir" | "mode"> & {
  sourceHardlinks?: "reject" | "allow";
};
type RootOpenWritableOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
  writeMode?: "replace" | "append" | "update";
};
type RootWriteJsonOptions = RootWriteOptions & {
  replacer?: Parameters<typeof JSON.stringify>[1];
  space?: Parameters<typeof JSON.stringify>[2];
  trailingNewline?: boolean;
};
type RootAppendOptions = RootWriteOptions & {
  prependNewlineIfNeeded?: boolean;
};

Per-method option shapes. Each picks the RootDefaults keys that apply, plus method-specific extras.

#SymlinkPolicy / HardlinkPolicy

type SymlinkPolicy = "reject" | "follow-within-root";
type HardlinkPolicy = "reject" | "allow";

The two policy unions you'll see throughout. "reject" is conservative; "follow-within-root" allows symlinks whose final target is still inside the root; "allow" (hardlinks only) is permissive. Defaults for both symlinks and hardlinks are "reject"; switch hardlinks to "allow" only when you intentionally accept hardlink aliases.

#FsSafeErrorCode / FsSafeErrorCategory

type FsSafeErrorCode =
  | "already-exists" | "hardlink" | "helper-failed" | "helper-unavailable"
  | "insecure-permissions" | "invalid-path" | "not-empty" | "not-file"
  | "not-found" | "not-owned" | "not-removable" | "outside-workspace"
  | "path-alias" | "path-mismatch" | "permission-unverified"
  | "symlink" | "timeout" | "too-large" | "unsupported-platform";

Closed union you switch on. See the Errors reference for what each one means.

FsSafeError.category is "policy" for unsafe input/target-state failures and "operational" for environment/runtime failures.

#See also

  • root() — how RootDefaults and Root*Options are used.
  • Errors — the closed code union in context.
  • Reading, Writing — option shapes per verb.