Comprehensive internal utility library for Prisma's CLI operations, schema management, generator handling, and engine interactions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The error handling domain provides comprehensive error management including Rust panic recovery, WASM error handling, error reporting, and user-friendly error messaging for the Prisma ecosystem.
RustPanicCustom error class for handling Rust panics from Prisma engines with detailed context and reporting capabilities.
class RustPanic extends Error {
readonly __typename = 'RustPanic'
constructor(
message: string,
public rustStack: string,
public request: any,
public area: ErrorArea,
public introspectionUrl?: string
)
}Properties:
__typename: 'RustPanic' - Type identifier for runtime checksrustStack: string - Rust stack trace from the panicrequest: any - Request that caused the panicarea: ErrorArea - Error area classificationintrospectionUrl?: string - Optional introspection URL for database errorsConstructor Parameters:
message: string - Human-readable error messagerustStack: string - Raw Rust stack tracerequest: any - Original request that triggered the panicarea: ErrorArea - Error area for categorizationintrospectionUrl?: string - Optional URL for database introspection errorsExample:
const panic = new RustPanic(
'Query engine panicked during execution',
'thread \'main\' panicked at \'assertion failed: user.id > 0\'',
{ query: 'SELECT * FROM users WHERE id = ?', params: [-1] },
ErrorArea.QUERY_ENGINE_LIBRARY_CLI,
'postgresql://localhost:5432/mydb'
)
console.log(panic.message) // 'Query engine panicked during execution'
console.log(panic.rustStack) // Full Rust stack trace
console.log(panic.area) // ErrorArea.QUERY_ENGINE_LIBRARY_CLI
console.log(panic.introspectionUrl) // 'postgresql://localhost:5432/mydb'isRustPanic(e)Type guard to safely check if an error is a RustPanic instance.
function isRustPanic(e: Error): e is RustPanicParameters:
e: Error - Error instance to checkReturns: e is RustPanic - Type guard indicating if error is a RustPanic
Example:
try {
await prisma.user.findMany()
} catch (error) {
if (isRustPanic(error)) {
console.error('Rust panic occurred:')
console.error('Area:', error.area)
console.error('Stack:', error.rustStack)
// Report panic for debugging
await sendPanic({
error,
cliVersion: '5.0.0',
enginesVersion: '5.0.0',
getDatabaseVersionSafe: async () => 'PostgreSQL 15.0'
})
} else {
console.error('Regular error:', error.message)
}
}isWasmPanic(error)Type guard to check if an error is a WASM runtime panic.
function isWasmPanic(error: Error): error is WasmPanicParameters:
error: Error - Error instance to checkReturns: error is WasmPanic - Type guard for WASM panic errors
getWasmError(error)Extracts error information from WASM panic with structured data.
function getWasmError(error: WasmPanic): { message: string; stack: string }Parameters:
error: WasmPanic - WASM panic error instanceReturns: { message: string; stack: string } - Extracted error information
Example:
try {
await formatSchema({ schemas })
} catch (error) {
if (isWasmPanic(error)) {
const { message, stack } = getWasmError(error)
console.error('WASM Error:', message)
console.error('Stack:', stack)
}
}sendPanic(options)Sends panic information to error reporting service for analysis and debugging.
function sendPanic(options: SendPanicOptions): Promise<number>SendPanicOptions:
interface SendPanicOptions {
error: RustPanic // The panic error
cliVersion: string // CLI version string
enginesVersion: string // Engines version string
getDatabaseVersionSafe: () => Promise<string> // Function to get DB version safely
}Parameters:
error: RustPanic - The panic error to reportcliVersion: string - Version of Prisma CLIenginesVersion: string - Version of Prisma enginesgetDatabaseVersionSafe: () => Promise<string> - Async function to safely retrieve database versionReturns: Promise<number> - Report ID for tracking
Example:
async function handlePanicWithReporting(error: RustPanic) {
try {
const reportId = await sendPanic({
error,
cliVersion: process.env.PRISMA_CLI_VERSION || 'unknown',
enginesVersion: process.env.PRISMA_ENGINES_VERSION || 'unknown',
getDatabaseVersionSafe: async () => {
try {
// Safely get database version
const result = await prisma.$queryRaw`SELECT version()`
return result[0].version
} catch {
return 'unknown'
}
}
})
console.log(`Panic reported with ID: ${reportId}`)
} catch (reportError) {
console.warn('Failed to report panic:', reportError.message)
}
}warnOnce(message)Warns only once for the same message to avoid spam in logs.
function warnOnce(message: string): voidParameters:
message: string - Warning message to displayBehavior: Only prints the warning the first time it's called with a specific message
Example:
function processLegacyConfig(config: any) {
if (config.legacyOption) {
warnOnce('legacyOption is deprecated and will be removed in v6.0')
// This warning will only appear once per process
}
}
// First call - warning shown
processLegacyConfig({ legacyOption: true })
// Subsequent calls - no warning
processLegacyConfig({ legacyOption: true })
processLegacyConfig({ legacyOption: true })ErrorAreaEnumeration categorizing different areas where errors can occur in the Prisma ecosystem.
enum ErrorArea {
LIFT_CLI = 'LIFT_CLI',
PHOTON_STUDIO = 'PHOTON_STUDIO',
INTROSPECTION_CLI = 'INTROSPECTION_CLI',
FMT_CLI = 'FMT_CLI',
QUERY_ENGINE_BINARY_CLI = 'QUERY_ENGINE_BINARY_CLI',
QUERY_ENGINE_LIBRARY_CLI = 'QUERY_ENGINE_LIBRARY_CLI'
}Areas:
LIFT_CLI - CLI lift operations (legacy migrations)PHOTON_STUDIO - Photon Studio operations (legacy)INTROSPECTION_CLI - Database introspection operations (legacy)FMT_CLI - Schema formatting operationsQUERY_ENGINE_BINARY_CLI - Query engine binary operationsQUERY_ENGINE_LIBRARY_CLI - Query engine library operationsWasmPanicBranded type for WASM runtime errors with specific characteristics.
type WasmPanic = Error & { name: 'RuntimeError' }Properties:
Errorname: 'RuntimeError' - Specific name for WASM panicsimport {
isRustPanic,
isWasmPanic,
getWasmError,
sendPanic,
warnOnce,
ErrorArea,
type RustPanic
} from '@prisma/internals'
interface ErrorContext {
operation: string
timestamp: Date
userId?: string
requestId?: string
}
class PrismaErrorHandler {
private reportingEnabled: boolean
private cliVersion: string
private enginesVersion: string
constructor(options: {
reportingEnabled?: boolean
cliVersion: string
enginesVersion: string
}) {
this.reportingEnabled = options.reportingEnabled ?? true
this.cliVersion = options.cliVersion
this.enginesVersion = options.enginesVersion
}
/**
* Handle any Prisma-related error with context
*/
async handleError(
error: Error,
context: ErrorContext
): Promise<{ handled: boolean; reportId?: number }> {
console.error(`❌ Error in ${context.operation} at ${context.timestamp.toISOString()}`)
if (context.requestId) {
console.error(` Request ID: ${context.requestId}`)
}
// Handle Rust panics
if (isRustPanic(error)) {
return await this.handleRustPanic(error, context)
}
// Handle WASM panics
if (isWasmPanic(error)) {
return this.handleWasmPanic(error, context)
}
// Handle regular errors
return this.handleRegularError(error, context)
}
/**
* Handle Rust panic with detailed reporting
*/
private async handleRustPanic(
error: RustPanic,
context: ErrorContext
): Promise<{ handled: boolean; reportId?: number }> {
console.error('🦀 Rust Panic Detected:')
console.error(` Area: ${error.area}`)
console.error(` Message: ${error.message}`)
// Log request details if available
if (error.request) {
console.error(' Request Details:')
console.error(` ${JSON.stringify(error.request, null, 2)}`)
}
// Show user-friendly message based on error area
this.showUserFriendlyPanicMessage(error.area)
// Report panic if enabled
let reportId: number | undefined
if (this.reportingEnabled) {
try {
reportId = await sendPanic({
error,
cliVersion: this.cliVersion,
enginesVersion: this.enginesVersion,
getDatabaseVersionSafe: async () => {
try {
// Attempt to get database version safely
return await this.getDatabaseVersionSafe()
} catch {
return 'unknown'
}
}
})
console.error(` Panic reported with ID: ${reportId}`)
} catch (reportError) {
console.warn(` Failed to report panic: ${reportError.message}`)
}
}
return { handled: true, reportId }
}
/**
* Handle WASM panic
*/
private handleWasmPanic(
error: WasmPanic,
context: ErrorContext
): { handled: boolean } {
console.error('🕸️ WASM Panic Detected:')
const { message, stack } = getWasmError(error)
console.error(` Message: ${message}`)
console.error(` Stack: ${stack}`)
// Show troubleshooting advice
console.error('\n💡 Troubleshooting WASM errors:')
console.error(' • Try updating to the latest Prisma version')
console.error(' • Check if your schema is valid')
console.error(' • Restart your development server')
return { handled: true }
}
/**
* Handle regular errors
*/
private handleRegularError(
error: Error,
context: ErrorContext
): { handled: boolean } {
console.error('⚠️ Regular Error:')
console.error(` Message: ${error.message}`)
if (error.stack) {
console.error(` Stack: ${error.stack}`)
}
// Check for common error patterns
if (error.message.includes('ECONNREFUSED')) {
console.error('\n💡 Connection refused - check if your database is running')
} else if (error.message.includes('authentication failed')) {
console.error('\n💡 Authentication failed - check your database credentials')
} else if (error.message.includes('relation') && error.message.includes('does not exist')) {
console.error('\n💡 Database table missing - run `prisma db push` or migrations')
}
return { handled: true }
}
/**
* Show user-friendly message based on panic area
*/
private showUserFriendlyPanicMessage(area: ErrorArea): void {
const messages = {
[ErrorArea.QUERY_ENGINE_BINARY_CLI]: 'Query engine crashed during database operation',
[ErrorArea.QUERY_ENGINE_LIBRARY_CLI]: 'Query engine library encountered an internal error',
[ErrorArea.FMT_CLI]: 'Schema formatter crashed while processing your schema',
[ErrorArea.INTROSPECTION_CLI]: 'Database introspection failed unexpectedly',
[ErrorArea.LIFT_CLI]: 'Migration engine encountered an error',
[ErrorArea.PHOTON_STUDIO]: 'Prisma Studio encountered an internal error'
}
const message = messages[area] || 'Prisma encountered an unexpected error'
console.error(`\n📝 ${message}`)
console.error('\n🔧 Next steps:')
console.error(' • Check your database connection')
console.error(' • Verify your schema syntax')
console.error(' • Update to the latest Prisma version')
console.error(' • Report this issue if it persists')
}
/**
* Safely get database version
*/
private async getDatabaseVersionSafe(): Promise<string> {
// Implementation would depend on having access to PrismaClient
// This is a placeholder
return 'PostgreSQL 15.0'
}
}import {
isRustPanic,
isWasmPanic,
warnOnce,
type RustPanic
} from '@prisma/internals'
interface RetryOptions {
maxAttempts: number
baseDelay: number
maxDelay: number
shouldRetry?: (error: Error, attempt: number) => boolean
}
class ErrorRecoveryManager {
/**
* Execute operation with retry logic and error recovery
*/
async withRetry<T>(
operation: () => Promise<T>,
options: RetryOptions
): Promise<T> {
const { maxAttempts, baseDelay, maxDelay, shouldRetry } = options
let lastError: Error | undefined
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation()
} catch (error) {
lastError = error as Error
// Don't retry on panics - they indicate serious issues
if (isRustPanic(error) || isWasmPanic(error)) {
console.error(`💥 Panic on attempt ${attempt} - not retrying`)
throw error
}
// Check custom retry logic
if (shouldRetry && !shouldRetry(error, attempt)) {
console.error(`🚫 Custom retry logic rejected attempt ${attempt}`)
throw error
}
// Don't retry on last attempt
if (attempt === maxAttempts) {
console.error(`🔄 Max attempts (${maxAttempts}) reached`)
throw error
}
// Calculate delay with exponential backoff
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay)
console.warn(`⏳ Attempt ${attempt} failed, retrying in ${delay}ms...`)
console.warn(` Error: ${error.message}`)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
throw lastError || new Error('Operation failed after retries')
}
/**
* Graceful degradation for non-critical operations
*/
async withFallback<T>(
primaryOperation: () => Promise<T>,
fallbackOperation: () => Promise<T>,
context: string
): Promise<T> {
try {
return await primaryOperation()
} catch (error) {
if (isRustPanic(error)) {
warnOnce(`Rust panic in ${context} - using fallback`)
console.warn(`Primary operation failed due to panic: ${error.message}`)
} else if (isWasmPanic(error)) {
warnOnce(`WASM panic in ${context} - using fallback`)
console.warn(`Primary operation failed due to WASM error`)
} else {
console.warn(`Primary operation failed in ${context}: ${error.message}`)
}
console.log(`🔄 Attempting fallback for ${context}...`)
return await fallbackOperation()
}
}
}
// Usage examples
const recoveryManager = new ErrorRecoveryManager()
// Database operation with retry
const users = await recoveryManager.withRetry(
async () => {
return await prisma.user.findMany()
},
{
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 5000,
shouldRetry: (error, attempt) => {
// Don't retry validation errors
return !error.message.includes('Invalid')
}
}
)
// Schema operation with fallback
const formattedSchema = await recoveryManager.withFallback(
async () => {
// Try WASM formatter
return await formatSchema({ schemas })
},
async () => {
// Fallback to simple string formatting
warnOnce('Using simple schema formatting as fallback')
return schemas // Return unformatted
},
'schema formatting'
)import {
isRustPanic,
isWasmPanic,
getWasmError,
ErrorArea,
type RustPanic
} from '@prisma/internals'
interface ErrorMetrics {
totalErrors: number
rustPanics: number
wasmPanics: number
regularErrors: number
errorsByArea: Record<ErrorArea, number>
commonErrors: Record<string, number>
}
class ErrorMonitor {
private metrics: ErrorMetrics
private errorHistory: Array<{
error: Error
timestamp: Date
context: string
}> = []
constructor() {
this.metrics = {
totalErrors: 0,
rustPanics: 0,
wasmPanics: 0,
regularErrors: 0,
errorsByArea: {} as Record<ErrorArea, number>,
commonErrors: {}
}
}
/**
* Record error for monitoring
*/
recordError(error: Error, context: string): void {
this.metrics.totalErrors++
this.errorHistory.push({
error,
timestamp: new Date(),
context
})
if (isRustPanic(error)) {
this.metrics.rustPanics++
this.recordPanicArea(error.area)
} else if (isWasmPanic(error)) {
this.metrics.wasmPanics++
} else {
this.metrics.regularErrors++
}
// Track common error patterns
const errorKey = this.categorizeError(error)
this.metrics.commonErrors[errorKey] = (this.metrics.commonErrors[errorKey] || 0) + 1
// Limit history size
if (this.errorHistory.length > 1000) {
this.errorHistory = this.errorHistory.slice(-500)
}
}
private recordPanicArea(area: ErrorArea): void {
this.metrics.errorsByArea[area] = (this.metrics.errorsByArea[area] || 0) + 1
}
private categorizeError(error: Error): string {
const message = error.message.toLowerCase()
if (message.includes('connection')) return 'connection_error'
if (message.includes('authentication')) return 'auth_error'
if (message.includes('timeout')) return 'timeout_error'
if (message.includes('syntax')) return 'syntax_error'
if (message.includes('permission')) return 'permission_error'
if (message.includes('not found')) return 'not_found_error'
return 'other_error'
}
/**
* Generate error report
*/
generateReport(): string {
const { metrics } = this
let report = '\n📊 Error Monitoring Report\n'
report += '═'.repeat(40) + '\n\n'
report += `📈 Overall Statistics:\n`
report += ` Total Errors: ${metrics.totalErrors}\n`
report += ` Rust Panics: ${metrics.rustPanics}\n`
report += ` WASM Panics: ${metrics.wasmPanics}\n`
report += ` Regular Errors: ${metrics.regularErrors}\n\n`
if (metrics.rustPanics > 0) {
report += `🦀 Panics by Area:\n`
for (const [area, count] of Object.entries(metrics.errorsByArea)) {
report += ` ${area}: ${count}\n`
}
report += '\n'
}
if (Object.keys(metrics.commonErrors).length > 0) {
report += `📋 Common Errors:\n`
const sortedErrors = Object.entries(metrics.commonErrors)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
for (const [category, count] of sortedErrors) {
report += ` ${category.replace('_', ' ')}: ${count}\n`
}
report += '\n'
}
// Recent errors
const recentErrors = this.errorHistory.slice(-5)
if (recentErrors.length > 0) {
report += `🕐 Recent Errors:\n`
for (const { error, timestamp, context } of recentErrors) {
const timeStr = timestamp.toISOString().substring(11, 19)
report += ` ${timeStr} [${context}] ${error.message.substring(0, 50)}...\n`
}
}
return report
}
/**
* Check if error rate is concerning
*/
getHealthStatus(): {
status: 'healthy' | 'warning' | 'critical'
message: string
} {
const recentErrors = this.errorHistory.filter(
entry => Date.now() - entry.timestamp.getTime() < 5 * 60 * 1000 // Last 5 minutes
)
const panicRate = this.metrics.rustPanics / Math.max(this.metrics.totalErrors, 1)
if (recentErrors.length > 10) {
return {
status: 'critical',
message: `High error rate: ${recentErrors.length} errors in last 5 minutes`
}
}
if (panicRate > 0.1) {
return {
status: 'critical',
message: `High panic rate: ${(panicRate * 100).toFixed(1)}% of errors are panics`
}
}
if (recentErrors.length > 5 || this.metrics.totalErrors > 20) {
return {
status: 'warning',
message: 'Elevated error levels detected'
}
}
return {
status: 'healthy',
message: 'Error levels within normal range'
}
}
}Install with Tessl CLI
npx tessl i tessl/npm-prisma--internals