Error Types
This page documents the error type system in ReX which provides a consistent approach to error handling and recovery.
Core Error Interface
typescript
/**
* Base interface for error recovery options
*/
interface ContentErrorRecoveryOptions {
/**
* Whether the error can be retried
*/
canRetry?: boolean
/**
* Suggested retry delay in milliseconds
*/
retryDelay?: number
/**
* Maximum number of retries suggested
*/
maxRetries?: number
/**
* Alternative URIs that may contain the content
*/
alternativeUris?: string[]
/**
* Fallback content that can be used
*/
fallbackContent?: Content<any>
/**
* Additional context for recovery
*/
[key: string]: any
}
/**
* Base error class for content-related errors
*/
class ContentError extends Error {
/**
* Create a new ContentError
* @param message Error message
* @param uri Content URI related to the error
* @param source Source of the error (adapter name, etc.)
* @param recoverable Whether the error is recoverable
* @param recoveryOptions Options for recovery
*/
constructor(
message: string,
public readonly uri?: string,
public readonly source?: string,
public readonly recoverable: boolean = false,
public readonly recoveryOptions?: ContentErrorRecoveryOptions
) {
super(message)
this.name = 'ContentError'
}
/**
* Get a string representation of the error
*/
toString(): string {
return `${this.name}: ${this.message}${this.uri ? ` (uri: ${this.uri})` : ''}${this.source ? ` (source: ${this.source})` : ''}`
}
}
Specialized Error Types
The system defines specific error types for different scenarios:
Not Found Errors
typescript
/**
* Error thrown when content is not found
*/
class ContentNotFoundError extends ContentError {
constructor(
uri: string,
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Content not found at URI: ${uri}`,
uri,
source,
false,
recoveryOptions
)
this.name = 'ContentNotFoundError'
}
}
Access Errors
typescript
/**
* Error thrown when content cannot be accessed due to permissions
*/
class ContentAccessError extends ContentError {
constructor(
uri: string,
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Access denied for content at URI: ${uri}`,
uri,
source,
false,
recoveryOptions
)
this.name = 'ContentAccessError'
}
}
Format Errors
typescript
/**
* Error thrown when content format is invalid
*/
class ContentFormatError extends ContentError {
constructor(
uri: string,
details?: string,
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Invalid content format at URI: ${uri}${details ? ` (${details})` : ''}`,
uri,
source,
true,
{
canRetry: false,
...recoveryOptions,
}
)
this.name = 'ContentFormatError'
}
}
Validation Errors
typescript
/**
* Error thrown when content validation fails
*/
class ContentValidationError extends ContentError {
/**
* Create a new ContentValidationError
* @param uri Content URI
* @param validationErrors Array of validation error messages
* @param source Error source
* @param recoveryOptions Recovery options
*/
constructor(
uri: string,
public readonly validationErrors: string[],
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Content validation failed at URI: ${uri}: ${validationErrors.join(', ')}`,
uri,
source,
true,
{
canRetry: true,
validationErrors,
...recoveryOptions,
}
)
this.name = 'ContentValidationError'
}
}
Operation Errors
typescript
/**
* Error thrown when a content operation fails
*/
class ContentOperationError extends ContentError {
/**
* Create a new ContentOperationError
* @param operation Operation name
* @param uri Content URI
* @param cause Original error
* @param source Error source
* @param recoveryOptions Recovery options
*/
constructor(
public readonly operation: string,
uri: string,
public readonly cause?: Error,
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Content operation '${operation}' failed for URI: ${uri}${cause ? ` (${cause.message})` : ''}`,
uri,
source,
!!recoveryOptions?.canRetry,
recoveryOptions
)
this.name = 'ContentOperationError'
}
}
Timeout Errors
typescript
/**
* Error thrown when a content operation times out
*/
class ContentTimeoutError extends ContentError {
/**
* Create a new ContentTimeoutError
* @param operation Operation name
* @param uri Content URI
* @param timeoutMs Timeout in milliseconds
* @param source Error source
* @param recoveryOptions Recovery options
*/
constructor(
public readonly operation: string,
uri: string,
public readonly timeoutMs: number,
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Content operation '${operation}' timed out after ${timeoutMs}ms for URI: ${uri}`,
uri,
source,
true,
{
canRetry: true,
retryDelay: Math.min(timeoutMs * 2, 30000), // Double the timeout, max 30s
...recoveryOptions,
}
)
this.name = 'ContentTimeoutError'
}
}
Conflict Errors
typescript
/**
* Error thrown when a content conflict occurs
*/
class ContentConflictError extends ContentError {
/**
* Create a new ContentConflictError
* @param uri Content URI
* @param source Error source
* @param conflictingContent Conflicting content
* @param recoveryOptions Recovery options
*/
constructor(
uri: string,
source?: string,
public readonly conflictingContent?: Content<any>,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(`Content conflict at URI: ${uri}`, uri, source, true, {
canRetry: false,
conflictingContent,
...recoveryOptions,
})
this.name = 'ContentConflictError'
}
}
Capability Errors
typescript
/**
* Error thrown when an adapter doesn't support an operation
*/
class ContentCapabilityError extends ContentError {
/**
* Create a new ContentCapabilityError
* @param operation Unsupported operation
* @param adapter Adapter name
* @param recoveryOptions Recovery options
*/
constructor(
public readonly operation: string,
public readonly adapter: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Operation '${operation}' not supported by adapter '${adapter}'`,
undefined,
adapter,
false,
recoveryOptions
)
this.name = 'ContentCapabilityError'
}
}
Transformation Errors
typescript
/**
* Error thrown when content transformation fails
*/
class ContentTransformationError extends ContentError {
/**
* Create a new ContentTransformationError
* @param uri Content URI
* @param sourceType Source content type
* @param targetType Target content type
* @param cause Original error
* @param source Error source
* @param recoveryOptions Recovery options
*/
constructor(
uri: string,
public readonly sourceType: string,
public readonly targetType: string,
public readonly cause?: Error,
source?: string,
recoveryOptions?: ContentErrorRecoveryOptions
) {
super(
`Failed to transform content from '${sourceType}' to '${targetType}' at URI: ${uri}${cause ? ` (${cause.message})` : ''}`,
uri,
source,
false,
recoveryOptions
)
this.name = 'ContentTransformationError'
}
}
Error Utilities
The system provides utilities for working with errors:
typescript
/**
* Check if an error is a ContentError
* @param error Error to check
*/
function isContentError(error: Error): error is ContentError {
return error instanceof ContentError || error.name === 'ContentError'
}
/**
* Check if an error is a specific type of ContentError
* @param error Error to check
* @param errorType Error class or name
*/
function isErrorOfType(
error: Error,
errorType: typeof ContentError | string
): boolean {
if (typeof errorType === 'string') {
return error.name === errorType
}
return error instanceof errorType
}
/**
* Create a ContentError from any error
* @param error Original error
* @param uri Content URI
* @param source Error source
*/
function wrapError(error: Error, uri?: string, source?: string): ContentError {
if (isContentError(error)) {
return error
}
return new ContentOperationError('unknown', uri || 'unknown', error, source, {
canRetry: false,
})
}
Usage Examples
Basic Error Handling
typescript
import { createContentStore } from '@lib/content'
import { ContentNotFoundError, ContentAccessError } from '@lib/content/errors'
// Create a store
const store = createContentStore()
// Read with error handling
async function readContent(uri: string) {
try {
const content = await store.read(uri)
return content
} catch (error) {
if (error instanceof ContentNotFoundError) {
console.error(`Content not found: ${error.uri}`)
return null
} else if (error instanceof ContentAccessError) {
console.error(`Access denied: ${error.uri}`)
await requestPermission(error.uri)
return null
} else {
console.error(`Unexpected error: ${error.message}`)
throw error
}
}
}
Recovery Options
typescript
import { createContentStore } from '@lib/content'
import { isContentError } from '@lib/content/errors'
// Create a store
const store = createContentStore()
// Read with recovery
async function readWithRecovery(uri: string, maxRetries = 3) {
let retries = 0
while (true) {
try {
return await store.read(uri)
} catch (error) {
if (
isContentError(error) &&
error.recoverable &&
error.recoveryOptions?.canRetry
) {
if (retries >= maxRetries) {
console.error(`Max retries (${maxRetries}) exceeded for ${uri}`)
throw error
}
const delay = error.recoveryOptions.retryDelay || 1000
console.warn(
`Retrying after ${delay}ms (${retries + 1}/${maxRetries})...`
)
await new Promise(resolve => setTimeout(resolve, delay))
retries++
continue
}
// Try alternative URIs if available
if (
isContentError(error) &&
error.recoveryOptions?.alternativeUris?.length
) {
for (const altUri of error.recoveryOptions.alternativeUris) {
try {
console.log(`Trying alternative URI: ${altUri}`)
return await store.read(altUri)
} catch (altError) {
console.warn(`Alternative URI failed: ${altError.message}`)
}
}
}
// Use fallback content if available
if (isContentError(error) && error.recoveryOptions?.fallbackContent) {
console.warn(`Using fallback content for ${uri}`)
return error.recoveryOptions.fallbackContent
}
throw error
}
}
}
Custom Error Classes
typescript
import { ContentError, ContentErrorRecoveryOptions } from '@lib/content/errors'
/**
* Error thrown when rate limiting occurs
*/
class RateLimitError extends ContentError {
/**
* Create a new RateLimitError
* @param uri Content URI
* @param retryAfter Retry after seconds
* @param source Error source
*/
constructor(
uri: string,
public readonly retryAfter: number,
source?: string
) {
super(
`Rate limit exceeded for URI: ${uri}. Retry after ${retryAfter} seconds.`,
uri,
source,
true,
{
canRetry: true,
retryDelay: retryAfter * 1000,
}
)
this.name = 'RateLimitError'
}
}
// Example HTTP adapter with rate limit handling
const httpAdapter = createAdapter({
name: 'http',
read: async uri => {
try {
const response = await fetch(`https://api.example.com/${uri}`)
if (response.status === 429) {
const retryAfter = parseInt(
response.headers.get('Retry-After') || '60',
10
)
throw new RateLimitError(uri, retryAfter, 'http')
}
if (!response.ok) {
throw new Error(`HTTP error ${response.status}: ${response.statusText}`)
}
const data = await response.text()
return {
data,
contentType: response.headers.get('Content-Type') || 'text/plain',
metadata: {
title: uri.split('/').pop() || uri,
updatedAt: new Date(),
},
}
} catch (error) {
if (error instanceof RateLimitError) {
throw error
}
throw new ContentOperationError('read', uri, error, 'http')
}
},
})
Middleware Error Handling
typescript
import { pipe, withErrorHandling } from '@lib/content/middleware'
import { isContentError, ContentNotFoundError } from '@lib/content/errors'
// Create a store with error handling middleware
const store = pipe(
baseStore,
withErrorHandling({
onError: (error, operation, uri) => {
console.error(
`Error in ${operation} operation for ${uri}: ${error.message}`
)
// Report errors to monitoring system
reportError(error, { operation, uri })
// Don't transform the error, let it propagate
return error
},
transformError: (error, operation, uri) => {
// Convert 404 errors to a custom format
if (error instanceof ContentNotFoundError) {
return new CustomNotFoundError(uri, error.source)
}
// Add context to errors
if (isContentError(error)) {
error.context = {
...error.context,
timestamp: new Date(),
operation,
uri,
}
}
return error
},
})
)
Error Patterns
The error system is designed to support several important patterns:
1. Error Classification
Errors are classified by type, enabling specific handling strategies:
typescript
try {
await store.read('doc.md')
} catch (error) {
if (error instanceof ContentNotFoundError) {
// Handle not found
} else if (error instanceof ContentAccessError) {
// Handle access denied
} else if (error instanceof ContentFormatError) {
// Handle invalid format
} else {
// Handle other errors
}
}
2. Error Recovery
Errors include recovery information to guide recovery strategies:
typescript
try {
await store.read('doc.md')
} catch (error) {
if (isContentError(error) && error.recoverable) {
const options = error.recoveryOptions || {}
if (options.canRetry) {
// Implement retry logic
}
if (options.alternativeUris?.length) {
// Try alternative URIs
}
if (options.fallbackContent) {
// Use fallback content
}
}
}
3. Error Enrichment
Errors are enriched with context for better debugging:
typescript
try {
await store.write('doc.md', content)
} catch (error) {
if (isContentError(error)) {
console.error(`
Error: ${error.message}
URI: ${error.uri}
Source: ${error.source}
Operation: ${error instanceof ContentOperationError ? error.operation : 'unknown'}
Recoverable: ${error.recoverable ? 'Yes' : 'No'}
`)
}
}
4. Error Translation
Errors from underlying systems are translated to ContentErrors:
typescript
async function readFile(uri) {
try {
// Convert URI to filesystem path
const path = convertUriToPath(uri)
return await fs.promises.readFile(path, 'utf8')
} catch (error) {
if (error.code === 'ENOENT') {
throw new ContentNotFoundError(uri, 'filesystem')
} else if (error.code === 'EACCES') {
throw new ContentAccessError(uri, 'filesystem')
} else {
throw new ContentOperationError('read', uri, error, 'filesystem')
}
}
}