3.2 KiB
rotation
(no submodules)
Purpose
Resolves the rotation policy carried by every createFileAuditSink
instance and implements the rotation primitive itself — the
rename(currentFile → currentFile.<utc-iso>.log) step plus the
post-rotation state reset. The module owns both dimensions documented
in the spec: size-based (maxSizeBytes, default 100 MiB) and
time-based (maxAgeMs, default 24h). Either condition reaching its
limit triggers a rotation on the next write.
Rotation lives in a separate module so the sink stays focused on
queueing + writing. The retention module reads the policy through the
same RotationState so the operator-facing knobs are documented in
exactly one place.
Features
resolveRotationPolicy(input?)— Zod-equivalent validator returning a frozenRotationPolicy. Defaults filled from the design doc.shouldRotate(state, nowMs)— pure predicate; size OR age satisfies.rotate(fs, basePath, state, clock)— performs the rename, updates state, returns the rotated path for retention to inspect.- Raises
AUDIT_FILE.ROTATION_FAILEDon rename failure with fullcausefor forensic logging.
Usage: Basic
import {
resolveRotationPolicy,
rotate,
shouldRotate,
} from "@suaveplan/api-audit-file/rotation";
const policy = resolveRotationPolicy({ maxSizeBytes: 1024 * 1024 });
const state = { openedAtMs: Date.now(), bytesWritten: 0, policy };
if (shouldRotate(state, Date.now())) {
await rotate(fs, "/var/log/audit.log", state, () => Date.now());
}
Usage: Advanced
Disable a dimension by setting it to Number.POSITIVE_INFINITY —
useful when an operator wants pure time rotation regardless of size:
const policy = resolveRotationPolicy({
maxSizeBytes: Number.POSITIVE_INFINITY,
maxAgeMs: 60 * 60 * 1000, // hourly
});
In tests, drive rotation deterministically with a FakeClock from
@suaveplan/testing:
import { FakeClock } from "@suaveplan/testing";
const clock = new FakeClock(0);
const state = { openedAtMs: 0, bytesWritten: 0, policy };
clock.advance(61_000);
await rotate(fs, basePath, state, clock.read);
API Reference
interface RotationPolicymaxSizeBytes: number— bytes before size rotation; default 100 MiB.maxAgeMs: number— ms before time rotation; default 24h.maxFiles: number— kept rotated files; default 30.
interface RotationStateopenedAtMs: number— wall-clock open time of current segment.bytesWritten: number— running byte count.policy: RotationPolicy— frozen at construction.
resolveRotationPolicy(input?): RotationPolicyshouldRotate(state, nowMs): booleanrotate(fs, basePath, state, clock): Promise<string>— returns the rotated file path.
Implementation Notes
The rotated suffix replaces : with - so the resulting filename is
Windows-portable and grep-friendly across shells. State mutation
happens AFTER the rename succeeds — a failed rename leaves the original
state intact, so the next call retries against the same segment rather
than corrupting accounting. bytesWritten resets to 0 instead of
re-stat'ing the new empty file; the next successful append updates it
in lock-step with the on-disk length.