@atmaca/errors
v0.1.0
Published
Atmaca Errors Package
Readme
@atmaca/errors
Dual-Protocol Error Classes for REST and JSON-RPC Services
The @atmaca/errors package provides a comprehensive set of error classes designed for modern microservices architectures. Each error includes both REST HTTP status codes and JSON-RPC 2.0 error codes, enabling seamless error translation across protocols.
Features
- Dual-Protocol Support: Every error includes both REST and JSON-RPC codes
- Rich Context: Metadata support for detailed error information
- Machine-Readable: Structured reason codes for programmatic handling
- Human-Friendly: Clear, descriptive error messages
- Framework Agnostic: Works with any Node.js framework
- Zero Dependencies: Lightweight and secure
Installation
npm install @atmaca/errorsQuick Start
import { NotFoundError, ValidationError } from '@atmaca/errors'
// Throw a not found error
const user = await db.users.findById(userId)
if (!user) {
throw new NotFoundError('User', { userId })
}
// Throw a validation error
if (age < 18) {
throw new ValidationError('age', age, 'must be 18 or older')
}Base Error Classes
RuntimeError
The base class for all runtime errors in Atmaca applications.
class RuntimeError extends Error {
name // Error class name (e.g., "NotFoundError")
reason // Machine-readable reason code (e.g., "user_not_found")
message // Human-readable error message
restCode // HTTP status code (e.g., 404)
jsonRpcCode // JSON-RPC 2.0 error code (e.g., -32002)
metadata // Additional context data
}Example:
import { NotFoundError } from '@atmaca/errors'
const error = new NotFoundError('User', { userId: 123 })
console.log(error.name) // "NotFoundError"
console.log(error.reason) // "user_not_found"
console.log(error.message) // "User not found"
console.log(error.restCode) // 404
console.log(error.jsonRpcCode) // -32002
console.log(error.metadata) // { userId: 123 }InitializationError
Used for errors that occur during application initialization.
class InitializationError extends Error {
name // "InitializationError"
reason // Machine-readable reason code
message // Human-readable error message
metadata // Additional context data
}Error Reference
JSON-RPC 2.0 Specific Errors
These errors align with the JSON-RPC 2.0 Specification:
ParseError
Invalid JSON received by the server.
import { ParseError } from '@atmaca/errors'
throw new ParseError('Unexpected token at position 42')
// REST: 400, JSON-RPC: -32700InvalidRequestError
The JSON sent is not a valid Request object.
import { InvalidRequestError } from '@atmaca/errors'
throw new InvalidRequestError('Missing "method" field')
// REST: 400, JSON-RPC: -32600InvalidParamsError
Invalid method parameters.
import { InvalidParamsError } from '@atmaca/errors'
throw new InvalidParamsError('Parameter "userId" must be a number')
// REST: 400, JSON-RPC: -32602InternalError
Internal JSON-RPC error.
import { InternalError } from '@atmaca/errors'
throw new InternalError('database connection', originalError)
// REST: 500, JSON-RPC: -32603REST-Compliant Errors
Client Errors (4xx)
ValidationError
Input validation failed.
import { ValidationError } from '@atmaca/errors'
throw new ValidationError('email', 'invalid-email', 'must be a valid email address')
// REST: 400, JSON-RPC: -32001
// Properties
error.field // 'email'
error.constraint // 'must be a valid email address'NotFoundError
Resource not found.
import { NotFoundError } from '@atmaca/errors'
throw new NotFoundError('User', { userId: 123 })
// REST: 404, JSON-RPC: -32002
// Properties
error.entity // 'User'
error.reason // 'user_not_found'
error.metadata // { userId: 123 }NotAuthorizedError
Authentication required.
import { NotAuthorizedError } from '@atmaca/errors'
throw new NotAuthorizedError('User', 'token expired', { tokenId: 'abc123' })
// REST: 401, JSON-RPC: -32005ForbiddenError
Action forbidden for user.
import { ForbiddenError } from '@atmaca/errors'
throw new ForbiddenError('User', 'delete account', { userId: 123 })
// REST: 403, JSON-RPC: -32004
// Properties
error.entity // 'User'
error.action // 'delete account'ConflictError
Resource conflict (e.g., duplicate entry).
import { ConflictError } from '@atmaca/errors'
throw new ConflictError('User', 'email already exists', { email: '[email protected]' })
// REST: 409, JSON-RPC: -32003
// Properties
error.entity // 'User'
error.conflictReason // 'email already exists'PreconditionFailedError
Precondition not met.
import { PreconditionFailedError } from '@atmaca/errors'
throw new PreconditionFailedError('If-Match header must match current ETag')
// REST: 412, JSON-RPC: -32007
// Properties
error.condition // 'If-Match header must match current ETag'PayloadTooLargeError
Request body too large.
import { PayloadTooLargeError } from '@atmaca/errors'
throw new PayloadTooLargeError(5242880, 1048576) // 5MB vs 1MB limit
// REST: 413, JSON-RPC: -32009
// Properties
error.size // 5242880
error.maxSize // 1048576UnsupportedMediaTypeError
Content type not supported.
import { UnsupportedMediaTypeError } from '@atmaca/errors'
throw new UnsupportedMediaTypeError('text/xml', ['application/json', 'application/x-www-form-urlencoded'])
// REST: 415, JSON-RPC: -32008
// Properties
error.providedType // 'text/xml'
error.supportedTypes // ['application/json', 'application/x-www-form-urlencoded']UnprocessableEntityError
Semantic validation error.
import { UnprocessableEntityError } from '@atmaca/errors'
throw new UnprocessableEntityError('invalid_date_range', 'Start date must be before end date')
// REST: 422, JSON-RPC: -32006RateLimitExceededError
Too many requests.
import { RateLimitExceededError } from '@atmaca/errors'
throw new RateLimitExceededError(100, 60000, 30) // 100 requests per minute, retry after 30s
// REST: 429, JSON-RPC: -32010
// Properties
error.limit // 100
error.windowMs // 60000
error.retryAfter // 30ResourceExhaustedError
Resource limit reached.
import { ResourceExhaustedError } from '@atmaca/errors'
throw new ResourceExhaustedError('database connections', 100, { current: 100 })
// REST: 429, JSON-RPC: -32011
// Properties
error.resource // 'database connections'
error.limit // 100BusinessRuleError
Business rule violation.
import { BusinessRuleError } from '@atmaca/errors'
throw new BusinessRuleError('minimum_order_value', 'Order total must be at least $10', { total: 5.99 })
// REST: 409, JSON-RPC: -32017
// Properties
error.rule // 'minimum_order_value'
error.description // 'Order total must be at least $10'GoneError
Resource permanently removed.
import { GoneError } from '@atmaca/errors'
throw new GoneError('API v1 endpoint', { deprecatedSince: '2024-01-01' })
// REST: 410, JSON-RPC: -32019
// Properties
error.resource // 'API v1 endpoint'Server Errors (5xx)
FunctionExecutionError
Handler execution failed.
import { FunctionExecutionError } from '@atmaca/errors'
throw new FunctionExecutionError('getUser', 'handler', originalError, { userId: 123 })
// REST: 500, JSON-RPC: -32015
// Properties
error.fnName // 'getUser'
error.phase // 'handler'
error.originalError // originalError objectBadGatewayError
Upstream service error.
import { BadGatewayError } from '@atmaca/errors'
throw new BadGatewayError('payment-service', upstreamError, { requestId: 'req-123' })
// REST: 502, JSON-RPC: -32012
// Properties
error.upstream // 'payment-service'
error.upstreamError // upstreamError objectServiceUnavailableError
Service temporarily unavailable.
import { ServiceUnavailableError } from '@atmaca/errors'
throw new ServiceUnavailableError('database', 60) // retry after 60 seconds
// REST: 503, JSON-RPC: -32013
// Properties
error.service // 'database'
error.retryAfter // 60DependencyError
Required dependency unavailable.
import { DependencyError } from '@atmaca/errors'
throw new DependencyError('processPayment', 'stripe-api', { reason: 'connection timeout' })
// REST: 503, JSON-RPC: -32014
// Properties
error.fnName // 'processPayment'
error.dependency // 'stripe-api'NotImplementedError
Feature not yet implemented.
import { NotImplementedError } from '@atmaca/errors'
throw new NotImplementedError('bulk export', { requestedBy: 'user-123' })
// REST: 501, JSON-RPC: -32016
// Properties
error.feature // 'bulk export'TimeoutError
Operation timed out.
import { TimeoutError } from '@atmaca/errors'
throw new TimeoutError('database query', 30000, { query: 'SELECT * FROM users' })
// REST: 504, JSON-RPC: -32018
// Properties
error.operation // 'database query'
error.timeoutMs // 30000Special Errors
MethodNotFoundError
Method/function does not exist (extends NotFoundError).
import { MethodNotFoundError } from '@atmaca/errors'
throw new MethodNotFoundError('nonExistentFunction')
// REST: 404, JSON-RPC: -32601TextErrorResponse
Generic error response with custom status code.
import { TextErrorResponse } from '@atmaca/errors'
throw new TextErrorResponse('Something went wrong', 500, originalError)
// REST: 500 (configurable)
// Properties
error.name // 'TextErrorResponse'
error.restCode // 500 (or custom code)Complete Error Reference Table
| Error Class | REST Code | JSON-RPC Code | Description |
|-------------|-----------|---------------|-------------|
| JSON-RPC Specific ||||
| ParseError | 400 | -32700 | Invalid JSON received by server |
| InvalidRequestError | 400 | -32600 | Invalid JSON-RPC Request object |
| MethodNotFoundError | 404 | -32601 | Method/function does not exist |
| InvalidParamsError | 400 | -32602 | Invalid method parameters |
| InternalError | 500 | -32603 | Internal JSON-RPC error |
| Client Errors (4xx) ||||
| ValidationError | 400 | -32001 | Input validation failed |
| NotFoundError | 404 | -32002 | Resource not found |
| NotAuthorizedError | 401 | -32005 | Authentication required |
| ForbiddenError | 403 | -32004 | Action forbidden for user |
| ConflictError | 409 | -32003 | Resource conflict (e.g., duplicate) |
| PreconditionFailedError | 412 | -32007 | Precondition not met |
| PayloadTooLargeError | 413 | -32009 | Request body too large |
| UnsupportedMediaTypeError | 415 | -32008 | Content type not supported |
| UnprocessableEntityError | 422 | -32006 | Semantic validation error |
| RateLimitExceededError | 429 | -32010 | Too many requests |
| ResourceExhaustedError | 429 | -32011 | Resource limit reached |
| BusinessRuleError | 409 | -32017 | Business rule violation |
| GoneError | 410 | -32019 | Resource permanently removed |
| Server Errors (5xx) ||||
| FunctionExecutionError | 500 | -32015 | Handler execution failed |
| InternalError | 500 | -32603 | Internal error |
| NotImplementedError | 501 | -32016 | Feature not yet implemented |
| BadGatewayError | 502 | -32012 | Upstream service error |
| ServiceUnavailableError | 503 | -32013 | Service temporarily unavailable |
| DependencyError | 503 | -32014 | Required dependency unavailable |
| TimeoutError | 504 | -32018 | Operation timed out |
Usage Examples
REST API Error Handling
import express from 'express'
import { NotFoundError, ValidationError, NotAuthorizedError } from '@atmaca/errors'
const app = express()
app.get('/users/{id}', async (req, res) => {
try {
const user = await db.users.findById(req.params.id)
if (!user) {
throw new NotFoundError('User', { userId: req.params.id })
}
if (!req.user || req.user.id !== user.id) {
throw new NotAuthorizedError('User', 'can only view own profile')
}
res.json(user)
} catch (error) {
// Use the REST code from the error
res.status(error.restCode || 500).json({
error: {
name: error.name,
reason: error.reason,
message: error.message,
metadata: error.metadata
}
})
}
})JSON-RPC Service Error Handling
import {
ParseError,
InvalidRequestError,
MethodNotFoundError,
InvalidParamsError
} from '@atmaca/errors'
async function handleJsonRpc(request) {
try {
// Parse JSON
let parsed
try {
parsed = JSON.parse(request.body)
} catch (e) {
throw new ParseError('Invalid JSON format')
}
// Validate request structure
if (!parsed.jsonrpc || !parsed.method) {
throw new InvalidRequestError('Missing required fields')
}
// Check method exists
if (!methods[parsed.method]) {
throw new MethodNotFoundError(parsed.method)
}
// Execute method
const result = await methods[parsed.method](parsed.params)
return {
jsonrpc: '2.0',
result,
id: parsed.id
}
} catch (error) {
// Use the JSON-RPC code from the error
return {
jsonrpc: '2.0',
error: {
code: error.jsonRpcCode || -32603,
message: error.message,
data: error.metadata
},
id: parsed?.id || null
}
}
}Best Practices
1. Always Include Metadata
// Good - includes context
throw new NotFoundError('User', { userId: 123, requestId: 'req-456' })
// Avoid - missing context
throw new NotFoundError('User')2. Use Specific Error Classes
// Good - specific error
throw new ConflictError('User', 'email already exists', { email: '[email protected]' })
// Avoid - generic error
throw new Error('Conflict: email already exists')3. Preserve Original Errors
// Good - includes original error
try {
await upstream.call()
} catch (err) {
throw new BadGatewayError('payment-service', err)
}
// Avoid - loses original context
try {
await upstream.call()
} catch (err) {
throw new BadGatewayError('payment-service')
}4. Use Machine-Readable Reason Codes
// The reason property is automatically generated for most errors
const error = new NotFoundError('User')
console.log(error.reason) // 'user_not_found'
const error2 = new ConflictError('Email Address', 'already in use')
console.log(error2.reason) // 'email_address_conflict'Protocol Bridging
Use errors seamlessly across different protocols:
import { NotFoundError } from '@atmaca/errors'
const error = new NotFoundError('Resource', { id: 123 })
// REST response
res.status(error.restCode).json({
error: {
message: error.message,
reason: error.reason,
metadata: error.metadata
}
})
// JSON-RPC response
res.json({
jsonrpc: '2.0',
error: {
code: error.jsonRpcCode,
message: error.message,
data: error.metadata
},
id: request.id
})
// GraphQL response
throw new GraphQLError(error.message, {
extensions: {
code: error.reason.toUpperCase(),
http: { status: error.restCode },
metadata: error.metadata
}
})TypeScript Support
TypeScript declarations are included:
import { NotFoundError, ValidationError, RuntimeError } from '@atmaca/errors'
function handleError(error: RuntimeError): void {
console.log(error.name) // string
console.log(error.reason) // string
console.log(error.message) // string
console.log(error.restCode) // number
console.log(error.jsonRpcCode) // number
console.log(error.metadata) // any
}License
MIT License
