{"version":3,"file":"writeCsv-DMw5OmWp.mjs","names":[],"sources":["../src/lib/helpers/writeCsv.ts"],"sourcesContent":["import { createWriteStream, writeFileSync, appendFileSync } from 'node:fs';\n\nimport { ObjByString } from '@transcend-io/type-utils';\nimport * as fastcsv from 'fast-csv';\n\n/**\n * Escape a CSV value\n *\n * @param value - Value to escape\n * @returns Escaped value\n */\nfunction escapeCsvValue(value: string): string {\n  if (value.includes('\"') || value.includes(',') || value.includes('\\n')) {\n    return `\"${value.replace(/\"/g, '\"\"')}\"`;\n  }\n  return value;\n}\n\n/**\n * Write a csv to file synchronously, overwriting any existing content\n *\n * @param filePath - File to write out to\n * @param data - Data to write\n * @param headers - Headers. If true, use object keys as headers. If array, use provided headers.\n */\nexport function writeCsvSync(filePath: string, data: ObjByString[], headers: string[]): void {\n  const rows: string[][] = [];\n\n  rows.push(headers);\n  rows.push(...data.map((row) => Object.values(row)));\n\n  // Build CSV content with proper escaping\n  const csvContent = rows.map((row) => row.map(escapeCsvValue).join(',')).join('\\n');\n\n  // Write to file, overwriting existing content\n  writeFileSync(filePath, csvContent);\n}\n\n/**\n * Initialize a CSV file by writing only the header row (or an empty file if no headers).\n *\n * @param filePath - CSV path\n * @param headers - Ordered list of column names; if empty, creates/empties the file\n */\nexport function initCsvFile(filePath: string, headers: string[]): void {\n  if (!headers || headers.length === 0) {\n    writeFileSync(filePath, '');\n    return;\n  }\n  const headerLine = headers.map(escapeCsvValue).join(',');\n  writeFileSync(filePath, `${headerLine}\\n`);\n}\n\n/**\n * Append rows to CSV using an explicit header order (no header line).\n * Values are written in the order of `headerOrder`.\n *\n * @param filePath - CSV path\n * @param data - Row objects\n * @param headerOrder - Column order to apply\n */\nexport function appendCsvRowsOrdered(\n  filePath: string,\n  data: ObjByString[],\n  headerOrder: string[],\n): void {\n  if (!data.length) return;\n\n  const lines = data.map((row) => {\n    const vals = headerOrder.map((key) => {\n      const v = row[key];\n      return v == null ? '' : String(v);\n    });\n    return vals.map(escapeCsvValue).join(',');\n  });\n\n  appendFileSync(filePath, `${lines.join('\\n')}\\n`);\n}\n\n/**\n * Append data to an existing csv file synchronously (legacy, uses Object.values order).\n * Prefer appendCsvRowsOrdered for deterministic column order.\n *\n * @param filePath - File to append to\n * @param data - Data to append\n */\nexport function appendCsvSync(filePath: string, data: ObjByString[]): void {\n  // Convert data to CSV rows\n  const rows = data.map((row) => Object.values(row));\n\n  // Build CSV content with proper escaping\n  const csvContent = rows.map((row) => row.map(escapeCsvValue).join(',')).join('\\n');\n\n  // Append to file with leading newline\n  appendFileSync(filePath, `\\n${csvContent}`);\n}\n\n/**\n * Write a csv to file asynchronously\n *\n * @param filePath - File to write out to\n * @param data - Data to write\n * @param headers - Headers\n */\nexport async function writeCsv(\n  filePath: string,\n  data: ObjByString[],\n  headers: boolean | string[] = true,\n): Promise<void> {\n  const ws = createWriteStream(filePath);\n  await new Promise<void>((resolve, reject) => {\n    try {\n      const stream = fastcsv.write(data, { headers, objectMode: true }).on('error', reject);\n\n      ws.on('error', reject);\n      ws.on('finish', () => resolve());\n\n      stream.pipe(ws);\n    } catch (err) {\n      reject(err);\n    }\n  });\n}\n\n/**\n * Parse a file path into a base name and extension\n *\n * @param filePath - File path to parse\n * @returns Base name and extension\n */\nexport function parseFilePath(filePath: string): {\n  /** Base name of the file */\n  baseName: string;\n  /** Extension of the file */\n  extension: string;\n} {\n  const lastDotIndex = filePath.lastIndexOf('.');\n  return {\n    baseName: lastDotIndex !== -1 ? filePath.substring(0, lastDotIndex) : filePath,\n    extension: lastDotIndex !== -1 ? filePath.substring(lastDotIndex) : '.csv',\n  };\n}\n\n/**\n * Convert an object row into values aligned to header order\n *\n * @param row - Row object\n * @param headerOrder - Header order\n * @returns Aligned row object\n */\nfunction rowToValues(row: ObjByString, headerOrder: string[]): Record<string, unknown> {\n  // fast-csv with objectMode expects objects; we ensure consistent key ordering\n  // by building a new object with keys in headerOrder.\n  const ordered: Record<string, unknown> = {};\n  for (const key of headerOrder) {\n    // Preserve undefined -> becomes empty cell in CSV\n    ordered[key] = row[key];\n  }\n  return ordered;\n}\n\n/**\n * Await the 'drain' event when backpressure indicates buffering\n *\n * @param stream - Writable stream\n * @returns Promise that resolves on 'drain'\n */\nfunction waitForDrain(stream: NodeJS.WritableStream): Promise<void> {\n  return new Promise((resolve) => {\n    stream.once('drain', resolve);\n  });\n}\n\n/**\n * Stream a large CSV dataset to a single file with proper backpressure handling.\n * (Kept for completeness; not used by the incremental write path.)\n *\n * @param filePath - File to write out to\n * @param data - Data to write (iterated without buffering the entire file content)\n * @param headers - If true, infer from first row; if string[], use provided; if false, omit header row\n * @returns Array with a single written file path\n */\nexport async function writeLargeCsv(\n  filePath: string,\n  data: ObjByString[],\n  headers: boolean | string[] = true,\n): Promise<string[]> {\n  // Determine header order\n  let headerOrder: string[] | false;\n  if (Array.isArray(headers)) {\n    headerOrder = headers;\n  } else if (headers === true) {\n    headerOrder = data.length > 0 ? Object.keys(data[0]) : [];\n  } else {\n    headerOrder = false;\n  }\n\n  const ws = createWriteStream(filePath);\n  const csvStream = fastcsv.format<ObjByString, ObjByString>({\n    headers: headerOrder || undefined,\n    objectMode: true,\n  });\n\n  // Pipe CSV stream into file write stream\n  const piping = csvStream.pipe(ws);\n\n  const completion = new Promise<void>((resolve, reject) => {\n    piping.on('finish', () => resolve());\n    piping.on('error', reject);\n    csvStream.on('error', reject);\n    ws.on('error', reject);\n  });\n\n  // Stream rows with backpressure handling\n  for (const row of data) {\n    const toWrite = headerOrder ? rowToValues(row, headerOrder) : row;\n    const ok = csvStream.write(toWrite);\n    if (!ok) {\n      // Respect backpressure: wait until the internal buffer drains\n      await waitForDrain(csvStream);\n    }\n  }\n\n  // Signal end of input and wait for finish\n  csvStream.end();\n  await completion;\n\n  return [filePath];\n}\n"],"mappings":"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"}