Worker-safe. The authoritative side of a hosted openturn match. Used byDocumentation Index
Fetch the complete documentation index at: https://openturn.io/docs/llms.txt
Use this file to discover all available pages before exploring further.
openturn dev (via @openturn/cli) and by the generated Cloudflare Worker bundle.
Install
Deployment definition
GameDeployment<TGame>
The minimum information needed to host a game.
defineGameDeployment(deployment)
Type-safe factory for a deployment. Errors at the type level if TGame’s state or views are not protocol-compatible (not JSON).
For cloud authoring, you do not need to call
defineGameDeployment yourself. openturn build generates the deployment — and the Cloudflare Worker that hosts it — directly from your app/game.ts and app/openturn.ts files. Reach for defineGameDeployment only when you need a hand-written Worker entry (custom bindings, advanced routing), when you are embedding @openturn/server into your own Bun/Node host, or when you are writing a test that exercises createRoomRuntime directly.loadGameDeployment(moduleValue)
Extract a deployment from a module value (handles default, deployment, and bare exports). Used by the CLI to load user-authored deployment files.
Room runtime
createRoomRuntime(options)
Build a RoomRuntime<TGame> for one room. The runtime accepts ProtocolClientMessages and produces RoomServerMessages.
RoomRuntime<TGame>
RoomRuntimeEnvelope<TGame>
{ playerID, message }: a message addressed to a specific seat.
RoomRuntimeState<TGame>
RoomRuntimeOptions<TGame>
The input to createRoomRuntime.
InitialSavedSnapshot<TGame>
{ branch?, initialNow, match, revision, seed, snapshot }. Feeds a hot-started room (decoded from a save envelope) directly into createRoomRuntime.
Profiles
Server-side settlement of per-player persistent state. See concepts: persistent profiles.onSettle: RoomSettleHandler<TGame>
Invoked exactly once when the match reaches a terminal result. Receives the pure delta computed by the game’s declared profile.commit, already restricted to seated players.
(roomID, userID).
Games that don’t declare profile call the handler with delta: {} — safe to skip.
onActionProfileCommit: RoomActionProfileCommitHandler<TGame>
Invoked once per in-match transition that emitted a profile delta (mid-match unlocks, incremental rewards). The host is responsible for dedupe on (roomID, actionID) — failures do not block gameplay.
onSaveRequest: RoomSaveHandler<TGame>
Invoked when a player requests a save envelope. Return { saveID, downloadURL? } to tell the client where the artifact lives.
RoomActionProfileCommitInput<TGame>
Input to onActionProfileCommit.
How openturn-cloud wires this
The generated game-worker’sGameRoom Durable Object reads a cloudAPIBase URL that’s threaded in via the x-openturn-cloud-base header on the cloud’s dispatch proxy. When it sees onSettle, it POSTs the delta to ${cloudAPIBase}/api/profiles/commit, signing the body with HMAC-SHA256 using ROOM_TOKEN_SECRET (the same secret already used for room tokens). No new env vars are required.
Hydration is the mirror call: at first bootstrap, the DO POSTs to /api/profiles/hydrate with the seated user IDs and merges the returned profile data into MatchInput.profiles before creating the session.
Persistence
RoomPersistence
@openturn/cli ships a SQLite adapter; the generated Cloudflare Worker uses Durable Object storage.
RoomPersistenceRecord<TGame>
The serialized snapshot of a room.
parseRoomPersistenceRecord(value)
Deserialize and validate a persistence record.
Save envelopes
Opaque, signed blobs that carry the log needed to resume a match elsewhere. Used byonSaveRequest on the server side and by the openturn-cloud replay UI on the client side.
encodeSave(payload, secret, options?) / decodeSave(bytes, secret)
Round-trip between SavedGamePayload and a self-describing encrypted binary envelope. decodeSave throws SaveDecodeError on malformed input; inspect its .code (SaveDecodeErrorCode = "magic" | "version" | "auth" | "key" | "payload") to disambiguate.
EncodeSaveOptions
SaveDecodeError
Error class thrown by decodeSave. Has a code: SaveDecodeErrorCode field for programmatic handling.
deriveSaveKey(saveID, secret)
Derive a stable, unguessable download key for a saveID — use when signing presigned URLs.
SAVE_FORMAT_VERSION
Constant: the version byte embedded in every save envelope. Bumped when the envelope schema breaks.
SavedGamePayload, SavedGameCheckpoint, SavedGameMeta
Types describing the decoded envelope shape.
Room tokens
signRoomToken(claims, secret)
Sign a room token. Returns SignedRoomToken = { claims, token }.
verifyRoomToken(token, secret)
Verify and decode. Returns RoomTokenClaims | null.
RoomTokenClaims
scope: "lobby" is issued during lobby, scope: "game" after seat assignment. The RoomTokenScope type alias is exported for reuse.
SignedRoomToken
signValue(value, secret)
HMAC-SHA256 signer used internally for room tokens; re-exported for server-to-server signing (e.g. DO → openturn-cloud profile commits share this primitive with ROOM_TOKEN_SECRET). Returns a base64url digest.
Lobby
LobbyRuntime
Pure state machine for the pre-game lobby. The Durable Object host and the local dev server both wrap an instance with their own WebSocket/presence plumbing; this class has no knowledge of sockets or persistence.
env: LobbyEnv—{ hostUserID, capacity, minPlayers, playerIDs, knownBots? }.knownBotsis the bot catalog (built viabuildKnownBots) and turns onlobby:assign_botvalidation.persisted?: LobbyPersistedState— rehydrate from storage; omit for a fresh lobby.
apply(userID, userName, message), takeSeat, leaveSeat, setReady, assignBot(hostUserID, seatIndex, botID), clearSeat(hostUserID, seatIndex), start(hostUserID), close(hostUserID), dropUser(userID), pruneToConnected(connectedUserIDs), buildStateMessage(roomID, connectedUserIDs), toPersisted(), playerIDFor(userID), seatIndexFor(userID). See how-to: build a lobby and reference: @openturn/lobby.
Lobby types
SeatRecord is now a discriminated union over kind: "human" | "bot". LobbyStartAssignment carries kind plus botID for bot seats. See reference: @openturn/lobby for the full discriminated shape.
Bot driver in the Durable Object
@openturn/server ships an in-DO BotDriver<TGame> that dispatches bot moves directly through RoomRuntime.handleClientMessage after every state change. This avoids opening a WebSocket-to-self from the DO and is the variant the cloud uses; sidecar processes that connect over the network use createHostedBotSupervisor from @openturn/lobby/supervisor instead.
tick() chains bot-vs-bot moves up to maxChainDepth (default 64) before yielding. isBot(playerID) exposes the seat assignment for the room’s broadcast logic. Cold-start safe: rebuild from lobbyRuntime.toPersisted().seats after DO wake-up.
BotDriver accepts a BotRegistryShape rather than the full BotRegistry from @openturn/lobby/registry to dodge the server → lobby → server package cycle — the structural shape on game.bots matches.
Cloudflare Worker factory
createGameWorker(deployment, options?) (from @openturn/server/worker)
Produce a Worker-ready { default: ExportedHandler, GameRoom: DurableObject } from a game deployment.
openturn build generates a Worker entry that calls this for you.
GameWorkerEnv
The required Worker bindings: GAME_ROOM (DurableObjectNamespace) and ROOM_TOKEN_SECRET (string).
GameWorkerOptions
{ idleReapMs?, lobbyTokenTtlSeconds?, gameTokenTtlSeconds? }.
GameWorkerInfoResponse
The shape served at /__info: { deploymentVersion, gameKey, players, schemaVersion, minPlayers, capacity }.
GameWorkerExports
The return type of createGameWorker: