# error-codes _(no submodules)_ ## Purpose The `@suaveplan/seed` package centralises every failure mode it can produce behind a single registry built with `defineCodes("SEED", [...])` from `@suaveplan/error`. The registry is the stable contract callers depend on: code strings are documented, frozen at module load, and never renamed without an explicit migration. This module exports the `SEED_CODES` registry, the derived `SeedErrorCode` type, and the `SeedError` class that every public failure path raises. `SeedError` extends `SuaveplanError` so `instanceof SeedError` narrows `code` to the exact union of `SEED.*` strings. It adds optional `seedName` and `dependency` discriminants that diagnose which seed (or which missing dependency) triggered the error, while leaving the base `context` map free for caller-supplied structured data. The class threads `cause` straight through `SuaveplanError`, so store-layer exceptions wrapped by the runner remain inspectable through `error.cause`. ## Features - Frozen `SEED_CODES` registry with seven namespaced codes - `SeedErrorCode` literal-union type derived from the registry - `SeedError` class extending `SuaveplanError` - Optional `seedName` and `dependency` diagnostic discriminants - Full `cause`-chain preservation via `SuaveplanError` - Compatible with the OTel span-event adapter shipped by `@suaveplan/error` ## Usage: Basic ```ts import { SEED_CODES, SeedError } from "@suaveplan/seed"; if (registry.has(seed.name)) { throw new SeedError(SEED_CODES.DUPLICATE_NAME, `seed "${seed.name}" already registered`, { seedName: seed.name, }); } ``` ## Usage: Advanced ```ts import { SEED_CODES, SeedError } from "@suaveplan/seed"; import { SuaveplanError } from "@suaveplan/error"; try { await store.insert(seed.table, batch); } catch (cause) { throw new SeedError(SEED_CODES.STORE_ERROR, `store rejected batch for ${seed.table}`, { cause: cause instanceof Error ? cause : undefined, seedName: seed.name, context: { table: seed.table, batchSize: batch.length }, }); } // Downstream: catch (err) { if (err instanceof SeedError && err.code === SEED_CODES.STORE_ERROR) { logger.error("seed.store.failure", { seedName: err.seedName, rootMessage: err.cause instanceof Error ? err.cause.message : String(err.cause), }); } } ``` ## API Reference ### `SEED_CODES` A frozen object literal of seven namespaced codes: | Property | Value | | ----------------------------- | ---------------------------------- | | `DUPLICATE_NAME` | `"SEED.DUPLICATE_NAME"` | | `UNREGISTERED_DEPENDENCY` | `"SEED.UNREGISTERED_DEPENDENCY"` | | `CIRCULAR_DEPENDENCY` | `"SEED.CIRCULAR_DEPENDENCY"` | | `DEPENDENCY_DEPTH_EXCEEDED` | `"SEED.DEPENDENCY_DEPTH_EXCEEDED"` | | `INVALID_CONFIG` | `"SEED.INVALID_CONFIG"` | | `STORE_ERROR` | `"SEED.STORE_ERROR"` | | `ABORTED` | `"SEED.ABORTED"` | ### `type SeedErrorCode` The literal union of every value in `SEED_CODES`. ### `class SeedError extends SuaveplanError` | Member | Type | | ------------------ | ---------------------------------------------------------------------- | | `code` | `SeedErrorCode` (narrowed) | | `seedName?` | `string` | | `dependency?` | `string` | | `cause?` | `unknown` (inherited) | | `timestamp` | `number` (inherited; `Date.now()` at construction) | | `constructor(...)` | `(code, message, options?: SeedErrorOptions)` | `SeedErrorOptions` extends `SuaveplanErrorOptions` (`cause`, `context`, `clock`, `traceId`) with the additional optional `seedName` and `dependency` fields. ## Implementation Notes `defineCodes` returns a `Object.freeze`d record with `null` prototype, so the registry cannot be mutated, reassigned, or extended at runtime — any attempt fails in strict mode (which TypeScript modules enforce). The literal type machinery preserves the exact `${Namespace}.${Reason}` string template across every consumer, so `switch (err.code)` enjoys exhaustive narrowing without runtime cost. `SeedError`'s constructor passes `message` straight through to `SuaveplanError`'s `messageKey` parameter. The base class resolves the key via the i18n resolver if one has been registered with `setMessageResolver`, falling back to the raw string otherwise — so this package works without i18n wiring and lights up automatically when `@suaveplan/i18n` is present. Optional `seedName` / `dependency` fields are stored on the error instance directly (not on `context`) so they survive structured-cloning and serialisation via the `toJSON` helper from `@suaveplan/error`. Consumers that programmatically dispatch on a missing dependency can read `err.dependency` without first parsing `err.context`.