Error Handling
Learn how to handle errors gracefully in dcupl applications, from initialization failures to data quality issues.
Error Categories
dcupl has three main error categories:
| Category | Description | When it occurs |
|---|---|---|
| Model Errors | Data quality and validation issues | During init() or data updates |
| Loader Errors | Configuration and resource loading failures | During loader.process() or init() |
| Response Errors | Runtime API failures | During queries or operations |
Model Errors
Model errors occur when data doesn't match the expected schema or validation rules.
Error Groups
| Group | Description |
|---|---|
ReferenceDataError |
Reference points to missing data |
PropertyDataError |
Property value doesn't match type or validation |
DataContainerError |
Issues with data container processing |
ModelDefinitionError |
Invalid model configuration |
Error Types
| Type | Description |
|---|---|
UndefinedAttribute |
Property not defined in model |
InvalidValidator |
Validation rule failed |
UndefinedValue |
Required field is missing |
NullValue |
Null value where not allowed |
WrongDataType |
Type mismatch (with strict mode) |
NonUniqueKey |
Duplicate key when unique required |
MissingModel |
Referenced model doesn't exist |
MissingProperty |
Property doesn't exist on model |
MissingReference |
Referenced item doesn't exist |
UnknownExpressionVariable |
Expression uses undefined variable |
RemoteReferenceKeyNotFound |
Remote reference target not found |
InvalidModelDefinition |
Model schema is invalid |
Handling Model Errors
import { Dcupl } from '@dcupl/core';
const dcupl = new Dcupl({
quality: { enabled: true },
});
// Setup models and data...
await dcupl.init();
// Check for errors after init
const errors = dcupl.quality.getErrors();
if (errors.length > 0) {
// Group errors by type
const errorsByType = errors.reduce(
(acc, err) => {
acc[err.errorType] = acc[err.errorType] || [];
acc[err.errorType].push(err);
return acc;
},
{} as Record<string, typeof errors>
);
console.error('Validation errors:', errorsByType);
// Handle specific error types
if (errorsByType.MissingReference) {
console.warn('Some references are broken - data may be incomplete');
}
if (errorsByType.UndefinedValue) {
console.error('Required fields missing - blocking error');
throw new Error('Data validation failed');
}
}Error Structure
type ModelError = {
type: 'model';
errorGroup:
| 'ReferenceDataError'
| 'PropertyDataError'
| 'DataContainerError'
| 'ModelDefinitionError';
errorType: string; // See error types table
title: string; // Human-readable title
description?: string; // Detailed description
model?: string; // Affected model key
itemKey?: string; // Affected item key
attribute?: string; // Affected attribute
meta?: Record<string, unknown>;
};Loader Errors
Loader errors occur when fetching or processing configuration and data resources.
Error Groups
| Group | Description |
|---|---|
LoaderConfigError |
Invalid configuration file or structure |
ResourceError |
Failed to fetch or process a resource |
Error Types
| Type | Description |
|---|---|
InvalidConfig |
Configuration file is malformed |
InvalidResource |
Resource could not be loaded or parsed |
Handling Loader Errors
import { Dcupl } from '@dcupl/core';
import { DcuplAppLoader } from '@dcupl/loader';
const dcupl = new Dcupl();
const loader = new DcuplAppLoader();
// Track loading progress and errors
loader.on((progress) => {
if (progress.type === 'resource_error') {
console.error(`Failed to load: ${progress.resourceKey}`, progress.error);
}
});
try {
await loader.config.fetch({
projectId: 'my-project',
apiKey: 'my-api-key',
});
} catch (error) {
console.error('Failed to fetch config:', error);
// Fallback to local config or show error UI
return;
}
try {
await loader.process();
} catch (error) {
console.error('Failed to process resources:', error);
// Handle gracefully
}
dcupl.loaders.add(loader, 'main');
try {
await dcupl.init();
} catch (error) {
console.error('Initialization failed:', error);
}Resource Retry Pattern
async function loadWithRetry(
loader: DcuplAppLoader,
options: { projectId: string; apiKey: string },
maxRetries = 3
) {
let lastError: Error | undefined;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await loader.config.fetch(options);
await loader.process();
return; // Success
} catch (error) {
lastError = error as Error;
console.warn(`Attempt ${attempt}/${maxRetries} failed:`, error);
if (attempt < maxRetries) {
// Exponential backoff
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
}
throw lastError;
}Response Errors
Response errors occur during runtime operations like queries.
Error Structure
type DcuplErrorResponse = {
name?: string;
message?: string;
type?: 'fatal' | 'minor';
};Handling Response Errors
// dcupl methods return results directly, but you can wrap in try-catch
function safeQuery<T>(list: DcuplList, options: QueryOptions): T[] | null {
try {
return list.catalog.query.items(options);
} catch (error) {
console.error('Query failed:', error);
return null;
}
}
// Usage
const products = safeQuery(productList, { offset: 0, limit: 10 });
if (products === null) {
// Show error state
}Event-Based Error Handling
Subscribe to dcupl events for real-time error detection.
const dcupl = new Dcupl({
quality: { enabled: true },
});
// Global error listener
dcupl.on((message) => {
// Track slow operations
if (message.duration && message.duration > 100) {
console.warn(`Slow operation: ${message.type} took ${message.duration}ms`);
}
// Track data changes for debugging
if (message.type === 'data_change') {
console.log('Data changed:', message.modelKey, message.args);
}
});
// List-specific error tracking
const list = dcupl.lists.create({ modelKey: 'Product' });
list.on((message) => {
if (message.type === 'query_execute') {
// Track query execution
console.log(`Query executed in ${message.duration}ms`);
}
});Logging Configuration
Configure logging for better debugging.
import { Dcupl } from '@dcupl/core';
const dcupl = new Dcupl({
logging: {
// Log levels: 'debug' | 'info' | 'warn' | 'error'
level: process.env.NODE_ENV === 'development' ? 'debug' : 'warn',
// Output format
format: process.env.NODE_ENV === 'development' ? 'pretty' : 'json',
// Custom log handler for external services
onLog: (entry) => {
if (entry.level === 'error') {
// Send to error tracking service
errorTracker.captureError(entry);
}
},
// Module-specific levels
modules: {
ModelParser: 'debug',
Catalog: 'warn',
},
},
});Error Recovery Strategies
Graceful Degradation
async function initializeDcupl() {
const dcupl = new Dcupl({ quality: { enabled: true } });
const loader = new DcuplAppLoader();
try {
// Try to load from remote
await loader.config.fetch({
projectId: process.env.DCUPL_PROJECT_ID!,
apiKey: process.env.DCUPL_API_KEY!,
});
} catch (error) {
console.warn('Remote config failed, using local fallback');
// Fallback to local configuration
loader.config.set({
resources: [{ key: 'products', type: 'data', url: '/data/products.json', model: 'Product' }],
models: [{ key: 'Product', properties: [{ key: 'name', type: 'string' }] }],
});
}
await loader.process();
dcupl.loaders.add(loader, 'main');
await dcupl.init();
// Check quality even after successful init
const errors = dcupl.quality.getErrors();
if (errors.length > 0) {
console.warn(`Initialized with ${errors.length} data quality issues`);
}
return dcupl;
}Partial Data Loading
async function loadCriticalDataFirst(dcupl: Dcupl, loader: DcuplAppLoader) {
// Load critical resources first
const criticalResources = loader.resources.getWithTag('critical');
const optionalResources = loader.resources.getWithTag('optional');
// Process critical resources
try {
await loader.process({ resourceTags: ['critical'] });
dcupl.loaders.add(loader, 'main');
await dcupl.init();
} catch (error) {
console.error('Critical data failed to load:', error);
throw error; // Can't continue without critical data
}
// Load optional resources in background
for (const resource of optionalResources) {
try {
await loader.process({ resourceTags: [resource.tags?.[0] || ''] });
await dcupl.update();
} catch (error) {
console.warn(`Optional resource ${resource.key} failed:`, error);
// Continue without this resource
}
}
}Validation Error Handling
function handleValidationErrors(dcupl: Dcupl, options: { strict: boolean }) {
const errors = dcupl.quality.getErrors();
// Categorize errors by severity
const critical = errors.filter(
(e) => e.errorType === 'MissingModel' || e.errorType === 'InvalidModelDefinition'
);
const warnings = errors.filter(
(e) => e.errorType === 'MissingReference' || e.errorType === 'UndefinedValue'
);
const info = errors.filter(
(e) => e.errorType === 'WrongDataType' || e.errorType === 'UndefinedAttribute'
);
// Report errors
if (critical.length > 0) {
console.error('Critical errors - cannot proceed:', critical);
if (options.strict) {
throw new Error('Critical validation errors');
}
}
if (warnings.length > 0) {
console.warn('Data warnings:', warnings);
}
if (info.length > 0 && process.env.NODE_ENV === 'development') {
console.info('Data info:', info);
}
return { critical, warnings, info };
}Debugging Tips
Enable Debug Logging
const dcupl = new Dcupl({
logging: {
level: 'debug',
format: 'pretty',
},
});Inspect Quality Metrics
await dcupl.init();
// Get quality analytics
const analytics = dcupl.quality.values;
console.log('Performance:', {
loading: `${analytics.loading}ms`,
processing: `${analytics.processing}ms`,
startup: `${analytics.startup}ms`,
});
console.log('Data Stats:', analytics.counts);
console.log('Error Summary:', analytics.errors);
// Find unused attributes
dcupl.quality.calculateUnusedData();
console.log('Unused:', analytics.unusedAttributes);Track Event Flow
// Development: log all events
if (process.env.NODE_ENV === 'development') {
dcupl.on((msg) => {
console.log(`[dcupl] ${msg.type}`, {
model: msg.modelKey,
list: msg.listKey,
duration: msg.duration ? `${msg.duration}ms` : undefined,
});
});
}Integration with Error Tracking Services
Sentry Integration
import * as Sentry from '@sentry/browser';
const dcupl = new Dcupl({
quality: { enabled: true },
logging: {
onLog: (entry) => {
if (entry.level === 'error') {
Sentry.captureException(new Error(entry.message), {
tags: { module: entry.module },
extra: entry.data,
});
}
},
},
});
// After init, report quality issues
await dcupl.init();
const errors = dcupl.quality.getErrors();
if (errors.length > 0) {
Sentry.captureMessage('dcupl data quality issues', {
level: 'warning',
extra: { errorCount: errors.length, errors: errors.slice(0, 10) },
});
}Best Practices
1. Always Enable Quality Checking
// Always enable in development
const dcupl = new Dcupl({
quality: { enabled: true },
});2. Check Errors After Init
await dcupl.init();
const errors = dcupl.quality.getErrors();
// Don't ignore errors!3. Use Event Listeners for Real-time Monitoring
dcupl.on((msg) => {
if (msg.duration && msg.duration > 100) {
reportSlowOperation(msg);
}
});4. Implement Graceful Fallbacks
try {
await loader.config.fetch(remoteConfig);
} catch {
loader.config.set(localFallback);
}5. Log Appropriately per Environment
logging: {
level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',
format: process.env.NODE_ENV === 'production' ? 'json' : 'pretty',
}Related
- Quality & Validation - Data validation rules
- Events Reference - All event types
- Debugging Guide - Debugging strategies
- DcuplAppLoader API - Loader configuration