first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
# GameServerError
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```typescript
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
```typescript
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"`.