/
TSClient.ts
316 lines (271 loc) · 8.94 KB
/
TSClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
import type { GeneratorConfig } from '@prisma/generator-helper'
import type { Platform } from '@prisma/get-platform'
import { getClientEngineType, getEnvPaths } from '@prisma/internals'
import indent from 'indent-string'
import { klona } from 'klona'
import path from 'path'
import { DMMFHelper } from '../../runtime/dmmf'
import type { DMMF } from '../../runtime/dmmf-types'
import type { GetPrismaClientConfig } from '../../runtime/getPrismaClient'
import type { InternalDatasource } from '../../runtime/utils/printDatasources'
import { buildDirname } from '../utils/buildDirname'
import { buildDMMF } from '../utils/buildDMMF'
import { buildInjectableEdgeEnv } from '../utils/buildInjectableEdgeEnv'
import { buildInlineDatasource } from '../utils/buildInlineDatasources'
import { buildInlineSchema } from '../utils/buildInlineSchema'
import { buildNFTAnnotations } from '../utils/buildNFTAnnotations'
import { buildRequirePath } from '../utils/buildRequirePath'
import { buildWarnEnvConflicts } from '../utils/buildWarnEnvConflicts'
import type { DatasourceOverwrite } from './../extractSqliteSources'
import { commonCodeJS, commonCodeTS } from './common'
import { Count } from './Count'
import { Enum } from './Enum'
import type { Generatable } from './Generatable'
import { ExportCollector } from './helpers'
import { InputType } from './Input'
import { Model } from './Model'
import { PrismaClientClass } from './PrismaClient'
export interface TSClientOptions {
projectRoot: string
clientVersion: string
engineVersion: string
document: DMMF.Document
runtimeDir: string
runtimeName: string
browser?: boolean
datasources: InternalDatasource[]
generator?: GeneratorConfig
platforms?: Platform[] // TODO: consider making it non-nullable
sqliteDatasourceOverrides?: DatasourceOverwrite[]
schemaPath: string
outputDir: string
activeProvider: string
dataProxy: boolean
}
export class TSClient implements Generatable {
protected readonly dmmf: DMMFHelper
constructor(protected readonly options: TSClientOptions) {
this.dmmf = new DMMFHelper(klona(options.document))
}
public async toJS(edge = false): Promise<string> {
const {
platforms,
generator,
sqliteDatasourceOverrides,
outputDir,
schemaPath,
runtimeDir,
runtimeName,
datasources,
dataProxy,
} = this.options
const envPaths = getEnvPaths(schemaPath, { cwd: outputDir })
const relativeEnvPaths = {
rootEnvPath: envPaths.rootEnvPath && path.relative(outputDir, envPaths.rootEnvPath),
schemaEnvPath: envPaths.schemaEnvPath && path.relative(outputDir, envPaths.schemaEnvPath),
}
// This ensures that any engine override is propagated to the generated clients config
const engineType = getClientEngineType(generator!)
if (generator) {
generator.config.engineType = engineType
}
const config: Omit<GetPrismaClientConfig, 'document' | 'dirname'> = {
generator,
relativeEnvPaths,
sqliteDatasourceOverrides,
relativePath: path.relative(outputDir, path.dirname(schemaPath)),
clientVersion: this.options.clientVersion,
engineVersion: this.options.engineVersion,
datasourceNames: datasources.map((d) => d.name),
activeProvider: this.options.activeProvider,
dataProxy: this.options.dataProxy,
}
// get relative output dir for it to be preserved even after bundling, or
// being moved around as long as we keep the same project dir structure.
const relativeOutdir = path.relative(process.cwd(), outputDir)
const code = `${commonCodeJS({ ...this.options, browser: false })}
${buildRequirePath(edge)}
${buildDirname(edge, relativeOutdir, runtimeDir)}
/**
* Enums
*/
// Based on
// https://github.com/microsoft/TypeScript/issues/3192#issuecomment-261720275
function makeEnum(x) { return x; }
${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') ?? ''}
${new Enum(
{
name: 'ModelName',
values: this.dmmf.mappings.modelOperations.map((m) => m.model),
},
true,
).toJS()}
${buildDMMF(dataProxy, this.options.document)}
/**
* Create the Client
*/
const config = ${JSON.stringify(config, null, 2)}
config.document = dmmf
config.dirname = dirname
${await buildInlineSchema(dataProxy, schemaPath)}
${buildInlineDatasource(dataProxy, datasources)}
${buildInjectableEdgeEnv(edge, datasources)}
${buildWarnEnvConflicts(edge, runtimeDir, runtimeName)}
const PrismaClient = getPrismaClient(config)
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
${buildNFTAnnotations(dataProxy, engineType, platforms, relativeOutdir)}
`
return code
}
public toTS(edge = false): string {
// edge exports the same ts definitions as the index
if (edge === true) return `export * from './index'`
const prismaClientClass = new PrismaClientClass(
this.dmmf,
this.options.datasources,
this.options.outputDir,
this.options.browser,
this.options.generator,
this.options.sqliteDatasourceOverrides,
path.dirname(this.options.schemaPath),
)
const collector = new ExportCollector()
const commonCode = commonCodeTS(this.options)
const modelAndTypes = Object.values(this.dmmf.typeAndModelMap).reduce((acc, modelOrType) => {
if (this.dmmf.outputTypeMap[modelOrType.name]) {
acc.push(new Model(modelOrType, this.dmmf, this.options.generator, collector))
}
return acc
}, [] as Model[])
// TODO: Make this code more efficient and directly return 2 arrays
const prismaEnums = this.dmmf.schema.enumTypes.prisma.map((type) => new Enum(type, true, collector).toTS())
const modelEnums = this.dmmf.schema.enumTypes.model?.map((type) => new Enum(type, false, collector).toTS())
const countTypes: Count[] = this.dmmf.schema.outputObjectTypes.prisma
.filter((t) => t.name.endsWith('CountOutputType'))
.map((t) => new Count(t, this.dmmf, this.options.generator, collector))
const code = `
/**
* Client
**/
${commonCode.tsWithoutNamespace()}
${modelAndTypes.map((m) => m.toTSWithoutNamespace()).join('\n')}
${
modelEnums && modelEnums.length > 0
? `
/**
* Enums
*/
// Based on
// https://github.com/microsoft/TypeScript/issues/3192#issuecomment-261720275
${modelEnums.join('\n\n')}
`
: ''
}
${prismaClientClass.toTSWithoutNamespace()}
export namespace Prisma {
${indent(
`${commonCode.ts()}
${new Enum(
{
name: 'ModelName',
values: this.dmmf.mappings.modelOperations.map((m) => m.model),
},
true,
collector,
).toTS()}
${prismaClientClass.toTS()}
export type Datasource = {
url?: string
}
/**
* Count Types
*/
${countTypes.map((t) => t.toTS()).join('\n')}
/**
* Models
*/
${modelAndTypes.map((model) => model.toTS()).join('\n')}
/**
* Enums
*/
// Based on
// https://github.com/microsoft/TypeScript/issues/3192#issuecomment-261720275
${prismaEnums.join('\n\n')}
/**
* Deep Input Types
*/
${this.dmmf.inputObjectTypes.prisma
.reduce((acc, inputType) => {
if (inputType.name.includes('Json') && inputType.name.includes('Filter')) {
// This generates types for JsonFilter to prevent the usage of 'path' without another parameter
const baseName = `Required<${inputType.name}Base>`
acc.push(`export type ${inputType.name} =
| PatchUndefined<
Either<${baseName}, Exclude<keyof ${baseName}, 'path'>>,
${baseName}
>
| OptionalFlat<Omit<${baseName}, 'path'>>`)
collector?.addSymbol(inputType.name)
acc.push(new InputType({ ...inputType, name: `${inputType.name}Base` }, collector).toTS())
} else {
acc.push(new InputType(inputType, collector).toTS())
}
return acc
}, [] as string[])
.join('\n')}
${this.dmmf.inputObjectTypes.model?.map((inputType) => new InputType(inputType, collector).toTS()).join('\n') ?? ''}
/**
* Batch Payload for updateMany & deleteMany & createMany
*/
export type BatchPayload = {
count: number
}
/**
* DMMF
*/
export const dmmf: runtime.BaseDMMF
`,
2,
)}}`
return code
}
public toBrowserJS(): string {
const code = `${commonCodeJS({
...this.options,
runtimeName: 'index-browser',
browser: true,
})}
/**
* Enums
*/
// Based on
// https://github.com/microsoft/TypeScript/issues/3192#issuecomment-261720275
function makeEnum(x) { return x; }
${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') ?? ''}
${new Enum(
{
name: 'ModelName',
values: this.dmmf.mappings.modelOperations.map((m) => m.model),
},
true,
).toJS()}
/**
* Create the Client
*/
class PrismaClient {
constructor() {
throw new Error(
\`PrismaClient is unable to be run in the browser.
In case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues\`,
)
}
}
exports.PrismaClient = PrismaClient
Object.assign(exports, Prisma)
`
return code
}
}