Error
Structured domain errors with enrichment, chaining, and Zod validation
The Error system provides rich error objects inspired by Python's exception classes. Errors have structured metadata, support enrichment with notes and cause chaining, and integrate seamlessly with Result.
Why Error?
JavaScript's native Error is limited. It only has a message and stack trace. The Error system adds:
- Structured args - domain-specific error data
- Notes - additional context
- Cause chaining - trace error provenance
- Zod validation - validate error arguments at runtime
- Enrichment methods - add notes, chain causes
Error vs Result
| Aspect | Result | Error |
|---|---|---|
| Purpose | Represent success/failure | Represent domain error |
| Methods | map, flatMap, getOrElse | addNotes, from |
| Chaining | Railway-oriented programming | Error provenance tracking |
| With Zod | No validation | Schema validation |
import { error, err, ok } from '@deessejs/fp';
// Error is a plain error object
const SizeError = error({
name: 'SizeError',
schema: z.object({
current: z.number(),
wanted: z.number(),
}),
});
const domainError = SizeError({ current: 3, wanted: 5 });
domainError.name; // 'SizeError'
domainError.args; // { current: 3, wanted: 5 }
domainError.notes; // []
domainError.cause; // None (until enriched)
// Wrap with err() to get Result methods
const result = err(domainError);
result.ok === false;
result.error === domainError; // reference, not selfCore Concepts
Error Properties
An Error<T> has these properties:
| Property | Type | Description |
|---|---|---|
name | string | Error type identifier |
args | T | Domain-specific error data |
notes | readonly string[] | Additional context |
cause | Maybe<Error> | Original error (chained) |
message | string | Human-readable message |
stack | string | Stack trace |
ErrorBuilder
A function that creates Error<T> instances:
type ErrorBuilder<T = object> = (args?: T) => Error<T>;Accessing Cause
The cause field is Maybe<Error>. Use Maybe methods to access it:
// Get cause name with default
error.cause.map(c => c.name).getOrElse('no cause');
// Check if cause exists
if (error.cause.isSome()) {
console.log(error.cause.value.name);
}
// Chain through nested causes
error.cause
.flatMap(c => c.cause)
.map(c => c.name)
.getOrElse('no cause');Reference
Creating Errors
error(options)
Creates an ErrorBuilder. Schema is optional.
import { z } from 'zod';
import { error } from '@deessejs/fp';
// With schema - validates arguments
const SizeError = error({
name: 'SizeError',
schema: z.object({
current: z.number(),
wanted: z.number(),
}),
});
// Without schema
const SimpleError = error({
name: 'SimpleError',
});Options:
| Option | Type | Description |
|---|---|---|
name | string | Error class name |
schema | ZodSchema<T> | Zod schema for args validation (optional) |
message | (args: T) => string | Custom message function |
Creating Error Instances
Call the ErrorBuilder to create instances:
// With schema - validates arguments
const domainError = SizeError({ current: 3, wanted: 5 });
// Without schema - args are optional
const simpleError = SimpleError(); // No args needed
const alsoWorks = SimpleError({}); // Also validEnrichment
addNotes(...notes)
Add contextual notes to an error:
const e = SizeError({ current: 3, wanted: 5 })
.addNotes('Attempted to process file', 'User: john');
e.notes; // ['Attempted to process file', 'User: john']from(cause)
Chain the cause of an error. Accepts Error, Err<Error>, or Maybe<Error>:
const NetworkError = error({
name: 'NetworkError',
schema: z.object({ host: z.string() }),
});
const networkError = NetworkError({ host: 'api.example.com' });
// From Error object
const e = SizeError({ current: 3, wanted: 5 }).from(networkError);
// From Result containing an Error
const result = err(networkError);
const e2 = SizeError({ current: 3, wanted: 5 }).from(result);
// From Maybe<Error>
const maybeError = some(networkError);
const e3 = SizeError({ current: 3, wanted: 5 }).from(maybeError);Combining Enrichments
const e = SizeError({ current: 3, wanted: 5 })
.addNotes('Processing file: data.json')
.from(networkError)
.addNotes('File processing failed');Validation
When a schema is provided, arguments are automatically validated:
SizeError({ current: 'not a number' });
// Returns Error with name 'SizeErrorValidationError'Custom message function:
const ValidationError = error({
name: 'ValidationError',
schema: z.object({ field: z.string() }),
message: (args) => `Field "${args.field}" is invalid`,
});ErrorGroups
exceptionGroup(errors)
Group multiple errors together:
import { exceptionGroup } from '@deessejs/fp';
const errors = exceptionGroup([
SizeError({ current: 3, wanted: 5 }),
ValidationError({ field: 'email' }),
]);
errors.name; // 'ExceptionGroup'
errors.exceptions; // [SizeError, ValidationError]Guards
isError(value)
Type guard that checks if a value is an Error:
import { isError } from '@deessejs/fp';
isError(SizeError({ current: 3, wanted: 5 })); // true
isError('not an error'); // false
isError(null); // falseisErrorGroup(value)
Type guard that checks if a value is an ErrorGroup:
import { isErrorGroup } from '@deessejs/fp';
isErrorGroup(exceptionGroup([...])); // true
isErrorGroup(SizeError({...})); // falseAssertions
assertIsError(value)
Assertion function that throws if value is not an Error:
import { assertIsError } from '@deessejs/fp';
assertIsError(value);
// Throws TypeError if not an Error
// value is Error here, TypeScript knows the typeassertIsErrorGroup(value)
Assertion function that throws if value is not an ErrorGroup:
import { assertIsErrorGroup } from '@deessejs/fp';
assertIsErrorGroup(value);
// Throws TypeError if not an ErrorGroupUtilities
getErrorMessage(error)
Extract a human-readable message:
import { getErrorMessage } from '@deessejs/fp';
getErrorMessage(SizeError({ current: 3, wanted: 5 }));
// Returns 'SizeError: {"current":3,"wanted":5}'
getErrorMessage(exceptionGroup([...]));
// Returns 'ExceptionGroup: 2 error(s)'flattenErrorGroup(group)
Get all errors from a group (including nested):
import { flattenErrorGroup } from '@deessejs/fp';
flattenErrorGroup(group); // Error[]filterErrorsByName(group, name)
Find errors by name in a group:
import { filterErrorsByName } from '@deessejs/fp';
filterErrorsByName(group, 'SizeError'); // SizeError[]
filterErrorsByName(group, 'NetworkError'); // NetworkError[]raise()
raise(error)
Throws the error and returns never. Use only for unrecoverable programmer errors:
import { raise } from '@deessejs/fp';
// Only for unrecoverable errors - things that indicate a bug
const process = (size: number): Result<Data, Error> => {
if (size > MAX_SIZE) {
raise(SizeError({ current: size, wanted: MAX_SIZE }));
}
return ok({ size });
};When NOT to use raise():
// Bad - breaks the rail for expected failures
const validateBad = (input: string): Result<string, Error> => {
if (!input) raise(EmptyInputError({}));
return ok(input);
};
// Good - error travels through the rail
const validate = (input: string): Result<string, Error> => {
if (!input) return err(EmptyInputError({}));
return ok(input);
};Using with Result
Error objects integrate with Result via err():
import { ok, err } from '@deessejs/fp';
const result = ok(10).flatMap((x) => {
if (x > 5) {
return err(SizeError({ current: x, wanted: 5 }));
}
return ok(x * 2);
});
result.mapErr((e) => console.log(e.name));Type Compatibility
Error<T> is compatible with JavaScript's native Error:
const e = SizeError({ current: 3, wanted: 5 });
// Error is compatible with globalThis.Error
const nativeErrorHandler = (e: Error) => {
console.log(e.message, e.stack);
};
nativeErrorHandler(SizeError({...}));See Also
- Result - For success/failure with error chaining
- Maybe - For optional values (null/undefined)
- Try - For wrapping sync functions that might throw
- AsyncResult - For async operations with error handling