veza/apps/web/src/utils/idNormalization.ts

185 lines
4.4 KiB
TypeScript

/**
* ID Normalization Utilities
* FE-TYPE-001: Ensure all IDs are string (UUID) not number
*
* Provides utilities to normalize IDs to strings (UUIDs) consistently
* across the application, preventing type mismatches.
*/
/**
* Normalize an ID to a string
* Handles both string and number IDs, converting them to strings
*
* @param id - ID value (string, number, or undefined/null)
* @returns Normalized string ID or undefined if input is null/undefined
*
* @example
* ```typescript
* normalizeId(123) // "123"
* normalizeId("abc-123") // "abc-123"
* normalizeId(null) // undefined
* ```
*/
export function normalizeId(
id: string | number | null | undefined,
): string | undefined {
if (id === null || id === undefined) {
return undefined;
}
if (typeof id === 'string') {
return id;
}
if (typeof id === 'number') {
return String(id);
}
// Fallback for any other type
return String(id);
}
/**
* Normalize an ID to a string (required)
* Throws an error if ID is null or undefined
*
* @param id - ID value (string, number, or undefined/null)
* @returns Normalized string ID
* @throws Error if id is null or undefined
*
* @example
* ```typescript
* normalizeIdRequired(123) // "123"
* normalizeIdRequired("abc-123") // "abc-123"
* normalizeIdRequired(null) // throws Error
* ```
*/
export function normalizeIdRequired(
id: string | number | null | undefined,
): string {
const normalized = normalizeId(id);
if (normalized === undefined) {
throw new Error('ID is required but was null or undefined');
}
return normalized;
}
/**
* Normalize an array of IDs to strings
*
* @param ids - Array of ID values
* @returns Array of normalized string IDs
*
* @example
* ```typescript
* normalizeIds([1, 2, "abc"]) // ["1", "2", "abc"]
* ```
*/
export function normalizeIds(
ids: (string | number | null | undefined)[],
): string[] {
return ids
.map((id) => normalizeId(id))
.filter((id): id is string => id !== undefined);
}
/**
* Normalize IDs in an object
* Recursively normalizes all fields that match common ID field names
*
* @param obj - Object to normalize
* @param idFields - Optional array of field names to normalize (default: common ID fields)
* @returns New object with normalized IDs
*
* @example
* ```typescript
* normalizeObjectIds({ id: 123, user_id: 456 })
* // { id: "123", user_id: "456" }
* ```
*/
export function normalizeObjectIds<T extends Record<string, any>>(
obj: T,
idFields: string[] = [
'id',
'user_id',
'track_id',
'playlist_id',
'conversation_id',
'message_id',
'sender_id',
'creator_id',
'created_by',
'parent_id',
'parent_message_id',
],
): T {
if (!obj || typeof obj !== 'object') {
return obj;
}
const normalized = { ...obj } as Record<string, any>;
for (const [key, value] of Object.entries(normalized)) {
// Normalize ID fields
if (idFields.includes(key)) {
normalized[key] = normalizeId(value);
}
// Recursively normalize nested objects
else if (
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
!(value instanceof Date)
) {
normalized[key] = normalizeObjectIds(value, idFields);
}
// Normalize arrays of objects
else if (
Array.isArray(value) &&
value.length > 0 &&
typeof value[0] === 'object'
) {
normalized[key] = value.map((item) =>
typeof item === 'object' && item !== null
? normalizeObjectIds(item, idFields)
: item,
);
}
}
return normalized as T;
}
/**
* Normalize IDs in an array of objects
*
* @param items - Array of objects to normalize
* @param idFields - Optional array of field names to normalize
* @returns Array of objects with normalized IDs
*
* @example
* ```typescript
* normalizeArrayIds([{ id: 1 }, { id: 2 }])
* // [{ id: "1" }, { id: "2" }]
* ```
*/
export function normalizeArrayIds<T extends Record<string, any>>(
items: T[],
idFields?: string[],
): T[] {
return items.map((item) => normalizeObjectIds(item, idFields));
}
/**
* Type guard to check if a value is a valid ID (string or number)
*/
export function isValidId(id: unknown): id is string | number {
return typeof id === 'string' || typeof id === 'number';
}
/**
* Type guard to check if a value is a valid string ID
*/
export function isValidStringId(id: unknown): id is string {
return typeof id === 'string' && id.length > 0;
}