Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,192
  • Linux
  • Apple
SchemaEngineWasm.ts11.6 kB
import Debug from '@prisma/debug' import type { ErrorCapturingSqlMigrationAwareDriverAdapterFactory, ErrorRegistry } from '@prisma/driver-adapter-utils' import { assertAlways, ErrorArea, getWasmError, isWasmPanic, relativizePathInPSLError, RustPanic, SchemaContext, wasm, wasmSchemaEngineLoader, } from '@prisma/internals' import { bold, red } from 'kleur/colors' import { SchemaEngine } from './SchemaEngine' import { EngineError } from './SchemaEngineCLI' import { EngineArgs } from './types' import { handleViewsIO } from './views/handleViewsIO' const debugStderr = Debug('prisma:schemaEngine:wasm:stderr') const debugStdout = Debug('prisma:schemaEngine:wasm:stdout') type SchemaEngineMethods = Omit<wasm.SchemaEngineWasm, 'free'> export type SchemaEngineInput<M extends keyof SchemaEngineMethods> = Parameters<wasm.SchemaEngineWasm[M]>[0] export type SchemaEngineOutput<M extends keyof SchemaEngineMethods> = ReturnType<wasm.SchemaEngineWasm[M]> interface SchemaEngineWasmSetupInput { adapter: ErrorCapturingSqlMigrationAwareDriverAdapterFactory enabledPreviewFeatures?: string[] schemaContext?: SchemaContext } export interface SchemaEngineWasmOptions extends Omit<SchemaEngineWasmSetupInput, 'adapter'> { debug?: boolean engine: wasm.SchemaEngineWasm errorRegistry: ErrorRegistry } /** * Wrapper around `@prisma/schema-engine-wasm`, which will eventually replace `SchemaEngineCLI`. * * TODOs: * - Catch and throw "rich" connector errors */ export class SchemaEngineWasm implements SchemaEngine { private engine: wasm.SchemaEngineWasm private errorRegistry: ErrorRegistry // TODO: forward enabled preview features to the Wasm engine private enabledPreviewFeatures?: string[] // `isRunning` is set to true when the engine is initialized, and set to false when the engine is stopped public isRunning = false private constructor({ debug, enabledPreviewFeatures, engine, errorRegistry }: SchemaEngineWasmOptions) { this.enabledPreviewFeatures = enabledPreviewFeatures if (debug) { Debug.enable('prisma:schemaEngine*') } this.engine = engine this.errorRegistry = errorRegistry } static async setup({ adapter, schemaContext, ...rest }: SchemaEngineWasmSetupInput): Promise<SchemaEngineWasm> { const debug = (arg: string) => { debugStderr(arg) } // Note: `datamodels` must be either `undefined` or a *non-empty* `LoadedFile[]`. const datamodels = schemaContext?.schemaFiles const engine = await wasmSchemaEngineLoader.loadSchemaEngine( { datamodels, }, debug, adapter, ) return new SchemaEngineWasm({ ...rest, engine, errorRegistry: adapter.errorRegistry }) } private async runCommand<M extends keyof SchemaEngineMethods>( command: M, input: SchemaEngineInput<M>, ): Promise<Awaited<SchemaEngineOutput<M>>> { if (process.env.FORCE_PANIC_SCHEMA_ENGINE && command !== 'debugPanic') return this.debugPanic() this.isRunning = true debugStdout('[%s] input: %o', command, input) try { // Don't modify this by extracting the method call into a variable, // as it breaks the binding to the WebAssembly instance, and triggers the // `TypeError: Cannot read properties of undefined (reading '__wbg_ptr')` // error. const result = await (this.engine[command] as (_: SchemaEngineInput<M>) => SchemaEngineOutput<M>)(input) debugStdout('[%s] result: %o', command, result) return result } catch (error) { const e = error as Error debugStdout('[%s] error: %o', command, e) if (isWasmPanic(e)) { debugStdout('[schema-engine] it is a Wasm panic') const { message, stack } = getWasmError(e) // Handle error and displays the interactive dialog to send panic error throw new RustPanic(serializePanic(message), stack, command, ErrorArea.LIFT_CLI) } else if ('code' in error) { throw new EngineError(red(`${error.code}\n\n${relativizePathInPSLError(error.message)}\n`), error.code) } else { assertAlways( e.name === 'SchemaConnectorError', 'Malformed error received from the engine, expected SchemaConnectorError', ) debugStderr('e.message', e.message) debugStderr('e.cause', e.cause) debugStderr('e.stack', e.stack) throw e } } } /** * Apply the migrations from the migrations directory to the database. * This is the command behind prisma migrate deploy. */ public applyMigrations(input: SchemaEngineInput<'applyMigrations'>): SchemaEngineOutput<'applyMigrations'> { return this.runCommand('applyMigrations', input) } /** * Create the next migration in the migrations history. * If draft is false and there are no unexecutable steps, it will also apply the newly created migration. * Note: This will use the shadow database on the connectors where we need one. */ public createMigration(input: SchemaEngineInput<'createMigration'>): SchemaEngineOutput<'createMigration'> { return this.runCommand('createMigration', input) } /** * Execute a database script directly on the specified live database. * Note that this may not be defined on all connectors. */ public async dbExecute(input: SchemaEngineInput<'dbExecute'>) { await this.runCommand('dbExecute', input) return null } /** * Make the Schema engine panic. Only useful to test client error handling. */ public async debugPanic() { await this.runCommand('debugPanic', undefined) return null as never } /** * The method called at the beginning of migrate dev to decide the course of action, * based on the current state of the workspace. * It acts as a wrapper around diagnoseMigrationHistory. * Its role is to interpret the diagnostic output, * and translate it to a concrete action to be performed by the CLI. */ public devDiagnostic(input: SchemaEngineInput<'devDiagnostic'>): SchemaEngineOutput<'devDiagnostic'> { return this.runCommand('devDiagnostic', input) } /** * Read the contents of the migrations directory and the migrations table, and returns their relative statuses. * At this stage, the Schema engine only reads, * it does not write to the database nor the migrations directory, nor does it use a shadow database. */ public diagnoseMigrationHistory( input: SchemaEngineInput<'diagnoseMigrationHistory'>, ): SchemaEngineOutput<'diagnoseMigrationHistory'> { return this.runCommand('diagnoseMigrationHistory', input) } /** * Make sure the Schema engine can connect to the database from the Prisma schema. */ public async ensureConnectionValidity(input: SchemaEngineInput<'ensureConnectionValidity'>) { await this.runCommand('ensureConnectionValidity', input) } /** * Development command for migrations. * Evaluate the data loss induced by the next migration the engine would generate on the main database. */ public evaluateDataLoss(input: SchemaEngineInput<'evaluateDataLoss'>): SchemaEngineOutput<'evaluateDataLoss'> { return this.runCommand('evaluateDataLoss', input) } /** * Get the database version for error reporting. * If no argument is given, the version of the database associated to the Prisma schema provided * in the constructor will be returned. */ public getDatabaseVersion(input?: SchemaEngineInput<'version'>): SchemaEngineOutput<'version'> { return this.runCommand('version', input) } /** * Given a Prisma schema, introspect the database definitions and update the schema with the results. * `compositeTypeDepth` is optional, and only required for MongoDB. */ public async introspect({ schema, force = false, baseDirectoryPath, viewsDirectoryPath, compositeTypeDepth = -1, namespaces, }: EngineArgs.IntrospectParams): SchemaEngineOutput<'introspect'> { const introspectResult = await this.runCommand('introspect', { schema, force, compositeTypeDepth, namespaces: namespaces ?? null, baseDirectoryPath, }) const { views } = introspectResult if (views) { await handleViewsIO({ views, viewsDirectoryPath }) } return introspectResult } /** * Compares two databases schemas from two arbitrary sources, * and display the difference as either a human-readable summary, * or an executable script that can be passed to dbExecute. * Connection to a shadow database is only necessary when either the from or the to params is a migrations directory. * Diffs have a direction. Which source is from and which is to matters. * The resulting diff should be thought of as a migration from the schema in `args.from` to the schema in `args.to`. * By default, we output a human-readable diff. If you want an executable script, pass the "script": true param. */ public async migrateDiff(input: SchemaEngineInput<'diff'>) { const { stdout, ...rest } = await this.runCommand('diff', input) if (stdout) { // Here we print the content from the Schema Engine to stdout directly // (it is not returned to the caller) process.stdout.write(stdout) } return rest } /** * Mark a migration as applied in the migrations table. * There are two possible outcomes: * - The migration is already in the table, but in a failed state. In this case, we will mark it as rolled back, then create a new entry. * - The migration is not in the table. We will create a new entry in the migrations table. The started_at and finished_at will be the same. * If it is already applied, we return a user-facing error. */ public async markMigrationApplied(input: SchemaEngineInput<'markMigrationApplied'>) { await this.runCommand('markMigrationApplied', input) } /** * Mark an existing failed migration as rolled back in the migrations table. It will still be there, but ignored for all purposes except as audit trail. */ public async markMigrationRolledBack(input: SchemaEngineInput<'markMigrationRolledBack'>) { await this.runCommand('markMigrationRolledBack', input) } /** * Try to make the database empty: no data and no schema. * On most connectors, this is implemented by dropping and recreating the database. * If that fails (most likely because of insufficient permissions), * the engine attempts a “best effort reset” by inspecting the contents of the database and dropping them individually. * Drop and recreate the database. The migrations will not be applied, as it would overlap with applyMigrations. */ public async reset(input: SchemaEngineInput<'reset'>) { await this.runCommand('reset', input) } /** * The command behind db push. */ public schemaPush(input: SchemaEngineInput<'schemaPush'>): SchemaEngineOutput<'schemaPush'> { return this.runCommand('schemaPush', input) } /** * SQL introspection that powers TypedSQL feature * @param args * @returns */ public introspectSql(input: SchemaEngineInput<'introspectSql'>): SchemaEngineOutput<'introspectSql'> { return this.runCommand('introspectSql', input) } /** * Stop the engine. */ public stop(): Promise<void> { this.isRunning = false this.engine.free() return Promise.resolve() } } /** The full message with context we return to the user in case of engine panic. */ function serializePanic(log: string): string { return `${red(bold('Error in Schema engine.\nReason: '))}${log}\n` }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prisma/prisma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server