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

drizzle-fixtures

v1.2.0

Published

Type-safe test data factories for Drizzle ORM

Readme

drizzle-fixtures

CI npm license

📖 Documentation — full API reference and guides

Type-safe test data factories for Drizzle ORM.
Introspects your schema at runtime. Generates fully-typed fixture data. Zero configuration.

// Other factory libraries require you to map every field manually:
const usersFactory = defineFactory({
  table: 'users',
  resolver: ({ sequence }) => ({    // ← you write this for every column
    id: sequence,
    email: `user-${sequence}@example.com`,
    role: 'viewer',
    verified: false,
    createdAt: new Date(),
  }),
})

// drizzle-fixtures reads your schema and figures it out:
const userFactory = defineFactory(users)  // ← that's it

const user  = userFactory.build()                  // InsertUser   no DB needed
const admin = userFactory.build({ role: 'admin' }) // typed override
const saved = await userFactory.create(db)         // SelectUser   inserts to DB

Table of Contents


Why

Most test helpers require you to manually map every column to a fake value. drizzle-fixtures reads your Drizzle schema at runtime and generates sensible values automatically with full TypeScript inference and zero configuration.

| Feature | drizzle-fixtures | | --- | --- | | Zero config setup | ✓ | | Fully typed build() / create() | ✓ | | Works without a database | ✓ | | Related records via use() | ✓ | | Compose factories | ✓ | | DB seeding with defineSeeder | ✓ | | Vitest & Jest helpers | ✓ | | Optional faker.js for richer values | ✓ | | Sequence counter for unique values | ✓ | | Immutable state presets | ✓ | | Zero runtime dependencies | ✓ |


vs @praha/drizzle-factory

The main alternative is @praha/drizzle-factory. It is a solid library, but uses a different philosophy: you write a resolver function that explicitly maps every column to a value.

drizzle-fixtures takes the opposite approach schema introspection does the mapping for you.

| | drizzle-fixtures | @praha/drizzle-factory | |---|---|---| | Setup | Zero config | Manual resolver per table | | Value inference | Automatic from schema | Manual | | Faker.js | Auto-detected | Not built-in | | Related records | ✓ use() | ✓ use() | | Compose factories | ✓ composeFactory | ✓ composeFactory | | DB seeding | ✓ defineSeeder | ✗ | | Test framework helpers | ✓ Vitest + Jest | ✗ | | CockroachDB / SingleStore | ✓ | ✗ |

When to use drizzle-fixtures: You want to get going fast with minimal boilerplate.
When to use @praha/drizzle-factory: You want full explicit control over every generated value.


Install

# npm
npm install --save-dev drizzle-fixtures

# pnpm
pnpm add -D drizzle-fixtures

# yarn
yarn add -D drizzle-fixtures

# bun
bun add -d drizzle-fixtures

Peer dependencies

# required   already installed if you use Drizzle
npm install drizzle-orm

# optional   enables richer generated values (see Faker.js section)
npm install --save-dev @faker-js/faker

Requirements: Node.js >= 18, Bun, or Deno (ESM-compatible runtimes).


Quick Start

1. Define your schema (normal Drizzle)

// schema.ts
import { pgTable, serial, text, varchar, boolean, timestamp } from 'drizzle-orm/pg-core'

export const users = pgTable('users', {
  id:        serial('id').primaryKey(),
  email:     varchar('email', { length: 255 }).notNull(),
  firstName: text('first_name'),
  role:      text('role').notNull().default('viewer'),
  verified:  boolean('verified').notNull().default(false),
  createdAt: timestamp('created_at').notNull().defaultNow(),
})

2. Create a factory

// factories/user.ts
import { defineFactory } from 'drizzle-fixtures'
import { users } from '../schema'

export const userFactory = defineFactory(users)

3. Use in tests

import { userFactory } from './factories/user'
import { db } from './db'

// Build in-memory (no DB)
const user  = userFactory.build()
// { email: '[email protected]', firstName: 'John', ... }

const admin = userFactory.build({ role: 'admin' })
const batch = userFactory.buildList(5)

