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

@kage0x3b/typeorm-seeding

v0.1.2

Published

Factory and seeder library for TypeORM with relationship descriptors, variants, and automatic cleanup

Readme

@kage0x3b/typeorm-seeding

npm version License: MIT

TypeScript-first factory and seeder library for TypeORM. Define how your entities are built, then create them in memory or persist them to the database with full relationship support.

Features

  • Factory pattern for declarative entity creation with Faker.js integration
  • Relationship descriptorsbelongsTo, hasMany, hasOne with automatic FK wiring
  • Named variants for reusable entity presets (e.g. admin, inactive)
  • In-memory builds or database persistence with automatic cleanup
  • Sequence counters and random selection descriptors
  • Labeled refs for cross-factory entity sharing
  • Typed context store via module augmentation
  • Transaction support with withTransaction
  • Seeder classes for orchestrating structured data population
  • ESM-only, TypeORM 0.3+, Node 18+

Installation

npm install --save-dev @kage0x3b/typeorm-seeding
# or
pnpm add --save-dev @kage0x3b/typeorm-seeding

Peer dependencies: typeorm (^0.3.20) and reflect-metadata (^0.2.0).

You must import reflect-metadata once at your application entry point before any TypeORM entity imports:

import 'reflect-metadata';

Quick Start

Define a factory for your entity:

import { Factory, sequence, type Faker, type FactorySchema } from '@kage0x3b/typeorm-seeding';
import { UserEntity } from './entities/UserEntity.js';

export class UserFactory extends Factory<UserEntity> {
    readonly model = UserEntity;

    define(faker: Faker): FactorySchema<UserEntity> {
        return {
            firstName: faker.person.firstName(),
            lastName: faker.person.lastName(),
            email: faker.internet.email(),
            role: faker.helpers.arrayElement(['user', 'editor', 'admin']),
            orderIndex: sequence((n) => n),
        };
    }
}

Create a seeding context and start building entities:

import { createSeedingContext } from '@kage0x3b/typeorm-seeding';

const ctx = createSeedingContext(dataSource); // pass your TypeORM DataSource

const userFactory = ctx.getFactory(UserFactory);

// In-memory (no DB writes)
const user = await userFactory.buildOne();

// Persisted to the database
const savedUser = await userFactory.persistOne({ role: 'admin' });

// Multiple entities
const users = await userFactory.persist(10);

Defining Factories

Every factory extends Factory<T> and defines two things:

  • model — the TypeORM entity class
  • define(faker) — returns a FactorySchema<T> mapping entity properties to values or descriptors
export class PetFactory extends Factory<PetEntity> {
    readonly model = PetEntity;

    define(faker: Faker): FactorySchema<PetEntity> {
        return {
            name: faker.animal.petName(),
            species: faker.helpers.arrayElement(['dog', 'cat', 'bird']),
            owner: belongsTo(UserFactory),
        };
    }
}

FactorySchema<T> covers plain data properties of T. Each property can be either its original type or a descriptor.

Descriptors

Descriptors are special objects returned within define() or variants() that control how field values are resolved at build time.

belongsTo<F>(factoryRef, overridesOrEntity?, variant?)

Declares a ManyToOne or owning-side OneToOne relationship. Creates a parent entity and sets the foreign key automatically.

owner: belongsTo(UserFactory)
owner: belongsTo(UserFactory, { role: 'admin' })
owner: belongsTo(UserFactory, existingUser) // skips creation
owner: belongsTo(UserFactory, undefined, 'admin') // use variant

Each entity gets its own parent — there is no implicit sharing. The optional variant parameter accepts a string or string array to create the parent using the specified variant(s).

hasMany<F>(factoryRef, count, overrides?, variant?)

Declares a OneToMany relationship. Creates count child entities referencing back to the parent. Only valid on array fields.

pets: hasMany(PetFactory, 3)
pets: hasMany(PetFactory, 2, { species: 'dog' })
pets: hasMany(PetFactory, 3, undefined, 'dog') // use variant

hasOne<F>(factoryRef, overrides?, variant?)

Declares a non-owning OneToOne relationship. Creates a single child entity referencing back to the parent.

profile: hasOne(ProfileFactory)
profile: hasOne(ProfileFactory, { bio: 'Custom bio' })
profile: hasOne(ProfileFactory, undefined, 'detailed') // use variant

sequence<R>(callback)

Auto-incrementing counter scoped per factory class, starting at 1.

orderIndex: sequence((n) => n)
email: sequence((n) => `user${n}@test.com`)

ref<V>(label)

Resolves to a previously labeled entity (see Labeled Refs).

company: ref('acmeCorp')
createdBy: ref('adminUser')

Variants

Define named presets that layer on top of define():

export class UserFactory extends Factory<UserEntity, 'admin' | 'inactive' | 'withPets'> {
    readonly model = UserEntity;

    define(faker: Faker): FactorySchema<UserEntity> {
        return {
            firstName: faker.person.firstName(),
            lastName: faker.person.lastName(),
            email: faker.internet.email(),
            role: 'user',
            isActive: true,
        };
    }

    variants() {
        return {
            admin: { role: 'admin', email: '[email protected]' },
            inactive: { isActive: false },
            withPets: { pets: hasMany(PetFactory, 3) },
        };
    }
}

The second type parameter narrows the valid variant names. It's optional; factories without an explicit variant type default to string.

Use variants when building or persisting:

const admin = await userFactory.variant('admin').persistOne();
const inactiveAdmin = await userFactory.variant('admin', 'inactive').persistOne();

Multiple variants can be combined — they are merged in order.

Building vs Persisting

