Files
skills/docs/gaming/games/ecs/src/errors/ecs-error.md
2026-05-29 08:20:56 +01:00

4.0 KiB

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

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

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).