Skip to main content

Error Handling

NeuroLink server adapters provide a comprehensive error handling system with typed error classes, automatic recovery strategies, and structured error responses. This guide covers the complete error hierarchy and how to handle errors effectively.


Error Architecture Overview​

The server adapter error system is built around:

  1. Typed Error Classes - 23 specialized error classes extending ServerAdapterError
  2. Error Categories - 9 categories for logical grouping
  3. Severity Levels - 4 levels for prioritization
  4. Recovery Strategies - Automatic retry and backoff configurations
  5. HTTP Status Mapping - Consistent HTTP status code mapping
ServerAdapterError (base class)
├── ConfigurationError
├── RouteConflictError
├── RouteNotFoundError
├── ValidationError
├── AuthenticationError
├── InvalidAuthenticationError
├── AuthorizationError
├── RateLimitError
├── TimeoutError
├── HandlerError
├── StreamingError
├── StreamAbortedError
├── WebSocketError
├── WebSocketConnectionError
├── ServerStartError
├── ServerStopError
├── AlreadyRunningError
├── NotRunningError
├── ShutdownTimeoutError
├── DrainTimeoutError
├── InvalidLifecycleStateError
└── MissingDependencyError

Error Categories​

Errors are grouped into 9 categories that determine handling behavior and recovery strategies:

CategoryDescriptionRecovery Strategy
CONFIGConfiguration and setup errorsFail immediately
VALIDATIONInput validation and schema errorsFail immediately
EXECUTIONRuntime handler and processing errorsRetry (3 attempts)
EXTERNALExternal service and dependency errorsExponential backoff
RATE_LIMITRate limiting exceededExponential backoff
AUTHENTICATIONMissing or invalid authenticationFail immediately
AUTHORIZATIONPermission and access denied errorsFail immediately
STREAMINGStreaming and SSE errorsRetry (2 attempts)
WEBSOCKETWebSocket connection and message errorsExponential backoff

Severity Levels​

Each error has a severity level for logging and alerting:

SeverityDescriptionExample Errors
LOWMinor issues, typically user errorsRouteNotFoundError, StreamAbortedError
MEDIUMModerate issues that may need attentionTimeoutError, AuthenticationError
HIGHSerious issues that should be investigatedHandlerError, ConfigurationError
CRITICALSystem-level failures requiring immediate actionServerStartError, MissingDependencyError

Error Classes Reference​

Base Class: ServerAdapterError​

All server adapter errors extend this base class:

import { ServerAdapterError } from "@juspay/neurolink";

class ServerAdapterError extends Error {
readonly code: string; // Unique error code
readonly category: string; // Error category
readonly severity: string; // Severity level
readonly retryable: boolean; // Whether retry is recommended
readonly retryAfterMs?: number; // Suggested retry delay
readonly requestId?: string; // Request identifier for tracing
readonly path?: string; // Request path
readonly method?: string; // HTTP method
readonly details?: object; // Additional error details
readonly cause?: Error; // Original error if wrapped

toJSON(): object; // Serialize for API response
getHttpStatus(): number; // Get appropriate HTTP status
}

Configuration Errors​

ConfigurationError​

Thrown when server configuration is invalid.

import { ConfigurationError } from "@juspay/neurolink";

throw new ConfigurationError(
"Invalid port number: must be between 1 and 65535",
{ port: 99999, field: "port" },
);
PropertyValue
CodeSERVER_ADAPTER_INVALID_CONFIG
CategoryCONFIG
SeverityHIGH
HTTP Status400
RetryableNo

MissingDependencyError​

Thrown when a required framework dependency is not installed.

import { MissingDependencyError } from "@juspay/neurolink";

throw new MissingDependencyError("express", "Express", "npm install express");
PropertyValue
CodeSERVER_ADAPTER_MISSING_DEPENDENCY
CategoryCONFIG
SeverityCRITICAL
HTTP Status500
RetryableNo

Route Errors​

RouteConflictError​

Thrown when registering a route that conflicts with an existing route.

import { RouteConflictError } from "@juspay/neurolink";

throw new RouteConflictError("/api/users/:id", "GET", "/api/users/:userId");
PropertyValue
CodeSERVER_ADAPTER_ROUTE_CONFLICT
CategoryCONFIG
SeverityHIGH
HTTP Status500
RetryableNo

