( input: Buffer | Uint8Array | string, delimiter = ',' )
| 97 | * For HTTP uploads prefer {@link createCsvParser} so the file isn't buffered. |
| 98 | */ |
| 99 | export async function parseCsvBuffer( |
| 100 | input: Buffer | Uint8Array | string, |
| 101 | delimiter = ',' |
| 102 | ): Promise<{ headers: string[]; rows: Record<string, unknown>[] }> { |
| 103 | const { parse } = await import('csv-parse/sync') |
| 104 | |
| 105 | let text: string |
| 106 | if (typeof input === 'string') { |
| 107 | text = input |
| 108 | } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) { |
| 109 | text = input.toString('utf-8') |
| 110 | } else { |
| 111 | text = new TextDecoder('utf-8').decode(input as Uint8Array) |
| 112 | } |
| 113 | |
| 114 | // double-cast-allowed: shared csvParseOptions() loses the `columns: true` literal that drives |
| 115 | // csv-parse's record-vs-string[][] overload, but `columns: true` is always set so records are objects |
| 116 | const parsed = parse(text, csvParseOptions(delimiter)) as unknown as Record<string, unknown>[] |
| 117 | |
| 118 | if (parsed.length === 0) { |
| 119 | throw new Error('CSV file has no data rows') |
| 120 | } |
| 121 | |
| 122 | const headers = Object.keys(parsed[0]) |
| 123 | if (headers.length === 0) { |
| 124 | throw new Error('CSV file has no headers') |
| 125 | } |
| 126 | |
| 127 | return { headers, rows: parsed } |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * Infers a column type from a sample of non-empty values. Order matters: we |
no test coverage detected