@cogs/errors
v0.2.0
Published
A suite of error classes which help you throw the most appropriate error in any situation
Readme
@cogs/errors
⚡ Error Handling at a Glance
This library provides a structured, Joyent-style approach to error handling — helping teams write predictable, recoverable, and well-typed errors in Node.js and browser environments.
Core Ideas
- Operational vs Programmer Errors Handle operational errors (network issues, bad input, unavailable services) gracefully. Let programmer errors (bugs, logic mistakes) crash fast and surface for debugging.
- Consistent Delivery
Use
throwfor sync code, and Promise rejection, callback, orerrorevents for async code. Never mix both in one API. - Structured Errors
Every class extends
Errorwith meaningfulname,code, and context (likestatusCodeorrelatesToSystems). - Recover or Crash Intentionally Operational errors are known and loggable. Programmer errors should terminate — not silently fail.
Common Behavior
All classes share the same ergonomics:
- Flexible constructors – pass a message, an options object, or both. Extra fields go under
error.data. - Normalized codes – any
codeyou provide is uppercased and stripped of illegal characters (e.g.foo bar→FOO_BAR). - Native
causesupport – chain root causes using{ cause }per Node’sErrorOptions.
try {
await fetchUpstream()
} catch (cause) {
throw new UpstreamServiceError("Partner API failed", {
code: "PARTNER_API_DOWN",
relatesToSystems: ["partner-api"],
cause,
})
}TL;DR
- Distinguish operational vs programmer errors
- Deliver errors consistently
- Use structured, contextual
Errorsubclasses- Log and recover from operational errors
- Let programmer errors crash safely
Error Handling Philosophy
This library follows the Joyent guide on Node.js error handling. It defines a consistent approach to distinguish operational errors (known, expected runtime failures) from programmer errors (bugs in logic), and to deliver and document them predictably.
Core Principles
Operational vs Programmer Errors
- Operational errors are expected problems in normal operation — network failures, missing files, invalid user input, etc.
- Programmer errors are bugs — invalid arguments, undefined variables, failed assertions, etc.
- Handle operational errors gracefully; fix programmer errors and allow them to surface (or crash safely).
Consistent Error Delivery
- Synchronous code: use
throw. - Asynchronous code: reject a Promise, invoke a callback with an error, or emit an
"error"event. - Avoid mixing sync
throwand async error delivery in a single API.
- Synchronous code: use
Structured Error Objects
- Extend the built-in
Errorclass (or subclasses) and include meaningfulname,message, and contextual properties. - Preserve
stacktraces, and wrap underlying causes instead of discarding them.
- Extend the built-in
throw Object.assign(new Error("Database connection failed"), {
name: "DatabaseError",
host,
port,
cause: err,
})- Documentation
- Each function should specify:
- expected arguments and constraints
- return values or results
- possible errors and how they are delivered
- Each function should specify:
OperationalError
The OperationalError class is the foundation for most other error types.
“Operational” here means we understand why the error occurred — it’s part of normal, recoverable runtime behavior rather than a programming bug.
Use
OperationalError(or one of its subclasses) to signal expected failures that your system can handle or report cleanly.
You can construct an operational error with a simple message:
throw new OperationalError("example message");Or use an object form to include a unique code and other context:
throw new OperationalError({
message: "example message",
code: "EXAMPLE_CODE"
});Error codes are normalized to uppercase, alphanumeric, and underscore-delimited.
error.message // "example message"
error.code // "EXAMPLE_CODE"You can also combine message and options:
throw new OperationalError("example message", { code: "EXAMPLE_CODE" });Extra fields are collected under error.data:
const error = new OperationalError({
message: "example message",
code: "EXAMPLE_CODE",
article: "d92acacb-ac53-4505-aa88-eae4b42de994"
});
error.data.article // "d92acacb-ac53-4505-aa88-eae4b42de994"OperationalError.relatesToSystems
Stores a list of systems or services related to the error — typically dependencies that failed or returned unexpected data.
OperationalError.cause
Holds the root cause (another Error), preserving diagnostic context:
try {
await fetch(upstream);
} catch (err) {
throw new OperationalError("Upstream request failed", { cause: err });
}OperationalError.isErrorMarkedAsOperational()
Utility to check whether an error is operational:
OperationalError.isErrorMarkedAsOperational(new OperationalError("x")); // true
OperationalError.isErrorMarkedAsOperational(new Error("x")); // falseHttpError
Extends OperationalError to represent HTTP status errors.
throw new HttpError("example message"); // defaults to 500Or with explicit data:
throw new HttpError({
message: "Resource not found",
statusCode: 404
});You can also create it directly from a status code:
throw new HttpError(404);Properties:
error.message // "Resource not found"
error.statusCode // 404
error.status // 404
error.statusMessage // "Not Found"
error.code // "HTTP_404"Why not use http-errors?
Unlike http-errors, this class extends OperationalError, marking all HTTP errors as known by default.
A code property is also set automatically, simplifying filtering and dashboard metrics.
DataStoreError
Represents an error while accessing a data store such as MongoDB, PostgreSQL, or Redis.
throw new DataStoreError("Could not connect to Redis");or
throw new DataStoreError({
code: "REDIS_CONNECTION_FAILED",
message: "Could not connect to Redis",
});UpstreamServiceError
Extends HttpError to represent upstream or third-party API failures.
throw new UpstreamServiceError("Content could not be fetched");Defaults to HTTP 502 (“Bad Gateway”). You can specify details and related systems:
throw new UpstreamServiceError({
code: "CONTENT_PIPELINE_FAILED",
message: "Upstream service responded with a 503",
statusCode: 503,
relatesToSystems: ["cp-content-pipeline-graphql"]
});UserInputError
Extends HttpError for client input validation errors (defaults to 400 Bad Request).
throw new UserInputError("Invalid email address");Or with structured data:
throw new UserInputError({
code: "REGISTRATION_INFO_INVALID",
message: "Invalid email address"
});BaseError
The abstract root class for all errors in this library.
It behaves like OperationalError but is explicitly non-operational — intended for use inside low-level or library code where operational semantics don’t apply.
const error = new BaseError("Internal failure");
error.isOperational === false;Supports the same properties as OperationalError:
throw new BaseError({
message: "example message",
code: "EXAMPLE_CODE",
cause: new TypeError("example cause")
});Summary
- Distinguish operational vs programmer errors
- Deliver errors consistently (throw vs async rejection)
- Use structured
Errorobjects with clearname,code, and context- Crash on programmer errors, recover from operational ones
- Document error behavior in every API