RouteNotFoundError​

Thrown when a requested route does not exist.

import { RouteNotFoundError } from "@juspay/neurolink";

throw new RouteNotFoundError("/api/unknown", "GET", "req-123");
PropertyValue
CodeSERVER_ADAPTER_ROUTE_NOT_FOUND
CategoryVALIDATION
SeverityLOW
HTTP Status404
RetryableNo

Validation Errors​

ValidationError​

Thrown when request validation fails.

import { ValidationError } from "@juspay/neurolink";

throw new ValidationError(
[
{ field: "email", message: "Invalid email format", value: "not-an-email" },
{ field: "age", message: "Must be a positive number", value: -5 },
],
"req-123",
);
PropertyValue
CodeSERVER_ADAPTER_VALIDATION_ERROR
CategoryVALIDATION
SeverityLOW
HTTP Status400
RetryableNo

Authentication & Authorization Errors​

AuthenticationError​

Thrown when authentication is required but not provided.

import { AuthenticationError } from "@juspay/neurolink";

throw new AuthenticationError("Bearer token required", "req-123");
PropertyValue
CodeSERVER_ADAPTER_AUTH_REQUIRED
CategoryAUTHENTICATION
SeverityMEDIUM
HTTP Status401
RetryableNo

InvalidAuthenticationError​

Thrown when provided authentication credentials are invalid.

import { InvalidAuthenticationError } from "@juspay/neurolink";

throw new InvalidAuthenticationError("Token expired", "req-123");
PropertyValue
CodeSERVER_ADAPTER_AUTH_INVALID
CategoryAUTHENTICATION
SeverityMEDIUM
HTTP Status401
RetryableNo

AuthorizationError​

Thrown when the authenticated user lacks required permissions.

import { AuthorizationError } from "@juspay/neurolink";

throw new AuthorizationError(
"Insufficient permissions to access this resource",
"req-123",
["admin", "moderator"],
);
PropertyValue
CodeSERVER_ADAPTER_FORBIDDEN
CategoryAUTHORIZATION
SeverityMEDIUM
HTTP Status403
RetryableNo

Rate Limiting Errors​

RateLimitError​

Thrown when request rate limits are exceeded.

import { RateLimitError } from "@juspay/neurolink";

throw new RateLimitError(
60000, // retry after 60 seconds
"Rate limit exceeded: 100 requests per minute",
"req-123",
);
PropertyValue
CodeSERVER_ADAPTER_RATE_LIMIT_EXCEEDED
CategoryRATE_LIMIT
SeverityMEDIUM
HTTP Status429
RetryableYes

Execution Errors​

TimeoutError​

Thrown when an operation exceeds its timeout.

import { TimeoutError } from "@juspay/neurolink";

throw new TimeoutError(30000, "AI generation", "req-123");
PropertyValue
CodeSERVER_ADAPTER_TIMEOUT
CategoryEXECUTION
SeverityMEDIUM
HTTP Status408
RetryableYes

HandlerError​

Thrown when a route handler fails during execution.

import { HandlerError } from "@juspay/neurolink";

throw new HandlerError(
"Failed to process request",
originalError,
"req-123",
"/api/agent/execute",
"POST",
);
PropertyValue
CodeSERVER_ADAPTER_HANDLER_ERROR
CategoryEXECUTION
SeverityHIGH
HTTP Status500
RetryableNo

Streaming Errors​

StreamingError​

Thrown when a streaming operation fails.

import { StreamingError } from "@juspay/neurolink";

throw new StreamingError("Stream write failed", originalError, "req-123");
PropertyValue
CodeSERVER_ADAPTER_STREAM_ERROR
CategorySTREAMING
SeverityMEDIUM
HTTP Status500
RetryableNo

StreamAbortedError​

Thrown when a client aborts a streaming connection.

import { StreamAbortedError } from "@juspay/neurolink";

throw new StreamAbortedError("Client disconnected", "req-123");
PropertyValue
CodeSERVER_ADAPTER_STREAM_ABORTED
CategorySTREAMING
SeverityLOW
HTTP Status499
RetryableNo

WebSocket Errors​

WebSocketError​

General WebSocket operation errors.

import { WebSocketError } from "@juspay/neurolink";

