Skip to content

Commit

Permalink
feat(client): Add exclude option to the queries
Browse files Browse the repository at this point in the history
Counterpart to prisma/prisma-engines#4807

Can be used standalone or combined with `include`, allows to exclude
the fields that normally would be included by default. Available in all
methods that return actual database records.

It is not useable together with `select` and attempt to do so would
cause type check and validation error.

When using together with result extensions, excluded dependency of a
computed field will be queried from a DB, but will not be returned to
the end user, unless computed field is exlucded as well (see "exclude
with extensions" tests in this PR). This behaviour is equivalent to what
we do if depenency of a computed field is not mentioned in explicit
`select`.

TODO:
- [ ] preview feature
- [ ] validation of non-existing fields in exclude

Close prisma/team-orm#1080
Close #5042
  • Loading branch information
SevInf committed Apr 11, 2024
1 parent cb7aa39 commit 02149a3
Show file tree
Hide file tree
Showing 32 changed files with 1,232 additions and 191 deletions.
210 changes: 92 additions & 118 deletions packages/client/src/generation/TSClient/Args.ts
Original file line number Diff line number Diff line change
@@ -1,144 +1,118 @@
import indent from 'indent-string'

import { DMMF } from '../dmmf-types'
import { getIncludeName, getLegacyModelArgName, getModelArgName, getSelectName } from '../utils'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import * as ts from '../ts-builders'
import {
extArgsParam,
getExcludeName,
getIncludeName,
getLegacyModelArgName,
getModelArgName,
getSelectName,
} from '../utils'
import { GenerateContext } from './GenerateContext'
import { getArgFieldJSDoc } from './helpers'
import { InputField } from './Input'
import { buildInputField } from './Input'

export class ArgsTypeBuilder {
private moduleExport: ts.Export<ts.TypeDeclaration<ts.ObjectType>>

private hasDefaultName = true

export class ArgsType implements Generatable {
private generatedName: string | null = null
private comment: string | null = null
constructor(
protected readonly args: readonly DMMF.SchemaArg[],
protected readonly type: DMMF.OutputType,
protected readonly context: GenerateContext,
protected readonly action?: DMMF.ModelAction,
) {}
public setGeneratedName(name: string): this {
this.generatedName = name
return this
private readonly type: DMMF.OutputType,
private readonly context: GenerateContext,
private readonly action?: DMMF.ModelAction,
) {
this.moduleExport = ts
.moduleExport(
ts.typeDeclaration(getModelArgName(type.name, action), ts.objectType()).addGenericParameter(extArgsParam),
)
.setDocComment(ts.docComment(`${type.name} ${action ?? 'without action'}`))
}

public setComment(comment: string): this {
this.comment = comment
return this
private addProperty(prop: ts.Property) {
this.moduleExport.declaration.type.add(prop)
}

public toTS(): string {
const { action, args } = this
const { name } = this.type
addSchemaArgs(args: readonly DMMF.SchemaArg[]): this {
for (const arg of args) {
const inputField = buildInputField(arg, this.context.genericArgsInfo).setDocComment(
ts.docComment(getArgFieldJSDoc(this.type, this.action, arg)),
)

const updatedArgs = args.map((arg) => {
return { ...arg, comment: getArgFieldJSDoc(this.type, action, arg) }
})
this.addProperty(inputField)
}
return this
}

const selectName = getSelectName(name)
addSelectArg(): this {
this.addProperty(
ts
.property(
'select',
ts.unionType([
ts.namedType(getSelectName(this.type.name)).addGenericArgument(extArgsParam.toArgument()),
ts.nullType,
]),
)
.optional()
.setDocComment(ts.docComment(`Select specific fields to fetch from the ${this.type.name}`)),
)

const argsToGenerate: DMMF.SchemaArg[] = [
{
name: 'select',
isRequired: false,
isNullable: true,
inputTypes: [
{
type: selectName,
location: 'inputObjectTypes',
isList: false,
},
{
type: 'null',
location: 'scalar',
isList: false,
},
],
comment: `Select specific fields to fetch from the ${name}`,
},
]
return this
}

addIncludeArgIfHasRelations(): this {
const hasRelationField = this.type.fields.some((f) => f.outputType.location === 'outputObjectTypes')

if (hasRelationField) {
const includeName = getIncludeName(name)
argsToGenerate.push({
name: 'include',
isRequired: false,
isNullable: true,
inputTypes: [
{
type: includeName,
location: 'inputObjectTypes',
isList: false,
},
{
type: 'null',
location: 'scalar',
isList: false,
},
],
comment: `Choose, which related nodes to fetch as well.`,
})
if (!hasRelationField) {
return this
}

argsToGenerate.push(...updatedArgs)
if (!action && !this.generatedName) {
this.context.defaultArgsAliases.addPossibleAlias(getModelArgName(name), getLegacyModelArgName(name))
}
const generatedName = this.generatedName ?? getModelArgName(name, action)
this.context.defaultArgsAliases.registerArgName(generatedName)
this.addProperty(
ts
.property('include', ts.unionType([ts.namedType(getIncludeName(this.type.name)), ts.nullType]))
.optional()
.setDocComment(ts.docComment('Choose, which related nodes to fetch as well')),
)

return `
/**
* ${this.getGeneratedComment()}
*/
export type ${generatedName}<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
${indent(argsToGenerate.map((arg) => new InputField(arg, this.context.genericArgsInfo).toTS()).join('\n'), TAB_SIZE)}
}
`
return this
}

private getGeneratedComment() {
return this.comment ?? `${this.type.name} ${this.action ?? 'without action'}`
addExcludeArg(): this {
// TODO: check preview feature
this.addProperty(
ts
.property(
'exclude',
ts.unionType([
ts.namedType(getExcludeName(this.type.name)).addGenericArgument(extArgsParam.toArgument()),
ts.nullType,
]),
)
.optional()
.setDocComment(ts.docComment(`Select specific fields to fetch from the ${this.type.name}`)),
)
return this
}
}

export class MinimalArgsType implements Generatable {
constructor(
protected readonly args: readonly DMMF.SchemaArg[],
protected readonly type: DMMF.OutputType,
protected readonly context: GenerateContext,
protected readonly action?: DMMF.ModelAction,
protected readonly generatedTypeName?: string,
) {}
public toTS(): string {
const { action, args } = this
const { name } = this.type
setGeneratedName(name: string): this {
this.hasDefaultName = false
this.moduleExport.declaration.setName(name)
return this
}

const updatedArgs = args.map((arg) => {
return { ...arg, comment: getArgFieldJSDoc(this.type, action, arg) }
})
setComment(comment: string): this {
this.moduleExport.setDocComment(ts.docComment(comment))
return this
}

if (!action && !this.generatedTypeName) {
this.context.defaultArgsAliases.addPossibleAlias(getModelArgName(name), getLegacyModelArgName(name))
createExport() {
if (!this.action && this.hasDefaultName) {
this.context.defaultArgsAliases.addPossibleAlias(
getModelArgName(this.type.name),
getLegacyModelArgName(this.type.name),
)
}
const typeName = this.generatedTypeName ?? getModelArgName(name, action)
this.context.defaultArgsAliases.registerArgName(typeName)
return `
/**
* ${name} ${action ? action : 'without action'}
*/
export type ${typeName}<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
${indent(
updatedArgs
.map((arg) => {
return new InputField(arg, this.context.genericArgsInfo).toTS()
})
.join('\n'),
TAB_SIZE,
)}
}
`
this.context.defaultArgsAliases.registerArgName(this.moduleExport.declaration.name)
return this.moduleExport
}
}
24 changes: 11 additions & 13 deletions packages/client/src/generation/TSClient/Count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,28 @@ import indent from 'indent-string'
import { DMMF } from '../dmmf-types'
import * as ts from '../ts-builders'
import { capitalize, getFieldArgName, getSelectName } from '../utils'
import { ArgsType, MinimalArgsType } from './Args'
import { ArgsTypeBuilder } from './Args'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import { TS } from './Generatable'
import { GenerateContext } from './GenerateContext'
import { buildOutputType } from './Output'

export class Count implements Generatable {
constructor(protected readonly type: DMMF.OutputType, protected readonly context: GenerateContext) {}
protected get argsTypes(): Generatable[] {
const argsTypes: Generatable[] = []
protected get argsTypes(): ts.Export<ts.TypeDeclaration>[] {
const argsTypes: ts.Export<ts.TypeDeclaration>[] = []

argsTypes.push(new ArgsType([], this.type, this.context))
argsTypes.push(
new ArgsTypeBuilder(this.type, this.context).addSelectArg().addIncludeArgIfHasRelations().createExport(),
)

for (const field of this.type.fields) {
if (field.args.length > 0) {
argsTypes.push(
new MinimalArgsType(
field.args,
this.type,
this.context,
undefined,
getCountArgsType(this.type.name, field.name),
),
new ArgsTypeBuilder(this.type, this.context)
.addSchemaArgs(field.args)
.setGeneratedName(getCountArgsType(this.type.name, field.name))
.createExport(),
)
}
}
Expand Down Expand Up @@ -69,7 +67,7 @@ ${indent(
}
// Custom InputTypes
${this.argsTypes.map((gen) => TS(gen)).join('\n')}
${this.argsTypes.map((typeExport) => ts.stringify(typeExport)).join('\n\n')}
`
}
}
Expand Down
6 changes: 1 addition & 5 deletions packages/client/src/generation/TSClient/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class InputField implements Generatable {
}
}

function buildInputField(field: DMMF.SchemaArg, genericsInfo: GenericArgsInfo, source?: string) {
export function buildInputField(field: DMMF.SchemaArg, genericsInfo: GenericArgsInfo, source?: string): ts.Property {
const tsType = buildAllFieldTypes(field.inputTypes, genericsInfo, source)

const tsProperty = ts.property(field.name, tsType)
Expand Down Expand Up @@ -60,10 +60,6 @@ function buildSingleFieldType(t: DMMF.InputTypeRef, genericsInfo: GenericArgsInf
type = namedInputType(scalarType ?? t.type)
}

if (type.name.endsWith('Select') || type.name.endsWith('Include')) {
type.addGenericArgument(ts.namedType('ExtArgs'))
}

if (genericsInfo.typeRefNeedsGenericModelArg(t)) {
if (source) {
type.addGenericArgument(ts.stringLiteral(source))
Expand Down

0 comments on commit 02149a3

Please sign in to comment.