@gftdcojp/ksqldb-orm
Version:
ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions
372 lines • 13.3 kB
JavaScript
;
/**
* Resilient ksqlDB Client - パーティション再バランシング対応の高可用性クライアント
*
* @description このモジュールは、既存のksqlDBクライアントに
* リトライ機構、サーキットブレーカー、フォールバック戦略を統合した
* リジリエントなクライアントを提供します。
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResilientKsqlDbClient = void 0;
exports.initializeResilientKsqlDbClient = initializeResilientKsqlDbClient;
exports.executePullQueryResilient = executePullQueryResilient;
exports.executeQueryResilient = executeQueryResilient;
exports.executePushQueryResilient = executePushQueryResilient;
exports.getKsqlDbHealth = getKsqlDbHealth;
exports.getKsqlDbMetrics = getKsqlDbMetrics;
exports.getKsqlDbCircuitBreakerState = getKsqlDbCircuitBreakerState;
exports.disposeResilientKsqlDbClient = disposeResilientKsqlDbClient;
const types_1 = require("./types");
const ksqldb_client_1 = require("./ksqldb-client");
const resilience_1 = require("./resilience");
const logger_1 = require("./utils/logger");
/**
* リジリエント ksqlDB クライアント
*
* 既存のksqlDBクライアントAPIと完全に互換性を保ちながら、
* パーティション再バランシングなどの障害に対する耐性を提供します。
*/
class ResilientKsqlDbClient {
constructor(config) {
this.initialized = false;
this.config = config;
// ResilientExecutor を初期化
this.executor = new resilience_1.ResilientExecutor(config.resilience?.retries, config.resilience?.circuitBreaker, config.resilience?.partitionAwareness, config.resilience?.fallback, config.resilience?.metrics);
}
/**
* クライアントを初期化
*
* 既存のinitializeKsqlDbClientと同じインターフェース
*/
async initialize() {
if (this.initialized) {
return;
}
const result = await this.executor.execute(() => {
(0, ksqldb_client_1.initializeKsqlDbClient)(this.config);
return Promise.resolve();
}, 'initialize-ksqldb-client');
if (result.error) {
throw new Error(`Failed to initialize ksqlDB client: ${result.error.message}`);
}
this.initialized = true;
logger_1.log.info('Resilient ksqlDB client initialized successfully');
}
/**
* リジリエント Pull Query 実行
*
* 既存のexecutePullQueryと互換性を保ちながら、
* リトライ・サーキットブレーカー・フォールバック機能を提供
*
* @param sql SQL文
* @param options 実行オプション(拡張版)
* @returns リジリエント実行結果
*/
async executePullQuery(sql, options = {}) {
this.ensureInitialized();
const { retries, backoffStrategy, fallbackToHttp, timeout, ...pullQueryOptions } = options;
// プライマリ操作(通常のクエリ実行)
const primaryOperation = () => (0, ksqldb_client_1.executePullQuery)(sql, pullQueryOptions);
// フォールバック操作(HTTP経由での実行など)
const fallbackOperation = () => {
logger_1.log.info('Executing pull query via fallback mechanism', { sql });
// ここでHTTPフォールバックや代替エンドポイントを試行
return (0, ksqldb_client_1.executePullQuery)(sql, { ...pullQueryOptions, format: 'object' });
};
// フォールバック設定がある場合は executeWithFallback を使用
const shouldUseFallback = fallbackToHttp ?? this.config.resilience?.fallback?.fallbackToHttp ?? true;
let result;
if (shouldUseFallback) {
result = await this.executor.executeWithFallback(primaryOperation, fallbackOperation, 'pull-query', {
retries,
backoffStrategy,
timeout
});
}
else {
result = await this.executor.execute(primaryOperation, 'pull-query', {
retries,
backoffStrategy,
timeout
});
}
return {
data: result.data,
error: result.error,
resilience: result.resilience
};
}
/**
* 下位互換性のためのPull Query実行
*
* 既存のAPIと完全に互換性を保持(リジリエンス機能は内部的に適用)
*
* @param sql SQL文
* @param options 従来のPull Queryオプション
* @returns 従来の形式の結果
*/
async executePullQueryCompat(sql, options = {}) {
const result = await this.executePullQuery(sql, options);
// エラーがある場合は従来通り例外をスロー
if (result.error) {
throw result.error;
}
return result.data;
}
/**
* リジリエント DDL/DML クエリ実行
*
* @param sql SQL文
* @param options 実行オプション
* @returns リジリエント実行結果
*/
async executeQuery(sql, options = {}) {
this.ensureInitialized();
const operation = () => (0, ksqldb_client_1.executeQuery)(sql);
const result = await this.executor.execute(operation, 'ddl-dml-query', options);
return {
data: result.data,
error: result.error,
resilience: result.resilience
};
}
/**
* 下位互換性のためのクエリ実行
*
* @param sql SQL文
* @returns 従来の形式の結果
*/
async executeQueryCompat(sql) {
const result = await this.executeQuery(sql);
if (result.error) {
throw result.error;
}
return result.data;
}
/**
* Push Query実行(ストリーミング)
*
* Push Queryはストリーミング性質上、リトライよりも
* 接続の復旧とエラーハンドリングに重点を置く
*
* @param sql SQL文
* @param onData データ受信コールバック
* @param onError エラーコールバック(拡張版)
* @param onComplete 完了コールバック
* @returns terminate関数
*/
async executePushQuery(sql, onData, onError, onComplete) {
this.ensureInitialized();
// Push Queryのエラーハンドリング拡張
const enhancedOnError = (error) => {
const errorType = resilience_1.ErrorClassifier.classifyError(error);
const canRetry = resilience_1.ErrorClassifier.isRetryableError(errorType);
logger_1.log.warn('Push query error detected', {
error: error.message,
errorType,
canRetry
});
if (onError) {
onError(error, {
errorType,
retryCount: 0, // Push queryではリトライカウントは管理しない
canRetry
});
}
};
return (0, ksqldb_client_1.executePushQuery)(sql, onData, enhancedOnError, onComplete);
}
/**
* ヘルスチェック実行
*
* クライアントの状態とサーキットブレーカーの状態を確認
*/
async healthCheck() {
const circuitBreakerState = this.executor.getCircuitBreakerState();
const metrics = this.executor.getMetrics();
// 簡単な接続テスト
let connectionTest = null;
try {
connectionTest = await this.executeQueryCompat('SHOW STREAMS;');
}
catch (error) {
connectionTest = { error: error.message };
}
// ステータス判定
let status;
if (!this.initialized || circuitBreakerState === types_1.CircuitBreakerState.OPEN) {
status = 'unhealthy';
}
else if (circuitBreakerState === types_1.CircuitBreakerState.HALF_OPEN || connectionTest?.error) {
status = 'degraded';
}
else {
status = 'healthy';
}
return {
status,
details: {
clientInitialized: this.initialized,
circuitBreakerState,
metrics,
lastError: connectionTest?.error
}
};
}
/**
* メトリクス情報を取得
*/
getMetrics() {
return this.executor.getMetrics();
}
/**
* サーキットブレーカーの状態を取得
*/
getCircuitBreakerState() {
return this.executor.getCircuitBreakerState();
}
/**
* サーキットブレーカーをリセット
*/
resetCircuitBreaker() {
this.executor.resetCircuitBreaker();
logger_1.log.info('Circuit breaker has been reset');
}
/**
* メトリクスをリセット
*/
resetMetrics() {
this.executor.resetMetrics();
logger_1.log.info('Resilience metrics have been reset');
}
/**
* クライアント設定を取得
*/
getConfig() {
return { ...this.config };
}
/**
* リソースクリーンアップ
*/
dispose() {
this.executor.dispose();
this.initialized = false;
logger_1.log.info('Resilient ksqlDB client disposed');
}
/**
* 初期化チェック
*/
ensureInitialized() {
if (!this.initialized) {
throw new Error('Resilient ksqlDB client is not initialized. Call initialize() first.');
}
}
}
exports.ResilientKsqlDbClient = ResilientKsqlDbClient;
/**
* グローバルなリジリエントクライアントインスタンス
*/
let globalResilientClient = null;
/**
* リジリエントksqlDBクライアントを初期化(グローバル)
*
* 既存のinitializeKsqlDbClientと同じインターフェースで、
* リジリエンス機能が追加されたクライアントを初期化
*
* @param config リジリエント設定を含むksqlDB設定
*/
async function initializeResilientKsqlDbClient(config) {
// 既存のクライアントがある場合はクリーンアップ
if (globalResilientClient) {
globalResilientClient.dispose();
}
globalResilientClient = new ResilientKsqlDbClient(config);
await globalResilientClient.initialize();
logger_1.log.info('Global resilient ksqlDB client initialized', {
url: config.url,
resilienceEnabled: !!config.resilience
});
}
/**
* グローバルなリジリエントPull Query実行
*
* 既存のexecutePullQueryと同じインターフェース(拡張オプション対応)
*
* @param sql SQL文
* @param options 拡張されたPull Queryオプション
* @returns 実行結果
*/
async function executePullQueryResilient(sql, options = {}) {
if (!globalResilientClient) {
throw new Error('Resilient ksqlDB client is not initialized. Call initializeResilientKsqlDbClient() first.');
}
return globalResilientClient.executePullQueryCompat(sql, options);
}
/**
* グローバルなリジリエントQuery実行
*
* @param sql SQL文
* @returns 実行結果
*/
async function executeQueryResilient(sql) {
if (!globalResilientClient) {
throw new Error('Resilient ksqlDB client is not initialized. Call initializeResilientKsqlDbClient() first.');
}
return globalResilientClient.executeQueryCompat(sql);
}
/**
* グローバルなリジリエントPush Query実行
*
* @param sql SQL文
* @param onData データ受信コールバック
* @param onError エラーコールバック
* @param onComplete 完了コールバック
* @returns terminate関数
*/
async function executePushQueryResilient(sql, onData, onError, onComplete) {
if (!globalResilientClient) {
throw new Error('Resilient ksqlDB client is not initialized. Call initializeResilientKsqlDbClient() first.');
}
return globalResilientClient.executePushQuery(sql, onData, onError, onComplete);
}
/**
* グローバルクライアントのヘルスチェック
*/
async function getKsqlDbHealth() {
if (!globalResilientClient) {
return {
status: 'unhealthy',
details: { error: 'Client not initialized' }
};
}
return globalResilientClient.healthCheck();
}
/**
* グローバルクライアントのメトリクス取得
*/
function getKsqlDbMetrics() {
if (!globalResilientClient) {
return null;
}
return globalResilientClient.getMetrics();
}
/**
* グローバルクライアントのサーキットブレーカー状態取得
*/
function getKsqlDbCircuitBreakerState() {
if (!globalResilientClient) {
return null;
}
return globalResilientClient.getCircuitBreakerState();
}
/**
* グローバルクライアントのリソースクリーンアップ
*/
function disposeResilientKsqlDbClient() {
if (globalResilientClient) {
globalResilientClient.dispose();
globalResilientClient = null;
logger_1.log.info('Global resilient ksqlDB client disposed');
}
}
//# sourceMappingURL=resilient-client.js.map