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.
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' }
});
Parameter Type Required Description codeApiErrorCode | string✅ Semantic error code (see taxonomy below) messagestring✅ Human-readable error description detailsApiErrorDetails❌ Structured metadata (field, reason, etc.)
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)
Code When 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
Code When 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
Code When 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
Code When 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
Code When to Use CONFIG_ERRORConfiguration file missing or invalid SCAFFOLD_ERRORProject scaffolding failure MIGRATION_ERRORMigration execution failure
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;
}
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 }
});
}
}
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' }
});
}
}
The details object supports these common fields:
Field Type Description 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.
Always use ObjectQLError — Never throw new Error() in packages
Choose specific error codes — Use DRIVER_QUERY_FAILED over generic INTERNAL_ERROR
Include context in messages — Include the object name, field name, or operation
Use details for machine-readable info — Error messages are for humans, details are for code
Preserve original error info — Include error.message in your ObjectQLError message
Check with instanceof — Use error instanceof ObjectQLError for type-safe handling