/** * Runtime Type Validation * * Validates data from Supabase queries at runtime to ensure type safety. * Uses Zod schemas to parse and validate database responses. */ import { z } from 'zod'; import type { Park, Ride, Company, RideModel } from '@/types/database'; // ============================================ // RUNTIME SCHEMAS (Mirror Database Types) // ============================================ export const parkRuntimeSchema = z.object({ id: z.string().uuid(), name: z.string(), slug: z.string(), description: z.string().nullable().optional(), park_type: z.string(), status: z.enum(['operating', 'closed_permanently', 'closed_temporarily', 'under_construction', 'planned', 'abandoned']), opening_date: z.string().nullable().optional(), opening_date_precision: z.string().nullable().optional(), closing_date: z.string().nullable().optional(), closing_date_precision: z.string().nullable().optional(), location_id: z.string().uuid().nullable().optional(), operator_id: z.string().uuid().nullable().optional(), property_owner_id: z.string().uuid().nullable().optional(), website_url: z.string().nullable().optional(), phone: z.string().nullable().optional(), email: z.string().nullable().optional(), banner_image_url: z.string().nullable().optional(), banner_image_id: z.string().nullable().optional(), card_image_url: z.string().nullable().optional(), card_image_id: z.string().nullable().optional(), ride_count: z.number().optional(), coaster_count: z.number().optional(), average_rating: z.number().nullable().optional(), review_count: z.number().optional(), view_count_7d: z.number().optional(), view_count_30d: z.number().optional(), view_count_all: z.number().optional(), created_at: z.string().optional(), updated_at: z.string().optional(), }).passthrough(); // Allow additional fields from joins export const rideRuntimeSchema = z.object({ id: z.string().uuid(), name: z.string(), slug: z.string(), description: z.string().nullable().optional(), category: z.string(), ride_sub_type: z.string().nullable().optional(), status: z.enum(['operating', 'closed_permanently', 'closed_temporarily', 'under_construction', 'relocated', 'stored', 'demolished']), park_id: z.string().uuid().nullable().optional(), manufacturer_id: z.string().uuid().nullable().optional(), designer_id: z.string().uuid().nullable().optional(), ride_model_id: z.string().uuid().nullable().optional(), opening_date: z.string().nullable().optional(), opening_date_precision: z.string().nullable().optional(), closing_date: z.string().nullable().optional(), closing_date_precision: z.string().nullable().optional(), height_requirement_cm: z.number().nullable().optional(), age_requirement: z.number().nullable().optional(), max_speed_kmh: z.number().nullable().optional(), duration_seconds: z.number().nullable().optional(), capacity_per_hour: z.number().nullable().optional(), gforce_max: z.number().nullable().optional(), inversions_count: z.number().nullable().optional(), length_meters: z.number().nullable().optional(), height_meters: z.number().nullable().optional(), drop_meters: z.number().nullable().optional(), angle_degrees: z.number().nullable().optional(), coaster_type: z.string().nullable().optional(), seating_type: z.string().nullable().optional(), intensity_level: z.string().nullable().optional(), former_names: z.array(z.unknown()).nullable().optional(), banner_image_url: z.string().nullable().optional(), banner_image_id: z.string().nullable().optional(), card_image_url: z.string().nullable().optional(), card_image_id: z.string().nullable().optional(), average_rating: z.number().nullable().optional(), review_count: z.number().optional(), view_count_7d: z.number().optional(), view_count_30d: z.number().optional(), view_count_all: z.number().optional(), created_at: z.string().optional(), updated_at: z.string().optional(), }).passthrough(); export const companyRuntimeSchema = z.object({ id: z.string().uuid(), name: z.string(), slug: z.string(), description: z.string().nullable().optional(), company_type: z.string(), person_type: z.string().nullable().optional(), founded_year: z.number().nullable().optional(), founded_date: z.string().nullable().optional(), founded_date_precision: z.string().nullable().optional(), headquarters_location: z.string().nullable().optional(), website_url: z.string().nullable().optional(), logo_url: z.string().nullable().optional(), banner_image_url: z.string().nullable().optional(), banner_image_id: z.string().nullable().optional(), card_image_url: z.string().nullable().optional(), card_image_id: z.string().nullable().optional(), average_rating: z.number().nullable().optional(), review_count: z.number().optional(), view_count_7d: z.number().optional(), view_count_30d: z.number().optional(), view_count_all: z.number().optional(), created_at: z.string().optional(), updated_at: z.string().optional(), }).passthrough(); export const rideModelRuntimeSchema = z.object({ id: z.string().uuid(), name: z.string(), slug: z.string(), manufacturer_id: z.string().uuid().nullable().optional(), category: z.string(), description: z.string().nullable().optional(), // Note: technical_specs deprecated - use ride_model_technical_specifications table created_at: z.string().optional(), updated_at: z.string().optional(), }).passthrough(); // ============================================ // VALIDATION HELPERS // ============================================ /** * Validate a single park record */ export function validatePark(data: unknown): Park { return parkRuntimeSchema.parse(data) as Park; } /** * Validate an array of park records */ export function validateParks(data: unknown): Park[] { return z.array(parkRuntimeSchema).parse(data) as Park[]; } /** * Safely validate parks (returns null on error) */ export function safeValidateParks(data: unknown): Park[] | null { const result = z.array(parkRuntimeSchema).safeParse(data); return result.success ? (result.data as Park[]) : null; } /** * Validate a single ride record */ export function validateRide(data: unknown): Ride { return rideRuntimeSchema.parse(data) as Ride; } /** * Validate an array of ride records */ export function validateRides(data: unknown): Ride[] { return z.array(rideRuntimeSchema).parse(data) as Ride[]; } /** * Safely validate rides (returns null on error) */ export function safeValidateRides(data: unknown): Ride[] | null { const result = z.array(rideRuntimeSchema).safeParse(data); return result.success ? (result.data as Ride[]) : null; } /** * Validate a single company record */ export function validateCompany(data: unknown): Company { return companyRuntimeSchema.parse(data) as Company; } /** * Validate an array of company records */ export function validateCompanies(data: unknown): Company[] { return z.array(companyRuntimeSchema).parse(data) as Company[]; } /** * Safely validate companies (returns null on error) */ export function safeValidateCompanies(data: unknown): Company[] | null { const result = z.array(companyRuntimeSchema).safeParse(data); return result.success ? (result.data as Company[]) : null; } /** * Validate a single ride model record */ export function validateRideModel(data: unknown): RideModel { return rideModelRuntimeSchema.parse(data) as RideModel; } /** * Validate an array of ride model records */ export function validateRideModels(data: unknown): RideModel[] { return z.array(rideModelRuntimeSchema).parse(data) as RideModel[]; } /** * Safely validate ride models (returns null on error) */ export function safeValidateRideModels(data: unknown): RideModel[] | null { const result = z.array(rideModelRuntimeSchema).safeParse(data); return result.success ? (result.data as RideModel[]) : null; } /** * Generic validator for any entity type */ export function validateEntity( data: unknown, schema: z.ZodSchema ): T { return schema.parse(data); } /** * Safe generic validator (returns null on error) */ export function safeValidateEntity( data: unknown, schema: z.ZodSchema ): T | null { const result = schema.safeParse(data); return result.success ? result.data : null; }