first commit
This commit is contained in:
128
docs/persistence/database/seed/src/error-codes/error-codes.md
Normal file
128
docs/persistence/database/seed/src/error-codes/error-codes.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# error-codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<SeedErrorCode>` 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<SeedErrorCode>`
|
||||
- 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<SeedErrorCode>`
|
||||
|
||||
| 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`.
|
||||
Reference in New Issue
Block a user