Standard library / fs · on the one-page reference

fs

Stable — the synchronous core (everything except the individually-marked functions below) wraps long-stable POSIX syscalls (open, read, stat, ...) that predate the runtime's newer async I/O layer.

Mostly synchronous, statically typed and Node-conventional, plus a small async trio (readFile/writeFile/appendFile), file watching (watch), and file-backed streams (createReadStream/createWriteStream). This is a practical subset, not the full Node fs API (a Lumen "fd" is a plain int index, not a raw OS handle) — see the roadmap for what is next.

fs.readFileSync(path, encoding?)

(string, string?) → string — UTF-8 text; throws a catchable Error naming the path if the file can't be read.

try {
  const data: string = fs.readFileSync("notes.txt");
  console.log(data);
} catch (e) {
  console.log(e.message); // cannot read 'notes.txt': FileNotFound
}
fs.readFile(path) Experimental Native only

string → Promise<string> — true non-blocking I/O, no thread pool, unlike Node's fs.promises.readFile.

async function main(): Promise<void> {
  const data: string = await fs.readFile("notes.txt");
  console.log(data);
}
fs.writeFile(path, data) Experimental Native only

(string, string) → Promise<void> — the async counterpart to fs.writeFileSync; creates the file or truncates it first, same as the sync version.

async function main(): Promise<void> {
  await fs.writeFile("notes.txt", "hello async world");
  console.log("done");
}
fs.appendFile(path, data) Experimental Native only

(string, string) → Promise<void> — the async counterpart to fs.appendFileSync; creates the file if it does not exist yet.

async function main(): Promise<void> {
  await fs.appendFile("log.txt", "line one\n");
  await fs.appendFile("log.txt", "line two\n");
  console.log("done");
}
fs.mkdir(path) Experimental Native only

string → Promise<void> — thread-pool backed, see fs.unlink above. No recursive option this pass, matching mkdirSync's default-false convention.

async function main(): Promise<void> {
  await fs.mkdir("build");
}
fs.rmdir(path) Experimental Native only

string → Promise<void> — thread-pool backed, see fs.unlink above.

async function main(): Promise<void> {
  await fs.rmdir("build");
}
fs.stat(path) Experimental Native only

string → Promise<Stats> — thread-pool backed, see fs.unlink above. Same Stats shape as fs.statSync.

async function main(): Promise<void> {
  const st = await fs.stat("notes.txt");
  console.log(st.size);
}
fs.writeFileSync(path, data)

(string, string) → void — creates the file or truncates it first; throws a catchable Error on an I/O failure.

fs.writeFileSync("notes.txt", "hello world");
fs.appendFileSync(path, data)

(string, string) → void — creates the file if it does not exist yet.

fs.appendFileSync("log.txt", "new line\n");
fs.existsSync(path)

string → bool

if (fs.existsSync("config.json")) {
  console.log("found it");
}
fs.mkdirSync(path, recursive?)

(string, bool?) → void — recursive defaults to false.

fs.mkdirSync("build/output", true);
fs.unlinkSync(path)

string → void — delete a file.

fs.unlinkSync("scratch.tmp");
fs.renameSync(oldPath, newPath)

(string, string) → void

fs.renameSync("draft.md", "final.md");
fs.copyFileSync(src, dest)

(string, string) → void

fs.copyFileSync("template.txt", "output.txt");
fs.rmdirSync(path)

string → void — removes an empty directory.

fs.rmdirSync("empty-dir");
fs.rmSync(path, recursive?)

(string, bool?) → void — recursive removes a whole directory tree.

fs.rmSync("build", true);
fs.truncateSync(path, len)

(string, int) → void

fs.truncateSync("log.txt", 0);
fs.linkSync(existingPath, newPath)

(string, string) → void — hard link.

fs.linkSync("original.txt", "alias.txt");
fs.symlinkSync(target, path)

(string, string) → void

fs.symlinkSync("/etc/hosts", "hosts-link");
fs.readlinkSync(path)

string → string

console.log(fs.readlinkSync("hosts-link"));  // /etc/hosts
fs.chmodSync(path, mode)

