Refactor: Implement type safety plan

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 03:40:10 +00:00
parent 288e87bcd3
commit f034ece3a2
6 changed files with 176 additions and 10 deletions

149
src/lib/typeConversions.ts Normal file
View 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));
}