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

reposql

v0.1.5

Published

A Git-native database that lives in your repository

Readme

RepoSQL

A Git-native database. Your data lives as plain-text XML files inside your repository, compiles to SQLite on demand, and synchronizes through normal git push and git pull workflows.

No server. No second remote. No separate sync strategy.


The Problem

For small teams, the code lives in Git — versioned, collaborative, mergeable. But the database lives somewhere else. It needs a server, credentials, and a separate workflow. None of the existing options let you do the thing that feels obvious: git pull and have your database updated.

  • SQLite in Git — binary file, not mergeable, conflicts destroy data
  • SQL dump files — manually maintained, easy to forget
  • Hosted databases — external dependency, costs money, breaks the "everything in one repo" rule

How It Works

RepoSQL has three layers:

  1. A flat folder of XML files — your source of truth, lives in Git, human-readable
  2. A build step — reads those files and compiles them into a local SQLite database
  3. Git hooks — automate the compile step so it's invisible

Your data is plain text. Git can diff it, merge it, and version it like any other file. SQLite is just the runtime view of that data — rebuilt on demand, never committed.


Install

npx reposql init

No global install required. Run this once in any git repository.


Quick Start

# Set up RepoSQL in an existing git repo
npx reposql init

# Edit db/schema.xml to define your tables, then build
npx reposql build

# Start of day — get teammates' changes
git pull
npx reposql sync

# End of day — share your changes
npx reposql push
git add db/records/
git commit -m "what you did"
git push

Repository Layout

my-project/
├── db/
│   ├── schema.xml          ← table definitions
│   └── records/            ← one XML file per record (committed)
│       ├── 01ARZ3NDEK...xml
│       └── 01BX5ZZKBK...xml
├── .reposql/
│   ├── config.json         ← source definitions (committed)
│   └── merge-driver.js     ← last-write-wins conflict resolution
├── .gitattributes          ← registers the merge driver
├── .gitignore              ← ignores app.db and state.json
└── app.db                  ← SQLite database (local only, never committed)

Schema

Define your tables in db/schema.xml:

<schema>
  <table name="users">
    <column name="name"  type="TEXT" />
    <column name="email" type="TEXT" unique="true" />
    <column name="role"  type="TEXT" default="member" />
  </table>

  <table name="products">
    <column name="name"  type="TEXT" />
    <column name="price" type="REAL" />
    <column name="stock" type="INTEGER" default="0" />
  </table>
</schema>

Supported column attributes: type, unique, default, not_null.


Record Format

Every piece of data is stored as a single XML file named by its ULID:

<!-- db/records/01ARZ3NDEKTSV4RRFFQ69G5FAV.xml -->
<record>
  <meta>
    <table>users</table>
    <operation>insert</operation>
  </meta>
  <data>
    <name>Alice</name>
    <email>[email protected]</email>
    <role>admin</role>
  </data>
</record>

Updates and deletes are new files that reference the original:

<record>
  <meta>
    <table>users</table>
    <operation>update</operation>
    <target>01ARZ3NDEKTSV4RRFFQ69G5FAV</target>
  </meta>
  <data>
    <email>[email protected]</email>
    <name>Alice</name>
    <role>admin</role>
  </data>
</record>
<record>
  <meta>
    <table>users</table>
    <operation>delete</operation>
    <target>01ARZ3NDEKTSV4RRFFQ69G5FAV</target>
  </meta>
</record>

Write API

Use the write API so your app doesn't have to create XML files manually:

import { RepoSQL } from 'reposql'

const db = new RepoSQL('./db')

// Insert — returns the new row's ULID
const id = db.insert('users', {
  name: 'Alice',
  email: '[email protected]',
  role: 'admin',
})

// Update
db.update('users', id, { email: '[email protected]', name: 'Alice', role: 'admin' })

// Delete
db.delete('users', id)

// Query (standard SQL via better-sqlite3)
const users = db.query('SELECT * FROM users WHERE role = ?', 'admin')
const alice  = db.queryOne('SELECT * FROM users WHERE email = ?', '[email protected]')

// Access the raw better-sqlite3 instance for advanced queries
db.db.prepare('SELECT COUNT(*) as n FROM users').get()

db.close()

Each call writes an XML record file and updates the local SQLite database immediately.

Your app can also query SQLite directly without the write API:

