Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# `error-codes` — `AUDIT_STDOUT_*` registry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module declares the four stable error codes the stdout audit sink
exposes to consumers. Every failure path inside `createStdoutAuditSink`
maps onto exactly one of them, so callers can branch on `error.code`
without parsing messages or stack traces. The registry uses
`@suaveplan/error`'s `defineCodes` so the union narrows under
`instanceof AuditStdoutError`.
The codes capture the four observable failure modes of a write-only,
fire-and-forget audit pipeline: a record that fails its own Zod schema
(`INVALID_RECORD`), an option bag that fails the constructor schema
(`INVALID_OPTIONS`), a transport error from the supplied writer
(`WRITER_FAILED`), and the bounded-backpressure drop case
(`QUEUE_FULL`). The codes are surfaced through the
`AuditStdoutError` subclass, which carries a `code` property typed as
the literal union — `instanceof` narrowing flows through.
## Features
- Stable string codes via `defineCodes("AUDIT_STDOUT", [...])`.
- `AuditStdoutError` subclass with public constructor (lint rule
`check-error-subclass-constructors` enforced).
- Code union exported as `AuditStdoutErrorCode` for callers who want
to switch over every member.
- No runtime side effects — the registry is a frozen object literal.
## Usage: Basic
```ts
import {
AUDIT_STDOUT_CODES,
AuditStdoutError,
} from "@suaveplan/api-audit-stdout/error-codes";
throw new AuditStdoutError(
AUDIT_STDOUT_CODES.INVALID_RECORD,
"error.audit_stdout.invalid_record"
);
```
## Usage: Advanced
```ts
import {
AUDIT_STDOUT_CODES,
AuditStdoutError,
type AuditStdoutErrorCode,
} from "@suaveplan/api-audit-stdout/error-codes";
function handle(err: unknown): "drop" | "rethrow" {
if (!(err instanceof AuditStdoutError)) return "rethrow";
const code: AuditStdoutErrorCode = err.code;
switch (code) {
case AUDIT_STDOUT_CODES.QUEUE_FULL:
case AUDIT_STDOUT_CODES.WRITER_FAILED:
return "drop";
case AUDIT_STDOUT_CODES.INVALID_RECORD:
case AUDIT_STDOUT_CODES.INVALID_OPTIONS:
return "rethrow";
}
}
```
## API Reference
- `AUDIT_STDOUT_CODES` — frozen `{ INVALID_RECORD, WRITER_FAILED,
QUEUE_FULL, INVALID_OPTIONS }` mapping to the corresponding
`"AUDIT_STDOUT.*"` literal.
- `AuditStdoutErrorCode` — union of the four literal strings.
- `AuditStdoutError` — `SuaveplanError<AuditStdoutErrorCode>` subclass
with a public constructor accepting `(code, message, options?)`.
## Implementation Notes
`defineCodes` returns a frozen object — adding a new code requires an
OpenSpec change. The subclass declares its own public constructor so
`new AuditStdoutError(...)` is callable from outside the module (the
base class is abstract with a protected constructor). The
`auditStdoutCode` field is a typed alias of `code` for callers who
want a non-generic accessor.

View File

@@ -0,0 +1,51 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
AUDIT_STDOUT_CODES,
AuditStdoutError,
type AuditStdoutErrorCode,
} from "./error-codes.js";
describe("AUDIT_STDOUT_CODES", () => {
it("exposes four stable string codes", { timeout: 5000 }, () => {
expect(AUDIT_STDOUT_CODES.INVALID_RECORD).toBe(
"AUDIT_STDOUT.INVALID_RECORD"
);
expect(AUDIT_STDOUT_CODES.WRITER_FAILED).toBe(
"AUDIT_STDOUT.WRITER_FAILED"
);
expect(AUDIT_STDOUT_CODES.QUEUE_FULL).toBe("AUDIT_STDOUT.QUEUE_FULL");
expect(AUDIT_STDOUT_CODES.INVALID_OPTIONS).toBe(
"AUDIT_STDOUT.INVALID_OPTIONS"
);
});
it("registry object is frozen", { timeout: 5000 }, () => {
expect(Object.isFrozen(AUDIT_STDOUT_CODES)).toBe(true);
});
});
describe("AuditStdoutError", () => {
it("constructor binds code into typed alias", { timeout: 5000 }, () => {
const err = new AuditStdoutError(
AUDIT_STDOUT_CODES.INVALID_RECORD,
"error.audit_stdout.invalid_record"
);
const code: AuditStdoutErrorCode = err.code;
expect(code).toBe(AUDIT_STDOUT_CODES.INVALID_RECORD);
expect(err.auditStdoutCode).toBe(AUDIT_STDOUT_CODES.INVALID_RECORD);
expect(err).toBeInstanceOf(AuditStdoutError);
expect(err).toBeInstanceOf(Error);
});
it("forwards cause through SuaveplanError options", {
timeout: 5000,
}, () => {
const cause = new Error("inner");
const err = new AuditStdoutError(
AUDIT_STDOUT_CODES.WRITER_FAILED,
"error.audit_stdout.writer_failed",
{ cause }
);
expect(err.cause).toBe(cause);
});
});

View File

@@ -0,0 +1,50 @@
import type { SuaveplanErrorOptions } from "@suaveplan/error";
import { defineCodes, SuaveplanError } from "@suaveplan/error";
/**
* Stable error-code registry for `@suaveplan/api-audit-stdout`.
*
* - `INVALID_RECORD` — the record passed to `sink.write(...)` failed
* `AuditRecord` Zod validation at the boundary. `cause` carries the
* underlying `z.ZodError`. Records that fail validation are NEVER
* written.
* - `WRITER_FAILED` — the supplied writer threw or rejected
* asynchronously when the sink attempted to write a line. The
* record is treated as a drop; callers see the original writer
* error via `cause`.
* - `QUEUE_FULL` — the queue exceeded `maxQueueMs` of pending work
* for the current record. The record is dropped, the drop counter
* increments, and (subject to the throttle) a warning is logged.
* - `INVALID_OPTIONS` — `createStdoutAuditSink({...})` was called
* with an option bag that failed Zod validation at the boundary.
*
* Consumers branch on the string value, e.g.
* `error.code === AUDIT_STDOUT_CODES.INVALID_RECORD`.
*/
export const AUDIT_STDOUT_CODES = defineCodes("AUDIT_STDOUT", [
"INVALID_RECORD",
"WRITER_FAILED",
"QUEUE_FULL",
"INVALID_OPTIONS",
] as const);
export type AuditStdoutErrorCode =
(typeof AUDIT_STDOUT_CODES)[keyof typeof AUDIT_STDOUT_CODES];
/**
* Concrete error class for the `api-audit-stdout` package. Extends
* {@link SuaveplanError} so `instanceof AuditStdoutError` narrows
* `code` to {@link AuditStdoutErrorCode}.
*/
export class AuditStdoutError extends SuaveplanError<AuditStdoutErrorCode> {
public readonly auditStdoutCode: AuditStdoutErrorCode;
public constructor(
code: AuditStdoutErrorCode,
message: string,
options?: SuaveplanErrorOptions
) {
super(code, message, options);
this.auditStdoutCode = code;
}
}

View File

@@ -0,0 +1,5 @@
export {
AUDIT_STDOUT_CODES,
AuditStdoutError,
type AuditStdoutErrorCode,
} from "./error-codes.js";