2025-11-29 21:19:28 +00:00
|
|
|
import { ErrorCode } from './types.js';
|
|
|
|
|
import type { ErrorContext } from './types.js';
|
2025-11-29 18:32:00 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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<string, unknown> {
|
|
|
|
|
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<ErrorContext> = {}, 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<ErrorContext> = {},
|
|
|
|
|
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<ErrorContext> = {}, 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<ErrorContext> = {},
|
|
|
|
|
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<ErrorContext> = {},
|
|
|
|
|
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<ErrorContext> = {}, 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<ErrorContext> = {},
|
|
|
|
|
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<ErrorContext> = {}, 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<ErrorContext> = {}, 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<ErrorContext> = {},
|
|
|
|
|
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<string, unknown> {
|
|
|
|
|
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<ErrorContext> = {}, cause?: Error) {
|
|
|
|
|
super(message, {
|
|
|
|
|
code: ErrorCode.CLUSTER_UNAVAILABLE,
|
|
|
|
|
retryable: true,
|
|
|
|
|
context: {
|
|
|
|
|
...context,
|
|
|
|
|
timestamp: new Date(),
|
|
|
|
|
},
|
|
|
|
|
cause,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|