npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@paulof25/emoji-separated-values

v1.0.0

Published

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?

Readme

😎 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

npm install emoji-separated-values

or with yarn:

yarn add emoji-separated-values

💻 Usage

🟢 Reading — Simple Example

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

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

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

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

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)

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

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

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

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";
  }
}