first commit
This commit is contained in:
104
docs/foundation/flow/rules/src/errors/rules-error.md
Normal file
104
docs/foundation/flow/rules/src/errors/rules-error.md
Normal 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.
|
||||
Reference in New Issue
Block a user