Skip to content

Error Handling

Lucid Resourceful provides a comprehensive error handling system with structured exceptions for different types of failures. All errors extend the AdonisJS Exception class and include detailed error information, HTTP status codes, and type-safe error checking capabilities.

Overview

The error system in Lucid Resourceful is designed to provide:

  • Structured Error Information: All errors include error codes, HTTP status codes, and detailed messages
  • Type-Safe Error Checking: Use type guards to identify specific error types
  • Rich Context: Validation errors include Joi validation details
  • Consistent HTTP Status Codes: Proper status codes for different error scenarios
  • Developer-Friendly Messages: Clear, actionable error messages for debugging

Importing Errors

Errors can be imported in two ways:

From the Root Module

typescript
import { errors } from '@nhtio/lucid-resourceful'

// Use specific error classes
const { E_FORBIDDEN, E_INVALID_PAYLOAD_EXCEPTION } = errors

try {
  // Some operation that might fail
} catch (error) {
  if (error instanceof errors.E_FORBIDDEN) {
    // Handle forbidden access
  }
}

From the Errors Sub-Module

typescript
import {
  E_FORBIDDEN,
  E_INVALID_PAYLOAD_EXCEPTION,
  E_RECORD_NOT_FOUND_EXCEPTION
} from '@nhtio/lucid-resourceful/errors'

try {
  // Some operation that might fail
} catch (error) {
  if (error instanceof E_FORBIDDEN) {
    // Handle forbidden access
  }
}

Error Categories

Lucid Resourceful errors are organized into several categories based on their context and purpose:

Configuration and Setup Errors

Errors that occur during model setup, decorator configuration, or data type instantiation.

Validation Errors

Errors related to data validation, both at the model level and request payload level.

Access Control Errors

Errors thrown when access control restrictions prevent operations.

Query and Database Errors

Errors related to database queries, Lucene query parsing, and missing records.

Type Casting Errors

Errors thrown during data type conversion and casting operations.

Error Types

Configuration Errors

E_INVALID_RESOURCEFUL_DATA_TYPE_OPTIONS

Thrown when invalid options are provided to data type constructors.

typescript
import { E_INVALID_RESOURCEFUL_DATA_TYPE_OPTIONS } from '@nhtio/lucid-resourceful/errors'
import { ResourcefulStringType } from '@nhtio/lucid-resourceful/definitions'

try {
  const stringType = new ResourcefulStringType({
    minLength: -1, // Invalid: negative length
    maxLength: 5
  })
} catch (error) {
  if (error instanceof E_INVALID_RESOURCEFUL_DATA_TYPE_OPTIONS) {
    console.error('Invalid data type options:', error.message)
    console.error('Validation details:', error.details)
    // Error includes Joi validation details
  }
}

Properties:

  • status: 500
  • code: 'E_INVALID_RESOURCEFUL_DATA_TYPE_OPTIONS'
  • details: Joi validation error details (if available)

E_INVALID_RESOURCEFUL_MIXIN_OPTIONS

Thrown when invalid options are provided to the withResourceful mixin.

typescript
import { E_INVALID_RESOURCEFUL_MIXIN_OPTIONS } from '@nhtio/lucid-resourceful/errors'
import { withResourceful } from '@nhtio/lucid-resourceful'

try {
  class User extends withResourceful({
    name: 123, // Invalid: name must be string
    accessControlFilters: 'invalid' // Invalid: must be object
  })(BaseModel) {}
} catch (error) {
  if (error instanceof E_INVALID_RESOURCEFUL_MIXIN_OPTIONS) {
    console.error('Invalid mixin options:', error.message)
    console.error('Validation details:', error.details)
  }
}

Properties:

  • status: 500
  • code: 'E_INVALID_RESOURCEFUL_MIXIN_OPTIONS'
  • details: Joi validation error details (if available)

E_INVALID_RESOURCEFUL_DECORATOR_OPTIONS

Thrown when invalid options are provided to resourceful decorators.

typescript
import { E_INVALID_RESOURCEFUL_DECORATOR_OPTIONS } from '@nhtio/lucid-resourceful/errors'
import { resourcefulColumn } from '@nhtio/lucid-resourceful'

