# EcsError & ECS_CODES _(no submodules)_ ## Purpose `EcsError` is the typed error class for every failure mode raised by `@suaveplan/ecs`. It extends `SuaveplanError` from `@suaveplan/error`, which means every thrown error carries a stable, fully-qualified `code` string and a structured `context` object documenting the inputs at the failure site. Callers can discriminate failures from this package either by `instanceof EcsError` or by inspecting `error.code`. `ECS_CODES` is the canonical registry of every code this package raises. The registry is generated through `defineCodes("ECS", [...])` from `@suaveplan/error`, which fully-qualifies the string literals (e.g., `"ECS.ENTITY_NOT_ALIVE"`) and exposes them as a frozen object. ## Features - Stable, fully-qualified codes — `ECS.ENTITY_NOT_ALIVE`, `ECS.COMPONENT_NOT_REGISTERED`, `ECS.COMPONENT_NAME_CONFLICT`, `ECS.COMPONENT_LIMIT_REACHED`, `ECS.COMPONENT_VALIDATION_FAILED`, `ECS.SYSTEM_NAME_CONFLICT`, `ECS.SNAPSHOT_INVALID`. - Every code is exercised by `src/errors/ecs-error.test.ts` from a real production code path — registry-to-throw-site coverage is a test invariant. - Structured `context` field on every error (`{ entity }`, `{ name }`, `{ issues }`, etc.) so the wire-redacted error envelope remains useful for debugging. - Cause chain preserved through `SuaveplanError`'s base support. - Code type `EcsCode` is a literal union narrowable in `switch` statements. ## Usage: Basic Example ```ts import { createWorld, defineComponent, ECS_CODES, EcsError } from "@suaveplan/ecs"; const Position = defineComponent("Position", { x: 0, y: 0 }); const world = createWorld(); const entity = world.spawn(); world.despawn(entity); try { world.addComponent(entity, Position); } catch (err) { if (err instanceof EcsError && err.code === ECS_CODES.ENTITY_NOT_ALIVE) { console.error("entity was already despawned"); } } ``` ## Usage: Advanced Example ```ts import { ECS_CODES, EcsError } from "@suaveplan/ecs"; function isRetryable(err: unknown): boolean { if (!(err instanceof EcsError)) return false; switch (err.code) { case ECS_CODES.ENTITY_NOT_ALIVE: case ECS_CODES.SNAPSHOT_INVALID: return false; case ECS_CODES.COMPONENT_VALIDATION_FAILED: return false; case ECS_CODES.COMPONENT_LIMIT_REACHED: case ECS_CODES.COMPONENT_NAME_CONFLICT: case ECS_CODES.COMPONENT_NOT_REGISTERED: case ECS_CODES.SYSTEM_NAME_CONFLICT: return false; default: return false; } } ``` ## API Reference | Symbol | Description | |---|---| | `EcsError` | `SuaveplanError`-extending class. Constructor: `new EcsError(code, messageKey, options?)`. | | `EcsCode` | Type union over every registered code string. | | `ECS_CODES` | Frozen registry returned by `defineCodes`. | | `ECS_CODES.ENTITY_NOT_ALIVE` | Thrown from `addComponent` / `removeComponent` / `setComponent` for despawned entities. | | `ECS_CODES.COMPONENT_NOT_REGISTERED` | `registry.getId(schema)` on an unknown schema. | | `ECS_CODES.COMPONENT_NAME_CONFLICT` | Two distinct schemas sharing a `name`. | | `ECS_CODES.COMPONENT_LIMIT_REACHED` | `register` past `maxComponents`. | | `ECS_CODES.COMPONENT_VALIDATION_FAILED` | Zod validator rejected merged component data. | | `ECS_CODES.SYSTEM_NAME_CONFLICT` | `addSystem` with a name already registered. | | `ECS_CODES.SNAPSHOT_INVALID` | `restore` payload failed `WorldSnapshotSchema`. | ## Implementation Notes - Codes are namespaced under `ECS.` so cross-package error log aggregation can filter by the `ECS.*` prefix. - `EcsError` does not override `Error.prototype.toJSON` — `SuaveplanError` already exposes `toWire` / `toSpanEvent` for serialisation, and we don't extend the contract. - The test suite enumerates `Object.values(ECS_CODES)` and triggers every code through a production call. If a new code is added without a real throw site, the test will fail — this is the registered-but-unthrown drift guard required by the spec-conformance checklist (`AGENTS.md` §13).