lila/packages/shared/src/schemas/lobby.ts
lila 6975384751 feat(api): add game:ready message for client state sync
- WsGameReadySchema added to shared schemas and WsClientMessageSchema
- handleGameReady sends current game:question directly to requesting
  client socket (not broadcast) — foundation for reconnection slice
- router dispatches game:ready to handleGameReady handler
2026-04-18 09:54:31 +02:00

137 lines
3.8 KiB
TypeScript

import * as z from "zod";
import { LOBBY_STATUSES } from "../constants.js";
import { GameQuestionSchema } from "./game.js";
export const LobbyPlayerSchema = z.object({
lobbyId: z.uuid(),
userId: z.string(),
score: z.number().int().min(0),
user: z.object({ id: z.string(), name: z.string() }),
});
export type LobbyPlayer = z.infer<typeof LobbyPlayerSchema>;
export const LobbySchema = z.object({
id: z.uuid(),
code: z.string().min(1).max(10),
hostUserId: z.string(),
status: z.enum(LOBBY_STATUSES),
createdAt: z.iso.datetime(),
players: z.array(LobbyPlayerSchema),
});
export type Lobby = z.infer<typeof LobbySchema>;
export const JoinLobbyResponseSchema = LobbySchema;
export type JoinLobbyResponse = z.infer<typeof JoinLobbyResponseSchema>;
// ----------------------------------------------------------------------------
// WebSocket: Client → Server
// ----------------------------------------------------------------------------
export const WsLobbyJoinSchema = z.object({
type: z.literal("lobby:join"),
code: z.string().min(1).max(10),
});
export type WsLobbyJoin = z.infer<typeof WsLobbyJoinSchema>;
export const WsLobbyLeaveSchema = z.object({
type: z.literal("lobby:leave"),
lobbyId: z.uuid(),
});
export type WsLobbyLeave = z.infer<typeof WsLobbyLeaveSchema>;
export const WsLobbyStartSchema = z.object({
type: z.literal("lobby:start"),
lobbyId: z.uuid(),
});
export type WsLobbyStart = z.infer<typeof WsLobbyStartSchema>;
export const WsGameReadySchema = z.object({
type: z.literal("game:ready"),
lobbyId: z.uuid(),
});
export type WsGameReady = z.infer<typeof WsGameReadySchema>;
export const WsGameAnswerSchema = z.object({
type: z.literal("game:answer"),
lobbyId: z.uuid(),
questionId: z.uuid(),
selectedOptionId: z.number().int().min(0).max(3),
});
export type WsGameAnswer = z.infer<typeof WsGameAnswerSchema>;
export const WsClientMessageSchema = z.discriminatedUnion("type", [
WsLobbyJoinSchema,
WsLobbyLeaveSchema,
WsLobbyStartSchema,
WsGameAnswerSchema,
WsGameReadySchema,
]);
export type WsClientMessage = z.infer<typeof WsClientMessageSchema>;
// ----------------------------------------------------------------------------
// WebSocket: Server → Client
// ----------------------------------------------------------------------------
export const WsLobbyStateSchema = z.object({
type: z.literal("lobby:state"),
lobby: LobbySchema,
});
export type WsLobbyState = z.infer<typeof WsLobbyStateSchema>;
export const WsGameQuestionSchema = z.object({
type: z.literal("game:question"),
question: GameQuestionSchema,
questionNumber: z.number().int().min(1),
totalQuestions: z.number().int().min(1),
});
export type WsGameQuestion = z.infer<typeof WsGameQuestionSchema>;
export const WsGameAnswerResultSchema = z.object({
type: z.literal("game:answer_result"),
correctOptionId: z.number().int().min(0).max(3),
results: z.array(
z.object({
userId: z.string(),
selectedOptionId: z.number().int().min(0).max(3).nullable(),
isCorrect: z.boolean(),
}),
),
players: z.array(LobbyPlayerSchema),
});
export type WsGameAnswerResult = z.infer<typeof WsGameAnswerResultSchema>;
export const WsGameFinishedSchema = z.object({
type: z.literal("game:finished"),
players: z.array(LobbyPlayerSchema),
winnerIds: z.array(z.string()),
});
export type WsGameFinished = z.infer<typeof WsGameFinishedSchema>;
export const WsErrorSchema = z.object({
type: z.literal("error"),
code: z.string(),
message: z.string(),
});
export type WsError = z.infer<typeof WsErrorSchema>;
export const WsServerMessageSchema = z.discriminatedUnion("type", [
WsLobbyStateSchema,
WsGameQuestionSchema,
WsGameAnswerResultSchema,
WsGameFinishedSchema,
WsErrorSchema,
]);
export type WsServerMessage = z.infer<typeof WsServerMessageSchema>;