diff --git a/examples/example-prj/src/__generated__/fabbrica/index.js b/examples/example-prj/src/__generated__/fabbrica/index.js index f8d00048..913f225a 100644 --- a/examples/example-prj/src/__generated__/fabbrica/index.js +++ b/examples/example-prj/src/__generated__/fabbrica/index.js @@ -44,7 +44,7 @@ function defineUserFactory(args = {}) { } exports.defineUserFactory = defineUserFactory; function isPostauthorFactory(x) { - return x._factoryFor === "User"; + return x?._factoryFor === "User"; } function autoGeneratePostScalarsOrEnums() { return { diff --git a/packages/prisma-fabbrica/src/templates/index.ts b/packages/prisma-fabbrica/src/templates/index.ts index e81d724e..f45473d7 100644 --- a/packages/prisma-fabbrica/src/templates/index.ts +++ b/packages/prisma-fabbrica/src/templates/index.ts @@ -4,6 +4,10 @@ import { template } from "talt"; type StripCreate = T extends `create${infer S}` ? Uncapitalize : T; +function byName(name: string | { readonly name: string }) { + return (x: T) => x.name === (typeof name === "string" ? name : name.name); +} + function camelize(pascal: string) { return pascal[0].toLowerCase() + pascal.slice(1); } @@ -56,6 +60,12 @@ function filterRequiredInputObjectTypeField(inputType: DMMF.InputType) { return filterRequiredFields(inputType).filter(isInputObjectTypeField); } +function filterBelongsToField(model: DMMF.Model, inputType: DMMF.InputType) { + return inputType.fields + .filter(isInputObjectTypeField) + .filter(field => model.fields.find(byName(field))?.isList === false); +} + function filterEnumFields(inputType: DMMF.InputType) { return inputType.fields.filter( field => @@ -65,7 +75,7 @@ function filterEnumFields(inputType: DMMF.InputType) { function extractFirstEnumValue(enums: DMMF.SchemaEnum[], field: DMMF.SchemaArg) { const typeName = field.inputTypes[0].type; - const found = enums.find(e => e.name === field.inputTypes[0].type); + const found = enums.find(byName(typeName)); if (!found) { throw new Error(`Not found enum ${typeName}`); } @@ -88,7 +98,7 @@ export const importStatement = (specifier: string, prismaClientModuleSpecifier: `(); export const scalarFieldType = ( - modelName: string, + model: DMMF.Model, fieldName: string, inputType: DMMF.SchemaArgInputType, ): ts.TypeNode => { @@ -116,14 +126,14 @@ export const scalarFieldType = ( // return template.typeNode`Prisma.Json`(); return ast.keywordTypeNode(ts.SyntaxKind.AnyKeyword); default: - throw new Error(`Unknown scalar type "${inputType.type}" for ${modelName}.${fieldName} .`); + throw new Error(`Unknown scalar type "${inputType.type}" for ${model.name}.${fieldName} .`); } }; -export const argInputType = (modelName: string, fieldName: string, inputType: DMMF.SchemaArgInputType): ts.TypeNode => { +export const argInputType = (model: DMMF.Model, fieldName: string, inputType: DMMF.SchemaArgInputType): ts.TypeNode => { const fieldType = () => { if (inputType.location === "scalar") { - return scalarFieldType(modelName, fieldName, inputType); + return scalarFieldType(model, fieldName, inputType); } else if (inputType.location === "enumTypes") { return ast.typeReferenceNode(ast.identifier(inputType.type as string)); } else if (inputType.location === "outputObjectTypes" || inputType.location === "inputObjectTypes") { @@ -140,7 +150,7 @@ export const argInputType = (modelName: string, fieldName: string, inputType: DM : fieldType(); }; -export const modelScalarOrEnumFields = (modelName: string, inputType: DMMF.InputType) => +export const modelScalarOrEnumFields = (model: DMMF.Model, inputType: DMMF.InputType) => template.statement` type MODEL_SCALAR_OR_ENUM_FIELDS = ${() => ast.typeLiteralNode( @@ -149,18 +159,16 @@ export const modelScalarOrEnumFields = (modelName: string, inputType: DMMF.Input undefined, field.name, undefined, - ast.unionTypeNode( - field.inputTypes.map(childInputType => argInputType(modelName, field.name, childInputType)), - ), + ast.unionTypeNode(field.inputTypes.map(childInputType => argInputType(model, field.name, childInputType))), ), ), )} `({ - MODEL_SCALAR_OR_ENUM_FIELDS: ast.identifier(`${modelName}ScalarOrEnumFields`), + MODEL_SCALAR_OR_ENUM_FIELDS: ast.identifier(`${model.name}ScalarOrEnumFields`), }); export const modelBelongsToRelationFactory = (fieldType: DMMF.SchemaArg, model: DMMF.Model) => { - const targetModel = model.fields.find(f => f.name === fieldType.name)!; + const targetModel = model.fields.find(byName(fieldType))!; return template.statement` type ${() => ast.identifier(`${model.name}${fieldType.name}Factory`)} = { _factoryFor: ${() => ast.literalTypeNode(ast.stringLiteral(targetModel.type))}; @@ -170,7 +178,7 @@ export const modelBelongsToRelationFactory = (fieldType: DMMF.SchemaArg, model: `(); }; -export const modelFactoryDefineInput = (modelName: string, inputType: DMMF.InputType) => +export const modelFactoryDefineInput = (model: DMMF.Model, inputType: DMMF.InputType) => template.statement` type MODEL_FACTORY_DEFINE_INPUT = ${() => ast.typeLiteralNode( @@ -180,16 +188,17 @@ export const modelFactoryDefineInput = (modelName: string, inputType: DMMF.Input field.name, !field.isRequired || isScalarOrEnumField(field) ? ast.token(ts.SyntaxKind.QuestionToken) : undefined, ast.unionTypeNode([ - ...(field.isRequired && isInputObjectTypeField(field) - ? [ast.typeReferenceNode(ast.identifier(`${modelName}${field.name}Factory`))] + ...((field.isRequired || model.fields.find(byName(field))!.isList === false) && + isInputObjectTypeField(field) + ? [ast.typeReferenceNode(ast.identifier(`${model.name}${field.name}Factory`))] : []), - ...field.inputTypes.map(childInputType => argInputType(modelName, field.name, childInputType)), + ...field.inputTypes.map(childInputType => argInputType(model, field.name, childInputType)), ]), ), ), )}; `({ - MODEL_FACTORY_DEFINE_INPUT: ast.identifier(`${modelName}FactoryDefineInput`), + MODEL_FACTORY_DEFINE_INPUT: ast.identifier(`${model.name}FactoryDefineInput`), }); export const modelFactoryDefineOptions = (modelName: string, isOpionalDefaultData: boolean) => @@ -212,12 +221,13 @@ export const modelFactoryDefineOptions = (modelName: string, isOpionalDefaultDat }); export const isModelAssociationFactory = (fieldType: DMMF.SchemaArg, model: DMMF.Model) => { - const targetModel = model.fields.find(f => f.name === fieldType.name)!; + const targetModel = model.fields.find(byName(fieldType))!; return template.statement` function ${() => ast.identifier(`is${model.name}${fieldType.name}Factory`)}( - x: MODEL_BELONGS_TO_RELATION_FACTORY | ${() => argInputType(model.name, fieldType.name, fieldType.inputTypes[0])} + x: MODEL_BELONGS_TO_RELATION_FACTORY | ${() => + argInputType(model, fieldType.name, fieldType.inputTypes[0])} | undefined ): x is MODEL_BELONGS_TO_RELATION_FACTORY { - return (x as any)._factoryFor === ${() => ast.stringLiteral(targetModel.type)}; + return (x as any)?._factoryFor === ${() => ast.stringLiteral(targetModel.type)}; } `({ MODEL_BELONGS_TO_RELATION_FACTORY: ast.typeReferenceNode(`${model.name}${fieldType.name}Factory`), @@ -237,10 +247,10 @@ export const autoGenerateModelScalarsOrEnumsFieldArgs = ( MODEL_NAME: ast.stringLiteral(model.name), FIELD_NAME: ast.stringLiteral(field.name), IS_ID: - model.fields.find(f => f.name === field.name)!.isId || model.primaryKey?.fields.includes(field.name) + model.fields.find(byName(field))!.isId || model.primaryKey?.fields.includes(field.name) ? ast.true() : ast.false(), - IS_UNIQUE: model.fields.find(f => f.name === field.name)!.isUnique ? ast.true() : ast.false(), + IS_UNIQUE: model.fields.find(byName(field))!.isUnique ? ast.true() : ast.false(), }) : ast.stringLiteral(extractFirstEnumValue(enums, field)); @@ -279,7 +289,7 @@ export const defineModelFactoryInernal = (model: DMMF.Model, inputType: DMMF.Inp const defaultData= await resolveValue(defaultDataResolver ?? {}); const defaultAssociations = ${() => ast.objectLiteralExpression( - filterRequiredInputObjectTypeField(inputType).map(field => + filterBelongsToField(model, inputType).map(field => ast.propertyAssignment( field.name, template.expression` @@ -373,15 +383,13 @@ export function getSourceFile({ ...document.datamodel.models .map(model => ({ model, createInputType: findPrsimaCreateInputTypeFromModelName(document, model.name) })) .flatMap(({ model, createInputType }) => [ - modelScalarOrEnumFields(model.name, createInputType), - ...filterRequiredInputObjectTypeField(createInputType).map(fieldType => + modelScalarOrEnumFields(model, createInputType), + ...filterBelongsToField(model, createInputType).map(fieldType => modelBelongsToRelationFactory(fieldType, model), ), - modelFactoryDefineInput(model.name, createInputType), + modelFactoryDefineInput(model, createInputType), modelFactoryDefineOptions(model.name, filterRequiredInputObjectTypeField(createInputType).length === 0), - ...filterRequiredInputObjectTypeField(createInputType).map(fieldType => - isModelAssociationFactory(fieldType, model), - ), + ...filterBelongsToField(model, createInputType).map(fieldType => isModelAssociationFactory(fieldType, model)), autoGenerateModelScalarsOrEnums(model, createInputType, document.schema.enumTypes.model ?? []), defineModelFactoryInernal(model, createInputType), defineModelFactory(model.name, createInputType), diff --git a/packages/prisma-fabbrica/src/templates/modelScalarOrEnumFields.test.ts b/packages/prisma-fabbrica/src/templates/modelScalarOrEnumFields.test.ts index 37d7c942..c7401cb0 100644 --- a/packages/prisma-fabbrica/src/templates/modelScalarOrEnumFields.test.ts +++ b/packages/prisma-fabbrica/src/templates/modelScalarOrEnumFields.test.ts @@ -52,7 +52,7 @@ describe(modelScalarOrEnumFields, () => { }); const inputType = findPrsimaCreateInputTypeFromModelName(dmmf, "TestModel"); const source = template.statement(expected)(); - expect(printNode(modelScalarOrEnumFields("TestModel", inputType))).toBe(printNode(source).trim()); + expect(printNode(modelScalarOrEnumFields(dmmf.datamodel.models[0], inputType))).toBe(printNode(source).trim()); }); it("does not generate for nullable field", async () => { @@ -70,6 +70,6 @@ describe(modelScalarOrEnumFields, () => { id: number; } `(); - expect(printNode(modelScalarOrEnumFields("TestModel", inputType))).toBe(printNode(expected).trim()); + expect(printNode(modelScalarOrEnumFields(dmmf.datamodel.models[0], inputType))).toBe(printNode(expected).trim()); }); }); 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 0e6465bf..f315986e 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 @@ -57,15 +57,22 @@ type PostScalarOrEnumFields = { id: string; title: string; }; +type PostauthorFactory = { + _factoryFor: "User"; + buildCreateInput: () => PromiseLike; +}; type PostFactoryDefineInput = { id?: string; title?: string; - author?: Prisma.UserCreateNestedOneWithoutPostsInput; + author?: PostauthorFactory | Prisma.UserCreateNestedOneWithoutPostsInput; reviews?: Prisma.ReviewCreateNestedManyWithoutPostInput; }; type PostFactoryDefineOptions = { defaultData?: Resolver; }; +function isPostauthorFactory(x: PostauthorFactory | Prisma.UserCreateNestedOneWithoutPostsInput | undefined): x is PostauthorFactory { + return (x as any)?._factoryFor === "User"; +} function autoGeneratePostScalarsOrEnums(): PostScalarOrEnumFields { return { id: scalarFieldValueGenerator.String({ modelName: "Post", fieldName: "id", isId: true, isUnique: false }), @@ -76,7 +83,11 @@ function definePostFactoryInternal({ defaultData: defaultDataResolver }: PostFac const buildCreateInput = async (inputData: Partial = {}) => { const requiredScalarData = autoGeneratePostScalarsOrEnums(); const defaultData = await resolveValue(defaultDataResolver ?? {}); - const defaultAssociations = {}; + const defaultAssociations = { + author: isPostauthorFactory(defaultData.author) ? { + create: await defaultData.author.buildCreateInput() + } : defaultData.author + }; const data: Prisma.PostCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; @@ -120,11 +131,11 @@ type ReviewFactoryDefineInput = { type ReviewFactoryDefineOptions = { defaultData: Resolver; }; -function isReviewpostFactory(x: ReviewpostFactory | Prisma.PostCreateNestedOneWithoutReviewsInput): x is ReviewpostFactory { - return (x as any)._factoryFor === "Post"; +function isReviewpostFactory(x: ReviewpostFactory | Prisma.PostCreateNestedOneWithoutReviewsInput | undefined): x is ReviewpostFactory { + return (x as any)?._factoryFor === "Post"; } -function isReviewreviewerFactory(x: ReviewreviewerFactory | Prisma.UserCreateNestedOneWithoutReviewsInput): x is ReviewreviewerFactory { - return (x as any)._factoryFor === "User"; +function isReviewreviewerFactory(x: ReviewreviewerFactory | Prisma.UserCreateNestedOneWithoutReviewsInput | undefined): x is ReviewreviewerFactory { + return (x as any)?._factoryFor === "User"; } function autoGenerateReviewScalarsOrEnums(): ReviewScalarOrEnumFields { return { diff --git a/packages/ts-compile-testing/fixtures/relations-one-to-many/sample.ts b/packages/ts-compile-testing/fixtures/relations-one-to-many/sample.ts index 944e5e37..e9b1fab3 100644 --- a/packages/ts-compile-testing/fixtures/relations-one-to-many/sample.ts +++ b/packages/ts-compile-testing/fixtures/relations-one-to-many/sample.ts @@ -8,3 +8,8 @@ export const ReviewFactory = defineReviewFactory({ reviewer: UserFactory, }, }); +export const PostFactoryAlt = definePostFactory({ + defaultData: { + author: UserFactory, + }, +}); 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 5f2555aa..20374988 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 @@ -10,14 +10,21 @@ type UserScalarOrEnumFields = { id: string; name: string; }; +type UserprofileFactory = { + _factoryFor: "Profile"; + buildCreateInput: () => PromiseLike; +}; type UserFactoryDefineInput = { id?: string; name?: string; - profile?: Prisma.ProfileCreateNestedOneWithoutUserInput; + profile?: UserprofileFactory | Prisma.ProfileCreateNestedOneWithoutUserInput; }; type UserFactoryDefineOptions = { defaultData?: Resolver; }; +function isUserprofileFactory(x: UserprofileFactory | Prisma.ProfileCreateNestedOneWithoutUserInput | undefined): x is UserprofileFactory { + return (x as any)?._factoryFor === "Profile"; +} function autoGenerateUserScalarsOrEnums(): UserScalarOrEnumFields { return { id: scalarFieldValueGenerator.String({ modelName: "User", fieldName: "id", isId: true, isUnique: false }), @@ -28,7 +35,11 @@ function defineUserFactoryInternal({ defaultData: defaultDataResolver }: UserFac const buildCreateInput = async (inputData: Partial = {}) => { const requiredScalarData = autoGenerateUserScalarsOrEnums(); const defaultData = await resolveValue(defaultDataResolver ?? {}); - const defaultAssociations = {}; + const defaultAssociations = { + profile: isUserprofileFactory(defaultData.profile) ? { + create: await defaultData.profile.buildCreateInput() + } : defaultData.profile + }; const data: Prisma.UserCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...inputData }; return data; }; @@ -65,8 +76,8 @@ type ProfileFactoryDefineInput = { type ProfileFactoryDefineOptions = { defaultData: Resolver; }; -function isProfileuserFactory(x: ProfileuserFactory | Prisma.UserCreateNestedOneWithoutProfileInput): x is ProfileuserFactory { - return (x as any)._factoryFor === "User"; +function isProfileuserFactory(x: ProfileuserFactory | Prisma.UserCreateNestedOneWithoutProfileInput | undefined): x is ProfileuserFactory { + return (x as any)?._factoryFor === "User"; } function autoGenerateProfileScalarsOrEnums(): ProfileScalarOrEnumFields { return {