mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 06:31:14 -05:00
Refactor: Implement type safety plan
This commit is contained in:
149
src/lib/typeConversions.ts
Normal file
149
src/lib/typeConversions.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Type Conversion Utilities
|
||||
*
|
||||
* This module provides type-safe conversion functions for handling:
|
||||
* - Database nulls → Application undefined
|
||||
* - Json types → Form data types
|
||||
* - Type guards for runtime validation
|
||||
*
|
||||
* These utilities eliminate the need for `as any` casts by providing
|
||||
* explicit, type-safe conversions at data boundaries.
|
||||
*/
|
||||
|
||||
import type { Json } from '@/integrations/supabase/types';
|
||||
|
||||
/**
|
||||
* Convert database null to application undefined
|
||||
* Database returns null, but forms expect undefined for optional fields
|
||||
*/
|
||||
export function nullToUndefined<T>(value: T | null): T | undefined {
|
||||
return value === null ? undefined : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all null values in an object to undefined
|
||||
* Use at database boundaries when fetching data for forms
|
||||
*/
|
||||
export function convertNullsToUndefined<T extends Record<string, unknown>>(
|
||||
obj: T
|
||||
): { [K in keyof T]: T[K] extends (infer U | null) ? (U | undefined) : T[K] } {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key in obj) {
|
||||
const value = obj[key];
|
||||
result[key] = value === null ? undefined : value;
|
||||
}
|
||||
return result as { [K in keyof T]: T[K] extends (infer U | null) ? (U | undefined) : T[K] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Photo item interface for type-safe photo handling
|
||||
*/
|
||||
export interface PhotoItem {
|
||||
url: string;
|
||||
caption?: string;
|
||||
title?: string;
|
||||
cloudflare_id?: string;
|
||||
order_index?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard: Check if value is a valid photo array
|
||||
*/
|
||||
export function isPhotoArray(data: unknown): data is PhotoItem[] {
|
||||
return (
|
||||
Array.isArray(data) &&
|
||||
data.every(
|
||||
(item) =>
|
||||
typeof item === 'object' &&
|
||||
item !== null &&
|
||||
'url' in item &&
|
||||
typeof (item as Record<string, unknown>).url === 'string'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely extract photo array from Json data
|
||||
* Handles both direct arrays and nested { photos: [...] } structures
|
||||
*/
|
||||
export function extractPhotoArray(data: Json): PhotoItem[] {
|
||||
// Direct array
|
||||
if (isPhotoArray(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Nested photos property
|
||||
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
||||
const obj = data as Record<string, Json>;
|
||||
if ('photos' in obj && isPhotoArray(obj.photos)) {
|
||||
return obj.photos;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard: Check if Json is a plain object (not array or null)
|
||||
*/
|
||||
export function isJsonObject(data: Json): data is Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Json to a form-compatible object
|
||||
* Converts all null values to undefined for form compatibility
|
||||
*/
|
||||
export function jsonToFormData(data: Json | unknown): Record<string, unknown> {
|
||||
if (!isJsonObject(data as Json)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(data as Record<string, Json>)) {
|
||||
result[key] = value === null ? undefined : value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard: Check if object has a specific property with a given type
|
||||
*/
|
||||
export function hasProperty<T>(
|
||||
obj: unknown,
|
||||
key: string,
|
||||
typeCheck: (value: unknown) => value is T
|
||||
): obj is Record<string, unknown> & { [K in typeof key]: T } {
|
||||
return (
|
||||
typeof obj === 'object' &&
|
||||
obj !== null &&
|
||||
key in obj &&
|
||||
typeCheck((obj as Record<string, unknown>)[key])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get a property from Json with type assertion
|
||||
*/
|
||||
export function getProperty<T = unknown>(
|
||||
data: Json,
|
||||
key: string,
|
||||
defaultValue?: T
|
||||
): T | undefined {
|
||||
if (isJsonObject(data) && key in data) {
|
||||
const value = data[key];
|
||||
return value === null ? defaultValue : (value as T);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array of database records with nulls to form-compatible records
|
||||
* Useful when fetching lists of entities for display/editing
|
||||
*/
|
||||
export function convertArrayNullsToUndefined<T extends Record<string, unknown>>(
|
||||
arr: T[]
|
||||
): Array<{ [K in keyof T]: T[K] extends (infer U | null) ? (U | undefined) : T[K] }> {
|
||||
return arr.map((item) => convertNullsToUndefined(item));
|
||||
}
|
||||
Reference in New Issue
Block a user