/** * Brainy Plugin System * * Simple plugin architecture for two use cases: * 1. Native acceleration (@soulcraft/cortex) * 2. Custom storage adapters (e.g., Redis, DynamoDB, custom backends) * * Plugins are auto-detected by package name or registered manually. */ import type { StorageAdapter } from './coreTypes.js' /** * Plugin interface — all brainy plugins must implement this. */ export interface BrainyPlugin { /** Unique plugin name (typically the npm package name) */ name: string /** * Called by brainy during init() to activate the plugin. * Return true if activation succeeded, false to skip. */ activate(context: BrainyPluginContext): Promise /** * Called when brainy.close() is invoked. Optional cleanup. */ deactivate?(): Promise } /** * Context passed to plugins during activation. */ export interface BrainyPluginContext { /** * Register a provider for a named subsystem. * * Well-known provider keys (used by cortex): * - 'metadataIndex' — MetadataIndexManager replacement * - 'graphIndex' — GraphAdjacencyIndex replacement * - 'entityIdMapper' — EntityIdMapper replacement * - 'cache' — UnifiedCache replacement * - 'hnsw' — HNSWIndex replacement * - 'roaring' — RoaringBitmap32 replacement * - 'embeddings' — Embedding engine replacement (single text) * - 'embedBatch' — Batch embedding engine (texts[] → vectors[]) * - 'distance' — Distance function overrides * - 'msgpack' — Msgpack encode/decode * - 'aggregation' — AggregationIndex replacement (incremental aggregates) * * Storage adapter keys: * - 'storage:' — Custom storage adapter factory */ registerProvider(key: string, implementation: unknown): void /** Brainy version for compatibility checks */ readonly version: string } /** * Storage adapter factory — plugins register these to provide * new storage backends that users reference by name. * * Example: A Redis plugin registers 'storage:redis', then users * can use `new Brainy({ storage: 'redis', redis: { host: '...' } })` */ export interface StorageAdapterFactory { create(config: Record): StorageAdapter | Promise name: string } /** * Plugin registry — manages plugin lifecycle and provider resolution. */ export class PluginRegistry { private plugins: Map = new Map() private providers: Map = new Map() private activated: Set = new Set() /** * Register a plugin manually. */ register(plugin: BrainyPlugin): void { this.plugins.set(plugin.name, plugin) } /** * Activate all registered plugins. */ async activateAll(context: BrainyPluginContext): Promise { const activated: string[] = [] for (const [name, plugin] of this.plugins) { if (this.activated.has(name)) continue try { const success = await plugin.activate(context) if (success) { this.activated.add(name) activated.push(name) } } catch (error) { console.warn(`[brainy] Plugin ${name} failed to activate:`, error) } } return activated } /** * Deactivate all plugins (called during close()). */ async deactivateAll(): Promise { for (const [name, plugin] of this.plugins) { if (!this.activated.has(name)) continue try { await plugin.deactivate?.() this.activated.delete(name) } catch { // Non-fatal } } } /** * Get a registered provider by key. */ getProvider(key: string): T | undefined { return this.providers.get(key) as T | undefined } /** * Check if a provider is registered. */ hasProvider(key: string): boolean { return this.providers.has(key) } /** * Register a provider (called by plugins via BrainyPluginContext). */ registerProvider(key: string, implementation: unknown): void { this.providers.set(key, implementation) } /** * Get a storage adapter factory by name. */ getStorageFactory(name: string): StorageAdapterFactory | undefined { return this.providers.get(`storage:${name}`) as StorageAdapterFactory | undefined } /** Get active plugin names */ getActivePlugins(): string[] { return [...this.activated] } /** Check if any plugins are active */ hasActivePlugins(): boolean { return this.activated.size > 0 } }