// Insert to DB
const saved   = await userFactory.create(db)
const records = await userFactory.createList(db, 3)

API Reference

defineFactory(table, options?)

Creates a factory. Call this once per table, typically in a factories/ file.

import { defineFactory } from 'drizzle-fixtures'

const userFactory = defineFactory(users, {
  overrides: {
    role:  'viewer',
    email: ({ seq }) => `user-${seq}@example.com`,
  },
})

| Option | Type | Description | | --- | --- | --- | | overrides | Overrides<InsertUser> | Default field values or generator functions applied to every build() |


factory.build(overrides?)

Returns a typed insert object. Never touches the database. Increments the sequence counter.

const user   = userFactory.build()
const admin  = userFactory.build({ role: 'admin' })
const custom = userFactory.build({
  email: ({ seq }) => `custom-${seq}@test.com`,
})

Returns: typeof users.$inferInsert


factory.buildList(n, overrides?)

Returns an array of n objects. Sequence counter increments across all items.

const users  = userFactory.buildList(5)
const admins = userFactory.buildList(3, { role: 'admin' })

Returns: Array<typeof users.$inferInsert>


factory.create(db, overrides?)

Inserts one record and returns the full select result (including DB-generated fields like id, createdAt).

const user  = await userFactory.create(db)
const admin = await userFactory.create(db, { role: 'admin' })

Returns: Promise<typeof users.$inferSelect>

For MySQL and SingleStore (no RETURNING), drizzle-fixtures automatically does an insert followed by a select by primary key.


factory.createList(db, n, overrides?)

Inserts n records sequentially. Returns all rows.

const users = await userFactory.createList(db, 10)

Returns: Promise<Array<typeof users.$inferSelect>>


factory.state(name, overrides)

Returns a new factory with preset overrides merged on top. The original factory is not mutated.

const adminFactory = userFactory.state('admin', {
  role:     'admin',
  verified: true,
})

const admin      = adminFactory.build()
const superAdmin = adminFactory.build({ email: '[email protected]' })

// States chain
const premiumAdmin = adminFactory.state('premium', { plan: 'pro' })

State factories have their own independent sequence counters.


factory.resetSeq()

Resets the internal sequence counter to 0. The next build() call will use seq 1.

userFactory.build()  // seq 1
userFactory.build()  // seq 2
userFactory.resetSeq()
userFactory.build()  // seq 1 again

factory.ready()

Resolves when faker.js detection is complete. Optional but recommended if you want guaranteed faker values on the first build() call.

const userFactory = defineFactory(users)
await userFactory.ready()
const user = userFactory.build() // guaranteed faker values if installed

Related Records

Use use() inside an override function to create a related record automatically when calling create().

import { defineFactory } from 'drizzle-fixtures'
import { users, posts } from './schema'

const userFactory = defineFactory(users)

const postFactory = defineFactory(posts, {
  overrides: {
    // use is undefined in build() context   guard with a ternary
    authorId: ({ use, seq }) =>
      use
        ? use(userFactory).then(u => u.id)  // create() → inserts real user
        : seq,                               // build() → uses seq as fallback
  },
})

// build()   sync, no DB, authorId = seq value
const post = postFactory.build()

// create()   inserts a user first, then inserts a post with the real user.id
const saved = await postFactory.create(db)

How it works

  • use is undefined in build() context and a live function in create() context
  • Always guard: use ? use(factory).then(...) : fallback
  • Each use() call creates a new related record no deduplication
  • Circular use() chains (A → B → A) throw an error immediately

Multiple relations

const commentFactory = defineFactory(comments, {
  overrides: {
    authorId: ({ use, seq }) => use ? use(userFactory).then(u => u.id) : seq,
    postId:   ({ use, seq }) => use ? use(postFactory).then(p => p.id) : seq,
  },
})

// Inserts: 1 user (post author) + 1 post + 1 user (comment author) + 1 comment
const comment = await commentFactory.create(db)

Composing Factories

Group multiple factories into a single namespaced object with a shared resetSeq().

import { composeFactory } from 'drizzle-fixtures'

const factory = composeFactory({
  users:    userFactory,
  posts:    postFactory,
  comments: commentFactory,
})

