@navios/di 0.2.1 → 0.3.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 (62) hide show
  1. package/README.md +299 -38
  2. package/docs/README.md +121 -48
  3. package/docs/api-reference.md +763 -0
  4. package/docs/container.md +274 -0
  5. package/docs/examples/basic-usage.mts +97 -0
  6. package/docs/examples/factory-pattern.mts +318 -0
  7. package/docs/examples/injection-tokens.mts +225 -0
  8. package/docs/examples/request-scope-example.mts +254 -0
  9. package/docs/examples/service-lifecycle.mts +359 -0
  10. package/docs/factory.md +584 -0
  11. package/docs/getting-started.md +308 -0
  12. package/docs/injectable.md +496 -0
  13. package/docs/injection-tokens.md +400 -0
  14. package/docs/lifecycle.md +539 -0
  15. package/docs/scopes.md +749 -0
  16. package/lib/_tsup-dts-rollup.d.mts +490 -145
  17. package/lib/_tsup-dts-rollup.d.ts +490 -145
  18. package/lib/index.d.mts +26 -12
  19. package/lib/index.d.ts +26 -12
  20. package/lib/index.js +993 -462
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +983 -453
  23. package/lib/index.mjs.map +1 -1
  24. package/package.json +2 -2
  25. package/project.json +10 -2
  26. package/src/__tests__/container.spec.mts +1301 -0
  27. package/src/__tests__/factory.spec.mts +137 -0
  28. package/src/__tests__/injectable.spec.mts +32 -88
  29. package/src/__tests__/injection-token.spec.mts +333 -17
  30. package/src/__tests__/request-scope.spec.mts +263 -0
  31. package/src/__type-tests__/factory.spec-d.mts +65 -0
  32. package/src/__type-tests__/inject.spec-d.mts +27 -28
  33. package/src/__type-tests__/injectable.spec-d.mts +42 -206
  34. package/src/container.mts +167 -0
  35. package/src/decorators/factory.decorator.mts +79 -0
  36. package/src/decorators/index.mts +1 -0
  37. package/src/decorators/injectable.decorator.mts +6 -56
  38. package/src/enums/injectable-scope.enum.mts +5 -1
  39. package/src/event-emitter.mts +18 -20
  40. package/src/factory-context.mts +2 -10
  41. package/src/index.mts +3 -2
  42. package/src/injection-token.mts +19 -4
  43. package/src/injector.mts +8 -20
  44. package/src/interfaces/factory.interface.mts +3 -3
  45. package/src/interfaces/index.mts +2 -0
  46. package/src/interfaces/on-service-destroy.interface.mts +3 -0
  47. package/src/interfaces/on-service-init.interface.mts +3 -0
  48. package/src/registry.mts +7 -16
  49. package/src/request-context-holder.mts +145 -0
  50. package/src/service-instantiator.mts +158 -0
  51. package/src/service-locator-event-bus.mts +0 -28
  52. package/src/service-locator-instance-holder.mts +27 -16
  53. package/src/service-locator-manager.mts +84 -0
  54. package/src/service-locator.mts +548 -393
  55. package/src/utils/defer.mts +73 -0
  56. package/src/utils/get-injectors.mts +91 -78
  57. package/src/utils/index.mts +2 -0
  58. package/src/utils/types.mts +52 -0
  59. package/docs/concepts/injectable.md +0 -182
  60. package/docs/concepts/injection-token.md +0 -145
  61. package/src/proxy-service-locator.mts +0 -83
  62. package/src/resolve-service.mts +0 -41
