first commit
This commit is contained in:
140
docs/gaming/games/game-server/src/errors/game-server-error.md
Normal file
140
docs/gaming/games/game-server/src/errors/game-server-error.md
Normal 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"`.
|
||||
Reference in New Issue
Block a user