try {
  class User extends BaseModel {
    @resourcefulColumn({
      type: 'invalid-type', // Invalid data type
      validate: 'not-a-schema' // Invalid validation schema
    })
    declare name: string
  }
} catch (error) {
  if (error instanceof E_INVALID_RESOURCEFUL_DECORATOR_OPTIONS) {
    console.error(`Invalid decorator options for field "${error.fieldName}":`, error.message)
    console.error('Validation details:', error.details)
  }
}

Properties:

  • status: 500
  • code: 'E_INVALID_RESOURCEFUL_DECORATOR_OPTIONS'
  • details: Joi validation error details (if available)

Validation Errors

E_INVALID_PAYLOAD_EXCEPTION

Thrown when request payload validation fails during create or update operations.

typescript
import { E_INVALID_PAYLOAD_EXCEPTION } from '@nhtio/lucid-resourceful/errors'

try {
  const user = await User.$onResourcefulCreate({
    name: '', // Invalid: empty name
    email: 'invalid-email' // Invalid: not a valid email format
  }, ctx, app)
} catch (error) {
  if (error instanceof E_INVALID_PAYLOAD_EXCEPTION) {
    console.error('Payload validation failed:', error.message)
    console.error('Validation details:', error.details)
    
    // Return structured error response
    return response.status(error.status).json({
      error: error.code,
      message: error.message,
      details: error.details
    })
  }
}

Properties:

  • status: 422
  • code: 'E_INVALID_PAYLOAD_EXCEPTION'
  • details: Joi validation error details (if available)

E_FORBIDDEN_PAYLOAD_EXCEPTION

Thrown when payload validation fails due to authorization constraints.

typescript
import { E_FORBIDDEN_PAYLOAD_EXCEPTION } from '@nhtio/lucid-resourceful/errors'

try {
  const user = await User.$onResourcefulCreate({
    name: 'John Doe',
    role: 'admin' // Forbidden: regular users cannot set admin role
  }, ctx, app)
} catch (error) {
  if (error instanceof E_FORBIDDEN_PAYLOAD_EXCEPTION) {
    console.error('Payload authorization failed:', error.message)
    console.error('Validation details:', error.details)
    
    return response.status(error.status).json({
      error: error.code,
      message: 'You are not authorized to perform this action'
    })
  }
}

Properties:

  • status: 403
  • code: 'E_FORBIDDEN_PAYLOAD_EXCEPTION'
  • details: Joi validation error details (if available)

E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION

Thrown when request parameters for index/list operations fail validation.

typescript
import { E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION } from '@nhtio/lucid-resourceful/errors'

try {
  const result = await User.$onResourcefulIndex(
    'invalid-lucene-query[]', // Invalid Lucene syntax
    -1, // Invalid page number
    0, // Invalid perPage value
    ['nonexistent_field'], // Invalid field name
    ctx,
    app
  )
} catch (error) {
  if (error instanceof E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION) {
    console.error('Index request validation failed:', error.message)
    console.error('Validation details:', error.details)
  }
}

Properties:

  • status: 422
  • code: 'E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION'
  • details: Joi validation error details (if available)

Access Control Errors

E_FORBIDDEN

Thrown when access control filters deny access to operations.

typescript
import { E_FORBIDDEN } from '@nhtio/lucid-resourceful/errors'

try {
  const user = await User.$onResourcefulRead(123, ctx, app)
} catch (error) {
  if (error instanceof E_FORBIDDEN) {
    console.error('Access denied:', error.message)
    
    return response.status(error.status).json({
      error: error.code,
      message: 'You do not have permission to access this resource'
    })
  }
}

Properties:

  • status: 403
  • code: 'E_FORBIDDEN'
  • details: Error message string

E_INVALID_COLUMN_ACCESS

Thrown when trying to access columns that don't exist or are not accessible.

typescript
import { E_INVALID_COLUMN_ACCESS } from '@nhtio/lucid-resourceful/errors'

try {
  const result = await User.$onResourcefulIndex(
    'name:john',
    1,
    10,
    ['password', 'secret_key'], // Fields not accessible
    ctx,
    app
  )
} catch (error) {
  if (error instanceof E_INVALID_COLUMN_ACCESS) {
    console.error('Column access denied:', error.message)
    // Might be: "You don't have access to column "password" or the column does not exist."
  }
}

