-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(client): Add
exclude
option to the queries
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
Showing
32 changed files
with
1,232 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.