feat(shared): add quiz session Zod schemas

Add the shared schemas for the quiz request/response cycle, defining
the contract between the API and the frontend.

- Reorganise packages/shared: move schemas into a schemas/ subdirectory
  grouped by domain. Delete the old flat schema.ts.
- Add AnswerOption, GameQuestion, GameSession, AnswerSubmission, and
  AnswerResult alongside the existing GameRequest.
- optionId is an integer 0-3 (positional, shuffled at session-build
  time so position carries no information).
- questionId and sessionId are UUIDs (globally unique, opaque, natural
  keys for Valkey storage later).
- gloss is  rather than optional, for a predictable
  shape on the frontend.
- options array enforced to exactly 4 elements at the schema level.
This commit is contained in:
lila 2026-04-10 21:43:53 +02:00
parent 2bcf9d7a97
commit ce6dc4fa32
3 changed files with 58 additions and 19 deletions

View file

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

View file

@ -1,18 +0,0 @@
import * as z from "zod";
import {
SUPPORTED_LANGUAGE_CODES,
SUPPORTED_POS,
GAME_ROUNDS,
DIFFICULTY_LEVELS,
} from "./constants.js";
export const GameRequestSchema = z.object({
source_language: z.enum(SUPPORTED_LANGUAGE_CODES),
target_language: z.enum(SUPPORTED_LANGUAGE_CODES),
pos: z.enum(SUPPORTED_POS),
difficulty: z.enum(DIFFICULTY_LEVELS),
rounds: z.enum(GAME_ROUNDS),
});
export type GameRequestType = z.infer<typeof GameRequestSchema>;

View file

@ -0,0 +1,57 @@
import * as z from "zod";
import {
SUPPORTED_LANGUAGE_CODES,
SUPPORTED_POS,
GAME_ROUNDS,
DIFFICULTY_LEVELS,
} from "../constants.js";
export const GameRequestSchema = z.object({
source_language: z.enum(SUPPORTED_LANGUAGE_CODES),
target_language: z.enum(SUPPORTED_LANGUAGE_CODES),
pos: z.enum(SUPPORTED_POS),
difficulty: z.enum(DIFFICULTY_LEVELS),
rounds: z.enum(GAME_ROUNDS),
});
export type GameRequest = z.infer<typeof GameRequestSchema>;
export const AnswerOptionSchema = z.object({
optionId: z.number().int().min(0).max(3),
text: z.string().min(1),
});
export type AnswerOption = z.infer<typeof AnswerOptionSchema>;
export const GameQuestionSchema = z.object({
questionId: z.uuid(),
prompt: z.string().min(1),
gloss: z.string().min(1).nullable(),
options: z.array(AnswerOptionSchema).length(4),
});
export type GameQuestion = z.infer<typeof GameQuestionSchema>;
export const GameSessionSchema = z.object({
sessionId: z.uuid(),
questions: z.array(GameQuestionSchema),
});
export type GameSession = z.infer<typeof GameSessionSchema>;
export const AnswerSubmissionSchema = z.object({
sessionId: z.uuid(),
questionId: z.uuid(),
selectedOptionId: z.number().int().min(0).max(3),
});
export type AnswerSubmission = z.infer<typeof AnswerSubmissionSchema>;
export const AnswerResultSchema = z.object({
questionId: z.uuid(),
isCorrect: z.boolean(),
correctOptionId: z.number().int().min(0).max(3),
});
export type AnswerResult = z.infer<typeof AnswerResultSchema>;