Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,192
  • Linux
  • Apple
RequestHandler.ts11.2 kB
import { Context } from '@opentelemetry/api' import { deserializeJsonResponse } from '@prisma/client-engine-runtime' import { Debug } from '@prisma/debug' import { assertNever } from '@prisma/internals' import stripAnsi from 'strip-ansi' import { EngineValidationError, InteractiveTransactionOptions, JsonQuery, LogEmitter, TransactionOptions, } from '../runtime/core/engines' import { PrismaClientInitializationError, PrismaClientKnownRequestError, PrismaClientRustPanicError, PrismaClientUnknownRequestError, } from '.' import { AccelerateExtensionFetchDecorator } from './core/engines/common/Engine' import { QueryEngineResultData } from './core/engines/common/types/QueryEngine' import { throwValidationException } from './core/errorRendering/throwValidationException' import { hasBatchIndex } from './core/errors/ErrorWithBatchIndex' import { createApplyBatchExtensionsFunction } from './core/extensions/applyQueryExtensions' import { MergedExtensionsList } from './core/extensions/MergedExtensionsList' import { getBatchId } from './core/jsonProtocol/getBatchId' import { isWrite } from './core/jsonProtocol/isWrite' import { GlobalOmitOptions } from './core/jsonProtocol/serializeJsonQuery' import { PrismaPromiseInteractiveTransaction, PrismaPromiseTransaction } from './core/request/PrismaPromise' import { Action, JsArgs } from './core/types/exported/JsApi' import { DataLoader } from './DataLoader' import type { Client, Unpacker } from './getPrismaClient' import { CallSite } from './utils/CallSite' import { createErrorMessageWithContext } from './utils/createErrorMessageWithContext' import { deepGet } from './utils/deep-set' import { deserializeRawResult, RawResponse } from './utils/deserializeRawResults' const debug = Debug('prisma:client:request_handler') export type RequestParams = { modelName?: string action: Action protocolQuery: JsonQuery dataPath: string[] clientMethod: string callsite?: CallSite transaction?: PrismaPromiseTransaction extensions: MergedExtensionsList args?: any headers?: Record<string, string> unpacker?: Unpacker otelParentCtx?: Context otelChildCtx?: Context globalOmit?: GlobalOmitOptions customDataProxyFetch?: AccelerateExtensionFetchDecorator } export type HandleErrorParams = { args: JsArgs error: any clientMethod: string callsite?: CallSite transaction?: PrismaPromiseTransaction modelName?: string globalOmit?: GlobalOmitOptions } export class RequestHandler { client: Client dataloader: DataLoader<RequestParams> private logEmitter?: LogEmitter constructor(client: Client, logEmitter?: LogEmitter) { this.logEmitter = logEmitter this.client = client this.dataloader = new DataLoader({ batchLoader: createApplyBatchExtensionsFunction(async ({ requests, customDataProxyFetch }) => { const { transaction, otelParentCtx } = requests[0] const queries = requests.map((r) => r.protocolQuery) const traceparent = this.client._tracingHelper.getTraceParent(otelParentCtx) // TODO: pass the child information to QE for it to issue links to queries // const links = requests.map((r) => trace.getSpanContext(r.otelChildCtx!)) const containsWrite = requests.some((r) => isWrite(r.protocolQuery.action)) const results = await this.client._engine.requestBatch(queries, { traceparent, transaction: getTransactionOptions(transaction), containsWrite, customDataProxyFetch, }) return results.map((result, i) => { if (result instanceof Error) { return result } try { return this.mapQueryEngineResult(requests[i], result) } catch (error) { return error } }) }), singleLoader: async (request) => { const interactiveTransaction = request.transaction?.kind === 'itx' ? getItxTransactionOptions(request.transaction) : undefined const response = await this.client._engine.request(request.protocolQuery, { traceparent: this.client._tracingHelper.getTraceParent(), interactiveTransaction, isWrite: isWrite(request.protocolQuery.action), customDataProxyFetch: request.customDataProxyFetch, }) return this.mapQueryEngineResult(request, response) }, batchBy: (request) => { if (request.transaction?.id) { return `transaction-${request.transaction.id}` } return getBatchId(request.protocolQuery) }, batchOrder(requestA, requestB) { if (requestA.transaction?.kind === 'batch' && requestB.transaction?.kind === 'batch') { return requestA.transaction.index - requestB.transaction.index } return 0 }, }) } async request(params: RequestParams) { try { return await this.dataloader.request(params) } catch (error) { const { clientMethod, callsite, transaction, args, modelName } = params this.handleAndLogRequestError({ error, clientMethod, callsite, transaction, args, modelName, globalOmit: params.globalOmit, }) } } mapQueryEngineResult({ dataPath, unpacker }: RequestParams, response: QueryEngineResultData<any>) { const data = response?.data /** * Unpack */ const result = this.unpack(data, dataPath, unpacker) if (process.env.PRISMA_CLIENT_GET_TIME) { return { data: result } } return result } /** * Handles the error and logs it, logging the error is done synchronously waiting for the event * handlers to finish. */ handleAndLogRequestError(params: HandleErrorParams): never { try { this.handleRequestError(params) } catch (err) { if (this.logEmitter) { this.logEmitter.emit('error', { message: err.message, target: params.clientMethod, timestamp: new Date() }) } throw err } } handleRequestError({ error, clientMethod, callsite, transaction, args, modelName, globalOmit, }: HandleErrorParams): never { debug(error) if (isMismatchingBatchIndex(error, transaction)) { // if this is batch error and current request was not it's cause, we don't add // context information to the error: this wasn't a request that caused batch to fail throw error } if (error instanceof PrismaClientKnownRequestError && isValidationError(error)) { const validationError = convertValidationError(error.meta as EngineValidationError) throwValidationException({ args, errors: [validationError], callsite, errorFormat: this.client._errorFormat, originalMethod: clientMethod, clientVersion: this.client._clientVersion, globalOmit, }) } let message = error.message if (callsite) { message = createErrorMessageWithContext({ callsite, originalMethod: clientMethod, isPanic: error.isPanic, showColors: this.client._errorFormat === 'pretty', message, }) } message = this.sanitizeMessage(message) // TODO: Do request with callsite instead, so we don't need to rethrow if (error.code) { const meta = modelName ? { modelName, ...error.meta } : error.meta throw new PrismaClientKnownRequestError(message, { code: error.code, clientVersion: this.client._clientVersion, meta, batchRequestIdx: error.batchRequestIdx, }) } else if (error.isPanic) { throw new PrismaClientRustPanicError(message, this.client._clientVersion) } else if (error instanceof PrismaClientUnknownRequestError) { throw new PrismaClientUnknownRequestError(message, { clientVersion: this.client._clientVersion, batchRequestIdx: error.batchRequestIdx, }) } 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) { if (this.client._errorFormat && this.client._errorFormat !== 'pretty') { return stripAnsi(message) } return message } unpack(data: unknown, dataPath: string[], unpacker?: Unpacker) { if (!data) { return data } if (data['data']) { data = data['data'] } if (!data) { return data } const operation = Object.keys(data)[0] const response = Object.values(data)[0] const pathForGet = dataPath.filter((key) => key !== 'select' && key !== 'include') const extractedResponse = deepGet(response, pathForGet) const deserializedResponse = operation === 'queryRaw' ? deserializeRawResult(extractedResponse as RawResponse) : (deserializeJsonResponse(extractedResponse) as unknown) return unpacker ? unpacker(deserializedResponse) : deserializedResponse } get [Symbol.toStringTag]() { return 'RequestHandler' } } function getTransactionOptions<PayloadType>( transaction?: PrismaPromiseTransaction<PayloadType>, ): TransactionOptions<PayloadType> | undefined { if (!transaction) { return undefined } if (transaction.kind === 'batch') { return { kind: 'batch', options: { isolationLevel: transaction.isolationLevel, }, } } if (transaction.kind === 'itx') { return { kind: 'itx', options: getItxTransactionOptions(transaction), } } assertNever(transaction, 'Unknown transaction kind') } function getItxTransactionOptions<PayloadType>( transaction: PrismaPromiseInteractiveTransaction<PayloadType>, ): InteractiveTransactionOptions<PayloadType> { return { id: transaction.id, payload: transaction.payload, } } function isMismatchingBatchIndex(error: any, transaction: PrismaPromiseTransaction | undefined) { return hasBatchIndex(error) && transaction?.kind === 'batch' && error.batchRequestIdx !== transaction.index } function isValidationError(error: PrismaClientKnownRequestError) { return ( error.code === 'P2009' || // validation error error.code === 'P2012' // required argument missing ) } /** * Engine validation errors include extra segment for selectionPath - root query field. * This function removes it (since it does not exist on js arguments). In case of `Union` * error type, removes heading element from selectionPath of nested errors as well. * @param error * @returns */ function convertValidationError(error: EngineValidationError): EngineValidationError { if (error.kind === 'Union') { return { kind: 'Union', errors: error.errors.map(convertValidationError), } } if (Array.isArray(error['selectionPath'])) { const [, ...selectionPath] = error['selectionPath'] return { ...error, selectionPath, } as EngineValidationError } return error }

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