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

allez-orm

v1.2.0

Published

AllezORM: lightweight browser SQLite ORM (sql.js) + schema generator CLI

Downloads

97

Readme

AllezORM

Ship a real SQLite database — in the browser — in under five minutes. Zero servers. Zero migrations. Zero "your data is gone because the user refreshed."

Built for the next era of software: client-first apps, AI-generated code, and teams that ship faster than infrastructure can keep up.

npm license tests node


Why this exists (read this part)

You've felt all three of these. Recently.

  1. Your "simple" app needs a real query layer in the browser, and you're one library choice away from localStorage.setItem(JSON.stringify(...)) creeping into production. Again.
  2. Your AI agent confidently rewrote your schema on Tuesday and you didn't notice until staging. The spec said one thing. The code did another. Nobody owned the diff.
  3. You shipped a "client-side prototype" that became the product, and now you're trying to graft IndexedDB persistence, foreign keys, and migrations onto something that was never designed for them.

AllezORM is a small, sharp answer to all three.

Open it, point it at a schema, and you have a real SQLite database running in the browser tab — persisted to IndexedDB on every write, queried with prepared statements, protected by inline foreign keys. The schema generator writes idiomatic, version-pinned files that your agents cannot silently drift away from. Because every file is anchored — by SHA-256 — back to the spec it came from.

That's the whole pitch. The rest of this README shows you the receipts.


What you get in 60 seconds

npm install allez-orm
// app.js
import { AllezORM } from "allez-orm";
import users from "./schemas/users.schema.js";
import posts from "./schemas/posts.schema.js";

const orm = await AllezORM.init({ dbName: "myapp.db", schemas: [users, posts] });

// Real prepared statements. Real foreign keys. Real persistence.
await orm.table("users").upsert({ id: 1, email: "[email protected]", display_name: "Ada" });
await orm.table("posts").insert({ user_id: 1, title: "Hello world" });

const recent = await orm.query(
  "SELECT u.email, p.title FROM posts p JOIN users u ON u.id = p.user_id ORDER BY p.id DESC LIMIT 10"
);

That's it. The database is alive in the tab. Refresh the page — it's still there. Open DevTools → Application → IndexedDB → myapp.db. The whole SQLite file is sitting there as a Uint8Array.

No backend. No service worker. No build step. No "but does it work in Safari" anxiety.


The five things that make it different

1. Spec-anchored schemas your agents can't silently break

Here's what a generated schema file looks like:

// users.schema.js (generated by allez-orm)
// DO NOT EDIT — regenerate by editing the spec, then running:
//   allez-orm from-json ../spec.json
// SPEC ../spec.json
// SPEC_SHA256 0f1ab429adc01ddeaa093a4b18a87836547f437e1bc31f5d1babddf9935c47dc
// TABLE_SHA256 531b42a728116ec1332467a5fb83b93f230afaf5ea75f4b4019ef2cf5d997adc
const usersSchema = {
  table: "users",
  version: 1,
  createSQL: `
CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT UNIQUE NOT NULL,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  deleted_at TEXT
);`
};
export default usersSchema;

Every generated file carries two cryptographic anchors back to the spec it came from. When an agent (or a junior dev, or future-you at 2am) opens this file, the very first thing they see is "DO NOT EDIT — regenerate from the spec."

Don't trust the comment? Don't have to. Run drift detection:

$ allez-orm verify schemas.spec.json --dir=./schemas
✔ verify: 4 table(s) match spec schemas.spec.json

Hand-edit a generated file:

$ allez-orm verify schemas.spec.json --dir=./schemas
✗ Drift detected in 1 file(s):
  - ./schemas/users.schema.js

To resync, run:  allez-orm from-json schemas.spec.json --dir=./schemas -f

Wire that into CI. Wire it into your pre-commit hook. Your agents do not get to quietly mutate your contract anymore.

You also get an AGENTS.md auto-emitted next to the schemas — picked up natively by Claude Code, Cursor, and every IDE that respects the convention — telling future agents exactly where the source of truth lives and how to regenerate.

This is the part Luca asked about. Now it ships.

2. A SQLite database in the browser, persisted, with FKs that actually enforce

