@goatlab/node-backend 0.2.6 → 1.0.0

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.
Files changed (34) hide show
  1. package/README.md +146 -14
  2. package/dist/container/Container.d.ts +86 -2
  3. package/dist/container/Container.js +260 -10
  4. package/dist/container/Container.js.map +1 -1
  5. package/dist/container/examples/batch-operations.example.d.ts +1 -0
  6. package/dist/container/examples/batch-operations.example.js +165 -0
  7. package/dist/container/examples/batch-operations.example.js.map +1 -0
  8. package/dist/container/types.d.ts +50 -0
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/server/bootstraps/getExpressTrpcApp.js +69 -7
  12. package/dist/server/bootstraps/getExpressTrpcApp.js.map +1 -1
  13. package/dist/server/middleware/memoryMonitor.example.d.ts +1 -0
  14. package/dist/server/middleware/memoryMonitor.example.js +109 -0
  15. package/dist/server/middleware/memoryMonitor.example.js.map +1 -0
  16. package/dist/server/middleware/memoryMonitor.middleware.d.ts +42 -0
  17. package/dist/server/middleware/memoryMonitor.middleware.js +134 -0
  18. package/dist/server/middleware/memoryMonitor.middleware.js.map +1 -0
  19. package/dist/server/services/secrets/examples/container-preload.example.d.ts +1 -0
  20. package/dist/server/services/secrets/examples/container-preload.example.js +148 -0
  21. package/dist/server/services/secrets/examples/container-preload.example.js.map +1 -0
  22. package/dist/server/services/secrets/index.d.ts +1 -0
  23. package/dist/server/services/secrets/index.js +6 -0
  24. package/dist/server/services/secrets/index.js.map +1 -0
  25. package/dist/server/services/secrets/secret.service.d.ts +46 -6
  26. package/dist/server/services/secrets/secret.service.js +256 -30
  27. package/dist/server/services/secrets/secret.service.js.map +1 -1
  28. package/dist/server/services/translations/translation.model.js +2 -1
  29. package/dist/server/services/translations/translation.model.js.map +1 -1
  30. package/dist/server/services/translations/translation.service.d.ts +8 -1
  31. package/dist/server/services/translations/translation.service.js +123 -13
  32. package/dist/server/services/translations/translation.service.js.map +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -62,38 +62,88 @@ const result = await cache.remember('expensive-op', 300000, async () => {
62
62
 
63
63
  ## Secret Management
64
64
 
65
- The `SecretService` provides secure secret management with support for multiple backends:
65
+ The `SecretService` provides secure secret management with support for multiple backends and preloading:
66
66
 
67
67
  ```typescript
68
68
  import { SecretService } from '@goatlab/node-backend'
69
69
 
70
- // File-based encrypted secrets
71
- const fileSecrets = new SecretService('FILE', '/path/to/secrets.json')
70
+ // File-based encrypted secrets with TTL caching
71
+ const fileSecrets = new SecretService({
72
+ provider: 'FILE',
73
+ location: '/path/to/secrets.json',
74
+ encryptionKey: process.env.ENCRYPTION_KEY,
75
+ cacheTTL: 300000 // 5 minutes (optional, default: 5 minutes)
76
+ })
72
77
 
73
78
  // HashiCorp Vault integration
74
- const vaultSecrets = new SecretService('VAULT', 'my-app/secrets')
79
+ const vaultSecrets = new SecretService({
80
+ provider: 'VAULT',
81
+ location: 'my-app/secrets',
82
+ encryptionKey: process.env.ENCRYPTION_KEY
83
+ })
75
84
 
76
- // Environment variables (new!)
77
- const envSecrets = new SecretService('ENV', 'APP') // Loads APP_* env vars
78
- const allEnvSecrets = new SecretService('ENV', '') // Loads all env vars
85
+ // Environment variables
86
+ const envSecrets = new SecretService({
87
+ provider: 'ENV',
88
+ location: 'APP', // Loads APP_* env vars
89
+ encryptionKey: process.env.ENCRYPTION_KEY // Now supports encryption for all providers
90
+ })
79
91
 
80
- // Usage
81
- await fileSecrets.loadSecrets()
82
- const apiKey = await fileSecrets.getSecret('API_KEY')
83
- const config = await fileSecrets.getSecretJson('CONFIG')
92
+ // Preload secrets for synchronous access (new!)
93
+ await fileSecrets.preload()
94
+ const apiKey = fileSecrets.getSecretSync('API_KEY') // Synchronous!
95
+ const config = fileSecrets.getSecretJsonSync('CONFIG') // Synchronous!
96
+
97
+ // Async operations still available
98
+ const apiKeyAsync = await fileSecrets.getSecret('API_KEY')
99
+ const configAsync = await fileSecrets.getSecretJson('CONFIG')
84
100
 
85
101
  // Store secrets (FILE and VAULT providers)
86
102
  await fileSecrets.storeSecrets({ API_KEY: 'secret-value' })
103
+
104
+ // Manual cache cleanup
105
+ SecretService.cleanupExpiredCache()
106
+
107
+ // Dispose when done (stops file watching)
108
+ fileSecrets.dispose()
87
109
  ```
88
110
 
89
111
  ### Secret Provider Features
90
112
 
91
- - **FILE**: Encrypted local file storage using AES encryption
113
+ - **FILE**: Encrypted local file storage using AES encryption with file watching
92
114
  - **VAULT**: HashiCorp Vault integration with automatic token management
93
- - **ENV**: Runtime environment variable access with optional prefix filtering
94
- - **Caching**: Automatic in-memory caching for improved performance
115
+ - **ENV**: Runtime environment variable access with encryption support
116
+ - **Preloading**: Load secrets once async, access synchronously afterward
117
+ - **Per-Tenant Encryption**: Each tenant can have its own encryption key
118
+ - **Automatic Invalidation**: File changes trigger automatic reload (FILE provider)
119
+ - **TTL Caching**: Configurable time-to-live caching with automatic expiration
95
120
  - **Type Safety**: Generic type support for JSON secrets
96
121
 
122
+ ### Preloading Pattern with Container
123
+
124
+ ```typescript
125
+ const container = new Container(factories, async (preload, meta) => {
126
+ // Create and preload secret service
127
+ const secretService = preload.secrets(meta.tenantId, {
128
+ provider: 'FILE',
129
+ location: meta.secretsLocation,
130
+ encryptionKey: meta.encryptionKey
131
+ })
132
+
133
+ await secretService.preload() // Load once async
134
+
135
+ // Use sync methods for instant access
136
+ const dbUrl = secretService.getSecretSync('DATABASE_URL')
137
+ const apiKey = secretService.getSecretSync('API_KEY')
138
+
139
+ // Create other services with preloaded secrets
140
+ const database = preload.database(meta.tenantId, { url: dbUrl })
141
+ const api = preload.api(meta.tenantId, { apiKey })
142
+
143
+ return { secrets: secretService, database, api }
144
+ })
145
+ ```
146
+
97
147
  ## Express + tRPC Integration
98
148
 
99
149
  Helper for creating Express applications with tRPC integration:
@@ -117,6 +167,88 @@ const app = getExpressTrpcApp({
117
167
  })
118
168
  ```
119
169
 
170
+ ### Performance Features (New!)
171
+
172
+ - **Optimized Compression**: Automatically skips compression for small responses (<1KB), SSE, WebSocket upgrades, and pre-compressed content
173
+ - **Memory Monitoring**: Built-in middleware tracks heap usage and triggers garbage collection when needed
174
+ - **Smart Rate Limiting**: Different limits for auth endpoints, API endpoints, and general routes
175
+
176
+ ## Container - Dependency Injection
177
+
178
+ Multi-tenant dependency injection container with performance optimizations:
179
+
180
+ ```typescript
181
+ import { Container } from '@goatlab/node-backend'
182
+
183
+ // Define your service factories
184
+ const factories = {
185
+ database: DatabaseService,
186
+ api: ApiService,
187
+ cache: CacheService
188
+ }
189
+
190
+ // Create container with initializer
191
+ const container = new Container(factories, async (preload, tenantMeta) => {
192
+ const db = preload.database(tenantMeta.id, tenantMeta.dbConfig)
193
+ const cache = preload.cache(tenantMeta.id)
194
+
195
+ return {
196
+ database: db,
197
+ api: preload.api(tenantMeta.id, db),
198
+ cache
199
+ }
200
+ })
201
+
202
+ // Bootstrap for a tenant
203
+ await container.bootstrap(tenantMeta, async () => {
204
+ const { database, api } = container.context
205
+ // Use services...
206
+ })
207
+
208
+ // Batch operations (new!)
209
+ const results = await container.bootstrapBatch([
210
+ { metadata: tenant1, fn: processTenant1 },
211
+ { metadata: tenant2, fn: processTenant2 }
212
+ ], {
213
+ concurrency: 10,
214
+ continueOnError: true,
215
+ onProgress: (completed, total) => console.log(`${completed}/${total}`)
216
+ })
217
+
218
+ // Batch cache invalidation (new!)
219
+ await container.invalidateTenantBatch(['tenant1', 'tenant2', 'tenant3'])
220
+ ```
221
+
222
+ ### Container Features
223
+
224
+ - **Multi-tenancy**: Isolated service instances per tenant
225
+ - **Batch Operations**: Process multiple tenants in parallel with concurrency control
226
+ - **Performance Metrics**: Built-in performance tracking and statistics
227
+ - **Cache Management**: Efficient caching with batch invalidation support
228
+ - **Type Safety**: Full TypeScript support with inference
229
+
230
+ ## Translation Service
231
+
232
+ High-performance translation service with template caching:
233
+
234
+ ```typescript
235
+ import { translationService } from '@goatlab/node-backend'
236
+
237
+ // Translations are automatically loaded and cached
238
+ const greeting = translationService.translate('welcome', { language: 'es' })
239
+
240
+ // With template parameters
241
+ const message = translationService.translate('user.greeting',
242
+ { language: 'en' },
243
+ { name: 'John' }
244
+ )
245
+
246
+ // Performance optimized with:
247
+ // - Compiled template caching
248
+ // - Locale preloading at startup
249
+ // - In-memory locale storage
250
+ ```
251
+
120
252
  ## Testing Utilities
121
253
 
122
254
  Comprehensive testing setup with testcontainers support:
@@ -1,4 +1,4 @@
1
- import { ContainerOptions, InstancesStructure, PreloadStructure } from './types';
1
+ import { ContainerOptions, InstancesStructure, PreloadStructure, BatchBootstrapOptions, BatchBootstrapResult, BatchInvalidationResult } from './types';
2
2
  /**
3
3
  * ═══════════════════════════════════════════════════════════════════════════════
4
4
  * 🏗️ MULTI-TENANT DEPENDENCY INJECTION CONTAINER
@@ -128,7 +128,7 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
128
128
  private readonly initializerCache;
129
129
  /**
130
130
  * High-performance metrics using Uint32Array for better JIT optimization
131
- * Indices: [hits, misses, creates, ctx, proxy, initHits, resets]
131
+ * Indices: [hits, misses, creates, ctx, proxy, initHits, resets, batchOps, batchErrors]
132
132
  * Auto-wraps at 2^32 without overflow checks for maximum performance
133
133
  */
134
134
  private readonly metrics;
@@ -285,6 +285,85 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
285
285
  instances: InstancesStructure<Defs>;
286
286
  result?: T;
287
287
  }>;
288
+ /**
289
+ * Bootstrap multiple tenants in parallel with controlled concurrency
290
+ *
291
+ * This method enables efficient initialization of multiple tenants while:
292
+ * - Controlling concurrency to avoid overwhelming the system
293
+ * - Isolating errors so one failure doesn't affect others
294
+ * - Providing progress tracking for long-running operations
295
+ * - Collecting performance metrics for each operation
296
+ *
297
+ * @param tenantBatch - Array of tenant metadata and optional functions to execute
298
+ * @param options - Options for controlling the batch operation
299
+ * @returns Array of results for each tenant, including successes and failures
300
+ *
301
+ * ```typescript
302
+ * const results = await container.bootstrapBatch([
303
+ * { metadata: tenant1Meta, fn: async () => processТenant1() },
304
+ * { metadata: tenant2Meta, fn: async () => processTenant2() },
305
+ * { metadata: tenant3Meta } // No function, just bootstrap
306
+ * ], {
307
+ * concurrency: 5,
308
+ * continueOnError: true,
309
+ * onProgress: (completed, total) => console.log(`${completed}/${total}`)
310
+ * })
311
+ *
312
+ * // Process results
313
+ * for (const result of results) {
314
+ * if (result.status === 'success') {
315
+ * console.log(`Tenant ${result.metadata.id} initialized in ${result.metrics.duration}ms`)
316
+ * } else {
317
+ * console.error(`Tenant ${result.metadata.id} failed:`, result.error)
318
+ * }
319
+ * }
320
+ * ```
321
+ */
322
+ bootstrapBatch<TMetadata = unknown, T = unknown>(tenantBatch: Array<{
323
+ metadata: TMetadata;
324
+ fn?: () => Promise<T>;
325
+ }>, options?: BatchBootstrapOptions<TMetadata>): Promise<Array<BatchBootstrapResult<Defs, TMetadata, T>>>;
326
+ /**
327
+ * Invalidate multiple tenant caches in batch
328
+ *
329
+ * Efficiently invalidates caches for multiple tenants with proper disposal
330
+ * and error handling. Useful for bulk updates or maintenance operations.
331
+ *
332
+ * @param tenantIds - Array of tenant IDs to invalidate
333
+ * @param reason - Optional reason for invalidation (for logging)
334
+ * @param distributed - Whether to propagate invalidation to other instances
335
+ * @returns Summary of the batch invalidation operation
336
+ *
337
+ * ```typescript
338
+ * const result = await container.invalidateTenantBatch(
339
+ * ['tenant1', 'tenant2', 'tenant3'],
340
+ * 'Bulk configuration update',
341
+ * true // Distribute to other instances
342
+ * )
343
+ *
344
+ * console.log(`Invalidated ${result.succeeded}/${result.total} tenants`)
345
+ * if (result.failed > 0) {
346
+ * console.error('Failed invalidations:', result.errors)
347
+ * }
348
+ * ```
349
+ */
350
+ invalidateTenantBatch(tenantIds: string[], reason?: string, distributed?: boolean): Promise<BatchInvalidationResult>;
351
+ /**
352
+ * Invalidate multiple service caches in batch
353
+ *
354
+ * @param serviceTypes - Array of service types to invalidate
355
+ * @param reason - Optional reason for invalidation
356
+ * @param distributed - Whether to propagate invalidation
357
+ * @returns Summary of the batch invalidation operation
358
+ *
359
+ * ```typescript
360
+ * const result = await container.invalidateServiceBatch(
361
+ * ['database', 'api.users', 'api.auth'],
362
+ * 'Service configuration update'
363
+ * )
364
+ * ```
365
+ */
366
+ invalidateServiceBatch(serviceTypes: string[], reason?: string, distributed?: boolean): Promise<BatchInvalidationResult>;
288
367
  /**
289
368
  * Get current performance metrics
290
369
  * Converts Uint32Array back to object format for compatibility
@@ -296,6 +375,8 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
296
375
  contextAccesses: number;
297
376
  proxyCacheHits: number;
298
377
  initializerCacheHits: number;
378
+ batchOperations: number;
379
+ batchErrors: number;
299
380
  };
300
381
  /**
301
382
  * Reset all performance metrics to zero
@@ -374,12 +455,15 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
374
455
  initializerCacheSize: number;
375
456
  initializerPromisesSize: number;
376
457
  cacheHitRatio: number;
458
+ batchSuccessRatio: number;
377
459
  cacheHits: number;
378
460
  cacheMisses: number;
379
461
  instanceCreations: number;
380
462
  contextAccesses: number;
381
463
  proxyCacheHits: number;
382
464
  initializerCacheHits: number;
465
+ batchOperations: number;
466
+ batchErrors: number;
383
467
  };
384
468
  /**
385
469
  * Check if there's an active tenant context
@@ -147,10 +147,10 @@ class Container {
147
147
  // ═══════════════════════════════════════════════════════════════════════════
148
148
  /**
149
149
  * High-performance metrics using Uint32Array for better JIT optimization
150
- * Indices: [hits, misses, creates, ctx, proxy, initHits, resets]
150
+ * Indices: [hits, misses, creates, ctx, proxy, initHits, resets, batchOps, batchErrors]
151
151
  * Auto-wraps at 2^32 without overflow checks for maximum performance
152
152
  */
153
- metrics = new Uint32Array(7);
153
+ metrics = new Uint32Array(9);
154
154
  /**
155
155
  * Metric indices for Uint32Array
156
156
  */
@@ -162,6 +162,8 @@ class Container {
162
162
  PROXIES: 4,
163
163
  INIT_HITS: 5,
164
164
  RESETS: 6,
165
+ BATCH_OPS: 7,
166
+ BATCH_ERRORS: 8
165
167
  };
166
168
  /**
167
169
  * Legacy overflow threshold for test compatibility
@@ -176,11 +178,20 @@ class Container {
176
178
  if (!this.options.enableMetrics)
177
179
  return;
178
180
  // Check for test mock of MAX_METRIC_VALUE (legacy compatibility)
179
- if (this.MAX_METRIC_VALUE < 1000 && this.metrics[idx] >= this.MAX_METRIC_VALUE) {
181
+ if (this.MAX_METRIC_VALUE < 1000 &&
182
+ this.metrics[idx] >= this.MAX_METRIC_VALUE) {
180
183
  // Legacy test behavior - reset metrics when mock threshold reached
181
184
  this.resetMetrics();
182
185
  if (this.options.enableDiagnostics) {
183
- const metricNames = ['cacheHits', 'cacheMisses', 'instanceCreations', 'contextAccesses', 'proxyCacheHits', 'initializerCacheHits', 'resets'];
186
+ const metricNames = [
187
+ 'cacheHits',
188
+ 'cacheMisses',
189
+ 'instanceCreations',
190
+ 'contextAccesses',
191
+ 'proxyCacheHits',
192
+ 'initializerCacheHits',
193
+ 'resets'
194
+ ];
184
195
  console.warn(`Container metrics reset due to overflow protection. Metric '${metricNames[idx] || 'unknown'}' reached ${this.metrics[idx]}`);
185
196
  }
186
197
  }
@@ -213,7 +224,7 @@ class Container {
213
224
  enableDiagnostics: false,
214
225
  enableDistributedInvalidation: false,
215
226
  distributedInvalidator: undefined,
216
- ...options,
227
+ ...options
217
228
  };
218
229
  // Pre-cache factory lookups for better performance
219
230
  this.preloadFactoryCache();
@@ -323,7 +334,7 @@ class Container {
323
334
  }
324
335
  // No factory found - must be a nested path, return another proxy
325
336
  return this.createPreloadProxy(newPath);
326
- },
337
+ }
327
338
  });
328
339
  this.proxyCache.set(path, proxy);
329
340
  return proxy;
@@ -408,7 +419,7 @@ class Container {
408
419
  // Property exists but is undefined - this is valid (e.g., optional services)
409
420
  return undefined;
410
421
  }
411
- // For symbols, especially well-known symbols like Symbol.iterator,
422
+ // For symbols, especially well-known symbols like Symbol.iterator,
412
423
  // just return undefined instead of throwing an error
413
424
  if (typeof prop === 'symbol') {
414
425
  return undefined;
@@ -445,7 +456,7 @@ class Container {
445
456
  }
446
457
  // Return value as-is (primitives, functions, Promises, arrays)
447
458
  return value;
448
- },
459
+ }
449
460
  });
450
461
  // Cache using WeakMap for automatic garbage collection
451
462
  this.contextProxyCache.set(obj, proxy);
@@ -463,7 +474,7 @@ class Container {
463
474
  simpleHash(str) {
464
475
  let hash = 5381;
465
476
  for (let i = 0; i < str.length; i++) {
466
- hash = ((hash << 5) + hash) + str.charCodeAt(i);
477
+ hash = (hash << 5) + hash + str.charCodeAt(i);
467
478
  }
468
479
  return (hash >>> 0).toString(36);
469
480
  }
@@ -561,6 +572,240 @@ class Container {
561
572
  }
562
573
  }
563
574
  // ═══════════════════════════════════════════════════════════════════════════
575
+ // BATCH OPERATIONS
576
+ // ═══════════════════════════════════════════════════════════════════════════
577
+ /**
578
+ * Bootstrap multiple tenants in parallel with controlled concurrency
579
+ *
580
+ * This method enables efficient initialization of multiple tenants while:
581
+ * - Controlling concurrency to avoid overwhelming the system
582
+ * - Isolating errors so one failure doesn't affect others
583
+ * - Providing progress tracking for long-running operations
584
+ * - Collecting performance metrics for each operation
585
+ *
586
+ * @param tenantBatch - Array of tenant metadata and optional functions to execute
587
+ * @param options - Options for controlling the batch operation
588
+ * @returns Array of results for each tenant, including successes and failures
589
+ *
590
+ * ```typescript
591
+ * const results = await container.bootstrapBatch([
592
+ * { metadata: tenant1Meta, fn: async () => processТenant1() },
593
+ * { metadata: tenant2Meta, fn: async () => processTenant2() },
594
+ * { metadata: tenant3Meta } // No function, just bootstrap
595
+ * ], {
596
+ * concurrency: 5,
597
+ * continueOnError: true,
598
+ * onProgress: (completed, total) => console.log(`${completed}/${total}`)
599
+ * })
600
+ *
601
+ * // Process results
602
+ * for (const result of results) {
603
+ * if (result.status === 'success') {
604
+ * console.log(`Tenant ${result.metadata.id} initialized in ${result.metrics.duration}ms`)
605
+ * } else {
606
+ * console.error(`Tenant ${result.metadata.id} failed:`, result.error)
607
+ * }
608
+ * }
609
+ * ```
610
+ */
611
+ async bootstrapBatch(tenantBatch, options = {}) {
612
+ const { concurrency = 10, continueOnError = true, timeout, onProgress } = options;
613
+ const results = [];
614
+ const total = tenantBatch.length;
615
+ let completed = 0;
616
+ let shouldAbort = false;
617
+ // Process tenants in chunks based on concurrency limit
618
+ for (let i = 0; i < total; i += concurrency) {
619
+ // Check if we should abort due to previous error in fail-fast mode
620
+ if (shouldAbort) {
621
+ break;
622
+ }
623
+ const chunk = tenantBatch.slice(i, i + concurrency);
624
+ const chunkPromises = chunk.map(async ({ metadata, fn }) => {
625
+ const startTime = Date.now();
626
+ try {
627
+ // Apply timeout if specified
628
+ let bootstrapPromise = this.bootstrap(metadata, fn);
629
+ if (timeout) {
630
+ bootstrapPromise = Promise.race([
631
+ bootstrapPromise,
632
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Bootstrap timeout after ${timeout}ms`)), timeout))
633
+ ]);
634
+ }
635
+ const { instances, result } = await bootstrapPromise;
636
+ const endTime = Date.now();
637
+ this.inc(Container.METRIC.BATCH_OPS);
638
+ return {
639
+ metadata,
640
+ status: 'success',
641
+ instances,
642
+ result,
643
+ metrics: {
644
+ startTime,
645
+ endTime,
646
+ duration: endTime - startTime
647
+ }
648
+ };
649
+ }
650
+ catch (error) {
651
+ const endTime = Date.now();
652
+ this.inc(Container.METRIC.BATCH_ERRORS);
653
+ if (this.options.enableDiagnostics) {
654
+ console.error(`Batch bootstrap failed for tenant:`, metadata, error);
655
+ }
656
+ const result = {
657
+ metadata,
658
+ status: 'error',
659
+ error: error instanceof Error ? error : new Error(String(error)),
660
+ metrics: {
661
+ startTime,
662
+ endTime,
663
+ duration: endTime - startTime
664
+ }
665
+ };
666
+ if (!continueOnError) {
667
+ // Mark that we should abort processing
668
+ shouldAbort = true;
669
+ }
670
+ return result;
671
+ }
672
+ finally {
673
+ completed++;
674
+ onProgress?.(completed, total, metadata);
675
+ }
676
+ });
677
+ // Wait for current chunk to complete before starting next
678
+ const chunkResults = await Promise.allSettled(chunkPromises);
679
+ // Extract results from Promise.allSettled
680
+ for (const settledResult of chunkResults) {
681
+ if (settledResult.status === 'fulfilled') {
682
+ results.push(settledResult.value);
683
+ }
684
+ else if (continueOnError) {
685
+ // This shouldn't happen as we handle errors above, but just in case
686
+ results.push({
687
+ metadata: tenantBatch[results.length].metadata,
688
+ status: 'error',
689
+ error: settledResult.reason,
690
+ metrics: {
691
+ startTime: Date.now(),
692
+ endTime: Date.now(),
693
+ duration: 0
694
+ }
695
+ });
696
+ }
697
+ }
698
+ // Check if we had any errors and should fail fast
699
+ if (!continueOnError && results.some(r => r.status === 'error')) {
700
+ const errorResult = results.find(r => r.status === 'error');
701
+ throw errorResult?.error || new Error('Batch operation failed');
702
+ }
703
+ }
704
+ return results;
705
+ }
706
+ /**
707
+ * Invalidate multiple tenant caches in batch
708
+ *
709
+ * Efficiently invalidates caches for multiple tenants with proper disposal
710
+ * and error handling. Useful for bulk updates or maintenance operations.
711
+ *
712
+ * @param tenantIds - Array of tenant IDs to invalidate
713
+ * @param reason - Optional reason for invalidation (for logging)
714
+ * @param distributed - Whether to propagate invalidation to other instances
715
+ * @returns Summary of the batch invalidation operation
716
+ *
717
+ * ```typescript
718
+ * const result = await container.invalidateTenantBatch(
719
+ * ['tenant1', 'tenant2', 'tenant3'],
720
+ * 'Bulk configuration update',
721
+ * true // Distribute to other instances
722
+ * )
723
+ *
724
+ * console.log(`Invalidated ${result.succeeded}/${result.total} tenants`)
725
+ * if (result.failed > 0) {
726
+ * console.error('Failed invalidations:', result.errors)
727
+ * }
728
+ * ```
729
+ */
730
+ async invalidateTenantBatch(tenantIds, reason, distributed = false) {
731
+ const result = {
732
+ total: tenantIds.length,
733
+ succeeded: 0,
734
+ failed: 0,
735
+ errors: []
736
+ };
737
+ // Process invalidations in parallel with error isolation
738
+ const invalidationPromises = tenantIds.map(async (tenantId) => {
739
+ try {
740
+ if (distributed) {
741
+ await this.invalidateTenantDistributed(tenantId, reason);
742
+ }
743
+ else {
744
+ this.invalidateTenantLocally(tenantId, reason);
745
+ }
746
+ result.succeeded++;
747
+ }
748
+ catch (error) {
749
+ result.failed++;
750
+ result.errors.push({
751
+ key: tenantId,
752
+ error: error instanceof Error ? error : new Error(String(error))
753
+ });
754
+ if (this.options.enableDiagnostics) {
755
+ console.error(`Failed to invalidate tenant ${tenantId}:`, error);
756
+ }
757
+ }
758
+ });
759
+ await Promise.allSettled(invalidationPromises);
760
+ return result;
761
+ }
762
+ /**
763
+ * Invalidate multiple service caches in batch
764
+ *
765
+ * @param serviceTypes - Array of service types to invalidate
766
+ * @param reason - Optional reason for invalidation
767
+ * @param distributed - Whether to propagate invalidation
768
+ * @returns Summary of the batch invalidation operation
769
+ *
770
+ * ```typescript
771
+ * const result = await container.invalidateServiceBatch(
772
+ * ['database', 'api.users', 'api.auth'],
773
+ * 'Service configuration update'
774
+ * )
775
+ * ```
776
+ */
777
+ async invalidateServiceBatch(serviceTypes, reason, distributed = false) {
778
+ const result = {
779
+ total: serviceTypes.length,
780
+ succeeded: 0,
781
+ failed: 0,
782
+ errors: []
783
+ };
784
+ const invalidationPromises = serviceTypes.map(async (serviceType) => {
785
+ try {
786
+ if (distributed) {
787
+ await this.invalidateServiceDistributed(serviceType, reason);
788
+ }
789
+ else {
790
+ this.invalidateServiceLocally(serviceType, reason);
791
+ }
792
+ result.succeeded++;
793
+ }
794
+ catch (error) {
795
+ result.failed++;
796
+ result.errors.push({
797
+ key: serviceType,
798
+ error: error instanceof Error ? error : new Error(String(error))
799
+ });
800
+ if (this.options.enableDiagnostics) {
801
+ console.error(`Failed to invalidate service ${serviceType}:`, error);
802
+ }
803
+ }
804
+ });
805
+ await Promise.allSettled(invalidationPromises);
806
+ return result;
807
+ }
808
+ // ═══════════════════════════════════════════════════════════════════════════
564
809
  // 📊 OBSERVABILITY & DEBUGGING
