feat(db): add lobbies and lobby_players tables + model

- Add lobbies and lobby_players tables with camelCase TS aliases
- Add LOBBY_STATUSES constant in shared
- Add lobbyModel with atomic addPlayer and transactional finishGame
- Enable Drizzle relational query API via { schema } option
This commit is contained in:
lila 2026-04-16 19:08:53 +02:00
parent 47a68c0315
commit cf56399a5e
2 changed files with 101 additions and 0 deletions

View file

@ -1,2 +1,3 @@
export * from "./constants.js";
export * from "./schemas/game.js";
export * from "./schemas/lobby.js";

View file

@ -1,6 +1,7 @@
import * as z from "zod";
import { LOBBY_STATUSES } from "../constants.js";
import { GameQuestionSchema } from "./game.js";
export const LobbyPlayerSchema = z.object({
lobbyId: z.uuid(),
@ -19,4 +20,103 @@ export const LobbySchema = z.object({
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 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,
]);
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 WsServerMessageSchema = z.discriminatedUnion("type", [
WsLobbyStateSchema,
WsGameQuestionSchema,
WsGameAnswerResultSchema,
WsGameFinishedSchema,
]);
export type WsServerMessage = z.infer<typeof WsServerMessageSchema>;