tryError Documentation
Error Factory API Reference
Complete reference for all error factory functions and their usage patterns.
Why Error Factories?
Error factories solve three core problems in JavaScript error handling:
Consistency
Standardized error structure across your entire application
Type Safety
Full TypeScript support with discriminated unions
Rich Context
Capture debugging information at the point of failure
Design Principles
- ✓Progressive Enhancement: Start simple, add complexity as needed
- ✓Ergonomic API: Natural parameter order, intuitive naming
- ✓Context-Rich: Capture relevant debugging information automatically
- ✓Type-First: Leverage TypeScript's discriminated unions
Core Factories
createError()
Primary factory functionwrapError()
Wrap existing errorsfromThrown()
Convert thrown exceptionsDomain-Specific Factories
validationError()
Field validation failuresamountError()
Financial/quantity errorsexternalError()
External service failuresentityError()
Entity not found/conflictsChoosing the Right Factory
• Start with domain-specific factories for common use cases
• Use createError() for custom error types and complex scenarios
• Use wrapError() when you need to add context to existing errors
• Use fromThrown() in try/catch blocks to convert exceptions
Simple Validation
// Quick field validation
const error = validationError('email', 'required', 'Email is required');
// With additional context
const error = validationError('age', 'range', 'Age must be 18-65', {
min: 18,
max: 65,
provided: 16
});
API Error Handling
// External service failure
const error = externalError('PaymentAPI', 'timeout', 'Payment service unavailable');
// With request context
const error = externalError('UserAPI', 'not_found', 'User not found', {
userId: '123',
endpoint: '/api/users/123'
});
Business Logic Errors
// Insufficient funds
const error = amountError(150, 100, 'insufficient', 'Insufficient balance');
// Entity conflicts
const error = entityError('user', 'john@example.com', 'User already exists');
✅ New: Improved Ergonomic Error Factories
We've added more intuitive error factory functions with better parameter orders:
Validation Errors (Simplified)
// ✅ NEW: Simple field validation
const error = validationError('email', 'invalid', 'Must be a valid email address', {
value: 'invalid-email'
});
// ✅ NEW: Multi-field validation
const formError = fieldValidationError({
email: ['Must be a valid email address'],
password: ['Must be at least 8 characters', 'Must contain a number']
}, 'FORM_VALIDATION_ERROR');
Amount Errors (Simplified)
// ✅ NEW: Intuitive amount error with context
const error = amountError(
150, // requested amount
100, // available amount
'insufficient',
'Insufficient funds available'
);
console.log(error.context.requestedAmount); // 150
console.log(error.context.availableAmount); // 100
External Service Errors (Simplified)
// ✅ NEW: Clear service error creation
const error = externalError('API', 'failed', 'Service unavailable', {
transactionId: 'tx_123',
statusCode: 503
});
// ✅ NEW: Quick entity errors
const userError = entityError('user', 'user_123', 'User not found');
Note: The original factory functions (createValidationError
, createAmountError
, etc.) are still available for backward compatibility. Use whichever style you prefer!
function createError<T extends string = string>(options: {
type: T; // Error category (required)
message: string; // Human-readable description (required)
cause?: unknown; // Underlying error (optional)
context?: Record<string, unknown>; // Additional debug data (optional)
timestamp?: number; // When error occurred (optional)
source?: string; // Source location (optional)
}): TryError<T>
// Examples
const validationError = createError({
type: 'ValidationError',
message: 'Email address is required'
});
const detailedError = createError({
type: 'DatabaseError',
message: 'Connection failed',
context: { host: 'localhost', port: 5432, retryAttempt: 3 },
cause: originalError
});
function wrapError<T extends string>(
type: T,
cause: unknown,
message?: string,
context?: Record<string, unknown>
): TryError<T>
// Examples
const wrappedError = wrapError('NetworkError', originalError, 'API call failed');
const detailedWrapper = wrapError(
'ServiceError',
error,
'User service unavailable',
{ service: 'user-api', endpoint: '/users/123' }
);
function fromThrown(
error: unknown,
context?: Record<string, unknown>
): TryError
// Automatically detects error types:
fromThrown(new TypeError('...')) // -> TryError<'TypeError'>
fromThrown(new SyntaxError('...')) // -> TryError<'SyntaxError'>
fromThrown(new Error('...')) // -> TryError<'Error'>
fromThrown('string error') // -> TryError<'StringError'>
fromThrown({ weird: 'object' }) // -> TryError<'UnknownError'>
// With context
const convertedError = fromThrown(caughtError, {
operation: 'parseJson',
input: jsonString
});
// Validation errors
function createValidationError(field: string, rule: string, value?: unknown) {
return createError({
type: 'ValidationError' as const,
message: `Validation failed for field '${field}': ${rule}`,
context: { field, rule, value }
});
}
// Network errors
function createNetworkError(endpoint: string, status?: number) {
return createError({
type: 'NetworkError' as const,
message: status ? `${endpoint} failed with ${status}` : `${endpoint} failed`,
context: { endpoint, status }
});
}
// Entity errors
function createNotFoundError(entityType: string, id: string | number) {
return createError({
type: 'NotFoundError' as const,
message: `${entityType} with id '${id}' not found`,
context: { entityType, id }
});
}
// Business logic errors
function createBusinessRuleError(rule: string, details?: string) {
return createError({
type: 'BusinessRuleError' as const,
message: `Business rule violated: ${rule}`,
context: { rule, details }
});
}
1. Use Descriptive Type Names
// ✅ Good: Descriptive and specific
createError({ type: 'EmailValidationError', message: '...' });
createError({ type: 'DatabaseConnectionTimeout', message: '...' });
createError({ type: 'InsufficientInventoryError', message: '...' });
// ❌ Bad: Generic and unclear
createError({ type: 'Error', message: '...' });
createError({ type: 'ValidationError', message: '...' }); // Too generic
createError({ type: 'Err', message: '...' }); // Abbreviated
2. Include Actionable Context
// ✅ Good: Includes actionable debugging information
createError({
type: 'ApiValidationError',
message: 'Request validation failed',
context: {
endpoint: '/api/users',
method: 'POST',
validationErrors: {
email: 'Invalid format',
age: 'Must be between 18 and 120'
},
requestId: 'req_123456'
}
});
// ❌ Bad: No context for debugging
createError({
type: 'ValidationError',
message: 'Validation failed'
});
3. Create Domain-Specific Factories
// ✅ Create reusable factories for your domain
class UserErrorFactory {
static notFound(userId: string) {
return createError({
type: 'UserNotFoundError' as const,
message: `User with ID ${userId} not found`,
context: { userId, entity: 'User' }
});
}
static alreadyExists(email: string) {
return createError({
type: 'UserAlreadyExistsError' as const,
message: `User with email ${email} already exists`,
context: { email, entity: 'User' }
});
}
static invalidCredentials() {
return createError({
type: 'InvalidCredentialsError' as const,
message: 'Invalid email or password',
context: { entity: 'User', action: 'authenticate' }
});
}
}
// Usage
const user = await tryAsync(() => findUser(userId));
if (!user) {
return UserErrorFactory.notFound(userId);
}
4. Type-Safe Factory Functions
// ✅ Use const assertions for literal types
function createApiError(status: number, message: string) {
const type = status >= 500
? 'ServerError' as const
: status >= 400
? 'ClientError' as const
: 'ApiError' as const;
return createError({
type,
message,
context: { status, category: status >= 500 ? 'server' : 'client' }
});
}
// ✅ Use union types for predefined error categories
type ValidationErrorType =
| 'RequiredFieldError'
| 'FormatValidationError'
| 'RangeValidationError'
| 'CustomValidationError';
function createValidationError(
type: ValidationErrorType,
field: string,
message: string
) {
return createError({
type,
message: `${type}: ${message}`,
context: { field, type, message }
});
}
// Usage
const apiError = createApiError(404, 'Resource not found');
const validationError = createValidationError('email', 'Invalid format', 'The email is not valid');
Why Context is Typed as `unknown`
Error context is intentionally typed as `unknown` for type safety. Since errors can originate from anywhere in your application with different context structures, TypeScript can't guarantee the shape of the context data.
Design Decision: This forces you to validate context before accessing properties, preventing runtime errors from unexpected context structures.
Type-Safe Context Access Patterns
// Define your expected context structure
interface ValidationContext {
field: string;
value: unknown;
rule: string;
}
// Create a type guard
function isValidationContext(context: unknown): context is ValidationContext {
return typeof context === 'object' &&
context !== null &&
'field' in context &&
'rule' in context &&
typeof (context as any).field === 'string';
}
// Safe usage
const result = trySync(() => validateEmail(email));
if (isTryError(result) && isValidationContext(result.context)) {
// ✅ Now context is properly typed
console.log(`Field ${result.context.field} failed rule: ${result.context.rule}`);
}
// Quick check with optional properties
function handleValidationError(error: TryError) {
const context = error.context as { field?: string; rule?: string } | undefined;
if (context?.field && context?.rule) {
return `Validation failed for ${context.field}: ${context.rule}`;
}
return `Validation failed: ${error.message}`;
}
// Reusable helper for safe context access
function getContextValue<T>(
error: TryError,
key: string,
defaultValue: T
): T {
if (typeof error.context === 'object' &&
error.context !== null &&
key in error.context) {
return (error.context as any)[key] ?? defaultValue;
}
return defaultValue;
}
// Usage
const field = getContextValue(error, 'field', 'unknown');
const status = getContextValue(error, 'status', 500);
Best Practices for Context Design
✅ Do
- • Use consistent context structure within domains
- • Document your context interfaces
- • Include debug-relevant information
- • Create type guards for validation
- • Provide fallback values
❌ Don't
- • Access context properties directly
- • Assume context structure without validation
- • Include sensitive data in context
- • Create overly complex context structures
- • Ignore context validation errors
Domain-Specific Context Patterns
// Define context interfaces for your domain
export interface ApiErrorContext {
endpoint: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
status?: number;
requestId?: string;
}
export interface ValidationErrorContext {
field: string;
value: unknown;
rule: string;
constraints?: Record<string, unknown>;
}
export interface BusinessErrorContext {
entity: string;
operation: string;
businessRule: string;
metadata?: Record<string, unknown>;
}
// Create factory functions with typed context
export function createApiError(
message: string,
context: ApiErrorContext
): TryError<'ApiError'> {
return createError({
type: 'ApiError' as const,
message,
context
});
}
// Type guards for each context type
export function isApiErrorContext(context: unknown): context is ApiErrorContext {
return typeof context === 'object' &&
context !== null &&
'endpoint' in context &&
'method' in context;
}
// Usage
const error = createApiError('Failed to fetch user', {
endpoint: '/api/users/123',
method: 'GET',
status: 404,
requestId: 'req_abc123'
});
if (isTryError(error) && isApiErrorContext(error.context)) {
// ✅ Fully typed access to context
console.log(`${error.context.method} ${error.context.endpoint} failed`);
}