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,90 @@
# sink
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The sink layer defines the `Sink` interface — the pluggable output primitive for `@suaveplan/logger` — and provides three concrete implementations: `ConsoleSink` for human-readable terminal output during development, `JsonSink` for line-delimited JSON (NDJSON) suitable for log aggregators, and `BufferedSink` for asynchronous, bounded-queue writes with configurable overflow policy and `AbortSignal`-aware flush. The `MultiSink` utility routes a single record to multiple sinks in parallel.
By decoupling the logger from any specific I/O implementation, the same `AbstractLogger` can write to a terminal, a file, a remote endpoint, or an in-memory buffer without code changes — only the sink configuration differs.
## Features
- `Sink` interface: `write(record)`, optional `flush(signal?)`, optional `close()`
- `ConsoleSink` — ANSI colour per level, ISO timestamp prefix, optional tag and metadata formatting
- `JsonSink` — serialises `LogRecord` to a single JSON line, passes to a pluggable `writer` callback (default: `process.stdout.write`)
- `BufferedSink` — bounded in-memory queue; `"drop"` policy silently discards overflow; `"block"` policy awaits drain; `flush(AbortSignal?)` drains with cancellation support
- `MultiSink` — fan-out to multiple sinks concurrently
- `isSink()` type guard; `flushSink()` / `closeSink()` helpers that no-op for sinks lacking those methods
## Usage: Basic Example
```ts
import { JsonSink } from "@suaveplan/logger/sink";
const sink = new JsonSink({ writer: (line) => process.stdout.write(line + "\n") });
sink.write(record); // {"ts":"...","level":"info","msg":"hello","ctx":{}}
```
## Usage: Advanced Example
```ts
import { BufferedSink } from "@suaveplan/logger/sink";
import { JsonSink } from "@suaveplan/logger/sink";
const json = new JsonSink({ writer: appendToFile });
const buf = new BufferedSink({
sink: json,
maxQueueSize: 500,
overflowPolicy: "drop",
});
// In a long-running process, flush on shutdown:
process.on("SIGTERM", async () => {
const ac = new AbortController();
setTimeout(() => ac.abort(), 5_000); // 5 s timeout
await buf.flush(ac.signal);
await buf.close();
});
```
## API Reference
### `interface Sink`
```ts
interface Sink {
write(record: LogRecord): void | Promise<void>;
flush?(signal?: AbortSignal): Promise<void>;
close?(): Promise<void>;
}
```
### `class ConsoleSink implements Sink`
Options: `{ colors?, timestamp?, includeMetadata?, includeTags? }`.
### `class JsonSink implements Sink`
Options: `{ writer? }`. Throws `LoggerError(LOGGER.SERIALIZE)` if `JSON.stringify` fails.
### `class BufferedSink implements Sink`
Options: `{ sink, maxQueueSize?, overflowPolicy? }`. Property `.dropped` counts overflow-dropped records.
### `class MultiSink implements Sink`
**Constructor**: `new MultiSink(sinks: Sink[])`. Writes and flushes all sinks concurrently.
### `isSink(value): value is Sink`
Type guard.
### `flushSink(sink, signal?) / closeSink(sink)`
Helpers that call `sink.flush(signal)` / `sink.close()` only if the method exists.
## Implementation Notes
`BufferedSink` does not start a background interval — it flushes lazily on `flush()` or `close()`. This keeps the class dependency-free (no `Clock` needed) and avoids surprises in test environments. The `"block"` overflow policy uses a simple Promise queue to suspend `write()` callers until the next flush iteration frees capacity.