export-to-csv
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -1,173 +0,1 @@ | ||
const defaults = { | ||
fieldSeparator: ",", | ||
decimalSeparator: ".", | ||
quoteStrings: true, | ||
quoteCharacter: '"', | ||
showTitle: false, | ||
title: "My Generated Report", | ||
filename: "generated", | ||
showColumnHeaders: true, | ||
useTextFile: false, | ||
useBom: true, | ||
columnHeaders: [], | ||
useKeysAsHeaders: false, | ||
boolDisplay: { true: "TRUE", false: "FALSE" }, | ||
}; | ||
const endOfLine = "\r\n"; | ||
const byteOrderMark = "\ufeff"; | ||
const mkConfig = (opts) => Object.assign({}, defaults, opts); | ||
class CsvGenerationError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "CsvGenerationError"; | ||
} | ||
} | ||
class EmptyHeadersError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "EmptyHeadersError"; | ||
} | ||
} | ||
class CsvDownloadEnvironmentError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "CsvDownloadEnvironmentError"; | ||
} | ||
} | ||
const pack = (value) => value; | ||
const unpack = (newtype) => newtype; | ||
const mkCsvOutput = (pack); | ||
const mkCsvRow = (pack); | ||
const thread = (initialValue, ...fns) => fns.reduce((r, fn) => fn(r), initialValue); | ||
const addBOM = (config) => (output) => config.useBom ? mkCsvOutput(unpack(output) + byteOrderMark) : output; | ||
const addTitle = (config) => (output) => config.showTitle ? mkCsvOutput(unpack(output) + config.title) : output; | ||
const addEndOfLine = (output) => (row) => mkCsvOutput(unpack(output) + unpack(row) + endOfLine); | ||
const buildRow = (config) => (row, data) => addFieldSeparator(config)(mkCsvRow(row + data)); | ||
const addFieldSeparator = (config) => (output) => pack(unpack(output) + config.fieldSeparator); | ||
const addHeaders = (config, headers) => (output) => { | ||
if (!config.showColumnHeaders) { | ||
return output; | ||
} | ||
if (headers.length < 1) { | ||
throw new EmptyHeadersError("Option to show headers but none supplied. Make sure there are keys in your collection or that you've supplied headers through the config options."); | ||
} | ||
let row = mkCsvRow(""); | ||
for (let keyPos = 0; keyPos < headers.length; keyPos++) { | ||
row = buildRow(config)(row, headers[keyPos]); | ||
} | ||
row = mkCsvRow(unpack(row).slice(0, -1)); | ||
return addEndOfLine(output)(row); | ||
}; | ||
const addBody = (config, headers, bodyData) => (output) => { | ||
let body = output; | ||
for (var i = 0; i < bodyData.length; i++) { | ||
let row = mkCsvRow(""); | ||
for (let keyPos = 0; keyPos < headers.length; keyPos++) { | ||
const header = headers[keyPos]; | ||
row = buildRow(config)(row, formatData(config, bodyData[i][header])); | ||
} | ||
// Remove trailing comma | ||
row = mkCsvRow(unpack(row).slice(0, -1)); | ||
body = addEndOfLine(body)(row); | ||
} | ||
return body; | ||
}; | ||
/** | ||
* | ||
* Convert CsvOutput => string for the typechecker. | ||
* | ||
* Useful if you need to take the return value and | ||
* treat is as a string in the rest of your program. | ||
*/ | ||
const asString = (unpack); | ||
const isFloat = (input) => +input === input && (!isFinite(input) || Boolean(input % 1)); | ||
const formatData = (config, data) => { | ||
if (config.decimalSeparator === "locale" && isFloat(data)) { | ||
return data.toLocaleString(); | ||
} | ||
if (config.decimalSeparator !== "." && isFloat(data)) { | ||
return data.toString().replace(".", config.decimalSeparator); | ||
} | ||
if (typeof data === "string") { | ||
let val = data; | ||
if (config.quoteStrings || | ||
data.indexOf(config.fieldSeparator) > -1 || | ||
data.indexOf("\n") > -1 || | ||
data.indexOf("\r") > -1) { | ||
val = config.quoteCharacter + data + config.quoteCharacter; | ||
} | ||
return val; | ||
} | ||
if (typeof data === "boolean") { | ||
// Convert to string to use as lookup in config | ||
const asStr = data ? "true" : "false"; | ||
// Return the custom boolean display if set | ||
return config.boolDisplay[asStr]; | ||
} | ||
return data; | ||
}; | ||
/** | ||
* | ||
* Generates CsvOutput data from JSON collection using | ||
* ConfigOptions given. | ||
* | ||
* To comfortably use the data as a string around your | ||
* application, look at {@link asString}. | ||
* | ||
* @throws {CsvGenerationError | EmptyHeadersError} | ||
*/ | ||
const generateCsv = (config) => (data) => { | ||
const withDefaults = mkConfig(config); | ||
const headers = withDefaults.useKeysAsHeaders | ||
? Object.keys(data[0]) | ||
: withDefaults.columnHeaders; | ||
// Build csv output starting with an empty string | ||
let output = thread(mkCsvOutput(""), addBOM(withDefaults), addTitle(withDefaults), addHeaders(withDefaults, headers), addBody(withDefaults, headers, data)); | ||
if (unpack(output).length < 1) { | ||
throw new CsvGenerationError("Output is empty. Is your data formatted correctly?"); | ||
} | ||
return output; | ||
}; | ||
/** | ||
* | ||
* **Only supported in browser environment.** | ||
* | ||
* Will create a hidden anchor link in the page with the | ||
* download attribute set to a blob version of the CsvOutput data. | ||
* | ||
* @throws {CsvDownloadEnvironmentError} | ||
*/ | ||
const download = (config) => (csvOutput) => { | ||
// Downloading is only supported in a browser environment. | ||
// Node users can simply write the output from generateCsv | ||
// to disk. | ||
if (!window) { | ||
throw new CsvDownloadEnvironmentError("Downloading only supported in a browser environment."); | ||
} | ||
const withDefaults = mkConfig(config); | ||
const data = unpack(csvOutput); | ||
// Create blob from CsvOutput either as text or csv file. | ||
const fileType = withDefaults.useTextFile ? "plain" : "csv"; | ||
const fileExtension = withDefaults.useTextFile ? "txt" : "csv"; | ||
let blob = new Blob([data], { | ||
type: `text/${fileType};charset=utf8;`, | ||
}); | ||
// Create link element in the browser and set the download | ||
// attribute to the blob that was created. | ||
let link = document.createElement("a"); | ||
link.download = `${withDefaults.filename}.${fileExtension}`; | ||
link.href = URL.createObjectURL(blob); | ||
// Ensure the link isn't visible to the user or cause layout shifts. | ||
link.setAttribute("visibility", "hidden"); | ||
// Add to document body, click and remove it. | ||
document.body.appendChild(link); | ||
link.click(); | ||
document.body.removeChild(link); | ||
}; | ||
export { asString, download, generateCsv, mkConfig }; | ||
//# sourceMappingURL=index.js.map | ||
var O={fieldSeparator:",",decimalSeparator:".",quoteStrings:!0,quoteCharacter:'"',showTitle:!1,title:"My Generated Report",filename:"generated",showColumnHeaders:!0,useTextFile:!1,useBom:!0,columnHeaders:[],useKeysAsHeaders:!1,boolDisplay:{true:"TRUE",false:"FALSE"},replaceUndefinedWith:""},_="\r\n",G="\uFEFF",S=(x)=>Object.assign({},O,x);class W extends Error{constructor(x){super(x);this.name="CsvGenerationError"}}class X extends Error{constructor(x){super(x);this.name="EmptyHeadersError"}}class Y extends Error{constructor(x){super(x);this.name="CsvDownloadEnvironmentError"}}var U=(x)=>x,A=(x)=>x,N=U,Q=U;var H=(x,...$)=>$.reduce((j,q)=>q(j),x),M=(x)=>($)=>x.useBom?N(A($)+G):$,T=(x)=>($)=>x.showTitle?N(A($)+x.title):$,V=(x)=>($)=>N(A(x)+A($)+_),F=(x)=>($,j)=>b(x)(Q($+j)),b=(x)=>($)=>U(A($)+x.fieldSeparator),L=(x,$)=>(j)=>{if(!x.showColumnHeaders)return j;if($.length<1)throw new X("Option to show headers but none supplied. Make sure there are keys in your collection or that you've supplied headers through the config options.");let q=Q("");for(let z=0;z<$.length;z++)q=F(x)(q,P(x,$[z]));return q=Q(A(q).slice(0,-1)),V(j)(q)},R=(x,$,j)=>(q)=>{let z=q;for(var K=0;K<j.length;K++){let J=Q("");for(let I=0;I<$.length;I++){const Z=$[I],E=typeof j[K][Z]==="undefined"?x.replaceUndefinedWith:j[K][Z];J=F(x)(J,P(x,E))}J=Q(A(J).slice(0,-1)),z=V(z)(J)}return z},m=A,B=(x)=>+x===x&&(!isFinite(x)||Boolean(x%1)),P=(x,$)=>{if(x.decimalSeparator==="locale"&&B($))return $.toLocaleString();if(x.decimalSeparator!=="."&&B($))return $.toString().replace(".",x.decimalSeparator);if(typeof $==="string"){let j=$;if(x.quoteStrings||x.fieldSeparator&&$.indexOf(x.fieldSeparator)>-1||$.indexOf("\n")>-1||$.indexOf("\r")>-1)j=x.quoteCharacter+$+x.quoteCharacter;return j}if(typeof $==="boolean"&&x.boolDisplay){const j=$?"true":"false";return x.boolDisplay[j]}return $};var o=(x)=>($)=>{const j=S(x),q=j.useKeysAsHeaders?Object.keys($[0]):j.columnHeaders;let z=H(N(""),M(j),T(j),L(j,q),R(j,q,$));if(A(z).length<1)throw new W("Output is empty. Is your data formatted correctly?");return z},t=(x)=>($)=>{if(!window)throw new Y("Downloading only supported in a browser environment.");const j=S(x),q=A($),z=j.useTextFile?"plain":"csv",K=j.useTextFile?"txt":"csv";let J=new Blob([q],{type:`text/${z};charset=utf8;`}),I=document.createElement("a");I.download=`${j.filename}.${K}`,I.href=URL.createObjectURL(J),I.setAttribute("visibility","hidden"),document.body.appendChild(I),I.click(),document.body.removeChild(I)};export{S as mkConfig,o as generateCsv,t as download,m as asString}; |
@@ -0,1 +1,2 @@ | ||
/// <reference lib="dom" /> | ||
import { CsvOutput, ConfigOptions, IO } from "./types"; | ||
@@ -2,0 +3,0 @@ /** |
@@ -23,2 +23,3 @@ export type Newtype<URI, A> = { | ||
}; | ||
replaceUndefinedWith?: string | boolean | null; | ||
}; | ||
@@ -25,0 +26,0 @@ export interface CsvOutput extends Newtype<{ |
{ | ||
"name": "export-to-csv", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Easily create CSV data from json collection", | ||
@@ -11,9 +11,8 @@ "type": "module", | ||
"scripts": { | ||
"build": "rm -rf output && rollup -c rollup.config.mjs", | ||
"e2e": "rm -rf integration/export-to-csv.js && npm run build && cp output/index.js integration/export-to-csv.js && playwright test", | ||
"e2e-ci": "rm -rf integration/export-to-csv.js && npm run build && cp output/index.js integration/export-to-csv.js && npx playwright install --with-deps && playwright test", | ||
"e2e-server": "npx http-server ./integration -p 3000", | ||
"test": "jest", | ||
"dev": "tsc --watch", | ||
"prepublishOnly": "npm run build" | ||
"build": "rm -rf output && bun build index.ts --outdir ./output --minify && tsc index.ts --declaration --emitDeclarationOnly --outdir ./output", | ||
"e2e": "rm -rf integration/export-to-csv.js && bun run build && cp output/index.js integration/export-to-csv.js && playwright test", | ||
"e2e-ci": "rm -rf integration/export-to-csv.js && bun run build && cp output/index.js integration/export-to-csv.js && bunx playwright install --with-deps && playwright test", | ||
"e2e-server": "bunx http-server ./integration -p 3000", | ||
"test": "bun test lib/__specs__/", | ||
"prepublishOnly": "bun run build" | ||
}, | ||
@@ -47,6 +46,6 @@ "keywords": [ | ||
"babel-jest": "^29.6.4", | ||
"bun-types": "^1.0.3", | ||
"http-server": "^14.1.1", | ||
"jest": "^29.6.4", | ||
"prettier": "3.0.3", | ||
"rollup": "^3.28.1", | ||
"bun": "~1.0.3", | ||
"tslib": "^2.6.2", | ||
@@ -53,0 +52,0 @@ "typescript": "~5.2.2" |
@@ -23,2 +23,4 @@ # export-to-csv | Export to CSV Mini Library | ||
### In-browser | ||
```typescript | ||
@@ -58,19 +60,57 @@ import { mkConfig, generateCsv, download } from "export-to-csv"; | ||
### Node.js | ||
```typescript | ||
import { mkConfig, generateCsv, asString } from "./output/index.js"; | ||
import { writeFile } from "node:fs"; | ||
import { Buffer } from "node:buffer"; | ||
// mkConfig merges your options with the defaults | ||
// and returns WithDefaults<ConfigOptions> | ||
const csvConfig = mkConfig({ useKeysAsHeaders: true }); | ||
const mockData = [ | ||
{ | ||
name: "Rouky", | ||
date: "2023-09-01", | ||
percentage: 0.4, | ||
quoted: '"Pickles"', | ||
}, | ||
{ | ||
name: "Keiko", | ||
date: "2023-09-01", | ||
percentage: 0.9, | ||
quoted: '"Cactus"', | ||
}, | ||
]; | ||
// Converts your Array<Object> to a CsvOutput string based on the configs | ||
const csv = generateCsv(csvConfig)(mockData); | ||
const filename = `${csvConfig.filename}.csv`; | ||
const csvBuffer = new Uint8Array(Buffer.from(asString(csv))); | ||
// Write the csv file to disk | ||
writeFile(filename, csvBuffer, (err) => { | ||
if (err) throw err; | ||
console.log("file saved: ", filename); | ||
}); | ||
``` | ||
## API | ||
| Option | Default | Type | Description | | ||
| ----------------- | ------------------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| fieldSeparator | "," | string | Defines the field separator character | | ||
| filename | "generated" | string | Sets the name of the file creates from the `download` function | | ||
| quoteStrings | false | boolean | Determines whether or not to quote strings (using `quoteCharacter`'s value). Whether or not this is set, `\r`, `\n`, and `fieldSeparator` will be quoted. | | ||
| quoteCharacter | '"' | string | Sets the quote character to use. | | ||
| decimalSeparator | "." | string | Defines the decimal separator character (default is .). If set to "locale", it uses the [language sensitive representation of the number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString). | | ||
| showTitle | false | boolean | Sets whether or not to add the value of `title` to the start of the CSV. (This is not supported by all CSV readers) | | ||
| title | "My Generated Report" | string | The title to display as the first line of the CSV file. (This **is not** the name of the file [see `filename`]) | | ||
| showColumnHeaders | true | boolean | Determines if columns should have headers. When set to `false`, the first row of the CSV will be data. | | ||
| columnHeaders | [] | Array<string> | **Use this option if column/header order is important!** Determines the headers to use as the first line of the CSV data. Items in the array _must match key names of objects in your input collection._ | | ||
| useKeysAsHeaders | false | boolean | If set, the CSV will use the key names in your collection as headers. **Warning: `headers` recommended for large collections. If set, it'll override the `headers` option. Column/header order also not guaranteed. Use `headers` only if order is important!** | | ||
| boolDisplay | {true: "TRUE", false: "FALSE"} | {true: string, false: string} | Determines how to display boolean values in the CSV. **This only works for `true` and `false`. `1` and `0` will not be coerced and will display as `1` and `0`.** | | ||
| useBom | true | boolean | Adds a [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark) which is required by Excel to display CSVs, despite is not being necessary with UTF-8 🤷♂️ | | ||
| useTextFile | false | boolean | Will download the file as `text/plain` instead of `text/csv` and use a `.txt` vs `.csv` file-extension. | | ||
| Option | Default | Type | Description | | ||
| ------------------- | -------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `fieldSeparator` | `","` | `string` | Defines the field separator character | | ||
| `filename` | `"generated"` | `string` | Sets the name of the file creates from the `download` function | | ||
| `quoteStrings` | `false` | `boolean` | Determines whether or not to quote strings (using `quoteCharacter`'s value). Whether or not this is set, `\r`, `\n`, and `fieldSeparator` will be quoted. | | ||
| `quoteCharacter` | `'"'` | `string` | Sets the quote character to use. | | ||
| `decimalSeparator` | `"."` | `string` | Defines the decimal separator character (default is .). If set to "locale", it uses the [language sensitive representation of the number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString). | | ||
| `showTitle` | `false` | `boolean` | Sets whether or not to add the value of `title` to the start of the CSV. (This is not supported by all CSV readers) | | ||
| `title` | `"My Generated Report"` | `string` | The title to display as the first line of the CSV file. (This **is not** the name of the file [see `filename`]) | | ||
| `showColumnHeaders` | `true` | `boolean` | Determines if columns should have headers. When set to `false`, the first row of the CSV will be data. | | ||
| `columnHeaders` | `[]` | `Array<string>` | **Use this option if column/header order is important!** Determines the headers to use as the first line of the CSV data. Items in the array _must match key names of objects in your input collection._ | | ||
| `useKeysAsHeaders` | `false` | `boolean` | If set, the CSV will use the key names in your collection as headers. **Warning: `headers` recommended for large collections. If set, it'll override the `headers` option. Column/header order also not guaranteed. Use `headers` only if order is important!** | | ||
| `boolDisplay` | `{true: "TRUE", false: "FALSE"}` | `{true: string, false: string}` | Determines how to display boolean values in the CSV. **This only works for `true` and `false`. `1` and `0` will not be coerced and will display as `1` and `0`.** | | ||
| `useBom` | `true` | `boolean` | Adds a [byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark) which is required by Excel to display CSVs, despite is not being necessary with UTF-8 🤷♂️ | | ||
| `useTextFile` | `false` | `boolean` | Will download the file as `text/plain` instead of `text/csv` and use a `.txt` vs `.csv` file-extension. | | ||
@@ -77,0 +117,0 @@ # Alternatives |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
159067
131
0
115215
111