# `fs-helpers` _(no submodules)_ ## Purpose Isolates the `node:fs` surface the sink touches behind an injectable `FsAdapter` interface and concrete `nodeFsAdapter` implementation. The indirection lets tests run the sink against a real filesystem under a temp directory (the default for this package's test suite) while keeping a clean seam for in-memory adapters used by external consumers. The module also exports the rotated-file enumeration helper used by the retention module — keeping the file-name conventions next to the filesystem primitives means rotation, retention, and sink all share the same view of "what counts as a rotated sibling of this base path". ## Features - `FsAdapter` — six-method surface: `appendLine`, `fileSize`, `rename`, `unlink`, `readdir`, `ensureWriteableDir`. - `nodeFsAdapter` — production adapter against `node:fs/promises`. - `listRotatedFiles(fs, basePath)` — returns full paths of every rotated sibling, sorted oldest-first by embedded timestamp. - `rotatedFilePathAt(basePath, iso)` — builds the rotated path for the supplied ISO timestamp; `:` is replaced with `-` so the name is Windows-safe and grep-friendly. ## Usage: Basic ```ts import { nodeFsAdapter } from "@suaveplan/api-audit-file/fs-helpers"; await nodeFsAdapter.ensureWriteableDir("/var/log/suaveplan"); await nodeFsAdapter.appendLine("/var/log/suaveplan/audit.log", "...\n", false); ``` ## Usage: Advanced Provide an injectable adapter to drive the sink against an in-memory filesystem: ```ts import type { FsAdapter } from "@suaveplan/api-audit-file/fs-helpers"; const inMemory = new Map(); const adapter: FsAdapter = { async appendLine(p, line) { inMemory.set(p, (inMemory.get(p) ?? "") + line); }, async fileSize(p) { return Buffer.byteLength(inMemory.get(p) ?? "", "utf8"); }, async rename(from, to) { inMemory.set(to, inMemory.get(from) ?? ""); inMemory.delete(from); }, async unlink(p) { inMemory.delete(p); }, async readdir() { return [...inMemory.keys()]; }, async ensureWriteableDir() { /* always writeable */ }, }; ``` ## API Reference - `interface FsAdapter` — see Features above. - `const nodeFsAdapter: FsAdapter` - `listRotatedFiles(fs: FsAdapter, basePath: string): Promise` - `rotatedFilePathAt(basePath: string, isoTimestamp: string): string` ## Implementation Notes `ensureWriteableDir` calls `mkdir(..., { recursive: true })` then a `W_OK` `access` probe so a fresh boot creates the directory and a borked permission setup throws `PATH_NOT_WRITEABLE` synchronously during sink construction. `fileSize` swallows `ENOENT` and returns `0` so the rotation logic naturally treats a not-yet-created file as empty — that simplifies the open-on-first-write code path.