@@ -0,0 +1,539 @@
1
+ # Service Lifecycle
2
+
3
+ Navios DI provides built-in support for service lifecycle management through the `OnServiceInit` and `OnServiceDestroy` interfaces. These hooks allow services to perform initialization and cleanup operations when they are created or destroyed.
4
+
5
+ ## Overview
6
+
7
+ Service lifecycle hooks are called automatically by the DI container:
8
+
9
+ - `onServiceInit()` - Called after the service is instantiated and all dependencies are injected
10
+ - `onServiceDestroy()` - Called when the service is being destroyed (e.g., during container invalidation)
11
+
12
+ ## Basic Usage
13
+
14
+ ### Service with Lifecycle Hooks
15
+
16
+ ```typescript
17
+ import { Injectable, OnServiceDestroy, OnServiceInit } from '@navios/di'
18
+
19
+ @Injectable()
20
+ class DatabaseService implements OnServiceInit, OnServiceDestroy {
21
+ private connection: any = null
22
+
23
+ async onServiceInit() {
24
+ console.log('Initializing database connection...')
25
+ this.connection = await this.connect()
26
+ console.log('Database connected successfully')
27
+ }
28
+
29
+ async onServiceDestroy() {
30
+ console.log('Closing database connection...')
31
+ if (this.connection) {
32
+ await this.connection.close()
33
+ console.log('Database connection closed')
34
+ }
35
+ }
36
+
37
+ private async connect() {
38
+ // Simulate database connection
39
+ return new Promise((resolve) => {
40
+ setTimeout(() => resolve({ connected: true }), 100)
41
+ })
42
+ }
43
+
44
+ async query(sql: string) {
45
+ if (!this.connection) {
46
+ throw new Error('Database not connected')
47
+ }
48
+ return `Query result: ${sql}`
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### Using the Service
54
+
55
+ ```typescript
56
+ import { Container } from '@navios/di'
57
+
58
+ const container = new Container()
59
+
60
+ // Service initialization happens automatically
61
+ const dbService = await container.get(DatabaseService)
62
+ // Output: "Initializing database connection..."
63
+ // Output: "Database connected successfully"
64
+
65
+ // Use the service
66
+ const result = await dbService.query('SELECT * FROM users')
67
+ console.log(result) // "Query result: SELECT * FROM users"
68
+
69
+ // Service destruction happens when invalidated
70
+ await container.invalidate(dbService)
71
+ // Output: "Closing database connection..."
72
+ // Output: "Database connection closed"
73
+ ```
74
+
75
+ ## Lifecycle Hook Details
76
+
77
+ ### OnServiceInit
78
+
79
+ The `onServiceInit` method is called after:
80
+
81
+ 1. The service instance is created
82
+ 2. All constructor dependencies are injected
83
+ 3. All property dependencies (via `asyncInject` or `inject`) are resolved
84
+
85
+ ```typescript
86
+ import { inject, Injectable, OnServiceInit } from '@navios/di'
87
+
88
+ @Injectable()
89
+ class LoggerService {
90
+ log(message: string) {
91
+ console.log(`[LOG] ${message}`)
92
+ }
93
+ }
94
+
95
+ @Injectable()
96
+ class DatabaseService implements OnServiceInit {
97
+ private readonly logger = inject(LoggerService)
98
+ private connection: any = null
99
+
100
+ async onServiceInit() {
101
+ // Logger is already available here
102
+ this.logger.log('Starting database initialization...')
103
+
104
+ this.connection = await this.connect()
105
+
106
+ this.logger.log('Database initialization complete')
107
+ }
108
+
109
+ private async connect() {
110
+ // Simulate connection
111
+ return new Promise((resolve) => {
112
+ setTimeout(() => resolve({ connected: true }), 100)
113
+ })
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### OnServiceDestroy
119
+
120
+ The `onServiceDestroy` method is called when:
121
+
122
+ 1. The service is explicitly invalidated via `container.invalidate()`
123
+ 2. The container is being destroyed
124
+ 3. The service instance is being garbage collected
125
+
126
+ ```typescript
127
+ import { Injectable, OnServiceDestroy } from '@navios/di'
128
+
129
+ @Injectable()
130
+ class CacheService implements OnServiceDestroy {
131
+ private cache = new Map()
132
+ private cleanupInterval: NodeJS.Timeout | null = null
133
+
134
+ async onServiceDestroy() {
135
+ console.log('Cleaning up cache service...')
136
+
137
+ // Clear the cache
138
+ this.cache.clear()
139
+
140
+ // Clear any intervals
141
+ if (this.cleanupInterval) {
142
+ clearInterval(this.cleanupInterval)
143
+ this.cleanupInterval = null
144
+ }
145
+
146
+ console.log('Cache service cleanup complete')
147
+ }
148
+
149
+ set(key: string, value: any) {
150
+ this.cache.set(key, value)
151
+ }
152
+
153
+ get(key: string) {
154
+ return this.cache.get(key)
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Advanced Patterns
160
+
161
+ ### Service with Multiple Resources
162
+
163
+ ```typescript
164
+ import { Injectable, OnServiceDestroy, OnServiceInit } from '@navios/di'
165
+
166
+ @Injectable()
167
+ class ResourceManager implements OnServiceInit, OnServiceDestroy {
168
+ private resources: Array<{ name: string; cleanup: () => Promise<void> }> = []
169
+
170
+ async onServiceInit() {
171
+ console.log('Initializing resources...')
172
+
173
+ // Initialize multiple resources
174
+ await this.initializeDatabase()
175
+ await this.initializeCache()
176
+ await this.initializeFileSystem()
177
+
178
+ console.log('All resources initialized')
179
+ }
180
+
181
+ async onServiceDestroy() {
182
+ console.log('Cleaning up resources...')
183
+
184
+ // Clean up in reverse order
185
+ for (let i = this.resources.length - 1; i >= 0; i--) {
186
+ const resource = this.resources[i]
187
+ try {
188
+ console.log(`Cleaning up ${resource.name}...`)
189
+ await resource.cleanup()
190
+ console.log(`${resource.name} cleaned up successfully`)
191
+ } catch (error) {
192
+ console.error(`Error cleaning up ${resource.name}:`, error)
193
+ }
194
+ }
195
+
196
+ this.resources = []
197
+ console.log('All resources cleaned up')
198
+ }
199
+
200
+ private async initializeDatabase() {
201
+ const connection = { connected: true }
202
+ this.resources.push({
203
+ name: 'Database',
204
+ cleanup: async () => {
205
+ connection.connected = false
206
+ console.log('Database connection closed')
207
+ },
208
+ })
209
+ }
210
+
211
+ private async initializeCache() {
212
+ const cache = new Map()
213
+ this.resources.push({
214
+ name: 'Cache',
215
+ cleanup: async () => {
216
+ cache.clear()
217
+ console.log('Cache cleared')
218
+ },
219
+ })
220
+ }
221
+
222
+ private async initializeFileSystem() {
223
+ const files = ['temp1.txt', 'temp2.txt']
224
+ this.resources.push({
225
+ name: 'FileSystem',
226
+ cleanup: async () => {
227
+ // Simulate file cleanup
228
+ console.log(`Cleaned up files: ${files.join(', ')}`)
229
+ },
230
+ })
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### Service with Conditional Initialization
236
+
237
+ ```typescript
238
+ import { Injectable, OnServiceDestroy, OnServiceInit } from '@navios/di'
239
+
240
+ @Injectable()
241
+ class ConditionalService implements OnServiceInit, OnServiceDestroy {
242
+ private initialized = false
243
+ private resources: any[] = []
244
+
245
+ async onServiceInit() {
246
+ console.log('Checking initialization conditions...')
247
+
248
+ // Check if we should initialize
249
+ if (await this.shouldInitialize()) {
250
+ await this.performInitialization()
251
+ this.initialized = true
252
+ console.log('Service initialized successfully')
253
+ } else {
254
+ console.log('Skipping initialization due to conditions')
255
+ }
256
+ }
257
+
258
+ async onServiceDestroy() {
259
+ if (this.initialized) {
260
+ console.log('Cleaning up initialized service...')
261
+ await this.performCleanup()
262
+ this.initialized = false
263
+ console.log('Service cleanup complete')
264
+ } else {
265
+ console.log('No cleanup needed - service was not initialized')
266
+ }
267
+ }
268
+
269
+ private async shouldInitialize(): Promise<boolean> {
270
+ // Check environment variables, configuration, etc.
271
+ return process.env.NODE_ENV !== 'test'
272
+ }
273
+
274
+ private async performInitialization() {
275
+ // Initialize resources
276
+ this.resources.push(await this.createResource('Resource1'))
277
+ this.resources.push(await this.createResource('Resource2'))
278
+ }
279
+
280
+ private async performCleanup() {
281
+ // Clean up resources
282
+ for (const resource of this.resources) {
283
+ await resource.cleanup()
284
+ }
285
+ this.resources = []
286
+ }
287
+
288
+ private async createResource(name: string) {
289
+ return {
290
+ name,
291
+ cleanup: async () => console.log(`Cleaned up ${name}`),
292
+ }
293
+ }
294
+ }
295
+ ```
296
+
297
+ ### Service with Error Handling
298
+
299
+ ```typescript
300
+ import { Injectable, OnServiceDestroy, OnServiceInit } from '@navios/di'
301
+
302
+ @Injectable()
303
+ class RobustService implements OnServiceInit, OnServiceDestroy {
304
+ private connection: any = null
305
+ private retryCount = 0
306
+ private maxRetries = 3
307
+
308
+ async onServiceInit() {
309
+ console.log('Initializing robust service...')
310
+
311
+ try {
312
+ await this.initializeWithRetry()
313
+ console.log('Service initialized successfully')
314
+ } catch (error) {
315
+ console.error('Failed to initialize service:', error)
316
+ // Don't throw - let the service be created but mark it as failed
317
+ this.connection = null
318
+ }
319
+ }
320
+
321
+ async onServiceDestroy() {
322
+ console.log('Cleaning up robust service...')
323
+
324
+ try {
325
+ if (this.connection) {
326
+ await this.connection.close()
327
+ console.log('Connection closed successfully')
328
+ }
329
+ } catch (error) {
330
+ console.error('Error during cleanup:', error)
331
+ // Don't throw - cleanup should be best effort
332
+ }
333
+ }
334
+
335
+ private async initializeWithRetry(): Promise<void> {
336
+ for (let i = 0; i < this.maxRetries; i++) {
337
+ try {
338
+ this.connection = await this.connect()
339
+ this.retryCount = i
340
+ return
341
+ } catch (error) {
342
+ console.log(`Initialization attempt ${i + 1} failed:`, error.message)
343
+ if (i === this.maxRetries - 1) {
344
+ throw error
345
+ }
346
+ // Wait before retry
347
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)))
348
+ }
349
+ }
350
+ }
351
+
352
+ private async connect() {
353
+ // Simulate connection that might fail
354
+ return new Promise((resolve, reject) => {
355
+ setTimeout(() => {
356
+ if (Math.random() > 0.3) {
357
+ // 70% success rate
358
+ resolve({ connected: true })
359
+ } else {
360
+ reject(new Error('Connection failed'))
361
+ }
362
+ }, 100)
363
+ })
364
+ }
365
+
366
+ async performOperation() {
367
+ if (!this.connection) {
368
+ throw new Error('Service not properly initialized')
369
+ }
370
+ return 'Operation completed'
371
+ }
372
+ }
373
+ ```
374
+
375
+ ## Lifecycle with Dependencies
376
+
377
+ ### Service Depending on Other Services with Lifecycle
378
+
379
+ ```typescript
380
+ import { inject, Injectable, OnServiceDestroy, OnServiceInit } from '@navios/di'
381
+
382
+ @Injectable()
383
+ class LoggerService implements OnServiceInit, OnServiceDestroy {
384
+ private logFile: any = null
385
+
386
+ async onServiceInit() {
387
+ console.log('Initializing logger...')
388
+ this.logFile = { name: 'app.log', open: true }
389
+ console.log('Logger initialized')
390
+ }
391
+
392
+ async onServiceDestroy() {
393
+ console.log('Closing logger...')
394
+ if (this.logFile) {
395
+ this.logFile.open = false
396
+ console.log('Logger closed')
397
+ }
398
+ }
399
+
400
+ log(message: string) {
401
+ console.log(`[LOG] ${message}`)
402
+ }
403
+ }
404
+
405
+ @Injectable()
406
+ class DatabaseService implements OnServiceInit, OnServiceDestroy {
407
+ private readonly logger = inject(LoggerService)
408
+ private connection: any = null
409
+
410
+ async onServiceInit() {
411
+ // Logger is already initialized at this point
412
+ this.logger.log('Initializing database...')
413
+
414
+ this.connection = await this.connect()
415
+
416
+ this.logger.log('Database initialized')
417
+ }
418
+
419
+ async onServiceDestroy() {
420
+ this.logger.log('Closing database...')
421
+
422
+ if (this.connection) {
423
+ await this.connection.close()
424
+ this.logger.log('Database closed')
425
+ }
426
+ }
427
+
428
+ private async connect() {
429
+ return new Promise((resolve) => {
430
+ setTimeout(() => resolve({ connected: true }), 100)
431
+ })
432
+ }
433
+ }
434
+ ```
435
+
436
+ ## Best Practices
437
+
438
+ ### 1. Always Check Resource State
439
+
440
+ ```typescript
441
+ // ✅ Good: Check if resource exists before cleanup
442
+ async onServiceDestroy() {
443
+ if (this.connection) {
444
+ await this.connection.close()
445
+ this.connection = null
446
+ }
447
+ }
448
+
449
+ // ❌ Avoid: Assuming resource exists
450
+ async onServiceDestroy() {
451
+ await this.connection.close() // Might throw if connection is null
452
+ }
453
+ ```
454
+
455
+ ### 2. Handle Errors Gracefully
456
+
457
+ ```typescript
458
+ // ✅ Good: Handle errors without throwing
459
+ async onServiceDestroy() {
460
+ try {
461
+ if (this.connection) {
462
+ await this.connection.close()
463
+ }
464
+ } catch (error) {
465
+ console.error('Error during cleanup:', error)
466
+ // Don't throw - cleanup should be best effort
467
+ }
468
+ }
469
+ ```
470
+
471
+ ### 3. Use Async/Await Consistently
472
+
473
+ ```typescript
474
+ // ✅ Good: Use async/await for consistency
475
+ async onServiceInit() {
476
+ this.connection = await this.connect()
477
+ this.cache = await this.initializeCache()
478
+ }
479
+
480
+ // ❌ Avoid: Mixing promises and async/await
481
+ async onServiceInit() {
482
+ this.connection = await this.connect()
483
+ this.cache = this.initializeCache() // Missing await
484
+ }
485
+ ```
486
+
487
+ ### 4. Clean Up in Reverse Order
488
+
489
+ ```typescript
490
+ // ✅ Good: Clean up in reverse order of initialization
491
+ async onServiceDestroy() {
492
+ // Clean up in reverse order
493
+ await this.cleanupResource3()
494
+ await this.cleanupResource2()
495
+ await this.cleanupResource1()
496
+ }
497
+ ```
498
+
499
+ ### 5. Don't Block Initialization
500
+
501
+ ```typescript
502
+ // ✅ Good: Don't block on non-critical operations
503
+ async onServiceInit() {
504
+ // Critical initialization
505
+ this.connection = await this.connect()
506
+
507
+ // Non-critical operations can be done asynchronously
508
+ this.initializeMetrics().catch(error => {
509
+ console.error('Failed to initialize metrics:', error)
510
+ })
511
+ }
512
+ ```
513
+
514
+ ## API Reference
515
+
516
+ ### OnServiceInit Interface
517
+
518
+ ```typescript
519
+ interface OnServiceInit {
520
+ onServiceInit(): Promise<void> | void
521
+ }
522
+ ```
523
+
524
+ ### OnServiceDestroy Interface
525
+
526
+ ```typescript
527
+ interface OnServiceDestroy {
528
+ onServiceDestroy(): Promise<void> | void
529
+ }
530
+ ```
531
+
532
+ ### Lifecycle Order
533
+
534
+ 1. **Service Creation**: Constructor is called
535
+ 2. **Dependency Injection**: Dependencies are injected
536
+ 3. **onServiceInit**: Called after all dependencies are resolved
537
+ 4. **Service Usage**: Service is ready for use
538
+ 5. **onServiceDestroy**: Called when service is being destroyed
539
+ 6. **Cleanup**: Resources are cleaned up