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

pointy-ts

v1.0.0

Published

Type-safe, chainable file and data manipulation library

Readme

🎯 Pointy


🤔 Why Pointy Exists

Manipulating config files, spreadsheets, and environment files is boring boilerplate. Every project has the same dance:

// Read file → parse → mutate → stringify → write
const fs = require('fs');
const raw = fs.readFileSync('config.json', 'utf-8');
const data = JSON.parse(raw);
data.database.host = process.env.DB_HOST;
data.database.ssl = true;
fs.writeFileSync('config.json', JSON.stringify(data, null, 2));

Five lines for one change. Multiply by every format you touch (JSON, CSV, YAML, Env…) and every project you ship. That's the pain Pointy solves.

Pointy wraps all that in a fluent, chainable builder that feels like writing a sentence — not a script.


✨ The Pointy Way

import { PointyBuilder } from 'pointy';

// One chain. One intent. Done.
await PointyBuilder.fromFile('config.json')
  .asJson()
  .pointTo('database.host')
  .update(process.env.DB_HOST)
  .pointTo('database.ssl')
  .update(true)
  .apply();

No manual parse. No manual stringify. No fs.writeFileSync. Just describe what you want to change, and Pointy handles how.


🚦 Without vs With Pointy

JSON Config

Without Pointy — 9 lines of ceremony:

import { readFileSync, writeFileSync } from 'fs';

const raw = readFileSync('config.json', 'utf-8');
const data = JSON.parse(raw);

data.app.version = '1.0.1';
data.features.cache = true;
data.users[0].role = 'superadmin';

data.features.notifications = { enabled: true, channel: 'email' };

writeFileSync('config.json', JSON.stringify(data, null, 2));

With Pointy — 5 lines of intent:

await PointyBuilder.fromFile('config.json')
  .asJson()
  .pointTo('app.version').update('1.0.1')
  .pointTo('features.cache').update(true)
  .pointTo('users.0.role').update('superadmin')
  .pointTo('features').append('notifications', { enabled: true, channel: 'email' })
  .apply();

CSV Spreadsheet

Without Pointy — manual PapaParse + filter + rewrite:

import Papa from 'papaparse';
import { readFileSync, writeFileSync } from 'fs';

const raw = readFileSync('users.csv', 'utf-8');
const { data: rows } = Papa.parse(raw, { header: true });
rows.forEach((r) => { if (r.name === 'Alice') r.city = 'Boston'; });
writeFileSync('users.csv', Papa.unparse(rows));

With Pointy — declarative selection + mutation:

await PointyBuilder.fromFile('users.csv')
  .asCSV()
  .pointTo('name', 'Alice', 'all')
  .update({ city: 'Boston' })
  .apply();

Environment Variables

Without Pointy — ad-hoc parsing, manual joins:

import { readFileSync, writeFileSync } from 'fs';

const text = readFileSync('.env', 'utf-8');
const lines = text.split('\n');
const result = lines
  .map((line) =>
    line.startsWith('DB_HOST=') ? 'DB_HOST=remote.host' : line
  )
  .join('\n');
writeFileSync('.env', result);

With Pointy — key-based navigation:

await PointyBuilder.fromFile('.env')
  .asEnv()
  .pointTo('DB_HOST')
  .update('remote.host')
  .apply();

🚀 Quick Start

import { PointyBuilder } from 'pointy';

// --- JSON ---
await PointyBuilder.fromFile('config.json')
  .asJson()
  .pointTo('database.host')
  .update('localhost')
  .apply();

// --- CSV ---
const csv = PointyBuilder.fromCSV('name,age\ntom,30')
  .asCSV()
  .pointTo('name', 'tom', 'all')
  .update({ age: '31' })
  .apply();

// --- YAML → JSON conversion ---
const json = PointyBuilder.fromFile('config.yaml')
  .asYaml()
  .pointTo('app.debug')
  .update(false)
  .toJson()
  .apply();

// --- Text ---
const text = PointyBuilder.fromText('line1\nline2')
  .asText()
  .pointTo('0')
  .update('header')
  .apply();

📦 Supported Formats