throw new WebSocketError("Message send failed", originalError, "ws-conn-123");
PropertyValue
CodeSERVER_ADAPTER_WEBSOCKET_ERROR
CategoryWEBSOCKET
SeverityMEDIUM
HTTP Status500
RetryableYes

WebSocketConnectionError​

Thrown when WebSocket connection establishment fails.

import { WebSocketConnectionError } from "@juspay/neurolink";

throw new WebSocketConnectionError("Handshake failed", originalError);
PropertyValue
CodeSERVER_ADAPTER_WEBSOCKET_CONNECTION_FAILED
CategoryWEBSOCKET
SeverityHIGH
HTTP Status500
RetryableYes

Server Lifecycle Errors​

ServerStartError​

Thrown when the server fails to start.

import { ServerStartError } from "@juspay/neurolink";

throw new ServerStartError(
"Port already in use",
originalError,
3000,
"0.0.0.0",
);
PropertyValue
CodeSERVER_ADAPTER_START_FAILED
CategoryCONFIG
SeverityCRITICAL
HTTP Status500
RetryableYes

ServerStopError​

Thrown when the server fails to stop cleanly.

import { ServerStopError } from "@juspay/neurolink";

throw new ServerStopError("Failed to close connections", originalError);
PropertyValue
CodeSERVER_ADAPTER_STOP_FAILED
CategoryEXECUTION
SeverityHIGH
HTTP Status500
RetryableNo

AlreadyRunningError​

Thrown when attempting to start an already running server.

import { AlreadyRunningError } from "@juspay/neurolink";

throw new AlreadyRunningError(3000, "0.0.0.0");
PropertyValue
CodeSERVER_ADAPTER_ALREADY_RUNNING
CategoryCONFIG
SeverityLOW
HTTP Status500
RetryableNo

NotRunningError​

Thrown when attempting to stop a server that is not running.

import { NotRunningError } from "@juspay/neurolink";

throw new NotRunningError();
PropertyValue
CodeSERVER_ADAPTER_NOT_RUNNING
CategoryCONFIG
SeverityLOW
HTTP Status500
RetryableNo

ShutdownTimeoutError​

Thrown when graceful shutdown exceeds the configured timeout.

import { ShutdownTimeoutError } from "@juspay/neurolink";

throw new ShutdownTimeoutError(30000, 5); // 30s timeout, 5 remaining connections
PropertyValue
CodeSERVER_ADAPTER_STOP_FAILED
CategoryEXECUTION
SeverityHIGH
HTTP Status500
RetryableNo

DrainTimeoutError​

Thrown when connection draining exceeds the configured timeout.

import { DrainTimeoutError } from "@juspay/neurolink";

throw new DrainTimeoutError(10000, 3); // 10s timeout, 3 remaining connections
PropertyValue
CodeSERVER_ADAPTER_STOP_FAILED
CategoryEXECUTION
SeverityMEDIUM
HTTP Status500
RetryableNo

InvalidLifecycleStateError​

Thrown when an operation is attempted in an invalid server state.

import { InvalidLifecycleStateError } from "@juspay/neurolink";

throw new InvalidLifecycleStateError("start", "stopping", [
"stopped",
"initialized",
]);
PropertyValue
CodeSERVER_ADAPTER_INVALID_LIFECYCLE_STATE
CategoryCONFIG
SeverityMEDIUM
HTTP Status500
RetryableNo

HTTP Status Code Mapping​

Errors automatically map to appropriate HTTP status codes:

Error CodeHTTP StatusDescription
VALIDATION_ERROR400Bad Request
SCHEMA_ERROR400Bad Request
INVALID_CONFIG400Bad Request
INVALID_ROUTE400Bad Request
AUTH_REQUIRED401Unauthorized
AUTH_INVALID401Unauthorized
FORBIDDEN403Forbidden
ROUTE_NOT_FOUND404Not Found
TIMEOUT408Request Timeout
RATE_LIMIT_EXCEEDED429Too Many Requests
STREAM_ABORTED499Client Closed Request
All other errors500Internal Server Error

Error Response Format​

All errors are serialized to a consistent JSON format:

{
"error": {
"code": "SERVER_ADAPTER_VALIDATION_ERROR",
"message": "Validation failed: Invalid email format, Must be a positive number",
"category": "VALIDATION",
"requestId": "req-abc123",
"details": {
"errors": [
{
"field": "email",
"message": "Invalid email format",
"value": "not-an-email"
},
{ "field": "age", "message": "Must be a positive number", "value": -5 }
]
},
"retryAfter": 60
}
}