Properties:

  • status: 403
  • code: 'E_INVALID_COLUMN_ACCESS'
  • details: Error message string

Query and Database Errors

E_RECORD_NOT_FOUND_EXCEPTION

Thrown when a requested record cannot be found or accessed.

typescript
import { E_RECORD_NOT_FOUND_EXCEPTION } from '@nhtio/lucid-resourceful/errors'

try {
  const user = await User.$onResourcefulRead(99999, ctx, app) // Non-existent ID
} catch (error) {
  if (error instanceof E_RECORD_NOT_FOUND_EXCEPTION) {
    console.error('Record not found:', error.message)
    
    return response.status(error.status).json({
      error: error.code,
      message: 'The requested resource was not found'
    })
  }
}

Properties:

  • status: 404
  • code: 'E_RECORD_NOT_FOUND_EXCEPTION'

E_MISSING_PRIMARY_KEY_EXCEPTION

Thrown when operations require a primary key but the model doesn't have one defined.

typescript
import { E_MISSING_PRIMARY_KEY_EXCEPTION } from '@nhtio/lucid-resourceful/errors'

try {
  // Model without a primary key
  class Settings extends withResourceful()(BaseModel) {
    // No primary key defined
  }
  
  const result = await Settings.$onResourcefulRead(1, ctx, app)
} catch (error) {
  if (error instanceof E_MISSING_PRIMARY_KEY_EXCEPTION) {
    console.error('Model configuration error:', error.message)
    // Message: "Settings" does not have a primary key defined
  }
}

Properties:

  • status: 503
  • code: 'E_MISSING_PRIMARY_KEY_EXCEPTION'

E_INVALID_LUCENE_QUERY

Thrown when Lucene query syntax is invalid.

typescript
import { E_INVALID_LUCENE_QUERY } from '@nhtio/lucid-resourceful/errors'

try {
  const result = await User.$onResourcefulIndex(
    'name:[invalid syntax', // Unclosed bracket
    1,
    10,
    ['id', 'name'],
    ctx,
    app
  )
} catch (error) {
  if (error instanceof E_INVALID_LUCENE_QUERY) {
    console.error('Invalid query syntax:', error.message)
    // Provide user-friendly feedback about query syntax
  }
}

Properties:

  • status: 400
  • code: 'E_INVALID_LUCENE_QUERY'
  • details: Error message string

E_LUCENE_SYNTAX_EXCEPTION

Thrown when Lucene query parsing encounters syntax errors with detailed location information.

typescript
import { E_LUCENE_SYNTAX_EXCEPTION } from '@nhtio/lucid-resourceful/errors'

try {
  const result = await User.$onResourcefulIndex(
    'name:john AND (email:', // Unclosed parenthesis
    1,
    10,
    ['id', 'name'],
    ctx,
    app
  )
} catch (error) {
  if (error instanceof E_LUCENE_SYNTAX_EXCEPTION) {
    console.error('Query syntax error:', error.message)
    console.error(`Error at line ${error.line}, column ${error.column}, offset ${error.offset}`)
    
    // Provide precise error location for debugging
    return response.status(error.status).json({
      error: error.code,
      message: 'Invalid query syntax',
      location: {
        line: error.line,
        column: error.column,
        offset: error.offset
      }
    })
  }
}

Properties:

  • status: 422
  • code: 'E_LUCENE_SYNTAX_EXCEPTION'
  • offset: Error offset in the query string
  • line: Line number where error occurred
  • column: Column number where error occurred

E_UNEXPECTED_COLUMN_IN_QUERY

Thrown when Lucene queries reference columns that don't exist in the model.

typescript
import { E_UNEXPECTED_COLUMN_IN_QUERY } from '@nhtio/lucid-resourceful/errors'

try {
  const result = await User.$onResourcefulIndex(
    'nonexistent_field:value', // Field doesn't exist
    1,
    10,
    ['id', 'name'],
    ctx,
    app
  )
} catch (error) {
  if (error instanceof E_UNEXPECTED_COLUMN_IN_QUERY) {
    console.error('Unknown column in query:', error.message)
    // Message: Unexpected column in query: "nonexistent_field"
  }
}

