UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

331 lines (286 loc) 7.99 kB
/** * Service Registry Loader * * Loads and queries the service registry with caching support. * Provides fast lookups by ID, category, tags, and search. * * @module registry-loader * @version 1.0.0 * @created Story 2.6 - Service Registry Creation */ const fs = require('fs').promises; const path = require('path'); // Cache configuration const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes /** * Service Registry class with caching */ class ServiceRegistry { constructor(options = {}) { this.registryPath = options.registryPath || null; this.cache = null; this.cacheTimestamp = 0; this.cacheTTL = options.cacheTTL || CACHE_TTL_MS; // Indexed lookups (built on load) this._byId = new Map(); this._byCategory = new Map(); this._byTag = new Map(); this._byAgent = new Map(); } /** * Load registry from file with caching * @param {boolean} force - Force reload ignoring cache * @returns {Promise<object>} Registry data */ async load(force = false) { const now = Date.now(); // Return cached if valid if (!force && this.cache && (now - this.cacheTimestamp) < this.cacheTTL) { return this.cache; } // Determine registry path const registryPath = this.registryPath || path.join(process.cwd(), '.aios-core/core/registry/service-registry.json'); const startTime = Date.now(); try { const content = await fs.readFile(registryPath, 'utf8'); this.cache = JSON.parse(content); this.cacheTimestamp = now; // Build indexes this._buildIndexes(); const loadTime = Date.now() - startTime; if (loadTime > 500) { console.warn(`Registry load time exceeded 500ms: ${loadTime}ms`); } return this.cache; } catch (error) { throw new Error(`Failed to load registry: ${error.message}`); } } /** * Build lookup indexes for fast queries */ _buildIndexes() { this._byId.clear(); this._byCategory.clear(); this._byTag.clear(); this._byAgent.clear(); for (const worker of this.cache.workers) { // By ID this._byId.set(worker.id, worker); // By category if (!this._byCategory.has(worker.category)) { this._byCategory.set(worker.category, []); } this._byCategory.get(worker.category).push(worker); // By tags for (const tag of worker.tags || []) { if (!this._byTag.has(tag)) { this._byTag.set(tag, []); } this._byTag.get(tag).push(worker); } // By agent for (const agent of worker.agents || []) { if (!this._byAgent.has(agent)) { this._byAgent.set(agent, []); } this._byAgent.get(agent).push(worker); } } } /** * Get worker by ID * @param {string} id - Worker ID * @returns {object|null} Worker entry or null */ async getById(id) { await this.load(); return this._byId.get(id) || null; } /** * Get workers by category * @param {string} category - Category name * @returns {Promise<Array>} Array of workers */ async getByCategory(category) { await this.load(); return this._byCategory.get(category) || []; } /** * Get workers by tag * @param {string} tag - Tag name * @returns {Promise<Array>} Array of workers */ async getByTag(tag) { await this.load(); return this._byTag.get(tag) || []; } /** * Get workers by multiple tags (AND) * @param {string[]} tags - Array of tags * @returns {Promise<Array>} Workers with all tags */ async getByTags(tags) { await this.load(); if (tags.length === 0) return []; if (tags.length === 1) return this.getByTag(tags[0]); // Get workers with first tag, then filter by rest const candidates = this._byTag.get(tags[0]) || []; return candidates.filter(worker => { const workerTags = new Set(worker.tags || []); return tags.every(tag => workerTags.has(tag)); }); } /** * Get workers for an agent * @param {string} agent - Agent ID * @returns {Promise<Array>} Workers for the agent */ async getForAgent(agent) { await this.load(); return this._byAgent.get(agent) || []; } /** * Search workers by query * @param {string} query - Search query * @param {object} options - Search options * @returns {Promise<Array>} Matching workers */ async search(query, options = {}) { await this.load(); const queryLower = query.toLowerCase(); const { category, maxResults = 20 } = options; let results = this.cache.workers.filter(worker => { // Filter by category if specified if (category && worker.category !== category) { return false; } // Search in ID, name, description, tags const searchText = [ worker.id, worker.name, worker.description, ...(worker.tags || []), ].join(' ').toLowerCase(); return searchText.includes(queryLower); }); // Score and sort by relevance results = results.map(worker => { let score = 0; if (worker.id.includes(queryLower)) score += 10; if (worker.name.toLowerCase().includes(queryLower)) score += 8; if ((worker.tags || []).some(t => t.includes(queryLower))) score += 5; if (worker.description.toLowerCase().includes(queryLower)) score += 2; return { worker, score }; }); results.sort((a, b) => b.score - a.score); return results.slice(0, maxResults).map(r => r.worker); } /** * Get all workers * @returns {Promise<Array>} All workers */ async getAll() { await this.load(); return this.cache.workers; } /** * Get registry metadata * @returns {Promise<object>} Registry info */ async getInfo() { await this.load(); return { version: this.cache.version, generated: this.cache.generated, totalWorkers: this.cache.totalWorkers, categories: Object.keys(this.cache.categories), }; } /** * Get category summary * @returns {Promise<object>} Categories with counts */ async getCategories() { await this.load(); return this.cache.categories; } /** * Get all unique tags * @returns {Promise<string[]>} Array of tags */ async getTags() { await this.load(); return Array.from(this._byTag.keys()).sort(); } /** * Check if worker exists * @param {string} id - Worker ID * @returns {Promise<boolean>} True if exists */ async exists(id) { await this.load(); return this._byId.has(id); } /** * Get worker count * @returns {Promise<number>} Total workers */ async count() { await this.load(); return this.cache.totalWorkers; } /** * Clear cache */ clearCache() { this.cache = null; this.cacheTimestamp = 0; this._byId.clear(); this._byCategory.clear(); this._byTag.clear(); this._byAgent.clear(); } /** * Get performance metrics * @returns {object} Cache metrics */ getMetrics() { return { cached: this.cache !== null, cacheAge: this.cache ? Date.now() - this.cacheTimestamp : null, workerCount: this._byId.size, categoryCount: this._byCategory.size, tagCount: this._byTag.size, }; } } // Singleton instance let _instance = null; /** * Get registry singleton * @param {object} options - Registry options * @returns {ServiceRegistry} Registry instance */ function getRegistry(options = {}) { if (!_instance || options.fresh) { _instance = new ServiceRegistry(options); } return _instance; } /** * Quick load function * @param {string} registryPath - Optional path to registry file * @returns {Promise<object>} Registry data */ async function loadRegistry(registryPath = null) { const registry = getRegistry({ registryPath }); return registry.load(); } // Export both class and helpers module.exports = { ServiceRegistry, getRegistry, loadRegistry, };