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

7.5 KiB

GameServerError

(no submodules)

Purpose

GameServerError is the single typed-exception class for the @suaveplan/game-server package. It extends SuaveplanError<GameServerCode> from @suaveplan/error, anchoring every failure raised by the server framework to a stable, lint-checkable code string in the GAME_SERVER.* namespace. Callers can branch on error.code without depending on instanceof semantics that fail across module realms.

The companion GAME_SERVER_CODES constant is produced by defineCodes("GAME_SERVER", [...]) so the registry is verified at compile time and contributes its codes to the global suaveplan error catalog. The registry only enumerates codes that an actual production code path throws today — follow-up migrations (engine, state, matchmaking, telemetry, network) extend the array as their throw sites land, in line with AGENTS.md §13.

Features

  • Single typed error class for the entire package.
  • Codes resolved through @suaveplan/error's defineCodes registry.
  • Optional cause and context for chained diagnostics.
  • Compile-time exhaustiveness on the GameServerCode union.
  • Code namespace GAME_SERVER.* is reserved for this package across the workspace.

Usage: Basic

import {
    GAME_SERVER_CODES,
    GameServerError,
} from "@suaveplan/game-server/errors";

try {
    container.resolve("missing");
} catch (err) {
    if (
        err instanceof GameServerError &&
        err.code === GAME_SERVER_CODES.SERVICE_NOT_REGISTERED
    ) {
        // recover by registering a fallback
    }
}

Usage: Advanced

import { GameServerError, GAME_SERVER_CODES } from "@suaveplan/game-server/errors";

function loadConfig(input: unknown) {
    const parsed = GameConfigSchema.safeParse(input);
    if (!parsed.success) {
        throw new GameServerError(
            GAME_SERVER_CODES.INVALID_GAME_CONFIG,
            "Game config failed Zod validation",
            { cause: parsed.error, context: { input } },
        );
    }
    return parsed.data;
}

The optional context bag is logged by the standard @suaveplan/logger formatter and forwarded as OpenTelemetry span attributes by @suaveplan/opentelemetry, so diagnostic data flows without bespoke wiring.

API Reference

GAME_SERVER_CODES

