UNPKG

csv-database

Version:

lightweight CSV database

118 lines (117 loc) 3.74 kB
const fastcsv = require("fast-csv"); const fs = require("fs"); const lockfile = require("proper-lockfile"); const tempy = require("tempy"); // Map SIGINT & SIGTERM to process exit // so that lockfile removes the lockfile automatically process .once("SIGINT", () => process.exit(1)) .once("SIGTERM", () => process.exit(1)); // lock middleware to ensure async/thread safety const lock = async (file, next) => new Promise((resolve, reject) => { lockfile.lock(file, { retries: { retries: 500, factor: 3, minTimeout: 1 * 10, maxTimeout: 60 * 1000, randomize: true } }, async (err, release) => { if (err) { reject(err); } try { await next(); } catch (err) { reject(err); } release(); resolve(); }); }); /** file i/o helpers */ const getCsvStream = (file, delimiter) => { // append flag : we only work with empty files or adding data const fileStream = fs.createWriteStream(file, { flags: "a" }); const csvStream = fastcsv.createWriteStream({ headers: true, delimiter }); csvStream.pipe(fileStream); return csvStream; }; const copyCsv = (from, to) => new Promise((resolve, reject) => { const fromStream = fs.createReadStream(from); fromStream.on("error", reject); const toStream = fs.createWriteStream(to); toStream.on("error", reject).on("close", resolve); fromStream.pipe(toStream); }); /** */ const read = async (filename, delimiter, events) => new Promise((resolve, reject) => { fastcsv .fromPath(filename, { delimiter, headers: true }) .on("data", events.onData) .on("error", reject) .on("end", resolve); }); const edit = (filename, delimiter, events) => new Promise((resolve, reject) => { const copy = tempy.file(); const tempStream = getCsvStream(copy, delimiter); fastcsv .fromPath(filename, { delimiter, headers: true }) .on("data", data => { const newData = events.onEdit(data); // handling deletion case when editing returns nothing if (newData) { tempStream.write(newData); } }) .on("error", reject) .on("end", () => { tempStream.end(); }); tempStream.on("end", () => { // copy data from tempfile to original file copyCsv(copy, filename) .then(resolve) .catch(reject); }); }); const add = (filename, delimiter, data) => new Promise((resolve, reject) => { const copy = tempy.file(); const tempStream = getCsvStream(copy, delimiter); fastcsv .fromPath(filename, { delimiter, headers: true }) .on("error", reject) .on("end", () => { // appending data at end of file for (const row of data) { tempStream.write(row); } tempStream.end(); }) .pipe(tempStream); tempStream.on("end", () => { // copy data from tempfile to original file copyCsv(copy, filename) .then(resolve) .catch(reject); }); }); const lockedEdit = async (filename, delimiter, events) => { const func = async () => edit(filename, delimiter, events); return lock(filename, func); }; const lockedAdd = async (filename, delimiter, data) => { const func = async () => add(filename, delimiter, data); return lock(filename, func); }; const csvEditor = (filename, delimiter) => ({ read: events => read(filename, delimiter, events), add: data => lockedAdd(filename, delimiter, data), edit: events => lockedEdit(filename, delimiter, events) }); module.exports = csvEditor;