Specialized

Regular file helpers

Regular file helpers

The advanced regular-file helpers provide direct read/append/stat helpers for absolute file paths, with an explicit "regular file or nothing" contract. Useful when you have a trusted absolute path and want a thin layer on top of fs that:

  • refuses non-regular files (directories, FIFOs, sockets, symlinks)
  • enforces a maxBytes read cap
  • separates "missing" from "io-error" in the result type
import {
  readRegularFile,
  readRegularFileSync,
  appendRegularFile,
  appendRegularFileSync,
  resolveRegularFileAppendFlags,
  statRegularFile,
  statRegularFileSync,
  type AppendRegularFileOptions,
  type RegularFileStatResult,
} from "@openclaw/fs-safe/advanced";

#Stat

#statRegularFile(filePath)

Async. Returns:

type RegularFileStatResult =
  | { missing: true }
  | { missing: false; stat: Stats };

A non-regular file (directory, FIFO, symlink, …) throws. Missing paths return { missing: true }; existing regular files return { missing: false, stat }.

import { statRegularFile } from "@openclaw/fs-safe/advanced";

const r = await statRegularFile("/var/log/app.log");
if (r.missing) return;
console.log(`size=${r.stat.size}`);

#statRegularFileSync(filePath)

Synchronous variant. Same shape.

#Read

#readRegularFile(params)

Async. Reads the entire file into a Buffer if it is a regular file, with maxBytes enforcement.

import { readRegularFile } from "@openclaw/fs-safe/advanced";

const result = await readRegularFile({
  filePath: "/var/log/app.log",
  maxBytes: 4 * 1024 * 1024,
});
processLog(result.buffer);

The result is { buffer, stat }. Missing files preserve the normal ENOENT shape; non-regular targets throw. Reads compare exact bigint identities from the preview, opened descriptor, and current path before consuming bytes; the returned stat remains numeric Node Stats. Unknown Windows identities receive one re-inspection without reopening; persistent unknowns or replacements throw FsSafeError("path-mismatch").

Throws FsSafeError with code too-large if the file exceeds maxBytes. Other I/O errors propagate as NodeJS.ErrnoException.

#readRegularFileSync(params)

Synchronous variant. Same shape; the only required field is filePath. maxBytes is optional.

#Append

#appendRegularFile(options)

Async. Opens the file in append mode, writes data, closes. Refuses non-regular targets:

import { appendRegularFile } from "@openclaw/fs-safe/advanced";

await appendRegularFile({
  filePath: "/var/log/app.log",
  content: `[${new Date().toISOString()}] ${line}\n`,
  encoding: "utf8",
});

#Options

type AppendRegularFileOptions = {
  filePath: string;
  content: string | Uint8Array;
  encoding?: BufferEncoding; // default utf8 when content is string
  maxFileBytes?: number;     // skip if the resulting file would exceed this
  mode?: number;             // default 0o600
  rejectSymlinkParents?: boolean;
};

The helper refuses symlink and hardlinked final targets. Preview, opened descriptor, and current pathname identities remain exact bigints through the append boundary; rounded-equal replacements and persistent unknown Windows identities reject before chmod or writing bytes. With rejectSymlinkParents: true, it also rejects symlinked ancestor directories.

On POSIX, O_NONBLOCK prevents a no-reader FIFO substituted before open from stalling admission. A confirmed non-regular target is refused before chmod or append; other open errors propagate unchanged. This safeguard does not change ordinary regular-file append semantics or require read permission.

The requested mode is applied through the admitted descriptor before any content is appended, so an existing file is tightened first. On successful completion, explicitly requested POSIX special bits are reapplied after the content write, which can otherwise clear set-ID bits. An initial chmod failure leaves the content untouched; a write or final chmod failure can leave appended content and is not rolled back. Windows does not enforce POSIX mode semantics.

#appendRegularFileSync(options)

Synchronous. Same options and mode ordering. Writes the complete input through the already-open descriptor, including when an individual write is short.

#resolveRegularFileAppendFlags()

Helper that returns the append helpers' O_WRONLY | O_APPEND | O_CREAT flags, plus O_NOFOLLOW where the platform provides it and O_NONBLOCK on POSIX where available. Windows flags are unchanged:

import { resolveRegularFileAppendFlags } from "@openclaw/fs-safe/advanced";

const flags = resolveRegularFileAppendFlags();

#Difference from Root methods

regular-fileRoot
Absolute paths only.Relative to the root.
Verifies path and descriptor identity around reads.Enforces the same checks within a trusted root.
Caller must be confident the path is trusted.Boundary check is automatic.
Stat reports missing explicitly; reads throw on missing/non-file.Throws FsSafeError with code.

If your call site already trusts the path (it came from your own config, not a caller), regular-file is a thinner, faster surface. If the path is caller-influenced, prefer root() or wrap in pathScope().

#Common patterns

#Read a config file if it's there, else seed

const info = await statRegularFile("/etc/app/config.json");
if (info.missing) {
  await writeJson("/etc/app/config.json", defaultConfig);
} else {
  const r = await readRegularFile({
    filePath: "/etc/app/config.json",
    maxBytes: 64 * 1024,
  });
  applyConfig(JSON.parse(r.buffer.toString("utf8")));
}

#Cheap "exists and is a file" check

const r = await statRegularFile(p);
if (r.missing) return false;
return true;

#Bounded log tail

const r = await readRegularFile({ filePath: logPath, maxBytes: 1 * 1024 * 1024 });
return r.buffer.toString("utf8").split("\n").slice(-100);

#See also

  • ReadingRoot reads with boundary checks.
  • Atomic writes — for atomic write semantics, prefer replaceFileAtomic.
  • fs.appendFile — Node's stock append, without regular-file gating.