UNPKG

aiwg

Version:

Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.

364 lines (333 loc) 8.69 kB
/** * Extension Registry * * Provides efficient storage, indexing, and retrieval of AIWG extensions. * Supports O(1) lookup by ID, type-based indexing, and command alias resolution. * * @architecture @.aiwg/architecture/unified-extension-schema.md * @tests @test/unit/extensions/registry.test.ts * @version 1.0.0 */ import type { Extension, ExtensionType } from './types.js'; /** * Extension registry for storing and querying extensions * * Maintains multiple indexes for efficient lookup: * - Primary storage: Map<id, Extension> for O(1) lookup by ID * - Type index: Map<type, Set<id>> for filtering by type * - Alias map: Map<alias, id> for command resolution * * @example * ```typescript * const registry = new ExtensionRegistry(); * * // Register an extension * registry.register(myExtension); * * // Lookup by ID (O(1)) * const ext = registry.get('mention-wire'); * * // Get all commands * const commands = registry.getByType('command'); * * // Resolve command alias * const id = registry.resolveCommand('wire'); // Returns 'mention-wire' * ``` */ export class ExtensionRegistry { /** * Primary storage: extension ID to Extension * * Provides O(1) lookup by ID. */ private extensions: Map<string, Extension> = new Map(); /** * Type index: extension type to Set of extension IDs * * Enables efficient filtering by type. */ private byType: Map<ExtensionType, Set<string>> = new Map(); /** * Alias map: command alias to extension ID * * Enables O(1) command resolution by name or alias. */ private aliasMap: Map<string, string> = new Map(); /** * Register an extension in the registry * * If an extension with the same ID already exists, it will be replaced. * Automatically updates type index and registers command aliases. * * @param extension - Extension to register * * @example * ```typescript * registry.register({ * id: 'mention-wire', * type: 'command', * name: 'Mention Wire', * // ... other fields * }); * ``` */ register(extension: Extension): void { const { id } = extension; // If extension already exists, remove it from old type index const existing = this.extensions.get(id); if (existing) { this.removeFromTypeIndex(existing); } // Store extension this.extensions.set(id, extension); // Add to type index this.addToTypeIndex(extension); // Register command aliases if present if (extension.type === 'command' && 'aliases' in extension.metadata) { const aliases = (extension.metadata as any).aliases; if (Array.isArray(aliases)) { aliases.forEach((alias: string) => { this.registerAlias(alias, id); }); } } // Register primary command name if (extension.type === 'command') { this.aliasMap.set(id, id); } } /** * Get extension by ID * * O(1) lookup via Map. * * @param id - Extension ID * @returns Extension if found, undefined otherwise * * @example * ```typescript * const ext = registry.get('mention-wire'); * if (ext) { * console.log(ext.name); * } * ``` */ get(id: string): Extension | undefined { return this.extensions.get(id); } /** * Get all extensions of a specific type * * Uses type index for efficient filtering. * * @param type - Extension type to filter by * @returns Array of extensions of the specified type * * @example * ```typescript * const commands = registry.getByType('command'); * const agents = registry.getByType('agent'); * ``` */ getByType(type: ExtensionType): Extension[] { const ids = this.byType.get(type); if (!ids || ids.size === 0) { return []; } const extensions: Extension[] = []; for (const id of ids) { const ext = this.extensions.get(id); if (ext) { extensions.push(ext); } } return extensions; } /** * Resolve a command name or alias to extension ID * * O(1) lookup via Map. * * @param command - Command name or alias * @returns Extension ID if found, undefined otherwise * * @example * ```typescript * const id = registry.resolveCommand('wire'); // 'mention-wire' * const ext = registry.get(id); * ``` */ resolveCommand(command: string): string | undefined { return this.aliasMap.get(command); } /** * Register a command alias * * Associates an alias with an extension ID. The extension does not * need to be registered first. * * @param alias - Command alias * @param extensionId - Target extension ID * * @example * ```typescript * registry.registerAlias('wire', 'mention-wire'); * registry.registerAlias('w', 'mention-wire'); * ``` */ registerAlias(alias: string, extensionId: string): void { this.aliasMap.set(alias, extensionId); } /** * Get all registered extensions * * Returns a new array containing all extensions. Modifications to the * returned array will not affect the registry. * * @returns Array of all extensions * * @example * ```typescript * const all = registry.getAll(); * console.log(`Registry contains ${all.length} extensions`); * ``` */ getAll(): Extension[] { return Array.from(this.extensions.values()); } /** * Get count of registered extensions * * @returns Number of extensions in the registry * * @example * ```typescript * console.log(`${registry.size} extensions registered`); * ``` */ get size(): number { return this.extensions.size; } /** * Check if extension exists in registry * * O(1) lookup via Map. * * @param id - Extension ID to check * @returns True if extension exists, false otherwise * * @example * ```typescript * if (registry.has('mention-wire')) { * console.log('Extension is registered'); * } * ``` */ has(id: string): boolean { return this.extensions.has(id); } /** * Clear the registry * * Removes all extensions, type index entries, and aliases. * * @example * ```typescript * registry.clear(); * console.log(registry.size); // 0 * ``` */ clear(): void { this.extensions.clear(); this.byType.clear(); this.aliasMap.clear(); } /** * Add extension to type index * * @private * @param extension - Extension to index */ private addToTypeIndex(extension: Extension): void { const { id, type } = extension; let typeSet = this.byType.get(type); if (!typeSet) { typeSet = new Set(); this.byType.set(type, typeSet); } typeSet.add(id); } /** * Remove extension from type index * * @private * @param extension - Extension to remove from index */ private removeFromTypeIndex(extension: Extension): void { const { id, type } = extension; const typeSet = this.byType.get(type); if (typeSet) { typeSet.delete(id); // Clean up empty sets if (typeSet.size === 0) { this.byType.delete(type); } } } } // ============================================ // Singleton Instance // ============================================ /** * Global registry instance * * Singleton instance shared across all calls to getRegistry(). */ let globalRegistry: ExtensionRegistry | null = null; /** * Get the global registry instance * * Returns a singleton ExtensionRegistry instance. All calls to this * function return the same instance, allowing state to be shared * across the application. * * @returns Global ExtensionRegistry instance * * @example * ```typescript * import { getRegistry } from './registry.js'; * * const registry = getRegistry(); * registry.register(myExtension); * * // Later, in another module * const sameRegistry = getRegistry(); * console.log(sameRegistry.has('my-extension')); // true * ``` */ export function getRegistry(): ExtensionRegistry { if (!globalRegistry) { globalRegistry = new ExtensionRegistry(); } return globalRegistry; } /** * Create a new registry instance * * Returns a new, independent ExtensionRegistry instance. Use this when * you need an isolated registry for testing or scoped operations. * * @returns New ExtensionRegistry instance * * @example * ```typescript * import { createRegistry } from './registry.js'; * * const testRegistry = createRegistry(); * testRegistry.register(testExtension); * // testRegistry is independent of global registry * ``` */ export function createRegistry(): ExtensionRegistry { return new ExtensionRegistry(); }