|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { CsvParseResult, CsvRow } from '../types/index.ts'; |
|
|
|
|
|
|
|
|
export * from './user.utils.ts'; |
|
|
|
|
|
|
|
|
export * from './analysis.utils.ts'; |
|
|
|
|
|
|
|
|
export * from './cache.utils.ts'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function debounce<T extends (...args: any[]) => any>( |
|
|
func: T, |
|
|
wait: number |
|
|
): (...args: Parameters<T>) => void { |
|
|
let timeout: NodeJS.Timeout | null = null; |
|
|
return function executedFunction(...args: Parameters<T>) { |
|
|
const later = () => { |
|
|
timeout = null; |
|
|
func(...args); |
|
|
}; |
|
|
if (timeout) clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function formatError(error: unknown): string { |
|
|
if (typeof error === 'string') return error; |
|
|
if (error && typeof error === 'object' && 'message' in error) { |
|
|
return String(error.message); |
|
|
} |
|
|
return 'An unexpected error occurred'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function sleep(ms: number): Promise<void> { |
|
|
return new Promise((resolve) => setTimeout(resolve, ms)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function isEmpty(value: unknown): boolean { |
|
|
if (value == null) return true; |
|
|
if (typeof value === 'string') return value.trim().length === 0; |
|
|
if (Array.isArray(value)) return value.length === 0; |
|
|
if (typeof value === 'object') return Object.keys(value).length === 0; |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function parseCsv(content: string): CsvParseResult { |
|
|
const lines = content.split(/\r?\n/).filter((line) => line.trim().length > 0); |
|
|
|
|
|
if (lines.length === 0) { |
|
|
return { headers: [], rows: [] }; |
|
|
} |
|
|
|
|
|
const parseLine = (line: string): string[] => { |
|
|
const cells: string[] = []; |
|
|
let current = ''; |
|
|
let inQuotes = false; |
|
|
|
|
|
for (let i = 0; i < line.length; i += 1) { |
|
|
const char = line[i]; |
|
|
const nextChar = line[i + 1]; |
|
|
|
|
|
if (char === '"') { |
|
|
if (inQuotes && nextChar === '"') { |
|
|
current += '"'; |
|
|
i += 1; |
|
|
} else { |
|
|
inQuotes = !inQuotes; |
|
|
} |
|
|
} else if (char === ',' && !inQuotes) { |
|
|
cells.push(current.trim()); |
|
|
current = ''; |
|
|
} else { |
|
|
current += char; |
|
|
} |
|
|
} |
|
|
|
|
|
cells.push(current.trim()); |
|
|
return cells; |
|
|
}; |
|
|
|
|
|
const headers = parseLine(lines[0]).map((header, index) => |
|
|
header ? header : `column_${index + 1}` |
|
|
); |
|
|
|
|
|
const rows: CsvRow[] = lines.slice(1).map((line) => { |
|
|
const values = parseLine(line); |
|
|
const row: CsvRow = {}; |
|
|
|
|
|
headers.forEach((header, idx) => { |
|
|
row[header] = values[idx]?.trim() ?? ''; |
|
|
}); |
|
|
|
|
|
return row; |
|
|
}); |
|
|
|
|
|
return { |
|
|
headers, |
|
|
rows: rows.filter((row) => |
|
|
Object.values(row).some((value) => value !== undefined && value !== '') |
|
|
), |
|
|
}; |
|
|
} |
|
|
|
|
|
|