Properties:

  • status: 400
  • code: 'E_UNEXPECTED_COLUMN_IN_QUERY'
  • details: Error message string

Type Casting Errors

E_UNCASTABLE

Thrown when values cannot be cast to the expected data type.

typescript
import { E_UNCASTABLE } from '@nhtio/lucid-resourceful/errors'

try {
  // This error typically occurs internally during data processing
  const value = castValueAsNumber('not-a-number')
} catch (error) {
  if (error instanceof Error && error.code === 'E_UNCASTABLE') {
    console.error('Type casting failed:', error.message)
    // Message: The value could not be cast to a valid number
  }
}

Properties:

  • status: 500
  • code: 'E_UNCASTABLE'

E_INVALID_PREPARED_VALUE

Thrown when values fail preparation for database storage.

typescript
import { E_INVALID_PREPARED_VALUE } from '@nhtio/lucid-resourceful/errors'

try {
  // This error typically occurs during model save operations
  user.email = 'invalid-email-format'
  await user.save()
} catch (error) {
  if (error instanceof Error && error.code === 'E_INVALID_PREPARED_VALUE') {
    console.error('Value preparation failed:', error.message)
    // Message: The value for "email" is not a valid email
  }
}

Properties:

  • status: 422
  • code: 'E_INVALID_PREPARED_VALUE'

E_INVALID_CONSUMED_VALUE

Thrown when values cannot be consumed from database results.

typescript
import { E_INVALID_CONSUMED_VALUE } from '@nhtio/lucid-resourceful/errors'

try {
  // This error typically occurs during model hydration
  const user = await User.find(1)
} catch (error) {
  if (error instanceof Error && error.code === 'E_INVALID_CONSUMED_VALUE') {
    console.error('Value consumption failed:', error.message)
    // Message: The value for "birth_date" could not be consumed as a valid date
  }
}

Properties:

  • status: 500
  • code: 'E_INVALID_CONSUMED_VALUE'

Type Guards for Error Checking

Lucid Resourceful provides type guards to safely check error types and access their properties.

Using isInstanceOf Type Guard

The isInstanceOf type guard provides a safe way to check error types, especially useful when dealing with bundled or transformed code where instanceof might not work reliably.

typescript
import { guards } from '@nhtio/lucid-resourceful/utils'
import { 
  E_FORBIDDEN, 
  E_INVALID_PAYLOAD_EXCEPTION,
  E_RECORD_NOT_FOUND_EXCEPTION 
} from '@nhtio/lucid-resourceful/errors'

try {
  const user = await User.$onResourcefulCreate(payload, ctx, app)
} catch (error) {
  // Using isInstanceOf for reliable type checking
  if (guards.isInstanceOf(error, 'E_FORBIDDEN', E_FORBIDDEN)) {
    return response.status(403).json({
      error: 'FORBIDDEN',
      message: 'Access denied'
    })
  }
  
  if (guards.isInstanceOf(error, 'E_INVALID_PAYLOAD_EXCEPTION', E_INVALID_PAYLOAD_EXCEPTION)) {
    return response.status(422).json({
      error: 'VALIDATION_ERROR',
      message: 'Invalid input data',
      details: error.details
    })
  }
  
  if (guards.isInstanceOf(error, 'E_RECORD_NOT_FOUND_EXCEPTION', E_RECORD_NOT_FOUND_EXCEPTION)) {
    return response.status(404).json({
      error: 'NOT_FOUND',
      message: 'Resource not found'
    })
  }
  
  // Unknown error - re-throw
  throw error
}

Comprehensive Error Handler

Here's an example of a comprehensive error handler that handles all Lucid Resourceful error types:

typescript
import { guards } from '@nhtio/lucid-resourceful/utils'
import { errors } from '@nhtio/lucid-resourceful'

