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:
2025-11-04 20:31:20 +01:00
parent 66aea51c39
commit d811efc394
26 changed files with 2444 additions and 6 deletions

View File

@@ -0,0 +1,79 @@
import { z } from 'zod';
import { matchListItemSchema } from './match.schema';
/**
* Zod schemas for API responses and error handling
*/
/** APIError schema */
export const apiErrorSchema = z.object({
error: z.string(),
message: z.string(),
status_code: z.number().int(),
timestamp: z.string().datetime().optional()
});
/** Generic APIResponse schema */
export const apiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
z.object({
data: dataSchema,
success: z.boolean(),
error: apiErrorSchema.optional()
});
/** MatchParseResponse schema */
export const matchParseResponseSchema = z.object({
match_id: z.number().positive(),
status: z.enum(['parsing', 'queued', 'completed', 'error']),
message: z.string(),
estimated_time: z.number().int().positive().optional()
});
/** MatchParseStatus schema */
export const matchParseStatusSchema = z.object({
match_id: z.number().positive(),
status: z.enum(['pending', 'parsing', 'completed', 'error']),
progress: z.number().int().min(0).max(100).optional(),
error_message: z.string().optional()
});
/** MatchesListResponse schema */
export const matchesListResponseSchema = z.object({
matches: z.array(matchListItemSchema),
next_page_time: z.number().int().optional(),
has_more: z.boolean(),
total_count: z.number().int().nonnegative().optional()
});
/** MatchesQueryParams schema */
export const matchesQueryParamsSchema = z.object({
limit: z.number().int().min(1).max(100).optional(),
map: z.string().optional(),
player_id: z.number().positive().optional(),
before_time: z.number().int().positive().optional()
});
/** TrackPlayerResponse schema */
export const trackPlayerResponseSchema = z.object({
success: z.boolean(),
message: z.string()
});
/** Parser functions */
export const parseAPIError = (data: unknown) => apiErrorSchema.parse(data);
export const parseMatchParseResponse = (data: unknown) => matchParseResponseSchema.parse(data);
export const parseMatchesList = (data: unknown) => matchesListResponseSchema.parse(data);
export const parseMatchesQueryParams = (data: unknown) => matchesQueryParamsSchema.parse(data);
export const parseTrackPlayerResponse = (data: unknown) => trackPlayerResponseSchema.parse(data);
/** Safe parser functions */
export const parseMatchesListSafe = (data: unknown) => matchesListResponseSchema.safeParse(data);
export const parseAPIErrorSafe = (data: unknown) => apiErrorSchema.safeParse(data);
/** Infer TypeScript types */
export type APIErrorSchema = z.infer<typeof apiErrorSchema>;
export type MatchParseResponseSchema = z.infer<typeof matchParseResponseSchema>;
export type MatchParseStatusSchema = z.infer<typeof matchParseStatusSchema>;
export type MatchesListResponseSchema = z.infer<typeof matchesListResponseSchema>;
export type MatchesQueryParamsSchema = z.infer<typeof matchesQueryParamsSchema>;
export type TrackPlayerResponseSchema = z.infer<typeof trackPlayerResponseSchema>;