Six runnable projects, small to large, each showing how complex, reproducible
data comes from very little fixture code. Every example uses Bun's built-in
bun:sqlite, so they run with zero native setup:
cd examples/<name>
bun install
bun run seed # seed and print a report
bun run verify # seed twice with the same seed → assert byte-identical data| # | Example | Size | Fixture LOC | Rows | Highlights |
|---|---|---|---|---|---|
| 1 | basic-blog | small | ~25 | ~125 | count+data, keyed rows, ref().random() |
| 2 | ecommerce-store | mid | ~70 | ~350 | FK chains + topo-order, ref().where(), multi-file fixtures |
| 3 | saas-multitenant | mid-large | ~80 | ~125 | tenant scoping, composite-PK join table |
| 4 | social-network | large | ~90 | ~1100 | 8 namespaces, nested refs in JSON, threaded comments |
| 5 | analytics-events | mid-large | ~60 | 50–5050 | --scenario datasets, high volume |
| 6 | testing-with-drizzle-saat | practical | ~25 | per-test | deterministic fixtures in a bun test suite |
// social-network/drizzle-saat/content.ts — 120 posts, each with a nested ref in JSON
{
table: posts, namespace: "post", count: 120,
data: () => ({
authorId: ref("user").random(),
body: faker.lorem.paragraph(),
metadata: { lang: "en", pinnedBy: ref("user", "ada") }, // ← resolved to an id
createdAt: faker.date.recent({ days: 30, refDate: "2026-06-01" }).toISOString(),
}),
}That's the whole posts dataset. drizzle-saat resolves the refs (top-level and nested), orders inserts by dependency, and produces the exact same rows for a given seed.
Every example is wired for full fixture type-safety. After bun install (which
generates .drizzle-saat/types.d.ts via a postinstall hook), run:
bun run typecheck # regenerates types, then `tsc --noEmit`This catches, at compile time:
ref("dwad") // ✗ "dwad" is not a known namespace
priority: "URGENT" // ✗ not in the column's enum
title: ref("user").random() // ✗ a number ref can't fill a string column
data: () => ({ title: "t" }) // ✗ missing required NOT NULL columnsEach example's
tsconfig.jsonlists".drizzle-saat/types.d.ts"explicitly — TypeScript'sincludeglobs skip dot-directories, so".drizzle-saat"alone is silently ignored and namespace checking won't work. (See the README's "Type safety" section.)
A fixed seed makes runs byte-identical — except for fakers anchored to the
clock. Always pass a fixed refDate to faker.date.* helpers, or those columns
will differ every run. See ecommerce-store for the details.
Each example uses bun:sqlite purely so it runs anywhere. In your own project
you don't write a runner — point drizzle.config.ts at your database and use the
CLI (npx drizzle-saat, npx drizzle-saat --scenario load, npx drizzle-saat --seed 7). The fixtures
are identical; switch dialect to postgresql or mysql and nothing else changes.