Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add createList and buildList method #49

Merged
merged 3 commits into from
Nov 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Prisma generator for model factories.
- [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)
Expand Down Expand Up @@ -153,6 +154,24 @@ 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()));
```

You can also pass list data assignable to `Partial<Prisma.UserCreateInput>[]` :

```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`.
Expand Down Expand Up @@ -230,17 +249,17 @@ console.log(posts.length); // -> 2

### Build input data only

`.buildCreateInput` method in factories provides data set to create the model, but never insert.
`.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.buildCreateInput();
const data = await UserFactory.build();
await prisma.user.create({ data });
```

For example, you can use `.buildCreateInput` method in other model's factory definition:
For example, you can use `.build` method in other model's factory definition:

```ts
const UserFactory = defineUserFactory();
Expand All @@ -252,7 +271,7 @@ const PostFactory = definePostFactory({
where: {
id: "user001",
},
create: await UserFactory.buildCreateInput({
create: await UserFactory.build({
id: "user001",
}),
},
Expand All @@ -266,21 +285,23 @@ 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.buildCreateInput` too.
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.buildCreateInput()],
create: await PostFactory.buildList(2),
},
});

console.log(await prisma.post.count()); // -> 1
console.log(await prisma.post.count()); // -> 2
```

Note: In the above example, `PostFactory.buildCreateInput()` resolves JSON data such as:
Note: In the above example, `PostFactory.build()` resolves JSON data such as:

```ts
{
Expand Down
8 changes: 7 additions & 1 deletion examples/example-prj/src/__generated__/fabbrica/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 29 additions & 7 deletions examples/example-prj/src/__generated__/fabbrica/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions examples/example-prj/src/connectOrCreate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const PostFactory = definePostFactory({
where: {
id: "user001",
},
create: await UserFactory.buildCreateInput({
create: await UserFactory.build({
id: "user001",
}),
},
Expand All @@ -27,9 +27,7 @@ describe("factories", () => {
});

it("creates related user at most one", async () => {
await PostFactory.create();
await PostFactory.create();
await PostFactory.create();
await PostFactory.createList(3);
await expect(prisma.user.count()).resolves.toBe(1);
});
});
Expand Down
4 changes: 2 additions & 2 deletions examples/example-prj/src/sample.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ describe("factories", () => {
it("creates record with children relation", async () => {
await UserFactory.create({
posts: {
create: [await PostFactory.buildCreateInput()],
create: await PostFactory.buildList(2),
},
});
const created = await prisma.user.findFirst({ include: { posts: true } });
expect(created?.posts.length).toBe(1);
expect(created?.posts.length).toBe(2);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFac
const seqKey = {};
const getSeq = () => getSequenceCounter(seqKey);
const screen = createScreener("User", modelFieldDefinitions);
const buildCreateInput = async (inputData: Partial<Prisma.UserCreateInput> = {}) => {
const build = async (inputData: Partial<Prisma.UserCreateInput> = {}) => {
const seq = getSeq();
const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq });
const resolveValue = normalizeResolver<UserFactoryDefineInput, BuildDataOptions>(defaultDataResolver ?? {});
Expand All @@ -48,19 +48,30 @@ function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFac
const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData };
return data;
};
const buildList = (inputData: number | Partial<Prisma.UserCreateInput>[]) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => build(data)));
};
const pickForConnect = (inputData: User) => ({
id: inputData.id
});
const create = async (inputData: Partial<Prisma.UserCreateInput> = {}) => {
const data = await buildCreateInput(inputData).then(screen);
const data = await build(inputData).then(screen);
return await getClient<PrismaClient>().user.create({ data });
};
const createList = (inputData: number | Partial<Prisma.UserCreateInput>[]) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => create(data)));
};
const createForConnect = (inputData: Partial<Prisma.UserCreateInput> = {}) => create(inputData).then(pickForConnect);
return {
_factoryFor: "User" as const,
buildCreateInput,
build,
buildList,
buildCreateInput: build,
pickForConnect,
create,
createList,
createForConnect,
};
}
Expand Down
28 changes: 22 additions & 6 deletions packages/prisma-fabbrica/src/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ export const modelBelongsToRelationFactory = (fieldType: DMMF.SchemaArg, model:
return template.statement<ts.TypeAliasDeclaration>`
type ${() => ast.identifier(`${model.name}${fieldType.name}Factory`)} = {
_factoryFor: ${() => ast.literalTypeNode(ast.stringLiteral(targetModel.type))};
buildCreateInput: () => PromiseLike<Prisma.${() =>
ast.identifier(fieldType.inputTypes[0].type as string)}["create"]>;
build: () => PromiseLike<Prisma.${() => ast.identifier(fieldType.inputTypes[0].type as string)}["create"]>;
};
`();
};
Expand Down Expand Up @@ -272,7 +271,7 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
const getSeq = () => getSequenceCounter(seqKey);
const screen = createScreener(${() => ast.stringLiteral(model.name)}, modelFieldDefinitions);

const buildCreateInput = async (
const build = async (
inputData: Partial<Prisma.MODEL_CREATE_INPUT> = {}
) => {
const seq = getSeq();
Expand All @@ -286,7 +285,7 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
field.name,
template.expression`
IS_MODEL_BELONGS_TO_RELATION_FACTORY(defaultData.FIELD_NAME) ? {
create: await defaultData.FIELD_NAME.buildCreateInput()
create: await defaultData.FIELD_NAME.build()
} : defaultData.FIELD_NAME
`({
IS_MODEL_BELONGS_TO_RELATION_FACTORY: ast.identifier(`is${model.name}${field.name}Factory`),
Expand All @@ -300,6 +299,13 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
return data;
};

const buildList = (
inputData: number | Partial<Prisma.MODEL_CREATE_INPUT>[]
) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => build(data)));
}

const pickForConnect = (inputData: ${() => ast.typeReferenceNode(model.name)}) => (
${() =>
ast.objectLiteralExpression(
Expand All @@ -313,17 +319,27 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp
const create = async (
inputData: Partial<Prisma.MODEL_CREATE_INPUT> = {}
) => {
const data = await buildCreateInput(inputData).then(screen);
const data = await build(inputData).then(screen);
return await getClient<PrismaClient>().MODEL_KEY.create({ data });
};

const createList = (
inputData: number | Partial<Prisma.MODEL_CREATE_INPUT>[]
) => {
const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData;
return Promise.all(list.map(data => create(data)));
}

const createForConnect = (inputData: Partial<Prisma.MODEL_CREATE_INPUT> = {}) => create(inputData).then(pickForConnect);

return {
_factoryFor: ${() => ast.stringLiteral(model.name)} as const,
buildCreateInput,
build,
buildList,
buildCreateInput: build,
pickForConnect,
create,
createList,
createForConnect,
};
}
Expand Down
Loading