565
810
  // ═══════════════════════════════════════════════════════════════════════════
566
811
  /**
@@ -575,6 +820,8 @@ class Container {
575
820
  contextAccesses: this.metrics[Container.METRIC.CONTEXTS],
576
821
  proxyCacheHits: this.metrics[Container.METRIC.PROXIES],
577
822
  initializerCacheHits: this.metrics[Container.METRIC.INIT_HITS],
823
+ batchOperations: this.metrics[Container.METRIC.BATCH_OPS],
824
+ batchErrors: this.metrics[Container.METRIC.BATCH_ERRORS]
578
825
  };
579
826
  }
580
827
  /**
@@ -780,7 +1027,7 @@ class Container {
780
1027
  : managerAny.size || 0;
781
1028
  stats[key] = {
782
1029
  size,
783
- maxSize: this.options.cacheSize,
1030
+ maxSize: this.options.cacheSize
784
1031
  };
785
1032
  }
786
1033
  return stats;
@@ -794,6 +1041,8 @@ class Container {
794
1041
  const totalCacheSize = Object.values(cacheStats).reduce((sum, stat) => sum + stat.size, 0);
795
1042
  const hits = this.metrics[Container.METRIC.HITS];
796
1043
  const misses = this.metrics[Container.METRIC.MISSES];
1044
+ const batchOps = this.metrics[Container.METRIC.BATCH_OPS];
1045
+ const batchErrors = this.metrics[Container.METRIC.BATCH_ERRORS];
797
1046
  return {
798
1047
  ...this.getMetrics(),
799
1048
  cacheStats,
@@ -804,6 +1053,7 @@ class Container {
804
1053
  initializerCacheSize: this.initializerCache.size,
805
1054
  initializerPromisesSize: this.initializerPromises.size,
806
1055
  cacheHitRatio: hits + misses > 0 ? hits / (hits + misses) : 0,
1056
+ batchSuccessRatio: batchOps > 0 ? (batchOps - batchErrors) / batchOps : 0
807
1057
  };
808
1058
  }
809
1059
  // ═══════════════════════════════════════════════════════════════════════════