import { BatchBootstrapOptions, BatchBootstrapResult, BatchInvalidationResult, ContainerOptions, DisposalResult, InstancesStructure, PreloadStructure } from './types'; /** * ═══════════════════════════════════════════════════════════════════════════════ * 🏗️ MULTI-TENANT DEPENDENCY INJECTION CONTAINER * ═══════════════════════════════════════════════════════════════════════════════ * * This Container provides a dependency injection system designed * for multi-tenant applications. Each tenant gets their own isolated service * instances while sharing the same factory definitions. * * Key Features: * • 🔄 Tenant-isolated service instances using AsyncLocalStorage * • ⚡ High-performance caching with LRU eviction * • 🪞 Intelligent proxy system for lazy loading and error handling * • 📊 Built-in performance metrics and debugging tools * • 🛡️ Type-safe service resolution with full TypeScript support * • 🔧 Support for both class constructors and factory functions * * Architecture Overview: * ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ * │ Factories │ -> │ Container │ -> │ Instances │ * │ (Shared Defs) │ │ (Per-tenant) │ │ (Per-tenant) │ * └─────────────────┘ └─────────────────┘ └─────────────────┘ * * Flow: * 1. Define factories once (shared across all tenants) * 2. Bootstrap container with tenant metadata * 3. Services are lazy-loaded and cached per tenant * 4. AsyncLocalStorage provides automatic context isolation * ═══════════════════════════════════════════════════════════════════════════════ */ /** * 🏗️ Multi-Tenant Dependency Injection Container * * The Container is the heart of the multi-tenant service architecture. It manages * service instantiation, caching, and tenant isolation using AsyncLocalStorage. * * @template Defs - Factory definitions record (shared across tenants) * @template TenantMetadata - Type of tenant-specific metadata (DB config, secrets, etc.) * @template TContext - Type of the context returned by the initializer (inferred from initializer return type, defaults to InstancesStructure) * * Key Responsibilities: * 1. 🏭 **Factory Management**: Register and cache service factories * 2. 🔄 **Tenant Isolation**: Each tenant gets isolated service instances * 3. ⚡ **Performance**: Multi-level caching for optimal performance * 4. 🪞 **Lazy Loading**: Services instantiated only when accessed * 5. 🛡️ **Error Handling**: Clear error messages for missing services * 6. 📊 **Observability**: Performance metrics and debugging tools * * Usage Pattern: * ```typescript * // 1. Define your services * const factories = { * database: DatabaseService, * api: { * users: UserApiService, * auth: AuthApiService * } * } * * // 2. Create container with initializer * const container = new Container(factories, async (preload, meta) => { * const db = preload.database('main', meta.connectionString) * return { * database: db, * api: { * users: preload.api.users('users', db), * auth: preload.api.auth('auth', db, meta.jwtSecret) * } * } * }) * * // 3. Bootstrap for a tenant and run code * await container.bootstrap(tenantMeta, async () => { * const { database, api } = container.context * const users = await api.users.getAll() * return users * }) * ``` */ export declare class Container, TenantMetadata, TContext = InstancesStructure> { private readonly factories; private readonly initializer; /** * Service instance cache managers - one per service type * Each manager handles LRU caching for that specific service * Lazy-allocated to save memory for unused services */ private readonly managers; /** * Kill switch: Set of blocked tenant IDs * Blocked tenants are rejected immediately at bootstrap without initialization */ private readonly blockedTenants; /** * Cooldown tracker: tenants whose initializer recently failed * Maps tenant cache key -> expiry timestamp (Date.now() + cooldown) * Prevents retry storms when a tenant's initializer is broken */ private readonly initializerCooldowns; /** * Bootstrap call counter for sampled heap checks * Only check memory every N calls to minimize overhead */ private bootstrapCounter; /** * AsyncLocalStorage provides automatic tenant context isolation * Each async call tree gets its own isolated service instances * Also stores tenant metadata for introspection */ private readonly als; /** * Pre-resolved factory lookup cache for performance * Avoids recursive object traversal on every service access */ private readonly factoryCache; /** * Cached preload proxy to avoid recreating the same proxy structure */ private preloadProxy; /** * Container configuration with sensible defaults */ private readonly options; /** * Inflight promise deduplication for bootstrap operations * Prevents concurrent bootstrap for same tenant from running initializer twice */ private readonly initializerPromises; /** * Tracks in-flight disposal operations per tenant * Bootstrap waits for pending disposal to complete before re-initializing * Prevents duplicate live instances when invalidation overlaps with re-bootstrap */ private readonly disposalPromises; /** * Proxy object cache: reuses proxy objects for the same paths (preload proxy only) * Reduces memory allocation and improves performance */ private readonly proxyCache; /** * Initializer cache: stores initialized instances per tenant with LRU eviction * Avoids re-running the expensive initializer function for the same tenant * Uses tiny-lru for O(1) eviction instead of hand-rolled linear scan */ private readonly initializerCache; /** * High-performance metrics using Uint32Array for better JIT optimization * Indices: [hits, misses, creates, ctx, proxy, initHits, resets, batchOps, batchErrors] * Auto-wraps at 2^32 without overflow checks for maximum performance */ private readonly metrics; /** * Metric indices for Uint32Array */ private static readonly METRIC; /** * Legacy overflow threshold for test compatibility * Note: With Uint32Array, overflow is handled automatically, but tests may mock this */ private MAX_METRIC_VALUE; /** * High-performance metric increment with optional legacy overflow simulation * Uint32Array automatically wraps at 2^32, but we maintain compatibility for tests */ private inc; /** * Emit a structured event if an event handler is configured * No-op if onEvent is not set, keeping zero overhead when unused */ private emit; /** * Create a new Container instance * * @param factories - Service factory definitions (shared across all tenants) * @param initializer - Function that creates tenant-specific service instances * @param options - Configuration options for performance and debugging * * The initializer function receives: * - preload: Proxy object for creating service instances with parameters * - meta: Tenant-specific metadata (DB config, secrets, etc.) * * And should return a structure matching your factory definitions but with * actual service instances instead of factory functions. */ constructor(factories: Defs, initializer: (preload: PreloadStructure, meta: TenantMetadata) => Promise, options?: ContainerOptions); /** * Get or create a cache manager for a service - lazy allocation * Saves memory by only creating caches for services that are actually used * Note: Type safety is enforced at compile time through generics, not runtime */ private getManager; /** * Pre-populate the factory cache by walking the entire factory tree * This eliminates the need for recursive object traversal during runtime */ private preloadFactoryCache; /** * Recursive factory tree walker that builds the flat factory cache * Converts nested object structure to flat dot-notation keys */ private walkFactories; /** * Get the preload proxy for service instantiation * The preload proxy allows you to create services with parameters: * * ```typescript * const db = preload.database('main', connectionString) * const userApi = preload.api.users('users', db, config) * ``` * * This is used during the initialization phase to wire up dependencies */ get preload(): PreloadStructure; /** * Create a proxy that intercepts property access and provides factory functions * * The proxy works by: * 1. Intercepting property access (e.g., preload.database) * 2. Looking up the factory for that path * 3. Returning a function that creates and caches instances * 4. For nested paths, returning another proxy * * This enables natural dot-notation access while maintaining lazy loading */ private createPreloadProxy; /** * Run a function within a specific tenant context (async version) * This is usually called internally by bootstrap, but can be used directly * for testing or advanced use cases */ runWithContext(instances: TContext, tenantMetadata: TenantMetadata, fn: () => Promise): Promise; /** * Run a synchronous function within a specific tenant context * Uses enterWith() to avoid creating extra async frame for sync operations * More efficient for pure synchronous code paths */ runWithContextSync(instances: TContext, tenantMetadata: TenantMetadata, fn: () => T): T; /** * Get the current tenant's service context * * This is the main way to access services within a tenant context: * ```typescript * const { database, api } = container.context * const users = await api.users.getAll() * ``` * * Throws an error if called outside of a tenant context */ get context(): TContext; /** * Simple string hash function for fallback tenant keys * Uses djb2 algorithm - fast and good enough for cache keys * Note: For very large metadata objects, consider upgrading to FNV-1a or crypto.createHash * if collision resistance is critical. Current implementation is optimized for speed. */ private simpleHash; /** * Create a stable cache key from tenant metadata * Uses common tenant properties or hashed JSON as fallback */ private createTenantCacheKey; /** * Get or create initialized instances for a tenant with race condition protection * Uses both result caching and inflight promise deduplication * Implements LRU eviction when cache exceeds maxInitializerCacheSize */ private getOrCreateInstances; /** * Bootstrap the container for a specific tenant and execute a function * * This is the main entry point for tenant-specific operations: * * @param meta - Tenant-specific metadata (DB config, secrets, etc.) * @param fn - Function to execute within the tenant context (optional) * @returns Object containing the initialized instances and function result * * ```typescript * // Example: Process a user request for tenant "acme" * const result = await container.bootstrap(acmeTenantMeta, async () => { * const { api } = container.context * return await api.users.getById(userId) * }) * * console.log(result.instances) // All initialized services * console.log(result.result) // Return value from the function * ``` * * The bootstrap process: * 1. Gets or creates initialized services for this tenant (with caching) * 2. Sets up AsyncLocalStorage context with the service instances * 3. Executes your function within that context * 4. Returns both the instances and your function's result */ bootstrap(meta: TenantMetadata, fn?: () => Promise): Promise<{ instances: TContext; result?: T; }>; /** * Bootstrap multiple tenants in parallel with controlled concurrency * * This method enables efficient initialization of multiple tenants while: * - Controlling concurrency to avoid overwhelming the system * - Isolating errors so one failure doesn't affect others * - Providing progress tracking for long-running operations * - Collecting performance metrics for each operation * * @param tenantBatch - Array of tenant metadata and optional functions to execute * @param options - Options for controlling the batch operation * @returns Array of results for each tenant, including successes and failures * * ```typescript * const results = await container.bootstrapBatch([ * { metadata: tenant1Meta, fn: async () => processТenant1() }, * { metadata: tenant2Meta, fn: async () => processTenant2() }, * { metadata: tenant3Meta } // No function, just bootstrap * ], { * concurrency: 5, * continueOnError: true, * onProgress: (completed, total) => console.log(`${completed}/${total}`) * }) * * // Process results * for (const result of results) { * if (result.status === 'success') { * console.log(`Tenant ${result.metadata.id} initialized in ${result.metrics.duration}ms`) * } else { * console.error(`Tenant ${result.metadata.id} failed:`, result.error) * } * } * ``` */ bootstrapBatch(tenantBatch: Array<{ metadata: TMetadata; fn?: () => Promise; }>, options?: BatchBootstrapOptions): Promise[]>; /** * Invalidate multiple tenant caches in batch * * Efficiently invalidates caches for multiple tenants with proper disposal * and error handling. Useful for bulk updates or maintenance operations. * * @param tenantIds - Array of tenant IDs to invalidate * @param reason - Optional reason for invalidation (for logging) * @param distributed - Whether to propagate invalidation to other instances * @returns Summary of the batch invalidation operation * * ```typescript * const result = await container.invalidateTenantBatch( * ['tenant1', 'tenant2', 'tenant3'], * 'Bulk configuration update', * true // Distribute to other instances * ) * * console.log(`Invalidated ${result.succeeded}/${result.total} tenants`) * if (result.failed > 0) { * console.error('Failed invalidations:', result.errors) * } * ``` */ invalidateTenantBatch(tenantIds: string[], reason?: string, distributed?: boolean): Promise; /** * Invalidate multiple service caches in batch * * @param serviceTypes - Array of service types to invalidate * @param reason - Optional reason for invalidation * @param distributed - Whether to propagate invalidation * @returns Summary of the batch invalidation operation * * ```typescript * const result = await container.invalidateServiceBatch( * ['database', 'api.users', 'api.auth'], * 'Service configuration update' * ) * ``` */ invalidateServiceBatch(serviceTypes: string[], reason?: string, distributed?: boolean): Promise; /** * Get current performance metrics * Converts Uint32Array back to object format for compatibility */ getMetrics(): { cacheHits: number; cacheMisses: number; instanceCreations: number; contextAccesses: number; proxyCacheHits: number; initializerCacheHits: number; batchOperations: number; batchErrors: number; }; /** * Reset all performance metrics to zero * High-performance reset using fill() method */ resetMetrics(): void; /** * Clear all service instance caches with proper disposal support * Calls optional dispose() hooks to prevent memory leaks (sockets, db handles, etc.) */ clearCaches(): void; /** * Async version of clearCaches that properly awaits all disposal operations * Use this method when you need to ensure all resources are fully disposed * before continuing (e.g., during graceful shutdown) * @returns DisposalResult with counts and any errors encountered */ clearCachesAsync(): Promise; /** * Setup distributed cache invalidation system * Connects to Redis pub/sub for coordinating cache invalidation across instances */ private setupDistributedInvalidation; /** * Invalidate all cached data for a specific tenant across all instances * This sends a distributed invalidation message via Redis pub/sub */ invalidateTenantDistributed(tenantId: string, reason?: string): Promise; /** * Invalidate all cached data for a specific service type across all instances */ invalidateServiceDistributed(serviceType: string, reason?: string): Promise; /** * Invalidate all cached data across all instances */ invalidateAllDistributed(reason?: string): Promise; /** * Invalidate cached data for a specific tenant (local only) with disposal support * This only affects the current instance */ private invalidateTenantLocally; /** * Invalidate cached data for a specific service type (local only) with disposal support */ private invalidateServiceLocally; /** * Invalidate all cached data (local only) */ private invalidateAllLocally; /** * Dispose all service instances across all tenants and clear caches * Useful for graceful shutdown and testing cleanup * Note: This also clears all caches to prevent resurrection of disposed services * @returns DisposalResult with counts and any errors encountered during disposal */ disposeAll(): Promise; /** * Get detailed cache statistics for each service * Shows how many instances are cached and the cache limits */ getCacheStats(): Record; /** * Get comprehensive performance statistics * Combines metrics, cache stats, and computed ratios for full observability */ getPerformanceStats(): { cacheStats: Record; totalCacheSize: number; pathCacheSize: number; proxyCacheSize: number; factoryCacheSize: number; initializerCacheSize: number; initializerPromisesSize: number; cacheHitRatio: number; batchSuccessRatio: number; cacheHits: number; cacheMisses: number; instanceCreations: number; contextAccesses: number; proxyCacheHits: number; initializerCacheHits: number; batchOperations: number; batchErrors: number; }; /** * Check if there's an active tenant context * * @returns true if called within a tenant context, false otherwise * * ```typescript * if (container.hasActiveContext()) { * const services = container.context * // Safe to access services * } * ``` */ hasActiveContext(): boolean; /** * Check if a service is available in the current tenant context * * @param servicePath - Dot-notation path to the service (e.g., "api.users") * @returns true if the service exists and is initialized, false otherwise * * ```typescript * if (container.hasService('api.users')) { * const users = container.context.api.users * // Safe to use users service * } * ``` */ hasService(servicePath: string): boolean; /** * Get the current tenant's metadata * * This allows access to tenant-specific configuration, credentials, and other * metadata that was passed to the bootstrap method: * * ```typescript * await container.bootstrap(tenantMeta, async () => { * const meta = container.getCurrentTenantMetadata() * console.log('Current tenant:', meta.id) * console.log('DB URL:', meta.connectionString) * }) * ``` * * @returns The tenant metadata that was passed to bootstrap * @throws Error if called outside of a tenant context */ getCurrentTenantMetadata(): TenantMetadata; /** * Get the current tenant ID from metadata * * This is a convenience method that extracts the tenant ID from the metadata. * It assumes the metadata has an 'id' property (common pattern). * * ```typescript * await container.bootstrap(tenantMeta, async () => { * const tenantId = container.getCurrentTenantId() * console.log('Processing request for tenant:', tenantId) * }) * ``` * * @returns The tenant ID if metadata has an 'id' property, undefined otherwise * @throws Error if called outside of a tenant context */ getCurrentTenantId(): string | undefined; /** * Get a list of all available services in the current tenant context * Useful for debugging, testing, or dynamic service discovery * * @returns Array of dot-notation service paths (e.g., ["database", "api.users", "api.auth"]) */ getAvailableServices(): string[]; /** * Recursively collect all service paths from the current context * Helper method for getAvailableServices() */ private collectServices; /** * Block a tenant from bootstrapping. Blocked tenants are rejected immediately. * Use this for: runaway crons, infinite loops, compromised keys, legal holds. */ blockTenant(tenantId: string): void; /** * Unblock a previously blocked tenant, restoring normal bootstrap behavior. */ unblockTenant(tenantId: string): void; /** * Check if a tenant is currently blocked. */ isTenantBlocked(tenantId: string): boolean; /** * Get the set of all currently blocked tenant IDs. */ getBlockedTenants(): ReadonlySet; }