diff --git a/packages/client/src/__tests__/integration/happy/exhaustive-schema-mongo/__snapshots__/test.ts.snap b/packages/client/src/__tests__/integration/happy/exhaustive-schema-mongo/__snapshots__/test.ts.snap index f53c7007ec39..cebc55231721 100644 --- a/packages/client/src/__tests__/integration/happy/exhaustive-schema-mongo/__snapshots__/test.ts.snap +++ b/packages/client/src/__tests__/integration/happy/exhaustive-schema-mongo/__snapshots__/test.ts.snap @@ -20810,6 +20810,6 @@ export namespace Prisma { /** * DMMF */ - export const dmmf: runtime.DMMF.Document; + export const dmmf: runtime.BaseDMMF } `; diff --git a/packages/client/src/__tests__/integration/happy/exhaustive-schema/__snapshots__/test.ts.snap b/packages/client/src/__tests__/integration/happy/exhaustive-schema/__snapshots__/test.ts.snap index de20cfdf68bc..a32202c4c832 100644 --- a/packages/client/src/__tests__/integration/happy/exhaustive-schema/__snapshots__/test.ts.snap +++ b/packages/client/src/__tests__/integration/happy/exhaustive-schema/__snapshots__/test.ts.snap @@ -18495,6 +18495,6 @@ export namespace Prisma { /** * DMMF */ - export const dmmf: runtime.DMMF.Document; + export const dmmf: runtime.BaseDMMF } `; diff --git a/packages/client/src/generation/TSClient/TSClient.ts b/packages/client/src/generation/TSClient/TSClient.ts index e76eb8db3c6f..6eb1156e9d6b 100644 --- a/packages/client/src/generation/TSClient/TSClient.ts +++ b/packages/client/src/generation/TSClient/TSClient.ts @@ -22,7 +22,7 @@ import { commonCodeJS, commonCodeTS } from './common' import { Count } from './Count' import { Enum } from './Enum' import type { Generatable } from './Generatable' -import { escapeJson, ExportCollector } from './helpers' +import { ExportCollector } from './helpers' import { InputType } from './Input' import { Model } from './Model' import { PrismaClientClass } from './PrismaClient' @@ -47,9 +47,8 @@ export interface TSClientOptions { export class TSClient implements Generatable { protected readonly dmmf: DMMFHelper - protected readonly dmmfString: string + constructor(protected readonly options: TSClientOptions) { - this.dmmfString = escapeJson(JSON.stringify(options.document)) this.dmmf = new DMMFHelper(klona(options.document)) } @@ -114,7 +113,7 @@ ${new Enum( }, true, ).toJS()} -${buildDMMF(dataProxy, this.dmmfString)} +${buildDMMF(dataProxy, this.options.document)} /** * Create the Client @@ -265,7 +264,7 @@ export type BatchPayload = { /** * DMMF */ -export const dmmf: runtime.DMMF.Document; +export const dmmf: runtime.BaseDMMF `, 2, )}}` diff --git a/packages/client/src/generation/utils/buildDMMF.ts b/packages/client/src/generation/utils/buildDMMF.ts index b51560e06b28..56593a968610 100644 --- a/packages/client/src/generation/utils/buildDMMF.ts +++ b/packages/client/src/generation/utils/buildDMMF.ts @@ -1,18 +1,25 @@ +import { DMMF } from '@prisma/generator-helper' import lzString from 'lz-string' +import { escapeJson } from '../TSClient/helpers' + /** * Creates the necessary declarations to embed the generated DMMF into the - * generated client. It compresses the DMMF for the data proxy engine. + * generated client. It compresses the DMMF for the data proxy engine. For + * data proxy, the full DMMF is exported, otherwise `schema` is removed. * @param dataProxy * @param dmmf * @returns */ -export function buildDMMF(dataProxy: boolean | undefined, dmmf: string) { +export function buildDMMF(dataProxy: boolean | undefined, dmmf: DMMF.Document) { if (dataProxy === true) { - return buildCompressedDMMF(dmmf) + const dmmfString = escapeJson(JSON.stringify(dmmf)) + return buildCompressedDMMF(dmmfString) } - return buildUncompressedDMMF(dmmf) + const { datamodel, mappings } = dmmf + const dmmfString = escapeJson(JSON.stringify({ datamodel, mappings })) + return buildUncompressedDMMF(dmmfString) } /** diff --git a/packages/client/src/runtime/RequestHandler.ts b/packages/client/src/runtime/RequestHandler.ts index bc6b516ba83b..0fc375c8f418 100644 --- a/packages/client/src/runtime/RequestHandler.ts +++ b/packages/client/src/runtime/RequestHandler.ts @@ -28,7 +28,6 @@ export type RequestParams = { callsite?: string rejectOnNotFound?: RejectOnNotFound runInTransaction?: boolean - showColors?: boolean engineHook?: EngineMiddleware args: any headers?: Record @@ -36,6 +35,12 @@ export type RequestParams = { unpacker?: Unpacker } +export type HandleErrorParams = { + error: any + clientMethod: string + callsite?: string +} + export type Request = { document: Document runInTransaction?: boolean @@ -99,7 +104,6 @@ export class RequestHandler { rejectOnNotFound, clientMethod, runInTransaction, - showColors, engineHook, args, headers, @@ -154,37 +158,42 @@ export class RequestHandler { return { data: unpackResult, elapsed } } return unpackResult - } catch (e: any) { - debug(e) - let message = e.message - if (callsite) { - const { stack } = printStack({ - callsite, - originalMethod: clientMethod, - onUs: e.isPanic, - showColors, - }) - message = `${stack}\n ${e.message}` - } + } catch (error) { + this.handleRequestError({ error, clientMethod, callsite }) + } + } - message = this.sanitizeMessage(message) - // TODO: Do request with callsite instead, so we don't need to rethrow - if (e.code) { - throw new PrismaClientKnownRequestError(message, e.code, this.client._clientVersion, e.meta) - } else if (e.isPanic) { - throw new PrismaClientRustPanicError(message, this.client._clientVersion) - } else if (e instanceof PrismaClientUnknownRequestError) { - throw new PrismaClientUnknownRequestError(message, this.client._clientVersion) - } else if (e instanceof PrismaClientInitializationError) { - throw new PrismaClientInitializationError(message, this.client._clientVersion) - } else if (e instanceof PrismaClientRustPanicError) { - throw new PrismaClientRustPanicError(message, this.client._clientVersion) - } + handleRequestError({ error, clientMethod, callsite }: HandleErrorParams): never { + debug(error) - e.clientVersion = this.client._clientVersion + let message = error.message + if (callsite) { + const { stack } = printStack({ + callsite, + originalMethod: clientMethod, + onUs: error.isPanic, + showColors: this.client._errorFormat === 'pretty', + }) + message = `${stack}\n ${error.message}` + } - throw e + message = this.sanitizeMessage(message) + // TODO: Do request with callsite instead, so we don't need to rethrow + if (error.code) { + throw new PrismaClientKnownRequestError(message, error.code, this.client._clientVersion, error.meta) + } else if (error.isPanic) { + throw new PrismaClientRustPanicError(message, this.client._clientVersion) + } else if (error instanceof PrismaClientUnknownRequestError) { + throw new PrismaClientUnknownRequestError(message, this.client._clientVersion) + } else if (error instanceof PrismaClientInitializationError) { + throw new PrismaClientInitializationError(message, this.client._clientVersion) + } else if (error instanceof PrismaClientRustPanicError) { + throw new PrismaClientRustPanicError(message, this.client._clientVersion) } + + error.clientVersion = this.client._clientVersion + + throw error } sanitizeMessage(message) { diff --git a/packages/client/src/runtime/core/model/applyFluent.ts b/packages/client/src/runtime/core/model/applyFluent.ts index 6ab972090a83..dd85fa11f6b4 100644 --- a/packages/client/src/runtime/core/model/applyFluent.ts +++ b/packages/client/src/runtime/core/model/applyFluent.ts @@ -86,7 +86,7 @@ export function applyFluent( prevUserArgs?: UserArgs, ) { // we retrieve the model that is described from the DMMF - const dmmfModel = client._dmmf.modelMap[dmmfModelName] + const dmmfModel = client._baseDmmf.modelMap[dmmfModelName] // map[field.name] === field, basically for quick access const dmmfModelFieldMap = dmmfModel.fields.reduce( @@ -126,7 +126,7 @@ export function applyFluent( // the only accessible fields are relations to be chained on function getOwnKeys(client: Client, dmmfModelName: string) { - return client._dmmf.modelMap[dmmfModelName].fields + return client._baseDmmf.modelMap[dmmfModelName].fields .filter((field) => field.kind === 'object') // relations .map((field) => field.name) } diff --git a/packages/client/src/runtime/core/model/applyModel.ts b/packages/client/src/runtime/core/model/applyModel.ts index f6899193fa13..6192c5e8a25e 100644 --- a/packages/client/src/runtime/core/model/applyModel.ts +++ b/packages/client/src/runtime/core/model/applyModel.ts @@ -71,7 +71,7 @@ export function applyModel(client: Client, dmmfModelName: string) { // the only accessible fields are the ones that are actions function getOwnKeys(client: Client, dmmfModelName: string) { - return [...Object.keys(client._dmmf.mappingsMap[dmmfModelName]), 'count'].filter( + return [...Object.keys(client._baseDmmf.mappingsMap[dmmfModelName]), 'count'].filter( (key) => !['model', 'plural'].includes(key), ) } diff --git a/packages/client/src/runtime/core/model/applyModels.ts b/packages/client/src/runtime/core/model/applyModels.ts index 160f65862752..ca37227e49b8 100644 --- a/packages/client/src/runtime/core/model/applyModels.ts +++ b/packages/client/src/runtime/core/model/applyModels.ts @@ -29,12 +29,12 @@ export function applyModels(client: C) { } // creates a new model proxy on the fly and caches it - if (client._dmmf.modelMap[dmmfModelName] !== undefined) { + if (client._baseDmmf.modelMap[dmmfModelName] !== undefined) { return (modelCache[dmmfModelName] = applyModel(client, dmmfModelName)) } // above silently failed if model name is lower cased - if (client._dmmf.modelMap[prop] !== undefined) { + if (client._baseDmmf.modelMap[prop] !== undefined) { return (modelCache[dmmfModelName] = applyModel(client, prop)) } }, @@ -44,5 +44,5 @@ export function applyModels(client: C) { // the only accessible fields are the ones that are models function getOwnKeys(client: Client) { - return [...Object.keys(client._dmmf.modelMap).map(dmmfToJSModelName), ...Object.keys(client)] + return [...Object.keys(client._baseDmmf.modelMap).map(dmmfToJSModelName), ...Object.keys(client)] } diff --git a/packages/client/src/runtime/dmmf-types.ts b/packages/client/src/runtime/dmmf-types.ts index 17539a141953..576f24b2bff9 100644 --- a/packages/client/src/runtime/dmmf-types.ts +++ b/packages/client/src/runtime/dmmf-types.ts @@ -1,3 +1,5 @@ import { DMMF } from '@prisma/generator-helper' export { DMMF } + +export type BaseDMMF = Pick diff --git a/packages/client/src/runtime/dmmf.ts b/packages/client/src/runtime/dmmf.ts index 3762b59c69ff..0d9909634f3e 100644 --- a/packages/client/src/runtime/dmmf.ts +++ b/packages/client/src/runtime/dmmf.ts @@ -1,72 +1,108 @@ import type { DMMF } from '@prisma/generator-helper' +import { BaseDMMF } from './dmmf-types' +import { applyMixins } from './utils/applyMixins' import type { Dictionary } from './utils/common' import { keyBy, ScalarTypeTable } from './utils/common' -export class DMMFHelper implements DMMF.Document { - public datamodel: DMMF.Datamodel - public schema: DMMF.Schema - public mappings: DMMF.Mappings - public queryType: DMMF.OutputType - public mutationType: DMMF.OutputType +class DMMFDatamodelHelper implements Pick { + datamodel: DMMF.Datamodel + datamodelEnumMap: Dictionary + modelMap: Dictionary + typeMap: Dictionary + typeAndModelMap: Dictionary - public outputTypes: { model: DMMF.OutputType[]; prisma: DMMF.OutputType[] } - public outputTypeMap: Dictionary + constructor({ datamodel }: Pick) { + this.datamodel = datamodel + this.datamodelEnumMap = this.getDatamodelEnumMap() + this.modelMap = this.getModelMap() + this.typeMap = this.getTypeMap() + this.typeAndModelMap = this.getTypeModelMap() + } + + getDatamodelEnumMap(): Dictionary { + return keyBy(this.datamodel.enums, 'name') + } + + getModelMap(): Dictionary { + return { ...keyBy(this.datamodel.models, 'name') } + } + + getTypeMap(): Dictionary { + return { ...keyBy(this.datamodel.types, 'name') } + } + + getTypeModelMap(): Dictionary { + return { ...this.getTypeMap(), ...this.getModelMap() } + } +} - public inputObjectTypes: { +class DMMFMappingsHelper implements Pick { + mappings: DMMF.Mappings + mappingsMap: Dictionary + + constructor({ mappings }: Pick) { + this.mappings = mappings + this.mappingsMap = this.getMappingsMap() + } + + getMappingsMap(): Dictionary { + return keyBy(this.mappings.modelOperations, 'model') + } +} + +class DMMFSchemaHelper implements Pick { + schema: DMMF.Schema + queryType: DMMF.OutputType + mutationType: DMMF.OutputType + + outputTypes: { model: DMMF.OutputType[]; prisma: DMMF.OutputType[] } + outputTypeMap: Dictionary + + inputObjectTypes: { model?: DMMF.InputType[] prisma: DMMF.InputType[] } - public inputTypeMap: Dictionary + inputTypeMap: Dictionary - public enumMap: Dictionary + enumMap: Dictionary - public datamodelEnumMap: Dictionary - public modelMap: Dictionary - public typeMap: Dictionary - public typeAndModelMap: Dictionary - public mappingsMap: Dictionary - public rootFieldMap: Dictionary - constructor({ datamodel, schema, mappings }: DMMF.Document) { - this.datamodel = datamodel + rootFieldMap: Dictionary + constructor({ schema }: Pick) { this.schema = schema - this.mappings = mappings this.enumMap = this.getEnumMap() - this.datamodelEnumMap = this.getDatamodelEnumMap() + this.queryType = this.getQueryType() this.mutationType = this.getMutationType() - this.modelMap = this.getModelMap() - this.typeMap = this.getTypeMap() - this.typeAndModelMap = this.getTypeModelMap() this.outputTypes = this.getOutputTypes() - this.outputTypeMap = this.getMergedOutputTypeMap() - this.resolveOutputTypes() this.inputObjectTypes = this.schema.inputObjectTypes this.inputTypeMap = this.getInputTypeMap() this.resolveInputTypes() this.resolveFieldArgumentTypes() - this.mappingsMap = this.getMappingsMap() // needed as references are not kept this.queryType = this.outputTypeMap.Query this.mutationType = this.outputTypeMap.Mutation this.rootFieldMap = this.getRootFieldMap() } + get [Symbol.toStringTag]() { return 'DMMFClass' } - protected outputTypeToMergedOutputType = (outputType: DMMF.OutputType): DMMF.OutputType => { + + outputTypeToMergedOutputType = (outputType: DMMF.OutputType): DMMF.OutputType => { return { ...outputType, fields: outputType.fields, } } - protected resolveOutputTypes() { + + resolveOutputTypes() { for (const type of this.outputTypes.model) { for (const field of type.fields) { if (typeof field.outputType.type === 'string' && !ScalarTypeTable[field.outputType.type]) { @@ -79,6 +115,7 @@ export class DMMFHelper implements DMMF.Document { } type.fieldMap = keyBy(type.fields, 'name') } + for (const type of this.outputTypes.prisma) { for (const field of type.fields) { if (typeof field.outputType.type === 'string' && !ScalarTypeTable[field.outputType.type]) { @@ -89,14 +126,18 @@ export class DMMFHelper implements DMMF.Document { field.outputType.type } } + type.fieldMap = keyBy(type.fields, 'name') } } - protected resolveInputTypes() { + + resolveInputTypes() { const inputTypes = this.inputObjectTypes.prisma + if (this.inputObjectTypes.model) { inputTypes.push(...this.inputObjectTypes.model) } + for (const type of inputTypes) { for (const field of type.fields) { for (const fieldInputType of field.inputTypes) { @@ -113,7 +154,8 @@ export class DMMFHelper implements DMMF.Document { type.fieldMap = keyBy(type.fields, 'name') } } - protected resolveFieldArgumentTypes() { + + resolveFieldArgumentTypes() { for (const type of this.outputTypes.prisma) { for (const field of type.fields) { for (const arg of field.args) { @@ -126,6 +168,7 @@ export class DMMFHelper implements DMMF.Document { } } } + for (const type of this.outputTypes.model) { for (const field of type.fields) { for (const arg of field.args) { @@ -139,13 +182,16 @@ export class DMMFHelper implements DMMF.Document { } } } - protected getQueryType(): DMMF.OutputType { + + getQueryType(): DMMF.OutputType { return this.schema.outputObjectTypes.prisma.find((t) => t.name === 'Query')! } - protected getMutationType(): DMMF.OutputType { + + getMutationType(): DMMF.OutputType { return this.schema.outputObjectTypes.prisma.find((t) => t.name === 'Mutation')! } - protected getOutputTypes(): { + + getOutputTypes(): { model: DMMF.OutputType[] prisma: DMMF.OutputType[] } { @@ -154,40 +200,47 @@ export class DMMFHelper implements DMMF.Document { prisma: this.schema.outputObjectTypes.prisma.map(this.outputTypeToMergedOutputType), } } - protected getDatamodelEnumMap(): Dictionary { - return keyBy(this.datamodel.enums, 'name') - } - protected getEnumMap(): Dictionary { + + getEnumMap(): Dictionary { return { ...keyBy(this.schema.enumTypes.prisma, 'name'), ...(this.schema.enumTypes.model ? keyBy(this.schema.enumTypes.model, 'name') : undefined), } } - protected getModelMap(): Dictionary { - return { ...keyBy(this.datamodel.models, 'name') } - } - protected getTypeMap(): Dictionary { - return { ...keyBy(this.datamodel.types, 'name') } - } - protected getTypeModelMap(): Dictionary { - return { ...this.getTypeMap(), ...this.getModelMap() } - } - protected getMergedOutputTypeMap(): Dictionary { + + getMergedOutputTypeMap(): Dictionary { return { ...keyBy(this.outputTypes.model, 'name'), ...keyBy(this.outputTypes.prisma, 'name'), } } - protected getInputTypeMap(): Dictionary { + + getInputTypeMap(): Dictionary { return { ...(this.schema.inputObjectTypes.model ? keyBy(this.schema.inputObjectTypes.model, 'name') : undefined), ...keyBy(this.schema.inputObjectTypes.prisma, 'name'), } } - protected getMappingsMap(): Dictionary { - return keyBy(this.mappings.modelOperations, 'model') - } - protected getRootFieldMap(): Dictionary { + + getRootFieldMap(): Dictionary { return { ...keyBy(this.queryType.fields, 'name'), ...keyBy(this.mutationType.fields, 'name') } } } + +export interface BaseDMMFHelper extends DMMFDatamodelHelper, DMMFMappingsHelper {} +export class BaseDMMFHelper { + constructor(dmmf: BaseDMMF) { + return Object.assign(this, new DMMFDatamodelHelper(dmmf), new DMMFMappingsHelper(dmmf)) + } +} + +applyMixins(BaseDMMFHelper, [DMMFDatamodelHelper, DMMFMappingsHelper]) + +export interface DMMFHelper extends BaseDMMFHelper, DMMFSchemaHelper {} +export class DMMFHelper { + constructor(dmmf: DMMF.Document) { + return Object.assign(this, new BaseDMMFHelper(dmmf), new DMMFSchemaHelper(dmmf)) + } +} + +applyMixins(DMMFHelper, [BaseDMMFHelper, DMMFSchemaHelper]) diff --git a/packages/client/src/runtime/getPrismaClient.ts b/packages/client/src/runtime/getPrismaClient.ts index 7cb08db247e8..59c7a2cec652 100644 --- a/packages/client/src/runtime/getPrismaClient.ts +++ b/packages/client/src/runtime/getPrismaClient.ts @@ -9,7 +9,9 @@ import { AsyncResource } from 'async_hooks' import fs from 'fs' import path from 'path' import * as sqlTemplateTag from 'sql-template-tag' +import { O } from 'ts-toolbelt' +import { getPrismaClientDMMF } from '../generation/getDMMF' import type { InlineDatasources } from '../generation/utils/buildInlineDatasources' import { PrismaClientValidationError } from '.' import { MetricsClient } from './core/metrics/MetricsClient' @@ -18,7 +20,7 @@ import { createPrismaPromise } from './core/request/createPrismaPromise' import type { PrismaPromise } from './core/request/PrismaPromise' import { getLockCountPromise } from './core/transaction/utils/createLockCountPromise' import { getCallSite } from './core/utils/getCallSite' -import { DMMFHelper } from './dmmf' +import { BaseDMMFHelper, DMMFHelper } from './dmmf' import type { DMMF } from './dmmf-types' import { getLogLevel } from './getLogLevel' import { mergeBy } from './mergeBy' @@ -209,7 +211,7 @@ export type LogEvent = { * closure with that config around a non-instantiated [[PrismaClient]]. */ export interface GetPrismaClientConfig { - document: DMMF.Document + document: O.Optional generator?: GeneratorConfig sqliteDatasourceOverrides?: DatasourceOverwrite[] relativeEnvPaths: { @@ -288,7 +290,8 @@ const TX_ID = Symbol.for('prisma.client.transaction.id') export interface Client { /** Only via tx proxy */ [TX_ID]?: string - _dmmf: DMMFHelper + _baseDmmf: BaseDMMFHelper + _dmmf?: DMMFHelper _engine: Engine _fetcher: RequestHandler _connectionPromise?: Promise @@ -311,7 +314,8 @@ export interface Client { export function getPrismaClient(config: GetPrismaClientConfig) { class PrismaClient implements Client { - _dmmf: DMMFHelper + _baseDmmf: BaseDMMFHelper + _dmmf?: DMMFHelper _engine: Engine _fetcher: RequestHandler _connectionPromise?: Promise @@ -401,7 +405,14 @@ export function getPrismaClient(config: GetPrismaClientConfig) { this._errorFormat = 'colorless' // default errorFormat } - this._dmmf = new DMMFHelper(config.document) + this._baseDmmf = new BaseDMMFHelper(config.document) + + if (this._dataProxy) { + // the data proxy can't get the dmmf from the engine + // so the generated client always has the full dmmf + const rawDmmf = config.document as DMMF.Document + this._dmmf = new DMMFHelper(rawDmmf) + } this._previewFeatures = config.generator?.previewFeatures ?? [] @@ -546,12 +557,14 @@ export function getPrismaClient(config: GetPrismaClientConfig) { /** * Disconnect from the database */ - $disconnect() { + async $disconnect() { try { - return this._engine.stop() + await this._engine.stop() } catch (e: any) { e.clientVersion = this._clientVersion throw e + } finally { + this._dmmf = undefined } } @@ -1053,6 +1066,11 @@ new PrismaClient({ lock, unpacker, }: InternalRequestParams) { + if (this._dmmf === undefined) { + const dmmf = await this._getDmmf({ clientMethod, callsite }) + this._dmmf = new DMMFHelper(getPrismaClientDMMF(dmmf)) + } + let rootField: string | undefined const operation = actionOperationMap[action] @@ -1062,7 +1080,7 @@ new PrismaClient({ let mapping if (model !== undefined) { - mapping = this._dmmf.mappingsMap[model] + mapping = this._dmmf?.mappingsMap[model] if (mapping === undefined) { throw new Error(`Could not find mapping for model ${model}`) } @@ -1074,7 +1092,7 @@ new PrismaClient({ throw new Error(`Invalid operation ${operation} for action ${action}`) } - const field = this._dmmf.rootFieldMap[rootField!] + const field = this._dmmf?.rootFieldMap[rootField!] if (field === undefined) { throw new Error( @@ -1127,7 +1145,6 @@ new PrismaClient({ isList, rootField: rootField!, callsite, - showColors: this._errorFormat === 'pretty', args, engineHook: this._middlewares.engine.get(0), runInTransaction, @@ -1137,6 +1154,14 @@ new PrismaClient({ }) } + private async _getDmmf(params: Pick) { + try { + return await this._engine.getDmmf() + } catch (error) { + this._fetcher.handleRequestError({ ...params, error }) + } + } + get $metrics(): MetricsClient { if (!this._hasPreviewFlag('metrics')) { throw new PrismaClientValidationError( diff --git a/packages/client/src/runtime/index.ts b/packages/client/src/runtime/index.ts index 69214d3e9e26..ed99ed4ffad6 100644 --- a/packages/client/src/runtime/index.ts +++ b/packages/client/src/runtime/index.ts @@ -2,7 +2,7 @@ import * as lzString from 'lz-string' export { MetricsClient } from './core/metrics/MetricsClient' export { DMMFHelper as DMMFClass } from './dmmf' -export { DMMF } from './dmmf-types' +export { type BaseDMMF, DMMF } from './dmmf-types' export type { PrismaClientOptions } from './getPrismaClient' export { getPrismaClient } from './getPrismaClient' export { makeDocument, PrismaClientValidationError, transformDocument, unpack } from './query' diff --git a/packages/client/src/runtime/utils/applyMixins.ts b/packages/client/src/runtime/utils/applyMixins.ts new file mode 100644 index 000000000000..50dc4df7b678 --- /dev/null +++ b/packages/client/src/runtime/utils/applyMixins.ts @@ -0,0 +1,17 @@ +/** + * Based on the TS documentation for class mixins. + * @see https://www.typescriptlang.org/docs/handbook/mixins.html + * @param derivedCtor + * @param constructors + */ +export function applyMixins(derivedCtor: any, constructors: any[]) { + for (const baseCtor of constructors) { + for (const name of Object.getOwnPropertyNames(baseCtor.prototype)) { + Object.defineProperty( + derivedCtor.prototype, + name, + Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ?? (Object.create(null) as PropertyDescriptor), + ) + } + } +} diff --git a/packages/engine-core/src/binary/BinaryEngine.ts b/packages/engine-core/src/binary/BinaryEngine.ts index 6eea684042f0..dd8ef62f71fc 100644 --- a/packages/engine-core/src/binary/BinaryEngine.ts +++ b/packages/engine-core/src/binary/BinaryEngine.ts @@ -1,6 +1,6 @@ import Debug from '@prisma/debug' import { getEnginesPath } from '@prisma/engines' -import type { ConnectorType, GeneratorConfig } from '@prisma/generator-helper' +import type { ConnectorType, DMMF, GeneratorConfig } from '@prisma/generator-helper' import type { Platform } from '@prisma/get-platform' import { getPlatform, platforms } from '@prisma/get-platform' import chalk from 'chalk' @@ -90,6 +90,7 @@ export class BinaryEngine extends Engine { private lastRustError?: RustError private socketPath?: string private getConfigPromise?: Promise + private getDmmfPromise?: Promise private stopPromise?: Promise private beforeExitListener?: () => Promise private dirname?: string @@ -844,6 +845,26 @@ You very likely have the wrong "binaryTarget" defined in the schema.prisma file. return JSON.parse(result.stdout) } + async getDmmf(): Promise { + if (!this.getDmmfPromise) { + this.getDmmfPromise = this._getDmmf() + } + return this.getDmmfPromise + } + + private async _getDmmf(): Promise { + const prismaPath = await this.getPrismaPath() + + const env = await this.getEngineEnvVars() + + const result = await execa(prismaPath, ['--enable-raw-queries', 'cli', 'dmmf'], { + env: omit(env, ['PORT']), + cwd: this.cwd, + }) + + return JSON.parse(result.stdout) + } + async version(forceRun = false) { if (this.versionPromise && !forceRun) { return this.versionPromise diff --git a/packages/engine-core/src/common/Engine.ts b/packages/engine-core/src/common/Engine.ts index 50a74780cbbb..8d3f8a2024c0 100644 --- a/packages/engine-core/src/common/Engine.ts +++ b/packages/engine-core/src/common/Engine.ts @@ -1,4 +1,4 @@ -import type { DataSource, GeneratorConfig } from '@prisma/generator-helper' +import type { DataSource, DMMF, GeneratorConfig } from '@prisma/generator-helper' import type { Metrics, MetricsOptionsJson, MetricsOptionsPrometheus } from './types/Metrics' import type { QueryEngineRequestHeaders, QueryEngineResult } from './types/QueryEngine' @@ -15,6 +15,7 @@ export abstract class Engine { abstract start(): Promise abstract stop(): Promise abstract getConfig(): Promise + abstract getDmmf(): Promise abstract version(forceRun?: boolean): Promise | string abstract request( query: string, diff --git a/packages/engine-core/src/data-proxy/DataProxyEngine.ts b/packages/engine-core/src/data-proxy/DataProxyEngine.ts index f1bad4f5ccd4..d86a84a86465 100644 --- a/packages/engine-core/src/data-proxy/DataProxyEngine.ts +++ b/packages/engine-core/src/data-proxy/DataProxyEngine.ts @@ -1,3 +1,4 @@ +import { DMMF } from '@prisma/generator-helper' import EventEmitter from 'events' import type { EngineConfig, EngineEventType, GetConfigResult } from '../common/Engine' @@ -88,6 +89,13 @@ export class DataProxyEngine extends Engine { } as GetConfigResult) } + getDmmf(): Promise { + // This code path should not be reachable, as it is handled upstream in `getPrismaClient`. + throw new NotImplementedYetError('getDmmf is not yet supported', { + clientVersion: this.clientVersion, + }) + } + private async uploadSchema() { const response = await request(await this.url('schema'), { method: 'PUT', diff --git a/packages/engine-core/src/library/LibraryEngine.ts b/packages/engine-core/src/library/LibraryEngine.ts index bfa891b92e9a..5c4f501eee22 100644 --- a/packages/engine-core/src/library/LibraryEngine.ts +++ b/packages/engine-core/src/library/LibraryEngine.ts @@ -1,4 +1,5 @@ import Debug from '@prisma/debug' +import { DMMF } from '@prisma/generator-helper' import type { Platform } from '@prisma/get-platform' import { getPlatform, isNodeAPISupported, platforms } from '@prisma/get-platform' import chalk from 'chalk' @@ -95,6 +96,7 @@ export class LibraryEngine extends Engine { engines.push(this) this.checkForTooManyEngines() } + private checkForTooManyEngines() { if (engines.length >= 10) { const runningEngines = engines.filter((e) => e.engine) @@ -105,6 +107,7 @@ export class LibraryEngine extends Engine { } } } + async transaction(action: 'start', options?: Tx.Options): Promise async transaction(action: 'commit', info: Tx.Info): Promise async transaction(action: 'rollback', info: Tx.Info): Promise @@ -357,7 +360,9 @@ You may have to run ${chalk.greenBright('prisma generate')} for your changes to } } - getConfig(): Promise { + async getConfig(): Promise { + await this.libraryInstantiationPromise + return this.library!.getConfig({ datamodel: this.datamodel, datasourceOverrides: this.datasourceOverrides, @@ -366,6 +371,12 @@ You may have to run ${chalk.greenBright('prisma generate')} for your changes to }) } + async getDmmf(): Promise { + await this.libraryInstantiationPromise + + return JSON.parse(await this.library!.dmmf(this.datamodel)) + } + version(): string { this.versionInfo = this.library?.version() return this.versionInfo?.version ?? 'unknown'