0
# Error Handling and Panic Management
1
2
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.
3
4
## Classes
5
6
### Rust Panic Error
7
8
#### `RustPanic`
9
10
Custom error class for handling Rust panics from Prisma engines with detailed context and reporting capabilities.
11
12
```typescript { .api }
13
class RustPanic extends Error {
14
readonly __typename = 'RustPanic'
15
16
constructor(
17
message: string,
18
public rustStack: string,
19
public request: any,
20
public area: ErrorArea,
21
public introspectionUrl?: string
22
)
23
}
24
```
25
26
**Properties:**
27
- `__typename: 'RustPanic'` - Type identifier for runtime checks
28
- `rustStack: string` - Rust stack trace from the panic
29
- `request: any` - Request that caused the panic
30
- `area: ErrorArea` - Error area classification
31
- `introspectionUrl?: string` - Optional introspection URL for database errors
32
33
**Constructor Parameters:**
34
- `message: string` - Human-readable error message
35
- `rustStack: string` - Raw Rust stack trace
36
- `request: any` - Original request that triggered the panic
37
- `area: ErrorArea` - Error area for categorization
38
- `introspectionUrl?: string` - Optional URL for database introspection errors
39
40
**Example:**
41
```typescript
42
const panic = new RustPanic(
43
'Query engine panicked during execution',
44
'thread \'main\' panicked at \'assertion failed: user.id > 0\'',
45
{ query: 'SELECT * FROM users WHERE id = ?', params: [-1] },
46
ErrorArea.QUERY_ENGINE_LIBRARY_CLI,
47
'postgresql://localhost:5432/mydb'
48
)
49
50
console.log(panic.message) // 'Query engine panicked during execution'
51
console.log(panic.rustStack) // Full Rust stack trace
52
console.log(panic.area) // ErrorArea.QUERY_ENGINE_LIBRARY_CLI
53
console.log(panic.introspectionUrl) // 'postgresql://localhost:5432/mydb'
54
```
55
56
## Functions
57
58
### Type Guards and Detection
59
60
#### `isRustPanic(e)`
61
62
Type guard to safely check if an error is a RustPanic instance.
63
64
```typescript { .api }
65
function isRustPanic(e: Error): e is RustPanic
66
```
67
68
**Parameters:**
69
- `e: Error` - Error instance to check
70
71
**Returns:** `e is RustPanic` - Type guard indicating if error is a RustPanic
72
73
**Example:**
74
```typescript
75
try {
76
await prisma.user.findMany()
77
} catch (error) {
78
if (isRustPanic(error)) {
79
console.error('Rust panic occurred:')
80
console.error('Area:', error.area)
81
console.error('Stack:', error.rustStack)
82
83
// Report panic for debugging
84
await sendPanic({
85
error,
86
cliVersion: '5.0.0',
87
enginesVersion: '5.0.0',
88
getDatabaseVersionSafe: async () => 'PostgreSQL 15.0'
89
})
90
} else {
91
console.error('Regular error:', error.message)
92
}
93
}
94
```
95
96
#### `isWasmPanic(error)`
97
98
Type guard to check if an error is a WASM runtime panic.
99
100
```typescript { .api }
101
function isWasmPanic(error: Error): error is WasmPanic
102
```
103
104
**Parameters:**
105
- `error: Error` - Error instance to check
106
107
**Returns:** `error is WasmPanic` - Type guard for WASM panic errors
108
109
#### `getWasmError(error)`
110
111
Extracts error information from WASM panic with structured data.
112
113
```typescript { .api }
114
function getWasmError(error: WasmPanic): { message: string; stack: string }
115
```
116
117
**Parameters:**
118
- `error: WasmPanic` - WASM panic error instance
119
120
**Returns:** `{ message: string; stack: string }` - Extracted error information
121
122
**Example:**
123
```typescript
124
try {
125
await formatSchema({ schemas })
126
} catch (error) {
127
if (isWasmPanic(error)) {
128
const { message, stack } = getWasmError(error)
129
console.error('WASM Error:', message)
130
console.error('Stack:', stack)
131
}
132
}
133
```
134
135
### Error Reporting
136
137
#### `sendPanic(options)`
138
139
Sends panic information to error reporting service for analysis and debugging.
140
141
```typescript { .api }
142
function sendPanic(options: SendPanicOptions): Promise<number>
143
```
144
145
**SendPanicOptions:**
146
```typescript { .api }
147
interface SendPanicOptions {
148
error: RustPanic // The panic error
149
cliVersion: string // CLI version string
150
enginesVersion: string // Engines version string
151
getDatabaseVersionSafe: () => Promise<string> // Function to get DB version safely
152
}
153
```
154
155
**Parameters:**
156
- `error: RustPanic` - The panic error to report
157
- `cliVersion: string` - Version of Prisma CLI
158
- `enginesVersion: string` - Version of Prisma engines
159
- `getDatabaseVersionSafe: () => Promise<string>` - Async function to safely retrieve database version
160
161
**Returns:** `Promise<number>` - Report ID for tracking
162
163
**Example:**
164
```typescript
165
async function handlePanicWithReporting(error: RustPanic) {
166
try {
167
const reportId = await sendPanic({
168
error,
169
cliVersion: process.env.PRISMA_CLI_VERSION || 'unknown',
170
enginesVersion: process.env.PRISMA_ENGINES_VERSION || 'unknown',
171
getDatabaseVersionSafe: async () => {
172
try {
173
// Safely get database version
174
const result = await prisma.$queryRaw`SELECT version()`
175
return result[0].version
176
} catch {
177
return 'unknown'
178
}
179
}
180
})
181
182
console.log(`Panic reported with ID: ${reportId}`)
183
} catch (reportError) {
184
console.warn('Failed to report panic:', reportError.message)
185
}
186
}
187
```
188
189
### Warning Management
190
191
#### `warnOnce(message)`
192
193
Warns only once for the same message to avoid spam in logs.
194
195
```typescript { .api }
196
function warnOnce(message: string): void
197
```
198
199
**Parameters:**
200
- `message: string` - Warning message to display
201
202
**Behavior:** Only prints the warning the first time it's called with a specific message
203
204
**Example:**
205
```typescript
206
function processLegacyConfig(config: any) {
207
if (config.legacyOption) {
208
warnOnce('legacyOption is deprecated and will be removed in v6.0')
209
// This warning will only appear once per process
210
}
211
}
212
213
// First call - warning shown
214
processLegacyConfig({ legacyOption: true })
215
216
// Subsequent calls - no warning
217
processLegacyConfig({ legacyOption: true })
218
processLegacyConfig({ legacyOption: true })
219
```
220
221
## Enums and Types
222
223
### Error Areas
224
225
#### `ErrorArea`
226
227
Enumeration categorizing different areas where errors can occur in the Prisma ecosystem.
228
229
```typescript { .api }
230
enum ErrorArea {
231
LIFT_CLI = 'LIFT_CLI',
232
PHOTON_STUDIO = 'PHOTON_STUDIO',
233
INTROSPECTION_CLI = 'INTROSPECTION_CLI',
234
FMT_CLI = 'FMT_CLI',
235
QUERY_ENGINE_BINARY_CLI = 'QUERY_ENGINE_BINARY_CLI',
236
QUERY_ENGINE_LIBRARY_CLI = 'QUERY_ENGINE_LIBRARY_CLI'
237
}
238
```
239
240
**Areas:**
241
- `LIFT_CLI` - CLI lift operations (legacy migrations)
242
- `PHOTON_STUDIO` - Photon Studio operations (legacy)
243
- `INTROSPECTION_CLI` - Database introspection operations (legacy)
244
- `FMT_CLI` - Schema formatting operations
245
- `QUERY_ENGINE_BINARY_CLI` - Query engine binary operations
246
- `QUERY_ENGINE_LIBRARY_CLI` - Query engine library operations
247
248
### WASM Error Types
249
250
#### `WasmPanic`
251
252
Branded type for WASM runtime errors with specific characteristics.
253
254
```typescript { .api }
255
type WasmPanic = Error & { name: 'RuntimeError' }
256
```
257
258
**Properties:**
259
- Extends standard `Error`
260
- `name: 'RuntimeError'` - Specific name for WASM panics
261
262
## Examples
263
264
### Comprehensive Error Handler
265
266
```typescript
267
import {
268
isRustPanic,
269
isWasmPanic,
270
getWasmError,
271
sendPanic,
272
warnOnce,
273
ErrorArea,
274
type RustPanic
275
} from '@prisma/internals'
276
277
interface ErrorContext {
278
operation: string
279
timestamp: Date
280
userId?: string
281
requestId?: string
282
}
283
284
class PrismaErrorHandler {
285
private reportingEnabled: boolean
286
private cliVersion: string
287
private enginesVersion: string
288
289
constructor(options: {
290
reportingEnabled?: boolean
291
cliVersion: string
292
enginesVersion: string
293
}) {
294
this.reportingEnabled = options.reportingEnabled ?? true
295
this.cliVersion = options.cliVersion
296
this.enginesVersion = options.enginesVersion
297
}
298
299
/**
300
* Handle any Prisma-related error with context
301
*/
302
async handleError(
303
error: Error,
304
context: ErrorContext
305
): Promise<{ handled: boolean; reportId?: number }> {
306
console.error(`β Error in ${context.operation} at ${context.timestamp.toISOString()}`)
307
308
if (context.requestId) {
309
console.error(` Request ID: ${context.requestId}`)
310
}
311
312
// Handle Rust panics
313
if (isRustPanic(error)) {
314
return await this.handleRustPanic(error, context)
315
}
316
317
// Handle WASM panics
318
if (isWasmPanic(error)) {
319
return this.handleWasmPanic(error, context)
320
}
321
322
// Handle regular errors
323
return this.handleRegularError(error, context)
324
}
325
326
/**
327
* Handle Rust panic with detailed reporting
328
*/
329
private async handleRustPanic(
330
error: RustPanic,
331
context: ErrorContext
332
): Promise<{ handled: boolean; reportId?: number }> {
333
console.error('π¦ Rust Panic Detected:')
334
console.error(` Area: ${error.area}`)
335
console.error(` Message: ${error.message}`)
336
337
// Log request details if available
338
if (error.request) {
339
console.error(' Request Details:')
340
console.error(` ${JSON.stringify(error.request, null, 2)}`)
341
}
342
343
// Show user-friendly message based on error area
344
this.showUserFriendlyPanicMessage(error.area)
345
346
// Report panic if enabled
347
let reportId: number | undefined
348
if (this.reportingEnabled) {
349
try {
350
reportId = await sendPanic({
351
error,
352
cliVersion: this.cliVersion,
353
enginesVersion: this.enginesVersion,
354
getDatabaseVersionSafe: async () => {
355
try {
356
// Attempt to get database version safely
357
return await this.getDatabaseVersionSafe()
358
} catch {
359
return 'unknown'
360
}
361
}
362
})
363
364
console.error(` Panic reported with ID: ${reportId}`)
365
} catch (reportError) {
366
console.warn(` Failed to report panic: ${reportError.message}`)
367
}
368
}
369
370
return { handled: true, reportId }
371
}
372
373
/**
374
* Handle WASM panic
375
*/
376
private handleWasmPanic(
377
error: WasmPanic,
378
context: ErrorContext
379
): { handled: boolean } {
380
console.error('πΈοΈ WASM Panic Detected:')
381
382
const { message, stack } = getWasmError(error)
383
console.error(` Message: ${message}`)
384
console.error(` Stack: ${stack}`)
385
386
// Show troubleshooting advice
387
console.error('\nπ‘ Troubleshooting WASM errors:')
388
console.error(' β’ Try updating to the latest Prisma version')
389
console.error(' β’ Check if your schema is valid')
390
console.error(' β’ Restart your development server')
391
392
return { handled: true }
393
}
394
395
/**
396
* Handle regular errors
397
*/
398
private handleRegularError(
399
error: Error,
400
context: ErrorContext
401
): { handled: boolean } {
402
console.error('β οΈ Regular Error:')
403
console.error(` Message: ${error.message}`)
404
405
if (error.stack) {
406
console.error(` Stack: ${error.stack}`)
407
}
408
409
// Check for common error patterns
410
if (error.message.includes('ECONNREFUSED')) {
411
console.error('\nπ‘ Connection refused - check if your database is running')
412
} else if (error.message.includes('authentication failed')) {
413
console.error('\nπ‘ Authentication failed - check your database credentials')
414
} else if (error.message.includes('relation') && error.message.includes('does not exist')) {
415
console.error('\nπ‘ Database table missing - run `prisma db push` or migrations')
416
}
417
418
return { handled: true }
419
}
420
421
/**
422
* Show user-friendly message based on panic area
423
*/
424
private showUserFriendlyPanicMessage(area: ErrorArea): void {
425
const messages = {
426
[ErrorArea.QUERY_ENGINE_BINARY_CLI]: 'Query engine crashed during database operation',
427
[ErrorArea.QUERY_ENGINE_LIBRARY_CLI]: 'Query engine library encountered an internal error',
428
[ErrorArea.FMT_CLI]: 'Schema formatter crashed while processing your schema',
429
[ErrorArea.INTROSPECTION_CLI]: 'Database introspection failed unexpectedly',
430
[ErrorArea.LIFT_CLI]: 'Migration engine encountered an error',
431
[ErrorArea.PHOTON_STUDIO]: 'Prisma Studio encountered an internal error'
432
}
433
434
const message = messages[area] || 'Prisma encountered an unexpected error'
435
console.error(`\nπ ${message}`)
436
437
console.error('\nπ§ Next steps:')
438
console.error(' β’ Check your database connection')
439
console.error(' β’ Verify your schema syntax')
440
console.error(' β’ Update to the latest Prisma version')
441
console.error(' β’ Report this issue if it persists')
442
}
443
444
/**
445
* Safely get database version
446
*/
447
private async getDatabaseVersionSafe(): Promise<string> {
448
// Implementation would depend on having access to PrismaClient
449
// This is a placeholder
450
return 'PostgreSQL 15.0'
451
}
452
}
453
```
454
455
### Error Recovery and Retry Logic
456
457
```typescript
458
import {
459
isRustPanic,
460
isWasmPanic,
461
warnOnce,
462
type RustPanic
463
} from '@prisma/internals'
464
465
interface RetryOptions {
466
maxAttempts: number
467
baseDelay: number
468
maxDelay: number
469
shouldRetry?: (error: Error, attempt: number) => boolean
470
}
471
472
class ErrorRecoveryManager {
473
474
/**
475
* Execute operation with retry logic and error recovery
476
*/
477
async withRetry<T>(
478
operation: () => Promise<T>,
479
options: RetryOptions
480
): Promise<T> {
481
const { maxAttempts, baseDelay, maxDelay, shouldRetry } = options
482
let lastError: Error | undefined
483
484
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
485
try {
486
return await operation()
487
} catch (error) {
488
lastError = error as Error
489
490
// Don't retry on panics - they indicate serious issues
491
if (isRustPanic(error) || isWasmPanic(error)) {
492
console.error(`π₯ Panic on attempt ${attempt} - not retrying`)
493
throw error
494
}
495
496
// Check custom retry logic
497
if (shouldRetry && !shouldRetry(error, attempt)) {
498
console.error(`π« Custom retry logic rejected attempt ${attempt}`)
499
throw error
500
}
501
502
// Don't retry on last attempt
503
if (attempt === maxAttempts) {
504
console.error(`π Max attempts (${maxAttempts}) reached`)
505
throw error
506
}
507
508
// Calculate delay with exponential backoff
509
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay)
510
511
console.warn(`β³ Attempt ${attempt} failed, retrying in ${delay}ms...`)
512
console.warn(` Error: ${error.message}`)
513
514
await new Promise(resolve => setTimeout(resolve, delay))
515
}
516
}
517
518
throw lastError || new Error('Operation failed after retries')
519
}
520
521
/**
522
* Graceful degradation for non-critical operations
523
*/
524
async withFallback<T>(
525
primaryOperation: () => Promise<T>,
526
fallbackOperation: () => Promise<T>,
527
context: string
528
): Promise<T> {
529
try {
530
return await primaryOperation()
531
} catch (error) {
532
if (isRustPanic(error)) {
533
warnOnce(`Rust panic in ${context} - using fallback`)
534
console.warn(`Primary operation failed due to panic: ${error.message}`)
535
} else if (isWasmPanic(error)) {
536
warnOnce(`WASM panic in ${context} - using fallback`)
537
console.warn(`Primary operation failed due to WASM error`)
538
} else {
539
console.warn(`Primary operation failed in ${context}: ${error.message}`)
540
}
541
542
console.log(`π Attempting fallback for ${context}...`)
543
return await fallbackOperation()
544
}
545
}
546
}
547
548
// Usage examples
549
const recoveryManager = new ErrorRecoveryManager()
550
551
// Database operation with retry
552
const users = await recoveryManager.withRetry(
553
async () => {
554
return await prisma.user.findMany()
555
},
556
{
557
maxAttempts: 3,
558
baseDelay: 1000,
559
maxDelay: 5000,
560
shouldRetry: (error, attempt) => {
561
// Don't retry validation errors
562
return !error.message.includes('Invalid')
563
}
564
}
565
)
566
567
// Schema operation with fallback
568
const formattedSchema = await recoveryManager.withFallback(
569
async () => {
570
// Try WASM formatter
571
return await formatSchema({ schemas })
572
},
573
async () => {
574
// Fallback to simple string formatting
575
warnOnce('Using simple schema formatting as fallback')
576
return schemas // Return unformatted
577
},
578
'schema formatting'
579
)
580
```
581
582
### Error Monitoring and Analytics
583
584
```typescript
585
import {
586
isRustPanic,
587
isWasmPanic,
588
getWasmError,
589
ErrorArea,
590
type RustPanic
591
} from '@prisma/internals'
592
593
interface ErrorMetrics {
594
totalErrors: number
595
rustPanics: number
596
wasmPanics: number
597
regularErrors: number
598
errorsByArea: Record<ErrorArea, number>
599
commonErrors: Record<string, number>
600
}
601
602
class ErrorMonitor {
603
private metrics: ErrorMetrics
604
private errorHistory: Array<{
605
error: Error
606
timestamp: Date
607
context: string
608
}> = []
609
610
constructor() {
611
this.metrics = {
612
totalErrors: 0,
613
rustPanics: 0,
614
wasmPanics: 0,
615
regularErrors: 0,
616
errorsByArea: {} as Record<ErrorArea, number>,
617
commonErrors: {}
618
}
619
}
620
621
/**
622
* Record error for monitoring
623
*/
624
recordError(error: Error, context: string): void {
625
this.metrics.totalErrors++
626
this.errorHistory.push({
627
error,
628
timestamp: new Date(),
629
context
630
})
631
632
if (isRustPanic(error)) {
633
this.metrics.rustPanics++
634
this.recordPanicArea(error.area)
635
} else if (isWasmPanic(error)) {
636
this.metrics.wasmPanics++
637
} else {
638
this.metrics.regularErrors++
639
}
640
641
// Track common error patterns
642
const errorKey = this.categorizeError(error)
643
this.metrics.commonErrors[errorKey] = (this.metrics.commonErrors[errorKey] || 0) + 1
644
645
// Limit history size
646
if (this.errorHistory.length > 1000) {
647
this.errorHistory = this.errorHistory.slice(-500)
648
}
649
}
650
651
private recordPanicArea(area: ErrorArea): void {
652
this.metrics.errorsByArea[area] = (this.metrics.errorsByArea[area] || 0) + 1
653
}
654
655
private categorizeError(error: Error): string {
656
const message = error.message.toLowerCase()
657
658
if (message.includes('connection')) return 'connection_error'
659
if (message.includes('authentication')) return 'auth_error'
660
if (message.includes('timeout')) return 'timeout_error'
661
if (message.includes('syntax')) return 'syntax_error'
662
if (message.includes('permission')) return 'permission_error'
663
if (message.includes('not found')) return 'not_found_error'
664
665
return 'other_error'
666
}
667
668
/**
669
* Generate error report
670
*/
671
generateReport(): string {
672
const { metrics } = this
673
674
let report = '\nπ Error Monitoring Report\n'
675
report += 'β'.repeat(40) + '\n\n'
676
677
report += `π Overall Statistics:\n`
678
report += ` Total Errors: ${metrics.totalErrors}\n`
679
report += ` Rust Panics: ${metrics.rustPanics}\n`
680
report += ` WASM Panics: ${metrics.wasmPanics}\n`
681
report += ` Regular Errors: ${metrics.regularErrors}\n\n`
682
683
if (metrics.rustPanics > 0) {
684
report += `π¦ Panics by Area:\n`
685
for (const [area, count] of Object.entries(metrics.errorsByArea)) {
686
report += ` ${area}: ${count}\n`
687
}
688
report += '\n'
689
}
690
691
if (Object.keys(metrics.commonErrors).length > 0) {
692
report += `π Common Errors:\n`
693
const sortedErrors = Object.entries(metrics.commonErrors)
694
.sort(([,a], [,b]) => b - a)
695
.slice(0, 5)
696
697
for (const [category, count] of sortedErrors) {
698
report += ` ${category.replace('_', ' ')}: ${count}\n`
699
}
700
report += '\n'
701
}
702
703
// Recent errors
704
const recentErrors = this.errorHistory.slice(-5)
705
if (recentErrors.length > 0) {
706
report += `π Recent Errors:\n`
707
for (const { error, timestamp, context } of recentErrors) {
708
const timeStr = timestamp.toISOString().substring(11, 19)
709
report += ` ${timeStr} [${context}] ${error.message.substring(0, 50)}...\n`
710
}
711
}
712
713
return report
714
}
715
716
/**
717
* Check if error rate is concerning
718
*/
719
getHealthStatus(): {
720
status: 'healthy' | 'warning' | 'critical'
721
message: string
722
} {
723
const recentErrors = this.errorHistory.filter(
724
entry => Date.now() - entry.timestamp.getTime() < 5 * 60 * 1000 // Last 5 minutes
725
)
726
727
const panicRate = this.metrics.rustPanics / Math.max(this.metrics.totalErrors, 1)
728
729
if (recentErrors.length > 10) {
730
return {
731
status: 'critical',
732
message: `High error rate: ${recentErrors.length} errors in last 5 minutes`
733
}
734
}
735
736
if (panicRate > 0.1) {
737
return {
738
status: 'critical',
739
message: `High panic rate: ${(panicRate * 100).toFixed(1)}% of errors are panics`
740
}
741
}
742
743
if (recentErrors.length > 5 || this.metrics.totalErrors > 20) {
744
return {
745
status: 'warning',
746
message: 'Elevated error levels detected'
747
}
748
}
749
750
return {
751
status: 'healthy',
752
message: 'Error levels within normal range'
753
}
754
}
755
}
756
```