/** * Tiny CSV serializer. RFC 4180 — quotes fields that contain commas, quotes, or newlines. */ export function csvEscape(value: unknown): string { if (value === null || value === undefined) return ''; const s = String(value); if (/[",\n\r]/.test(s)) { return `"${s.replace(/"/g, '""')}"`; } return s; } export function csvRow(values: unknown[]): string { return values.map(csvEscape).join(','); } export function csvBuild(rows: unknown[][]): string { return rows.map(csvRow).join('\r\n'); } /** Build a Response with a CSV download. */ export function csvResponse(csv: string, filename: string): Response { // Prepend BOM so Excel detects UTF-8 (important for Thai characters) const body = '\uFEFF' + csv; return new Response(body, { headers: { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': `attachment; filename="${filename}"` } }); }