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'sdefineCodesregistry. - Optional
causeandcontextfor chained diagnostics. - Compile-time exhaustiveness on the
GameServerCodeunion. - 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 byDIContainer.resolveon an unknown identifier.INVALID_GAME_CONFIG/INVALID_SESSION_METADATA/INVALID_PLAYER_INFO— thrown by the correspondingparse*boundary functions in./types.INVALID_MOVE—AbstractGameLogic.processMovevalidation 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 beforestart().ENGINE_ALREADY_STARTED— secondstart()call on a live engine.ENGINE_DISPOSED— engine method called afterdispose().INVALID_ENGINE_OPTIONS— Zod failure onGameEngineOptions.INVALID_GAME_LOOP_OPTIONS— Zod failure onGameLoopOptions.GAME_LOOP_NOT_RUNNING—pause/resume/stopcalled on an idle loop.GAME_LOOP_ALREADY_RUNNING— secondstart()call on a running loop.PLUGIN_NOT_FOUND—uninstall,enable,disable,geton an unknown plugin name.DUPLICATE_PLUGIN—installcalled with an already-registered plugin name.PLUGIN_LIFECYCLE_FAILED— wraps any error thrown from a plugin lifecycle hook.MATCHMAKING_QUEUE_FULL—QueueManager.addToQueuerejected because the per-key queue is at the configured cap.INVALID_QUEUE_REQUEST— Zod failure on aQueueRequestarriving atQueueManager/MatchmakingEngine.INVALID_MATCH_CRITERIA— Zod failure on aMatchCriteriaarriving atMatchmakingEngine.createMatch.NO_MATCH_FOUND—MatchmakingEngine.createMatchreturnednullwhen the caller requestedthrowOnNoMatch.INVALID_SKILL_SCORE—SkillMatcherreceived a non-finite rating or seeker missing a positivewaitingSince.SPECTATOR_LIMIT_REACHED—SpectatorManager.addSpectatorrejected because the session is at its spectator cap.INVALID_SPECTATOR_REQUEST— Zod failure onSpectatorPermissionsor duplicate spectator id.UNKNOWN_MATCHING_ALGORITHM—MatchmakingEngine.createMatchwas passed an algorithm string outside the registered union.POOL_EXHAUSTED—ObjectPool.acquirerejected because the pool'smaxSizeis reached and no slots are free.POOL_DISPOSED—ObjectPoolmethod called afterdispose().RELEASE_FOREIGN_OBJECT—ObjectPool.releasereceived an object that did not originate from this pool.INVALID_POOL_CONFIG—ObjectPoolconstructor received a non-positiveinitialSize/maxSize, ormaxSizesmaller thaninitialSize.INVALID_TIER_CONFIG—NetworkTierAdapterconstructor received an empty / malformed tier list or a node-capability with a non-finite score.UNKNOWN_TIER—NetworkTierAdapterqueried 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:GameServerCodemessage:stringoptions?:{ 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".