| Method | DB Write | Returns | |--------|----------|---------| | buildOne(overrides?) | No | Promise<T> | | build(count, overrides?) | No | Promise<T[]> | | persistOne(overrides?) | Yes | Promise<T> | | persist(count, overrides?) | Yes | Promise<T[]> |

Built entities receive temporary negative IDs so relationships can be wired up without a database round-trip. Overrides accept plain values, null, or descriptors — pass a plain value or an existing entity to skip auto-creation, or use a descriptor for dynamic generation:

// Use sequence() in an override for unique values per entity
const users = await userFactory.build(5, {
    email: sequence((n) => `batch-${n}@test.com`),
});

// Use belongsTo() in an override to auto-create a related entity
const pet = await petFactory.persistOne({
    owner: belongsTo(UserFactory, { role: 'admin' }),
});

Seeding Context

createSeedingContext is the entry point for using the library.

import { createSeedingContext } from '@kage0x3b/typeorm-seeding';

const ctx = createSeedingContext(dataSource);

ctx.getFactory(FactoryClass)

Returns the cached factory instance for the given class, typed as the concrete factory subclass (e.g. UserFactory, not Factory<UserEntity>).

ctx.resetSequences()

Resets all sequence counters back to zero. Use between tests for isolation.

ctx.clearRefs()

Clears all labeled refs.

ctx.reset()

Combines resetSequences(), clearRefs(), and clears the creation log (without deleting from the database).

ctx.cleanup()

Deletes all entities persisted through this context in reverse creation order, respecting foreign key constraints.

ctx.runSeeders(seeders)

Runs an array of seeder classes in order.

Seeders

Seeders orchestrate factory calls to populate the database with structured data:

import { Seeder } from '@kage0x3b/typeorm-seeding';

export class DatabaseSeeder extends Seeder {
    async run(): Promise<void> {
        const admin = await this.factory(UserFactory)
            .variant('admin')
            .persistOne()
            .as('adminUser');

        await this.factory(UserFactory).persist(10);

        await this.factory(PetFactory).persist(3, { owner: admin });
    }
}

Run seeders via the context:

await ctx.runSeeders([DatabaseSeeder]);

Seeders run in order and share the same context, so factories and sequence counters carry across seeders.

Labeled Refs

Chain .as(label) after persistOne() or buildOne() to register an entity under a string label:

await userFactory.persistOne({ role: 'admin' }).as('adminUser');
await companyFactory.persistOne({ name: 'Acme Corp' }).as('acmeCorp');

Reference labeled entities in factory definitions with ref():

define(faker: Faker): FactorySchema<InvoiceEntity> {
    return {
        amount: faker.number.float({ min: 10, max: 1000, fractionDigits: 2 }),
        company: ref('acmeCorp'),
        issuedBy: ref('adminUser'),
    };
}

Or look them up imperatively:

const admin = ctx.ref<UserEntity>('adminUser');

Context Store

For well-known shared entities (like a current user), use the typed context store via module augmentation:

declare module '@kage0x3b/typeorm-seeding' {
    interface SeedingUserContext {
        currentUser: UserEntity;
        company: CompanyEntity;
    }
}

Populate it in seeders, read it in factories:

// Seeder
this.ctx.store.company = await this.factory(CompanyFactory).persistOne();

// Factory
define(faker: Faker): FactorySchema<ProjectEntity> {
    return {
        name: faker.commerce.productName(),
        company: this.ctx.store.company,
    };
}

Transaction Support

Use ctx.withTransaction(em) to create a child context scoped to a transaction:

await dataSource.transaction(async (em) => {
    const txCtx = ctx.withTransaction(em);
    const user = await txCtx.getFactory(UserFactory).persistOne();
    const pets = await txCtx.getFactory(PetFactory).persist(3, { owner: user });
});

The child context shares the factory cache and creation log with the parent but routes all persistence through the given EntityManager.

Test Setup

Basic Setup

import { DataSource } from 'typeorm';
import { createSeedingContext, type SeedingContext } from '@kage0x3b/typeorm-seeding';

let dataSource: DataSource;
let ctx: SeedingContext;

beforeAll(async () => {
    dataSource = new DataSource({
        type: 'sqlite',
        database: ':memory:',
        entities: [UserEntity, PetEntity],
        synchronize: true,
    });
    await dataSource.initialize();

    ctx = createSeedingContext(dataSource);
});

beforeEach(() => {
    ctx.resetSequences();
    ctx.clearRefs();
});

afterAll(async () => {
    await ctx.cleanup();
    await dataSource.destroy();
});

Transaction-Per-Test Pattern

Wrap each test in a transaction that rolls back for a clean database without truncation:

let txCtx: SeedingContext;
let queryRunner: QueryRunner;

beforeEach(async () => {
    ctx.reset();
    queryRunner = dataSource.createQueryRunner();
    await queryRunner.startTransaction();
    txCtx = ctx.withTransaction(queryRunner.manager);
});

afterEach(async () => {
    await queryRunner.rollbackTransaction();
    await queryRunner.release();
});

it('creates a user with pets', async () => {
    const user = await txCtx.getFactory(UserFactory).variant('withPets').persistOne();
    expect(user.pets).toHaveLength(3);
});

AI Agent Skill

This repository includes a skill file (SKILL.md) that helps AI coding agents create and edit factories, seeders, relationship descriptors, variants, and test setup for this library.

Install it with:

npx skills install @kage0x3b/typeorm-seeding

Acknowledgements

This project was built with assistance from Claude Code. All generated code has been thoroughly reviewed, the library has 100% test coverage, and it is actively used in internal projects.

License

MIT