Response Fields​

FieldTypeDescription
codestringUnique error code for programmatic handling
messagestringHuman-readable error message
categorystringError category for grouping
requestIdstringRequest ID for tracing (when available)
detailsobjectAdditional context-specific information
retryAfternumberSuggested retry delay in seconds (for retryable errors)

Recovery Strategies​

Each error category has a predefined recovery strategy:

const ErrorRecoveryStrategies = {
CONFIG: {
strategy: "fail",
maxRetries: 0,
baseDelayMs: 0,
},
VALIDATION: {
strategy: "fail",
maxRetries: 0,
baseDelayMs: 0,
},
EXECUTION: {
strategy: "retry",
maxRetries: 3,
baseDelayMs: 1000,
},
EXTERNAL: {
strategy: "exponentialBackoff",
maxRetries: 5,
baseDelayMs: 1000,
},
RATE_LIMIT: {
strategy: "exponentialBackoff",
maxRetries: 3,
baseDelayMs: 5000,
},
AUTHENTICATION: {
strategy: "fail",
maxRetries: 0,
baseDelayMs: 0,
},
AUTHORIZATION: {
strategy: "fail",
maxRetries: 0,
baseDelayMs: 0,
},
STREAMING: {
strategy: "retry",
maxRetries: 2,
baseDelayMs: 500,
},
WEBSOCKET: {
strategy: "exponentialBackoff",
maxRetries: 5,
baseDelayMs: 1000,
},
};

Strategy Types​

StrategyDescription
failFail immediately without retry
retryRetry with fixed delay between attempts
exponentialBackoffRetry with exponentially increasing delays (1s, 2s, 4s, 8s, ...)

Custom Error Handling​

Global Error Handler​

Register a global error handler for custom error processing:

import { createServer, ServerAdapterError, wrapError } from "@juspay/neurolink";

const server = await createServer(neurolink, {
framework: "hono",
config: { port: 3000 },
});

// Register global error handler
server.onError((error, context) => {
// Wrap unknown errors
const serverError =
error instanceof ServerAdapterError
? error
: wrapError(error, context.requestId, context.path, context.method);

// Log based on severity
if (serverError.severity === "CRITICAL") {
alertOps(serverError);
}

if (serverError.severity === "HIGH" || serverError.severity === "CRITICAL") {
logger.error("Server error", {
code: serverError.code,
message: serverError.message,
requestId: serverError.requestId,
path: serverError.path,
stack: serverError.stack,
});
}

// Track metrics
metrics.increment("server.errors", {
code: serverError.code,
category: serverError.category,
severity: serverError.severity,
});

// Return the error (will be serialized to JSON response)
return serverError;
});

Route-Level Error Handling​

Handle errors in specific routes:

server.registerRoute({
method: "POST",
path: "/api/custom",
handler: async (ctx) => {
try {
const result = await processRequest(ctx.body);
return result;
} catch (error) {
// Transform domain errors to server errors
if (error instanceof DomainValidationError) {
throw new ValidationError(
[{ field: error.field, message: error.message }],
ctx.requestId,
);
}

if (error instanceof ExternalServiceError) {
throw new HandlerError(
"External service unavailable",
error,
ctx.requestId,
ctx.path,
ctx.method,
);
}

// Re-throw server adapter errors
throw error;
}
},
});

Using wrapError Helper​

The wrapError utility converts unknown errors to ServerAdapterError:

import { wrapError, ServerAdapterError } from "@juspay/neurolink";

function handleError(error: unknown, requestId: string): ServerAdapterError {
// Already a ServerAdapterError - return as-is
if (error instanceof ServerAdapterError) {
return error;
}

// Wrap as HandlerError
return wrapError(error, requestId, "/api/endpoint", "POST");
}

Implementing Retry Logic​

Use recovery strategies for automatic retry:

import { ErrorRecoveryStrategies, ServerAdapterError } from "@juspay/neurolink";

