@goatlab/node-backend 0.2.5 → 0.4.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 (44) hide show
  1. package/README.md +146 -14
  2. package/dist/container/Container.d.ts +113 -25
  3. package/dist/container/Container.js +391 -168
  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/helpers.d.ts +8 -0
  9. package/dist/container/helpers.js +22 -0
  10. package/dist/container/helpers.js.map +1 -1
  11. package/dist/container/types.d.ts +60 -0
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/server/bootstraps/getExpressTrpcApp.d.ts +5 -1
  15. package/dist/server/bootstraps/getExpressTrpcApp.js +216 -12
  16. package/dist/server/bootstraps/getExpressTrpcApp.js.map +1 -1
  17. package/dist/server/middleware/memoryMonitor.example.d.ts +1 -0
  18. package/dist/server/middleware/memoryMonitor.example.js +109 -0
  19. package/dist/server/middleware/memoryMonitor.example.js.map +1 -0
  20. package/dist/server/middleware/memoryMonitor.middleware.d.ts +42 -0
  21. package/dist/server/middleware/memoryMonitor.middleware.js +134 -0
  22. package/dist/server/middleware/memoryMonitor.middleware.js.map +1 -0
  23. package/dist/server/middleware/productionError.middleware.d.ts +16 -0
  24. package/dist/server/middleware/productionError.middleware.js +94 -0
  25. package/dist/server/middleware/productionError.middleware.js.map +1 -0
  26. package/dist/server/middleware/security.middleware.d.ts +28 -0
  27. package/dist/server/middleware/security.middleware.js +151 -0
  28. package/dist/server/middleware/security.middleware.js.map +1 -0
  29. package/dist/server/services/secrets/examples/container-preload.example.d.ts +1 -0
  30. package/dist/server/services/secrets/examples/container-preload.example.js +148 -0
  31. package/dist/server/services/secrets/examples/container-preload.example.js.map +1 -0
  32. package/dist/server/services/secrets/index.d.ts +1 -0
  33. package/dist/server/services/secrets/index.js +6 -0
  34. package/dist/server/services/secrets/index.js.map +1 -0
  35. package/dist/server/services/secrets/secret.service.d.ts +48 -6
  36. package/dist/server/services/secrets/secret.service.js +280 -28
  37. package/dist/server/services/secrets/secret.service.js.map +1 -1
  38. package/dist/server/services/translations/translation.model.js +2 -1
  39. package/dist/server/services/translations/translation.model.js.map +1 -1
  40. package/dist/server/services/translations/translation.service.d.ts +8 -1
  41. package/dist/server/services/translations/translation.service.js +123 -13
  42. package/dist/server/services/translations/translation.service.js.map +1 -1
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +13 -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
@@ -83,6 +83,7 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
83
83
  /**
84
84
  * Service instance cache managers - one per service type
85
85
  * Each manager handles LRU caching for that specific service
86
+ * Lazy-allocated to save memory for unused services
86
87
  */
87
88
  private readonly managers;
88
89
  /**
@@ -109,11 +110,6 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
109
110
  * Prevents concurrent bootstrap for same tenant from running initializer twice
110
111
  */
111
112
  private readonly initializerPromises;
112
- /**
113
- * Path string cache: converts ['user', 'repo'] -> "user.repo"
114
- * Optimized to avoid repeated string joins and array operations
115
- */
116
- private readonly pathCache;
117
113
  /**
118
114
  * Proxy object cache: reuses proxy objects for the same paths
119
115
  * Reduces memory allocation and improves performance
@@ -132,14 +128,14 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
132
128
  private readonly initializerCache;
133
129
  /**
134
130
  * High-performance metrics using Uint32Array for better JIT optimization
135
- * Indices: [hits, misses, creates, ctx, paths, proxy, initHits, resets]
131
+ * Indices: [hits, misses, creates, ctx, proxy, initHits, resets, batchOps, batchErrors]
136
132
  * Auto-wraps at 2^32 without overflow checks for maximum performance
137
133
  */
138
134
  private readonly metrics;
139
135
  /**
140
136
  * Metric indices for Uint32Array
141
137
  */
142
- private static readonly METRIC_INDICES;
138
+ private static readonly METRIC;
143
139
  /**
144
140
  * Legacy overflow threshold for test compatibility
145
141
  * Note: With Uint32Array, overflow is handled automatically, but tests may mock this
@@ -166,25 +162,16 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
166
162
  */
