Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/quramy/prisma-fabbrica
Prisma generator to define model factory
https://github.com/quramy/prisma-fabbrica
factory factory-bot jest prisma prisma-orm
Last synced: about 19 hours ago
JSON representation
Prisma generator to define model factory
- Host: GitHub
- URL: https://github.com/quramy/prisma-fabbrica
- Owner: Quramy
- License: mit
- Created: 2022-11-15T16:29:10.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2025-01-30T18:43:23.000Z (9 days ago)
- Last Synced: 2025-01-31T17:15:55.607Z (8 days ago)
- Topics: factory, factory-bot, jest, prisma, prisma-orm
- Language: TypeScript
- Homepage:
- Size: 2.25 MB
- Stars: 266
- Watchers: 5
- Forks: 14
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# prisma-fabbrica
[![github actions](https://github.com/Quramy/prisma-fabbrica/workflows/build/badge.svg)](https://github.com/Quramy/talt/actions)
[![npm version](https://badge.fury.io/js/@quramy%2Fprisma-fabbrica.svg)](https://badge.fury.io/js/@quramy%2Fprisma-fabbrica)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Quramy/prisma-fabbrica/main/LICENSE)Prisma generator for model factories.
## ToC
- [Getting started](#getting-started)
- [Usage of factories](#usage-of-factories)
- [Field default values](#field-default-values)
- [Use sequence for scalar fields](#use-sequence-for-scalar-fields)
- [Shorthand for create list](#shorthand-for-create-list)
- [Required relation](#required-relation)
- [Connection helper](#connection-helper)
- [Build input data only](#build-input-data-only)
- [has-many / has-one relation](#has-many--has-one-relation)
- [Custom scalar field generation](#custom-scalar-field-generation)
- [Traits](#traits)
- [Callbacks](#callbacks)
- [Transient fields](#transient-fields)
- [Field value precedence](#field-value-precedence)
- [More examples](#more-examples)
- [Generator configuration](#generator-configuration)
- [Tips](#tips)
- [Works with jest-prisma](#works-with-jest-prisma)
- [Suppress TS circular dependencies error](#suppress-ts-circular-dependencies-error)
- [Factory interface with types](#factory-interface-with-types)
- [Version compatibility](#version-compatibility)
- [License](#license)## Getting started
```sh
npm i @quramy/prisma-fabbrica -D
```Then, edit your `prisma/schema.prisma` and append the prisma-fabbrica generator configuration:
```graphql
generator client {
provider = "prisma-client-js"
}generator fabbrica {
provider = "prisma-fabbrica"
}
```And run generate command.
```sh
npx prisma generate
```The above command generates JavaScript and TypeScript type definition files under `src/__generated__/fabbrica` directory. You can define your model factory importing them.
For example, if `schema.prisma` has the following `User` model, you can import `defineUserFactory` and define `UserFactory` using this function.
```graphql
model User {
id String @id
name String
posts Post[]
}
``````ts
/* src/seed.ts */import { PrismaClient } from "@prisma/client";
import { initialize, defineUserFactory } from "./__generated__/fabbrica";
const prisma = new PrismaClient();
initialize({ prisma });async function seed() {
const UserFactory = defineUserFactory();await UserFactory.create();
await UserFactory.create({ name: "Alice" });
await UserFactory.create({ id: "user002", name: "Bob" });console.log(await prisma.user.count()); // -> 3
}seed();
```> [!NOTE]
> The factories use Prisma client instance passed by `initialize` function.If you want to use factories in your test code see [Works with jest-prisma](#works-with-jest-prisma) section below.
## Usage of factories
### Field default values
Factory by defined with `defineUserFactory` automatically fills required scalar fields.
For example, the following `User` model has some required field, `id`, `email`, `firstName` and `lastName` .
```graphql
model User {
id Int @id
email String @unique
firstName String
lastName String
middleName String?
createdAt DateTime @default(now())
}
``````ts
const UserFactory = defineUserFactory();await UserFactory.create(); // Insert record with auto filled id, email, firstName and lastName values
```See https://github.com/Quramy/prisma-fabbrica/blob/main/packages/prisma-fabbrica/src/scalar/gen.ts if you want auto filling rule details.
> [!NOTE]
> prisma-fabbrica auto filling does not generate values of fields with `@default()` function because these fields are not required and values of them are generated by Prisma engine.Default filling rule also can be overwritten.
```ts
const UserFactory = defineUserFactory({
defaultData: async () => ({
email: await generateRandomEmailAddress(),
}),
});await UserFactory.create();
```### Use sequence for scalar fields
`seq` parameter provides sequential number which increments when called `.create()` .
```ts
const UserFactory = defineUserFactory({
defaultData: async ({ seq }) => ({
id: `user${seq.toString().padStart(3, "0")}`,
}),
});await UserFactory.create(); // Insert with id: "user000"
await UserFactory.create(); // Insert with id: "user001"
await UserFactory.create(); // Insert with id: "user002"
```And the sequential number can be reset via `resetSequence` .
```ts
/* your.testSetup.ts */import { resetSequence } from "./__generated__/fabbrica";
beforeEach(() => resetSequence());
```### Shorthand for create list
Each factory provides `.createList` method to insert multiple records.
```ts
await UserFactory.createList(3);// The above code is equivalent to the following
await Promise.all([0, 1, 2].map(() => UserFactory.create()));
```The 2nd argument(optional) accepts an object which is assignable to `Partial` :
```ts
await UserFactory.createList(3, { name: "Bob" });
```You can also pass list data assignable to `Partial[]` :
```ts
await UserFactory.createList([{ id: "user01" }, { id: "user02" }]);
```### Required relation
Sometimes, creating a model requires other model existence. For example, the following model `Post` belongs to other model `User`.
```graphql
model User {
id String @id
name String
posts Post[]
}model Post {
id String @id
title String
author User @relation(fields: [authorId], references: [id])
authorId String
}
```You should tell how to connect `author` field when define Post factory.
#### Using related model factory (recommended)
The easiest way is to give `UserFactory` when `definePostFactory` like this:
```ts
const UserFactory = defineUserFactory();const PostFactory = definePostFactory({
defaultData: {
author: UserFactory,
},
});
```The above `PostFactory` creates `User` model for each `PostFactory.create()` calling,
#### Manual create or connect
Similar to using `prisma.post.create`, you can also use `connect` / `create` / `createOrConnect` options.
```ts
const PostFactory = definePostFactory({
defaultData: async () => ({
author: {
connect: {
id: (await prisma.user.findFirst()!).id,
},
// Alternatively, create or createOrConnect options are allowed.
},
}),
});
```### Connection helper
Required relation rules can be overwritten when `.create` method. `createForConnect` can be used to connect.
```ts
const UserFactory = defineUserFactory();const PostFactory = definePostFactory({
defaultData: {
author: UserFactory,
},
});const author = await UserFactory.createForConnect();
await PostFactory.create({ author: { connect: author } });
await PostFactory.create({ author: { connect: author } });const { posts } = await prisma.user.findUnique({ where: author, include: { posts: true } });
console.log(posts.length); // -> 2
```### Build input data only
`.build` method in factories provides data set to create the model, but never insert.
```ts
await UserFactory.create();// The above code is equivalent to the bellow:
const data = await UserFactory.build();
await prisma.user.create({ data });
```For example, you can use `.build` method in other model's factory definition:
```ts
const UserFactory = defineUserFactory();const PostFactory = definePostFactory({
defaultData: async () => ({
author: {
connectOrCreate: {
where: {
id: "user001",
},
create: await UserFactory.build({
id: "user001",
}),
},
},
}),
});await PostFactory.create();
await PostFactory.create();console.log(await prisma.user.count()); // -> 1
```Like `createList`, `buildList` is also available.
### has-many / has-one relation
Sometimes, you may want a user data whose has post record. You can use `PostFactory.build` or `PostFactory.buildList` .
```ts
await UserFactory.create({
posts: {
create: await PostFactory.buildList(2),
},
});console.log(await prisma.post.count()); // -> 2
```> [!NOTE]
> In the above example, `PostFactory.build()` resolves JSON data such as:```ts
{
id: "...",
title: "...",
author: { ... } // Derived from PostFactory defaultData
}
```The `author` field is not allowed in `prisma.user.create` context. So `UserFactory` automatically filters the `author` field out in `.create` method.
### Custom scalar field generation
prisma-fabbrica provides function to complete scalar fields( https://github.com/Quramy/prisma-fabbrica/blob/main/packages/prisma-fabbrica/src/scalar/gen.ts ).
`registerScalarFieldValueGenerator` allows to custom this rule. For example:
```ts
import { registerScalarFieldValueGenerator } from "./__generated__/fabbrica";registerScalarFieldValueGenerator({
String: ({ modelName, fieldName, seq }) => `${modelName}_${fieldName}_${seq}`,
});
````registerScalarFieldValueGenerator` accepts an object `Record`.
Field type is one of `Boolean`, `String`, `Int`, `Float`, `BigInt`, `Decimal`, `DateTime`, `Bytes`, and `Json`.
`FieldGenerateFunction` is a function to return corresponding fieled type.See also https://github.com/Quramy/prisma-fabbrica/blob/main/packages/prisma-fabbrica/src/scalar/types.ts .
### Traits
Traits allow you to group fields together and apply them to factory.
```ts
const UserFactory = defineUserFactory({
defaultData: {
name: "sample user",
},
traits: {
withdrawal: {
data: {
name: "****",
status: "WITHDRAWAL",
},
},
},
});
````traits` option accepts an object and the option object's keys are treated as the trait's name. And you can set `data` option to the each trait key. The `data` option accepts value of the same types as the `defaultData` (i.e. plain object, function, async function)
And you can pass the trait's name to `UserFactory.use` function:
```ts
const deactivatedUser = await UserFactory.use("withdrawal").create();
```Multiple traits are also available:
```ts
await UserFactory.use("someTrait", "anotherTrait").create();
```### Callbacks
You can set callback function before or after factory execution.
```ts
const UserFactory = defineUserFactory({
onAfterCreate: async user => {
await PostFactory.create({
author: { connect: uesr },
});
},
});await UserFactory.create();
```Callback functions are also available within trait definition.
```ts
const UserFactory = defineUserFactory({
traits: {
withComment: {
onAfterCreate: async user => {
await PostFactory.create({
author: { connect: uesr },
});
},
},
},
});await UserFactory.create();
await UserFactory.use("withComment").create();
```> [!NOTE]
> The above code is to explain the callback. If you want to create association, first consider to use `defaultData` and `trait.data` option as in [has-many / has-one relation](#has-many--has-one-relation).The following three types are available as callback function:
```ts
const UserFactory = defineUserFactory({
onAfterBuild: async createInput => {
// do something
},
onBeforeCreate: async createInput => {
// do something
},
onAfterCreate: async createdData => {
// do something
},
});
```And here, the parameter types are:
- `createInput` is assignable to model create function parameter (e.g. `Prsima.UserCreateInput`).
- `createdData` is resolved object by model create function (e.g. `User` model type)### Transient fields
Transient fields allow to define arbitrary parameters to factory and to pass them when calling `create` or `build`.
```ts
const UserFactory = defineUserFactory.withTransientFields({
loginCount: 0, // `0` is default value of this parameter
})({
defaultData: async ({ loginCount }) => {
// using loginCount
},
});await UserFactory.create({ name: "Bob", loginCount: 10 });
```Transient fields passed from factories' `create` method don't affect Prisma's `create` result.
> [!NOTE]
> You can't use model field names defined in your schema.prisma as transient parameters because they're not passed to `prisma.user.create` method.Transient fields also can be accessed from [traits](#traits) or [callbacks](#callbacks).
```ts
const UserFactory = defineUserFactory.withTransientFields({
loginCount: 0,
})({
// Transient fields are passed to callback functions as the 2nd argument.
onAfterCreate: async (createdUser, { loginCount }) => {
for (let i = 0; i < loginCount; i++) {
await writeLoginLog(createdUser.id);
}
},
traits: {
data: async ({ loginCount }) => {
// using loginCount
},
},
});
```### Field value precedence
Each field is determined in the following priority order(lower numbers have higher priority):
1. Factory's `.create` or `.build` function's argument
1. The applied trait's `data` entry
1. Factories `defaultData` entry
1. Value derived from `registerScalarFieldValueGenerator` if the field is required scalar(or enum)### More examples
There are more example codes in https://github.com/Quramy/prisma-fabbrica/tree/main/examples/example-prj/src .
## Generator configuration
The following options are available:
```graphql
generator fabbrica {
provider = "prisma-fabbrica"
output = "../src/__generated__/fabbrica"
tsconfig = "../tsconfig.json"
noTranspile = false
}
```- `output`: Directory path to generate files.
- `tsconfig`: TypeScript configuration file path. prisma-fabbrica uses it's `compilerOptions` when generating `.js` and `.d.ts` files. If missing tsconfig json file, fallback to `--target es2020 --module commonjs`.
- `noTranspile`: If set `true`, this generator only generates raw `.ts` file and stop to transpile to `.js` and `.d.ts` .## Tips
### Works with jest-prisma
If you use [@quramy/jest-prisma](https://github.com/Quramy/jest-prisma) or [@quramy/jest-prisma-node](https://github.com/Quramy/jest-prisma/tree/main/packages/jest-prisma-node), you can pass `@quramy/prisma-fabbrica/scripts/jest-prisma` to `setupFilesAfterEnv` in your Jest configuration file.
```js
/* jest.config.mjs */export default {
preset: "ts-jest",
transform: {
"^.+\\.tsx?$": "ts-jest",
},
testEnvironment: "@quramy/jest-prisma/environment",
setupFilesAfterEnv: ["@quramy/prisma-fabbrica/scripts/jest-prisma"],
};
```This script calls prisma-fabbrica's `initialize` function and configures Prisma client used from each factory to integrate to join to transaction managed by jest-prisma.
### Suppress TS circular dependencies error
Sometimes, factories need each other factory as the following, however TypeScript compiler emits errors via circular dependencies.
```ts
// 'UserFactory' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
export const UserFactory = defineUserFactory({
defaultData: async () => ({
posts: {
connect: await PostFactory.buildList(1),
},
}),
});// 'PostFactory' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
const PostFactory = definePostFactory({
defaultData: {
author: UserFactory,
},
});
````FactoryInterface` types are available to avoid this error. See [Factory interface with types](#factory-interface-with-types) section if you want usage of factory interface.
```ts
import { defineUserFactory, definePostFactory, type UserFactoryInterface } from "./__generated__/fabbrica";const UserFactory = defineUserFactory({
defaultData: async () => ({
posts: {
connect: await PostFactory.buildList(1),
},
}),
});function getUserFactory(): UserFactoryInterface {
return UserFactory;
}const PostFactory = definePostFactory({
defaultData: {
author: getUserFactory(),
},
});
```### Factory interface with types
> [!WARNING]
> Factory interface type parameters may change in future versions without notice.Factory interface (e.g. `UserFactory` ) takes 2 optional type parameters:
- `TTransientFields`: Type of [transient fields](#transient-fields) object. By default, `Record` .
- `TTraitName`: Names of available [traits](#traits). By default, `string | symbol` .For example:
```ts
// Specify transient fields type
declare function getUserFactory(): UserFactoryInterface<{ loginCount: number }>;await getUserFactory().create({ loginCount: 10 });
// @ts-expect-error
await getUserFactory().create({ hoge: 10 });
``````ts
// Specify available trait names
declare function getUserFactory(): UserFactoryInterface<{}, "someTrait" | "anotherTrait">;await getUserFactory().use("someTrait").create();
await getUserFactory().use("anotherTrait").create();// @ts-expect-error
await getUserFactory().use("hoge").create();
```## Version compatibility
- **If your @prisma/client's version >= 5.0.0, install `@quramy/[email protected]`.**
- **If your @prisma/client's version < 5.0.0, install `@quramy/[email protected]`.**## License
MIT