import Debug from '@prisma/debug' ; import { BinaryType } from '@prisma/fetch-engine' ; import chalk from 'chalk' ; import type { ChildProcess } from 'child_process' ; import { spawn } from 'child_process' ; import { ErrorArea , RustPanic } from './panic' ; import { resolveBinary } from './resolveBinary' ; import byline from './utils/byline' ; const debugCli = Debug ( 'prisma:introspectionEngine:cli' ) ; const debugRpc = Debug ( 'prisma:introspectionEngine:rpc' ) ; const debugStderr = Debug ( 'prisma:introspectionEngine:stderr' ) ; const debugStdin = Debug ( 'prisma:introspectionEngine:stdin' ) ; export interface IntrospectionEngineOptions { binaryPath ? : string ; debug ? : boolean ; cwd ? : string ; } export interface RPCPayload { id : number ; jsonrpc : string ; method : string ; params : any ; } export class IntrospectionPanic extends Error { public request : any ; public rustStack : string ; constructor ( message , rustStack , request ) { super ( message ) ; this . rustStack = rustStack ; this . request = request ; } } export class IntrospectionError extends Error { public code : string ; constructor ( message , code ) { super ( message ) ; this . code = code ; } } export type IntrospectionWarnings = IntrospectionWarningsUnhandled | IntrospectionWarningsInvalidReintro | IntrospectionWarningsMissingUnique | IntrospectionWarningsEmptyFieldName | IntrospectionWarningsUnsupportedType | IntrospectionWarningsInvalidEnumName | IntrospectionWarningsCuidPrisma1 | IntrospectionWarningsUuidPrisma1 | IntrospectionWarningsFieldModelReintro | IntrospectionWarningsFieldMapReintro | IntrospectionWarningsEnumMapReintro | IntrospectionWarningsEnumValueMapReintro | IntrospectionWarningsCuidReintro | IntrospectionWarningsUuidReintro | IntrospectionWarningsUpdatedAtReintro | IntrospectionWarningsMongoMultipleTypes ; type AffectedModel = { model : string ; } [ ] ; type AffectedModelAndField = { model : string ; field : string ; } [ ] ; type AffectedModelAndFieldAndType = { model : string ; field : string ; tpe : string ; } [ ] ; type AffectedEnum = { enm : string ; } [ ] ; type AffectedEnumAndValue = { enm : string ; value : string ; } [ ] ; interface IntrospectionWarning { code : number ; message : string ; affected : AffectedModel | AffectedModelAndField | AffectedModelAndFieldAndType | AffectedEnum | AffectedEnumAndValue | null ; } interface IntrospectionWarningsUnhandled extends IntrospectionWarning { code : number ; affected : any ; } interface IntrospectionWarningsInvalidReintro extends IntrospectionWarning { code : 0 ; affected : null ; } interface IntrospectionWarningsMissingUnique extends IntrospectionWarning { code : 1 ; affected : AffectedModel ; } interface IntrospectionWarningsEmptyFieldName extends IntrospectionWarning { code : 2 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsUnsupportedType extends IntrospectionWarning { code : 3 ; affected : AffectedModelAndFieldAndType ; } interface IntrospectionWarningsInvalidEnumName extends IntrospectionWarning { code : 4 ; affected : AffectedEnumAndValue ; } interface IntrospectionWarningsCuidPrisma1 extends IntrospectionWarning { code : 5 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsUuidPrisma1 extends IntrospectionWarning { code : 6 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsFieldModelReintro extends IntrospectionWarning { code : 7 ; affected : AffectedModel ; } interface IntrospectionWarningsFieldMapReintro extends IntrospectionWarning { code : 8 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsEnumMapReintro extends IntrospectionWarning { code : 9 ; affected : AffectedEnum ; } interface IntrospectionWarningsEnumValueMapReintro extends IntrospectionWarning { code : 10 ; affected : AffectedEnum ; } interface IntrospectionWarningsCuidReintro extends IntrospectionWarning { code : 11 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsUuidReintro extends IntrospectionWarning { code : 12 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsUpdatedAtReintro extends IntrospectionWarning { code : 13 ; affected : AffectedModelAndField ; } interface IntrospectionWarningsMongoMultipleTypes extends IntrospectionWarning { code : 101 ; name : [ affected : AffectedModelAndFieldAndType ] ; } export type IntrospectionSchemaVersion = 'Prisma2' | 'Prisma1' | 'Prisma11' | 'NonPrisma' ; let messageId = 1 ; export class IntrospectionEngine { private debug : boolean ; private cwd : string ; private child ? : ChildProcess ; private listeners : { [ key ] : ( result : any , err ? : any ) => any ; } = { } ; private messages : string [ ] = [ ] ; private lastRequest ? : any ; private lastError ? : any ; private initPromise ? : Promise < void > ; private lastUrl ? : string ; public isRunning = false ; constructor ( { debug , cwd } = { debug : false , cwd : process . cwd ( ) , } ) { if ( debug ) { Debug . enable ( 'IntrospectionEngine*' ) ; } this . debug = Boolean ( debug ) ; this . cwd = cwd || process . cwd ( ) ; } public stop ( ) { if ( this . child ) { this . child . kill ( ) ; this . isRunning = false ; } } private rejectAll ( err ) { Object . entries ( this . listeners ) . map ( ( [ id , listener ] ) => { listener ( null , err ) ; delete this . listeners [ id ] ; } ) ; } private registerCallback ( id , callback ) { this . listeners [ id ] = callback ; } public getDatabaseDescription ( schema ) { return this . runCommand ( this . getRPCPayload ( 'getDatabaseDescription' , { schema } ) ) ; } public getDatabaseVersion ( schema ) { return this . runCommand ( this . getRPCPayload ( 'getDatabaseVersion' , { schema } ) ) ; } public introspect ( schema , force ? , compositeTypeDepth ? ) { this . lastUrl = schema ; return this . runCommand ( this . getRPCPayload ( 'introspect' , { schema , force , compositeTypeDepth } ) ) ; } public debugPanic ( ) { return this . runCommand ( this . getRPCPayload ( 'debugPanic' , undefined ) ) ; } public listDatabases ( schema ) { this . lastUrl = schema ; return this . runCommand ( this . getRPCPayload ( 'listDatabases' , { schema } ) ) ; } public getDatabaseMetadata ( schema ) { this . lastUrl = schema ; return this . runCommand ( this . getRPCPayload ( 'getDatabaseMetadata' , { schema } ) ) ; } private handleResponse ( response ) { let result ; try { result = JSON . parse ( response ) ; } catch ( e ) { console . error ( `Could not parse introspection engine response: ${ response . slice ( 0 , 200 ) }` ) ; } if ( result ) { if ( result . backtrace ) { console . log ( result ) ; } if ( ! result . id ) { console . error ( `Response ${ JSON . stringify ( result ) } doesn't have an id and I can't handle that (yet)` ) ; } if ( ! this . listeners [ result . id ] ) { console . error ( `Got result for unknown id ${ result . id }` ) ; } if ( this . listeners [ result . id ] ) { this . listeners [ result . id ] ( result ) ; delete this . listeners [ result . id ] ; } } } private init ( ) { if ( ! this . initPromise ) { this . initPromise = this . internalInit ( ) ; } return this . initPromise ; } private internalInit ( ) { return new Promise ( async ( resolve , reject ) : Promise < void > => { try { const binaryPath = await resolveBinary ( BinaryType . introspectionEngine ) ; debugRpc ( 'starting introspection engine with binary: ' + binaryPath ) ; this . child = spawn ( binaryPath , { env : process . env , cwd : this . cwd , stdio : [ 'pipe' , 'pipe' , 'pipe' ] , } ) ; this . isRunning = true ; this . child . on ( 'error' , ( err ) => { console . error ( '[introspection-engine] error: %s' , err ) ; this . child ?. kill ( ) ; this . rejectAll ( err ) ; reject ( err ) ; } ) ; this . child . stdin ?. on ( 'error' , ( err ) => { console . error ( err ) ; this . child ?. kill ( ) ; } ) ; this . child . on ( 'exit' , ( code ) => { this . isRunning = false ; if ( code === 255 && this . lastError && this . lastError . is_panic ) { const err = new RustPanic ( this . lastError . message , this . lastError . backtrace , this . lastRequest , ErrorArea . INTROSPECTION_CLI , undefined , this . lastUrl ) ; this . rejectAll ( err ) ; reject ( err ) ; return ; } const messages = this . messages . join ( '\n' ) ; let err ; if ( code !== 0 || messages . includes ( 'panicked at' ) ) { let errorMessage = chalk . red . bold ( 'Error in introspection engine: ' ) + messages ; if ( this . lastError && this . lastError . msg === 'PANIC' ) { errorMessage = serializePanic ( this . lastError ) ; err = new IntrospectionPanic ( errorMessage , messages , this . lastRequest ) ; } else if ( messages . includes ( 'panicked at' ) ) { err = new IntrospectionPanic ( errorMessage , messages , this . lastRequest ) ; } err = err || new Error ( errorMessage ) ; this . rejectAll ( err ) ; reject ( err ) ; } } ) ; this . child . stdin ! . on ( 'error' , ( err ) => { debugStdin ( err ) ; } ) ; byline ( this . child . stderr ) . on ( 'data' , ( data ) => { const msg = String ( data ) ; this . messages . push ( msg ) ; debugStderr ( msg ) ; try { const json = JSON . parse ( msg ) ; if ( json . backtrace ) { this . lastError = json ; } if ( json . level === 'ERRO' ) { this . lastError = json ; } } catch ( e ) { debugCli ( e ) ; } } ) ; byline ( this . child . stdout ) . on ( 'data' , ( line ) => { this . handleResponse ( String ( line ) ) ; } ) ; setImmediate ( ( ) => { resolve ( ) ; } ) ; } catch ( e ) { this . child ?. kill ( ) ; reject ( e ) ; } } ) ; } private async runCommand ( request ) { await this . init ( ) ; if ( process . env . FORCE_PANIC_INTROSPECTION_ENGINE ) { request = this . getRPCPayload ( 'debugPanic' , undefined ) ; } if ( this . child ?. killed ) { throw new Error ( `Can't execute ${ JSON . stringify ( request ) } because introspection engine already exited.` ) ; } return new Promise ( ( resolve , reject ) => { this . registerCallback ( request . id , ( response , err ) => { if ( err ) { return reject ( err ) ; } if ( typeof response . result !== 'undefined' ) { resolve ( response . result ) ; } else { if ( response . error ) { this . child ?. kill ( ) ; debugRpc ( response ) ; if ( response . error . data ?. is_panic ) { const message = response . error . data ?. error ?. message ?? response . error . message ; reject ( new RustPanic ( message , message , request , ErrorArea . INTROSPECTION_CLI , undefined , this . lastUrl ) ) ; } else if ( response . error . data ?. message ) { let message = `${ response . error . data . message }\n` ; if ( response . error . data ?. error_code ) { message = chalk . redBright ( `${ response . error . data . error_code }\n\n` ) + message ; reject ( new IntrospectionError ( message , response . error . data . error_code ) ) ; } else { reject ( new Error ( message ) ) ; } } else { reject ( new Error ( `${ chalk . redBright ( 'Error in RPC' ) }\n Request: ${ JSON . stringify ( request , null , 2 ) }\nResponse: ${ JSON . stringify ( response , null , 2 ) }\n${ response . error . message }\n` ) ) ; } } else { reject ( new Error ( `Got invalid RPC response without .result property: ${ JSON . stringify ( response ) }` ) ) ; } } } ) ; if ( this . child ! . stdin ! . destroyed ) { throw new Error ( `Can't execute ${ JSON . stringify ( request ) } because introspection engine is destroyed.` ) ; } debugRpc ( 'SENDING RPC CALL' , JSON . stringify ( request ) ) ; this . child ! . stdin ! . write ( JSON . stringify ( request ) + '\n' ) ; this . lastRequest = request ; } ) ; } private getRPCPayload ( method , params ) { return { id : messageId ++ , jsonrpc : '2.0' , method , params : params ? [ { ... params } ] : undefined , } ; } } function serializePanic ( log ) { return `${ chalk . red . bold ( 'Error in introspection engine.\nReason: ' ) }
${ log . reason } in ${ chalk . underline ( `${ log . file }:${ log . line }:${ log . column }` ) }

Please create an issue in the ${ chalk . bold ( 'prisma' ) } repo with the error 🙏:
${ chalk . underline ( 'https://github.com/prisma/prisma/issues/new' ) }\n` ; }