| Format | Start | Coerce | Key Paths | |---|---|---|---| | JSON | fromJson() / fromFile('x.json') | asJson() | Dot-path (a.b.c, users.0.email) | | CSV | fromCSV() / fromFile('x.csv') | asCSV() | Row index (0), column match (name,Alice,all), START_ROWS.0.END_ROWS.10 | | Text | fromText() / fromFile('x.txt') | asText() | Line index (0), START, END, ranges | | Env | fromEnv() / fromFile('.env') | asEnv() | Key name (DB_HOST) | | YAML | fromYaml() / fromFile('x.yaml') | asYaml() | Dot-path (same as JSON) | | TOON | fromToon() / fromFile('x.toon') | asToon() | Nested section keys |


🔄 Format Conversions

Pointy can read one format and write another. Zero manual re-serialization.

// Read YAML, write JSON
const json = PointyBuilder.fromFile('config.yaml')
  .asYaml()
  .pointTo('server.port')
  .update(3000)
  .toJson()
  .outputToFile('config.json')
  .apply();

| From ↓ / To → | JSON | YAML | CSV | Text | Env | TOON | |---|---|---|---|---|---|---| | JSON | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | CSV | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | | Text | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Env | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | | YAML | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | | TOON | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |

(❌ = semantically unsafe, blocked at type level)


🧩 Plugin System

Register your own format at runtime:

import { PointyBuilder, FormatStrategy } from 'pointy';

class TomlStrategy implements FormatStrategy<'toml'> {
  readonly format = 'toml';
  parse(source) { /* ... */ }
  stringify(data) { /* ... */ }
  navigate(data, path) { /* ... */ }
  mutate(data, op) { /* ... */ }
}

PointyBuilder.registerFormat('toml', new TomlStrategy(), ['.toml']);

const result = PointyBuilder.fromFormat('toml', 'key = val')
  .asFormat('toml')
  .pointTo('key')
  .update('new')
  .apply();

⚡ Benchmarks

Run: npm run bench

| Operation | ops/sec | vs. Raw JSON | |---|---|---| | Raw JSON parse/stringify (baseline) | 1,818,000 | 1.0× | | JsonStrategy.parse + mutate | 1,339,000 | 0.74× | | Pointy: 1 update | 958,000 | 0.53× | | Pointy: 3 chained updates | 801,000 | 0.44× |

Pointy adds ~2× overhead over raw JSON — the price for immutability, type safety, and format abstraction but probably we can improve it in the future.


🛡️ Type Safety

Pointy enforces method sequencing at compile-time through a type-level state machine. Invalid chains are caught by TypeScript before your code runs:

// ❌ Compile-time errors
cannot mutate before coercion:      PointyBuilder.fromFile('x.json').update(1)
cannot coerce after active:          PointyBuilder.fromJson('{}').asJson().asCSV()
could not call terminal then mutate: PointyBuilder.fromObject({}).outputToFile('out').update(1)

Entry Points & Phases

| Entry Point | Initial Phase | Methods Available | |---|---|---| | fromFile | Coercing | asJson, asCSV, asText, asEnv, asYaml, asToon | | fromJson, fromCSV, fromText, fromEnv, fromYaml, fromToon | Coercing | Same as above | | fromObject | Active | pointTo, update, toJson, apply, outputToFile, … |

After any .asXxx() or .asFormat(), the builder transitions to Active, unlocking navigation, mutations, conversions, and terminal operations.


📁 Samples

Check the samples/ folder for real files you can run against:

// Update JSON config
await PointyBuilder.fromFile('samples/config.json')
  .asJson()
  .pointTo('database.host')
  .update('remote.host')
  .apply();

// Update CSV rows
await PointyBuilder.fromFile('samples/users.csv')
  .asCSV()
  .pointTo('name', 'Alice', 'all')
  .update({ city: 'Remote' })
  .apply();

// Update env variables
await PointyBuilder.fromFile('samples/.env')
  .asEnv()
  .pointTo('DEBUG')
  .update('true')
  .apply();

📜 License

Apache 2.0

This license gives you the same broad freedoms as MIT (use, modify, distribute, even commercially), with the addition of a patent grant. Most importantly: if you use this library, you must include attribution — a copyright notice and a copy of the license — so anyone who uses your software knows Pointy is part of the picture.

MIT is simpler but offers no patent protection and no formal attribution requirement. We chose Apache 2.0 because it protects both users and contributors, and it ensures credit is given where it's due.