/** * Modern versioning utilities for relational version tables * * These functions work with the new trigger-based versioning system. * All version creation is automatic via database triggers - no manual calls needed. * * @see docs/versioning/ARCHITECTURE.md for system design * @see docs/versioning/API.md for complete API reference */ import { supabase } from '@/lib/supabaseClient'; import type { EntityType } from '@/types/versioning'; import { createTableQuery } from './supabaseHelpers'; import { handleNonCriticalError } from './errorHandler'; /** * Manually trigger cleanup of old versions for a specific entity type * * Note: This should normally run automatically via pg_cron, but can be called manually. * * @param entityType - The entity type to clean up ('park', 'ride', 'company', 'ride_model') * @param keepCount - Number of most recent versions to keep per entity (default: 50) * @returns Number of versions deleted * * @example * ```typescript * const deleted = await cleanupVersions('park', 50); * console.log(`Deleted ${deleted} old park versions`); * ``` */ export async function cleanupVersions( entityType: EntityType, keepCount: number = 50 ): Promise { const { data, error } = await supabase.rpc('cleanup_old_versions', { entity_type: entityType, keep_versions: keepCount }); if (error) { handleNonCriticalError(error, { action: 'Version cleanup', metadata: { entityType, keepCount } }); return 0; } return data as number; } /** * Get statistics about versions for a specific entity * * @param entityType - The entity type ('park', 'ride', 'company', 'ride_model') * @param entityId - The UUID of the entity * @returns Version statistics including total count, date range, and change type distribution * * @example * ```typescript * const stats = await getVersionStats('park', 'uuid-here'); * console.log(`Total versions: ${stats.totalVersions}`); * console.log(`Change types:`, stats.changeTypes); * ``` */ export async function getVersionStats( entityType: EntityType, entityId: string ) { const entityIdCol = `${entityType}_id`; // Directly query the version table based on entity type // Use simpler type inference to avoid TypeScript deep instantiation issues let result; if (entityType === 'park') { result = await supabase .from('park_versions') .select('version_number, created_at, change_type', { count: 'exact' }) .eq('park_id', entityId) .order('version_number', { ascending: true }); } else if (entityType === 'ride') { result = await supabase .from('ride_versions') .select('version_number, created_at, change_type', { count: 'exact' }) .eq('ride_id', entityId) .order('version_number', { ascending: true }); } else if (entityType === 'company') { result = await supabase .from('company_versions') .select('version_number, created_at, change_type', { count: 'exact' }) .eq('company_id', entityId) .order('version_number', { ascending: true }); } else { result = await supabase .from('ride_model_versions') .select('version_number, created_at, change_type', { count: 'exact' }) .eq('ride_model_id', entityId) .order('version_number', { ascending: true }); } const { data, error } = result; if (error || !data) { handleNonCriticalError(error || new Error('No data returned'), { action: 'Fetch version stats', metadata: { entityType, entityId } }); return null; } if (data.length === 0) { return { totalVersions: 0, oldestVersion: null, newestVersion: null, changeTypes: {} }; } // Type-safe access to version data const versions = data as unknown as Array<{ version_number: number; created_at: string; change_type: string; }>; return { totalVersions: versions.length, oldestVersion: versions[0]?.created_at || null, newestVersion: versions[versions.length - 1]?.created_at || null, changeTypes: versions.reduce((acc, v) => { acc[v.change_type] = (acc[v.change_type] || 0) + 1; return acc; }, {} as Record) }; } /** * Get total version counts across all entity types * * Useful for monitoring storage usage and cleanup effectiveness. * * @returns Total version counts for each entity type * * @example * ```typescript * const counts = await getAllVersionCounts(); * console.log('Park versions:', counts.park); * console.log('Ride versions:', counts.ride); * ``` */ export async function getAllVersionCounts() { const counts = { park: 0, ride: 0, company: 0, ride_model: 0, }; const parkCount = await supabase .from('park_versions') .select('*', { count: 'exact', head: true }); counts.park = parkCount.count || 0; const rideCount = await supabase .from('ride_versions') .select('*', { count: 'exact', head: true }); counts.ride = rideCount.count || 0; const companyCount = await supabase .from('company_versions') .select('*', { count: 'exact', head: true }); counts.company = companyCount.count || 0; const modelCount = await supabase .from('ride_model_versions') .select('*', { count: 'exact', head: true }); counts.ride_model = modelCount.count || 0; return counts; } /** * Check if an entity has any versions * * @param entityType - The entity type * @param entityId - The UUID of the entity * @returns True if entity has at least one version */ export async function hasVersions( entityType: EntityType, entityId: string ): Promise { // Directly query the version table based on entity type with explicit column names let result; if (entityType === 'park') { result = await supabase .from('park_versions') .select('*', { count: 'exact', head: true }) .eq('park_id', entityId); } else if (entityType === 'ride') { result = await supabase .from('ride_versions') .select('*', { count: 'exact', head: true }) .eq('ride_id', entityId); } else if (entityType === 'company') { result = await supabase .from('company_versions') .select('*', { count: 'exact', head: true }) .eq('company_id', entityId); } else { result = await supabase .from('ride_model_versions') .select('*', { count: 'exact', head: true }) .eq('ride_model_id', entityId); } return (result.count || 0) > 0; }