// Full factory API on each member
const user = factory.users.build()
const post = await factory.posts.create(db, { authorId: user.id })

// Reset all sequences at once
factory.resetSeq()

Seeding

Orchestrate full database seeding with ordered operations and clean reset support.

import { defineSeeder } from 'drizzle-fixtures'

const seeder = defineSeeder(db, {
  users: {
    seed:   () => userFactory.createList(db, 10),
    before: async () => db.delete(users),  // truncate before re-seed
  },
  posts: {
    seed:   () => postFactory.createList(db, 50),
    before: async () => db.delete(posts),
  },
  // Shorthand   no before hook
  tags: () => tagFactory.createList(db, 20),
})

// Run all seeds in definition order
const result = await seeder.run()
// result: { users: User[], posts: Post[], tags: Tag[] }

// Run only specific seeds
await seeder.run(['users'])

// Truncate + re-seed (calls before hooks, then seed functions)
await seeder.reset()

// Reset only specific tables
await seeder.reset(['posts'])

Seeds always execute sequentially in definition order later seeds can safely reference FK values from earlier ones.


Test Framework Integration

Eliminate per-file boilerplate for sequence reset and DB cleanup.

Vitest

import { useFactory, useSeeder } from 'drizzle-fixtures/vitest'

// Auto-reset seq in beforeEach
const user = useFactory(userFactory)
const post = useFactory(postFactory)

it('creates a user', () => {
  const u = user.build()  // seq always starts at 1 in each test
})

// Auto-reset seq + DB cleanup in afterEach
const user = useFactory(userFactory, {
  db,
  cleanup: () => db.delete(users),
})

// Seeder helper   runs seeder.reset() in beforeEach
useSeeder(seeder)
useSeeder(seeder, ['users', 'posts'])  // reset specific seeds only

Jest

import { useFactory, useSeeder } from 'drizzle-fixtures/jest'

// Identical API
const user = useFactory(userFactory)
useSeeder(seeder)

Value Generation

drizzle-fixtures uses a two-level system to generate values.

Level 1 Semantic name heuristics

Checked first. Matches against the TypeScript field name (case-insensitive, substring match).

| Pattern | Generated value (no faker) | | --- | --- | | email | [email protected] | | firstName, first_name | John | | lastName, last_name | Doe | | name | Entity 1 | | username | user_1 | | slug | slug-1 | | phone, phoneNumber | +15550000001 | | url, website | https://example-1.com | | description, bio, content, body | Lorem ipsum dolor sit amet | | title | Title 1 | | createdAt, created_at | new Date() | | updatedAt, updated_at | new Date() | | deletedAt, deleted_at | null | | verified, isVerified, active, isActive, enabled | true | | count, quantity, amount, total | seq | | price, cost | seq * 10 | | order, position, rank, sort, index | seq | | avatar, image, photo, picture, thumbnail | null | | token, secret, hash, password | secret-1 | | code | CODE0001 | | city | City 1 | | country | US | | address | 1 Main St | | zip, zipCode, postalCode | 10001 |

Level 2 Column type fallback

Used when no semantic match found.

| Data type | Column type examples | Generated value | | --- | --- | --- | | string | PgText, PgVarchar, SQLiteText | text-1 | | number | PgInteger, MySqlInt | seq | | number | PgBigInt, MySqlBigInt | BigInt(seq) | | number | PgNumeric, PgDecimal | "10.00" | | number | PgDoublePrecision, PgReal | seq * 1.5 | | boolean | PgBoolean, MySqlBoolean | true | | date | PgTimestamp, MySqlDatetime | new Date() | | date | PgDate, MySqlDate | "2024-01-01" | | string | PgUUID | crypto.randomUUID() | | json | PgJson, PgJsonb, MySqlJson | {} | | string | PgEnum, MySqlEnum | first enum value |

Skip conditions

These fields are omitted from build() output the database handles them:

  • Serial / autoincrement primary keys
  • Any column with a DB default (defaultNow(), .default('viewer'), .defaultRandom()) unless explicitly overridden

