feat: complete Phase 3 - Domain Modeling & Data Layer
Implements comprehensive type system, runtime validation, API client, and testing infrastructure for CS2.WTF. TypeScript Interfaces (src/lib/types/): - Match.ts: Match, MatchPlayer, MatchListItem types - Player.ts: Player, PlayerMeta, PlayerProfile types - RoundStats.ts: Round economy and performance data - Weapon.ts: Weapon statistics with hit groups (HitGroup, WeaponType enums) - Message.ts: Chat messages with filtering support - api.ts: API responses, errors, and APIException class - Complete type safety with strict null checks Zod Schemas (src/lib/schemas/): - Runtime validation for all data models - Schema parsers with safe/unsafe variants - Special handling for backend typo (looses → losses) - Share code validation regex - CS2-specific validations (rank 0-30000, MR12 rounds) API Client (src/lib/api/): - client.ts: Axios-based HTTP client with error handling - Request cancellation support (AbortController) - Automatic retry logic for transient failures - Timeout handling (10s default) - Typed APIException errors - players.ts: Player endpoints (profile, meta, track/untrack, search) - matches.ts: Match endpoints (parse, details, weapons, rounds, chat, search) - Zod validation on all API responses MSW Mock Handlers (src/mocks/): - fixtures.ts: Comprehensive mock data for testing - handlers/players.ts: Mock player API endpoints - handlers/matches.ts: Mock match API endpoints - browser.ts: Browser MSW worker for development - server.ts: Node MSW server for Vitest tests - Realistic responses with delays and pagination - Safe integer IDs to avoid precision loss Configuration: - .env.example: Complete environment variable documentation - src/vite-env.d.ts: Vite environment type definitions - All strict TypeScript checks passing (0 errors, 0 warnings) Features: - Cancellable requests for search (prevent race conditions) - Data normalization (backend typo handling) - Comprehensive error types (NetworkError, Timeout, etc.) - Share code parsing and validation - Pagination support for players and matches - Mock data for offline development and testing Build Status: ✓ Production build successful Type Check: ✓ 0 errors, 0 warnings Lint: ✓ All checks passed Phase 3 Completion: 100% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
101
src/lib/schemas/match.schema.ts
Normal file
101
src/lib/schemas/match.schema.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
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.number().positive(),
|
||||
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),
|
||||
|
||||
// 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.number().positive(),
|
||||
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}$/),
|
||||
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(),
|
||||
players: z.array(matchPlayerSchema).optional()
|
||||
});
|
||||
|
||||
/** MatchListItem schema */
|
||||
export const matchListItemSchema = z.object({
|
||||
match_id: z.number().positive(),
|
||||
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)
|
||||
});
|
||||
|
||||
/** 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<typeof matchSchema>;
|
||||
export type MatchPlayerSchema = z.infer<typeof matchPlayerSchema>;
|
||||
export type MatchListItemSchema = z.infer<typeof matchListItemSchema>;
|
||||
Reference in New Issue
Block a user