Files
skills/docs/foundation/flow/rules/src/errors/rules-error.md
2026-05-29 08:20:56 +01:00

5.1 KiB

RulesError — Typed error registry for @suaveplan/rules

(no submodules)

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

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

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.