class ResourcefulErrorHandler {
  static handle(error: unknown, response: Response) {
    // Configuration and setup errors
    if (guards.isInstanceOf(error, 'E_INVALID_RESOURCEFUL_DATA_TYPE_OPTIONS', errors.E_INVALID_RESOURCEFUL_DATA_TYPE_OPTIONS)) {
      return this.handleConfigurationError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_INVALID_RESOURCEFUL_MIXIN_OPTIONS', errors.E_INVALID_RESOURCEFUL_MIXIN_OPTIONS)) {
      return this.handleConfigurationError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_INVALID_RESOURCEFUL_DECORATOR_OPTIONS', errors.E_INVALID_RESOURCEFUL_DECORATOR_OPTIONS)) {
      return this.handleConfigurationError(error, response)
    }
    
    // Validation errors
    if (guards.isInstanceOf(error, 'E_INVALID_PAYLOAD_EXCEPTION', errors.E_INVALID_PAYLOAD_EXCEPTION)) {
      return this.handleValidationError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_FORBIDDEN_PAYLOAD_EXCEPTION', errors.E_FORBIDDEN_PAYLOAD_EXCEPTION)) {
      return this.handleForbiddenPayloadError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION', errors.E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION)) {
      return this.handleIndexRequestError(error, response)
    }
    
    // Access control errors
    if (guards.isInstanceOf(error, 'E_FORBIDDEN', errors.E_FORBIDDEN)) {
      return this.handleForbiddenError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_INVALID_COLUMN_ACCESS', errors.E_INVALID_COLUMN_ACCESS)) {
      return this.handleColumnAccessError(error, response)
    }
    
    // Query and database errors
    if (guards.isInstanceOf(error, 'E_RECORD_NOT_FOUND_EXCEPTION', errors.E_RECORD_NOT_FOUND_EXCEPTION)) {
      return this.handleNotFoundError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_MISSING_PRIMARY_KEY_EXCEPTION', errors.E_MISSING_PRIMARY_KEY_EXCEPTION)) {
      return this.handleMissingPrimaryKeyError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_LUCENE_SYNTAX_EXCEPTION', errors.E_LUCENE_SYNTAX_EXCEPTION)) {
      return this.handleLuceneSyntaxError(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_INVALID_LUCENE_QUERY', errors.E_INVALID_LUCENE_QUERY)) {
      return this.handleInvalidLuceneQuery(error, response)
    }
    
    if (guards.isInstanceOf(error, 'E_UNEXPECTED_COLUMN_IN_QUERY', errors.E_UNEXPECTED_COLUMN_IN_QUERY)) {
      return this.handleUnexpectedColumnError(error, response)
    }
    
    // Unknown error
    return this.handleUnknownError(error, response)
  }
  
  private static handleValidationError(error: errors.E_INVALID_PAYLOAD_EXCEPTION, response: Response) {
    return response.status(422).json({
      error: 'VALIDATION_ERROR',
      message: 'The provided data is invalid',
      details: error.details?.map(detail => ({
        field: detail.path.join('.'),
        message: detail.message,
        value: detail.context?.value
      }))
    })
  }
  
  private static handleForbiddenError(error: errors.E_FORBIDDEN, response: Response) {
    return response.status(403).json({
      error: 'FORBIDDEN',
      message: 'You do not have permission to perform this action'
    })
  }
  
  private static handleNotFoundError(error: errors.E_RECORD_NOT_FOUND_EXCEPTION, response: Response) {
    return response.status(404).json({
      error: 'NOT_FOUND',
      message: 'The requested resource was not found'
    })
  }
  
  private static handleLuceneSyntaxError(error: errors.E_LUCENE_SYNTAX_EXCEPTION, response: Response) {
    return response.status(422).json({
      error: 'QUERY_SYNTAX_ERROR',
      message: 'Invalid query syntax',
      details: {
        line: error.line,
        column: error.column,
        offset: error.offset
      }
    })
  }
  
  // ... other error handlers
}

Error Context and Debugging

Joi Validation Details

Many Lucid Resourceful errors include Joi validation details that provide structured information about validation failures:

typescript
try {
  const user = await User.$onResourcefulCreate(invalidPayload, ctx, app)
} catch (error) {
  if (error instanceof E_INVALID_PAYLOAD_EXCEPTION && error.details) {
    error.details.forEach(detail => {
      console.log(`Field: ${detail.path.join('.')}`)
      console.log(`Message: ${detail.message}`)
      console.log(`Value: ${detail.context?.value}`)
      console.log(`Label: ${detail.context?.label}`)
    })
  }
}

Error Chaining

Lucid Resourceful errors preserve the original error cause when wrapping lower-level errors:

typescript
try {
  // Operation that might fail
} catch (error) {
  if (error instanceof E_LUCENE_SYNTAX_EXCEPTION) {
    console.log('Resourceful error:', error.message)
    console.log('Original error:', error.cause) // Original Lucene parser error
  }
}

Development vs Production Error Handling

Handle errors differently based on environment:

typescript
class ErrorHandler {
  static handle(error: unknown, response: Response, isDevelopment: boolean = false) {
    if (guards.isInstanceOf(error, 'E_INVALID_PAYLOAD_EXCEPTION', E_INVALID_PAYLOAD_EXCEPTION)) {
      const responseData = {
        error: 'VALIDATION_ERROR',
        message: 'Invalid input data'
      }
      
      if (isDevelopment) {
        // Include detailed validation information in development
        responseData.details = error.details
        responseData.stack = error.stack
      }
      
      return response.status(422).json(responseData)
    }
    
    // Handle other errors similarly
  }
}

Best Practices

1. Use Type Guards for Reliable Error Checking

Always use the isInstanceOf type guard for error checking to ensure compatibility across different bundling and transformation scenarios:

typescript
// ✅ Good: Using type guard
if (guards.isInstanceOf(error, 'E_FORBIDDEN', E_FORBIDDEN)) {
  // Handle forbidden error
}

// ❌ Avoid: Direct instanceof (may not work with bundled code)
if (error instanceof E_FORBIDDEN) {
  // This might fail in some environments
}

2. Provide User-Friendly Error Messages

Transform technical error messages into user-friendly ones:

typescript
const ERROR_MESSAGES = {
  E_INVALID_PAYLOAD_EXCEPTION: 'Please check your input and try again',
  E_FORBIDDEN: 'You do not have permission to perform this action',
  E_RECORD_NOT_FOUND_EXCEPTION: 'The requested item was not found',
  E_LUCENE_SYNTAX_EXCEPTION: 'Your search query is invalid'
}

function getUserFriendlyMessage(error: unknown): string {
  if (error instanceof Error && error.code) {
    return ERROR_MESSAGES[error.code as keyof typeof ERROR_MESSAGES] || 'An unexpected error occurred'
  }
  return 'An unexpected error occurred'
}

3. Log Errors for Debugging

Always log errors with sufficient context for debugging:

typescript
import { Logger } from '@adonisjs/logger'

class ErrorLogger {
  static log(error: unknown, context: Record<string, any> = {}) {
    if (guards.isInstanceOf(error, 'E_INVALID_PAYLOAD_EXCEPTION', E_INVALID_PAYLOAD_EXCEPTION)) {
      Logger.warn('Validation error occurred', {
        error: error.message,
        details: error.details,
        context
      })
    } else if (guards.isInstanceOf(error, 'E_FORBIDDEN', E_FORBIDDEN)) {
      Logger.warn('Access denied', {
        error: error.message,
        context
      })
    } else {
      Logger.error('Unexpected error', {
        error: error.message,
        stack: error.stack,
        context
      })
    }
  }
}

4. Handle Validation Details Appropriately

Extract useful information from Joi validation details:

typescript
function formatValidationErrors(details: ValidationError['details'] = []) {
  return details.map(detail => {
    const field = detail.path.join('.')
    const message = detail.message.replace(/["']/g, '') // Remove quotes
    
    return {
      field,
      message,
      value: detail.context?.value,
      code: detail.type
    }
  })
}

5. Graceful Degradation

Handle errors gracefully to maintain application stability:

typescript
async function safeOperation<T>(
  operation: () => Promise<T>,
  fallback: T
): Promise<T> {
  try {
    return await operation()
  } catch (error) {
    // Log the error
    ErrorLogger.log(error)
    
    // Return fallback value instead of crashing
    return fallback
  }
}

// Usage
const users = await safeOperation(
  () => User.$onResourcefulIndex(query, page, perPage, fields, ctx, app),
  { records: [], total: 0, page: 1, perPage: 10 }
)

API Reference

For detailed API documentation of error classes, see: