v0.1.0 — early development, not yet on npm

Find SQL bugs
before your users do

Property-based testing for PostgreSQL. Generates random valid datasets that respect your schema — then tries to break your queries.

$ npm install sqlproof

⚠ Not yet published — follow the repo for updates

Get Started → View on GitHub

// why sqlproof

Your queries work on your fixtures.
Do they work on real data?

Hand-crafted test data misses edge cases. SqlProof generates thousands of valid random datasets and finds the one that breaks your invariants.

Schema-aware generation

Respects foreign keys, CHECK constraints, UNIQUE, NOT NULL, and enum types automatically. No invalid data, no constraint violations.

Minimal counterexamples

When a property fails, fast-check shrinks the dataset to the smallest possible example — so you fix the bug, not hunt for it.

Zero infrastructure

Spins up a disposable Postgres via testcontainers. No external DB needed. Each run gets its own isolated schema — fast and clean.

Works with your test runner

Drop it into Vitest or Jest. Call proof.check() from an it() block. No new tools to learn.

Four steps, zero boilerplate

1

Parse schema

Reads your SQL file or introspects a live Postgres DB to extract tables, types, FKs, and constraints.

2

Generate data

Creates random valid rows for every table in FK-dependency order. Respects all constraints.

3

Insert & run

Inserts into an isolated Postgres schema, runs your property, then drops the schema. Repeats up to N times.

4

Report

On failure, shrinks to the minimal counterexample and reports it with a reproducible seed.

// example

See it in action

orders.test.ts
import { SqlProof } from 'sqlproof'; const proof = await SqlProof.connect({ schemaFile: './schema.sql' }); await proof.invariant('no orphan line items', { generate: { customers: 10, orders: 50, line_items: 200 }, query: `SELECT li.id FROM line_items li LEFT JOIN orders o ON li.order_id = o.id WHERE o.id IS NULL`, expectEmpty: true, }); // ✗ Property failed: "no orphan line items" // After 3 runs — seed: 1708891234 // Reproduce: proof.invariant('...', { ..., seed: 1708891234 })