// Node.js
import Database from 'better-sqlite3'
const db = new Database('./app.db')
const users = db.prepare('SELECT * FROM users').all()
# Python
import sqlite3
conn = sqlite3.connect('./app.db')
users = conn.execute('SELECT * FROM users').fetchall()

CLI Reference

| Command | Description | |---------|-------------| | npx reposql init | Set up RepoSQL in a git repository | | npx reposql build | Rebuild SQLite from scratch | | npx reposql sync | Apply new XML records to SQLite (run after git pull) | | npx reposql push | Export SQLite changes as XML record files (run before git push) | | npx reposql status | Show unsynced records and local SQLite changes | | npx reposql log | Human-readable history of all data changes | | npx reposql migrate | Apply schema.xml changes to SQLite | | npx reposql snapshot | Collapse records into a snapshot and archive history |

Options

npx reposql build    --quiet
npx reposql sync     --quiet
npx reposql push     --dry-run
npx reposql snapshot --dry-run

npx reposql log --table users
npx reposql log --author [email protected]
npx reposql log --since 2026-01-01
npx reposql log --limit 50

# Multi-source: target a specific database
npx reposql build  --source products
npx reposql sync   --source logs
npx reposql status --source users

Multiple Databases

A single repository can contain multiple independent databases — each with its own schema, records directory, and SQLite file.

Setup

# First database (default)
npx reposql init

# Additional databases
npx reposql init --db analytics/data.sqlite --name analytics
npx reposql init --db audit/logs.sqlite     --name audit

This registers each source in .reposql/config.json:

{
  "sources": {
    "default":   { "dbDir": "db",        "sqliteFile": "app.db"                  },
    "analytics": { "dbDir": "analytics", "sqliteFile": "analytics/data.sqlite"   },
    "audit":     { "dbDir": "audit",     "sqliteFile": "audit/logs.sqlite"        }
  }
}

Running commands across sources

# All commands run for ALL sources by default
npx reposql sync
npx reposql build
npx reposql status

# Or target a specific source
npx reposql sync   --source analytics
npx reposql build  --source audit
npx reposql status --source default

Each source maintains its own sync cursor and baseline independently. State is stored per-source in .reposql/state.json (gitignored).


Day-to-Day Workflow

# Start of day
git pull
npx reposql sync        # apply teammates' record files to your SQLite

# ... build your feature, your app reads/writes SQLite normally ...

# End of day
npx reposql push        # export your SQLite changes as XML record files
git add db/records/
git commit -m "add user alice, update product price"
git push

Two extra commands. Everything else stays the same.


Conflict Resolution

Because every record is its own uniquely-named file, two teammates can never conflict on the same file during normal use. You never modify existing files — you always append new ones.

In the rare case where the same file is modified in both branches, the registered merge driver resolves it automatically using last-write-wins (higher ULID = newer = kept). The developer never sees a conflict.

# .gitattributes (written by `reposql init`)
db/records/*.xml merge=reposql-driver

Snapshots

When db/records/ grows large, collapse history into a single snapshot:

npx reposql snapshot

This writes the current state to db/snapshot.xml, archives old record files to db/archive/YYYY-MM/, and clears db/records/. Future builds load the snapshot first, then replay any new records on top. Teammates get the snapshot on their next git pull.


Schema Changes

To add a column, update db/schema.xml and run:

npx reposql migrate

This applies ALTER TABLE ADD COLUMN for new columns and validates existing records. Column drops require a full npx reposql build.


Known Limitations

  • Not for high write concurrency — designed for small teams, not APIs with thousands of writes per second
  • Not for large datasets — comfortable up to ~50,000 records before needing the snapshot strategy
  • No real-time sync — data syncs on git pull, not live. This is intentional.
  • Last-write-wins is lossy — if two teammates update the same record simultaneously, one change is lost. Acceptable for small teams, wrong for financial or critical data.

Comparison

| | RepoSQL | SQLite in Git | Dolt | Hosted DB | |-----------------------|:-------:|:-------------:|:----:|:---------:| | Lives in Git repo | ✓ | ✓ | ✓ | | | One remote (GitHub) | ✓ | ✓ | | | | Mergeable data | ✓ | | ✓ | | | SQL queries | ✓ | ✓ | ✓ | ✓ | | No server required | ✓ | ✓ | ✓ | | | Full audit history | ✓ | | ✓ | | | Human-readable diffs | ✓ | | ✓ | | | Small team friendly | ✓ | ✓ | ⚠ | ⚠ |


License

MIT