UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

604 lines (603 loc) 21.2 kB
/** * Multi-System Query Engine * * Provides fluent interface for cross-database queries with priority-ordered execution. * Part of Task 3.3: Query Correlation Key Layer * * @example * ```typescript * const query = new MultiSystemQuery(dbService); * const results = await query * .forTask('task-001') * .includingEntities(['agent', 'skill', 'artifact']) * .fromSystems(['redis', 'sqlite', 'postgres']) * .withCache(true) * .execute(); * // Returns: { agents: [...], skills: [...], artifacts: [...] } * ``` */ import { DatabaseErrorCode } from './database-service/types.js'; import { buildCorrelationKey, buildWildcardPattern } from './database-service/correlation.js'; import { createLogger } from './logging.js'; import { createDatabaseError } from './database-service/errors.js'; import { createErrorAggregator } from './error-aggregator.js'; /** * Multi-system query builder * * Provides fluent interface for building and executing cross-database queries. */ export class MultiSystemQuery { dbService; cache; logger; errorAggregator; // Query configuration correlationKey; wildcardPattern; entities = []; systems = [ 'redis', 'sqlite', 'postgres' ]; priority = 'balanced'; useCache = false; timeout = 2000; filters = []; strictMode = false; constructor(config){ this.dbService = config.dbService; this.cache = config.cache; this.useCache = config.enableCache || false; this.timeout = config.defaultTimeout || 2000; this.logger = config.logger || createLogger('multi-system-query'); } /** * Query for specific task * * @param taskId - Task ID to query * @returns Query builder (fluent) */ forTask(taskId) { this.correlationKey = { type: 'task', id: taskId }; return this; } /** * Query for specific agent * * @param agentId - Agent ID to query * @returns Query builder (fluent) */ forAgent(agentId) { this.correlationKey = { type: 'agent', id: agentId }; return this; } /** * Query for specific skill * * @param skillId - Skill ID to query * @returns Query builder (fluent) */ forSkill(skillId) { this.correlationKey = { type: 'skill', id: skillId }; return this; } /** * Query for specific execution * * @param executionId - Execution ID to query * @returns Query builder (fluent) */ forExecution(executionId) { this.correlationKey = { type: 'execution', id: executionId }; return this; } /** * Query with custom correlation key * * @param key - Correlation key * @returns Query builder (fluent) */ withKey(key) { this.correlationKey = key; return this; } /** * Query with wildcard pattern * * @param pattern - Wildcard pattern * @returns Query builder (fluent) */ withPattern(pattern) { this.wildcardPattern = pattern; return this; } /** * Include specific entity types * * @param entities - Entity types to include * @returns Query builder (fluent) */ includingEntities(entities) { this.entities = entities; return this; } /** * Query from specific database systems * * @param systems - Database systems to query * @returns Query builder (fluent) */ fromSystems(systems) { this.systems = systems; return this; } /** * Set execution priority * * @param priority - Execution priority * @returns Query builder (fluent) */ withPriority(priority) { this.priority = priority; return this; } /** * Enable or disable caching * * @param enabled - Enable cache * @returns Query builder (fluent) */ withCache(enabled) { this.useCache = enabled; return this; } /** * Set query timeout * * @param timeout - Timeout in milliseconds * @returns Query builder (fluent) */ withTimeout(timeout) { this.timeout = timeout; return this; } /** * Add query filter * * @param filter - Query filter * @returns Query builder (fluent) */ addFilter(filter) { this.filters.push(filter); return this; } /** * Set whether to fail on partial errors * * @param fail - Fail if any system fails * @returns Query builder (fluent) */ failOnPartialError(fail = true) { this.strictMode = fail; return this; } /** * Execute multi-system query * * @returns Multi-system query results * @throws {DatabaseError} If all systems fail or critical error occurs */ async execute() { const startTime = Date.now(); // Validate query configuration this.validateQuery(); // Initialize error aggregator with correlation ID this.errorAggregator = createErrorAggregator(); const correlationId = this.errorAggregator.getCorrelationId(); this.logger.info('Starting multi-system query', { correlationId, systems: this.systems, priority: this.priority }); // Build cache key const cacheKey = this.buildCacheKey(); // Check cache first if (this.useCache && this.cache) { const cached = this.cache.get(cacheKey); if (cached) { this.logger.debug('Cache hit', { cacheKey, correlationId }); return { ...cached, cacheHit: true }; } } // Execute query based on priority let result; try { result = await this.executeByPriority(); } catch (error) { // Critical error during execution this.logger.error('Critical error during query execution', error instanceof Error ? error : undefined, { correlationId, errorMessage: error instanceof Error ? error.message : String(error) }); throw createDatabaseError(DatabaseErrorCode.QUERY_FAILED, 'Multi-system query failed with critical error', error instanceof Error ? error : undefined, { correlationId, systems: this.systems }); } // Check if we should fail based on errors if (this.errorAggregator.shouldFailOperation(this.systems)) { const errorReport = this.errorAggregator.createReport(); this.logger.error('Multi-system query failed', undefined, { correlationId, errorReport }); const aggregationResult = this.errorAggregator.getResult(this.systems); throw createDatabaseError(aggregationResult.hasCriticalErrors ? DatabaseErrorCode.QUERY_FAILED : DatabaseErrorCode.QUERY_FAILED, aggregationResult.allSystemsFailed ? 'All database systems failed' : 'Critical errors occurred during query execution', undefined, { correlationId, errorCount: aggregationResult.totalErrors, failedSystems: Object.keys(aggregationResult.errorsBySystem), errors: aggregationResult.allErrors.map((e)=>({ system: e.system, code: e.error.code, message: e.error.message, severity: e.severity })) }); } // Check for partial errors if (this.strictMode && result.errors && result.errors.length > 0) { const errorReport = this.errorAggregator.createReport(); this.logger.error('Query failed due to partial errors', undefined, { correlationId, errorCount: result.errors.length, errorReport }); throw createDatabaseError(DatabaseErrorCode.QUERY_FAILED, `Query failed with ${result.errors.length} error(s)`, undefined, { correlationId, errors: result.errors }); } // Merge and deduplicate results result.merged = this.mergeResults(result); // Calculate execution time result.executionTime = Date.now() - startTime; result.timestamp = new Date(); result.cacheHit = false; // Validate performance requirement (<2s) if (result.executionTime >= this.timeout) { this.logger.warn('Query exceeded timeout', { correlationId, executionTime: result.executionTime, timeout: this.timeout }); } // Log success this.logger.info('Multi-system query completed', { correlationId, executionTime: result.executionTime, resultCount: result.merged.length, errorCount: result.errors?.length || 0 }); // Store in cache if (this.useCache && this.cache) { this.cache.set(cacheKey, result); } return result; } /** * Execute query based on priority strategy */ async executeByPriority() { const result = { correlationKey: this.buildCorrelationKeyString(), merged: [], executionTime: 0, timestamp: new Date(), errors: [] }; switch(this.priority){ case 'fastest': return this.executeFastest(result); case 'balanced': return this.executeBalanced(result); case 'comprehensive': return this.executeComprehensive(result); default: return this.executeBalanced(result); } } /** * Execute fastest strategy (stop on first result) */ async executeFastest(result) { if (!this.errorAggregator) { throw new Error('Error aggregator not initialized'); } // Query in priority order: Redis → SQLite → PostgreSQL const orderedSystems = this.getOrderedSystems(); for (const system of orderedSystems){ // Check circuit breaker if (this.errorAggregator.isCircuitOpen(system)) { this.logger.warn(`Circuit breaker open for ${system}, skipping`, { correlationId: this.errorAggregator.getCorrelationId() }); const error = this.createError(system, new Error('Circuit breaker open')); result.errors?.push(error); this.errorAggregator.addError(system, error, { strategy: 'fastest' }); continue; } try { const data = await this.querySystem(system); this.errorAggregator.recordSuccess(system); if (data && data.length > 0) { result[system] = data; return result; // Stop on first non-empty result } } catch (error) { const dbError = this.createError(system, error); this.logger.warn(`Query failed for ${system}`, { error: dbError, correlationId: this.errorAggregator.getCorrelationId() }); result.errors?.push(dbError); this.errorAggregator.addError(system, dbError, { strategy: 'fastest' }); } } return result; } /** * Execute balanced strategy (priority-ordered parallel) */ async executeBalanced(result) { if (!this.errorAggregator) { throw new Error('Error aggregator not initialized'); } const orderedSystems = this.getOrderedSystems(); const promises = orderedSystems.map(async (system)=>{ // Check circuit breaker if (this.errorAggregator.isCircuitOpen(system)) { this.logger.warn(`Circuit breaker open for ${system}, skipping`, { correlationId: this.errorAggregator.getCorrelationId() }); const error = this.createError(system, new Error('Circuit breaker open')); result.errors?.push(error); this.errorAggregator.addError(system, error, { strategy: 'balanced' }); return; } try { const data = await this.querySystem(system); this.errorAggregator.recordSuccess(system); if (data && data.length > 0) { result[system] = data; } } catch (error) { const dbError = this.createError(system, error); this.logger.warn(`Query failed for ${system}`, { error: dbError, correlationId: this.errorAggregator.getCorrelationId() }); result.errors?.push(dbError); this.errorAggregator.addError(system, dbError, { strategy: 'balanced' }); } }); await Promise.all(promises); return result; } /** * Execute comprehensive strategy (all systems in parallel) */ async executeComprehensive(result) { if (!this.errorAggregator) { throw new Error('Error aggregator not initialized'); } const promises = this.systems.map(async (system)=>{ // Check circuit breaker if (this.errorAggregator.isCircuitOpen(system)) { this.logger.warn(`Circuit breaker open for ${system}, skipping`, { correlationId: this.errorAggregator.getCorrelationId() }); const error = this.createError(system, new Error('Circuit breaker open')); result.errors?.push(error); this.errorAggregator.addError(system, error, { strategy: 'comprehensive' }); result[system] = []; return; } try { const data = await this.querySystem(system); this.errorAggregator.recordSuccess(system); result[system] = data || []; } catch (error) { const dbError = this.createError(system, error); this.logger.warn(`Query failed for ${system}`, { error: dbError, correlationId: this.errorAggregator.getCorrelationId() }); result.errors?.push(dbError); this.errorAggregator.addError(system, dbError, { strategy: 'comprehensive' }); result[system] = []; } }); await Promise.all(promises); return result; } /** * Query specific database system */ async querySystem(system) { const adapter = this.dbService.getAdapter(system); const results = []; // If wildcard pattern, use pattern matching if (this.wildcardPattern) { const pattern = buildWildcardPattern(this.wildcardPattern); // For Redis, use SCAN with pattern if (system === 'redis') { const keys = await this.scanRedisKeys(pattern); for (const key of keys){ const data = await adapter.get(key); if (data) results.push(data); } } else { // For SQLite/PostgreSQL, use raw query with LIKE const data = await adapter.raw('SELECT * FROM correlation_data WHERE key LIKE ?', [ pattern.replace('*', '%') ]); results.push(...Array.isArray(data) ? data : []); } } else if (this.correlationKey) { // Query specific entities for correlation key if (this.entities.length > 0) { for (const entity of this.entities){ const key = buildCorrelationKey({ ...this.correlationKey, entity }); const data = await adapter.get(key); if (data) results.push(data); } } else { // Query just the correlation key const key = buildCorrelationKey(this.correlationKey); const data = await adapter.get(key); if (data) results.push(data); } } return results; } /** * Scan Redis keys with pattern */ async scanRedisKeys(pattern) { const adapter = this.dbService.getAdapter('redis'); const keys = []; // Use SCAN command for efficient key iteration let cursor = '0'; do { const result = await adapter.raw('SCAN', [ cursor, 'MATCH', pattern, 'COUNT', '100' ]); cursor = result[0]; keys.push(...result[1]); }while (cursor !== '0') return keys; } /** * Merge results from multiple databases */ mergeResults(result) { const allResults = []; const seen = new Set(); // Merge in priority order: Redis → SQLite → PostgreSQL const orderedSystems = this.getOrderedSystems(); for (const system of orderedSystems){ const systemResults = result[system]; if (!systemResults) continue; for (const item of systemResults){ const key = this.getDeduplicationKey(item); if (!seen.has(key)) { seen.add(key); allResults.push(item); } } } return allResults; } /** * Get deduplication key for result item */ getDeduplicationKey(item) { if (item && typeof item === 'object') { // Try common ID fields if (item.id) return `id:${item.id}`; if (item._id) return `_id:${item._id}`; if (item.key) return `key:${item.key}`; // Fallback to JSON stringify return JSON.stringify(item); } return String(item); } /** * Get ordered systems based on priority */ getOrderedSystems() { // Priority order: Redis (fastest) → SQLite → PostgreSQL const order = [ 'redis', 'sqlite', 'postgres' ]; return order.filter((system)=>this.systems.includes(system)); } /** * Validate query configuration */ validateQuery() { if (!this.correlationKey && !this.wildcardPattern) { throw createDatabaseError(DatabaseErrorCode.VALIDATION_FAILED, 'Query must specify either correlationKey or wildcardPattern', undefined, { query: this }); } if (this.systems.length === 0) { throw createDatabaseError(DatabaseErrorCode.VALIDATION_FAILED, 'Query must specify at least one database system', undefined, { query: this }); } } /** * Build cache key for query */ buildCacheKey() { const parts = [ 'msq' ]; // multi-system-query if (this.correlationKey) { parts.push(buildCorrelationKey(this.correlationKey)); } else if (this.wildcardPattern) { parts.push(buildWildcardPattern(this.wildcardPattern)); } if (this.entities.length > 0) { parts.push(`entities:${this.entities.join(',')}`); } parts.push(`systems:${this.systems.join(',')}`); parts.push(`priority:${this.priority}`); return parts.join(':'); } /** * Build correlation key string */ buildCorrelationKeyString() { if (this.correlationKey) { return buildCorrelationKey(this.correlationKey); } else if (this.wildcardPattern) { return buildWildcardPattern(this.wildcardPattern); } return 'unknown'; } /** * Create database error */ createError(system, error) { return { code: DatabaseErrorCode.QUERY_FAILED, message: `Query failed for ${system}: ${error.message || error}`, originalError: error instanceof Error ? error : undefined, context: { system } }; } } /** * Create multi-system query builder * * @param config - Query builder configuration * @returns Multi-system query builder instance */ export function createMultiSystemQuery(config) { return new MultiSystemQuery(config); } //# sourceMappingURL=multi-system-query.js.map