160 lines
4.3 KiB
TypeScript
160 lines
4.3 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;
|
||
|
|
}
|
||
|
|
|