167
163
  constructor(factories: Defs, initializer: (preload: PreloadStructure<Defs>, meta: TenantMetadata) => Promise<Partial<InstancesStructure<Defs>>>, options?: ContainerOptions);
168
164
  /**
169
- * Recursively create cache managers for all services in the factory tree
170
- * Each service gets its own LRU cache with the configured size limit
165
+ * Get or create a cache manager for a service - lazy allocation
166
+ * Saves memory by only creating caches for services that are actually used
167
+ * Note: Type safety is enforced at compile time through generics, not runtime
171
168
  */
172
- private createManagers;
173
- /**
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
176
- */
177
- private getOrCachePath;
169
+ private getManager;
178
170
  /**
179
171
  * Pre-populate the factory cache by walking the entire factory tree
180
172
  * This eliminates the need for recursive object traversal during runtime
181
173
  */
182
174
  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;
188
175
  /**
189
176
  * Recursive factory tree walker that builds the flat factory cache
190
177
  * Converts nested object structure to flat dot-notation keys
@@ -252,8 +239,15 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
252
239
  */
253
240
  private createContextProxy;
254
241
  /**
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)
242
+ * Simple string hash function for fallback tenant keys
243
+ * Uses djb2 algorithm - fast and good enough for cache keys
244
+ * Note: For very large metadata objects, consider upgrading to FNV-1a or crypto.createHash
245
+ * if collision resistance is critical. Current implementation is optimized for speed.
246
+ */
247
+ private simpleHash;
248
+ /**
249
+ * Create a stable cache key from tenant metadata
250
+ * Uses common tenant properties or hashed JSON as fallback
257
251
  */
258
252
  private createTenantCacheKey;
259
253
  /**
@@ -291,6 +285,85 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
291
285
  instances: InstancesStructure<Defs>;
292
286
  result?: T;
293
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>;
294
367
  /**
295
368
  * Get current performance metrics
296
369
  * Converts Uint32Array back to object format for compatibility
@@ -300,9 +373,10 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
300
373
  cacheMisses: number;
301
374
  instanceCreations: number;
302
375
  contextAccesses: number;
303
- pathCacheHits: number;
304
376
  proxyCacheHits: number;
305
377
  initializerCacheHits: number;
378
+ batchOperations: number;
379
+ batchErrors: number;
306
380
  };
307
381
  /**
308
382
  * Reset all performance metrics to zero
@@ -314,6 +388,12 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
314
388
  * Calls optional dispose() hooks to prevent memory leaks (sockets, db handles, etc.)
315
389
  */
316
390
  clearCaches(): void;
391
+ /**
392
+ * Async version of clearCaches that properly awaits all disposal operations
393
+ * Use this method when you need to ensure all resources are fully disposed
394
+ * before continuing (e.g., during graceful shutdown)
395
+ */
396
+ clearCachesAsync(): Promise<void>;
317
397
  /**
318
398
  * Setup distributed cache invalidation system
319
399
  * Connects to Redis pub/sub for coordinating cache invalidation across instances
@@ -345,6 +425,12 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
345
425
  * Invalidate all cached data (local only)
346
426
  */
347
427
  private invalidateAllLocally;
428
+ /**
429
+ * Dispose all service instances across all tenants and clear caches
430
+ * Useful for graceful shutdown and testing cleanup
431
+ * Note: This also clears all caches to prevent resurrection of disposed services
432
+ */
433
+ disposeAll(): Promise<void>;
348
434
  /**
349
435
  * Get detailed cache statistics for each service
350
436
  * Shows how many instances are cached and the cache limits
@@ -369,13 +455,15 @@ export declare class Container<Defs extends Record<string, unknown>, TenantMetad
369
455
  initializerCacheSize: number;
370
456
  initializerPromisesSize: number;
371
457
  cacheHitRatio: number;
458
+ batchSuccessRatio: number;
372
459
  cacheHits: number;
373
460
  cacheMisses: number;
374
461
  instanceCreations: number;
375
462
  contextAccesses: number;
376
- pathCacheHits: number;
377
463
  proxyCacheHits: number;
378
464
  initializerCacheHits: number;
465
+ batchOperations: number;
466
+ batchErrors: number;
379
467
  };
380
468
  /**
381
469
  * Check if there's an active tenant context