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,104 @@
# `RulesError` — Typed error registry for `@suaveplan/rules`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`RulesError` is the single error class thrown across the entire `@suaveplan/rules` surface. It extends `SuaveplanError<RulesErrorCode>` so every failure carries a stable, machine-readable code drawn from a frozen 24-entry registry, plus the standard `SuaveplanError` wire format, OTel-adapter integration, and cause-chain preservation. Callers should never catch a generic `Error` from this package — `instanceof RulesError` (or `isRulesError(value)`) is the canonical discrimination path.
The registry is defined at module load via `defineCodes("RULES", [...] as const)`, which exports `RULES_CODES` as a readonly tuple literal. The `RulesErrorCode` type is derived from that tuple, so the TypeScript compiler enforces that every `new RulesError(code, ...)` call site uses a code that actually exists in the registry. Adding a new code requires an OpenSpec change (the conformance script verifies the registry against the spec's `## ADDED Requirements`).
## Features
- 24 stable codes covering engine / operator / field-resolution / action / event / validator / serializer / optimizer / plugin failure modes.
- `RulesError` class extends `SuaveplanError` for cross-package wire-format and OTel-adapter compatibility.
- Optional `cause`, `traceId`, and `details` fields preserved through construction.
- `isRulesError(value)` type guard for catch arms that need to discriminate package errors from unrelated runtime errors.
- Every code is forward-declared at module load — no dynamic registration, no string-typed escape hatches.
## Usage: Basic Example
```ts
import { RulesError, isRulesError } from "@suaveplan/rules/errors";
function evaluate(rule: unknown) {
if (typeof rule !== "object" || rule === null) {
throw new RulesError("RULES.RULE_INVALID", "rule must be an object");
}
// ... real evaluation
}
try {
evaluate(null);
} catch (err) {
if (isRulesError(err) && err.code === "RULES.RULE_INVALID") {
// structured handling
} else {
throw err;
}
}
```
## Usage: Advanced Example
```ts
import { RulesError } from "@suaveplan/rules/errors";
async function resolveField(data: unknown, path: string, strict: boolean) {
try {
return walk(data, path);
} catch (cause) {
throw new RulesError(
"RULES.FIELD_RESOLUTION_FAILED",
`cannot resolve "${path}" in strict mode`,
{
cause,
traceId: currentTraceId(),
details: { path, strict, hasData: data !== undefined },
},
);
}
}
```
The `details` object becomes part of the structured wire format when the error
crosses a process boundary (for example, when a Hono middleware serialises a
denied request into a JSON envelope). The OTel adapter on `SuaveplanError`
projects `code`, `traceId`, and `details` as span attributes automatically.
## API Reference
### `RULES_CODES: readonly RulesErrorCode[]`
The frozen 24-entry registry of stable error codes. Every entry is prefixed
with `RULES.` and corresponds to a documented failure mode in the package's
OpenSpec contract.
### `type RulesErrorCode`
Literal-union type derived from `RULES_CODES`. Constructor calls that pass a
string not in the union are rejected at compile time.
### `class RulesError extends SuaveplanError<RulesErrorCode>`
- `new RulesError(code, message, opts?)` — constructor.
- `code: RulesErrorCode` — required, must be a member of `RULES_CODES`.
- `message: string` — required, human-readable summary.
- `opts.cause?: unknown` — original thrown value when wrapping.
- `opts.traceId?: string` — propagated to the SuaveplanError base; appears in OTel span attributes.
- `opts.details?: Readonly<Record<string, unknown>>` — structured context; omitted from `err.details` when not supplied.
- Inherits `code`, `message`, `cause`, `traceId`, `toWire()` from `SuaveplanError`.
- `name` is `"RulesError"`.
### `isRulesError(value: unknown): value is RulesError`
Returns `true` only when `value` is a `RulesError` instance. Plain `Error`
objects with a `code` field do not satisfy the guard.
## Implementation Notes
- The registry is intentionally flat (no nested namespaces). Sub-domain grouping is expressed via the prefix (`ENGINE_*`, `ACTION_*`, `VALIDATOR_*`) rather than a structured tree, matching the convention used by `@suaveplan/crypto-rsa` and `@suaveplan/webauthn`.
- `details` is stored only when explicitly supplied; the property is `undefined` (not an empty object) otherwise. This keeps the wire format compact and lets receivers test `if (err.details)` without false positives.
- `RulesError` is `extends SuaveplanError` not `extends Error` — the AGENTS.md §2c rule against deleting registry entries to satisfy a gate applies here. If a code is "unused", the fix is to wire it into the path the proposal designed it for, not to remove it.
- Cross-runtime: tests import runner primitives from `@suaveplan/testing/runner` per CLAUDE.md rule 11. Direct `vitest` / `bun:test` imports are banned in this package.