kiri-factory
v0.1.1
Published
Schema-driven Drizzle factories for explicit test rows and relation wiring
Maintainers
Readme
kiri-factory
Schema-first test factories for Drizzle ORM.
[!IMPORTANT]
kiri-factoryis still early. It is published, but small API changes are still possible across0.xreleases. For production apps, prefer pinning an exact version and checking the changelog before upgrades.
kiri-factory gives you test-friendly factories directly from your Drizzle schema.
It supports both:
- stable Drizzle
relations(...) - Drizzle's current beta
Relational Queries v2shape throughdefineRelations(...)and thekiri-factory/rqb-v2entrypoint
You can start with zero per-table factory definitions.
Required columns are inferred from the schema using the same generator selection logic that powers drizzle-seed, missing single-column parents can be auto-created during create(), and create() still gives you the inserted row back.
When you do want shared defaults, defineFactory(..., { columns }) uses the same public drizzle-seed generator surface as the official seeding API.
That means the same columns(f) definition can power both small test setup and bulk seeding.
In short:
- start from your schema
- use
create()/createMany()for test rows - add
defineFactory(...)only where shared defaults help - reuse the same
columns(f)indrizzle-seed
Quick Example
import * as schema from "./db/schema";
import { createFactories } from "kiri-factory";
const factories = createFactories({
db,
schema,
seed: 42,
});
const session = await factories.sessions.create();
// a required single-column parent can be auto-created when its table is in the runtimeIf one table needs shared values, add a factory definition:
const customerFactory = defineFactory(customers, {
columns: (f) => ({
status: "active",
companyName: f.companyName(),
contactName: f.fullName(),
contactEmail: f.email(),
}),
});
const factories = createFactories({
db,
schema,
seed: 42,
definitions: {
customers: customerFactory,
},
});And the same definition plugs straight into drizzle-seed:
await seed(db, schema).refine((f) => ({
customers: {
count: 1000,
columns: customerFactory.columns(f),
},
}));seed is optional. The default is 0, which preserves the built-in stable per-column
determinism. kiri-factory does not log the seed automatically, but you can read it back
from the runtime with factories.getSeed().
What seed is good for:
- reproducible test data across runs
- easier debugging when a generated row causes a failure
- intentionally varying generated data in CI when you want broader coverage
This follows the same general idea as Drizzle's own deterministic seed docs:
If you want opt-in variation between runs, a common pattern is:
const factories = createFactories({
db,
schema,
seed: Number(process.env.TEST_SEED ?? 0),
});The generated values still depend on factory call order and sequence state, so the most reliable reproduction recipe is: same schema, same seed, same sequence reset, same call order.
Install
pnpm add drizzle-orm kiri-factoryRequirements:
- ESM only
- Node
^20.19.0 || >=22.12.0 - peer install range:
drizzle-orm>=0.36.4 <1 || >=1.0.0-beta.1 <2 kiri-factoryis tested withdrizzle-orm0.45.xkiri-factory/rqb-v2is tested with Drizzle's current betaRelational Queries v2path ondrizzle-orm1.0.0-beta.21
The peer range is intentionally broader than the repository test matrix so Drizzle users do not get blocked on install while the beta line keeps moving. The versions above are the ones currently exercised in this repository.
kiri-factory/rqb-v1 is kept as a compatibility alias for the stable path.
Entrypoints
| Import | Use when |
| --------------------- | --------------------------------------------------------------------- |
| kiri-factory | your project uses stable relations(...) |
| kiri-factory/rqb-v2 | your project uses beta defineRelations(...) / Relational Queries v2 |
What It Does
build()/buildMany()for in-memory rowscreate()/createMany()for persisted rowsfor("relation", row)for explicit parent wiringdefineFactory(..., { columns })for reusable per-table definitions
kiri-factory is intentionally narrow. It does not try to replace drizzle-seed,
prove every database constraint ahead of time, or hide multi-row scenarios behind a
second DSL.
RQB v2 defineRelations(...)
import { PGlite } from "@electric-sql/pglite";
import { drizzle } from "drizzle-orm/pglite";
import { createFactories } from "kiri-factory/rqb-v2";
import { defineRelations } from "drizzle-orm";
import * as schema from "./db/schema";
const client = new PGlite();
const db = drizzle({ client });
const relations = defineRelations(schema, (r) => ({
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
},
users: {
posts: r.many.posts(),
},
}));
const factories = createFactories({
db,
relations,
});
const author = await factories.users.create();
const post = await factories.posts.for("author", author).create();If you want the official seeding API itself, go straight to:
Docs
The docs are split into small Markdown files so humans and agents can jump directly to one topic.
- Docs index
- Getting started
- Defining factories
- Relations
- Inference and
CHECKguardrails - Adapters and transactions
- Compatibility and limits
- API reference
- Versioning and entrypoints
- Troubleshooting
- FAQ
- Recipes
Development
This repository uses pnpm workspaces, catalogs, and Vite+.
pnpm setup:hooks
pnpm check
pnpm test
pnpm buildpnpm setup:hooks installs the repo-local Vite+ hooks.
The pre-commit hook runs vp staged, which applies vp check --fix to staged files.
