diff --git a/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts b/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts index a5dedde3..115fc2e1 100644 --- a/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts +++ b/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts @@ -2,6 +2,7 @@ import type { EnumModel } from "../client/client.js"; import type { ComplexIdModel } from "../client/client.js"; import type { FieldTypePatternModel } from "../client/client.js"; import type { NoPkModel } from "../client/client.js"; +import type { NoRequiredScalarModel } from "../client/client.js"; import type { SampleEnum } from "../client/client.js"; import type { Prisma, PrismaClient } from "../client/client.js"; import { createInitializer, createScreener, getScalarFieldValueGenerator, normalizeResolver, normalizeList, getSequenceCounter, createCallbackChain, destructure } from "@quramy/prisma-fabbrica/lib/internal"; @@ -38,6 +39,9 @@ const modelFieldDefinitions: ModelWithFields[] = [{ }, { name: "NoPkModel", fields: [] + }, { + name: "NoRequiredScalarModel", + fields: [] }, { name: "UnsupportedModel", fields: [] @@ -634,3 +638,136 @@ export const defineNoPkModelFactory = ( options => defineNoPkModelFactoryInternal(options ?? {}, defaultTransientFieldValues); + +type NoRequiredScalarModelScalarOrEnumFields = {}; + +type NoRequiredScalarModelFactoryDefineInput = { + id?: string; +}; + +type NoRequiredScalarModelTransientFields = Record & Partial>; + +type NoRequiredScalarModelFactoryTrait> = { + data?: Resolver, BuildDataOptions>; +} & CallbackDefineOptions; + +type NoRequiredScalarModelFactoryDefineOptions = Record> = { + defaultData?: Resolver>; + traits?: { + [traitName: TraitName]: NoRequiredScalarModelFactoryTrait; + }; +} & CallbackDefineOptions; + +type NoRequiredScalarModelTraitKeys> = Exclude; + +export interface NoRequiredScalarModelFactoryInterfaceWithoutTraits> { + readonly _factoryFor: "NoRequiredScalarModel"; + build(inputData?: Partial): PromiseLike; + buildCreateInput(inputData?: Partial): PromiseLike; + buildList(list: readonly Partial[]): PromiseLike; + buildList(count: number, item?: Partial): PromiseLike; + pickForConnect(inputData: NoRequiredScalarModel): Pick; + create(inputData?: Partial): PromiseLike; + createList(list: readonly Partial[]): PromiseLike; + createList(count: number, item?: Partial): PromiseLike; + createForConnect(inputData?: Partial): PromiseLike>; +} + +export interface NoRequiredScalarModelFactoryInterface = Record, TTraitName extends TraitName = TraitName> extends NoRequiredScalarModelFactoryInterfaceWithoutTraits { + use(name: TTraitName, ...names: readonly TTraitName[]): NoRequiredScalarModelFactoryInterfaceWithoutTraits; +} + +function autoGenerateNoRequiredScalarModelScalarsOrEnums({}: { + readonly seq: number; +}): NoRequiredScalarModelScalarOrEnumFields { + return {}; +} + +function defineNoRequiredScalarModelFactoryInternal, TOptions extends NoRequiredScalarModelFactoryDefineOptions>({ defaultData: defaultDataResolver, onAfterBuild, onBeforeCreate, onAfterCreate, traits: traitsDefs = {} }: TOptions, defaultTransientFieldValues: TTransients): NoRequiredScalarModelFactoryInterface> { + const getFactoryWithTraits = (traitKeys: readonly NoRequiredScalarModelTraitKeys[] = []) => { + const seqKey = {}; + const getSeq = () => getSequenceCounter(seqKey); + const screen = createScreener("NoRequiredScalarModel", modelFieldDefinitions); + const handleAfterBuild = createCallbackChain([ + onAfterBuild, + ...traitKeys.map(traitKey => traitsDefs[traitKey]?.onAfterBuild), + ]); + const handleBeforeCreate = createCallbackChain([ + ...traitKeys.slice().reverse().map(traitKey => traitsDefs[traitKey]?.onBeforeCreate), + onBeforeCreate, + ]); + const handleAfterCreate = createCallbackChain([ + onAfterCreate, + ...traitKeys.map(traitKey => traitsDefs[traitKey]?.onAfterCreate), + ]); + const build = async (inputData: Partial = {}) => { + const seq = getSeq(); + const requiredScalarData = autoGenerateNoRequiredScalarModelScalarsOrEnums({ seq }); + const resolveValue = normalizeResolver>(defaultDataResolver ?? {}); + const [transientFields, filteredInputData] = destructure(defaultTransientFieldValues, inputData); + const resolverInput = { seq, ...transientFields }; + const defaultData = await traitKeys.reduce(async (queue, traitKey) => { + const acc = await queue; + const resolveTraitValue = normalizeResolver, BuildDataOptions>(traitsDefs[traitKey]?.data ?? {}); + const traitData = await resolveTraitValue(resolverInput); + return { + ...acc, + ...traitData, + }; + }, resolveValue(resolverInput)); + const defaultAssociations = {} as Prisma.NoRequiredScalarModelCreateInput; + const data: Prisma.NoRequiredScalarModelCreateInput = { ...requiredScalarData, ...defaultData, ...defaultAssociations, ...filteredInputData }; + await handleAfterBuild(data, transientFields); + return data; + }; + const buildList = (...args: unknown[]) => Promise.all(normalizeList>(...args).map(data => build(data))); + const pickForConnect = (inputData: NoRequiredScalarModel) => ({ + id: inputData.id + }); + const create = async (inputData: Partial = {}) => { + const data = await build({ ...inputData }).then(screen); + const [transientFields] = destructure(defaultTransientFieldValues, inputData); + await handleBeforeCreate(data, transientFields); + const createdData = await getClient().noRequiredScalarModel.create({ data }); + await handleAfterCreate(createdData, transientFields); + return createdData; + }; + const createList = (...args: unknown[]) => Promise.all(normalizeList>(...args).map(data => create(data))); + const createForConnect = (inputData: Partial = {}) => create(inputData).then(pickForConnect); + return { + _factoryFor: "NoRequiredScalarModel" as const, + build, + buildList, + buildCreateInput: build, + pickForConnect, + create, + createList, + createForConnect, + }; + }; + const factory = getFactoryWithTraits(); + const useTraits = (name: NoRequiredScalarModelTraitKeys, ...names: readonly NoRequiredScalarModelTraitKeys[]) => { + return getFactoryWithTraits([name, ...names]); + }; + return { + ...factory, + use: useTraits, + }; +} + +interface NoRequiredScalarModelFactoryBuilder { + (options?: TOptions): NoRequiredScalarModelFactoryInterface<{}, NoRequiredScalarModelTraitKeys>; + withTransientFields: (defaultTransientFieldValues: TTransients) => >(options?: TOptions) => NoRequiredScalarModelFactoryInterface>; +} + +/** + * Define factory for {@link NoRequiredScalarModel} model. + * + * @param options + * @returns factory {@link NoRequiredScalarModelFactoryInterface} + */ +export const defineNoRequiredScalarModelFactory = ((options?: TOptions): NoRequiredScalarModelFactoryInterface => { + return defineNoRequiredScalarModelFactoryInternal(options ?? {}, {}); +}) as NoRequiredScalarModelFactoryBuilder; + +defineNoRequiredScalarModelFactory.withTransientFields = defaultTransientFieldValues => options => defineNoRequiredScalarModelFactoryInternal(options ?? {}, defaultTransientFieldValues); diff --git a/packages/artifact-testing/fixtures/field-variation/schema.prisma b/packages/artifact-testing/fixtures/field-variation/schema.prisma index 529a676a..2a90165f 100644 --- a/packages/artifact-testing/fixtures/field-variation/schema.prisma +++ b/packages/artifact-testing/fixtures/field-variation/schema.prisma @@ -59,6 +59,10 @@ model NoPkModel { id Int @unique } +model NoRequiredScalarModel { + id String @id @default(cuid()) +} + model UnsupportedModel { id Int @id unsupportedField Unsupported("tsvector") diff --git a/packages/prisma-fabbrica/src/templates/index.ts b/packages/prisma-fabbrica/src/templates/index.ts index 9518426b..12c1d6e5 100644 --- a/packages/prisma-fabbrica/src/templates/index.ts +++ b/packages/prisma-fabbrica/src/templates/index.ts @@ -371,21 +371,28 @@ export const autoGenerateModelScalarsOrEnums = ( model: DMMF.Model, inputType: DMMF.InputType, enums: readonly DMMF.SchemaEnum[], -) => - template.statement` - function AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS({ seq }: { readonly seq: number }): MODEL_SCALAR_OR_ENUM_FIELDS { - return ${() => - ast.objectLiteralExpression( - filterRequiredScalarOrEnumFields(inputType).map(field => - ast.propertyAssignment(field.name, autoGenerateModelScalarsOrEnumsFieldArgs(model, field, enums)), - ), - true, - )}; - } - `({ +) => { + const requiredFields = filterRequiredScalarOrEnumFields(inputType); + const compiled = requiredFields.length + ? template.statement` + function AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS({ seq }: { readonly seq: number }): MODEL_SCALAR_OR_ENUM_FIELDS { + return ${() => + ast.objectLiteralExpression( + requiredFields.map(field => + ast.propertyAssignment(field.name, autoGenerateModelScalarsOrEnumsFieldArgs(model, field, enums)), + ), + true, + )}; + }` + : template.statement` + function AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS({}: { readonly seq: number }): MODEL_SCALAR_OR_ENUM_FIELDS { + return {}; + }`; + return compiled({ AUTO_GENERATE_MODEL_SCALARS_OR_ENUMS: ast.identifier(`autoGenerate${model.name}ScalarsOrEnums`), MODEL_SCALAR_OR_ENUM_FIELDS: ast.identifier(`${model.name}ScalarOrEnumFields`), }); +}; export const defineModelFactoryInternal = (document: DMMF.Document, model: DMMF.Model, inputType: DMMF.InputType) => template.statement`