@orchard9ai/error-handling
Version:
Federated error handling package with go-core-http-toolkit format support and logging integration
204 lines (173 loc) • 5.63 kB
text/typescript
import { ErrorHandler } from '../core/ErrorHandler.js';
import { parseHttpError, parseFetchError, parseUnknownError } from '../utils/errorParsers.js';
import { createUserFriendlyMessage, createRetryAction, createSupportAction } from '../utils/displayHelpers.js';
import type { ApiError, ErrorContext, DisplayError, ErrorHandlerConfig } from '../types/index.js';
/**
* Specialized error handler for API requests
*/
export class ApiErrorHandler extends ErrorHandler {
constructor(config: Partial<ErrorHandlerConfig> = {}) {
super({
loggerName: 'api-error-handler',
...config
});
}
/**
* Handle fetch API errors
*/
async handleFetchError(
response: Response,
context: ErrorContext = {}
): Promise<DisplayError> {
let responseText: string;
try {
responseText = await response.text();
} catch {
responseText = '';
}
const apiError = parseHttpError(response, responseText);
// Add HTTP-specific context
const enrichedContext: ErrorContext = {
...context,
action: 'http_request',
metadata: {
url: response.url,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
}
};
return this.handleApiError(apiError, enrichedContext);
}
/**
* Handle network errors (fetch failures)
*/
override handleNetworkError(error: Error, context: ErrorContext = {}): DisplayError {
const apiError = parseFetchError(error);
const enrichedContext: ErrorContext = {
...context,
action: 'network_request',
metadata: {
errorName: error.name,
errorMessage: error.message
}
};
return this.handleApiError(apiError, enrichedContext);
}
/**
* Handle unknown errors
*/
handleUnknownError(error: unknown, context: ErrorContext = {}): DisplayError {
const apiError = parseUnknownError(error);
const enrichedContext: ErrorContext = {
...context,
action: 'unknown_error',
metadata: {
errorType: typeof error
}
};
return this.handleApiError(apiError, enrichedContext);
}
/**
* Create a wrapper for fetch that handles errors
*/
createFetchWrapper(baseUrl: string = ''): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
try {
const url = typeof input === 'string' ? `${baseUrl}${input}` : input;
const response = await fetch(url, init);
if (!response.ok) {
const displayError = await this.handleFetchError(response, {
component: 'fetch-wrapper',
action: 'api-request'
});
// Throw a specialized error that includes display information
const error = new ApiRequestError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
displayError
);
throw error;
}
return response;
} catch (error) {
if (error instanceof ApiRequestError) {
throw error;
}
const displayError = this.handleNetworkError(error as Error, {
component: 'fetch-wrapper',
action: 'network-request'
});
throw new ApiRequestError(
(error as Error).message,
0,
displayError
);
}
};
}
/**
* Enhanced transform with API-specific logic
*/
override transformForDisplay(error: ApiError): DisplayError {
const baseDisplay = super.transformForDisplay(error);
// Add API-specific actions
const actions = [...(baseDisplay.actions || [])];
// Add support action for server errors
if (error.code === 'INTERNAL_ERROR' || error.code === 'SERVICE_UNAVAILABLE') {
actions.push(createSupportAction());
}
// Add retry action for retryable errors
if (baseDisplay.retryable) {
actions.unshift(createRetryAction(() => {
// Apps should override this with their retry logic
}));
}
return {
...baseDisplay,
message: createUserFriendlyMessage(error),
actions
};
}
}
/**
* Specialized error class for API requests
*/
export class ApiRequestError extends Error {
public readonly statusCode: number;
public readonly displayError: DisplayError;
constructor(message: string, statusCode: number, displayError: DisplayError) {
super(message);
this.name = 'ApiRequestError';
this.statusCode = statusCode;
this.displayError = displayError;
}
}
/**
* Create a global API error handler instance
*/
let globalApiErrorHandler: ApiErrorHandler | null = null;
export function getGlobalApiErrorHandler(): ApiErrorHandler {
if (!globalApiErrorHandler) {
globalApiErrorHandler = new ApiErrorHandler();
}
return globalApiErrorHandler;
}
/**
* Configure the global API error handler
*/
export function configureGlobalApiErrorHandler(config: Partial<ErrorHandlerConfig>): void {
globalApiErrorHandler = new ApiErrorHandler(config);
}
/**
* Convenience function to handle API errors
*/
export function handleApiError(error: ApiError, context?: ErrorContext): DisplayError {
return getGlobalApiErrorHandler().handleApiError(error, context);
}
/**
* Convenience function to handle fetch errors
*/
export async function handleFetchError(response: Response, context?: ErrorContext): Promise<DisplayError> {
return getGlobalApiErrorHandler().handleFetchError(response, context);
}