API reference
The complete public surface of @warlock.js/fs. It’s organized the way you
should reach for it: the fs facade is the main event —
the async, ergonomic surface you’ll use for almost everything. The
low-level primitives sit underneath it
as the synchronous escape hatch and the building blocks the facade delegates to.
The fs facade
Section titled “The fs facade”import { fs } from "@warlock.js/fs";fs is a single async object. Four groups hang off it — fs.files.* for
files, fs.dirs.* for directories, fs.file(path) / fs.dir(path) for lazy
handles, and fs.hash.* / fs.exists() for the odds and ends. Every method is
async by design; synchronous callers drop to the
primitives.
The facade does not sandbox paths — containment is the storage layer’s job.
fs.files.* — file operations
Section titled “fs.files.* — file operations”Read:
get(path: string, options?: ReadOptions): Promise<string | Buffer> // string; Buffer when { encoding: null }getJson<T>(path: string, options?: ReadJsonOptions<T>): Promise<T> // { schema } validates, { default } on ENOENTWrite:
put(path: string, content: string | Buffer, options?: WriteOptions): Promise<void>putJson(path: string, value: unknown, options?: WriteJsonOptions): Promise<void>create(path: string, content: string | Buffer, options?: WriteOptions): Promise<void> // put with { overwrite: false }createJson(path: string, value: unknown, options?: WriteJsonOptions): Promise<void> // putJson with { overwrite: false }Append / prepend:
append(path: string, content: string): Promise<void>prepend(path: string, content: string): Promise<void>appendLine(path: string, line: string): Promise<void> // newline-terminatedappendJsonLine(path: string, value: unknown): Promise<void> // NDJSON — one JSON record per lineTransform in place:
edit(path: string, editor: (content: string) => string | Promise<string>): Promise<void>editJson<T>(path: string, editor: (value: T) => T | Promise<T>): Promise<void>mergeJson<T>(path: string, partial: Partial<T>, options?: MergeJsonOptions): Promise<void> // shallow; { deep } to recurseensureJson<T>(path: string, fallback: T): Promise<T> // read, or create-and-return the fallbackExistence / shape / lifecycle:
exists(path: string): Promise<boolean>isEmpty(path: string): Promise<boolean>size(path: string): Promise<number>ensure(path: string): Promise<void> // create-if-missing — never truncatestouch(path: string): Promise<void>remove(path: string): Promise<void> // ENOENT-safeMetadata:
stats(path: string): Promise<FileStats>lastModified(path: string): Promise<Date>hash(path: string, algorithm?: HashAlgorithm): Promise<string>checksumMatches(path: string, expected: string, algorithm?: HashAlgorithm): Promise<boolean>Move / copy / stream:
copy(from: string, to: string, options?: CopyOptions): Promise<void>move(from: string, to: string, options?: MoveOptions): Promise<void> // EXDEV-safe: falls back to copy+deletereadLines(path: string): AsyncIterable<string> // for await (const line of fs.files.readLines(p))fs.dirs.* — directory operations
Section titled “fs.dirs.* — directory operations”Lifecycle:
ensure(path: string): Promise<void> // recursive, idempotentremove(path: string): Promise<void> // recursive deleteempty(path: string): Promise<void> // clear contents, keep the directoryExistence / shape:
exists(path: string): Promise<boolean>isEmpty(path: string): Promise<boolean>count(path: string): Promise<number> // immediate childrensize(path: string): Promise<number> // total bytes, recursiveMove / copy / metadata:
copy(from: string, to: string, options?: CopyOptions): Promise<void>move(from: string, to: string, options?: MoveOptions): Promise<void> // EXDEV-safestats(path: string): Promise<FileStats>hash(path: string, algorithm?: HashAlgorithm): Promise<string> // stable tree fingerprintListing / walking:
list(path: string, options?: ListOptions): Promise<string[]> // files + subdirectorieslistFiles(path: string, options?: ListOptions): Promise<string[]>listDirs(path: string, options?: ListOptions): Promise<string[]>walk(path: string, options?: WalkOptions): AsyncIterable<WalkEntry> // { path, name, type }fs.file(path): File
Section titled “fs.file(path): File”A lazy, immutable handle to a file path. No IO runs in the constructor, and
mutating methods (copy / copyTo / move / moveTo / rename) return a
new handle rather than changing this one.
const file = fs.file("cache/report.json");const renamed = await file.rename("report.v2.json"); // `file` still points at report.jsonProperties:
file.path: stringfile.name: string // "report.json"file.basename: string // "report"file.extension: string // ".json"file.parent(): DirectoryMethods mirror fs.files.* (path-bound):
get(options?) / getJson<T>(options?)put(content, options?) / putJson(value, options?)append(content) / prepend(content) / appendJsonLine(value)edit(editor) / editJson(editor) / mergeJson(partial, options?)exists() / isEmpty() / ensure() / touch() / remove()stats() / size() / lastModified() / hash(algorithm?)readLines(): AsyncIterable<string>copy(to: string): Promise<File> copyTo(dir: string): Promise<File>move(to: string): Promise<File> moveTo(dir: string): Promise<File>rename(name: string): Promise<File>fs.dir(path): Directory
Section titled “fs.dir(path): Directory”A lazy, immutable handle to a directory path. file(...segments) and
dir(...segments) compose child handles without touching disk.
const uploads = fs.dir("storage/uploads");const avatar = uploads.dir("users").file("42", "avatar.png"); // storage/uploads/users/42/avatar.pngProperties:
dir.path: stringdir.name: stringdir.parent(): Directorydir.file(...segments: string[]): Filedir.dir(...segments: string[]): DirectoryMethods mirror fs.dirs.* (path-bound):
ensure() / remove() / empty()exists() / isEmpty() / count()stats() / size() / hash(algorithm?)list(options?) / listFiles(options?): Promise<File[]> listDirs(options?): Promise<Directory[]>walk(options?): AsyncIterable<WalkEntry>copy(to: string): Promise<Directory> move(to: string): Promise<Directory>fs.exists · fs.hash.*
Section titled “fs.exists · fs.hash.*”fs.exists(path: string): Promise<boolean> // type-agnostic — file OR directory
fs.hash.string(content: string, algorithm?): string // sync — pure, in-memoryfs.hash.buffer(bytes: Buffer | Uint8Array, algorithm?): string // syncfs.hash.file(path: string, algorithm?): Promise<string> // async — reads from diskfs.hash.dir(path: string, algorithm?): Promise<string> // async — stable tree fingerprintAll hashing defaults to sha256.
Option & result types
Section titled “Option & result types”Every option bag is a plain object, exported from the package root.
type ReadOptions = { encoding?: BufferEncoding | null }; // encoding: null → Buffer
type ReadJsonOptions<T = unknown> = { schema?: StandardSchemaV1<T>; // validate the parsed value default?: T; // returned on ENOENT instead of throwing};
type WriteOptions = { encoding?: BufferEncoding; // string writes; default "utf-8" atomic?: boolean; // temp file + rename ensureDir?: boolean; // create parents; default true overwrite?: boolean; // false → throw if target exists; default true};
type WriteJsonOptions = WriteOptions & { indent?: number }; // default 2type MergeJsonOptions = WriteJsonOptions & { deep?: boolean }; // shallow by default
type CopyOptions = { overwrite?: boolean; // default true errorOnExist?: boolean; // default false dereference?: boolean; // follow symlinks; default false};
type MoveOptions = { overwrite?: boolean; ensureDir?: boolean }; // ensureDir default true
type ListOptions = { recursive?: boolean };type WalkOptions = { recursive?: boolean; followSymlinks?: boolean };type WalkEntry = { path: string; name: string; type: "file" | "directory"; // discriminated by `type`, never `kind`};
type FileStats = { path: string; name: string; size: number; type: "file" | "directory"; lastModified: Date; raw: import("node:fs").Stats; // escape hatch to the raw stats};Schema validation on JSON reads is validator-agnostic — @warlock.js/fs ships
with zero dependencies, so it speaks the neutral
Standard Schema contract instead of importing any
validator.
interface StandardSchemaV1<Output = unknown> { readonly "~standard": { /* version, vendor, validate */ } }
class JsonSchemaValidationError extends Error { readonly path: string; readonly issues: ReadonlyArray<StandardSchemaIssue>;}Any Standard-Schema-compliant validator — @warlock.js/seal, zod, valibot —
can be passed as getJson’s schema. On failure it throws
JsonSchemaValidationError carrying the raw issues.
Low-level primitives (sync + *Async)
Section titled “Low-level primitives (sync + *Async)”The flat functions the facade is built on. Reach for these when you need a
synchronous call (the facade is async-only) or want the thinnest possible
wrapper over node:fs. Each comes in a bare (sync) and *Async flavor.
Read / write / JSON
Section titled “Read / write / JSON”getFileAsync(path: string): Promise<string> getFile(path: string): stringgetJsonFileAsync<T>(path: string): Promise<T> getJsonFile<T>(path: string): TputFileAsync(filePath: string, content: string): Promise<void> putFile(filePath, content): voidputJsonFileAsync(filePath: string, value: unknown): Promise<void> putJsonFile(filePath, value): voidput* create missing parents, overwrite existing files, and are text-only
— for binary or crash-safe writes use the atomic writers.
Atomic write (async only)
Section titled “Atomic write (async only)”atomicWriteAsync(filePath: string, content: string | Buffer): Promise<void> // temp file + renameatomicWriteJsonAsync(filePath: string, value: unknown): Promise<void> // pretty-printed, 2-spaceDirectories
Section titled “Directories”ensureDirectoryAsync(path: string): Promise<void> ensureDirectory(path: string): void // recursive, idempotentremoveDirectoryAsync(path: string): Promise<void> removeDirectory(path: string): void // recursive, ENOENT-safeListing
Section titled “Listing”listAsync(dir: string): Promise<string[]> list(dir: string): string[] // files + subdirslistFilesAsync(dir: string): Promise<string[]> listFiles(dir: string): string[]listDirectoriesAsync(dir: string): Promise<string[]> listDirectories(dir: string): string[]All return full paths joined to the directory.
Copy / rename
Section titled “Copy / rename”copyFileAsync(source, destination): Promise<void> copyFile(source, destination): void // creates dest parentcopyDirectoryAsync(source, destination): Promise<void> copyDirectory(source, destination): void // recursiverenameFileAsync(from, to): Promise<void> renameFile(from, to): void // no auto-parent; EXDEV on cross-mountDelete
Section titled “Delete”unlinkAsync(path: string): Promise<void> unlink(path: string): void // single file, ENOENT-safe(Recursive deletes live on removeDirectory* above.)
lastModifiedAsync(path: string): Promise<Date> lastModified(path: string): Date // mtimestatsAsync(path: string): Promise<import("node:fs").Stats> stats(path): import("node:fs").StatsExistence
Section titled “Existence”pathExistsAsync(path: string): Promise<boolean> pathExists(path: string): boolean // file OR directoryfileExistsAsync(path: string): Promise<boolean> fileExists(path: string): boolean // file only (follows symlinks)directoryExistsAsync(path: string): Promise<boolean> directoryExists(path: string): boolean // directory onlyHashing
Section titled “Hashing”type HashAlgorithm = "sha256" | "sha1" | "md5" | "sha512"; // default "sha256"
hashFileAsync(path: string, algorithm?: HashAlgorithm): Promise<string> // streaming — constant memoryhashFileSmallAsync(path: string, algorithm?: HashAlgorithm): Promise<string> // one-shot; small files onlyhashFile(path: string, algorithm?: HashAlgorithm): string // sync; small files onlyhashString(content: string, algorithm?: HashAlgorithm): string // in-memoryhashBuffer(content: Buffer | Uint8Array, algorithm?: HashAlgorithm): string // in-memorySee also
Section titled “See also”- The fs facade — why
fs.*is the way in. - Read and write files — the IO walkthrough.
- Write atomically — the atomic-write deep dive.
- Manage directories — directory operations end to end.
- Hash files — hashing strings, buffers, files, and trees.
Source: @warlock.js/fs/src/