UNPKG

@paulof25/emoji-separated-values

Version:

emoji-separated-values (or simply ESV) is your new favorite npm package for handling text-based data. Forget about boring commas — we use emojis as delimiters. Because why not?

509 lines (378 loc) 12.6 kB
# 😎 emoji-separated-values (.ESV) > The modern, fun, and slightly absurd alternative to `.CSV` files. `emoji-separated-values` (or simply **ESV**) is your new favorite npm package for handling text-based data. Forget about boring commas — we use **emojis** as delimiters. Because why not? --- ## 🚀 Features - **Read, write, and update** `.ESV` files easily. - Built with **streams** for high performance — even your old, Inca-era PC can handle it. - Can be used as a **lightweight database**, supporting full CRUD operations. - Includes **filtering**, **query-like syntax**, and both **simple** and **advanced** APIs. - Fully compatible with traditional `.CSV` files (but... why would you do that?). --- ## 🧠 Philosophy `emoji-separated-values` embraces the most effective agile methodology ever invented: **XGH — Extreme Go Horse™**. Even though the project itself wasn’t built that way (thankfully), the spirit remains: > “Do as I say, not as I do.” 🐎 --- ## ⚙️ Installation ```bash npm install emoji-separated-values ``` or with yarn: ```bash yarn add emoji-separated-values ``` --- ## 💻 Usage ### 🟢 Reading — Simple Example ```ts import { QuickEsv } from "emoji-separated-values"; async function read() { const esv = new QuickEsv(); const list = await esv.readEsvFile("public/esvFile.esv", 10, 10, "🟩"); console.log(list); } ``` **Parameters** | Name | Type | Description | |------|------|-------------| | `filePath` | `string` | Path to the `.esv` file | | `skip` | `number` | Number of rows to skip | | `limit` | `number` | Max number of rows to read | | `separator` | `string` | Emoji used as separator | --- ### 🧩 Reading with Filters ```ts import { EsvFilterOperator, QuickEsv, type EsvFilterType } from "emoji-separated-values"; const filters = [ { field: "City", value: "Rio de Janeiro", operator: EsvFilterOperator.Equals }, { field: "Profession", value: "Entrepreneur", operator: EsvFilterOperator.NotEquals }, { field: "Age", value: 20, operator: EsvFilterOperator.LessThanOrEqual }, ]; async function filter() { const esv = new QuickEsv(); const filtered = await esv.filterEsvFile("public/esvFile.esv", 0, 10, filters, "🟩"); console.log(filtered); } ``` --- ### ✍️ Writing ```ts import { QuickEsv, type EsvRowType } from "emoji-separated-values"; const data: EsvRowType[] = [ { Name: "João", Age: "28", City: "Rio de Janeiro", Profession: "Architect" }, { Name: "Lucas", Age: "36", City: "Belém", Profession: "Designer" }, { Name: "Isabela", Age: "51", City: "Rio de Janeiro", Profession: "Entrepreneur" } ]; async function write() { const esv = new QuickEsv(); await esv.writeEsvFile("public/esvFile.esv", data, "🟩"); } ``` If the file doesn’t exist, it will be **created**. If it exists, new data will be **appended**. --- ### 🔄 Updating ```ts import { EsvFilterOperator, type EsvFilterType, QuickEsv, type EsvRowType } from "emoji-separated-values"; const filters = [ { field: "City", value: "Rio de Janeiro", operator: EsvFilterOperator.Equals }, { field: "Profession", value: "Entrepreneur", operator: EsvFilterOperator.NotEquals }, { field: "Age", value: 20, operator: EsvFilterOperator.LessThanOrEqual }, ]; async function update() { const esv = new QuickEsv(); await esv.updateEsvFile( "public/esvFile.esv", { Name: "João", Age: "28", City: "Rio de Janeiro", Profession: "Architect" }, filters, "🟩" ); } ``` --- ### 🗑️ Deleting ```ts import { EsvFilterOperator, QuickEsv, type EsvFilterType } from "emoji-separated-values"; const filters = [ { field: "City", value: "Rio de Janeiro", operator: EsvFilterOperator.Equals }, { field: "Profession", value: "Entrepreneur", operator: EsvFilterOperator.NotEquals }, { field: "Age", value: 20, operator: EsvFilterOperator.LessThanOrEqual }, ]; async function deleteData() { const esv = new QuickEsv(); await esv.deleteEsvFile("public/esvFile.esv", filters, "🟩"); } ``` --- ## ⚡ Advanced Usage (for the brave) If you enjoy doing things the hard way — maybe for performance reasons (or just for fun) — you can use the **manual API**. It gives you fine-grained control over file operations using `ManualEsv`, ideal for large `.ESV` files. Examples include: - Reading manually line by line - Filtering rows with custom logic - Updating or deleting rows using stream-based operations > ⚠️ But let’s be honest — your file probably isn’t that big. 😉 (See `/examples/manual/` in the repo for full reference code.) --- ## 🤝 Contributing Contributions are welcome! If you want to improve this ridiculous yet brilliant idea, feel free to: 1. Fork the repository 2. Create a new branch (`feature/your-feature`) 3. Commit your changes 4. Submit a pull request Please make sure to write clear commit messages and keep the humor consistent. --- ## 🪪 License MIT License © 2025 — [Your Name Here] Feel free to use, modify, and share this package as long as you don’t replace the emojis with commas. That would be blasphemy. --- ## 🧡 Final Words `.CSV` is the past. `.ESV` is the future. Because **data should make you smile**. 😄 --- ## 🧠 Advanced Examples > For those who like to suffer a little more than necessary — > welcome to the *Manual Mode*. If you’re handling a huge `.ESV` file (or if you just want to look smart in front of your teammates), you can use the **Manual API**, which exposes lower-level control over reading, filtering, updating, and deleting data using Node streams. --- ### 📖 Manual Read (No Filters) ```ts import { ManualEsv, type EsvRowType } from "emoji-separated-values"; export class ReadEsv { repository: ManualEsv; constructor(repository: ManualEsv) { this.repository = repository; } async execute(filePath: string, separator: string, skip?: number, limit?: number) { const reader = await this.repository.readEsvFile(filePath); let lineCount = 0; let header: string[] = []; const records: EsvRowType[] = []; for await (const line of reader) { if (lineCount === 0) { header = this.repository .splitEsvLine(line, separator) .map((h) => this.repository.normalizeValue(h) as string); lineCount++; continue; } if (skip && skip > 0) { skip--; continue; } if (!line) continue; if (limit && lineCount > limit) break; lineCount++; const record = this.repository.parseEsvLine(line, header, separator); records.push(record); } reader.close(); return records; } } ``` --- ### 🔍 Manual Read with Filters ```ts import { ManualEsv, type EsvRowType, type EsvFilterType, Operations } from "emoji-separated-values"; export class FilterEsv { repository: ManualEsv; operations: Operations; constructor(repository: ManualEsv) { this.repository = repository; this.operations = new Operations(); } async execute( filePath: string, separator: string, skip?: number, limit?: number, filters?: EsvFilterType[] ) { const reader = await this.repository.readEsvFile(filePath); let lineCount = 0; let header: string[] = []; const records: EsvRowType[] = []; for await (const line of reader) { if (lineCount === 0) { header = this.repository .splitEsvLine(line, separator) .map((h) => this.repository.normalizeValue(h) as string); lineCount++; continue; } if (!line) continue; const record = this.repository.parseEsvLine(line, header, separator); if (!this.operations.filterRow(record, filters)) continue; if (skip && skip > 0) { skip--; continue; } if (limit && lineCount > limit) break; records.push(record); lineCount++; } reader.close(); return records; } } ``` --- ### 🧩 Manual Update ```ts import { ManualEsv, type EsvRowType, type EsvFilterType, Operations } from "emoji-separated-values"; export class UpdateEsv { repository: ManualEsv; operations: Operations; tempPath: string; constructor(repository: ManualEsv) { this.repository = repository; this.operations = new Operations(); this.tempPath = ""; } async execute(filePath: string, separator: string, newData: EsvRowType, filters: EsvFilterType[]) { this.tempPath = `${filePath}.tmp`; const tmpExists = await this.repository.fileExists(this.tempPath); if (tmpExists) this.repository.deleteFile(this.tempPath); const reader = await this.repository.readEsvFile(filePath); const writer = this.repository.getFileWriteStream(this.tempPath); let lineCount = 0; let header: string[] = []; let updated = false; for await (const line of reader) { if (lineCount === 0) { header = this.repository .splitEsvLine(line, separator) .map((h) => this.repository.normalizeValue(h) as string); writer.write(this.formatHeader(header, separator)); lineCount++; continue; } if (!line) continue; const record = this.repository.parseEsvLine(line, header, separator); if (!this.operations.filterRow(record, filters)) { writer.write(line + "\n"); continue; } const updatedLine = this.updateLine(record, newData, separator); writer.write(updatedLine); updated = true; lineCount++; } reader.close(); writer.end(); return new Promise<void>((resolve, reject) => { writer.on("finish", () => { if (!updated) { this.repository.deleteFile(this.tempPath); return reject(new Error("No record updated")); } console.log("ESV file updated successfully"); this.repository.renameFile(this.tempPath, filePath); resolve(); }); writer.on("error", reject); }); } updateLine(record: EsvRowType, newData: EsvRowType, separator: string) { Object.entries(newData).forEach(([key, value]) => (record[key] = value)); return this.formatLine(record, separator); } formatLine(record: EsvRowType, separator: string) { const values = Object.values(record).map((value) => this.repository.escapeField(String(value)) ); return values.join(separator) + "\n"; } formatHeader(header: string[], separator: string) { return header.map((h) => this.repository.escapeField(h)).join(separator) + "\n"; } } ``` --- ### 💀 Manual Delete ```ts import { ManualEsv, type EsvFilterType, Operations } from "emoji-separated-values"; export class DeleteEsv { repository: ManualEsv; operations: Operations; tempPath: string; constructor(repository: ManualEsv) { this.repository = repository; this.operations = new Operations(); this.tempPath = ""; } async execute(filePath: string, separator: string, filters: EsvFilterType[]) { this.tempPath = `${filePath}.tmp`; const tmpExists = await this.repository.fileExists(this.tempPath); if (tmpExists) this.repository.deleteFile(this.tempPath); const reader = await this.repository.readEsvFile(filePath); const writer = this.repository.getFileWriteStream(this.tempPath); let lineCount = 0; let header: string[] = []; let deleted = false; for await (const line of reader) { if (lineCount === 0) { header = this.repository .splitEsvLine(line, separator) .map((h) => this.repository.normalizeValue(h) as string); writer.write(this.formatHeader(header, separator)); lineCount++; continue; } if (!line) continue; const record = this.repository.parseEsvLine(line, header, separator); lineCount++; if (this.operations.filterRow(record, filters)) { deleted = true; } else { writer.write(line + "\n"); } } reader.close(); writer.end(); return new Promise<void>((resolve, reject) => { writer.on("finish", () => { if (!deleted) { this.repository.deleteFile(this.tempPath); return reject(new Error("No record deleted")); } console.log("ESV record deleted successfully"); this.repository.renameFile(this.tempPath, filePath); resolve(); }); writer.on("error", reject); }); } formatHeader(header: string[], separator: string) { return header.map((h) => this.repository.escapeField(h)).join(separator) + "\n"; } } ```