@smartsamurai/krapi-sdk
Version:
KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)
286 lines (266 loc) • 7.41 kB
text/typescript
/**
* Adapter Error Handler
*
* Provides centralized error handling for all adapters, ensuring consistent
* error transformation and context preservation between client and server modes.
*/
import { KrapiError } from "../../core/krapi-error";
import { HttpError } from "../../http-clients/http-error";
import { normalizeError, enrichError, createRequestId } from "../../utils/error-handler";
/**
* Adapter mode type
*/
type Mode = "client" | "server";
/**
* Handle adapter errors with consistent transformation
*
* Central error handler for all adapters that transforms errors from HTTP clients
* and services into standardized KrapiError format with appropriate context.
*
* @param error - The error to handle
* @param mode - Current adapter mode ("client" or "server")
* @param operation - Operation name for context
* @param context - Additional context for the error
* @returns Never - Always throws the transformed error
*/
export function handleAdapterError(
error: unknown,
mode: Mode,
operation: string,
context?: Record<string, unknown>
): never {
let krapiError: KrapiError;
// Handle HttpError specifically
if (error instanceof HttpError) {
krapiError = transformHttpErrorToAdapterError(error, mode, operation, context);
} else if (error instanceof KrapiError) {
// Already a KrapiError, just enrich with adapter context
krapiError = enrichError(error, {
adapterMode: mode,
operation,
...context,
});
} else {
// Generic error transformation
krapiError = normalizeError(error, "INTERNAL_ERROR", {
adapterMode: mode,
operation,
...context,
});
}
// Ensure request ID exists
if (!krapiError.requestId) {
krapiError = new KrapiError(
krapiError.message,
krapiError.code,
krapiError.status,
krapiError.details,
createRequestId(),
krapiError.cause
);
}
throw krapiError;
}
/**
* Transform HttpError to adapter-specific KrapiError
*
* @param httpError - HTTP error to transform
* @param mode - Adapter mode
* @param operation - Operation name
* @param context - Additional context
* @returns Transformed KrapiError
*/
function transformHttpErrorToAdapterError(
httpError: HttpError,
mode: Mode,
operation: string,
context?: Record<string, unknown>
): KrapiError {
// Import here to avoid circular dependencies
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { transformHttpError } = require("../../utils/error-handler");
let krapiError = transformHttpError(httpError, {
adapterMode: mode,
operation,
...context,
});
// Add adapter-specific context based on operation type
if (operation.includes("auth") || operation.includes("login")) {
krapiError = enrichError(krapiError, {
category: "authentication",
adapterType: "auth",
});
} else if (operation.includes("create") || operation.includes("update") || operation.includes("delete")) {
krapiError = enrichError(krapiError, {
category: "data_modification",
adapterType: "data",
});
} else if (operation.includes("get") || operation.includes("list") || operation.includes("find")) {
krapiError = enrichError(krapiError, {
category: "data_retrieval",
adapterType: "query",
});
}
return krapiError;
}
/**
* Create initialization error for adapters
*
* @param component - Component that failed to initialize
* @param mode - Adapter mode
* @param reason - Reason for failure
* @returns KrapiError for initialization failure
*/
export function createAdapterInitError(
component: string,
mode: Mode,
reason?: string
): KrapiError {
const message = `${component} not initialized. Please ensure you're in ${mode} mode.`;
const fullMessage = reason ? `${message} Reason: ${reason}` : message;
return new KrapiError(
fullMessage,
"INTERNAL_ERROR",
undefined,
{
component,
mode,
reason,
initializationError: true,
},
createRequestId()
);
}
/**
* Wrap adapter operations with error handling
*
* Higher-order function that wraps adapter methods with consistent error handling.
*
* @param operation - Operation function to wrap
* @param mode - Adapter mode
* @param operationName - Name of the operation
* @param contextFn - Function to generate context from arguments
* @returns Wrapped operation function
*/
export function withAdapterErrorHandling<T extends unknown[], R>(
operation: (...args: T) => Promise<R>,
mode: Mode,
operationName: string,
contextFn?: (...args: T) => Record<string, unknown>
) {
return async (...args: T): Promise<R> => {
try {
return await operation(...args);
} catch (error) {
const context = contextFn ? contextFn(...args) : {};
handleAdapterError(error, mode, operationName, context);
}
};
}
/**
* Handle mode validation errors
*
* @param requiredMode - Required mode for operation
* @param currentMode - Current adapter mode
* @param operation - Operation name
* @returns Never - Always throws error
*/
export function handleModeError(
requiredMode: Mode,
currentMode: Mode,
operation: string
): never {
const error = new KrapiError(
`Operation '${operation}' requires ${requiredMode} mode, but adapter is in ${currentMode} mode`,
"INTERNAL_ERROR",
undefined,
{
requiredMode,
currentMode,
operation,
modeValidationError: true,
},
createRequestId()
);
throw error;
}
/**
* Validate adapter mode before operation
*
* @param currentMode - Current adapter mode
* @param requiredMode - Required mode
* @param operation - Operation name
*/
export function validateAdapterMode(
currentMode: Mode,
requiredMode: Mode,
operation: string
): void {
if (currentMode !== requiredMode) {
handleModeError(requiredMode, currentMode, operation);
}
}
/**
* Create service operation error
*
* @param serviceName - Name of the service
* @param operation - Operation name
* @param mode - Adapter mode
* @param originalError - Original error
* @returns KrapiError for service operation failure
*/
export function createServiceOperationError(
serviceName: string,
operation: string,
mode: Mode,
originalError?: unknown
): KrapiError {
return new KrapiError(
`${serviceName} operation '${operation}' failed in ${mode} mode`,
"INTERNAL_ERROR",
undefined,
{
serviceName,
operation,
mode,
serviceOperationError: true,
originalError,
},
createRequestId()
);
}
/**
* Transform service errors to adapter errors
*
* @param serviceError - Error from service layer
* @param serviceName - Name of the service
* @param operation - Operation name
* @param mode - Adapter mode
* @param context - Additional context
* @returns Transformed KrapiError
*/
export function transformServiceError(
serviceError: unknown,
serviceName: string,
operation: string,
mode: Mode,
context?: Record<string, unknown>
): KrapiError {
if (serviceError instanceof KrapiError) {
return enrichError(serviceError, {
serviceName,
operation,
mode,
layer: "service",
...context,
});
}
return normalizeError(serviceError, "INTERNAL_ERROR", {
serviceName,
operation,
mode,
layer: "service",
serviceError: true,
...context,
});
}