first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user