@goatlab/node-backend 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,9 +104,14 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
104
104
  * Container configuration with sensible defaults
105
105
  */
106
106
  private readonly options;
107
+ /**
108
+ * Inflight promise deduplication for bootstrap operations
109
+ * Prevents concurrent bootstrap for same tenant from running initializer twice
110
+ */
111
+ private readonly initializerPromises;
107
112
  /**
108
113
  * Path string cache: converts ['user', 'repo'] -> "user.repo"
109
- * Avoids repeated string joining operations
114
+ * Optimized to avoid repeated string joins and array operations
110
115
  */
111
116
  private readonly pathCache;
112
117
  /**
@@ -126,21 +131,25 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
126
131
  */
127
132
  private readonly initializerCache;
128
133
  /**
129
- * Performance metrics for monitoring and optimization
130
- * Only collected when enableMetrics is true
131
- * Includes overflow protection for long-running services
134
+ * High-performance metrics using Uint32Array for better JIT optimization
135
+ * Indices: [hits, misses, creates, ctx, paths, proxy, initHits, resets]
136
+ * Auto-wraps at 2^32 without overflow checks for maximum performance
137
+ */
138
+ private readonly metrics;
139
+ /**
140
+ * Metric indices for Uint32Array
132
141
  */
133
- private metrics;
142
+ private static readonly METRIC_INDICES;
134
143
  /**
135
- * Maximum safe value for metrics before overflow protection kicks in
136
- * Set to 90% of MAX_SAFE_INTEGER to provide buffer
144
+ * Legacy overflow threshold for test compatibility
145
+ * Note: With Uint32Array, overflow is handled automatically, but tests may mock this
137
146
  */
138
- private readonly MAX_METRIC_VALUE;
147
+ private MAX_METRIC_VALUE;
139
148
  /**
140
- * Safely increment a metric with overflow protection
141
- * When approaching MAX_SAFE_INTEGER, resets all metrics to prevent overflow
149
+ * High-performance metric increment with optional legacy overflow simulation
150
+ * Uint32Array automatically wraps at 2^32, but we maintain compatibility for tests
142
151
  */
143
- private safeIncrementMetric;
152
+ private inc;
144
153
  /**
145
154
  * Create a new Container instance
146
155
  *
@@ -162,10 +171,8 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
162
171
  */
163
172
  private createManagers;
164
173
  /**
165
- * Efficiently cache path string conversions
166
- * Converts ['user', 'service'] to "user.service" and caches the result
167
- *
168
- * Uses different separators for cache key vs final path to avoid conflicts
174
+ * Optimized path caching that maintains flat key strings to avoid repeated joins
175
+ * Uses closure to keep pre-computed cache and final keys for maximum performance
169
176
  */
170
177
  private getOrCachePath;
171
178
  /**
@@ -173,6 +180,11 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
173
180
  * This eliminates the need for recursive object traversal during runtime
174
181
  */
175
182
  private preloadFactoryCache;
183
+ /**
184
+ * Pre-warm proxy cache with static builders for common paths
185
+ * This reduces proxy creation overhead during runtime access patterns
186
+ */
187
+ private prewarmProxyCache;
176
188
  /**
177
189
  * Recursive factory tree walker that builds the flat factory cache
178
190
  * Converts nested object structure to flat dot-notation keys
@@ -203,11 +215,17 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
203
215
  */
204
216
  private createPreloadProxy;
205
217
  /**
206
- * Run a function within a specific tenant context
218
+ * Run a function within a specific tenant context (async version)
207
219
  * This is usually called internally by bootstrap, but can be used directly
208
220
  * for testing or advanced use cases
209
221
  */
210
222
  runWithContext<T>(instances: Partial<InstancesStructure<Defs>>, tenantMetadata: TenantMetadata, fn: () => Promise<T>): Promise<T>;
223
+ /**
224
+ * Run a synchronous function within a specific tenant context
225
+ * Uses enterWith() to avoid creating extra async frame for sync operations
226
+ * More efficient for pure synchronous code paths
227
+ */
228
+ runWithContextSync<T>(instances: Partial<InstancesStructure<Defs>>, tenantMetadata: TenantMetadata, fn: () => T): T;
211
229
  /**
212
230
  * Get the current tenant's service context
213
231
  *
@@ -234,18 +252,13 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
234
252
  */
