diff --git a/README.md b/README.md index f32ddc99..594e6fa4 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,12 @@ Prisma generator for model factories. - [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) - [Generator configuration](#generator-configuration) - [Tips](#tips) - [Works with jest-prisma](#works-with-jest-prisma) @@ -125,6 +128,50 @@ const UserFactory = defineUserFactory({ 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())); +``` + +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`. @@ -202,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(); @@ -224,7 +271,7 @@ const PostFactory = definePostFactory({ where: { id: "user001", }, - create: await UserFactory.buildCreateInput({ + create: await UserFactory.build({ id: "user001", }), }, @@ -238,6 +285,34 @@ 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. + ## Generator configuration The following options are available: diff --git a/examples/example-prj/package.json b/examples/example-prj/package.json index 527e7a9b..6d291a4f 100644 --- a/examples/example-prj/package.json +++ b/examples/example-prj/package.json @@ -7,8 +7,8 @@ "clean": "echo nothing to do", "migrate:test:ci": "DATABASE_URL=\"file:./test.db\" prisma migrate dev", "generate": "prisma generate", - "test": "jest", - "test:ci": "DATABASE_URL=\"file:./test.db\" jest" + "test": "DATABASE_URL=\"file:./test.db\" jest --maxWorkers=1", + "test:ci": "DATABASE_URL=\"file:./test.db\" jest --maxWorkers=1" }, "devDependencies": { "@quramy/jest-prisma-node": "1.1.2", diff --git a/examples/example-prj/src/__generated__/fabbrica/index.d.ts b/examples/example-prj/src/__generated__/fabbrica/index.d.ts index 97ba5a25..c00646f5 100644 --- a/examples/example-prj/src/__generated__/fabbrica/index.d.ts +++ b/examples/example-prj/src/__generated__/fabbrica/index.d.ts @@ -2,29 +2,35 @@ import { User } from "@prisma/client"; import { Post } from "@prisma/client"; import { Prisma } from "@prisma/client"; import { Resolver } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; type UserFactoryDefineInput = { id?: string; name?: string; posts?: Prisma.PostCreateNestedManyWithoutAuthorInput; }; type UserFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; export declare function defineUserFactory(args?: UserFactoryDefineOptions): { _factoryFor: "User"; + build: (inputData?: Partial) => Promise; + buildList: (inputData: number | Partial[]) => Promise; buildCreateInput: (inputData?: Partial) => Promise; pickForConnect: (inputData: User) => { id: string; }; create: (inputData?: Partial) => Promise; + createList: (inputData: number | Partial[]) => Promise; createForConnect: (inputData?: Partial) => Promise<{ id: string; }>; }; type PostauthorFactory = { _factoryFor: "User"; - buildCreateInput: () => PromiseLike; + build: () => PromiseLike; }; type PostFactoryDefineInput = { id?: string; @@ -32,15 +38,18 @@ type PostFactoryDefineInput = { author: PostauthorFactory | Prisma.UserCreateNestedOneWithoutPostsInput; }; type PostFactoryDefineOptions = { - defaultData: Resolver; + defaultData: Resolver; }; export declare function definePostFactory(args: PostFactoryDefineOptions): { _factoryFor: "Post"; + build: (inputData?: Partial) => Promise; + buildList: (inputData: number | Partial[]) => Promise; buildCreateInput: (inputData?: Partial) => Promise; pickForConnect: (inputData: Post) => { id: string; }; create: (inputData?: Partial) => Promise; + createList: (inputData: number | Partial[]) => Promise; createForConnect: (inputData?: Partial) => Promise<{ id: string; }>; diff --git a/examples/example-prj/src/__generated__/fabbrica/index.js b/examples/example-prj/src/__generated__/fabbrica/index.js index 913f225a..f13d7714 100644 --- a/examples/example-prj/src/__generated__/fabbrica/index.js +++ b/examples/example-prj/src/__generated__/fabbrica/index.js @@ -3,39 +3,72 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.definePostFactory = exports.defineUserFactory = exports.initialize = void 0; +exports.definePostFactory = exports.defineUserFactory = exports.resetSequence = exports.initialize = void 0; const clientHolder_1 = require("@quramy/prisma-fabbrica/lib/clientHolder"); +const relations_1 = require("@quramy/prisma-fabbrica/lib/relations"); const gen_1 = __importDefault(require("@quramy/prisma-fabbrica/lib/scalar/gen")); const helpers_1 = require("@quramy/prisma-fabbrica/lib/helpers"); var prisma_fabbrica_1 = require("@quramy/prisma-fabbrica"); Object.defineProperty(exports, "initialize", { enumerable: true, get: function () { return prisma_fabbrica_1.initialize; } }); -function autoGenerateUserScalarsOrEnums() { +Object.defineProperty(exports, "resetSequence", { enumerable: true, get: function () { return prisma_fabbrica_1.resetSequence; } }); +const modelFieldDefinitions = [{ + name: "User", + fields: [{ + name: "posts", + type: "Post", + relationName: "PostToUser" + }] + }, { + name: "Post", + fields: [{ + name: "author", + type: "User", + relationName: "PostToUser" + }] + }]; +function autoGenerateUserScalarsOrEnums({ seq }) { return { - id: gen_1.default.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), - name: gen_1.default.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false }) + id: gen_1.default.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false, seq }), + name: gen_1.default.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false, seq }) }; } function defineUserFactoryInternal({ defaultData: defaultDataResolver }) { - const buildCreateInput = async (inputData = {}) => { - const requiredScalarData = autoGenerateUserScalarsOrEnums(); - const defaultData = await (0, helpers_1.resolveValue)(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => (0, helpers_1.getSequenceCounter)(seqKey); + const screen = (0, relations_1.createScreener)("User", modelFieldDefinitions); + const build = async (inputData = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq }); + const resolveValue = (0, helpers_1.normalizeResolver)(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData) => ({ id: inputData.id }); const create = async (inputData = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await (0, clientHolder_1.getClient)().user.create({ data }); }; + const createList = (inputData) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "User", - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } @@ -46,37 +79,53 @@ exports.defineUserFactory = defineUserFactory; function isPostauthorFactory(x) { return x?._factoryFor === "User"; } -function autoGeneratePostScalarsOrEnums() { +function autoGeneratePostScalarsOrEnums({ seq }) { return { - id: gen_1.default.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false }), - title: gen_1.default.String({ modelName: "Post", fieldName: "title", isId: false, isUnique: false }) + id: gen_1.default.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false, seq }), + title: gen_1.default.String({ modelName: "Post", fieldName: "title", isId: false, isUnique: false, seq }) }; } function definePostFactoryInternal({ defaultData: defaultDataResolver }) { - const buildCreateInput = async (inputData = {}) => { - const requiredScalarData = autoGeneratePostScalarsOrEnums(); - const defaultData = await (0, helpers_1.resolveValue)(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => (0, helpers_1.getSequenceCounter)(seqKey); + const screen = (0, relations_1.createScreener)("Post", modelFieldDefinitions); + const build = async (inputData = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGeneratePostScalarsOrEnums({ seq }); + const resolveValue = (0, helpers_1.normalizeResolver)(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = { author: isPostauthorFactory(defaultData.author) ? { - create: await defaultData.author.buildCreateInput() + create: await defaultData.author.build() } : defaultData.author }; const data = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData) => ({ id: inputData.id }); const create = async (inputData = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await (0, clientHolder_1.getClient)().post.create({ data }); }; + const createList = (inputData) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "Post", - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } diff --git a/examples/example-prj/src/connectOrCreate.test.ts b/examples/example-prj/src/connectOrCreate.test.ts new file mode 100644 index 00000000..f1ae44ab --- /dev/null +++ b/examples/example-prj/src/connectOrCreate.test.ts @@ -0,0 +1,34 @@ +import { defineUserFactory, definePostFactory } from "./__generated__/fabbrica"; + +const prisma = jestPrisma.client; + +const UserFactory = defineUserFactory(); + +const PostFactory = definePostFactory({ + defaultData: async () => ({ + author: { + connectOrCreate: { + where: { + id: "user001", + }, + create: await UserFactory.build({ + id: "user001", + }), + }, + }, + }), +}); + +describe("factories", () => { + describe("PostFactory", () => { + it("creates post record", async () => { + await PostFactory.create(); + await expect(prisma.user.count()).resolves.toBe(1); + }); + + it("creates related user at most one", async () => { + await PostFactory.createList(3); + await expect(prisma.user.count()).resolves.toBe(1); + }); + }); +}); diff --git a/examples/example-prj/src/sample.test.ts b/examples/example-prj/src/sample.test.ts index 1ce10580..b62fbcec 100644 --- a/examples/example-prj/src/sample.test.ts +++ b/examples/example-prj/src/sample.test.ts @@ -10,21 +10,6 @@ const PostFactory = definePostFactory({ }, }); -const PostFactoryAlt = definePostFactory({ - defaultData: async () => ({ - author: { - connectOrCreate: { - where: { - id: "user001", - }, - create: await UserFactory.buildCreateInput({ - id: "user001", - }), - }, - }, - }), -}); - describe("factories", () => { describe("UserFactory", () => { it("creates records without input parameters", async () => { @@ -38,6 +23,16 @@ describe("factories", () => { const user = await prisma.user.findUnique({ where: { id: "user001" } }); expect(user).toEqual({ id: "user001", name: "Quramy" }); }); + + it("creates record with children relation", async () => { + await UserFactory.create({ + posts: { + create: await PostFactory.buildList(2), + }, + }); + const created = await prisma.user.findFirst({ include: { posts: true } }); + expect(created?.posts.length).toBe(2); + }); }); describe("PostFactory", () => { @@ -58,18 +53,4 @@ describe("factories", () => { expect(userWithPosts?.posts.length).toBe(3); }); }); - - describe("PostFactoryAlt", () => { - it("creates post record", async () => { - await PostFactoryAlt.create(); - await expect(prisma.user.count()).resolves.toBe(1); - }); - - it("creates related user at most one", async () => { - await PostFactoryAlt.create(); - await PostFactoryAlt.create(); - await PostFactoryAlt.create(); - await expect(prisma.user.count()).resolves.toBe(1); - }); - }); }); diff --git a/examples/example-prj/src/sequence.test.ts b/examples/example-prj/src/sequence.test.ts new file mode 100644 index 00000000..7c663359 --- /dev/null +++ b/examples/example-prj/src/sequence.test.ts @@ -0,0 +1,28 @@ +import { defineUserFactory, resetSequence } from "./__generated__/fabbrica"; + +const prisma = jestPrisma.client; + +const UserFactory = defineUserFactory({ + defaultData: async ({ seq }) => ({ + id: `user${seq.toString().padStart(3, "0")}`, + }), +}); + +describe("factories", () => { + describe("UserFactory", () => { + beforeEach(async () => { + resetSequence(); + await UserFactory.create(); + }); + + it("creates record with sequential id", async () => { + await expect(prisma.user.findUnique({ where: { id: "user000" } })).resolves.not.toBeNull(); + }); + + it("expected that the sequential id is restarted", async () => { + await UserFactory.create(); + await expect(prisma.user.findUnique({ where: { id: "user000" } })).resolves.not.toBeNull(); + await expect(prisma.user.findUnique({ where: { id: "user001" } })).resolves.not.toBeNull(); + }); + }); +}); diff --git a/packages/prisma-fabbrica/src/helpers/createJSONLiteral.ts b/packages/prisma-fabbrica/src/helpers/createJSONLiteral.ts new file mode 100644 index 00000000..00e12814 --- /dev/null +++ b/packages/prisma-fabbrica/src/helpers/createJSONLiteral.ts @@ -0,0 +1,42 @@ +import ts from "typescript"; +import { ast } from "./astShorthand"; + +type Primitive = string | boolean | number; + +type JSONObjLike = { + [key: string]: Primitive | JSONObjLike | JSONObjLike[]; +}; + +type JSONLike = JSONObjLike | JSONObjLike[]; + +function createArrayLitreral(obj: ReadonlyArray): ts.ArrayLiteralExpression { + return ast.arrayLiteralExpression(obj.map(createObjectLiteral)); +} + +function createObjectLiteral(obj: JSONObjLike): ts.ObjectLiteralExpression { + return ast.objectLiteralExpression( + Object.entries(obj).map(([k, v]) => + ast.propertyAssignment( + k, + typeof v === "string" + ? ast.stringLiteral(v) + : typeof v === "number" + ? ast.numericLiteral(v) + : typeof v === "boolean" + ? v + ? ast.true() + : ast.false() + : Array.isArray(v) + ? createArrayLitreral(v) + : typeof v === "object" + ? createObjectLiteral(v) + : (null as never), + ), + ), + true, + ); +} + +export function createJSONLiteral(obj: JSONLike): ts.Expression { + return Array.isArray(obj) ? createArrayLitreral(obj) : createObjectLiteral(obj); +} diff --git a/packages/prisma-fabbrica/src/helpers/index.ts b/packages/prisma-fabbrica/src/helpers/index.ts index 293a25bd..a6f4fa91 100644 --- a/packages/prisma-fabbrica/src/helpers/index.ts +++ b/packages/prisma-fabbrica/src/helpers/index.ts @@ -1,3 +1,6 @@ export * from "./valueResolver"; export * from "./stringConverter"; export * from "./astShorthand"; +export * from "./sequence"; +export * from "./selectors"; +export * from "./createJSONLiteral"; diff --git a/packages/prisma-fabbrica/src/helpers/selectors.ts b/packages/prisma-fabbrica/src/helpers/selectors.ts new file mode 100644 index 00000000..33e76ab7 --- /dev/null +++ b/packages/prisma-fabbrica/src/helpers/selectors.ts @@ -0,0 +1,3 @@ +export function byName(name: string | { readonly name: string }) { + return (x: T) => x.name === (typeof name === "string" ? name : name.name); +} diff --git a/packages/prisma-fabbrica/src/helpers/sequence.ts b/packages/prisma-fabbrica/src/helpers/sequence.ts new file mode 100644 index 00000000..63e1b9c9 --- /dev/null +++ b/packages/prisma-fabbrica/src/helpers/sequence.ts @@ -0,0 +1,15 @@ +let sequenceCounterMap: WeakMap = new Map(); + +export function resetSequence() { + sequenceCounterMap = new Map(); +} + +export function getSequenceCounter(key: any) { + const c = sequenceCounterMap.get(key); + if (c == null) { + sequenceCounterMap.set(key, 0); + return 0; + } + sequenceCounterMap.set(key, c + 1); + return c + 1; +} diff --git a/packages/prisma-fabbrica/src/helpers/valueResolver.test.ts b/packages/prisma-fabbrica/src/helpers/valueResolver.test.ts new file mode 100644 index 00000000..94b3507b --- /dev/null +++ b/packages/prisma-fabbrica/src/helpers/valueResolver.test.ts @@ -0,0 +1,29 @@ +import { Resolver, normalizeResolver } from "./valueResolver"; + +type TestResolver = Resolver<{ value: string }, { seq: number }>; + +describe(normalizeResolver, () => { + test("with plain object", async () => { + const r: TestResolver = { + value: "hoge", + }; + const resolver = normalizeResolver<{ value: string }, { seq: number }>(r); + await expect(resolver({ seq: 1 })).resolves.toEqual({ value: "hoge" }); + }); + + test("with sync function", async () => { + const r: TestResolver = ({ seq }) => ({ + value: `${seq}`, + }); + const resolver = normalizeResolver<{ value: string }, { seq: number }>(r); + await expect(resolver({ seq: 1 })).resolves.toEqual({ value: "1" }); + }); + + test("with async function", async () => { + const r: TestResolver = async ({ seq }) => ({ + value: `${await seq}`, + }); + const resolver = normalizeResolver<{ value: string }, { seq: number }>(r); + await expect(resolver({ seq: 1 })).resolves.toEqual({ value: "1" }); + }); +}); diff --git a/packages/prisma-fabbrica/src/helpers/valueResolver.ts b/packages/prisma-fabbrica/src/helpers/valueResolver.ts index edfdea7f..0e3bac83 100644 --- a/packages/prisma-fabbrica/src/helpers/valueResolver.ts +++ b/packages/prisma-fabbrica/src/helpers/valueResolver.ts @@ -1,6 +1,13 @@ -export type Resolver> = T | (() => T) | (() => PromiseLike); +export type Resolver, S extends Record> = + | T + | ((opt: S) => T) + | ((opt: S) => PromiseLike); -export async function resolveValue>(resolver: Resolver) { - const fn = typeof resolver === "function" ? resolver : () => Promise.resolve(resolver); - return (await fn()) as T; +export function normalizeResolver, S extends Record>( + resolver: Resolver, +): (opt: S) => Promise { + return async (opt: S) => { + const fn = typeof resolver === "function" ? resolver : () => Promise.resolve(resolver); + return (await fn(opt)) as T; + }; } diff --git a/packages/prisma-fabbrica/src/initialize.ts b/packages/prisma-fabbrica/src/initialize.ts index bf4c2e85..84da06d1 100644 --- a/packages/prisma-fabbrica/src/initialize.ts +++ b/packages/prisma-fabbrica/src/initialize.ts @@ -1,4 +1,6 @@ import { resetClient, setClient, PrismaClientLike } from "./clientHolder"; +import { resetSequence } from "./helpers"; +export { resetSequence } from "./helpers"; export type InitializeOptions = { readonly prisma: PrismaClientLike | (() => PrismaClientLike); @@ -6,8 +8,10 @@ export type InitializeOptions = { export function reset() { resetClient(); + resetSequence(); } export function initialize(options: InitializeOptions) { setClient(options.prisma); + resetSequence(); } diff --git a/packages/prisma-fabbrica/src/relations/index.ts b/packages/prisma-fabbrica/src/relations/index.ts new file mode 100644 index 00000000..73498494 --- /dev/null +++ b/packages/prisma-fabbrica/src/relations/index.ts @@ -0,0 +1 @@ +export * from "./screen"; diff --git a/packages/prisma-fabbrica/src/relations/screen.test.ts b/packages/prisma-fabbrica/src/relations/screen.test.ts new file mode 100644 index 00000000..ef18fea2 --- /dev/null +++ b/packages/prisma-fabbrica/src/relations/screen.test.ts @@ -0,0 +1,138 @@ +import { DMMF } from "@prisma/generator-helper"; +import { getDMMF } from "@prisma/internals"; +import { createFieldDefinitions, createScreener } from "./screen"; + +describe(createScreener, () => { + let document: DMMF.Document; + let subject: (data: T) => T; + describe("For User - Post releation", () => { + beforeEach(async () => { + document = await getDMMF({ + datamodel: ` + model User { + id Int @id + posts Post[] + } + model Post { + id Int @id + authorId Int + author User @relation(fields: [authorId], references: [id]) + } + `, + }); + }); + + describe("User screen", () => { + beforeEach(() => { + subject = createScreener("User", createFieldDefinitions(document.datamodel.models)); + }); + it("screens author in posts create relation", () => { + expect( + subject({ + id: "", + posts: { + create: [{ id: "", author: {} }], + }, + }), + ).toEqual({ + id: "", + posts: { + create: [{ id: "" }], + }, + }); + }); + + it("screens author in posts connectOrCreate relation", () => { + expect( + subject({ + id: "", + posts: { + connectOrCreate: [{ where: { id: "" }, create: { id: "", author: {} } }], + }, + }), + ).toEqual({ + id: "", + posts: { + connectOrCreate: [{ where: { id: "" }, create: { id: "" } }], + }, + }); + }); + + it("does nothing for connect", () => { + expect( + subject({ + id: "", + posts: { + connect: [{ id: "", name: "" }], + }, + }), + ).toEqual({ + id: "", + posts: { + connect: [{ id: "", name: "" }], + }, + }); + }); + }); + + describe("Post screen", () => { + beforeEach(() => { + subject = createScreener("Post", createFieldDefinitions(document.datamodel.models)); + }); + + it("screening posts in user create relation", () => { + expect( + subject({ + id: "", + author: { + create: { id: "", posts: [] }, + }, + }), + ).toEqual({ + id: "", + author: { + create: { id: "" }, + }, + }); + }); + + it("screening posts in user connectOrCreate relation", () => { + expect( + subject({ + id: "", + author: { + connectOrCreate: { + where: { id: "" }, + create: { id: "", posts: [] }, + }, + }, + }), + ).toEqual({ + id: "", + author: { + connectOrCreate: { + where: { id: "" }, + create: { id: "" }, + }, + }, + }); + }); + + it("does nothing for connect", () => { + expect( + subject({ + id: "", + author: { + connect: { id: "", name: "" }, + }, + }), + ).toEqual({ + id: "", + author: { + connect: { id: "", name: "" }, + }, + }); + }); + }); + }); +}); diff --git a/packages/prisma-fabbrica/src/relations/screen.ts b/packages/prisma-fabbrica/src/relations/screen.ts new file mode 100644 index 00000000..2073d001 --- /dev/null +++ b/packages/prisma-fabbrica/src/relations/screen.ts @@ -0,0 +1,107 @@ +import { DMMF } from "@prisma/generator-helper"; +import { byName } from "../helpers"; + +export type ModelWithFields = { + name: string; + fields: { + name: string; + type: string; + relationName: string; + }[]; +}; + +function isCreateChild(def: any): def is { create: Record } { + return typeof def.create === "object" && !Array.isArray(def.create); +} + +function isCreateChildrenList(def: any): def is { create: Record[] } { + return typeof def.create === "object" && Array.isArray(def.create); +} + +function isCorCChild(def: any): def is { connectOrCreate: { where: unknown; create: Record } } { + return typeof def.connectOrCreate === "object" && !Array.isArray(def.connectOrCreate); +} + +function isCorCChildrenList( + def: any, +): def is { connectOrCreate: { where: unknown; create: Record }[] } { + return typeof def.connectOrCreate === "object" && Array.isArray(def.connectOrCreate); +} + +export function createFieldDefinitions(models: DMMF.Model[]): ModelWithFields[] { + return models.map(m => ({ + name: m.name, + fields: m.fields + .filter(f => f.kind === "object") + .map(f => ({ + name: f.name, + type: f.type, + relationName: f.relationName ?? "", + })), + })); +} + +function removeProperties(propertyNames: readonly string[], target: Record) { + for (const name of propertyNames) { + delete target[name]; + } + return target; +} + +export function createScreener(modelName: string, fieldDefinitions: readonly ModelWithFields[]) { + const screenInternal = (parentModelName: string, createInput: any): any => { + const result = Object.keys(createInput).reduce((acc, fieldName) => { + const fieldDef = fieldDefinitions.find(byName(parentModelName))?.fields.find(byName(fieldName)); + if (fieldDef) { + const nextModel = fieldDefinitions.find(byName(fieldDef.type))!; + const fieldNamesToBeRemoved = + fieldDefinitions + .find(byName(nextModel.name)) + ?.fields.filter(f => f.relationName === fieldDef.relationName) + .map(f => f.name) ?? []; + const connectionDefinition = createInput[fieldName] as + | undefined + | { + create?: unknown; + connect?: unknown; + connectOrCreate?: unknown; + }; + if (!connectionDefinition) return acc; + const modifiedConnectionDefinition = { ...connectionDefinition }; + if (isCreateChild(connectionDefinition)) { + modifiedConnectionDefinition.create = removeProperties( + fieldNamesToBeRemoved, + screenInternal(nextModel.name, connectionDefinition.create), + ); + } else if (isCreateChildrenList(connectionDefinition)) { + modifiedConnectionDefinition.create = connectionDefinition.create + .map(screenInternal.bind(null, nextModel.name)) + .map(removeProperties.bind(null, fieldNamesToBeRemoved)); + } + if (isCorCChild(connectionDefinition)) { + modifiedConnectionDefinition.connectOrCreate = { + ...connectionDefinition.connectOrCreate, + create: removeProperties( + fieldNamesToBeRemoved, + screenInternal(nextModel.name, connectionDefinition.connectOrCreate.create), + ), + }; + } else if (isCorCChildrenList(connectionDefinition)) { + modifiedConnectionDefinition.connectOrCreate = connectionDefinition.connectOrCreate.map(def => ({ + ...def, + create: removeProperties(fieldNamesToBeRemoved, screenInternal(nextModel.name, def.create)), + })); + } + return { + ...acc, + [fieldName]: modifiedConnectionDefinition, + }; + } else { + return { ...acc, [fieldName]: createInput[fieldName] }; + } + }, {} as any); + return result; + }; + const screen = (createInput: T) => screenInternal(modelName, createInput) as T; + return screen; +} diff --git a/packages/prisma-fabbrica/src/scalar/gen.ts b/packages/prisma-fabbrica/src/scalar/gen.ts index 7930ad2c..e5b09375 100644 --- a/packages/prisma-fabbrica/src/scalar/gen.ts +++ b/packages/prisma-fabbrica/src/scalar/gen.ts @@ -9,9 +9,9 @@ const scalarFieldValueGenerator: ScalarFieldValueGenerator = { } return `${fieldName} field`; }, - Int: ({ isId, isUnique }) => { + Int: ({ isId, isUnique, seq }) => { if (isId || isUnique) { - return Date.now(); + return seq; } return 10; }, diff --git a/packages/prisma-fabbrica/src/scalar/types.ts b/packages/prisma-fabbrica/src/scalar/types.ts index cc6fdecd..65668f0b 100644 --- a/packages/prisma-fabbrica/src/scalar/types.ts +++ b/packages/prisma-fabbrica/src/scalar/types.ts @@ -3,6 +3,7 @@ export type ScalarFieldGenerateOptions = { readonly fieldName: string; readonly isUnique: boolean; readonly isId: boolean; + readonly seq: number; }; export interface ScalarFieldValueGenerator { diff --git a/packages/prisma-fabbrica/src/scripts/jest-prisma/setup.ts b/packages/prisma-fabbrica/src/scripts/jest-prisma/setup.ts index ee14f043..f8d67091 100644 --- a/packages/prisma-fabbrica/src/scripts/jest-prisma/setup.ts +++ b/packages/prisma-fabbrica/src/scripts/jest-prisma/setup.ts @@ -1,4 +1,5 @@ import { initialize } from "../../initialize"; +import { resetSequence } from "../../helpers"; beforeAll(() => { if (typeof jestPrisma === "object") { @@ -8,4 +9,6 @@ beforeAll(() => { } }); +beforeEach(() => resetSequence()); + declare var jestPrisma: any; diff --git a/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap b/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap index 379c7ef9..150d64c5 100644 --- a/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap +++ b/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap @@ -5,9 +5,17 @@ exports[`getSourceFile generates TypeScript AST 1`] = ` import { Prisma } from "@prisma/client"; import type { PrismaClient } from "@prisma/client"; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; +import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; -import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; +const modelFieldDefinitions: ModelWithFields[] = [{ + name: "User", + fields: [] + }]; type UserScalarOrEnumFields = { id: number; name: string; @@ -17,35 +25,53 @@ type UserFactoryDefineInput = { name?: string; }; type UserFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGenerateUserScalarsOrEnums(): UserScalarOrEnumFields { +function autoGenerateUserScalarsOrEnums({ seq }: { + readonly seq: number; +}): UserScalarOrEnumFields { return { - id: scalarFieldValueGenerator.Int({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), - name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.Int({ modelName: "User", fieldName: "id", isId: true, isUnique: false, seq }), + name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false, seq }) }; } function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateUserScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("User", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + 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 = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().user.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "User" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } diff --git a/packages/prisma-fabbrica/src/templates/autoGenerateModelScalarsOrEnumsFieldArgs.test.ts b/packages/prisma-fabbrica/src/templates/autoGenerateModelScalarsOrEnumsFieldArgs.test.ts index e8f6641f..25f1e112 100644 --- a/packages/prisma-fabbrica/src/templates/autoGenerateModelScalarsOrEnumsFieldArgs.test.ts +++ b/packages/prisma-fabbrica/src/templates/autoGenerateModelScalarsOrEnumsFieldArgs.test.ts @@ -13,7 +13,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "id", expected: ` - scalarFieldValueGenerator.Int({ modelName: "TestModel", fieldName: "id", isId: true, isUnique: false }) + scalarFieldValueGenerator.Int({ modelName: "TestModel", fieldName: "id", isId: true, isUnique: false, seq }) `, }, { @@ -26,7 +26,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "complexIdField", expected: ` - scalarFieldValueGenerator.Int({ modelName: "TestModel", fieldName: "complexIdField", isId: true, isUnique: false }) + scalarFieldValueGenerator.Int({ modelName: "TestModel", fieldName: "complexIdField", isId: true, isUnique: false, seq }) `, }, { @@ -38,7 +38,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "uniqueField", expected: ` - scalarFieldValueGenerator.String({ modelName: "TestModel", fieldName: "uniqueField", isId: false, isUnique: true }) + scalarFieldValueGenerator.String({ modelName: "TestModel", fieldName: "uniqueField", isId: false, isUnique: true, seq }) `, }, { @@ -50,7 +50,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "booleanField", expected: ` - scalarFieldValueGenerator.Boolean({ modelName: "TestModel", fieldName: "booleanField", isId: false, isUnique: false }) + scalarFieldValueGenerator.Boolean({ modelName: "TestModel", fieldName: "booleanField", isId: false, isUnique: false, seq }) `, }, { @@ -62,7 +62,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "stringField", expected: ` - scalarFieldValueGenerator.String({ modelName: "TestModel", fieldName: "stringField", isId: false, isUnique: false }) + scalarFieldValueGenerator.String({ modelName: "TestModel", fieldName: "stringField", isId: false, isUnique: false, seq }) `, }, { @@ -74,7 +74,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "intField", expected: ` - scalarFieldValueGenerator.Int({ modelName: "TestModel", fieldName: "intField", isId: false, isUnique: false }) + scalarFieldValueGenerator.Int({ modelName: "TestModel", fieldName: "intField", isId: false, isUnique: false, seq }) `, }, { @@ -86,7 +86,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "floatField", expected: ` - scalarFieldValueGenerator.Float({ modelName: "TestModel", fieldName: "floatField", isId: false, isUnique: false }) + scalarFieldValueGenerator.Float({ modelName: "TestModel", fieldName: "floatField", isId: false, isUnique: false, seq }) `, }, { @@ -98,7 +98,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "bigIntField", expected: ` - scalarFieldValueGenerator.BigInt({ modelName: "TestModel", fieldName: "bigIntField", isId: false, isUnique: false }) + scalarFieldValueGenerator.BigInt({ modelName: "TestModel", fieldName: "bigIntField", isId: false, isUnique: false, seq }) `, }, { @@ -110,7 +110,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "dateTimeField", expected: ` - scalarFieldValueGenerator.DateTime({ modelName: "TestModel", fieldName: "dateTimeField", isId: false, isUnique: false }) + scalarFieldValueGenerator.DateTime({ modelName: "TestModel", fieldName: "dateTimeField", isId: false, isUnique: false, seq }) `, }, { @@ -122,7 +122,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "bytesField", expected: ` - scalarFieldValueGenerator.Bytes({ modelName: "TestModel", fieldName: "bytesField", isId: false, isUnique: false }) + scalarFieldValueGenerator.Bytes({ modelName: "TestModel", fieldName: "bytesField", isId: false, isUnique: false, seq }) `, }, { @@ -134,7 +134,7 @@ describe(autoGenerateModelScalarsOrEnumsFieldArgs, () => { `, targetField: "jsonField", expected: ` - scalarFieldValueGenerator.Json({ modelName: "TestModel", fieldName: "jsonField", isId: false, isUnique: false }) + scalarFieldValueGenerator.Json({ modelName: "TestModel", fieldName: "jsonField", isId: false, isUnique: false, seq }) `, }, { diff --git a/packages/prisma-fabbrica/src/templates/index.ts b/packages/prisma-fabbrica/src/templates/index.ts index 50384519..654c22d8 100644 --- a/packages/prisma-fabbrica/src/templates/index.ts +++ b/packages/prisma-fabbrica/src/templates/index.ts @@ -1,11 +1,8 @@ import { DMMF } from "@prisma/generator-helper"; import ts from "typescript"; import { template } from "talt"; -import { camelize, ast } from "../helpers"; - -function byName(name: string | { readonly name: string }) { - return (x: T) => x.name === (typeof name === "string" ? name : name.name); -} +import { camelize, ast, byName, createJSONLiteral } from "../helpers"; +import { createFieldDefinitions } from "../relations"; export function findPrsimaCreateInputTypeFromModelName(document: DMMF.Document, modelName: string) { const search = `${modelName}CreateInput`; @@ -68,9 +65,14 @@ export const header = (prismaClientModuleSpecifier: string) => import { Prisma } from ${() => ast.stringLiteral(prismaClientModuleSpecifier)}; import type { PrismaClient } from ${() => ast.stringLiteral(prismaClientModuleSpecifier)}; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; + import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; - import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; - export { initialize } from "@quramy/prisma-fabbrica"; + import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; + export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; + + type BuildDataOptions = { + readonly seq: number; + }; `(); export const importStatement = (specifier: string, prismaClientModuleSpecifier: string) => @@ -78,6 +80,11 @@ export const importStatement = (specifier: string, prismaClientModuleSpecifier: import { ${() => ast.identifier(specifier)} } from ${() => ast.stringLiteral(prismaClientModuleSpecifier)}; `(); +export const modelFieldDefinitions = (models: DMMF.Model[]) => + template.statement` + const modelFieldDefinitions: ModelWithFields[] = ${() => createJSONLiteral(createFieldDefinitions(models))} +`(); + export const scalarFieldType = ( model: DMMF.Model, fieldName: string, @@ -153,8 +160,7 @@ export const modelBelongsToRelationFactory = (fieldType: DMMF.SchemaArg, model: return template.statement` type ${() => ast.identifier(`${model.name}${fieldType.name}Factory`)} = { _factoryFor: ${() => ast.literalTypeNode(ast.stringLiteral(targetModel.type))}; - buildCreateInput: () => PromiseLike - ast.identifier(fieldType.inputTypes[0].type as string)}["create"]>; + build: () => PromiseLike ast.identifier(fieldType.inputTypes[0].type as string)}["create"]>; }; `(); }; @@ -186,7 +192,7 @@ export const modelFactoryDefineOptions = (modelName: string, isOpionalDefaultDat isOpionalDefaultData ? template.statement` type MODEL_FACTORY_DEFINE_OPTIONS = { - defaultData?: Resolver; + defaultData?: Resolver; }; `({ MODEL_FACTORY_DEFINE_OPTIONS: ast.identifier(`${modelName}FactoryDefineOptions`), @@ -194,7 +200,7 @@ export const modelFactoryDefineOptions = (modelName: string, isOpionalDefaultDat }) : template.statement` type MODEL_FACTORY_DEFINE_OPTIONS = { - defaultData: Resolver; + defaultData: Resolver; }; `({ MODEL_FACTORY_DEFINE_OPTIONS: ast.identifier(`${modelName}FactoryDefineOptions`), @@ -222,7 +228,7 @@ export const autoGenerateModelScalarsOrEnumsFieldArgs = ( ) => field.inputTypes[0].location === "scalar" ? template.expression` - scalarFieldValueGenerator.SCALAR_TYPE({ modelName: MODEL_NAME, fieldName: FIELD_NAME, isId: IS_ID, isUnique: IS_UNIQUE }) + scalarFieldValueGenerator.SCALAR_TYPE({ modelName: MODEL_NAME, fieldName: FIELD_NAME, isId: IS_ID, isUnique: IS_UNIQUE, seq }) `({ SCALAR_TYPE: ast.identifier(field.inputTypes[0].type as string), MODEL_NAME: ast.stringLiteral(model.name), @@ -241,7 +247,7 @@ export const autoGenerateModelScalarsOrEnums = ( enums: DMMF.SchemaEnum[], ) => template.statement` - function AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS(): MODEL_SCALAR_OR_ENUM_FIELDS { + function AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS({ seq }: { readonly seq: number }): MODEL_SCALAR_OR_ENUM_FIELDS { return ${() => ast.objectLiteralExpression( filterRequiredScalarOrEnumFields(inputType).map(field => @@ -260,11 +266,18 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp function DEFINE_MODEL_FACTORY_INERNAL({ defaultData: defaultDataResolver }: MODEL_FACTORY_DEFINE_OPTIONS) { - const buildCreateInput = async ( + + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener(${() => ast.stringLiteral(model.name)}, modelFieldDefinitions); + + const build = async ( inputData: Partial = {} ) => { - const requiredScalarData = AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS() - const defaultData= await resolveValue(defaultDataResolver ?? {}); + const seq = getSeq(); + const requiredScalarData = AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS({ seq }) + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = ${() => ast.objectLiteralExpression( filterBelongsToField(model, inputType).map(field => @@ -272,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`), @@ -285,6 +298,14 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp const data: Prisma.MODEL_CREATE_INPUT = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData}; return data; }; + + const buildList = ( + inputData: number | Partial[] + ) => { + 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( @@ -294,24 +315,38 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp true, )} ); + const create = async ( inputData: Partial = {} ) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().MODEL_KEY.create({ data }); }; + + const createList = ( + inputData: number | Partial[] + ) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + } + const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); + return { _factoryFor: ${() => ast.stringLiteral(model.name)} as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } `({ MODEL_KEY: ast.identifier(camelize(model.name)), DEFINE_MODEL_FACTORY_INERNAL: ast.identifier(`define${model.name}FactoryInternal`), + MODEL_FACTORY_DEFINE_INPUT: ast.identifier(`${model.name}FactoryDefineInput`), MODEL_FACTORY_DEFINE_OPTIONS: ast.identifier(`${model.name}FactoryDefineOptions`), MODEL_CREATE_INPUT: ast.identifier(`${model.name}CreateInput`), AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS: ast.identifier(`autoGenerate${model.name}ScalarsOrEnums`), @@ -358,6 +393,7 @@ export function getSourceFile({ ...modelNames.map(modelName => importStatement(modelName, prismaClientModuleSpecifier)), ...enums.map(enumName => importStatement(enumName, prismaClientModuleSpecifier)), ...header(prismaClientModuleSpecifier).statements, + modelFieldDefinitions(document.datamodel.models), ...document.datamodel.models .map(model => ({ model, createInputType: findPrsimaCreateInputTypeFromModelName(document, model.name) })) .flatMap(({ model, createInputType }) => [ diff --git a/packages/ts-compile-testing/fixtures/field-variation/__generated__/fabbrica/index.ts b/packages/ts-compile-testing/fixtures/field-variation/__generated__/fabbrica/index.ts index 67265b4d..e7ed38ce 100644 --- a/packages/ts-compile-testing/fixtures/field-variation/__generated__/fabbrica/index.ts +++ b/packages/ts-compile-testing/fixtures/field-variation/__generated__/fabbrica/index.ts @@ -4,9 +4,20 @@ import { Role } from "./../client"; import { Prisma } from "./../client"; import type { PrismaClient } from "./../client"; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; +import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; -import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; +const modelFieldDefinitions: ModelWithFields[] = [{ + name: "User", + fields: [] + }, { + name: "ComplexIdModel", + fields: [] + }]; type UserScalarOrEnumFields = { id: string; role: Role; @@ -18,35 +29,53 @@ type UserFactoryDefineInput = { roles?: Prisma.UserCreaterolesInput | Prisma.Enumerable; }; type UserFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGenerateUserScalarsOrEnums(): UserScalarOrEnumFields { +function autoGenerateUserScalarsOrEnums({ seq }: { + readonly seq: number; +}): UserScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), + id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false, seq }), role: "USER" }; } function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateUserScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("User", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + 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 = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().user.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "User" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } @@ -62,36 +91,54 @@ type ComplexIdModelFactoryDefineInput = { lastName?: string; }; type ComplexIdModelFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGenerateComplexIdModelScalarsOrEnums(): ComplexIdModelScalarOrEnumFields { +function autoGenerateComplexIdModelScalarsOrEnums({ seq }: { + readonly seq: number; +}): ComplexIdModelScalarOrEnumFields { return { - firstName: scalarFieldValueGenerator.String({ modelName: "ComplexIdModel", fieldName: "firstName", isId: true, isUnique: false }), - lastName: scalarFieldValueGenerator.String({ modelName: "ComplexIdModel", fieldName: "lastName", isId: true, isUnique: false }) + firstName: scalarFieldValueGenerator.String({ modelName: "ComplexIdModel", fieldName: "firstName", isId: true, isUnique: false, seq }), + lastName: scalarFieldValueGenerator.String({ modelName: "ComplexIdModel", fieldName: "lastName", isId: true, isUnique: false, seq }) }; } function defineComplexIdModelFactoryInternal({ defaultData: defaultDataResolver }: ComplexIdModelFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateComplexIdModelScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("ComplexIdModel", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateComplexIdModelScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.ComplexIdModelCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData: ComplexIdModel) => ({ firstName: inputData.firstName, lastName: inputData.lastName }); const create = async (inputData: Partial = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().complexIdModel.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "ComplexIdModel" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } diff --git a/packages/ts-compile-testing/fixtures/field-variation/sample.ts b/packages/ts-compile-testing/fixtures/field-variation/sample.ts new file mode 100644 index 00000000..cd35febb --- /dev/null +++ b/packages/ts-compile-testing/fixtures/field-variation/sample.ts @@ -0,0 +1,7 @@ +import { defineUserFactory } from "./__generated__/fabbrica"; + +export const UserFactoryWithSeq = defineUserFactory({ + defaultData: async ({ seq }) => ({ + id: seq.toString(), + }), +}); diff --git a/packages/ts-compile-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts b/packages/ts-compile-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts index 75afe714..6c057f30 100644 --- a/packages/ts-compile-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts +++ b/packages/ts-compile-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts @@ -3,9 +3,28 @@ import { Category } from "./../client"; import { Prisma } from "./../client"; import type { PrismaClient } from "./../client"; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; +import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; -import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; +const modelFieldDefinitions: ModelWithFields[] = [{ + name: "Post", + fields: [{ + name: "categories", + type: "Category", + relationName: "CategoryToPost" + }] + }, { + name: "Category", + fields: [{ + name: "posts", + type: "Post", + relationName: "CategoryToPost" + }] + }]; type PostScalarOrEnumFields = { id: string; title: string; @@ -16,35 +35,53 @@ type PostFactoryDefineInput = { categories?: Prisma.CategoryCreateNestedManyWithoutPostsInput; }; type PostFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGeneratePostScalarsOrEnums(): PostScalarOrEnumFields { +function autoGeneratePostScalarsOrEnums({ seq }: { + readonly seq: number; +}): PostScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false }), - title: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "title", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false, seq }), + title: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "title", isId: false, isUnique: false, seq }) }; } function definePostFactoryInternal({ defaultData: defaultDataResolver }: PostFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGeneratePostScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("Post", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGeneratePostScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.PostCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData: Post) => ({ id: inputData.id }); const create = async (inputData: Partial = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().post.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "Post" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } @@ -61,35 +98,53 @@ type CategoryFactoryDefineInput = { posts?: Prisma.PostCreateNestedManyWithoutCategoriesInput; }; type CategoryFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGenerateCategoryScalarsOrEnums(): CategoryScalarOrEnumFields { +function autoGenerateCategoryScalarsOrEnums({ seq }: { + readonly seq: number; +}): CategoryScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "Category", fieldName: "id", isId: true, isUnique: false }), - name: scalarFieldValueGenerator.String({ modelName: "Category", fieldName: "name", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "Category", fieldName: "id", isId: true, isUnique: false, seq }), + name: scalarFieldValueGenerator.String({ modelName: "Category", fieldName: "name", isId: false, isUnique: false, seq }) }; } function defineCategoryFactoryInternal({ defaultData: defaultDataResolver }: CategoryFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateCategoryScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("Category", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateCategoryScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.CategoryCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData: Category) => ({ id: inputData.id }); const create = async (inputData: Partial = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().category.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "Category" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } diff --git a/packages/ts-compile-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts b/packages/ts-compile-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts index f315986e..71eec8db 100644 --- a/packages/ts-compile-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts +++ b/packages/ts-compile-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts @@ -4,9 +4,47 @@ import { Review } from "./../client"; import { Prisma } from "./../client"; import type { PrismaClient } from "./../client"; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; +import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; -import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; +const modelFieldDefinitions: ModelWithFields[] = [{ + name: "User", + fields: [{ + name: "posts", + type: "Post", + relationName: "PostToUser" + }, { + name: "reviews", + type: "Review", + relationName: "ReviewToUser" + }] + }, { + name: "Post", + fields: [{ + name: "author", + type: "User", + relationName: "PostToUser" + }, { + name: "reviews", + type: "Review", + relationName: "PostToReview" + }] + }, { + name: "Review", + fields: [{ + name: "post", + type: "Post", + relationName: "PostToReview" + }, { + name: "reviewer", + type: "User", + relationName: "ReviewToUser" + }] + }]; type UserScalarOrEnumFields = { id: string; name: string; @@ -18,35 +56,53 @@ type UserFactoryDefineInput = { reviews?: Prisma.ReviewCreateNestedManyWithoutReviewerInput; }; type UserFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGenerateUserScalarsOrEnums(): UserScalarOrEnumFields { +function autoGenerateUserScalarsOrEnums({ seq }: { + readonly seq: number; +}): UserScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), - name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false, seq }), + name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false, seq }) }; } function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateUserScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("User", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + 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 = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().user.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "User" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } @@ -59,7 +115,7 @@ type PostScalarOrEnumFields = { }; type PostauthorFactory = { _factoryFor: "User"; - buildCreateInput: () => PromiseLike; + build: () => PromiseLike; }; type PostFactoryDefineInput = { id?: string; @@ -68,42 +124,60 @@ type PostFactoryDefineInput = { reviews?: Prisma.ReviewCreateNestedManyWithoutPostInput; }; type PostFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; function isPostauthorFactory(x: PostauthorFactory | Prisma.UserCreateNestedOneWithoutPostsInput | undefined): x is PostauthorFactory { return (x as any)?._factoryFor === "User"; } -function autoGeneratePostScalarsOrEnums(): PostScalarOrEnumFields { +function autoGeneratePostScalarsOrEnums({ seq }: { + readonly seq: number; +}): PostScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false }), - title: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "title", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false, seq }), + title: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "title", isId: false, isUnique: false, seq }) }; } function definePostFactoryInternal({ defaultData: defaultDataResolver }: PostFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGeneratePostScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("Post", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGeneratePostScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = { author: isPostauthorFactory(defaultData.author) ? { - create: await defaultData.author.buildCreateInput() + create: await defaultData.author.build() } : defaultData.author }; const data: Prisma.PostCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData: Post) => ({ id: inputData.id }); const create = async (inputData: Partial = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().post.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "Post" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } @@ -116,11 +190,11 @@ type ReviewScalarOrEnumFields = { }; type ReviewpostFactory = { _factoryFor: "Post"; - buildCreateInput: () => PromiseLike; + build: () => PromiseLike; }; type ReviewreviewerFactory = { _factoryFor: "User"; - buildCreateInput: () => PromiseLike; + build: () => PromiseLike; }; type ReviewFactoryDefineInput = { id?: string; @@ -129,7 +203,7 @@ type ReviewFactoryDefineInput = { reviewer: ReviewreviewerFactory | Prisma.UserCreateNestedOneWithoutReviewsInput; }; type ReviewFactoryDefineOptions = { - defaultData: Resolver; + defaultData: Resolver; }; function isReviewpostFactory(x: ReviewpostFactory | Prisma.PostCreateNestedOneWithoutReviewsInput | undefined): x is ReviewpostFactory { return (x as any)?._factoryFor === "Post"; @@ -137,40 +211,58 @@ function isReviewpostFactory(x: ReviewpostFactory | Prisma.PostCreateNestedOneWi function isReviewreviewerFactory(x: ReviewreviewerFactory | Prisma.UserCreateNestedOneWithoutReviewsInput | undefined): x is ReviewreviewerFactory { return (x as any)?._factoryFor === "User"; } -function autoGenerateReviewScalarsOrEnums(): ReviewScalarOrEnumFields { +function autoGenerateReviewScalarsOrEnums({ seq }: { + readonly seq: number; +}): ReviewScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "Review", fieldName: "id", isId: true, isUnique: false }), - body: scalarFieldValueGenerator.String({ modelName: "Review", fieldName: "body", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "Review", fieldName: "id", isId: true, isUnique: false, seq }), + body: scalarFieldValueGenerator.String({ modelName: "Review", fieldName: "body", isId: false, isUnique: false, seq }) }; } function defineReviewFactoryInternal({ defaultData: defaultDataResolver }: ReviewFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateReviewScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("Review", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateReviewScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = { post: isReviewpostFactory(defaultData.post) ? { - create: await defaultData.post.buildCreateInput() + create: await defaultData.post.build() } : defaultData.post, reviewer: isReviewreviewerFactory(defaultData.reviewer) ? { - create: await defaultData.reviewer.buildCreateInput() + create: await defaultData.reviewer.build() } : defaultData.reviewer }; const data: Prisma.ReviewCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData: Review) => ({ id: inputData.id }); const create = async (inputData: Partial = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().review.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "Review" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } diff --git a/packages/ts-compile-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts b/packages/ts-compile-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts index 20374988..2b5f7d93 100644 --- a/packages/ts-compile-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts +++ b/packages/ts-compile-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts @@ -3,16 +3,35 @@ import { Profile } from "./../client"; import { Prisma } from "./../client"; import type { PrismaClient } from "./../client"; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; +import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; -import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; +const modelFieldDefinitions: ModelWithFields[] = [{ + name: "User", + fields: [{ + name: "profile", + type: "Profile", + relationName: "ProfileToUser" + }] + }, { + name: "Profile", + fields: [{ + name: "user", + type: "User", + relationName: "ProfileToUser" + }] + }]; type UserScalarOrEnumFields = { id: string; name: string; }; type UserprofileFactory = { _factoryFor: "Profile"; - buildCreateInput: () => PromiseLike; + build: () => PromiseLike; }; type UserFactoryDefineInput = { id?: string; @@ -20,42 +39,60 @@ type UserFactoryDefineInput = { profile?: UserprofileFactory | Prisma.ProfileCreateNestedOneWithoutUserInput; }; type UserFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; function isUserprofileFactory(x: UserprofileFactory | Prisma.ProfileCreateNestedOneWithoutUserInput | undefined): x is UserprofileFactory { return (x as any)?._factoryFor === "Profile"; } -function autoGenerateUserScalarsOrEnums(): UserScalarOrEnumFields { +function autoGenerateUserScalarsOrEnums({ seq }: { + readonly seq: number; +}): UserScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), - name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false, seq }), + name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false, seq }) }; } function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateUserScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("User", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = { profile: isUserprofileFactory(defaultData.profile) ? { - create: await defaultData.profile.buildCreateInput() + create: await defaultData.profile.build() } : defaultData.profile }; const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + 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 = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().user.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "User" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } @@ -67,48 +104,66 @@ type ProfileScalarOrEnumFields = { }; type ProfileuserFactory = { _factoryFor: "User"; - buildCreateInput: () => PromiseLike; + build: () => PromiseLike; }; type ProfileFactoryDefineInput = { id?: string; user: ProfileuserFactory | Prisma.UserCreateNestedOneWithoutProfileInput; }; type ProfileFactoryDefineOptions = { - defaultData: Resolver; + defaultData: Resolver; }; function isProfileuserFactory(x: ProfileuserFactory | Prisma.UserCreateNestedOneWithoutProfileInput | undefined): x is ProfileuserFactory { return (x as any)?._factoryFor === "User"; } -function autoGenerateProfileScalarsOrEnums(): ProfileScalarOrEnumFields { +function autoGenerateProfileScalarsOrEnums({ seq }: { + readonly seq: number; +}): ProfileScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "Profile", fieldName: "id", isId: true, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "Profile", fieldName: "id", isId: true, isUnique: false, seq }) }; } function defineProfileFactoryInternal({ defaultData: defaultDataResolver }: ProfileFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateProfileScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("Profile", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateProfileScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = { user: isProfileuserFactory(defaultData.user) ? { - create: await defaultData.user.buildCreateInput() + create: await defaultData.user.build() } : defaultData.user }; const data: Prisma.ProfileCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => build(data))); + }; const pickForConnect = (inputData: Profile) => ({ id: inputData.id }); const create = async (inputData: Partial = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().profile.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "Profile" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; } diff --git a/packages/ts-compile-testing/fixtures/simple-model/__generated__/fabbrica/index.ts b/packages/ts-compile-testing/fixtures/simple-model/__generated__/fabbrica/index.ts index 41ecf916..8b34bc49 100644 --- a/packages/ts-compile-testing/fixtures/simple-model/__generated__/fabbrica/index.ts +++ b/packages/ts-compile-testing/fixtures/simple-model/__generated__/fabbrica/index.ts @@ -2,9 +2,17 @@ import { User } from "./../client"; import { Prisma } from "./../client"; import type { PrismaClient } from "./../client"; import { getClient } from "@quramy/prisma-fabbrica/lib/clientHolder"; +import { ModelWithFields, createScreener } from "@quramy/prisma-fabbrica/lib/relations"; import scalarFieldValueGenerator from "@quramy/prisma-fabbrica/lib/scalar/gen"; -import { Resolver, resolveValue } from "@quramy/prisma-fabbrica/lib/helpers"; -export { initialize } from "@quramy/prisma-fabbrica"; +import { Resolver, normalizeResolver, getSequenceCounter } from "@quramy/prisma-fabbrica/lib/helpers"; +export { initialize, resetSequence } from "@quramy/prisma-fabbrica"; +type BuildDataOptions = { + readonly seq: number; +}; +const modelFieldDefinitions: ModelWithFields[] = [{ + name: "User", + fields: [] + }]; type UserScalarOrEnumFields = { id: string; name: string; @@ -14,35 +22,53 @@ type UserFactoryDefineInput = { name?: string; }; type UserFactoryDefineOptions = { - defaultData?: Resolver; + defaultData?: Resolver; }; -function autoGenerateUserScalarsOrEnums(): UserScalarOrEnumFields { +function autoGenerateUserScalarsOrEnums({ seq }: { + readonly seq: number; +}): UserScalarOrEnumFields { return { - id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), - name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false }) + id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false, seq }), + name: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "name", isId: false, isUnique: false, seq }) }; } function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFactoryDefineOptions) { - const buildCreateInput = async (inputData: Partial = {}) => { - const requiredScalarData = autoGenerateUserScalarsOrEnums(); - const defaultData = await resolveValue(defaultDataResolver ?? {}); + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("User", modelFieldDefinitions); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateUserScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver(defaultDataResolver ?? {}); + const defaultData = await resolveValue({ seq }); const defaultAssociations = {}; const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; + const buildList = (inputData: number | Partial[]) => { + 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 = {}) => { - const data = await buildCreateInput(inputData); + const data = await build(inputData).then(screen); return await getClient().user.create({ data }); }; + const createList = (inputData: number | Partial[]) => { + const list = typeof inputData === "number" ? [...new Array(inputData).keys()].map(() => ({})) : inputData; + return Promise.all(list.map(data => create(data))); + }; const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); return { _factoryFor: "User" as const, - buildCreateInput, + build, + buildList, + buildCreateInput: build, pickForConnect, create, + createList, createForConnect, }; }