@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
package/docs/scopes.md ADDED
@@ -0,0 +1,749 @@
1
+ # Service Scopes
2
+
3
+ Service scopes determine the lifetime and sharing behavior of service instances in Navios DI. Understanding scopes is crucial for managing resources efficiently and avoiding common pitfalls.
4
+
5
+ ## Overview
6
+
7
+ Navios DI supports three service scopes:
8
+
9
+ - **Singleton**: One instance shared across the entire application
10
+ - **Transient**: New instance created for each injection
11
+ - **Request**: One instance shared within a request context, isolated between requests
12
+
13
+ ## Singleton Scope
14
+
15
+ Singleton is the default scope. A single instance is created and shared across all injections.
16
+
17
+ ### Basic Usage
18
+
19
+ ```typescript
20
+ import { Injectable, InjectableScope } from '@navios/di'
21
+
22
+ // Explicit singleton (default)
23
+ @Injectable({ scope: InjectableScope.Singleton })
24
+ class DatabaseService {
25
+ private connection: any = null
26
+
27
+ async connect() {
28
+ if (!this.connection) {
29
+ this.connection = await this.createConnection()
30
+ }
31
+ return this.connection
32
+ }
33
+
34
+ private async createConnection() {
35
+ console.log('Creating database connection...')
36
+ return { connected: true, id: Math.random() }
37
+ }
38
+ }
39
+
40
+ // Implicit singleton (same as above)
41
+ @Injectable()
42
+ class CacheService {
43
+ private cache = new Map()
44
+
45
+ set(key: string, value: any) {
46
+ this.cache.set(key, value)
47
+ }
48
+
49
+ get(key: string) {
50
+ return this.cache.get(key)
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### Singleton Behavior
56
+
57
+ ```typescript
58
+ import { Container } from '@navios/di'
59
+
60
+ const container = new Container()
61
+
62
+ // All these calls return the same instance
63
+ const db1 = await container.get(DatabaseService)
64
+ const db2 = await container.get(DatabaseService)
65
+ const db3 = await container.get(DatabaseService)
66
+
67
+ console.log(db1 === db2) // true
68
+ console.log(db2 === db3) // true
69
+ console.log(db1 === db3) // true
70
+ ```
71
+
72
+ ### Singleton with Dependencies
73
+
74
+ ```typescript
75
+ import { inject, Injectable } from '@navios/di'
76
+
77
+ @Injectable()
78
+ class LoggerService {
79
+ private logs: string[] = []
80
+
81
+ log(message: string) {
82
+ this.logs.push(message)
83
+ console.log(`[LOG] ${message}`)
84
+ }
85
+
86
+ getLogs() {
87
+ return [...this.logs]
88
+ }
89
+ }
90
+
91
+ @Injectable()
92
+ class UserService {
93
+ private readonly logger = inject(LoggerService)
94
+
95
+ createUser(name: string) {
96
+ this.logger.log(`Creating user: ${name}`)
97
+ return { id: Math.random().toString(36), name }
98
+ }
99
+ }
100
+
101
+ @Injectable()
102
+ class OrderService {
103
+ private readonly logger = inject(LoggerService)
104
+
105
+ createOrder(userId: string) {
106
+ this.logger.log(`Creating order for user: ${userId}`)
107
+ return { id: Math.random().toString(36), userId }
108
+ }
109
+ }
110
+
111
+ // Usage
112
+ const container = new Container()
113
+ const userService = await container.get(UserService)
114
+ const orderService = await container.get(OrderService)
115
+
116
+ const user = userService.createUser('Alice')
117
+ const order = orderService.createOrder(user.id)
118
+
119
+ // Both services share the same logger instance
120
+ const logger = await container.get(LoggerService)
121
+ console.log(logger.getLogs()) // Contains logs from both services
122
+ ```
123
+
124
+ ## Transient Scope
125
+
126
+ Transient scope creates a new instance for each injection request.
127
+
128
+ ### Basic Usage
129
+
130
+ ```typescript
131
+ import { Injectable, InjectableScope } from '@navios/di'
132
+
133
+ @Injectable({ scope: InjectableScope.Transient })
134
+ class RequestService {
135
+ private readonly requestId = Math.random().toString(36)
136
+ private readonly createdAt = new Date()
137
+
138
+ getRequestId() {
139
+ return this.requestId
140
+ }
141
+
142
+ getCreatedAt() {
143
+ return this.createdAt
144
+ }
145
+ }
146
+ ```
147
+
148
+ ### Transient Behavior
149
+
150
+ ```typescript
151
+ import { Container } from '@navios/di'
152
+
153
+ const container = new Container()
154
+
155
+ // Each call creates a new instance
156
+ const req1 = await container.get(RequestService)
157
+ const req2 = await container.get(RequestService)
158
+ const req3 = await container.get(RequestService)
159
+
160
+ console.log(req1 === req2) // false
161
+ console.log(req2 === req3) // false
162
+ console.log(req1 === req3) // false
163
+
164
+ console.log(req1.getRequestId()) // Different ID
165
+ console.log(req2.getRequestId()) // Different ID
166
+ console.log(req3.getRequestId()) // Different ID
167
+ ```
168
+
169
+ ### Transient with Dependencies
170
+
171
+ ```typescript
172
+ import { inject, Injectable, InjectableScope } from '@navios/di'
173
+
174
+ @Injectable()
175
+ class LoggerService {
176
+ log(message: string) {
177
+ console.log(`[LOG] ${message}`)
178
+ }
179
+ }
180
+
181
+ @Injectable({ scope: InjectableScope.Transient })
182
+ class RequestHandler {
183
+ private readonly logger = inject(LoggerService)
184
+ private readonly requestId = Math.random().toString(36)
185
+
186
+ async handleRequest() {
187
+ const logger = await this.logger
188
+ logger.log(`Handling request ${this.requestId}`)
189
+ return { requestId: this.requestId, status: 'processed' }
190
+ }
191
+ }
192
+
193
+ // Usage
194
+ const container = new Container()
195
+ const handler1 = await container.get(RequestHandler)
196
+ const handler2 = await container.get(RequestHandler)
197
+
198
+ const result1 = await handler1.handleRequest()
199
+ const result2 = await handler2.handleRequest()
200
+
201
+ console.log(result1.requestId) // Different ID
202
+ console.log(result2.requestId) // Different ID
203
+ ```
204
+
205
+ ## Request Scope
206
+
207
+ Request scope creates one instance per request context and shares it within that request. This is ideal for web applications where you need request-specific data that should be isolated between different requests.
208
+
209
+ ### Basic Usage
210
+
211
+ ```typescript
212
+ import { Injectable, InjectableScope } from '@navios/di'
213
+
214
+ @Injectable({ scope: InjectableScope.Request })
215
+ class RequestContext {
216
+ private readonly requestId = Math.random().toString(36)
217
+ private readonly startTime = Date.now()
218
+ private readonly userId: string
219
+
220
+ constructor(userId: string) {
221
+ this.userId = userId
222
+ }
223
+
224
+ getRequestId() {
225
+ return this.requestId
226
+ }
227
+
228
+ getDuration() {
229
+ return Date.now() - this.startTime
230
+ }
231
+
232
+ getUserId() {
233
+ return this.userId
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### Request Context Management
239
+
240
+ ```typescript
241
+ import { Container } from '@navios/di'
242
+
243
+ const container = new Container()
244
+
245
+ // Begin a request context
246
+ const requestId = 'req-123'
247
+ container.beginRequest(requestId, { userId: 'user123' })
248
+
249
+ // All injections within this request will share the same Request-scoped instances
250
+ const context1 = await container.get(RequestContext)
251
+ const context2 = await container.get(RequestContext)
252
+
253
+ console.log(context1 === context2) // true - same instance within request
254
+
255
+ // End the request context (cleans up all request-scoped instances)
256
+ await container.endRequest(requestId)
257
+ ```
258
+
259
+ ### Request Scope with Dependencies
260
+
261
+ ```typescript
262
+ import { inject, Injectable, InjectableScope } from '@navios/di'
263
+
264
+ @Injectable()
265
+ class LoggerService {
266
+ log(message: string) {
267
+ console.log(`[LOG] ${message}`)
268
+ }
269
+ }
270
+
271
+ @Injectable({ scope: InjectableScope.Request })
272
+ class UserSession {
273
+ private readonly logger = inject(LoggerService)
274
+ private readonly sessionId = Math.random().toString(36)
275
+ private readonly userId: string
276
+
277
+ constructor(userId: string) {
278
+ this.userId = userId
279
+ }
280
+
281
+ async logActivity(activity: string) {
282
+ const logger = await this.logger
283
+ logger.log(`User ${this.userId}: ${activity}`)
284
+ }
285
+
286
+ getSessionId() {
287
+ return this.sessionId
288
+ }
289
+ }
290
+
291
+ @Injectable({ scope: InjectableScope.Request })
292
+ class OrderService {
293
+ private readonly userSession = inject(UserSession)
294
+ private orders: string[] = []
295
+
296
+ async createOrder(productName: string) {
297
+ const session = await this.userSession
298
+ const orderId = `order_${Math.random().toString(36)}`
299
+
300
+ this.orders.push(orderId)
301
+ await session.logActivity(`Created order ${orderId} for ${productName}`)
302
+
303
+ return { orderId, userId: session.getSessionId() }
304
+ }
305
+ }
306
+ ```
307
+
308
+ ### Request Context Switching
309
+
310
+ You can manage multiple request contexts and switch between them:
311
+
312
+ ```typescript
313
+ const container = new Container()
314
+
315
+ // Start multiple requests
316
+ container.beginRequest('req-1', { userId: 'user1' })
317
+ container.beginRequest('req-2', { userId: 'user2' })
318
+
319
+ // Switch to request 1
320
+ container.setCurrentRequestContext('req-1')
321
+ const context1 = await container.get(RequestContext)
322
+
323
+ // Switch to request 2
324
+ container.setCurrentRequestContext('req-2')
325
+ const context2 = await container.get(RequestContext)
326
+
327
+ // Different instances for different requests
328
+ console.log(context1 !== context2) // true
329
+
330
+ // Clean up
331
+ await container.endRequest('req-1')
332
+ await container.endRequest('req-2')
333
+ ```
334
+
335
+ ## Scope Compatibility
336
+
337
+ ### Injection Method Compatibility
338
+
339
+ | Scope | inject | asyncInject |
340
+ | --------- | ---------------- | ------------ |
341
+ | Singleton | ✅ Supported | ✅ Supported |
342
+ | Transient | ❌ Not Supported | ✅ Supported |
343
+ | Request | ✅ Supported | ✅ Supported |
344
+
345
+ ### Why inject Doesn't Work with Transient
346
+
347
+ ```typescript
348
+ // ❌ This will cause an error
349
+ @Injectable({ scope: InjectableScope.Transient })
350
+ class TransientService {}
351
+
352
+ @Injectable()
353
+ class ConsumerService {
354
+ private readonly service = inject(TransientService)
355
+ // Error: Cannot use inject with transient services
356
+ }
357
+
358
+ // ✅ Use inject instead
359
+ @Injectable()
360
+ class ConsumerService {
361
+ private readonly service = asyncInject(TransientService)
362
+
363
+ async doSomething() {
364
+ const service = await this.service
365
+ // Use the service
366
+ }
367
+ }
368
+ ```
369
+
370
+ ## Real-World Examples
371
+
372
+ ### Singleton: Database Connection Pool
373
+
374
+ ```typescript
375
+ import { Injectable, OnServiceDestroy, OnServiceInit } from '@navios/di'
376
+
377
+ @Injectable({ scope: InjectableScope.Singleton })
378
+ class DatabasePool implements OnServiceInit, OnServiceDestroy {
379
+ private connections: any[] = []
380
+ private maxConnections = 10
381
+
382
+ async onServiceInit() {
383
+ console.log('Initializing database connection pool...')
384
+ // Initialize connection pool
385
+ for (let i = 0; i < this.maxConnections; i++) {
386
+ this.connections.push({ id: i, busy: false })
387
+ }
388
+ console.log(`Pool initialized with ${this.maxConnections} connections`)
389
+ }
390
+
391
+ async onServiceDestroy() {
392
+ console.log('Closing database connection pool...')
393
+ this.connections = []
394
+ }
395
+
396
+ async getConnection() {
397
+ const available = this.connections.find((conn) => !conn.busy)
398
+ if (!available) {
399
+ throw new Error('No available connections')
400
+ }
401
+
402
+ available.busy = true
403
+ console.log(`Using connection ${available.id}`)
404
+ return available
405
+ }
406
+
407
+ releaseConnection(connection: any) {
408
+ connection.busy = false
409
+ console.log(`Released connection ${connection.id}`)
410
+ }
411
+ }
412
+ ```
413
+
414
+ ### Transient: Request Context
415
+
416
+ ```typescript
417
+ import { Injectable, InjectableScope } from '@navios/di'
418
+
419
+ @Injectable({ scope: InjectableScope.Transient })
420
+ class RequestContext {
421
+ private readonly requestId = Math.random().toString(36)
422
+ private readonly startTime = Date.now()
423
+ private readonly userAgent: string
424
+ private readonly ip: string
425
+
426
+ constructor(userAgent: string, ip: string) {
427
+ this.userAgent = userAgent
428
+ this.ip = ip
429
+ }
430
+
431
+ getRequestId() {
432
+ return this.requestId
433
+ }
434
+
435
+ getDuration() {
436
+ return Date.now() - this.startTime
437
+ }
438
+
439
+ getUserAgent() {
440
+ return this.userAgent
441
+ }
442
+
443
+ getIp() {
444
+ return this.ip
445
+ }
446
+ }
447
+
448
+ @Injectable()
449
+ class RequestHandler {
450
+ private readonly context = asyncInject(RequestContext)
451
+
452
+ async handleRequest() {
453
+ const ctx = await this.context
454
+ console.log(`Handling request ${ctx.getRequestId()} from ${ctx.getIp()}`)
455
+
456
+ // Process request...
457
+
458
+ console.log(
459
+ `Request ${ctx.getRequestId()} completed in ${ctx.getDuration()}ms`,
460
+ )
461
+ }
462
+ }
463
+ ```
464
+
465
+ ### Mixed Scopes
466
+
467
+ ```typescript
468
+ import { asyncInject, inject, Injectable, InjectableScope } from '@navios/di'
469
+
470
+ // Singleton services
471
+ @Injectable()
472
+ class ConfigService {
473
+ getConfig() {
474
+ return { apiUrl: 'https://api.example.com', timeout: 5000 }
475
+ }
476
+ }
477
+
478
+ @Injectable()
479
+ class LoggerService {
480
+ log(message: string) {
481
+ console.log(`[LOG] ${new Date().toISOString()} - ${message}`)
482
+ }
483
+ }
484
+
485
+ // Transient service
486
+ @Injectable({ scope: InjectableScope.Transient })
487
+ class UserSession {
488
+ private readonly sessionId = Math.random().toString(36)
489
+ private readonly userId: string
490
+
491
+ constructor(userId: string) {
492
+ this.userId = userId
493
+ }
494
+
495
+ getSessionId() {
496
+ return this.sessionId
497
+ }
498
+
499
+ getUserId() {
500
+ return this.userId
501
+ }
502
+ }
503
+
504
+ // Service using both scopes
505
+ @Injectable()
506
+ class UserService {
507
+ private readonly config = inject(ConfigService) // Singleton
508
+ private readonly logger = inject(LoggerService) // Singleton
509
+ private readonly session = asyncInject(UserSession) // Transient
510
+
511
+ async authenticateUser(userId: string) {
512
+ this.logger.log(`Authenticating user ${userId}`)
513
+
514
+ const session = await this.session
515
+ this.logger.log(
516
+ `Created session ${session.getSessionId()} for user ${userId}`,
517
+ )
518
+
519
+ return {
520
+ userId,
521
+ sessionId: session.getSessionId(),
522
+ apiUrl: this.config.getConfig().apiUrl,
523
+ }
524
+ }
525
+ }
526
+ ```
527
+
528
+ ## Best Practices
529
+
530
+ ### 1. Use Singleton for Stateless Services
531
+
532
+ ```typescript
533
+ // ✅ Good: Stateless service as singleton
534
+ @Injectable({ scope: InjectableScope.Singleton })
535
+ class EmailService {
536
+ async sendEmail(to: string, subject: string, body: string) {
537
+ // No state, safe to share
538
+ return await this.sendViaProvider(to, subject, body)
539
+ }
540
+ }
541
+
542
+ // ❌ Avoid: Stateful service as singleton
543
+ @Injectable({ scope: InjectableScope.Singleton })
544
+ class UserSessionService {
545
+ private currentUser: User | null = null // State!
546
+
547
+ setCurrentUser(user: User) {
548
+ this.currentUser = user // Shared state can cause issues
549
+ }
550
+ }
551
+ ```
552
+
553
+ ### 2. Use Transient for Stateful Services
554
+
555
+ ```typescript
556
+ // ✅ Good: Stateful service as transient
557
+ @Injectable({ scope: InjectableScope.Transient })
558
+ class UserSession {
559
+ private readonly userId: string
560
+ private readonly sessionId: string
561
+ private readonly createdAt: Date
562
+
563
+ constructor(userId: string) {
564
+ this.userId = userId
565
+ this.sessionId = Math.random().toString(36)
566
+ this.createdAt = new Date()
567
+ }
568
+
569
+ getUserId() {
570
+ return this.userId
571
+ }
572
+ }
573
+ ```
574
+
575
+ ### 3. Use Singleton for Expensive Resources
576
+
577
+ ```typescript
578
+ // ✅ Good: Expensive resource as singleton
579
+ @Injectable({ scope: InjectableScope.Singleton })
580
+ class DatabaseConnection {
581
+ private connection: any = null
582
+
583
+ async getConnection() {
584
+ if (!this.connection) {
585
+ // Expensive operation - only do once
586
+ this.connection = await this.createConnection()
587
+ }
588
+ return this.connection
589
+ }
590
+ }
591
+ ```
592
+
593
+ ### 4. Use Transient for Request-Specific Data
594
+
595
+ ```typescript
596
+ // ✅ Good: Request-specific data as transient
597
+ @Injectable({ scope: InjectableScope.Transient })
598
+ class RequestContext {
599
+ private readonly requestId: string
600
+ private readonly startTime: number
601
+ private readonly headers: Record<string, string>
602
+
603
+ constructor(headers: Record<string, string>) {
604
+ this.requestId = Math.random().toString(36)
605
+ this.startTime = Date.now()
606
+ this.headers = headers
607
+ }
608
+ }
609
+ ```
610
+
611
+ ### 5. Consider Performance Implications
612
+
613
+ ```typescript
614
+ // ✅ Good: Lightweight transient service
615
+ @Injectable({ scope: InjectableScope.Transient })
616
+ class RequestIdGenerator {
617
+ generate() {
618
+ return Math.random().toString(36)
619
+ }
620
+ }
621
+
622
+ // ❌ Avoid: Heavy transient service
623
+ @Injectable({ scope: InjectableScope.Transient })
624
+ class HeavyService {
625
+ constructor() {
626
+ // Heavy initialization for each instance
627
+ this.initializeExpensiveResources()
628
+ }
629
+ }
630
+ ```
631
+
632
+ ## Common Pitfalls
633
+
634
+ ### 1. State Leakage in Singletons
635
+
636
+ ```typescript
637
+ // ❌ Problem: State leakage
638
+ @Injectable({ scope: InjectableScope.Singleton })
639
+ class CacheService {
640
+ private cache = new Map()
641
+
642
+ set(key: string, value: any) {
643
+ this.cache.set(key, value)
644
+ }
645
+
646
+ get(key: string) {
647
+ return this.cache.get(key)
648
+ }
649
+
650
+ // Problem: Cache persists across requests
651
+ clear() {
652
+ this.cache.clear()
653
+ }
654
+ }
655
+
656
+ // ✅ Solution: Use transient for request-scoped cache
657
+ @Injectable({ scope: InjectableScope.Transient })
658
+ class RequestCache {
659
+ private cache = new Map()
660
+
661
+ set(key: string, value: any) {
662
+ this.cache.set(key, value)
663
+ }
664
+
665
+ get(key: string) {
666
+ return this.cache.get(key)
667
+ }
668
+ }
669
+ ```
670
+
671
+ ### 2. Incorrect Injection Method
672
+
673
+ ```typescript
674
+ // ❌ Problem: Using inject with transient
675
+ @Injectable({ scope: InjectableScope.Transient })
676
+ class TransientService {}
677
+
678
+ @Injectable()
679
+ class ConsumerService {
680
+ private readonly service = inject(TransientService)
681
+ // Error: Cannot use inject with transient services
682
+ }
683
+
684
+ // ✅ Solution: Use asyncInject with transient
685
+ @Injectable()
686
+ class ConsumerService {
687
+ private readonly service = asyncInject(TransientService)
688
+
689
+ async doSomething() {
690
+ const service = await this.service
691
+ // Use the service
692
+ }
693
+ }
694
+ ```
695
+
696
+ ### 3. Memory Leaks with Transient Services
697
+
698
+ ```typescript
699
+ // ❌ Problem: Transient service holding references
700
+ @Injectable({ scope: InjectableScope.Transient })
701
+ class TransientService {
702
+ private listeners: Function[] = []
703
+
704
+ addListener(listener: Function) {
705
+ this.listeners.push(listener)
706
+ }
707
+
708
+ // Problem: Listeners are never cleaned up
709
+ }
710
+
711
+ // ✅ Solution: Implement cleanup
712
+ @Injectable({ scope: InjectableScope.Transient })
713
+ class TransientService implements OnServiceDestroy {
714
+ private listeners: Function[] = []
715
+
716
+ addListener(listener: Function) {
717
+ this.listeners.push(listener)
718
+ }
719
+
720
+ async onServiceDestroy() {
721
+ this.listeners = []
722
+ }
723
+ }
724
+ ```
725
+
726
+ ## API Reference
727
+
728
+ ### InjectableScope Enum
729
+
730
+ ```typescript
731
+ enum InjectableScope {
732
+ Singleton = 'Singleton', // One instance shared across the application
733
+ Transient = 'Transient', // New instance created for each injection
734
+ Request = 'Request', // One instance shared within a request context
735
+ }
736
+ ```
737
+
738
+ ### Scope Configuration
739
+
740
+ ```typescript
741
+ @Injectable({ scope: InjectableScope.Singleton })
742
+ class SingletonService {}
743
+
744
+ @Injectable({ scope: InjectableScope.Transient })
745
+ class TransientService {}
746
+
747
+ @Injectable({ scope: InjectableScope.Request })
748
+ class RequestService {}
749
+ ```