Files
skills/docs/api-stack/api/api-audit-file/src/fs-helpers/fs-helpers.md
2026-05-29 08:20:56 +01:00

2.8 KiB

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

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:

import type { FsAdapter } from "@suaveplan/api-audit-file/fs-helpers";

const inMemory = new Map<string, string>();
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<string[]>
  • 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.