UNPKG

bowling-analysis-system

Version:

A comprehensive system for analyzing bowling techniques using video processing and metrics calculation

256 lines (217 loc) 7.3 kB
/** * Central registry for services with dependency injection * @class ServiceRegistry */ class ServiceRegistry { /** * Creates an instance of ServiceRegistry * @param {Object} options - Options * @param {Object} [options.logger] - Logger instance * @param {Object} [options.config] - Configuration object */ constructor(options = {}) { this.logger = options.logger || console; this.config = options.config || {}; this.services = new Map(); this.factories = new Map(); this.instances = new Map(); this.initializing = new Set(); this.initialized = new Set(); } /** * Register a service factory * @param {String} name - Unique service name * @param {Function} factory - Factory function that creates the service * @param {Object} [options={}] - Service options * @param {String[]} [options.dependencies=[]] - Service dependencies * @param {Boolean} [options.singleton=true] - Whether service is a singleton * @param {Boolean} [options.lazy=true] - Whether to initialize on first get * @returns {ServiceRegistry} - For method chaining */ register(name, factory, options = {}) { if (this.services.has(name)) { this.logger.warn(`Service '${name}' already registered, overwriting`); } const serviceOptions = { dependencies: options.dependencies || [], singleton: options.singleton !== false, lazy: options.lazy !== false, ...options }; this.services.set(name, { name, factory, options: serviceOptions }); this.factories.set(name, factory); return this; } /** * Register a service instance directly * @param {String} name - Unique service name * @param {Object} instance - Service instance * @returns {ServiceRegistry} - For method chaining */ registerInstance(name, instance) { if (this.instances.has(name)) { this.logger.warn(`Service instance '${name}' already registered, overwriting`); } this.instances.set(name, instance); this.initialized.add(name); return this; } /** * Check if a service is registered * @param {String} name - Service name * @returns {Boolean} - True if service is registered */ has(name) { return this.services.has(name) || this.instances.has(name); } /** * Get a service instance * @param {String} name - Service name * @returns {Object} - Service instance * @throws {Error} - If service is not registered or can't be created */ get(name) { // Return existing instance if available if (this.instances.has(name)) { return this.instances.get(name); } // Check if service is registered if (!this.services.has(name)) { throw new Error(`Service not registered: ${name}`); } // Get service definition const service = this.services.get(name); // Check for circular dependencies if (this.initializing.has(name)) { throw new Error(`Circular dependency detected while initializing service: ${name}`); } // Create instance try { this.initializing.add(name); // Resolve dependencies const dependencies = {}; for (const depName of service.options.dependencies) { dependencies[depName] = this.get(depName); } // Create instance const instance = service.factory({ logger: this.logger, config: this.config, registry: this, dependencies }); // Store instance if singleton if (service.options.singleton) { this.instances.set(name, instance); } this.initialized.add(name); return instance; } finally { this.initializing.delete(name); } } /** * Initialize all non-lazy services * @returns {Promise<Map<String, Object>>} - Map of service name to instance */ async initializeServices() { const promises = []; for (const [name, service] of this.services) { if (!service.options.lazy) { promises.push((async () => { try { const instance = this.get(name); // Call initialize method if available if (instance && typeof instance.initialize === 'function') { await instance.initialize(); } return [name, instance]; } catch (error) { this.logger.error(`Error initializing service '${name}':`, error); throw error; } })()); } } const results = await Promise.all(promises); return new Map(results); } /** * Remove a service * @param {String} name - Service name * @returns {Boolean} - True if service was removed */ remove(name) { const wasService = this.services.delete(name); const wasInstance = this.instances.delete(name); this.factories.delete(name); this.initialized.delete(name); return wasService || wasInstance; } /** * Get all registered service names * @returns {Array<String>} - Array of service names */ getServiceNames() { return Array.from(new Set([ ...this.services.keys(), ...this.instances.keys() ])); } /** * Get all initialized service instances * @returns {Map<String, Object>} - Map of service name to instance */ getInitializedServices() { return new Map(this.instances); } /** * Create a child registry that inherits services from this one * @param {Object} [options={}] - Options for child registry * @returns {ServiceRegistry} - Child registry */ createChildRegistry(options = {}) { const childOptions = { logger: options.logger || this.logger, config: { ...this.config, ...(options.config || {}) } }; const child = new ServiceRegistry(childOptions); // Child can access parent services const parentGet = this.get.bind(this); child.get = function(name) { if (child.instances.has(name)) { return child.instances.get(name); } if (child.services.has(name)) { return ServiceRegistry.prototype.get.call(child, name); } // Try to get from parent return parentGet(name); }; // Child can check parent services const parentHas = this.has.bind(this); child.has = function(name) { return child.services.has(name) || child.instances.has(name) || parentHas(name); }; return child; } /** * Configure a service factory by name * @param {String} name - Service name * @param {Object} options - Configuration options * @returns {ServiceRegistry} - For method chaining */ configure(name, options) { if (!this.services.has(name)) { throw new Error(`Cannot configure unknown service: ${name}`); } const service = this.services.get(name); service.options = { ...service.options, ...options }; return this; } } module.exports = ServiceRegistry;