@bengchet/typeorm-seeding
v5.0.4
Published
🌱 A delightful way to seed test data into your database.
Downloads
4
Readme
Contents
Installation
Before using this TypeORM extension please read the TypeORM Getting Started documentation. This explains how to setup a TypeORM project.
After that install the extension with npm
or yarn
. Add development flag if you are not using seeders nor factories in production code.
npm i [-D] @jorgebodega/typeorm-seeding
yarn add [-D] @jorgebodega/typeorm-seeding
pnpm add [-D] @jorgebodega/typeorm-seeding
Configuration
To configure the path to your seeders extends the TypeORM config file or use environment variables like TypeORM. If both are used the environment variables will be prioritized.
ormconfig.js
module.exports = {
...
seeders: ['src/seeds/**/*{.ts,.js}'],
defaultSeeder: RootSeeder,
...
}
.env
TYPEORM_SEEDING_SEEDERS=src/seeds/**/*{.ts,.js}
TYPEORM_SEEDING_DEFAULT_SEEDER=RootSeeder
Introduction
Isn't it exhausting to create some sample data for your database, well this time is over!
How does it work? Just create a entity factory and/or seed script.
Entity
@Entity()
class User {
@PrimaryGeneratedColumn('increment')
id!: number
@Column()
name!: string
@Column()
lastName!: string
@Column()
email!: string
@OneToMany(() => Pet, (pet) => pet.owner)
pets?: Pet[]
@ManyToOne(() => Country, (country) => country.users, { nullable: false })
@JoinColumn()
country!: Country
}
Factory
class UserFactory extends Factory<User> {
protected entity = User
protected attrs: FactorizedAttrs<User> = {
name: faker.name.firstName(),
lastName: async () => faker.name.lastName(),
email: new InstanceAttribute((instance) =>
[instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''),
),
country: new Subfactory(CountryFactory),
}
}
Seeder
class UserSeeder extends Seeder {
async run(connection: Connection) {
await new UserFactory().createMany(10)
await this.call(connection, [PetSeeder])
}
}
Factory
Factory is how we provide a way to simplify entities creation, implementing a factory creational pattern. It is defined as an abstract class with generic typing, so you have to extend over it.
class UserFactory extends Factory<User> {
protected entity = User
protected attrs: FactorizedAttrs<User> = {
...
}
}
attrs
Attributes objects are superset from the original entity attributes.
protected attrs: FactorizedAttrs<User> = {
name: faker.name.firstName(),
lastName: async () => faker.name.lastName(),
email: new InstanceAttribute((instance) =>
[instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''),
),
country: new Subfactory(CountryFactory),
}
Those factorized attributes resolves to the value of the original attribute, and could be one of the following types:
Simple value
Nothing special, just a value with same type.
protected attrs: FactorizedAttrs<User> = {
name: faker.name.firstName(),
}
Function
Function that could be sync or async, and return a value of the same type. This function will be executed once per entity.
protected attrs: FactorizedAttrs<User> = {
lastName: async () => faker.name.lastName(),
}
InstanceAttribute
class InstanceAttribute<T, V> {
constructor(private callback: (entity: T) => V) {}
...
}
Class with a function that receive the current instance and returns a value of the same type. It is ideal for attributes that could depend on some others to be computed.
Will be executed after the entity has been created and the rest of the attributes have been calculated, but before persistance (in case of create
or createMany
).
protected attrs: FactorizedAttrs<User> = {
name: faker.name.firstName(),
lastName: async () => faker.name.lastName(),
email: new InstanceAttribute((instance) =>
[instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''),
),
}
In this simple case, if name
or lastName
override the value in any way, the email
attribute will be affected too.
LazyInstanceAttribute
class LazyInstanceAttribute<T, V> {
constructor(private callback: (entity: T) => V) {}
...
}
Class with similar functionality than InstanceAttribute
, but it will be executed only after persistance. This is useful for attributes that depends on the database id, like relations.
Just remember that, if you use make
or makeMany
, the only difference between InstanceAttribute
and LazyInstanceAttribute
is that LazyInstanceAttribute
will be processed the last.
protected attrs: FactorizedAttrs<User> = {
name: faker.name.firstName(),
email: new LazyInstanceAttribute((instance) =>
[instance.name.toLowerCase(), instance.id, '@email.com'].join(''),
),
}
Subfactory
export class Subfactory<T> {
constructor(factory: Constructable<Factory<T>>)
constructor(factory: Constructable<Factory<T>>, values?: Partial<FactorizedAttrs<T>>)
constructor(factory: Constructable<Factory<T>>, count?: number)
constructor(factory: Constructable<Factory<T>>, values?: Partial<FactorizedAttrs<T>>, count?: number)
...
}
Subfactories are just a wrapper of another factory, to avoid explicit operations that could lead to unexpected results over that factory, like
protected attrs: FactorizedAttrs<User> = {
country: async () => new CountryFactory().create({
name: faker.address.country(),
}),
}
instead of
protected attrs: FactorizedAttrs<User> = {
country: new Subfactory(CountryFactory, {
name: faker.address.country(),
}),
}
Subfactory just execute the same kind of operation (make
or create
) over the factory. If count
param is provided, it will execute makeMany
/createMany
instead of make
/create
, and returns an array.
make
& makeMany
Make and makeMany executes the factory functions and return a new instance of the given entity. The instance is filled with the generated values from the factory function, but not saved in the database.
- overrideParams - Override some of the attributes of the entity.
make(overrideParams: Partial<FactorizedAttrs<T>> = {}): Promise<T>
makeMany(amount: number, overrideParams: Partial<FactorizedAttrs<T>> = {}): Promise<T[]>
new UserFactory().make()
new UserFactory().makeMany(10)
// override the email
new UserFactory().make({ email: '[email protected]' })
new UserFactory().makeMany(10, { email: '[email protected]' })
create
& createMany
the create and createMany method is similar to the make and makeMany method, but at the end the created entity instance gets persisted in the database using TypeORM entity manager.
- overrideParams - Override some of the attributes of the entity.
- saveOptions - Save options from TypeORM
create(overrideParams: Partial<FactorizedAttrs<T>> = {}, saveOptions?: SaveOptions): Promise<T>
createMany(amount: number, overrideParams: Partial<FactorizedAttrs<T>> = {}, saveOptions?: SaveOptions): Promise<T[]>
new UserFactory().create()
new UserFactory().createMany(10)
// override the email
new UserFactory().create({ email: '[email protected]' })
new UserFactory().createMany(10, { email: '[email protected]' })
// using save options
new UserFactory().create({ email: '[email protected]' }, { listeners: false })
new UserFactory().createMany(10, { email: '[email protected]' }, { listeners: false })
Seeder
Seeder class is how we provide a way to insert data into databases, and could be executed by the command line or by helper method. Is an abstract class with one method to be implemented, and a helper function to run some more seeder sequentially.
class UserSeeder extends Seeder {
async run(connection: Connection) {
...
}
}
run
This function is the one that needs to be defined when extending the class. Could use call
to run some other seeders.
run(connection: Connection): Promise<void>
async run(connection: Connection) {
await new UserFactory().createMany(10)
await this.call(connection, [PetSeeder])
}
CLI Configuration
There are two possible commands to execute, one to see the current configuration and one to run a seeder.
Add the following scripts to your package.json
file to configure them.
"scripts": {
"seed:run": "typeorm-seeding seed",
...
}
seed
This command execute a seeder, that could be specified as a parameter.
typeorm-seeding seed <path>
The name of the seeder to execute (either set with the --seed
option or with default in configs) must be the seeder's class name, and thus, the seeder must be exported with a named export. Please avoid default export for seeders: it may imply unwanted behavior. (See #75).
Options
| Option | Default | Description |
| ---------------------- | ------------------------------------ | ----------------------------------------------------- |
| --dataSource
or -d
| | Path of the data source |
Testing features
We provide some testing features that we already use to test this package, like connection configuration.
The entity factories can also be used in testing. To do so call the useFactories
or useSeeders
function.
useSeeders
Execute one or more seeders.
useSeeders(entrySeeders: ClassConstructor<Seeder> | ClassConstructor<Seeder>[]): Promise<void>
useSeeders(
entrySeeders: ClassConstructor<Seeder> | ClassConstructor<Seeder>[],
customOptions: Partial<ConnectionConfiguration>,
): Promise<void>
useDataSource
Use specific data source on the factories. If the data source is not initialized when provided, can be initialized with the forceInitialization
flag.
useDataSource(dataSource: DataSource): Promise<void>
useDataSource(dataSource: DataSource, overrideOptions: Partial<DataSourceOptions>): Promise<void>
useDataSource(dataSource: DataSource, forceInitialization: boolean): Promise<void>
useDataSource(
dataSource: DataSource,
overrideOptions: Partial<DataSourceOptions>,
forceInitialization: boolean,
): Promise<void>