(string, int) → void — POSIX mode bits.

fs.chmodSync("script.sh", 0o755);  // rwxr-xr-x
fs.accessSync(path, mode?)

(string, int?) → bool — mode bitmask R_OK=4 W_OK=2 X_OK=1; returns bool rather than throwing.

console.log(fs.accessSync("data.csv", 4));  // readable?
fs.cpSync(src, dest, recursive?)

(string, string, bool?) → void — falls back to a single file copy when src is not a directory.

fs.cpSync("assets", "dist/assets", true);
fs.mkdtempSync(prefix)

string → string — unique suffix from a timestamp + counter, not cryptographic; returns the created path.

const dir: string = fs.mkdtempSync("/tmp/build-");
console.log(dir);  // /tmp/build-1a2b3c4d
fs.statSync(path)

string → { size, isFile, isDirectory, mtimeMs } — the first record-returning builtin; isFile/isDirectory are bool fields, not methods.

const info = fs.statSync("report.pdf");
console.log(info.size);
console.log(info.isFile);
fs.openSync(path, flags)

(string, string) → int — a Lumen "fd" is an index into an internal file table, not a raw OS handle. flags is "r" (read, must exist), "w" (write, create/truncate), or "a" (append, create if missing, existing content kept, every writeSync lands at the current end of file — verified across separate open/close cycles, each reopen correctly re-seeks to wherever the file ends at that point, not just where it ended at the first open).

const fd: int = fs.openSync("data.bin", "r");
const logFd: int = fs.openSync("app.log", "a");
fs.writeSync(logFd, "another line\n");
fs.closeSync(logFd);
fs.closeSync(fd)

int → void

fs.closeSync(fd);
fs.readSync(fd, length)

(int, int) → string — reads up to length bytes from the current file position; works on string, not a Buffer (Lumen has none yet).

const fd: int = fs.openSync("data.bin", "r");
const chunk: string = fs.readSync(fd, 1024);
fs.closeSync(fd);
fs.writeSync(fd, data)

(int, string) → int — writes at the current file position, returns bytes written.

const fd: int = fs.openSync("out.bin", "w");
const n: int = fs.writeSync(fd, "hello");
fs.closeSync(fd);
fs.lstatSync(path) Experimental

string → { size, isFile, isDirectory, mtimeMs } — like statSync, but does not follow a symlink at path; stats the link itself.

const info = fs.lstatSync("hosts-link");
console.log(info.isFile);  // false: the link itself isn't a regular file
fs.fstatSync(fd) Experimental

int → { size, isFile, isDirectory, mtimeMs } — stats an already-open fd instead of resolving a path again.

const fd: int = fs.openSync("data.bin", "r");
console.log(fs.fstatSync(fd).size);
fs.closeSync(fd);
fs.fchmodSync(fd, mode) Experimental

(int, int) → void — fd-based form of chmodSync.

const fd: int = fs.openSync("script.sh", "w");
fs.fchmodSync(fd, 0o755);
fs.closeSync(fd);
fs.lchmodSync(path, mode) Experimental

(string, int) → void — chmod without following a symlink at path; best effort (not every OS lets you chmod a symlink directly).

fs.lchmodSync("hosts-link", 0o644);
fs.fchownSync(fd, uid, gid) Experimental

(int, int, int) → void — pass -1 for either id to leave it unchanged.

const fd: int = fs.openSync("data.bin", "w");
fs.fchownSync(fd, 1000, -1);  // change owner, leave group alone
fs.closeSync(fd);
fs.chownSync(path, uid, gid) Experimental