async function executeWithRetry<T>(
operation: () => Promise<T>,
category: string,
): Promise<T> {
const strategy = ErrorRecoveryStrategies[category];
let lastError: Error | undefined;

for (let attempt = 0; attempt <= strategy.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;

// Don't retry if strategy is "fail"
if (strategy.strategy === "fail") {
throw error;
}

// Check if error is retryable
if (error instanceof ServerAdapterError && !error.retryable) {
throw error;
}

// Calculate delay
const delay =
strategy.strategy === "exponentialBackoff"
? strategy.baseDelayMs * Math.pow(2, attempt)
: strategy.baseDelayMs;

// Use retryAfterMs if provided
const actualDelay =
error instanceof ServerAdapterError && error.retryAfterMs
? error.retryAfterMs
: delay;

if (attempt < strategy.maxRetries) {
await sleep(actualDelay);
}
}
}

throw lastError;
}

Error Codes Reference​

Configuration Errors​

CodeDescription
SERVER_ADAPTER_INVALID_CONFIGInvalid server configuration
SERVER_ADAPTER_MISSING_DEPENDENCYRequired framework dependency not found
SERVER_ADAPTER_FRAMEWORK_INIT_FAILEDFramework initialization failed

Route Errors​

CodeDescription
SERVER_ADAPTER_ROUTE_NOT_FOUNDRequested route does not exist
SERVER_ADAPTER_ROUTE_CONFLICTRoute conflicts with existing route
SERVER_ADAPTER_INVALID_ROUTEInvalid route definition

Execution Errors​

CodeDescription
SERVER_ADAPTER_HANDLER_ERRORRoute handler execution failed
SERVER_ADAPTER_TIMEOUTOperation timed out
SERVER_ADAPTER_MIDDLEWARE_ERRORMiddleware execution failed

Authentication/Authorization Errors​

CodeDescription
SERVER_ADAPTER_AUTH_REQUIREDAuthentication required but not provided
SERVER_ADAPTER_AUTH_INVALIDInvalid authentication credentials
SERVER_ADAPTER_FORBIDDENAccess denied (insufficient permissions)

Rate Limiting Errors​

CodeDescription
SERVER_ADAPTER_RATE_LIMIT_EXCEEDEDRequest rate limit exceeded

Streaming Errors​

CodeDescription
SERVER_ADAPTER_STREAM_ERRORStreaming operation failed
SERVER_ADAPTER_STREAM_ABORTEDClient aborted the stream

WebSocket Errors​

CodeDescription
SERVER_ADAPTER_WEBSOCKET_ERRORWebSocket operation failed
SERVER_ADAPTER_WEBSOCKET_CONNECTION_FAILEDWebSocket connection failed

Validation Errors​

CodeDescription
SERVER_ADAPTER_VALIDATION_ERRORRequest validation failed
SERVER_ADAPTER_SCHEMA_ERRORSchema validation failed

Lifecycle Errors​

CodeDescription
SERVER_ADAPTER_START_FAILEDServer failed to start
SERVER_ADAPTER_STOP_FAILEDServer failed to stop
SERVER_ADAPTER_ALREADY_RUNNINGServer is already running
SERVER_ADAPTER_NOT_RUNNINGServer is not running

Best Practices​

1. Use Specific Error Classes​

Throw the most specific error class for your situation:

// Good - specific error with context
throw new ValidationError(
[{ field: "email", message: "Invalid format" }],
requestId,
);

// Avoid - generic error
throw new Error("Validation failed");

2. Include Request Context​

Always include request ID, path, and method when available:

throw new HandlerError(
"Processing failed",
cause,
context.requestId, // For tracing
context.path, // For debugging
context.method, // For debugging
);

3. Provide Actionable Details​

Include details that help diagnose the issue:

throw new ConfigurationError("Invalid rate limit configuration", {
field: "maxRequests",
provided: -100,
expected: "positive integer",
hint: "maxRequests must be greater than 0",
});

4. Respect Retry-After Headers​

When handling RateLimitError, honor the retryAfterMs:

if (error instanceof RateLimitError) {
response.setHeader("Retry-After", Math.ceil(error.retryAfterMs / 1000));
}

5. Log Appropriately by Severity​

switch (error.severity) {
case "CRITICAL":
logger.fatal(error);
alertOps(error);
break;
case "HIGH":
logger.error(error);
break;
case "MEDIUM":
logger.warn(error);
break;
case "LOW":
logger.info(error);
break;
}