This is not a Promise-flavored wrapper around localStorage. AllezORM runs real SQLite via sql.js (WASM) — the same engine in your phone, your browser, every plane in the air, and most cars sold this decade.

  • PRAGMA foreign_keys = ON — and stays on, even across saves. (We found and fixed a subtle sql.js bug where db.export() resets connection PRAGMAs. Now they don't.)
  • Prepared statements for every helper — no string concatenation, no injection holes. SQL identifier validation rejects anything that doesn't match /^[A-Za-z_][A-Za-z0-9_]*$/, and we test it against the OWASP injection corpus.
  • Debounced auto-save to IndexedDB after every write. Configurable.
  • Versioned onUpgrade(db, from, to) hook per schema, so you can ship v2 of your app and migrate users in-place.

3. A CLI that generates schemas the way you'd write them by hand

# Ad-hoc — one table, fast:
allez-orm create table users email:text!+ display_name:text! --stamps

# Or declarative — the source-of-truth path:
allez-orm from-json schemas.spec.json

Compact field syntax that fits in your head: name:type with flag suffixes (! = NOT NULL, + = UNIQUE, ->target = inline foreign key, --onDelete= for every FK at once). One mental model. No DSL to memorize.

Spec a referenced table that doesn't exist yet? The CLI auto-generates an ID-only stub for it so your DDL compiles immediately and you can fill it in later.

4. AllezORM Studio — a visual workbench for your in-browser DB

npm run dev opens Studio: a clean, single-page workbench with searchable tables, sortable columns, a SQL scratchpad, and a Create table / Add column dialog that calls the CLI for you and live-reloads the ORM.

It's the kind of tool you wish you had during the first week of a new project. Now you do.

5. Tested with the seriousness of a database

This repo currently ships with 67 automated tests across six suites:

| Suite | What it covers | |---|---| | test-cli.mjs | CLI smoke tests, force/overwrite, FK stubs | | test-security.mjs | SQL identifier injection corpus, prepared-statement integration | | test-spec-anchor.mjs | Spec-anchor headers, AGENTS.md, drift detection | | test-generator-thorough.mjs | 21 edge-case generator scenarios, real sql.js compile-and-run, idempotency | | test-fk-persistence.mjs | Regression: FK pragma survives db.export() and repeated saves | | test-e2e-studio.mjs | Playwright E2E: full Studio flow including IndexedDB reload, FK violations |

npm test       # everything except the browser E2E
npm run test:e2e  # browser E2E (requires playwright + chromium)
npm run test:all  # both

Install

npm install allez-orm

You don't need a bundler. You don't need a framework. You don't need a server. The package is pure ESM, runs in browsers natively, and exposes a single default entry plus a CLI:

  • import { AllezORM } from "allez-orm" — the ORM
  • npx allez-orm … — the schema generator
  • import "allez-orm/cli" — programmatic CLI access

WASM is loaded from sql.js's CDN by default. Want to self-host the WASM? Pass wasmLocateFile to AllezORM.init.


Quickstart: from zero to a real database in 5 commands

# 1. Init a project
npm init -y && npm install allez-orm

# 2. Generate a schema declaratively
cat > schemas.spec.json <<'JSON'
{
  "outDir": "./schemas",
  "defaultOnDelete": "cascade",
  "tables": [
    { "name": "users", "stamps": true,
      "fields": [
        { "name": "email", "type": "text", "unique": true, "notnull": true },
        { "name": "display_name", "type": "text", "notnull": true }
      ] },
    { "name": "posts", "stamps": true,
      "fields": [
        { "name": "title", "type": "text", "notnull": true },
        { "name": "user_id", "type": "integer", "fk": { "table": "users" } }
      ] }
  ]
}
JSON

# 3. Generate the schema files (and AGENTS.md)
npx allez-orm from-json schemas.spec.json

# 4. Verify alignment any time you like
npx allez-orm verify schemas.spec.json

# 5. Use it from your app
echo 'import { AllezORM } from "allez-orm";
import users from "./schemas/users.schema.js";
import posts from "./schemas/posts.schema.js";
const orm = await AllezORM.init({ schemas: [users, posts] });
await orm.table("users").upsert({ id: 1, email: "[email protected]", display_name: "Ada", created_at: new Date().toISOString(), updated_at: new Date().toISOString() });
console.log(await orm.query("SELECT * FROM users"));
' > app.mjs

You now have a typed, persistent, foreign-key-enforced database running in any modern browser, generated from a single source-of-truth file.


The CLI

allez-orm create table <name> [fields...] [options]
allez-orm from-json <config.json> [--dir=<outDir>] [-f|--force]
allez-orm verify    <config.json> [--dir=<outDir>]
allez-orm --print-json-schema

Field syntax (CLI form)

col[:type][flags][->target]

Examples:
  email:text!+              email TEXT UNIQUE NOT NULL
  user_id:integer->users    user_id INTEGER REFERENCES users(id)
  parent_id:integer->nodes  self-referencing FK; no stub generated
  body                      body TEXT  (type defaults to TEXT)

Flags: ! = NOT NULL, + = UNIQUE. Type aliases (case-insensitive): text|string, int|integer, real|float, numeric|number, blob, bool.

Options

| Flag | What it does | |---|---| | --dir=<outDir> | Output directory (defaults to schemas_cli, or outDir from spec) | | --stamps | Add created_at (NOT NULL), updated_at (NOT NULL), deleted_at (nullable) | | --onDelete=<mode> | Apply ON DELETE behavior to every FK: cascade\|restrict\|setnull\|noaction | | -f, --force | Overwrite existing files (also: ALLEZ_FORCE=1) | | --help | Show help |

from-json config shape

Print the canonical JSON Schema:

npx allez-orm --print-json-schema

The config supports both rich field objects and compact string tokens in the same array — pick whichever fits the table:

{
  "tables": [
    { "name": "items", "fields": ["sku:text!+", "qty:integer!"] },
    { "name": "memberships", "fields": [
      { "name": "user_id", "type": "integer", "fk": { "table": "users" } },
      { "name": "org_id",  "type": "integer", "fk": { "table": "orgs"  } }
    ]}
  ]
}

verify — your CI safety net

verify re-runs generation in memory and byte-compares against what's on disk. It exits non-zero on:

  • Drift — a generated file was hand-edited.
  • Stale files — the spec moved forward but files weren't regenerated.
  • Missing files — the spec references a table whose file isn't there.
# .github/workflows/ci.yml
- run: npx allez-orm verify schemas.spec.json

One line. No more 2am Slack messages about schema drift.


The ORM API

Init

const orm = await AllezORM.init({
  dbName: "myapp.db",          // IndexedDB key; defaults to "allez.db"
  autoSaveMs: 1500,            // debounce window for IndexedDB writes
  wasmLocateFile: f => `/wasm/${f}`,  // self-host sql-wasm.wasm
  schemas: [usersSchema, postsSchema]
});

Per-table helpers (parameterized, FK-aware)

const users = orm.table("users");

await users.insert({ email: "[email protected]", display_name: "Ada" });
await users.upsert({ id: 1, email: "[email protected]", display_name: "Ada" });
await users.update(1, { display_name: "Ada Lovelace" });
await users.findById(1);                            // single row
await users.searchLike("ada", ["display_name"]);    // LIKE %ada% across columns
await users.deleteSoft(1);                          // sets deleted_at / deletedAt
await users.remove(1);                              // hard delete

Raw SQL — when you need it

const rows = await orm.query("SELECT * FROM users WHERE id IN (?, ?)", [1, 2]);
const one = await orm.get("SELECT * FROM users LIMIT 1");
await orm.execute("UPDATE users SET display_name = ? WHERE id = ?", ["X", 1]);

Schema versioning

const usersSchema = {
  table: "users",
  version: 2,
  createSQL: `CREATE TABLE IF NOT EXISTS users (...);`,
  async onUpgrade(db, from, to) {
    if (from < 2) db.exec(`ALTER TABLE users ADD COLUMN avatar_url TEXT`);
  }
};

Manual save

await orm.saveNow(); // force a write to IndexedDB right now

Security

This package treats SQL identifiers as an attack surface. Every table and column name passed to the table() helpers goes through safeIdent(), which validates against a strict character class and rejects every payload in the OWASP injection corpus. You can audit the test for yourself at tests/test-security.mjs (13 assertions).

All values are bound via prepared statements. There is no string concatenation of user input into SQL anywhere in the runtime.

If you find a security issue, please open a private advisory in the GitHub repo. We respond.


What you avoid by using this

This is the part that's hard to feel until you've been burned. So picture it:

  • No "the IndexedDB schema is one thing, the SQL schema is another" debugging at midnight. One file is the truth. One command verifies it.
  • No agents quietly mutating your contract. The header on every generated file is unmissable. The verify command is automatic.
  • No localStorage.setItem(JSON.stringify(...)) sprawling across your codebase as you slowly reinvent indexes. The Studio is the indexed database you wanted on day one.
  • No "we'll add a real backend later" technical debt. You may discover you never need one. Plenty of apps don't.
  • No surprise FK behavior. We tested the pragma persistence so you don't have to.

Roadmap & status

AllezORM is production-shape for client-only apps: tests gate every release, the API surface is small and stable, and the schema generator's output format is now SHA-anchored (so we'll bump major if we ever change it).

Coming soon:

  • WAL-style append journaling for crash-safe writes
  • Optional Web Worker mode for off-main-thread queries
  • An allez-orm sync command for two-way reconciliation with a remote SQL database (so the same schema works client-side and server-side)

Want one of those sooner? Open an issue and say so.


Contributing

git clone https://github.com/AllezMarketing/allez-orm.git
cd allez-orm
npm install
npm test           # unit + integration
npx playwright install chromium
npm run test:e2e   # browser E2E
npm run dev        # launch Studio at http://localhost:5174

PRs that touch generator output must keep the existing schema files byte-stable (or bump version and update the SHA anchors deliberately). npm test will tell you immediately.


License

ISC © Allez Marketing LLC


One last thing

If you read this far, you already know what to do. The install is one command. The first table is one config. The verify is one CI line.

The friction between you and a working database is smaller than it has ever been. Use the next ten minutes well.

npm install allez-orm