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

ripply

v0.0.3

Published

Real-time incremental map-reduce indexes for SQLite and Postgres — RavenDB-style pre-computed aggregates as a tiny standalone library

Readme

Ripply 🌊

Real-time incremental map-reduce indexes for SQLite and Postgres.

Pre-computed, always-fresh aggregates — counts by status, revenue by month, workload by assignee — maintained incrementally as your rows change. Inserts, updates, and deletes. Query time is a key lookup, never a GROUP BY scan.

Inspired by RavenDB's map-reduce indexes; built as a small standalone TypeScript library for Bun/Node. No framework, no server, no lock-in.

import { createRipply } from "ripply";
import { sqliteSource, sqliteStore } from "ripply/sqlite";

const ripply = createRipply({
  source: sqliteSource({ db, collections: { work_orders: { pk: ["id"] } } }),
  store: sqliteStore({ db }),
});

ripply.defineIndex("countByStatus", {
  collection: "work_orders",
  map: (wo) => ({ status: wo.status, count: 1 }),
  reduce: { groupBy: ["status"], aggregate: { count: "sum" } },
});

await ripply.start(); // installs change capture, processes incrementally

await ripply.index("countByStatus").all();
// [{ status: "pending", count: 49 }, { status: "completed", count: 20 }, ...]

// Update a row → the affected groups update in real time. Delete one → the
// tally goes down. No rescans.

The tally is a real table. Every index materializes as ripply_<name> with your groupBy fields and aggregates as plain columns — query it with any SQL client, no Ripply required, and declare ordinary SQL indexes on it:

SELECT tech, revenue, jobs FROM ripply_revenueByTech ORDER BY revenue DESC;

And a tally can feed another index. Cascading rollups (RavenDB 4's OutputReduceToCollection), incremental all the way down:

ripply.defineIndex("revenueByMonth", {
  collection: "ripply_revenueByDay", // ← another index's output table
  map: (day) => ({ month: day.day.slice(0, 7), revenue: day.revenue }),
  reduce: { groupBy: ["month"], aggregate: { revenue: "sum" } },
});

Live demo

bun examples/work-orders/server.ts   # → http://localhost:4242

ripply live dashboard

Random inserts/updates/deletes ripple through four indexes (including a day→month cascade) in ~2ms per drain, with the changelog auto-pruned to zero.

How it works

  1. Capture — SQLite: generated triggers append to a changelog table. Postgres: trigger-outbox (default) or logical-decoding CDC (opt-in).
  2. Map — each changed row is mapped to zero-or-more index entries.
  3. Reconcile — the row's previous contribution is read from Ripply's own entries table and reconciled toward the new one. Linear aggregates (sum/count/avg) apply O(1) deltas; non-linear (min/max/distinct) re-reduce just the affected group from its entries.
  4. Drill down — the intermediate entries are queryable: not just "49 pending," but which 49.

Reprocessing is idempotent by construction, so crashes and replays never corrupt an index. When source and store share a database, updates are exactly-once and transactional.

Status

🚧 Early development, moving fast. Phases 0, 1, and 2 complete:

  • Backend-free engine proven by property-based invariant tests (incremental result == full rebuild over random op sequences), idempotent replay, crash-safety, and map-versioning tests
  • SQLite adapter — generated trigger capture, transactional store, materialized tally tables, cascading indexes
  • Postgres adapter — one generic trigger-outbox capture function, typed materialized tally tables (columnTypes overrides), and snapshot-windowed cursors: polling that provably never skips a transaction that commits out of BIGSERIAL order (a held-transaction test and a concurrent-writers stress test enforce it). Zero dependencies — built on Bun's native Bun.sql. Works great on hosted Postgres (Neon): no replication slots, no WAL retention, survives connection pooling.
  • The identical invariant suite runs against the in-memory reference, real SQLite, and real Postgres — 60 tests green
  • Verified against RavenDB itself: a production RavenDB map-reduce index ported to Ripply over 452 live documents produced exactly matching reduce groups (scripts/ravendb-oracle.ts)

Next: ergonomics (compiled build, drill-down polish) and opt-in logical-decoding CDC. See PLAN.md.

⚠️ Published as TypeScript source (Bun-first) while pre-1.0; a compiled build lands with the ergonomics phase.

License

MIT © Claudia


Built with 💙 by Michael & Claudia