Skip to content

Commit

Permalink
fix(client): Enum type clashes
Browse files Browse the repository at this point in the history
Follow up to #20150.
Changes we did to extensions have fixed name clashes for models, but not
for enums. Enums problem is more complicated: in case of name clash, it
is impossible for contents of `Prisma` namespace to reference the value
from outer scope.
Solution is: all top-level enums are put into $Enums namespace. Types
within `Prisma` namespace always refer to them using fully-qualifed
name. Top-level enums are re-exported from `$Enums` namespace for
backward compatiblity.

Also, does opportunistic clean-ups/refactors

Fix #20031
  • Loading branch information
SevInf committed Jul 12, 2023
1 parent 99a96e8 commit e17899d
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 122 deletions.
7 changes: 4 additions & 3 deletions packages/client/src/generation/TSClient/Count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import indent from 'indent-string'
import type { DMMFHelper } from '../dmmf'
import { DMMF } from '../dmmf-types'
import { GenericArgsInfo } from '../GenericsArgsInfo'
import * as ts from '../ts-builders'
import { capitalize, getFieldArgName, getSelectName } from '../utils'
import { ArgsType, MinimalArgsType } from './Args'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import { TS } from './Generatable'
import { OutputType } from './Output'
import { buildOutputType } from './Output'

