UNPKG

n8n-nodes-semble

Version:

n8n community node for Semble practice management system - automate bookings, patients, and product/service catalog management

273 lines (272 loc) 9.46 kB
"use strict"; /** * @fileoverview ServiceContainer.ts * @description Dependency injection container for clean architecture and testability. Provides service registration, resolution, and lifecycle management. * @author Mike Hatcher * @website https://progenious.com * @namespace N8nNodesSemble.Core.ServiceContainer * @since 2.0.0 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ServiceContainerUtils = exports.serviceContainer = exports.ServiceContainer = exports.ServiceLifetime = void 0; exports.Service = Service; exports.Injectable = Injectable; const SembleError_1 = require("./SembleError"); /** * Service lifecycle types */ var ServiceLifetime; (function (ServiceLifetime) { ServiceLifetime["SINGLETON"] = "singleton"; ServiceLifetime["TRANSIENT"] = "transient"; ServiceLifetime["SCOPED"] = "scoped"; })(ServiceLifetime || (exports.ServiceLifetime = ServiceLifetime = {})); /** * Dependency injection container implementation * Supports singleton, transient, and scoped service lifetimes */ class ServiceContainer { constructor() { this.services = new Map(); this.singletonInstances = new Map(); this.scopedInstances = new Map(); this.resolutionStack = []; } /** * Register a service with the container */ register(name, factory, lifetime = ServiceLifetime.TRANSIENT, dependencies = []) { if (this.services.has(name)) { throw new SembleError_1.SembleError(`Service '${name}' is already registered`); } this.services.set(name, { name, factory, lifetime, dependencies }); } /** * Register a singleton service */ registerSingleton(name, factory, dependencies = []) { this.register(name, factory, ServiceLifetime.SINGLETON, dependencies); } /** * Register a transient service */ registerTransient(name, factory, dependencies = []) { this.register(name, factory, ServiceLifetime.TRANSIENT, dependencies); } /** * Register a scoped service */ registerScoped(name, factory, dependencies = []) { this.register(name, factory, ServiceLifetime.SCOPED, dependencies); } /** * Resolve a service by name */ resolve(name, scope) { // Check for circular dependencies if (this.resolutionStack.includes(name)) { const cycle = [...this.resolutionStack, name].join(' -> '); throw new SembleError_1.SembleError(`Circular dependency detected: ${cycle}`); } const registration = this.services.get(name); if (!registration) { throw new SembleError_1.SembleError(`Service '${name}' is not registered`); } this.resolutionStack.push(name); try { switch (registration.lifetime) { case ServiceLifetime.SINGLETON: return this.resolveSingleton(registration); case ServiceLifetime.SCOPED: return this.resolveScoped(registration, scope || 'default'); case ServiceLifetime.TRANSIENT: default: return this.resolveTransient(registration, scope); } } finally { this.resolutionStack.pop(); } } /** * Resolve singleton service */ resolveSingleton(registration) { if (this.singletonInstances.has(registration.name)) { return this.singletonInstances.get(registration.name); } const dependencies = this.resolveDependencies(registration.dependencies || []); const instance = registration.factory(...dependencies); this.singletonInstances.set(registration.name, instance); return instance; } /** * Resolve scoped service */ resolveScoped(registration, scope) { if (!this.scopedInstances.has(scope)) { this.scopedInstances.set(scope, new Map()); } const scopeInstances = this.scopedInstances.get(scope); if (scopeInstances.has(registration.name)) { return scopeInstances.get(registration.name); } const dependencies = this.resolveDependencies(registration.dependencies || [], scope); const instance = registration.factory(...dependencies); scopeInstances.set(registration.name, instance); return instance; } /** * Resolve transient service */ resolveTransient(registration, scope) { const dependencies = this.resolveDependencies(registration.dependencies || [], scope); return registration.factory(...dependencies); } /** * Resolve service dependencies */ resolveDependencies(dependencies, scope) { return dependencies.map(dep => this.resolve(dep, scope)); } /** * Check if a service is registered */ isRegistered(name) { return this.services.has(name); } /** * Get all registered service names */ getRegisteredServices() { return Array.from(this.services.keys()); } /** * Clear all services and instances */ clear() { this.services.clear(); this.singletonInstances.clear(); this.scopedInstances.clear(); this.resolutionStack = []; } /** * Clear scoped instances for a specific scope */ clearScope(scope) { this.scopedInstances.delete(scope); } /** * Create a scoped container */ createScope(scopeName) { return new ScopedServiceContainer(this, scopeName); } /** * Get service registration info */ getServiceInfo(name) { return this.services.get(name); } /** * Register multiple services at once */ registerBatch(registrations) { for (const reg of registrations) { this.register(reg.name, reg.factory, reg.lifetime || ServiceLifetime.TRANSIENT, reg.dependencies || []); } } } exports.ServiceContainer = ServiceContainer; /** * Scoped service container implementation */ class ScopedServiceContainer { constructor(parentContainer, scopeName) { this.parentContainer = parentContainer; this.scopeName = scopeName; } register(name, factory, lifetime, dependencies) { this.parentContainer.register(name, factory, lifetime, dependencies); } resolve(name) { return this.parentContainer.resolve(name, this.scopeName); } isRegistered(name) { return this.parentContainer.isRegistered(name); } clear() { this.parentContainer.clearScope(this.scopeName); } createScope(scopeName) { return this.parentContainer.createScope(scopeName); } } /** * Default service container instance */ exports.serviceContainer = new ServiceContainer(); /** * Service decorator for automatic registration */ function Service(name, lifetime = ServiceLifetime.TRANSIENT) { return function (constructor) { exports.serviceContainer.register(name, () => new constructor(), lifetime); return constructor; }; } /** * Injectable decorator for dependency injection */ function Injectable(dependencies = []) { return function (constructor) { const originalConstructor = constructor; const injectedConstructor = function (...args) { const resolvedDependencies = dependencies.map(dep => exports.serviceContainer.resolve(dep)); return new originalConstructor(...resolvedDependencies, ...args); }; injectedConstructor.prototype = originalConstructor.prototype; return injectedConstructor; }; } /** * Utility functions for common service patterns */ class ServiceContainerUtils { /** * Register all Semble core services */ static registerCoreServices(container) { // This will be called from the main node files to register all services // Services will be registered in dependency order // Core services (no dependencies) container.registerSingleton('config', () => ({}), []); container.registerSingleton('cache', () => ({}), ['config']); // API services container.registerSingleton('credentials', () => ({}), ['config']); container.registerSingleton('queryService', () => ({}), ['credentials', 'cache']); container.registerSingleton('fieldDiscovery', () => ({}), ['queryService', 'cache']); container.registerSingleton('permissionCheck', () => ({}), ['queryService', 'cache']); container.registerSingleton('validation', () => ({}), ['fieldDiscovery']); // Component services container.registerTransient('resourceSelector', () => ({}), ['fieldDiscovery', 'cache']); container.registerTransient('eventTrigger', () => ({}), []); container.registerTransient('eventAction', () => ({}), []); container.registerTransient('pollTime', () => ({}), []); container.registerSingleton('additionalFields', () => ({}), ['fieldDiscovery']); } /** * Create a service container with all Semble services registered */ static createConfiguredContainer() { const container = new ServiceContainer(); ServiceContainerUtils.registerCoreServices(container); return container; } } exports.ServiceContainerUtils = ServiceContainerUtils;