(string, int, int) → void — pass -1 for either id to leave it unchanged. Follows symlinks (see fs.lchownSync below for the one that shouldn't). Implemented by opening the file and reusing fchownSync's working fd-based path, since the path-based owner-change call panics unconditionally in this Zig version's runtime.

fs.chownSync("data.bin", 1000, 1000);
fs.lchownSync(path, uid, gid) Experimental

(string, int, int) → void — like chownSync but changes the symlink itself, not what it points to. Linux only (raw lchown, no libc) — unlike chownSync, this can't be built by opening the file first, since opening a symlink path normally follows it.

fs.symlinkSync("/data/real.bin", "link.bin");
fs.lchownSync("link.bin", 1000, 1000);
fs.realpathSync(path) Experimental

string → string — resolves symlinks and returns the canonical path. Returns path unchanged if it doesn't exist or can't be resolved.

console.log(fs.realpathSync("./current-link"));
fs.writevSync(fd, buffers) Experimental

(int, string[]) → int — writes every chunk in buffers in a single syscall, returns the total bytes written. Linux only (raw writev, no libc).

const fd: int = fs.openSync("out.bin", "w");
const n: int = fs.writevSync(fd, ["hello ", "world"]);
fs.closeSync(fd);
fs.readvSync(fd, sizes) Experimental

(int, int[]) → string[] — reads into sizes.length chunks in a single syscall, returning that many strings sized to what was actually read. Deviation from Node: Node's readv fills caller-provided mutable buffers; Lumen's string is immutable, so this takes desired chunk sizes instead and owns the buffers itself — same underlying vectored read, a shape that fits the type system. Linux only (raw readv, no libc).

const fd: int = fs.openSync("data.bin", "r");
const chunks: string[] = fs.readvSync(fd, [4, 100]);  // header, then the rest
fs.closeSync(fd);
fs.fsyncSync(fd) Experimental

int → void — flush file data and metadata to disk.

fs.fsyncSync(fd);
fs.fdatasyncSync(fd) Experimental

int → void — aliases fsyncSync (no data-only sync separate from a full sync is exposed here).

fs.fdatasyncSync(fd);
fs.ftruncateSync(fd, len) Experimental

(int, int) → void — fd-based form of truncateSync.

const fd: int = fs.openSync("log.txt", "w");
fs.ftruncateSync(fd, 0);
fs.closeSync(fd);
fs.futimesSync(fd, atimeMs, mtimeMs) Experimental

(int, int, int) → void — set access/modify times (milliseconds since epoch) on an open fd.

fs.futimesSync(fd, 1700000000000, 1700000000000);
fs.utimesSync(path, atimeMs, mtimeMs) Experimental

(string, int, int) → void — path-based form, follows symlinks.

fs.utimesSync("notes.txt", 1700000000000, 1700000000000);
fs.lutimesSync(path, atimeMs, mtimeMs) Experimental

(string, int, int) → void — like utimesSync, but sets the timestamps on a symlink itself rather than its target.

fs.lutimesSync("hosts-link", 1700000000000, 1700000000000);
fs.readdirSync(path) Experimental

string → string[] — entry names in a directory, in no particular order.

const names: string[] = fs.readdirSync(".");
for (const n of names) {
  console.log(n);
}
fs.watch(path, listener) Experimental Wasm: limited

(string, (string, string) → void) → void — blocking, never returns; calls listener with the name of the file that changed and an event type ("change" for a data modification, "rename" for a create/delete/move — matching Node's own fs.watch convention, not inotify's full granularity) on every event under path. Linux only (raw inotify, no libc). Not EventEmitter-based like Node's real FSWatcher — nothing drives an emitter asynchronously on its own here, so this calls the listener directly in a blocking loop instead, the same shape as http.createServer.

function onChange(name: string, eventType: string): void {
  console.log(name + " " + eventType);
}

fs.watch("./src", onChange);
fs.createReadStream(path) Experimental

string → ReadableStream — built the same way Map/Set/EventEmitter are, a real dedicated type. .read() returns the next chunk (up to 64KB), an empty string at EOF; .close() closes the file. A missing/unopenable file degrades to a stream that always reads "" rather than crashing, the same fallback shape every other fs function already uses. Synchronous/blocking, no async or backpressure integration yet.

const r = fs.createReadStream("large.log");
let chunk: string = r.read();
while (chunk != "") {
  console.log(chunk.length);
  chunk = r.read();
}
r.close();
fs.createWriteStream(path) Experimental

string → WritableStream — .write(chunk) appends a chunk to the file (opens/ truncates on creation); .close() flushes and closes.

const w = fs.createWriteStream("out.log");
w.write("line one\n");
w.write("line two\n");
w.close();