A frozen object of code constants:

  • SERVICE_NOT_REGISTERED — thrown by DIContainer.resolve on an unknown identifier.
  • INVALID_GAME_CONFIG / INVALID_SESSION_METADATA / INVALID_PLAYER_INFO — thrown by the corresponding parse* boundary functions in ./types.
  • INVALID_MOVE — AbstractGameLogic.processMove validation failure.
  • PLAYER_ALREADY_EXISTS / PLAYER_NOT_FOUND — AbstractGameLogic.{addPlayer,removePlayer}.
  • LISTENER_LIMIT_EXCEEDED / INVALID_LISTENER_COUNT / EVENT_WAIT_TIMEOUT — GameEventEmitter.
  • STATE_DIFF_FAILED / STATE_PATCH_FAILED / STATE_RESTORE_FAILED / INVALID_SNAPSHOT / STATE_DECOMPRESS_FAILED / STATE_SYNC_TRANSPORT_FAILED — state-manager / differ / compressor / sync.
  • TRANSPORT_ALREADY_INITIALIZED / TRANSPORT_NOT_INITIALIZED / TRANSPORT_NOT_CONNECTED / INVALID_MESSAGE_FORMAT / RECONNECT_EXHAUSTED / RECONNECT_IN_PROGRESS — network adapter + reconnection manager + transports.
  • ENGINE_NOT_STARTED — engine method called before start().
  • ENGINE_ALREADY_STARTED — second start() call on a live engine.
  • ENGINE_DISPOSED — engine method called after dispose().
  • INVALID_ENGINE_OPTIONS — Zod failure on GameEngineOptions.
  • INVALID_GAME_LOOP_OPTIONS — Zod failure on GameLoopOptions.
  • GAME_LOOP_NOT_RUNNING — pause/resume/stop called on an idle loop.
  • GAME_LOOP_ALREADY_RUNNING — second start() call on a running loop.
  • PLUGIN_NOT_FOUND — uninstall, enable, disable, get on an unknown plugin name.
  • DUPLICATE_PLUGIN — install called with an already-registered plugin name.
  • PLUGIN_LIFECYCLE_FAILED — wraps any error thrown from a plugin lifecycle hook.
  • MATCHMAKING_QUEUE_FULL — QueueManager.addToQueue rejected because the per-key queue is at the configured cap.
  • INVALID_QUEUE_REQUEST — Zod failure on a QueueRequest arriving at QueueManager / MatchmakingEngine.
  • INVALID_MATCH_CRITERIA — Zod failure on a MatchCriteria arriving at MatchmakingEngine.createMatch.
  • NO_MATCH_FOUND — MatchmakingEngine.createMatch returned null when the caller requested throwOnNoMatch.
  • INVALID_SKILL_SCORE — SkillMatcher received a non-finite rating or seeker missing a positive waitingSince.
  • SPECTATOR_LIMIT_REACHED — SpectatorManager.addSpectator rejected because the session is at its spectator cap.
  • INVALID_SPECTATOR_REQUEST — Zod failure on SpectatorPermissions or duplicate spectator id.
  • UNKNOWN_MATCHING_ALGORITHM — MatchmakingEngine.createMatch was passed an algorithm string outside the registered union.
  • POOL_EXHAUSTED — ObjectPool.acquire rejected because the pool's maxSize is reached and no slots are free.
  • POOL_DISPOSED — ObjectPool method called after dispose().
  • RELEASE_FOREIGN_OBJECT — ObjectPool.release received an object that did not originate from this pool.
  • INVALID_POOL_CONFIG — ObjectPool constructor received a non-positive initialSize / maxSize, or maxSize smaller than initialSize.
  • INVALID_TIER_CONFIG — NetworkTierAdapter constructor received an empty / malformed tier list or a node-capability with a non-finite score.
  • UNKNOWN_TIER — NetworkTierAdapter queried for a tier index / node-type that is not registered.

Each value is the fully-qualified string e.g. "GAME_SERVER.SERVICE_NOT_REGISTERED". The latter six codes are introduced by Wave A (core foundation): INVALID_MOVE fires from AbstractGameLogic.processMove when validation fails; PLAYER_ALREADY_EXISTS / PLAYER_NOT_FOUND from addPlayer / removePlayer; LISTENER_LIMIT_EXCEEDED / INVALID_LISTENER_COUNT / EVENT_WAIT_TIMEOUT from GameEventEmitter.

The state-domain codes added in Wave B-3 (STATE_*, INVALID_SNAPSHOT) are thrown by the state-manager, state-differ, delta-compressor, and state-sync modules. STATE_DIFF_FAILED and STATE_PATCH_FAILED wrap unexpected exceptions raised while walking deeply-nested structures; STATE_RESTORE_FAILED and INVALID_SNAPSHOT surface when callers try to restore a snapshot whose shape disagrees with the manager's state schema; STATE_DECOMPRESS_FAILED wraps malformed wire packets; and STATE_SYNC_TRANSPORT_FAILED flags transport-layer send/receive faults.

type GameServerCode

Union of all fully-qualified code strings in the registry.

new GameServerError(code, message, options?)

  • code: GameServerCode
  • message: string
  • options?: { cause?: unknown; context?: Record<string, unknown> }

Instances expose .code, .message, .cause (or undefined), .context (or undefined when not supplied), and .name === "GameServerError".

Implementation Notes

SuaveplanError is parameterised over a code union, so removing a code from the defineCodes literal array produces a TypeScript error at every throw site — this is the lint-checkable contract that keeps the registry minimal and honest. Codes are flat strings (no nesting) because @suaveplan/error's canonical form is a single dot-delimited identifier. The class explicitly sets this.name because subclasses of Error inherit the parent name in some runtimes; downstream log scrapers key on the exact string "GameServerError".