Model.ts•33.7 kB
import { capitalize } from '@prisma/client-common'
import * as DMMF from '@prisma/dmmf'
import * as ts from '@prisma/ts-builders'
import indent from 'indent-string'
import { klona } from 'klona'
import type { DMMFHelper } from '../dmmf'
import { InputField } from '../TSClient'
import {
extArgsParam,
getAggregateArgsName,
getAggregateGetName,
getAggregateInputType,
getAggregateName,
getAvgAggregateName,
getCountAggregateInputName,
getCountAggregateOutputName,
getCountOutputTypeName,
getCreateManyAndReturnOutputType,
getFieldArgName,
getFieldRefsTypeName,
getGroupByArgsName,
getGroupByName,
getGroupByPayloadName,
getIncludeCreateManyAndReturnName,
getIncludeUpdateManyAndReturnName,
getMaxAggregateName,
getMinAggregateName,
getModelArgName,
getModelFieldArgsName,
getPayloadName,
getSelectCreateManyAndReturnName,
getSelectUpdateManyAndReturnName,
getSumAggregateName,
getUpdateManyAndReturnOutputType,
} from '../utils'
import { ArgsTypeBuilder } from './Args'
import { TAB_SIZE } from './constants'
import { Count } from './Count'
import { GenerateContext } from './GenerateContext'
import { getArgFieldJSDoc, getMethodJSDoc, getMethodJSDocBody, wrapComment } from './helpers'
import { InputType } from './Input'
import { ModelFieldRefs } from './ModelFieldRefs'
import { buildOutputType } from './Output'
import { buildModelPayload } from './Payload'
import { buildIncludeType, buildOmitType, buildScalarSelectType, buildSelectType } from './SelectIncludeOmit'
import { getModelActions } from './utils/getModelActions'
import * as tsx from './utils/type-builders'
export class Model {
private type: DMMF.OutputType
private createManyAndReturnType: undefined | DMMF.OutputType
private updateManyAndReturnType: undefined | DMMF.OutputType
private mapping?: DMMF.ModelMapping
private dmmf: DMMFHelper
constructor(
private readonly model: DMMF.Model,
private readonly context: GenerateContext,
) {
this.dmmf = context.dmmf
this.type = this.context.dmmf.outputTypeMap.model[model.name]
this.createManyAndReturnType = this.context.dmmf.outputTypeMap.model[getCreateManyAndReturnOutputType(model.name)]
this.updateManyAndReturnType = this.context.dmmf.outputTypeMap.model[getUpdateManyAndReturnOutputType(model.name)]
this.mapping = this.context.dmmf.mappings.modelOperations.find((m) => m.model === model.name)!
}
private get argsTypes(): ts.Export<ts.TypeDeclaration>[] {
const argsTypes: ts.Export<ts.TypeDeclaration>[] = []
for (const action of Object.keys(DMMF.ModelAction)) {
const fieldName = this.rootFieldNameForAction(action as DMMF.ModelAction)
if (!fieldName) {
continue
}
const field = this.dmmf.rootFieldMap[fieldName]
if (!field) {
throw new Error(`Oops this must not happen. Could not find field ${fieldName} on either Query or Mutation`)
}
if (
action === 'updateMany' ||
action === 'deleteMany' ||
action === 'createMany' ||
action === 'findRaw' ||
action === 'aggregateRaw'
) {
argsTypes.push(
new ArgsTypeBuilder(this.type, this.context, action as DMMF.ModelAction)
.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 === 'updateManyAndReturn') {
const args = new ArgsTypeBuilder(this.type, this.context, action as DMMF.ModelAction)
.addSelectArg(getSelectUpdateManyAndReturnName(this.type.name))
.addOmitArg()
.addSchemaArgs(field.args)
if (this.updateManyAndReturnType) {
args.addIncludeArgIfHasRelations(
getIncludeUpdateManyAndReturnName(this.model.name),
this.updateManyAndReturnType,
)
}
argsTypes.push(args.createExport())
} else if (action !== 'groupBy' && action !== 'aggregate') {
argsTypes.push(
new ArgsTypeBuilder(this.type, this.context, action as DMMF.ModelAction)
.addSelectArg()
.addOmitArg()
.addIncludeArgIfHasRelations()
.addSchemaArgs(field.args)
.createExport(),
)
}
}
for (const field of this.type.fields) {
if (!field.args.length) {
continue
}
const fieldOutput = this.dmmf.resolveOutputObjectType(field.outputType)
if (!fieldOutput) {
continue
}
argsTypes.push(
new ArgsTypeBuilder(fieldOutput, this.context)
.addSelectArg()
.addOmitArg()
.addIncludeArgIfHasRelations()
.addSchemaArgs(field.args)
.setGeneratedName(getModelFieldArgsName(field, this.model.name))
.setComment(`${this.model.name}.${field.name}`)
.createExport(),
)
}
argsTypes.push(
new ArgsTypeBuilder(this.type, this.context)
.addSelectArg()
.addOmitArg()
.addIncludeArgIfHasRelations()
.createExport(),
)
return argsTypes
}
private rootFieldNameForAction(action: DMMF.ModelAction) {
return this.mapping?.[action]
}
private getGroupByTypes() {
const { model, mapping } = this
const groupByType = this.dmmf.outputTypeMap.prisma[getGroupByName(model.name)]
if (!groupByType) {
throw new Error(`Could not get group by type for model ${model.name}`)
}
const groupByRootField = this.dmmf.rootFieldMap[mapping!.groupBy!]
if (!groupByRootField) {
throw new Error(`Could not find groupBy root field for model ${model.name}. Mapping: ${mapping?.groupBy}`)
}
const groupByArgsName = getGroupByArgsName(model.name)
return `
export type ${groupByArgsName}<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
${indent(
groupByRootField.args
.map((arg) => {
const updatedArg = { ...arg, comment: getArgFieldJSDoc(this.type, DMMF.ModelAction.groupBy, arg) }
return new InputField(updatedArg, this.context).toTS()
})
.concat(
groupByType.fields
.filter((f) => f.outputType.location === 'outputObjectTypes')
.map((f) => {
if (f.outputType.location === 'outputObjectTypes') {
return `${f.name}?: ${getAggregateInputType(f.outputType.type)}${f.name === '_count' ? ' | true' : ''}`
}
// to make TS happy, but can't happen, as we filter for outputObjectTypes
return ''
}),
)
.join('\n'),
TAB_SIZE,
)}
}
${ts.stringify(buildOutputType(groupByType))}
type ${getGroupByPayloadName(model.name)}<T extends ${groupByArgsName}> = Prisma.PrismaPromise<
Array<
Prisma.PickEnumerable<${groupByType.name}, T['by']> &
{
[P in ((keyof T) & (keyof ${groupByType.name}))]: P extends '_count'
? T[P] extends boolean
? number
: Prisma.GetScalarType<T[P], ${groupByType.name}[P]>
: Prisma.GetScalarType<T[P], ${groupByType.name}[P]>
}
>
>
`
}
private getAggregationTypes() {
const { model, mapping } = this
let aggregateType = this.dmmf.outputTypeMap.prisma[getAggregateName(model.name)]
if (!aggregateType) {
throw new Error(`Could not get aggregate type "${getAggregateName(model.name)}" for "${model.name}"`)
}
aggregateType = klona(aggregateType)
const aggregateRootField = this.dmmf.rootFieldMap[mapping!.aggregate!]
if (!aggregateRootField) {
throw new Error(`Could not find aggregate root field for model ${model.name}. Mapping: ${mapping?.aggregate}`)
}
const aggregateTypes = [aggregateType]
const avgType = this.dmmf.outputTypeMap.prisma[getAvgAggregateName(model.name)]
const sumType = this.dmmf.outputTypeMap.prisma[getSumAggregateName(model.name)]
const minType = this.dmmf.outputTypeMap.prisma[getMinAggregateName(model.name)]
const maxType = this.dmmf.outputTypeMap.prisma[getMaxAggregateName(model.name)]
const countType = this.dmmf.outputTypeMap.prisma[getCountAggregateOutputName(model.name)]
if (avgType) {
aggregateTypes.push(avgType)
}
if (sumType) {
aggregateTypes.push(sumType)
}
if (minType) {
aggregateTypes.push(minType)
}
if (maxType) {
aggregateTypes.push(maxType)
}
if (countType) {
aggregateTypes.push(countType)
}
const aggregateArgsName = getAggregateArgsName(model.name)
const aggregateName = getAggregateName(model.name)
return `${aggregateTypes
.map(buildOutputType)
.map((type) => ts.stringify(type))
.join('\n\n')}
${
aggregateTypes.length > 1
? aggregateTypes
.slice(1)
.map((type) => {
const newType: DMMF.InputType = {
name: getAggregateInputType(type.name),
constraints: {
maxNumFields: null,
minNumFields: null,
},
fields: type.fields.map((field) => ({
...field,
name: field.name,
isNullable: false,
isRequired: false,
inputTypes: [
{
isList: false,
location: 'scalar',
type: 'true',
},
],
})),
}
return new InputType(newType, this.context).toTS()
})
.join('\n')
: ''
}
export type ${aggregateArgsName}<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
${indent(
aggregateRootField.args
.map((arg) => {
const updatedArg = { ...arg, comment: getArgFieldJSDoc(this.type, DMMF.ModelAction.aggregate, arg) }
return new InputField(updatedArg, this.context).toTS()
})
.concat(
aggregateType.fields.map((f) => {
let data = ''
const comment = getArgFieldJSDoc(this.type, DMMF.ModelAction.aggregate, f.name)
data += comment ? wrapComment(comment) + '\n' : ''
if (f.name === '_count' || f.name === 'count') {
data += `${f.name}?: true | ${getCountAggregateInputName(model.name)}`
} else {
data += `${f.name}?: ${getAggregateInputType(f.outputType.type)}`
}
return data
}),
)
.join('\n'),
TAB_SIZE,
)}
}
export type ${getAggregateGetName(model.name)}<T extends ${getAggregateArgsName(model.name)}> = {
[P in keyof T & keyof ${aggregateName}]: P extends '_count' | 'count'
? T[P] extends true
? number
: Prisma.GetScalarType<T[P], ${aggregateName}[P]>
: Prisma.GetScalarType<T[P], ${aggregateName}[P]>
}`
}
private getDeepInputTypes() {
return this.dmmf.inputObjectTypes.prisma
?.filter((i) => i.meta?.grouping === this.model.name)
.map((inputType) => new InputType(inputType, this.context).toTS())
.join('\n')
}
private getCountTypes() {
const countTypes: Count[] = this.dmmf.schema.outputObjectTypes.prisma
?.filter((t) => t.name === getCountOutputTypeName(this.model.name))
.map((t) => new Count(t, this.context))
return countTypes.map((t) => t.toTS()).join('\n')
}
private getModelExport(): string {
const docLines = this.model.documentation ?? ''
const modelLine = `Model ${this.model.name}\n`
const docs = `${modelLine}${docLines}`
const modelTypeExport = ts
.moduleExport(
ts.typeDeclaration(
`${this.model.name}Model`,
ts
.namedType(`runtime.Types.Result.DefaultSelection`)
.addGenericArgument(ts.namedType(getPayloadName(this.model.name))),
),
)
.setDocComment(ts.docComment(docs))
return ts.stringify(modelTypeExport)
}
public toTS(): string {
const { model } = this
const isComposite = this.dmmf.isComposite(model.name)
const omitType = ts.stringify(
buildOmitType({ modelName: this.model.name, context: this.context, fields: this.type.fields }),
{
newLine: 'leading',
},
)
const hasRelationField = model.fields.some((f) => f.kind === 'object')
const includeType = hasRelationField
? ts.stringify(
buildIncludeType({ modelName: this.model.name, context: this.context, fields: this.type.fields }),
{
newLine: 'leading',
},
)
: ''
const createManyAndReturnIncludeType =
hasRelationField && this.createManyAndReturnType
? ts.stringify(
buildIncludeType({
typeName: getIncludeCreateManyAndReturnName(this.model.name),
modelName: this.model.name,
context: this.context,
fields: this.createManyAndReturnType.fields,
}),
{
newLine: 'leading',
},
)
: ''
const updateManyAndReturnIncludeType =
hasRelationField && this.updateManyAndReturnType
? ts.stringify(
buildIncludeType({
typeName: getIncludeUpdateManyAndReturnName(this.model.name),
modelName: this.model.name,
context: this.context,
fields: this.updateManyAndReturnType.fields,
}),
{
newLine: 'leading',
},
)
: ''
return `
${this.getModelExport()}
${!isComposite ? this.getAggregationTypes() : ''}
${!isComposite ? this.getGroupByTypes() : ''}
${this.getDeepInputTypes()}
${this.getCountTypes()}
${ts.stringify(buildSelectType({ modelName: this.model.name, fields: this.type.fields, context: this.context }))}
${
this.createManyAndReturnType
? ts.stringify(
buildSelectType({
modelName: this.model.name,
fields: this.createManyAndReturnType.fields,
context: this.context,
typeName: getSelectCreateManyAndReturnName(this.model.name),
}),
{ newLine: 'leading' },
)
: ''
}
${
this.updateManyAndReturnType
? ts.stringify(
buildSelectType({
modelName: this.model.name,
fields: this.updateManyAndReturnType.fields,
context: this.context,
typeName: getSelectUpdateManyAndReturnName(this.model.name),
}),
{ newLine: 'leading' },
)
: ''
}
${ts.stringify(buildScalarSelectType({ modelName: this.model.name, fields: this.type.fields, context: this.context }), {
newLine: 'leading',
})}
${omitType}${includeType}${createManyAndReturnIncludeType}${updateManyAndReturnIncludeType}
${ts.stringify(buildModelPayload(this.model, this.context), { newLine: 'none' })}
export type ${model.name}GetPayload<S extends boolean | null | undefined | ${getModelArgName(
model.name,
)}> = runtime.Types.Result.GetResult<${getPayloadName(model.name)}, S>
${isComposite ? '' : new ModelDelegate(this.type, this.context).toTS()}
${new ModelFieldRefs(this.type).toTS()}
// Custom InputTypes
${this.argsTypes.map((type) => ts.stringify(type)).join('\n\n')}
`
}
}
class ModelDelegate {
constructor(
protected readonly outputType: DMMF.OutputType,
protected readonly context: GenerateContext,
) {}
/**
* Returns all available non-aggregate or group actions
* Includes both dmmf and client-only actions
*
* @param availableActions
* @returns
*/
private getNonAggregateActions(availableActions: DMMF.ModelAction[]): DMMF.ModelAction[] {
const actions = availableActions.filter(
(key) => key !== DMMF.ModelAction.aggregate && key !== DMMF.ModelAction.groupBy && key !== DMMF.ModelAction.count,
)
return actions
}
public toTS(): string {
const { name } = this.outputType
const { dmmf } = this.context
const mapping = dmmf.mappingsMap[name] ?? { model: name, plural: `${name}s` }
const modelOrType = dmmf.typeAndModelMap[name]
const availableActions = getModelActions(dmmf, name)
const nonAggregateActions = this.getNonAggregateActions(availableActions)
const groupByArgsName = getGroupByArgsName(name)
const countArgsName = getModelArgName(name, DMMF.ModelAction.count)
const genericDelegateParams = [extArgsParam, ts.genericParameter('GlobalOmitOptions').default(ts.objectType())]
const excludedArgsForCount = ['select', 'include', 'distinct', 'omit']
if (this.context.isPreviewFeatureOn('relationJoins')) {
excludedArgsForCount.push('relationLoadStrategy')
}
const excludedArgsForCountType = excludedArgsForCount.map((name) => `'${name}'`).join(' | ')
return `\
${
availableActions.includes(DMMF.ModelAction.aggregate)
? `export type ${countArgsName}<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> =
Omit<${getModelArgName(name, DMMF.ModelAction.findMany)}, ${excludedArgsForCountType}> & {
select?: ${getCountAggregateInputName(name)} | true
}
`
: ''
}
export interface ${name}Delegate<${genericDelegateParams.map((param) => ts.stringify(param)).join(', ')}> {
${indent(`[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['model']['${name}'], meta: { name: '${name}' } }`, TAB_SIZE)}
${nonAggregateActions
.map((action) => {
const method = buildModelDelegateMethod(name, action, this.context)
return ts.stringify(method, { indentLevel: 1, newLine: 'trailing' })
})
.join('\n')}
${
availableActions.includes(DMMF.ModelAction.aggregate)
? `${indent(getMethodJSDoc(DMMF.ModelAction.count, mapping, modelOrType), TAB_SIZE)}
count<T extends ${countArgsName}>(
args?: Prisma.Subset<T, ${countArgsName}>,
): Prisma.PrismaPromise<
T extends runtime.Types.Utils.Record<'select', any>
? T['select'] extends true
? number
: Prisma.GetScalarType<T['select'], ${getCountAggregateOutputName(name)}>
: number
>
`
: ''
}
${
availableActions.includes(DMMF.ModelAction.aggregate)
? `${indent(getMethodJSDoc(DMMF.ModelAction.aggregate, mapping, modelOrType), TAB_SIZE)}
aggregate<T extends ${getAggregateArgsName(name)}>(args: Prisma.Subset<T, ${getAggregateArgsName(
name,
)}>): Prisma.PrismaPromise<${getAggregateGetName(name)}<T>>
`
: ''
}
${
availableActions.includes(DMMF.ModelAction.groupBy)
? `${indent(getMethodJSDoc(DMMF.ModelAction.groupBy, mapping, modelOrType), TAB_SIZE)}
groupBy<
T extends ${groupByArgsName},
HasSelectOrTake extends Prisma.Or<
Prisma.Extends<'skip', Prisma.Keys<T>>,
Prisma.Extends<'take', Prisma.Keys<T>>
>,
OrderByArg extends Prisma.True extends HasSelectOrTake
? { orderBy: ${groupByArgsName}['orderBy'] }
: { orderBy?: ${groupByArgsName}['orderBy'] },
OrderFields extends Prisma.ExcludeUnderscoreKeys<Prisma.Keys<Prisma.MaybeTupleToUnion<T['orderBy']>>>,
ByFields extends Prisma.MaybeTupleToUnion<T['by']>,
ByValid extends Prisma.Has<ByFields, OrderFields>,
HavingFields extends Prisma.GetHavingFields<T['having']>,
HavingValid extends Prisma.Has<ByFields, HavingFields>,
ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False,
InputErrors extends ByEmpty extends Prisma.True
? \`Error: "by" must not be empty.\`
: HavingValid extends Prisma.False
? {
[P in HavingFields]: P extends ByFields
? never
: P extends string
? \`Error: Field "$\{P}" used in "having" needs to be provided in "by".\`
: [
Error,
'Field ',
P,
\` in "having" needs to be provided in "by"\`,
]
}[HavingFields]
: 'take' extends Prisma.Keys<T>
? 'orderBy' extends Prisma.Keys<T>
? ByValid extends Prisma.True
? {}
: {
[P in OrderFields]: P extends ByFields
? never
: \`Error: Field "$\{P}" in "orderBy" needs to be provided in "by"\`
}[OrderFields]
: 'Error: If you provide "take", you also need to provide "orderBy"'
: 'skip' extends Prisma.Keys<T>
? 'orderBy' extends Prisma.Keys<T>
? ByValid extends Prisma.True
? {}
: {
[P in OrderFields]: P extends ByFields
? never
: \`Error: Field "$\{P}" in "orderBy" needs to be provided in "by"\`
}[OrderFields]
: 'Error: If you provide "skip", you also need to provide "orderBy"'
: ByValid extends Prisma.True
? {}
: {
[P in OrderFields]: P extends ByFields
? never
: \`Error: Field "$\{P}" in "orderBy" needs to be provided in "by"\`
}[OrderFields]
>(args: Prisma.SubsetIntersection<T, ${groupByArgsName}, OrderByArg> & InputErrors): {} extends InputErrors ? ${getGroupByPayloadName(
name,
)}<T> : Prisma.PrismaPromise<InputErrors>`
: ''
}
/**
* Fields of the ${name} model
*/
readonly fields: ${getFieldRefsTypeName(name)};
}
${ts.stringify(buildFluentWrapperDefinition(name, this.outputType, this.context))}
`
}
}
function buildModelDelegateMethod(modelName: string, actionName: DMMF.ModelAction, context: GenerateContext) {
const mapping = context.dmmf.mappingsMap[modelName] ?? { model: modelName, plural: `${modelName}s` }
const modelOrType = context.dmmf.typeAndModelMap[modelName]
const dependencyValidators = getNonAggregateMethodDependencyValidations(mapping, actionName, context)
const method = ts
.method(actionName)
.setDocComment(ts.docComment(getMethodJSDocBody(actionName, mapping, modelOrType)))
.addParameter(getNonAggregateMethodArgs(modelName, actionName, dependencyValidators))
.setReturnType(getReturnType({ modelName, actionName }))
const generic = getNonAggregateMethodGenericParam(modelName, actionName)
if (generic) {
method.addGenericParameter(generic)
}
for (const validator of dependencyValidators) {
method.addGenericParameter(validator)
}
return method
}
function getNonAggregateMethodArgs(
modelName: string,
actionName: DMMF.ModelAction,
dependencyValidators: ts.GenericParameter[],
) {
const makeParameter = (type: ts.TypeBuilder) => {
if (dependencyValidators.length > 0) {
type = ts.intersectionType([type, ...dependencyValidators.map((validator) => ts.namedType(validator.name))])
}
return ts.parameter('args', type)
}
if (actionName === DMMF.ModelAction.count) {
const type = tsx.omit(
ts.namedType(getModelArgName(modelName, DMMF.ModelAction.findMany)),
ts
.unionType(ts.stringLiteral('select'))
.addVariant(ts.stringLiteral('include'))
.addVariant(ts.stringLiteral('distinct')),
)
return makeParameter(type).optional()
}
if (actionName === DMMF.ModelAction.findRaw || actionName === DMMF.ModelAction.aggregateRaw) {
return makeParameter(ts.namedType(`Prisma.${getModelArgName(modelName, actionName)}`)).optional()
}
const type = ts
.namedType('Prisma.SelectSubset')
.addGenericArgument(ts.namedType('T'))
.addGenericArgument(
ts.namedType(getModelArgName(modelName, actionName)).addGenericArgument(extArgsParam.toArgument()),
)
const param = makeParameter(type)
if (
actionName === DMMF.ModelAction.findMany ||
actionName === DMMF.ModelAction.findFirst ||
actionName === DMMF.ModelAction.deleteMany ||
actionName === DMMF.ModelAction.createMany ||
actionName === DMMF.ModelAction.createManyAndReturn ||
actionName === DMMF.ModelAction.findFirstOrThrow
) {
param.optional()
}
return param
}
function getNonAggregateMethodGenericParam(modelName: string, actionName: DMMF.ModelAction) {
if (
actionName === DMMF.ModelAction.count ||
actionName === DMMF.ModelAction.findRaw ||
actionName === DMMF.ModelAction.aggregateRaw
) {
return null
}
const arg = ts.genericParameter('T')
if (actionName === DMMF.ModelAction.aggregate) {
return arg.extends(ts.namedType(getAggregateArgsName(modelName)))
}
return arg.extends(ts.namedType(getModelArgName(modelName, actionName)))
}
function getNonAggregateMethodDependencyValidations(
modelMapping: DMMF.ModelMapping,
actionName: DMMF.ModelAction,
context: GenerateContext,
): ts.GenericParameter[] {
const outputFieldName = modelMapping[actionName]
if (!outputFieldName) {
throw new Error(`Missing mapping for ${modelMapping.model}.${actionName}`)
}
const outputField =
context.dmmf.outputTypeMap.prisma['Query'].fields.find((f) => f.name === outputFieldName) ??
context.dmmf.outputTypeMap.prisma['Mutation'].fields.find((f) => f.name === outputFieldName)
if (!outputField) {
throw new Error(`Can't find output field ${outputFieldName} in the schema`)
}
const validators: ts.GenericParameter[] = []
for (const args of outputField.args) {
if (args.requiresOtherFields === undefined) {
continue
}
const objectType = ts.objectType()
for (const reqArg of args.requiresOtherFields) {
objectType.add(ts.property(reqArg, ts.objectType()))
}
validators.push(
ts
.genericParameter(`${capitalize(args.name)}DependenciesValidator`)
.extends(
ts
.conditionalType()
.check(ts.stringLiteral(args.name))
.extends(ts.namedType('Prisma.Keys<T>'))
.then(objectType)
.else(ts.objectType()),
),
)
}
return validators
}
type GetReturnTypeOptions = {
modelName: string
actionName: DMMF.ModelAction
isChaining?: boolean
isNullable?: boolean
}
/**
* Get the complicated extract output
* @param name Model name
* @param actionName action name
*/
function getReturnType({
modelName,
actionName,
isChaining = false,
isNullable = false,
}: GetReturnTypeOptions): ts.TypeBuilder {
if (actionName === DMMF.ModelAction.count) {
return tsx.promise(ts.numberType)
}
if (actionName === DMMF.ModelAction.aggregate) {
return tsx.promise(ts.namedType(getAggregateGetName(modelName)).addGenericArgument(ts.namedType('T')))
}
if (actionName === DMMF.ModelAction.findRaw || actionName === DMMF.ModelAction.aggregateRaw) {
return tsx.prismaPromise(ts.namedType('Prisma.JsonObject'))
}
if (
actionName === DMMF.ModelAction.deleteMany ||
actionName === DMMF.ModelAction.updateMany ||
actionName === DMMF.ModelAction.createMany
) {
return tsx.prismaPromise(ts.namedType('Prisma.BatchPayload'))
}
const isList =
actionName === DMMF.ModelAction.findMany ||
actionName === DMMF.ModelAction.createManyAndReturn ||
actionName === DMMF.ModelAction.updateManyAndReturn
/**
* Important: We handle findMany or isList special, as we don't want chaining from there
*/
if (isList) {
let result: ts.TypeBuilder = getResultType(modelName, actionName)
if (isChaining) {
result = ts.unionType(result).addVariant(ts.namedType('Null'))
}
return tsx.prismaPromise(result)
}
if (isChaining && actionName === DMMF.ModelAction.findUniqueOrThrow) {
const nullType = isNullable ? ts.nullType : ts.namedType('Null')
const result = ts.unionType<ts.TypeBuilder>(getResultType(modelName, actionName)).addVariant(nullType)
return getFluentWrapper(modelName, result, nullType)
}
if (actionName === DMMF.ModelAction.findFirst || actionName === DMMF.ModelAction.findUnique) {
const result = ts.unionType<ts.TypeBuilder>(getResultType(modelName, actionName)).addVariant(ts.nullType)
return getFluentWrapper(modelName, result, ts.nullType)
}
return getFluentWrapper(modelName, getResultType(modelName, actionName))
}
function getFluentWrapper(modelName: string, resultType: ts.TypeBuilder, nullType: ts.TypeBuilder = ts.neverType) {
return ts
.namedType(`Prisma.${fluentWrapperName(modelName)}`)
.addGenericArgument(resultType)
.addGenericArgument(nullType)
.addGenericArgument(extArgsParam.toArgument())
.addGenericArgument(ts.namedType('GlobalOmitOptions'))
}
function getResultType(modelName: string, actionName: DMMF.ModelAction) {
return ts
.namedType('runtime.Types.Result.GetResult')
.addGenericArgument(ts.namedType(getPayloadName(modelName)).addGenericArgument(extArgsParam.toArgument()))
.addGenericArgument(ts.namedType('T'))
.addGenericArgument(ts.stringLiteral(actionName))
.addGenericArgument(ts.namedType('GlobalOmitOptions'))
}
function buildFluentWrapperDefinition(modelName: string, outputType: DMMF.OutputType, context: GenerateContext) {
const definition = ts.interfaceDeclaration(fluentWrapperName(modelName))
definition
.addGenericParameter(ts.genericParameter('T'))
.addGenericParameter(ts.genericParameter('Null').default(ts.neverType))
.addGenericParameter(extArgsParam)
.addGenericParameter(ts.genericParameter('GlobalOmitOptions').default(ts.objectType()))
.extends(tsx.prismaPromise(ts.namedType('T')))
definition.add(ts.property(ts.toStringTag, ts.stringLiteral('PrismaPromise')).readonly())
definition.addMultiple(
outputType.fields
.filter(
(field) =>
field.outputType.location === 'outputObjectTypes' &&
!context.dmmf.isComposite(field.outputType.type) &&
field.name !== '_count',
)
.map((field) => {
const fieldArgType = ts
.namedType(`Prisma.${getFieldArgName(field, modelName)}`)
.addGenericArgument(extArgsParam.toArgument())
const argsParam = ts.genericParameter('T').extends(fieldArgType).default(ts.objectType())
return ts
.method(field.name)
.addGenericParameter(argsParam)
.addParameter(ts.parameter('args', subset(argsParam.toArgument(), fieldArgType)).optional())
.setReturnType(
getReturnType({
modelName: field.outputType.type,
actionName: field.outputType.isList ? DMMF.ModelAction.findMany : DMMF.ModelAction.findUniqueOrThrow,
isChaining: true,
isNullable: field.isNullable,
}),
)
}),
)
definition.add(
ts
.method('then')
.setDocComment(
ts.docComment`
Attaches callbacks for the resolution and/or rejection of the Promise.
@param onfulfilled The callback to execute when the Promise is resolved.
@param onrejected The callback to execute when the Promise is rejected.
@returns A Promise for the completion of which ever callback is executed.
`,
)
.addGenericParameter(ts.genericParameter('TResult1').default(ts.namedType('T')))
.addGenericParameter(ts.genericParameter('TResult2').default(ts.neverType))
.addParameter(promiseCallback('onfulfilled', ts.parameter('value', ts.namedType('T')), ts.namedType('TResult1')))
.addParameter(promiseCallback('onrejected', ts.parameter('reason', ts.anyType), ts.namedType('TResult2')))
.setReturnType(tsx.promise(ts.unionType([ts.namedType('TResult1'), ts.namedType('TResult2')]))),
)
definition.add(
ts
.method('catch')
.setDocComment(
ts.docComment`
Attaches a callback for only the rejection of the Promise.
@param onrejected The callback to execute when the Promise is rejected.
@returns A Promise for the completion of the callback.
`,
)
.addGenericParameter(ts.genericParameter('TResult').default(ts.neverType))
.addParameter(promiseCallback('onrejected', ts.parameter('reason', ts.anyType), ts.namedType('TResult')))
.setReturnType(tsx.promise(ts.unionType([ts.namedType('T'), ts.namedType('TResult')]))),
)
definition.add(
ts
.method('finally')
.setDocComment(
ts.docComment`
Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
resolved value cannot be modified from the callback.
@param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
@returns A Promise for the completion of the callback.
`,
)
.addParameter(
ts.parameter('onfinally', ts.unionType([ts.functionType(), ts.undefinedType, ts.nullType])).optional(),
)
.setReturnType(tsx.promise(ts.namedType('T'))),
)
return ts.moduleExport(definition).setDocComment(ts.docComment`
The delegate class that acts as a "Promise-like" for ${modelName}.
Why is this prefixed with \`Prisma__\`?
Because we want to prevent naming conflicts as mentioned in
https://github.com/prisma/prisma-client-js/issues/707
`)
}
function promiseCallback(name: string, callbackParam: ts.Parameter, returnType: ts.TypeBuilder) {
return ts
.parameter(
name,
ts.unionType([
ts.functionType().addParameter(callbackParam).setReturnType(typeOrPromiseLike(returnType)),
ts.undefinedType,
ts.nullType,
]),
)
.optional()
}
function typeOrPromiseLike(type: ts.TypeBuilder) {
return ts.unionType([type, ts.namedType('PromiseLike').addGenericArgument(type)])
}
function subset(arg: ts.TypeBuilder, baseType: ts.TypeBuilder) {
return ts.namedType('Prisma.Subset').addGenericArgument(arg).addGenericArgument(baseType)
}
function fluentWrapperName(modelName: string) {
return `Prisma__${modelName}Client`
}