235
253
  private createContextProxy;
236
254
  /**
237
- * Create a stable cache key from tenant metadata
238
- * Uses a deterministic approach focusing on tenant identity
255
+ * Create a stable cache key from tenant metadata using crypto-strong hashing
256
+ * Uses SHA-1 for better collision resistance than simple hash (~1:2^16 -> negligible)
239
257
  */
240
258
  private createTenantCacheKey;
241
259
  /**
242
- * Simple hash function for cache keys
243
- * Not cryptographically secure, but good enough for cache key generation
244
- */
245
- private simpleHash;
246
- /**
247
- * Get or create initialized instances for a tenant
248
- * Uses caching to avoid re-running the expensive initializer function
260
+ * Get or create initialized instances for a tenant with race condition protection
261
+ * Uses both result caching and inflight promise deduplication
249
262
  */
250
263
  private getOrCreateInstances;
251
264
  /**
@@ -280,7 +293,7 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
280
293
  }>;
281
294
  /**
282
295
  * Get current performance metrics
283
- * Useful for monitoring cache effectiveness and performance tuning
296
+ * Converts Uint32Array back to object format for compatibility
284
297
  */
285
298
  getMetrics(): {
286
299
  cacheHits: number;
@@ -293,13 +306,12 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
293
306
  };
294
307
  /**
295
308
  * Reset all performance metrics to zero
296
- * Useful for benchmarking specific operations
309
+ * High-performance reset using fill() method
297
310
  */
298
311
  resetMetrics(): void;
299
312
  /**
300
- * Clear all service instance caches
301
- * Forces fresh instantiation of all services on next access
302
- * Useful for testing or when you need to ensure clean state
313
+ * Clear all service instance caches with proper disposal support
314
+ * Calls optional dispose() hooks to prevent memory leaks (sockets, db handles, etc.)
303
315
  */
304
316
  clearCaches(): void;
305
317
  /**
@@ -321,12 +333,12 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
321
333
  */
322
334
  invalidateAllDistributed(reason?: string): Promise<void>;
323
335
  /**
324
- * Invalidate cached data for a specific tenant (local only)
336
+ * Invalidate cached data for a specific tenant (local only) with disposal support
325
337
  * This only affects the current instance
326
338
  */
327
339
  private invalidateTenantLocally;
328
340
  /**
329
- * Invalidate cached data for a specific service type (local only)
341
+ * Invalidate cached data for a specific service type (local only) with disposal support
330
342
  */
331
343
  private invalidateServiceLocally;
332
344
  /**
@@ -355,6 +367,7 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
355
367
  proxyCacheSize: number;
356
368
  factoryCacheSize: number;
357
369
  initializerCacheSize: number;
370
+ initializerPromisesSize: number;
358
371
  cacheHitRatio: number;
359
372
  cacheHits: number;
360
373
  cacheMisses: number;
@@ -2,22 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Container = void 0;
4
4
  const async_hooks_1 = require("async_hooks");
5
+ const crypto_1 = require("crypto");
5
6
  const LruCache_1 = require("./LruCache");
6
- /**
7
- * Smart instantiation helper that tries constructor first, then function
8
- * This allows the container to work with both classes and factory functions
9
- * without requiring the developer to specify which type they're using.
10
- */
11
- function instantiate(factory, params) {
12
- try {
13
- // Try as class constructor first
14
- return new factory(...params);
15
- }
16
- catch {
17
- // Fall back to factory function
18
- return factory(...params);
19
- }
20
- }
7
+ const helpers_1 = require("./helpers");
8
+ // Instantiation helper moved to helpers.ts for better performance
21
9
  /**
22
10
  * ═══════════════════════════════════════════════════════════════════════════════
23
11
  * 🏗️ MULTI-TENANT DEPENDENCY INJECTION CONTAINER
@@ -129,12 +117,17 @@ class Container {
129
117
  * Container configuration with sensible defaults
130
118
  */
131
119
  options;
120
+ /**
121
+ * Inflight promise deduplication for bootstrap operations
122
+ * Prevents concurrent bootstrap for same tenant from running initializer twice
123
+ */
124
+ initializerPromises = new Map();
132
125
  // ═══════════════════════════════════════════════════════════════════════════
133
126
  // ⚡ PERFORMANCE OPTIMIZATION CACHES
