import { ErrorCode, ErrorContext } from './types.js'; /** * Base error class for all Elasticsearch client errors * * @example * ```typescript * throw new ElasticsearchError('Connection failed', { * code: ErrorCode.CONNECTION_FAILED, * retryable: true, * context: { * timestamp: new Date(), * operation: 'connect', * statusCode: 503 * } * }); * ``` */ export class ElasticsearchError extends Error { /** Error code for categorization */ public readonly code: ErrorCode; /** Whether this error is retryable */ public readonly retryable: boolean; /** Additional context about the error */ public readonly context: ErrorContext; constructor( message: string, options: { code: ErrorCode; retryable: boolean; context: ErrorContext; cause?: Error; } ) { super(message, { cause: options.cause }); this.name = this.constructor.name; this.code = options.code; this.retryable = options.retryable; this.context = { ...options.context, timestamp: options.context.timestamp || new Date(), }; // Maintains proper stack trace for where error was thrown (V8 only) if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } /** * Convert error to JSON for logging/serialization */ toJSON(): Record { return { name: this.name, message: this.message, code: this.code, retryable: this.retryable, context: this.context, stack: this.stack, }; } /** * Check if error is of a specific code */ is(code: ErrorCode): boolean { return this.code === code; } /** * Check if error is retryable */ canRetry(): boolean { return this.retryable; } } /** * Connection-related errors */ export class ConnectionError extends ElasticsearchError { constructor(message: string, context: Partial = {}, cause?: Error) { super(message, { code: ErrorCode.CONNECTION_FAILED, retryable: true, context: { ...context, timestamp: new Date(), }, cause, }); } } /** * Timeout errors */ export class TimeoutError extends ElasticsearchError { constructor( message: string, operation: string, timeoutMs: number, context: Partial = {}, cause?: Error ) { super(message, { code: ErrorCode.REQUEST_TIMEOUT, retryable: true, context: { ...context, operation, timeout: timeoutMs, timestamp: new Date(), }, cause, }); } } /** * Index not found error */ export class IndexNotFoundError extends ElasticsearchError { constructor(indexName: string, context: Partial = {}, cause?: Error) { super(`Index not found: ${indexName}`, { code: ErrorCode.INDEX_NOT_FOUND, retryable: false, context: { ...context, index: indexName, timestamp: new Date(), }, cause, }); } } /** * Document not found error */ export class DocumentNotFoundError extends ElasticsearchError { constructor( documentId: string, indexName?: string, context: Partial = {}, cause?: Error ) { super(`Document not found: ${documentId}${indexName ? ` in index ${indexName}` : ''}`, { code: ErrorCode.DOCUMENT_NOT_FOUND, retryable: false, context: { ...context, documentId, index: indexName, timestamp: new Date(), }, cause, }); } } /** * Document conflict error (version mismatch, optimistic locking) */ export class DocumentConflictError extends ElasticsearchError { constructor( documentId: string, message: string, context: Partial = {}, cause?: Error ) { super(message, { code: ErrorCode.DOCUMENT_CONFLICT, retryable: true, // Can retry with updated version context: { ...context, documentId, timestamp: new Date(), }, cause, }); } } /** * Authentication error */ export class AuthenticationError extends ElasticsearchError { constructor(message: string, context: Partial = {}, cause?: Error) { super(message, { code: ErrorCode.AUTHENTICATION_FAILED, retryable: false, context: { ...context, timestamp: new Date(), }, cause, }); } } /** * Authorization error (insufficient permissions) */ export class AuthorizationError extends ElasticsearchError { constructor( operation: string, resource: string, context: Partial = {}, cause?: Error ) { super(`Not authorized to perform ${operation} on ${resource}`, { code: ErrorCode.AUTHORIZATION_FAILED, retryable: false, context: { ...context, operation, resource, timestamp: new Date(), }, cause, }); } } /** * Configuration error */ export class ConfigurationError extends ElasticsearchError { constructor(message: string, context: Partial = {}, cause?: Error) { super(message, { code: ErrorCode.INVALID_CONFIGURATION, retryable: false, context: { ...context, timestamp: new Date(), }, cause, }); } } /** * Query parsing error */ export class QueryParseError extends ElasticsearchError { constructor(query: unknown, reason: string, context: Partial = {}, cause?: Error) { super(`Failed to parse query: ${reason}`, { code: ErrorCode.QUERY_PARSE_ERROR, retryable: false, context: { ...context, query, timestamp: new Date(), }, cause, }); } } /** * Bulk operation error with partial failures */ export class BulkOperationError extends ElasticsearchError { public readonly successfulCount: number; public readonly failedCount: number; public readonly failures: Array<{ documentId?: string; error: string; status: number; }>; constructor( message: string, successful: number, failed: number, failures: Array<{ documentId?: string; error: string; status: number }>, context: Partial = {}, cause?: Error ) { super(message, { code: failed === 0 ? ErrorCode.BULK_REQUEST_FAILED : ErrorCode.PARTIAL_BULK_FAILURE, retryable: true, // Failed items can be retried context: { ...context, successfulCount: successful, failedCount: failed, timestamp: new Date(), }, cause, }); this.successfulCount = successful; this.failedCount = failed; this.failures = failures; } toJSON(): Record { return { ...super.toJSON(), successfulCount: this.successfulCount, failedCount: this.failedCount, failures: this.failures, }; } } /** * Cluster unavailable error */ export class ClusterUnavailableError extends ElasticsearchError { constructor(message: string, context: Partial = {}, cause?: Error) { super(message, { code: ErrorCode.CLUSTER_UNAVAILABLE, retryable: true, context: { ...context, timestamp: new Date(), }, cause, }); } }