import { z } from 'zod'; /** * Zod schemas for Match data models * Provides runtime validation and type safety */ /** MatchPlayer schema */ export const matchPlayerSchema = z.object({ id: z.string().min(1), // Steam ID uint64 as string to preserve precision name: z.string().min(1), avatar: z.string().url(), team_id: z.number().int().min(2).max(3), // 2 = T, 3 = CT // Performance metrics kills: z.number().int().nonnegative(), deaths: z.number().int().nonnegative(), assists: z.number().int().nonnegative(), headshot: z.number().int().nonnegative(), mvp: z.number().int().nonnegative(), score: z.number().int().nonnegative(), kast: z.number().int().min(0).max(100).optional(), // Rank (CS2 Premier rating: 0-30000) rank_old: z.number().int().min(0).max(30000).optional(), rank_new: z.number().int().min(0).max(30000).optional(), // Damage dmg_enemy: z.number().int().nonnegative().optional(), dmg_team: z.number().int().nonnegative().optional(), // Multi-kills mk_2: z.number().int().nonnegative().optional(), mk_3: z.number().int().nonnegative().optional(), mk_4: z.number().int().nonnegative().optional(), mk_5: z.number().int().nonnegative().optional(), // Utility damage ud_he: z.number().int().nonnegative().optional(), ud_flames: z.number().int().nonnegative().optional(), ud_flash: z.number().int().nonnegative().optional(), ud_smoke: z.number().int().nonnegative().optional(), ud_decoy: z.number().int().nonnegative().optional(), // Flash statistics flash_assists: z.number().int().nonnegative().optional(), flash_duration_enemy: z.number().nonnegative().optional(), flash_duration_team: z.number().nonnegative().optional(), flash_duration_self: z.number().nonnegative().optional(), flash_total_enemy: z.number().int().nonnegative().optional(), flash_total_team: z.number().int().nonnegative().optional(), flash_total_self: z.number().int().nonnegative().optional(), // Other crosshair: z.string().optional(), color: z.enum(['green', 'yellow', 'purple', 'blue', 'orange', 'grey']).optional(), avg_ping: z.number().nonnegative().optional() }); /** Match schema */ export const matchSchema = z.object({ match_id: z.string().min(1), // uint64 as string to preserve precision share_code: z .string() .regex(/^(CSGO-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5})?$/) .optional(), map: z.string().min(1), date: z.string().datetime(), score_team_a: z.number().int().nonnegative(), score_team_b: z.number().int().nonnegative(), duration: z.number().int().positive(), match_result: z.number().int().min(0).max(2), // 0 = tie, 1 = team_a win, 2 = team_b win max_rounds: z.number().int().positive(), demo_parsed: z.boolean(), vac_present: z.boolean(), gameban_present: z.boolean(), tick_rate: z.number().positive().optional(), players: z.array(matchPlayerSchema).optional() }); /** MatchListItem schema */ export const matchListItemSchema = z.object({ match_id: z.string().min(1), // uint64 as string to preserve precision map: z.string().min(1), date: z.string().datetime(), score_team_a: z.number().int().nonnegative(), score_team_b: z.number().int().nonnegative(), duration: z.number().int().positive(), demo_parsed: z.boolean(), player_count: z.number().int().min(2).max(10).optional() }); /** Parser functions for safe data validation */ export const parseMatch = (data: unknown) => matchSchema.parse(data); export const parseMatchSafe = (data: unknown) => matchSchema.safeParse(data); export const parseMatchPlayer = (data: unknown) => matchPlayerSchema.parse(data); export const parseMatchListItem = (data: unknown) => matchListItemSchema.parse(data); /** Infer TypeScript types from schemas */ export type MatchSchema = z.infer; export type MatchPlayerSchema = z.infer; export type MatchListItemSchema = z.infer;