Skip to content

Commit

Permalink
fix(client): createManyAndReturn types (prisma#24231)
Browse files Browse the repository at this point in the history
Jolg42 authored May 21, 2024
1 parent 8555ec4 commit 05ca9b0
Showing 9 changed files with 653 additions and 271 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

16 changes: 5 additions & 11 deletions packages/client/src/generation/TSClient/Args.ts
Original file line number Diff line number Diff line change
@@ -46,15 +46,12 @@ export class ArgsTypeBuilder {
return this
}

addSelectArg(): this {
addSelectArg(selectTypeName: string = getSelectName(this.type.name)): this {
this.addProperty(
ts
.property(
'select',
ts.unionType([
ts.namedType(getSelectName(this.type.name)).addGenericArgument(extArgsParam.toArgument()),
ts.nullType,
]),
ts.unionType([ts.namedType(selectTypeName).addGenericArgument(extArgsParam.toArgument()), ts.nullType]),
)
.optional()
.setDocComment(ts.docComment(`Select specific fields to fetch from the ${this.type.name}`)),
@@ -63,8 +60,8 @@ export class ArgsTypeBuilder {
return this
}

addIncludeArgIfHasRelations(): this {
const hasRelationField = this.type.fields.some((f) => f.outputType.location === 'outputObjectTypes')
addIncludeArgIfHasRelations(includeTypeName = getIncludeName(this.type.name), type = this.type): this {
const hasRelationField = type.fields.some((f) => f.outputType.location === 'outputObjectTypes')
if (!hasRelationField) {
return this
}
@@ -73,10 +70,7 @@ export class ArgsTypeBuilder {
ts
.property(
'include',
ts.unionType([
ts.namedType(getIncludeName(this.type.name)).addGenericArgument(extArgsParam.toArgument()),
ts.nullType,
]),
ts.unionType([ts.namedType(includeTypeName).addGenericArgument(extArgsParam.toArgument()), ts.nullType]),
)
.optional()
.setDocComment(ts.docComment('Choose, which related nodes to fetch as well')),
56 changes: 51 additions & 5 deletions packages/client/src/generation/TSClient/Model.ts
Original file line number Diff line number Diff line change
@@ -13,17 +13,20 @@ import {
getAvgAggregateName,
getCountAggregateInputName,
getCountAggregateOutputName,
getCreateManyAndReturnOutputType,
getFieldArgName,
getFieldRefsTypeName,
getGroupByArgsName,
getGroupByName,
getGroupByPayloadName,
getIncludeCreateManyAndReturnName,
getMaxAggregateName,
getMinAggregateName,
getModelArgName,
getModelFieldArgsName,
getPayloadName,
getReturnType,
getSelectCreateManyAndReturnName,
getSumAggregateName,
} from '../utils'
import { InputField } from './../TSClient'
@@ -41,13 +44,16 @@ import { getModelActions } from './utils/getModelActions'

export class Model implements Generable {
protected type: DMMF.OutputType
protected createManyAndReturnType: undefined | DMMF.OutputType
protected mapping?: DMMF.ModelMapping
private dmmf: DMMFHelper
private genericsInfo: GenericArgsInfo
constructor(protected readonly model: DMMF.Model, protected readonly context: GenerateContext) {
this.dmmf = context.dmmf
this.genericsInfo = context.genericArgsInfo
this.type = this.context.dmmf.outputTypeMap.model[model.name]

this.createManyAndReturnType = this.context.dmmf.outputTypeMap.model[getCreateManyAndReturnOutputType(model.name)]
this.mapping = this.context.dmmf.mappings.modelOperations.find((m) => m.model === model.name)!
}

@@ -75,6 +81,19 @@ export class Model implements Generable {
.addSchemaArgs(field.args)
.createExport(),
)
} else if (action === 'createManyAndReturn') {
const args = new ArgsTypeBuilder(this.type, this.context, action as DMMF.ModelAction)
.addSelectArg(getSelectCreateManyAndReturnName(this.type.name))
.addOmitArg()
.addSchemaArgs(field.args)

if (this.createManyAndReturnType) {
args.addIncludeArgIfHasRelations(
getIncludeCreateManyAndReturnName(this.model.name),
this.createManyAndReturnType,
)
}
argsTypes.push(args.createExport())
} else if (action !== 'groupBy' && action !== 'aggregate') {
argsTypes.push(
new ArgsTypeBuilder(this.type, this.context, action as DMMF.ModelAction)
@@ -316,17 +335,32 @@ export type ${getAggregateGetName(model.name)}<T extends ${getAggregateArgsName(

const omitType = this.context.isPreviewFeatureOn('omitApi')
? ts.stringify(buildOmitType({ modelName: this.model.name, dmmf: this.dmmf, fields: this.type.fields }), {
newLine: 'both',
newLine: 'leading',
})
: ''

const hasRelationField = model.fields.some((f) => f.kind === 'object')
const includeType = hasRelationField
? ts.stringify(buildIncludeType({ modelName: this.model.name, dmmf: this.dmmf, fields: this.type.fields }), {
newLine: 'both',
newLine: 'leading',
})
: ''

const createManyAndReturnIncludeType =
hasRelationField && this.createManyAndReturnType
? ts.stringify(
buildIncludeType({
typeName: getIncludeCreateManyAndReturnName(this.model.name),
modelName: this.model.name,
dmmf: this.dmmf,
fields: this.createManyAndReturnType.fields,
}),
{
newLine: 'leading',
},
)
: ''

return `
/**
* Model ${model.name}
@@ -337,12 +371,24 @@ ${!isComposite ? this.getAggregationTypes() : ''}
${!isComposite ? this.getGroupByTypes() : ''}
${ts.stringify(buildSelectType({ modelName: this.model.name, fields: this.type.fields }))}
${
this.createManyAndReturnType
? ts.stringify(
buildSelectType({
modelName: this.model.name,
fields: this.createManyAndReturnType.fields,
typeName: getSelectCreateManyAndReturnName(this.model.name),
}),
{ newLine: 'leading' },
)
: ''
}
${ts.stringify(buildScalarSelectType({ modelName: this.model.name, fields: this.type.fields }), {
newLine: 'leading',
})}
${omitType}
${includeType}
${ts.stringify(buildModelPayload(this.model, this.dmmf), { newLine: 'both' })}
${omitType}${includeType}${createManyAndReturnIncludeType}
${ts.stringify(buildModelPayload(this.model, this.dmmf), { newLine: 'none' })}
type ${model.name}GetPayload<S extends boolean | null | undefined | ${getModelArgName(
model.name,
16 changes: 11 additions & 5 deletions packages/client/src/generation/TSClient/SelectIncludeOmit.ts
Original file line number Diff line number Diff line change
@@ -6,14 +6,20 @@ import { extArgsParam, getFieldArgName, getIncludeName, getOmitName, getSelectNa
import { lowerCase } from '../utils/common'

type BuildIncludeTypeParams = {
typeName?: string
modelName: string
dmmf: DMMFHelper
fields: readonly DMMF.SchemaField[]
}

export function buildIncludeType({ modelName, dmmf, fields }: BuildIncludeTypeParams) {
export function buildIncludeType({
modelName,
typeName = getIncludeName(modelName),
dmmf,
fields,
}: BuildIncludeTypeParams) {
const type = buildSelectOrIncludeObject(modelName, getIncludeFields(fields, dmmf))
return buildExport(getIncludeName(modelName), type)
return buildExport(typeName, type)
}

type BuildOmitTypeParams = {
@@ -45,16 +51,17 @@ export function buildOmitType({ modelName, fields, dmmf }: BuildOmitTypeParams)
type BuildSelectTypeParams = {
modelName: string
fields: readonly DMMF.SchemaField[]
typeName?: string
}

export function buildSelectType({ modelName, fields }: BuildSelectTypeParams) {
export function buildSelectType({ modelName, typeName = getSelectName(modelName), fields }: BuildSelectTypeParams) {
const objectType = buildSelectOrIncludeObject(modelName, fields)
const selectType = ts
.namedType('$Extensions.GetSelect')
.addGenericArgument(objectType)
.addGenericArgument(modelResultExtensionsType(modelName))

return buildExport(getSelectName(modelName), selectType)
return buildExport(typeName, selectType)
}

function modelResultExtensionsType(modelName: string) {
@@ -77,7 +84,6 @@ function buildSelectOrIncludeObject(modelName: string, fields: readonly DMMF.Sch
const fieldType = ts.unionType<ts.PrimitiveType | ts.NamedType>(ts.booleanType)
if (field.outputType.location === 'outputObjectTypes') {
const subSelectType = ts.namedType(getFieldArgName(field, modelName))

subSelectType.addGenericArgument(extArgsParam.toArgument())

fieldType.addVariant(subSelectType)
28 changes: 20 additions & 8 deletions packages/client/src/generation/utils.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,26 @@ export function getSelectName(modelName: string): string {
return `${modelName}Select`
}

export function getSelectCreateManyAndReturnName(modelName: string): string {
return `${modelName}SelectCreateManyAndReturn`
}

export function getIncludeName(modelName: string): string {
return `${modelName}Include`
}

export function getIncludeCreateManyAndReturnName(modelName: string): string {
return `${modelName}IncludeCreateManyAndReturn`
}

export function getCreateManyAndReturnOutputType(modelName: string): string {
return `CreateMany${modelName}AndReturnOutputType`
}

export function getOmitName(modelName: string): string {
return `${modelName}Omit`
}

export function getAggregateName(modelName: string): string {
return `Aggregate${capitalize(modelName)}`
}
@@ -63,14 +83,6 @@ export function getAggregateScalarGetName(modelName: string): string {
return `Get${capitalize(modelName)}AggregateScalarType`
}

export function getIncludeName(modelName: string): string {
return `${modelName}Include`
}

export function getOmitName(modelName: string): string {
return `${modelName}Omit`
}

export function getFieldArgName(field: DMMF.SchemaField, modelName: string): string {
if (field.args.length) {
return getModelFieldArgsName(field, modelName)
Original file line number Diff line number Diff line change
@@ -133,6 +133,114 @@ testMatrix.setupTestSuite(
},
])
})

test('should fail include on the user side', async () => {
const email1 = faker.internet.email()

await expect(
prisma.user.createManyAndReturn({
// @ts-expect-error
include: {
posts: true,
},
data: [
{
email: email1,
},
],
}),
).rejects.toThrow('Unknown field `posts` for include statement on model `CreateManyUserAndReturnOutputType`.')
})

test('take should fail', async () => {
const email1 = faker.internet.email()

await expect(
prisma.user.createManyAndReturn({
// @ts-expect-error
take: 1,
data: [
{
email: email1,
},
],
}),
).rejects.toThrow('Unknown argument `take`')
})

test('orderBy should fail', async () => {
const email1 = faker.internet.email()

await expect(
prisma.user.createManyAndReturn({
// @ts-expect-error
orderBy: {
email: 'asc',
},
data: [
{
email: email1,
},
],
}),
).rejects.toThrow('Unknown argument `orderBy`.')
})

test('distinct should fail', async () => {
const email1 = faker.internet.email()

await expect(
prisma.user.createManyAndReturn({
// @ts-expect-error
distinct: 'id',
data: [
{
email: email1,
},
],
}),
).rejects.toThrow('Unknown argument `distinct`.')
})

test('select _count should fail', async () => {
const email1 = faker.internet.email()

await expect(
prisma.user.createManyAndReturn({
select: {
// @ts-expect-error
_count: true,
},
data: [
{
email: email1,
},
],
}),
).rejects.toThrow(
'Unknown field `_count` for select statement on model `CreateManyUserAndReturnOutputType`. Available options are marked with ?.',
)
})

test('include _count should fail', async () => {
const email1 = faker.internet.email()

await expect(
prisma.user.createManyAndReturn({
// @ts-expect-error
include: {
_count: true,
},
data: [
{
email: email1,
},
],
}),
).rejects.toThrow(
'Unknown field `_count` for include statement on model `CreateManyUserAndReturnOutputType`. Available options are marked with ?.',
)
})
},
{
skipDriverAdapter: {

0 comments on commit 05ca9b0

Please sign in to comment.