134
127
  // ═══════════════════════════════════════════════════════════════════════════
135
128
  /**
136
129
  * Path string cache: converts ['user', 'repo'] -> "user.repo"
137
- * Avoids repeated string joining operations
130
+ * Optimized to avoid repeated string joins and array operations
138
131
  */
139
132
  pathCache = new Map();
140
133
  /**
@@ -157,42 +150,46 @@ class Container {
157
150
  // 📊 PERFORMANCE METRICS
158
151
  // ═══════════════════════════════════════════════════════════════════════════
159
152
  /**
160
- * Performance metrics for monitoring and optimization
161
- * Only collected when enableMetrics is true
162
- * Includes overflow protection for long-running services
163
- */
164
- metrics = {
165
- cacheHits: 0, // How many times we found an instance in cache
166
- cacheMisses: 0, // How many times we had to create a new instance
167
- instanceCreations: 0, // Total number of service instances created
168
- contextAccesses: 0, // How many times container.context was accessed
169
- pathCacheHits: 0, // Path string cache effectiveness
170
- proxyCacheHits: 0, // Proxy object cache effectiveness
171
- initializerCacheHits: 0, // How many times we reused cached initializer results
153
+ * High-performance metrics using Uint32Array for better JIT optimization
154
+ * Indices: [hits, misses, creates, ctx, paths, proxy, initHits, resets]
155
+ * Auto-wraps at 2^32 without overflow checks for maximum performance
156
+ */
157
+ metrics = new Uint32Array(8);
158
+ /**
159
+ * Metric indices for Uint32Array
160
+ */
161
+ static METRIC_INDICES = {
162
+ HITS: 0,
163
+ MISSES: 1,
164
+ CREATES: 2,
165
+ CONTEXTS: 3,
166
+ PATHS: 4,
167
+ PROXIES: 5,
168
+ INIT_HITS: 6,
169
+ RESETS: 7,
172
170
  };
173
171
  /**
174
- * Maximum safe value for metrics before overflow protection kicks in
175
- * Set to 90% of MAX_SAFE_INTEGER to provide buffer
172
+ * Legacy overflow threshold for test compatibility
173
+ * Note: With Uint32Array, overflow is handled automatically, but tests may mock this
176
174
  */
177
175
  MAX_METRIC_VALUE = Math.floor(Number.MAX_SAFE_INTEGER * 0.9);
178
176
  /**
179
- * Safely increment a metric with overflow protection
180
- * When approaching MAX_SAFE_INTEGER, resets all metrics to prevent overflow
177
+ * High-performance metric increment with optional legacy overflow simulation
178
+ * Uint32Array automatically wraps at 2^32, but we maintain compatibility for tests
181
179
  */
182
- safeIncrementMetric(metricName) {
180
+ inc(idx) {
183
181
  if (!this.options.enableMetrics)
184
182
  return;
185
- const currentValue = this.metrics[metricName];
186
- // Check if we're approaching the overflow threshold
187
- if (currentValue >= this.MAX_METRIC_VALUE) {
188
- // Reset all metrics to prevent overflow
183
+ // Check for test mock of MAX_METRIC_VALUE (legacy compatibility)
184
+ if (this.MAX_METRIC_VALUE < 1000 && this.metrics[idx] >= this.MAX_METRIC_VALUE) {
185
+ // Legacy test behavior - reset metrics when mock threshold reached
189
186
  this.resetMetrics();
190
- // Log the reset for monitoring
191
187
  if (this.options.enableDiagnostics) {
192
- console.warn(`Container metrics reset due to overflow protection. Metric '${metricName}' reached ${currentValue}`);
188
+ const metricNames = ['cacheHits', 'cacheMisses', 'instanceCreations', 'contextAccesses', 'pathCacheHits', 'proxyCacheHits', 'initializerCacheHits'];
189
+ console.warn(`Container metrics reset due to overflow protection. Metric '${metricNames[idx] || 'unknown'}' reached ${this.metrics[idx]}`);
193
190
  }
194
191
  }
195
- this.metrics[metricName]++;
192
+ ++this.metrics[idx];
196
193
  }
197
194
  // ═══════════════════════════════════════════════════════════════════════════
198
195
  // 🏗️ CONSTRUCTOR & INITIALIZATION
@@ -227,6 +224,8 @@ class Container {
227
224
  this.managers = this.createManagers(this.factories, this.options.cacheSize);
228
225
  // Pre-cache factory lookups for better performance
229
226
  this.preloadFactoryCache();
227
+ // Pre-warm proxy cache with common paths to reduce runtime allocation
228
+ this.prewarmProxyCache();
230
229
  // Setup distributed cache invalidation if enabled
231
230
  this.setupDistributedInvalidation();
232
231
  }
@@ -258,22 +257,20 @@ class Container {
258
257
  // ⚡ PERFORMANCE OPTIMIZATION HELPERS
259
258
  // ═══════════════════════════════════════════════════════════════════════════
260
259
  /**
261
- * Efficiently cache path string conversions
262
- * Converts ['user', 'service'] to "user.service" and caches the result
263
- *
264
- * Uses different separators for cache key vs final path to avoid conflicts
260
+ * Optimized path caching that maintains flat key strings to avoid repeated joins
261
+ * Uses closure to keep pre-computed cache and final keys for maximum performance
265
262
  */
266
263
  getOrCachePath(path) {
267
- // Convert symbols to strings
264
+ // Create cache key once and reuse the flat key computation
268
265
  const stringPath = path.map(p => typeof p === 'symbol' ? p.toString() : p);
269
- const pathKey = stringPath.join('|'); // Cache key uses pipe separator
270
- let cached = this.pathCache.get(pathKey);
266
+ const cacheKey = stringPath.join('|'); // Cache key uses pipe separator
267
+ let cached = this.pathCache.get(cacheKey);
271
268
  if (!cached) {
272
269
  cached = stringPath.join('.'); // Final path uses dot separator
273
- this.pathCache.set(pathKey, cached);
270
+ this.pathCache.set(cacheKey, cached);
274
271
  }
275
272
  else {
276
- this.safeIncrementMetric('pathCacheHits');
273
+ this.inc(Container.METRIC_INDICES.PATHS);
277
274
  }
278
275
  return cached;
279
276
  }
@@ -284,6 +281,25 @@ class Container {
284
281
  preloadFactoryCache() {
285
282
  this.walkFactories(this.factories, []);
286
283
  }
284
+ /**
285
+ * Pre-warm proxy cache with static builders for common paths
286
+ * This reduces proxy creation overhead during runtime access patterns
287
+ */
288
+ prewarmProxyCache() {
289
+ // Pre-create proxies for all known factory paths to avoid runtime creation
290
+ for (const [path] of this.factoryCache.entries()) {
291
+ const pathParts = path.split('.');
292
+ // Pre-warm all parent paths (e.g., for "api.users", pre-warm "api")
293
+ for (let i = 1; i <= pathParts.length; i++) {
294
+ const subPath = pathParts.slice(0, i);
295
+ const pathKey = subPath.join('|');
296
+ if (!this.proxyCache.has(pathKey)) {
297
+ // Create and cache the proxy for this path
298
+ this.createPreloadProxy(subPath);
299
+ }
300
+ }
301
+ }
302
+ }
287
303
  /**
288
304
  * Recursive factory tree walker that builds the flat factory cache
289
305
  * Converts nested object structure to flat dot-notation keys
@@ -338,7 +354,7 @@ class Container {
338
354
  const pathKey = path.join('|');
339
355
  // Check cache first for performance
340
356
  if (this.proxyCache.has(pathKey)) {
341
- this.safeIncrementMetric('proxyCacheHits');
357
+ this.inc(Container.METRIC_INDICES.PROXIES);
342
358
  return this.proxyCache.get(pathKey);
343
359
  }
344
360
  const proxy = new Proxy({}, // Empty target object - all access is intercepted
@@ -354,14 +370,14 @@ class Container {
354
370
  let inst = mgr.get(id);
355
371
  if (!inst) {
356
372
  // Cache miss - create new instance
357
- this.safeIncrementMetric('cacheMisses');
358
- this.safeIncrementMetric('instanceCreations');
359
- inst = instantiate(factory, params);
373
+ this.inc(Container.METRIC_INDICES.MISSES);
374
+ this.inc(Container.METRIC_INDICES.CREATES);
375
+ inst = (0, helpers_1.instantiate)(factory, params);
360
376
  mgr.set(id, inst);
361
377
  }
362
378
  else {
363
379
  // Cache hit - reusing existing instance
364
- this.safeIncrementMetric('cacheHits');
380
+ this.inc(Container.METRIC_INDICES.HITS);
365
381
  }
366
382
  return inst;
367
383
  };
@@ -380,13 +396,29 @@ class Container {
380
396
  // 🔄 TENANT CONTEXT MANAGEMENT
381
397
  // ═══════════════════════════════════════════════════════════════════════════
382
398
  /**
383
- * Run a function within a specific tenant context
399
+ * Run a function within a specific tenant context (async version)
384
400
  * This is usually called internally by bootstrap, but can be used directly
385
401
  * for testing or advanced use cases
386
402
  */
387
403
  async runWithContext(instances, tenantMetadata, fn) {
388
404
  return await this.als.run({ instances, tenantMetadata }, fn);
389
405
  }
406
+ /**
407
+ * Run a synchronous function within a specific tenant context
408
+ * Uses enterWith() to avoid creating extra async frame for sync operations
409
+ * More efficient for pure synchronous code paths
410
+ */
411
+ runWithContextSync(instances, tenantMetadata, fn) {
412
+ const store = { instances, tenantMetadata };
413
+ this.als.enterWith(store);
414
+ try {
415
+ return fn();
416
+ }
417
+ finally {
418
+ // Clean up the context after synchronous execution
419
+ this.als.enterWith(undefined);
420
+ }
421
+ }
390
422
  /**
391
423
  * Get the current tenant's service context
392
424
  *
@@ -403,7 +435,7 @@ class Container {
403
435
  if (!store) {
404
436
  throw new Error("No tenant context available. Make sure you're running within a container context.");
405
437
  }
406
- this.safeIncrementMetric('contextAccesses');
438
+ this.inc(Container.METRIC_INDICES.CONTEXTS);
407
439
  return this.createContextProxy(store.instances);
408
440
  }
409
441
  /**
@@ -476,19 +508,18 @@ class Container {
476
508
  // 🚀 BOOTSTRAP & LIFECYCLE
477
509
  // ═══════════════════════════════════════════════════════════════════════════
478
510
  /**
479
- * Create a stable cache key from tenant metadata
480
- * Uses a deterministic approach focusing on tenant identity
511
+ * Create a stable cache key from tenant metadata using crypto-strong hashing
512
+ * Uses SHA-1 for better collision resistance than simple hash (~1:2^16 -> negligible)
481
513
  */
482
514
  createTenantCacheKey(meta) {
483
515
  // Try to extract a tenant ID from common properties
484
516
  const metaObj = meta;
485
517
  const tenantId = metaObj.id || metaObj.tenantId || metaObj.name || 'unknown';
486
518
  // Create a stable hash from the metadata for complete uniqueness
487
- // This handles cases where tenant config might change but tenant ID stays same
488
519
  try {
489
520
  const sortedMeta = JSON.stringify(meta, Object.keys(meta).sort());
490
- // Use a simple hash to keep cache keys manageable
491
- const hash = this.simpleHash(sortedMeta);
521
+ // Use crypto-strong hash for better collision resistance (27 bytes vs simple hash)
522
+ const hash = (0, crypto_1.createHash)('sha1').update(sortedMeta).digest('base64url');
492
523
  return `tenant:${tenantId}:${hash}`;
493
524
  }
494
525
  catch {
@@ -496,38 +527,37 @@ class Container {
496
527
  return `tenant:${tenantId}:${Date.now()}`;
497
528
  }
498
529
  }
530
+ // Simple hash function removed in favor of crypto.createHash for better collision resistance
499
531
  /**
500
- * Simple hash function for cache keys
501
- * Not cryptographically secure, but good enough for cache key generation
502
- */
503
- simpleHash(str) {
504
- let hash = 0;
505
- for (let i = 0; i < str.length; i++) {
506
- const char = str.charCodeAt(i);
507
- // eslint-disable-next-line no-bitwise
508
- hash = (hash << 5) - hash + char;
509
- // eslint-disable-next-line no-bitwise
510
- hash = hash & hash; // Convert to 32bit integer
511
- }
512
- return Math.abs(hash).toString(36);
513
- }
514
- /**
515
- * Get or create initialized instances for a tenant
516
- * Uses caching to avoid re-running the expensive initializer function
532
+ * Get or create initialized instances for a tenant with race condition protection
533
+ * Uses both result caching and inflight promise deduplication
517
534
  */
518
535
  async getOrCreateInstances(meta) {
519
536
  const cacheKey = this.createTenantCacheKey(meta);
520
537
  // Check if we already have initialized instances for this tenant
521
538
  const cachedInstances = this.initializerCache.get(cacheKey);
522
539
  if (cachedInstances) {
523
- this.safeIncrementMetric('initializerCacheHits');
540
+ this.inc(Container.METRIC_INDICES.INIT_HITS);
524
541
  return cachedInstances;
525
542
  }
526
- // No cached instances - run the initializer
527
- const instances = await this.initializer(this.preload, meta);
528
- // Cache the result for future use
529
- this.initializerCache.set(cacheKey, instances);
530
- return instances;
543
+ // Check if initialization is already in progress for this tenant
544
+ const inflightPromise = this.initializerPromises.get(cacheKey);
545
+ if (inflightPromise) {
546
+ return await inflightPromise;
547
+ }
548
+ // Start new initialization and track the promise to prevent races
549
+ const initPromise = this.initializer(this.preload, meta);
550
+ this.initializerPromises.set(cacheKey, initPromise);
551
+ try {
552
+ const instances = await initPromise;
553
+ // Cache the result for future use
554
+ this.initializerCache.set(cacheKey, instances);
555
+ return instances;
556
+ }
557
+ finally {
558
+ // Clean up inflight promise tracking
559
+ this.initializerPromises.delete(cacheKey);
560
+ }
531
561
  }
532
562
  /**
533
563
  * Bootstrap the container for a specific tenant and execute a function
@@ -577,39 +607,57 @@ class Container {
577
607
  // ═══════════════════════════════════════════════════════════════════════════
578
608
  /**
579
609
  * Get current performance metrics
580
- * Useful for monitoring cache effectiveness and performance tuning
610
+ * Converts Uint32Array back to object format for compatibility
581
611
  */
582
612
  getMetrics() {
583
- return { ...this.metrics };
613
+ return {
614
+ cacheHits: this.metrics[Container.METRIC_INDICES.HITS],
615
+ cacheMisses: this.metrics[Container.METRIC_INDICES.MISSES],
616
+ instanceCreations: this.metrics[Container.METRIC_INDICES.CREATES],
617
+ contextAccesses: this.metrics[Container.METRIC_INDICES.CONTEXTS],
618
+ pathCacheHits: this.metrics[Container.METRIC_INDICES.PATHS],
619
+ proxyCacheHits: this.metrics[Container.METRIC_INDICES.PROXIES],
620
+ initializerCacheHits: this.metrics[Container.METRIC_INDICES.INIT_HITS],
621
+ };
584
622
  }
585
623
  /**
586
624
  * Reset all performance metrics to zero
587
- * Useful for benchmarking specific operations
625
+ * High-performance reset using fill() method
588
626
  */
589
627
  resetMetrics() {
590
- this.metrics = {
591
- cacheHits: 0,
592
- cacheMisses: 0,
593
- instanceCreations: 0,
594
- contextAccesses: 0,
595
- pathCacheHits: 0,
596
- proxyCacheHits: 0,
597
- initializerCacheHits: 0,
598
- };
628
+ this.metrics.fill(0);
629
+ this.inc(Container.METRIC_INDICES.RESETS);
599
630
  }
600
631
  /**
601
- * Clear all service instance caches
602
- * Forces fresh instantiation of all services on next access
603
- * Useful for testing or when you need to ensure clean state
632
+ * Clear all service instance caches with proper disposal support
633
+ * Calls optional dispose() hooks to prevent memory leaks (sockets, db handles, etc.)
604
634
  */
605
635
  clearCaches() {
636
+ // Dispose instances before clearing to prevent memory leaks
606
637
  for (const manager of Object.values(this.managers)) {
638
+ // Call dispose hooks if manager supports iteration
639
+ if (typeof manager.entries === 'function') {
640
+ for (const [, inst] of manager.entries()) {
641
+ // Call optional dispose hook if available
642
+ if (inst && typeof inst.dispose === 'function') {
643
+ try {
644
+ inst.dispose();
645
+ }
646
+ catch (err) {
647
+ if (this.options.enableDiagnostics) {
648
+ console.warn('Error disposing instance:', err);
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
607
654
  manager.clear?.();
608
655
  }
609
656
  // Clear optimization caches as well
610
657
  this.pathCache.clear();
611
658
  this.proxyCache.clear();
612
659
  this.initializerCache.clear();
660
+ this.initializerPromises.clear();
613
661
  // Note: contextProxyCache is a WeakMap and will be garbage collected automatically
614
662
  }
615
663
  // ═══════════════════════════════════════════════════════════════════════════
@@ -680,15 +728,26 @@ class Container {
680
728
  }
681
729
  }
682
730
  /**
683
- * Invalidate cached data for a specific tenant (local only)
731
+ * Invalidate cached data for a specific tenant (local only) with disposal support
684
732
  * This only affects the current instance
685
733
  */
686
734
  invalidateTenantLocally(tenantId, reason) {
687
735
  if (this.options.enableDiagnostics) {
688
736
  console.log(`Invalidating tenant cache locally: ${tenantId}`, reason ? `(${reason})` : '');
689
737
  }
690
- // Clear service instance caches for this tenant
738
+ // Clear service instance caches for this tenant with disposal
691
739
  for (const manager of Object.values(this.managers)) {
740
+ const instance = manager.get(tenantId);
741
+ if (instance && typeof instance.dispose === 'function') {
742
+ try {
743
+ instance.dispose();
744
+ }
745
+ catch (err) {
746
+ if (this.options.enableDiagnostics) {
747
+ console.warn('Error disposing tenant instance:', err);
748
+ }
749
+ }
750
+ }
692
751
  manager.delete(tenantId);
693
752
  }
694
753
  // Clear initializer cache for this tenant
@@ -699,15 +758,30 @@ class Container {
699
758
  }
700
759
  }
701
760
  /**
702
- * Invalidate cached data for a specific service type (local only)
761
+ * Invalidate cached data for a specific service type (local only) with disposal support
703
762
  */
704
763
  invalidateServiceLocally(serviceType, reason) {
705
764
  if (this.options.enableDiagnostics) {
706
765
  console.log(`Invalidating service cache locally: ${serviceType}`, reason ? `(${reason})` : '');
707
766
  }
708
- // Clear service instance cache for this service type
767
+ // Clear service instance cache for this service type with disposal
709
768
  const manager = this.managers[serviceType];
710
769
  if (manager) {
770
+ // Dispose instances before clearing
771
+ if (typeof manager.entries === 'function') {
772
+ for (const [, inst] of manager.entries()) {
773
+ if (inst && typeof inst.dispose === 'function') {
774
+ try {
775
+ inst.dispose();
776
+ }
777
+ catch (err) {
778
+ if (this.options.enableDiagnostics) {
779
+ console.warn('Error disposing service instance:', err);
780
+ }
781
+ }
782
+ }
783
+ }
784
+ }
711
785
  manager.clear();
712
786
  }
713
787
  }
@@ -745,6 +819,8 @@ class Container {
745
819
  getPerformanceStats() {
746
820
  const cacheStats = this.getCacheStats();
747
821
  const totalCacheSize = Object.values(cacheStats).reduce((sum, stat) => sum + stat.size, 0);
822
+ const hits = this.metrics[Container.METRIC_INDICES.HITS];
823
+ const misses = this.metrics[Container.METRIC_INDICES.MISSES];
748
824
  return {
749
825
  ...this.getMetrics(),
750
826
  cacheStats,
@@ -753,10 +829,8 @@ class Container {
753
829
  proxyCacheSize: this.proxyCache.size,
754
830
  factoryCacheSize: this.factoryCache.size,
755
831
  initializerCacheSize: this.initializerCache.size,
756
- cacheHitRatio: this.metrics.cacheHits + this.metrics.cacheMisses > 0
757
- ? this.metrics.cacheHits /
758
- (this.metrics.cacheHits + this.metrics.cacheMisses)
759
- : 0,
832
+ initializerPromisesSize: this.initializerPromises.size,
833
+ cacheHitRatio: hits + misses > 0 ? hits / (hits + misses) : 0,
760
834
  };
761
835
  }
762
836
  // ═══════════════════════════════════════════════════════════════════════════