first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# `fs-helpers`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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.