export class Count implements Generatable {
constructor(
Expand Down Expand Up @@ -42,14 +43,14 @@ export class Count implements Generatable {
public toTS(): string {
const { type } = this
const { name } = type
const outputType = new OutputType(this.dmmf, this.type)
const outputType = buildOutputType(type)

return `
/**
* Count Type ${name}
*/
${outputType.toTS()}
${ts.stringify(outputType)}
export type ${getSelectName(name)}<ExtArgs extends $Extensions.Args = $Extensions.DefaultArgs> = {
${indent(
Expand Down
5 changes: 4 additions & 1 deletion packages/client/src/generation/TSClient/Enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export class Enum implements Generatable {
${indent(type.values.map((v) => `${v}: ${this.getValueJS(v)}`).join(',\n'), TAB_SIZE)}
}`
const enumBody = this.isStrictEnum() ? `makeStrictEnum(${enumVariants})` : enumVariants
return `exports.${this.useNamespace ? 'Prisma.' : ''}${type.name} = ${enumBody};`

return this.useNamespace
? `exports.Prisma.${type.name} = ${enumBody};`
: `exports.${type.name} = exports.$Enums.${type.name} = ${enumBody};`
}

private getValueJS(value: string): string {
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/generation/TSClient/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ function buildSingleFieldType(
}

type = namedInputType(scalarType ?? t.type)
} else if (t.location === 'enumTypes' && t.namespace === 'model') {
type = ts.namedType(`$Enums.${t.type.name}`)
} else {
type = namedInputType(t.type.name)
}
Expand Down
31 changes: 14 additions & 17 deletions packages/client/src/generation/TSClient/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ import { TS } from './Generatable'
import { getArgFieldJSDoc, getArgs, getGenericMethod, getMethodJSDoc, wrapComment } from './helpers'
import { InputType } from './Input'
import { ModelFieldRefs } from './ModelFieldRefs'
import { buildModelOutputProperty, OutputType } from './Output'
import { SchemaOutputType } from './SchemaOutput'
import { buildModelOutputProperty, buildOutputType } from './Output'
import { buildIncludeType, buildScalarSelectType, buildSelectType } from './SelectInclude'
import { getModelActions } from './utils/getModelActions'

Expand All @@ -48,7 +47,6 @@ const extArgsParam = ts
.default(ts.namedType('$Extensions.DefaultArgs'))

export class Model implements Generatable {
protected outputType: OutputType
protected type: DMMF.OutputType
protected mapping?: DMMF.ModelMapping
constructor(
Expand All @@ -58,7 +56,6 @@ export class Model implements Generatable {
protected readonly generator?: GeneratorConfig,
) {
this.type = dmmf.outputTypeMap[model.name]
this.outputType = new OutputType(dmmf, this.type)
this.mapping = dmmf.mappings.modelOperations.find((m) => m.model === model.name)!
}
protected get argsTypes(): Generatable[] {
Expand Down Expand Up @@ -147,7 +144,7 @@ ${indent(
)}
}
${new OutputType(this.dmmf, groupByType).toTS()}
${ts.stringify(buildOutputType(groupByType))}
type ${getGroupByPayloadName(model.name)}<T extends ${groupByArgsName}> = Prisma.PrismaPromise<
Array<
Expand Down Expand Up @@ -204,7 +201,10 @@ type ${getGroupByPayloadName(model.name)}<T extends ${groupByArgsName}> = Prisma

const aggregateName = getAggregateName(model.name)

return `${aggregateTypes.map((type) => new SchemaOutputType(type).toTS()).join('\n')}
return `${aggregateTypes
.map(buildOutputType)
.map((type) => ts.stringify(type))
.join('\n\n')}
${
aggregateTypes.length > 1
Expand Down Expand Up @@ -334,12 +334,9 @@ export type ${getAggregateGetName(model.name)}<T extends ${getAggregateArgsName(

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

return `
Expand All @@ -351,8 +348,8 @@ ${!this.dmmf.typeMap[model.name] ? this.getAggregationTypes() : ''}
${!this.dmmf.typeMap[model.name] ? this.getGroupByTypes() : ''}
${ts.stringify(buildSelectType({ modelName: this.model.name, fields: this.outputType.fields }))}
${ts.stringify(buildScalarSelectType({ modelName: this.model.name, fields: this.outputType.fields }), {
${ts.stringify(buildSelectType({ modelName: this.model.name, fields: this.type.fields }))}
${ts.stringify(buildScalarSelectType({ modelName: this.model.name, fields: this.type.fields }), {
newLine: 'leading',
})}
${includeType}
Expand All @@ -361,9 +358,9 @@ type ${model.name}GetPayload<S extends boolean | null | undefined | ${getArgName
model.name
}Payload, S>
${isComposite ? '' : new ModelDelegate(this.outputType, this.dmmf, this.generator).toTS()}
${isComposite ? '' : new ModelDelegate(this.type, this.dmmf, this.generator).toTS()}
${new ModelFieldRefs(this.generator, this.outputType).toTS()}
${new ModelFieldRefs(this.generator, this.type).toTS()}
// Custom InputTypes
${this.argsTypes.map((gen) => TS(gen)).join('\n')}
Expand All @@ -372,7 +369,7 @@ ${this.argsTypes.map((gen) => TS(gen)).join('\n')}
}
export class ModelDelegate implements Generatable {
constructor(
protected readonly outputType: OutputType,
protected readonly outputType: DMMF.OutputType,
protected readonly dmmf: DMMFHelper,
protected readonly generator?: GeneratorConfig,
) {}
Expand Down
83 changes: 31 additions & 52 deletions packages/client/src/generation/TSClient/Output.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import indent from 'indent-string'

import type { DMMFHelper } from '../dmmf'
import type { DMMF } from '../dmmf-types'
import * as ts from '../ts-builders'
import { GraphQLScalarToJSTypeTable, isSchemaEnum, needsNamespace } from '../utils/common'
import { TAB_SIZE } from './constants'
import type { Generatable } from './Generatable'
import { wrapComment } from './helpers'

export function buildModelOutputProperty(field: DMMF.Field, dmmf: DMMFHelper, useNamespace = false) {
let fieldTypeName = GraphQLScalarToJSTypeTable[field.type] || field.type
Expand All @@ -24,6 +19,8 @@ export function buildModelOutputProperty(field: DMMF.Field, dmmf: DMMFHelper, us
payloadType.addGenericArgument(ts.namedType('ExtArgs'))
}
fieldType = payloadType
} else if (field.kind === 'enum') {
fieldType = ts.namedType(`$Enums.${fieldTypeName}`)
} else {
fieldType = ts.namedType(fieldTypeName)
}
Expand All @@ -40,57 +37,39 @@ export function buildModelOutputProperty(field: DMMF.Field, dmmf: DMMFHelper, us
return property
}

export class OutputField implements Generatable {
constructor(
protected readonly dmmf: DMMFHelper,
protected readonly field: DMMF.SchemaField,
protected readonly useNamespace = false,
) {}
public toTS(): string {
const { field, useNamespace } = this
// ENUMTODO
let fieldType
export function buildOutputType(type: DMMF.OutputType) {
return ts.moduleExport(ts.typeDeclaration(type.name, ts.objectType().addMultiple(type.fields.map(buildOutputField))))
}

if (field.outputType.location === 'scalar') {
fieldType = GraphQLScalarToJSTypeTable[field.outputType.type as string]
} else if (field.outputType.location === 'enumTypes') {
if (isSchemaEnum(field.outputType.type)) {
fieldType = field.outputType.type.name
}
} else {
fieldType = (field.outputType.type as DMMF.OutputType).name
}
function buildOutputField(field: DMMF.SchemaField) {
let fieldType: ts.TypeBuilder
if (typeof field.outputType.type === 'string') {
const typeNames = GraphQLScalarToJSTypeTable[field.outputType.type] ?? field.outputType.type
fieldType = Array.isArray(typeNames) ? ts.namedType(typeNames[0]) : ts.namedType(typeNames)
} else if (field.outputType.location === 'enumTypes' && field.outputType.namespace === 'model') {
fieldType = ts.namedType(enumTypeName(field.outputType))
} else {
fieldType = ts.namedType(field.outputType.type.name)
}

if (Array.isArray(fieldType)) {
fieldType = fieldType[0]
}
if (field.outputType.isList) {
fieldType = ts.array(fieldType)
} else if (field.isNullable) {
fieldType = ts.unionType(fieldType).addVariant(ts.nullType)
}

const arrayStr = field.outputType.isList ? `[]` : ''
const nullableStr = field.isNullable && !field.outputType.isList ? ' | null' : ''
const namespaceStr = useNamespace && needsNamespace(field.outputType.type, this.dmmf) ? `Prisma.` : ''
const deprecated = field.deprecation
? `@deprecated since ${field.deprecation.sinceVersion} because ${field.deprecation.reason}`
: ''
const jsdoc = deprecated ? wrapComment(deprecated) + '\n' : ''
return `${jsdoc}${field.name}: ${namespaceStr}${fieldType}${arrayStr}${nullableStr}`
const property = ts.property(field.name, fieldType)
if (field.deprecation) {
property.setDocComment(
ts.docComment(`@deprecated since ${field.deprecation.sinceVersion} because ${field.deprecation.reason}`),
)
}

return property
}

export class OutputType implements Generatable {
public name: string
public fields: DMMF.SchemaField[]
constructor(protected readonly dmmf: DMMFHelper, protected readonly type: DMMF.OutputType) {
this.name = type.name
this.fields = type.fields
}
public toTS(): string {
const { type } = this
return `
export type ${type.name} = {
${indent(
type.fields.map((field) => new OutputField(this.dmmf, { ...field, ...field.outputType }).toTS()).join('\n'),
TAB_SIZE,
)}
}`
}
function enumTypeName(ref: DMMF.TypeRefEnum) {
const name = isSchemaEnum(ref.type) ? ref.type.name : ref.type
const namespace = ref.namespace === 'model' ? '$Enums' : 'Prisma'
return `${namespace}.${name}`
}
42 changes: 0 additions & 42 deletions packages/client/src/generation/TSClient/SchemaOutput.ts

This file was deleted.

21 changes: 17 additions & 4 deletions packages/client/src/generation/TSClient/TSClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { InternalDatasource } from '../../runtime/utils/printDatasources'
import { DMMFHelper } from '../dmmf'
import type { DMMF } from '../dmmf-types'
import { GenericArgsInfo } from '../GenericsArgsInfo'
import * as ts from '../ts-builders'
import { buildDebugInitialization } from '../utils/buildDebugInitialization'
import { buildDirname } from '../utils/buildDirname'
import { buildRuntimeDataModel } from '../utils/buildDMMF'
Expand Down Expand Up @@ -112,7 +113,7 @@ ${buildRequirePath(edge)}
/**
* Enums
*/
exports.$Enums = {}
${this.dmmf.schema.enumTypes.prisma.map((type) => new Enum(type, true).toJS()).join('\n\n')}
${this.dmmf.schema.enumTypes.model?.map((type) => new Enum(type, false).toJS()).join('\n\n') ?? ''}
Expand Down Expand Up @@ -169,7 +170,16 @@ ${buildNFTAnnotations(dataProxy, engineType, platforms, relativeOutdir)}

const prismaEnums = this.dmmf.schema.enumTypes.prisma.map((type) => new Enum(type, true).toTS())

const modelEnums = this.dmmf.schema.enumTypes.model?.map((type) => new Enum(type, false).toTS())
const modelEnums: string[] = []
const modelEnumsAliases: string[] = []
for (const enumType of this.dmmf.schema.enumTypes.model ?? []) {
const namespacedType = ts.namedType(`$Enums.${enumType.name}`)
modelEnums.push(new Enum(enumType, false).toTS())
modelEnumsAliases.push(
ts.stringify(ts.moduleExport(ts.typeDeclaration(enumType.name, namespacedType))),
ts.stringify(ts.moduleExport(ts.constDeclaration(enumType.name, namespacedType))),
)
}

const fieldRefs = this.dmmf.schema.fieldRefTypes.prisma?.map((type) => new FieldRefInput(type).toTS()) ?? []

Expand All @@ -186,13 +196,16 @@ ${commonCode.tsWithoutNamespace()}
${modelAndTypes.map((m) => m.toTSWithoutNamespace()).join('\n')}
${
modelEnums && modelEnums.length > 0
modelEnums.length > 0
? `
/**
* Enums
*/
export namespace $Enums {
${modelEnums.join('\n\n')}
}
${modelEnums.join('\n\n')}
${modelEnumsAliases.join('\n\n')}
`
: ''
}
Expand Down
2 changes: 0 additions & 2 deletions packages/client/src/generation/TSClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@ export { Enum } from './Enum'
export { BrowserJS, JS, TS } from './Generatable'
export { InputField, InputType } from './Input'
export { Model, ModelDelegate } from './Model'
export { OutputField, OutputType } from './Output'
export { SchemaOutputField, SchemaOutputType } from './SchemaOutput'
export { TSClient } from './TSClient'
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConstDeclaration } from './ConstDeclaration'
import { TypeDeclaration } from './TypeDeclaration'

// TODO: enum, class, interface
export type AnyDeclarationBuilder = TypeDeclaration
export type AnyDeclarationBuilder = TypeDeclaration | ConstDeclaration
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { constDeclaration } from './ConstDeclaration'
import { docComment } from './DocComment'
import { namedType } from './NamedType'
import { stringify } from './stringify'

const A = namedType('A')

test('basic', () => {
expect(stringify(constDeclaration('B', A))).toMatchInlineSnapshot(`const B: A`)
})

test('with doc comment', () => {
const decl = constDeclaration('B', A).setDocComment(docComment('Some important value'))
expect(stringify(decl)).toMatchInlineSnapshot(`
/**
* Some important value
*/
const B: A
`)
})

0 comments on commit e17899d

Please sign in to comment.