UUID primary keys are the exception drizzle-fixtures generates a crypto.randomUUID() for them.


Faker.js Integration

Install @faker-js/faker and drizzle-fixtures detects it automatically at runtime. No configuration needed.

npm install --save-dev @faker-js/faker

When faker is available, semantic heuristics use realistic values:

| Pattern | Faker value | | --- | --- | | email | faker.internet.email() | | firstName | faker.person.firstName() | | lastName | faker.person.lastName() | | name | faker.person.fullName() | | username | faker.internet.username() | | slug | faker.helpers.slugify(faker.lorem.words(2)) | | phone | faker.phone.number() | | url, website | faker.internet.url() | | description, bio, content, body | faker.lorem.paragraph() | | title | faker.lorem.sentence(3) | | avatar, image, photo | faker.image.avatar() | | city | faker.location.city() | | country | faker.location.country() | | address | faker.location.streetAddress() | | zip, postalCode | faker.location.zipCode() |

Faker is detected via a dynamic import() at module load time. Call await factory.ready() to guarantee faker values on the first build() call.


Supported Dialects

| Dialect | Status | Notes | | --- | --- | --- | | PostgreSQL | ✅ Supported | Uses RETURNING for create() | | SQLite | ✅ Supported | Uses RETURNING for create() | | MySQL | ✅ Supported | Insert + select-by-PK (no RETURNING) | | CockroachDB | ✅ Supported | PG-compatible, uses RETURNING | | SingleStore | ✅ Supported | MySQL-compatible, insert + select-by-PK | | MSSQL | 🔜 Planned | Phase 4 |


TypeScript

drizzle-fixtures is fully typed end-to-end. No any.

import { defineFactory } from 'drizzle-fixtures'
import { users } from './schema'

const factory = defineFactory(users)

// build() return type is exactly typeof users.$inferInsert
const user = factory.build()
//    ^? { email: string; firstName: string | null; role: string; ... }

// create() return type is exactly typeof users.$inferSelect
const saved = await factory.create(db)
//    ^? { id: number; email: string; createdAt: Date; ... }

// Wrong field types are a compile error
factory.build({ role: 123 })
//             ^^^^ Type 'number' is not assignable to type 'string'

Override function type:

// build() context   use is undefined
type BuildCtx  = { seq: number; use: undefined }

// create() context   use is a live function
type CreateCtx = { seq: number; use: <T extends Table>(factory: Factory<T>) => Promise<InferSelectModel<T>> }

type FieldOverride<T> = T | ((ctx: BuildCtx | CreateCtx) => T | Promise<T>)

How It Works

Schema introspection

defineFactory calls getTableColumns(table) from drizzle-orm once at definition time. This returns an object keyed by TypeScript field names, with column descriptors exposing dataType, columnType, notNull, hasDefault, primary, enumValues, and more.

Build pipeline (per field, per build() call)

for each column:
  1. skip?              → serial PK or hasDefault (no user override) → omit
  2. user override?     → resolve (static or fn({ seq, use: undefined })) → use it
  3. semantic match?    → name heuristic → use it
  4. type fallback      → dataType/columnType dispatch → use it

Create pipeline (per field, per create() call)

for each column:
  1. skip?              → serial PK or hasDefault (no user override) → omit
  2. user override?     → resolve (static or async fn({ seq, use })) → await it
  3. semantic match?    → name heuristic → use it
  4. type fallback      → dataType/columnType dispatch → use it
→ insert to DB (sequential, respects FK order)
→ return full SelectModel via RETURNING or select-by-PK

MySQL / SingleStore create() path

These dialects do not support RETURNING. drizzle-fixtures duck-types the insert builder if .returning is not present, it falls back to insert → find PK column → SELECT * FROM table WHERE pk = insertedValue LIMIT 1.

Faker detection

At module import time drizzle-fixtures fires import('@faker-js/faker') asynchronously and caches the result. build() is synchronous and reads the cached value (null if faker is absent or not yet resolved). Call await factory.ready() to wait for detection before your first build().


Contributing

Issues and PRs welcome. Please include a failing test case when reporting a bug.

Open an issue