120 lines
3.3 KiB
JavaScript
120 lines
3.3 KiB
JavaScript
/*
|
|
CSV Stringify
|
|
|
|
Please look at the [project documentation](https://csv.js.org/stringify/) for
|
|
additional information.
|
|
*/
|
|
|
|
import { Transform } from "stream";
|
|
import { CsvError } from "./api/CsvError.js";
|
|
import { is_object } from "./utils/is_object.js";
|
|
import { stringifier } from "./api/index.js";
|
|
import { normalize_options } from "./api/normalize_options.js";
|
|
|
|
class Stringifier extends Transform {
|
|
constructor(opts = {}) {
|
|
super({ ...{ writableObjectMode: true }, ...opts });
|
|
const [err, options] = normalize_options(opts);
|
|
if (err !== undefined) throw err;
|
|
// Expose options
|
|
this.options = options;
|
|
// Internal state
|
|
this.state = {
|
|
stop: false,
|
|
};
|
|
// Information
|
|
this.info = {
|
|
records: 0,
|
|
};
|
|
this.api = stringifier(this.options, this.state, this.info);
|
|
this.api.options.on_record = (...args) => {
|
|
this.emit("record", ...args);
|
|
};
|
|
}
|
|
_transform(chunk, encoding, callback) {
|
|
if (this.state.stop === true) {
|
|
return;
|
|
}
|
|
const err = this.api.__transform(chunk, this.push.bind(this));
|
|
if (err !== undefined) {
|
|
this.state.stop = true;
|
|
}
|
|
callback(err);
|
|
}
|
|
_flush(callback) {
|
|
if (this.state.stop === true) {
|
|
// Note, Node.js 12 call flush even after an error, we must prevent
|
|
// `callback` from being called in flush without any error.
|
|
return;
|
|
}
|
|
if (this.info.records === 0) {
|
|
this.api.bom(this.push.bind(this));
|
|
const err = this.api.headers(this.push.bind(this));
|
|
if (err) callback(err);
|
|
}
|
|
callback();
|
|
}
|
|
}
|
|
|
|
const stringify = function () {
|
|
let data, options, callback;
|
|
for (const i in arguments) {
|
|
const argument = arguments[i];
|
|
const type = typeof argument;
|
|
if (data === undefined && Array.isArray(argument)) {
|
|
data = argument;
|
|
} else if (options === undefined && is_object(argument)) {
|
|
options = argument;
|
|
} else if (callback === undefined && type === "function") {
|
|
callback = argument;
|
|
} else {
|
|
throw new CsvError("CSV_INVALID_ARGUMENT", [
|
|
"Invalid argument:",
|
|
`got ${JSON.stringify(argument)} at index ${i}`,
|
|
]);
|
|
}
|
|
}
|
|
const stringifier = new Stringifier(options);
|
|
if (callback) {
|
|
const chunks = [];
|
|
stringifier.on("readable", function () {
|
|
let chunk;
|
|
while ((chunk = this.read()) !== null) {
|
|
chunks.push(chunk);
|
|
}
|
|
});
|
|
stringifier.on("error", function (err) {
|
|
callback(err);
|
|
});
|
|
stringifier.on("end", function () {
|
|
try {
|
|
callback(undefined, chunks.join(""));
|
|
} catch (err) {
|
|
// This can happen if the `chunks` is extremely long; it may throw
|
|
// "Cannot create a string longer than 0x1fffffe8 characters"
|
|
// See [#386](https://github.com/adaltas/node-csv/pull/386)
|
|
callback(err);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
if (data !== undefined) {
|
|
const writer = function () {
|
|
for (const record of data) {
|
|
stringifier.write(record);
|
|
}
|
|
stringifier.end();
|
|
};
|
|
// Support Deno, Rollup doesnt provide a shim for setImmediate
|
|
if (typeof setImmediate === "function") {
|
|
setImmediate(writer);
|
|
} else {
|
|
setTimeout(writer, 0);
|
|
}
|
|
}
|
|
return stringifier;
|
|
};
|
|
|
|
// export default stringify
|
|
export { stringify, CsvError, Stringifier };
|