β

ObjectQL v4.0 is currently in Beta.

ObjectStack LogoObjectQL
Guides

Error Handling

Structured error handling with ObjectQLError — error codes, patterns, and best practices

ObjectQL uses a structured error handling pattern built around the ObjectQLError class. All packages in the ecosystem throw ObjectQLError instead of plain Error objects, providing consistent error codes, messages, and optional details across every layer.


ObjectQLError Class

import { ObjectQLError, ApiErrorCode } from '@objectql/types';

throw new ObjectQLError({
  code: ApiErrorCode.NOT_FOUND,
  message: 'Object "orders" not found in metadata registry',
  details: { field: 'objectName', reason: 'Object not registered' }
});

Constructor

ParameterTypeRequiredDescription
codeApiErrorCode | stringSemantic error code (see taxonomy below)
messagestringHuman-readable error description
detailsApiErrorDetailsStructured metadata (field, reason, etc.)

Properties

  • code — The error code string (e.g., 'NOT_FOUND', 'DRIVER_QUERY_FAILED')
  • message — Human-readable error message (inherited from Error)
  • details — Optional structured metadata for programmatic error inspection
  • name — Always 'ObjectQLError' (useful for instanceof checks)
  • stack — Stack trace (inherited from Error)

Error Code Taxonomy

Core Error Codes (ApiErrorCode enum)

CodeWhen to Use
INVALID_REQUESTMalformed request parameters or missing required fields
VALIDATION_ERRORField validation failure (type mismatch, constraint violation)
UNAUTHORIZEDMissing or invalid authentication credentials
FORBIDDENAuthenticated but insufficient permissions (RBAC violation)
NOT_FOUNDObject, record, or resource does not exist
CONFLICTDuplicate key, optimistic concurrency conflict
INTERNAL_ERRORUnexpected internal error (catch-all)
DATABASE_ERRORGeneric database-level error
RATE_LIMIT_EXCEEDEDToo many requests

Driver Error Codes

CodeWhen to Use
DRIVER_ERRORGeneric driver error
DRIVER_CONNECTION_FAILEDFailed to connect to the database
DRIVER_QUERY_FAILEDQuery execution failed (validation, constraint, etc.)
DRIVER_TRANSACTION_FAILEDTransaction begin/commit/rollback failed
DRIVER_UNSUPPORTED_OPERATIONOperation not supported by this driver

Protocol Error Codes

CodeWhen to Use
PROTOCOL_ERRORGeneric protocol error
PROTOCOL_INVALID_REQUESTInvalid protocol request format
PROTOCOL_METHOD_NOT_FOUNDUnknown method/endpoint in the protocol
PROTOCOL_BATCH_ERRORBatch operation failure

Plugin Error Codes

CodeWhen to Use
TENANT_ISOLATION_VIOLATIONCross-tenant data access attempt
TENANT_NOT_FOUNDTenant context missing or invalid
WORKFLOW_TRANSITION_DENIEDInvalid state machine transition
FORMULA_EVALUATION_ERRORFormula expression evaluation failed

Tool/CLI Error Codes

CodeWhen to Use
CONFIG_ERRORConfiguration file missing or invalid
SCAFFOLD_ERRORProject scaffolding failure
MIGRATION_ERRORMigration execution failure

Error Handling Patterns

Catching ObjectQLError

import { ObjectQLError } from '@objectql/types';

try {
  const record = await repo.findOne(id);
} catch (error) {
  if (error instanceof ObjectQLError) {
    // Structured error — inspect code and details
    switch (error.code) {
      case 'NOT_FOUND':
        return { status: 404, message: error.message };
      case 'VALIDATION_ERROR':
        return { status: 400, fields: error.details?.fields };
      case 'FORBIDDEN':
        return { status: 403, permission: error.details?.required_permission };
      default:
        return { status: 500, message: 'Internal server error' };
    }
  }
  // Unknown error — re-throw
  throw error;
}

Throwing in Drivers

import { ObjectQLError } from '@objectql/types';

async find(objectName: string, query: UnifiedQuery) {
  try {
    return await this.db.collection(objectName).find(query.filter).toArray();
  } catch (error: any) {
    throw new ObjectQLError({
      code: 'DRIVER_QUERY_FAILED',
      message: `MongoDB query failed on "${objectName}": ${error.message}`,
      details: { field: 'query', reason: error.code }
    });
  }
}

Throwing in Hooks

import { ObjectQLError, ApiErrorCode } from '@objectql/types';

export async function beforeInsert(context: HookContext) {
  const { doc } = context;
  
  if (!doc.email?.includes('@')) {
    throw new ObjectQLError({
      code: ApiErrorCode.VALIDATION_ERROR,
      message: 'Invalid email address',
      details: { field: 'email', reason: 'Must contain @ symbol' }
    });
  }
}

ApiErrorDetails Reference

The details object supports these common fields:

FieldTypeDescription
fieldstringThe field that caused the error
reasonstringHuman-readable reason for the error
fieldsRecord<string, string>Multiple field errors (for bulk validation)
required_permissionstringThe permission needed (for FORBIDDEN errors)
user_rolesstring[]Current user's roles (for FORBIDDEN errors)
retry_afternumberSeconds to wait before retrying (for RATE_LIMIT_EXCEEDED)

Additional custom fields can be added via the index signature [key: string]: unknown.


Best Practices

  1. Always use ObjectQLError — Never throw new Error() in packages
  2. Choose specific error codes — Use DRIVER_QUERY_FAILED over generic INTERNAL_ERROR
  3. Include context in messages — Include the object name, field name, or operation
  4. Use details for machine-readable info — Error messages are for humans, details are for code
  5. Preserve original error info — Include error.message in your ObjectQLError message
  6. Check with instanceof — Use error instanceof ObjectQLError for type-safe handling

On this page