Initial Commit
This commit is contained in:
@@ -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.
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
AUDIT_STDOUT_CODES,
|
||||
AuditStdoutError,
|
||||
type AuditStdoutErrorCode,
|
||||
} from "./error-codes.js";
|
||||
Reference in New Issue
Block a user