@navios/di 0.5.1 → 0.6.1

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 (122) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +196 -219
  3. package/docs/README.md +69 -11
  4. package/docs/api-reference.md +281 -117
  5. package/docs/container.md +220 -56
  6. package/docs/examples/request-scope-example.mts +2 -2
  7. package/docs/factory.md +3 -8
  8. package/docs/getting-started.md +37 -8
  9. package/docs/migration.md +318 -37
  10. package/docs/request-contexts.md +263 -175
  11. package/docs/scopes.md +79 -42
  12. package/lib/browser/index.d.mts +1577 -0
  13. package/lib/browser/index.d.mts.map +1 -0
  14. package/lib/browser/index.mjs +3013 -0
  15. package/lib/browser/index.mjs.map +1 -0
  16. package/lib/index-7jfWsiG4.d.mts +1211 -0
  17. package/lib/index-7jfWsiG4.d.mts.map +1 -0
  18. package/lib/index-DW3K5sOX.d.cts +1206 -0
  19. package/lib/index-DW3K5sOX.d.cts.map +1 -0
  20. package/lib/index.cjs +389 -0
  21. package/lib/index.cjs.map +1 -0
  22. package/lib/index.d.cts +376 -0
  23. package/lib/index.d.cts.map +1 -0
  24. package/lib/index.d.mts +371 -78
  25. package/lib/index.d.mts.map +1 -0
  26. package/lib/index.mjs +325 -63
  27. package/lib/index.mjs.map +1 -1
  28. package/lib/testing/index.cjs +9 -0
  29. package/lib/testing/index.d.cts +2 -0
  30. package/lib/testing/index.d.mts +2 -2
  31. package/lib/testing/index.mjs +2 -72
  32. package/lib/testing-BG_fa9TJ.mjs +2656 -0
  33. package/lib/testing-BG_fa9TJ.mjs.map +1 -0
  34. package/lib/testing-DIaIRiJz.cjs +2896 -0
  35. package/lib/testing-DIaIRiJz.cjs.map +1 -0
  36. package/package.json +29 -7
  37. package/project.json +2 -2
  38. package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
  39. package/src/__tests__/async-local-storage.spec.mts +333 -0
  40. package/src/__tests__/container.spec.mts +30 -25
  41. package/src/__tests__/e2e.browser.spec.mts +790 -0
  42. package/src/__tests__/e2e.spec.mts +1222 -0
  43. package/src/__tests__/factory.spec.mts +1 -1
  44. package/src/__tests__/get-injectors.spec.mts +1 -1
  45. package/src/__tests__/injectable.spec.mts +1 -1
  46. package/src/__tests__/injection-token.spec.mts +1 -1
  47. package/src/__tests__/library-findings.spec.mts +563 -0
  48. package/src/__tests__/registry.spec.mts +2 -2
  49. package/src/__tests__/request-scope.spec.mts +266 -274
  50. package/src/__tests__/service-instantiator.spec.mts +18 -17
  51. package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
  52. package/src/__tests__/service-locator-manager.spec.mts +15 -15
  53. package/src/__tests__/service-locator.spec.mts +167 -244
  54. package/src/__tests__/unified-api.spec.mts +27 -27
  55. package/src/__type-tests__/factory.spec-d.mts +2 -2
  56. package/src/__type-tests__/inject.spec-d.mts +2 -2
  57. package/src/__type-tests__/injectable.spec-d.mts +1 -1
  58. package/src/browser.mts +16 -0
  59. package/src/container/container.mts +319 -0
  60. package/src/container/index.mts +2 -0
  61. package/src/container/scoped-container.mts +350 -0
  62. package/src/decorators/factory.decorator.mts +4 -4
  63. package/src/decorators/injectable.decorator.mts +5 -5
  64. package/src/errors/di-error.mts +12 -5
  65. package/src/errors/index.mts +0 -8
  66. package/src/index.mts +156 -15
  67. package/src/interfaces/container.interface.mts +82 -0
  68. package/src/interfaces/factory.interface.mts +2 -2
  69. package/src/interfaces/index.mts +1 -0
  70. package/src/internal/context/async-local-storage.mts +120 -0
  71. package/src/internal/context/factory-context.mts +18 -0
  72. package/src/internal/context/index.mts +3 -0
  73. package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
  74. package/src/internal/context/resolution-context.mts +63 -0
  75. package/src/internal/context/sync-local-storage.mts +51 -0
  76. package/src/internal/core/index.mts +5 -0
  77. package/src/internal/core/instance-resolver.mts +641 -0
  78. package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
  79. package/src/internal/core/invalidator.mts +437 -0
  80. package/src/internal/core/service-locator.mts +202 -0
  81. package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
  82. package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
  83. package/src/internal/holder/holder-manager.mts +85 -0
  84. package/src/internal/holder/holder-storage.interface.mts +116 -0
  85. package/src/internal/holder/index.mts +6 -0
  86. package/src/internal/holder/instance-holder.mts +109 -0
  87. package/src/internal/holder/request-storage.mts +134 -0
  88. package/src/internal/holder/singleton-storage.mts +105 -0
  89. package/src/internal/index.mts +4 -0
  90. package/src/internal/lifecycle/circular-detector.mts +77 -0
  91. package/src/internal/lifecycle/index.mts +2 -0
  92. package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
  93. package/src/testing/__tests__/test-container.spec.mts +2 -2
  94. package/src/testing/test-container.mts +4 -4
  95. package/src/token/index.mts +2 -0
  96. package/src/{injection-token.mts → token/injection-token.mts} +1 -1
  97. package/src/{registry.mts → token/registry.mts} +1 -1
  98. package/src/utils/get-injectable-token.mts +1 -1
  99. package/src/utils/get-injectors.mts +32 -15
  100. package/src/utils/types.mts +1 -1
  101. package/tsdown.config.mts +67 -0
  102. package/lib/_tsup-dts-rollup.d.mts +0 -1283
  103. package/lib/_tsup-dts-rollup.d.ts +0 -1283
  104. package/lib/chunk-2M576LCC.mjs +0 -2043
  105. package/lib/chunk-2M576LCC.mjs.map +0 -1
  106. package/lib/index.d.ts +0 -78
  107. package/lib/index.js +0 -2127
  108. package/lib/index.js.map +0 -1
  109. package/lib/testing/index.d.ts +0 -2
  110. package/lib/testing/index.js +0 -2060
  111. package/lib/testing/index.js.map +0 -1
  112. package/lib/testing/index.mjs.map +0 -1
  113. package/src/container.mts +0 -227
  114. package/src/factory-context.mts +0 -8
  115. package/src/instance-resolver.mts +0 -559
  116. package/src/request-context-manager.mts +0 -149
  117. package/src/service-invalidator.mts +0 -429
  118. package/src/service-locator-instance-holder.mts +0 -70
  119. package/src/service-locator-manager.mts +0 -85
  120. package/src/service-locator.mts +0 -246
  121. package/tsup.config.mts +0 -12
  122. /package/src/{injector.mts → injectors.mts} +0 -0
package/docs/container.md CHANGED
@@ -9,6 +9,7 @@ The Container wraps a `ServiceLocator` instance and provides convenient methods
9
9
  - Getting service instances
10
10
  - Invalidating services and their dependencies
11
11
  - Managing service lifecycle
12
+ - Creating request-scoped containers via `ScopedContainer`
12
13
  - Accessing the underlying service locator for advanced usage
13
14
 
14
15
  ## Basic Usage
@@ -26,14 +27,7 @@ const customRegistry = new Registry()
26
27
  const container = new Container(customRegistry)
27
28
 
28
29
  // Create with custom registry and logger
29
- const mockLogger = {
30
- log: console.log,
31
- error: console.error,
32
- warn: console.warn,
33
- info: console.info,
34
- debug: console.debug,
35
- }
36
- const container = new Container(customRegistry, mockLogger)
30
+ const container = new Container(customRegistry, console)
37
31
  ```
38
32
 
39
33
  ### Getting Service Instances
@@ -131,6 +125,25 @@ await container.ready()
131
125
  const [userService, emailService, dbService] = await Promise.all(promises)
132
126
  ```
133
127
 
128
+ ### Synchronous Instance Access
129
+
130
+ Use `tryGetSync` to get an instance synchronously if it already exists:
131
+
132
+ ```typescript
133
+ const container = new Container()
134
+
135
+ // First, ensure the service is created
136
+ await container.get(UserService)
137
+
138
+ // Now you can get it synchronously
139
+ const userService = container.tryGetSync(UserService)
140
+ if (userService) {
141
+ console.log('Service already exists:', userService)
142
+ } else {
143
+ console.log('Service not yet created')
144
+ }
145
+ ```
146
+
134
147
  ### Accessing the Service Locator
135
148
 
136
149
  For advanced usage, you can access the underlying `ServiceLocator`:
@@ -145,37 +158,86 @@ const instance = await serviceLocator.getOrThrowInstance(UserService)
145
158
 
146
159
  ## Request Context Management
147
160
 
148
- The Container provides built-in support for request contexts, allowing you to manage request-scoped services:
161
+ The Container provides built-in support for request contexts via `ScopedContainer`. This allows you to manage request-scoped services with proper isolation between concurrent requests.
162
+
163
+ ### Creating a ScopedContainer
149
164
 
150
165
  ```typescript
166
+ import { Container, Injectable, InjectableScope } from '@navios/di'
167
+
168
+ @Injectable({ scope: InjectableScope.Request })
169
+ class RequestContext {
170
+ userId?: string
171
+ correlationId?: string
172
+ }
173
+
151
174
  const container = new Container()
152
175
 
153
- // Begin a request context
154
- const context = container.beginRequest('req-123', { userId: 456 }, 100)
176
+ // Begin a request context - returns a ScopedContainer
177
+ const scoped = container.beginRequest('req-123', {
178
+ userId: 'user-456',
179
+ correlationId: 'corr-789',
180
+ })
155
181
 
156
- // Add request-specific instances
157
- const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
158
- context.addInstance(REQUEST_ID_TOKEN, 'req-123')
182
+ // Get request-scoped services from the ScopedContainer
183
+ const context = await scoped.get(RequestContext)
159
184
 
160
- // Set as current context
161
- container.setCurrentRequestContext('req-123')
185
+ // Access metadata
186
+ const userId = scoped.getMetadata('userId')
162
187
 
163
- // Services can now access request-scoped data
164
- @Injectable()
165
- class RequestService {
166
- private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
188
+ // Clean up when done
189
+ await scoped.endRequest()
190
+ ```
167
191
 
168
- async process() {
169
- const id = await this.requestId
170
- console.log(`Processing in request: ${id}`)
171
- }
172
- }
192
+ ### Why ScopedContainer?
173
193
 
174
- const service = await container.get(RequestService)
175
- await service.process() // "Processing in request: req-123"
194
+ The `ScopedContainer` pattern eliminates race conditions that occurred with the old API:
176
195
 
177
- // Clean up when done
178
- await container.endRequest('req-123')
196
+ ```typescript
197
+ // OLD API (removed) - had race conditions:
198
+ // container.setCurrentRequestContext('req-A')
199
+ // const serviceA = await container.get(RequestService) // async...
200
+ // container.setCurrentRequestContext('req-B') // Request B starts while A is resolving
201
+ // Service A might get Request B's context! Bug!
202
+
203
+ // NEW API - no race conditions:
204
+ const scopedA = container.beginRequest('req-A')
205
+ const serviceA = await scopedA.get(RequestService) // Always gets A's context
206
+
207
+ const scopedB = container.beginRequest('req-B')
208
+ const serviceB = await scopedB.get(RequestService) // Always gets B's context
209
+ ```
210
+
211
+ ### Request-Scoped Service Error
212
+
213
+ Attempting to get a request-scoped service from the main Container throws an error:
214
+
215
+ ```typescript
216
+ @Injectable({ scope: InjectableScope.Request })
217
+ class RequestService {}
218
+
219
+ // ❌ This throws an error
220
+ await container.get(RequestService)
221
+ // Error: Cannot resolve request-scoped service "RequestService" from Container.
222
+ // Use beginRequest() to create a ScopedContainer for request-scoped services.
223
+
224
+ // ✅ Use ScopedContainer instead
225
+ const scoped = container.beginRequest('req-123')
226
+ const service = await scoped.get(RequestService)
227
+ ```
228
+
229
+ ### Tracking Active Requests
230
+
231
+ ```typescript
232
+ const scoped1 = container.beginRequest('req-1')
233
+ const scoped2 = container.beginRequest('req-2')
234
+
235
+ // Check active requests
236
+ console.log(container.hasActiveRequest('req-1')) // true
237
+ console.log(container.getActiveRequestIds()) // Set { 'req-1', 'req-2' }
238
+
239
+ await scoped1.endRequest()
240
+ console.log(container.hasActiveRequest('req-1')) // false
179
241
  ```
180
242
 
181
243
  ## Best Practices
@@ -220,13 +282,22 @@ await container.invalidate(userSessionService)
220
282
  ### 3. Handle Errors Gracefully
221
283
 
222
284
  ```typescript
285
+ import { DIError, DIErrorCode } from '@navios/di'
286
+
223
287
  try {
224
288
  const service = await container.get(NonExistentService)
225
289
  } catch (error) {
226
- if (error instanceof InstanceNotFoundError) {
227
- console.error('Service not registered:', error.message)
228
- } else {
229
- console.error('Unexpected error:', error)
290
+ if (error instanceof DIError) {
291
+ switch (error.code) {
292
+ case DIErrorCode.FactoryNotFound:
293
+ console.error('Service not registered:', error.message)
294
+ break
295
+ case DIErrorCode.CircularDependency:
296
+ console.error('Circular dependency:', error.message)
297
+ break
298
+ default:
299
+ console.error('DI error:', error.message)
300
+ }
230
301
  }
231
302
  }
232
303
  ```
@@ -253,33 +324,50 @@ const paymentContainer = new Container(paymentRegistry)
253
324
  ### 5. Manage Request Contexts Properly
254
325
 
255
326
  ```typescript
256
- // Always clean up request contexts
327
+ // Always clean up request contexts with try/finally
257
328
  async function handleRequest(req, res) {
258
329
  const requestId = generateRequestId()
259
- const context = container.beginRequest(requestId, {
330
+ const scoped = container.beginRequest(requestId, {
260
331
  ip: req.ip,
261
332
  userAgent: req.get('User-Agent'),
262
333
  })
263
334
 
264
335
  try {
265
- container.setCurrentRequestContext(requestId)
266
- await processRequest(req, res)
336
+ const service = await scoped.get(RequestHandler)
337
+ await service.handle(req, res)
267
338
  } finally {
268
- await container.endRequest(requestId)
339
+ await scoped.endRequest()
269
340
  }
270
341
  }
271
342
  ```
272
343
 
344
+ ### 6. Clean Up on Shutdown
345
+
346
+ ```typescript
347
+ async function shutdown() {
348
+ // Dispose the container to clean up all services
349
+ await container.dispose()
350
+ }
351
+
352
+ process.on('SIGTERM', shutdown)
353
+ process.on('SIGINT', shutdown)
354
+ ```
355
+
273
356
  ## API Reference
274
357
 
275
358
  ### Constructor
276
359
 
277
360
  ```typescript
278
- constructor(registry?: Registry, logger?: Console | null)
361
+ constructor(
362
+ registry?: Registry,
363
+ logger?: Console | null,
364
+ injectors?: Injectors
365
+ )
279
366
  ```
280
367
 
281
368
  - `registry`: Optional registry instance (defaults to global registry)
282
369
  - `logger`: Optional logger instance for debugging
370
+ - `injectors`: Optional custom injectors
283
371
 
284
372
  ### Methods
285
373
 
@@ -295,6 +383,8 @@ Gets a service instance from the container.
295
383
  - `get<T>(token: BoundInjectionToken<T, any>): Promise<T>`
296
384
  - `get<T>(token: FactoryInjectionToken<T, any>): Promise<T>`
297
385
 
386
+ **Note:** Throws an error for request-scoped services. Use `ScopedContainer` instead.
387
+
298
388
  #### `invalidate(service: unknown): Promise<void>`
299
389
 
300
390
  Invalidates a service and its dependencies.
@@ -303,47 +393,121 @@ Invalidates a service and its dependencies.
303
393
 
304
394
  Waits for all pending operations to complete.
305
395
 
396
+ #### `dispose(): Promise<void>`
397
+
398
+ Disposes the container and cleans up all resources.
399
+
400
+ #### `clear(): Promise<void>`
401
+
402
+ Clears all instances and bindings.
403
+
404
+ #### `isRegistered(token: any): boolean`
405
+
406
+ Checks if a service is registered.
407
+
408
+ #### `tryGetSync<T>(token: any, args?: any): T | null`
409
+
410
+ Gets an instance synchronously if it already exists and is ready.
411
+
306
412
  #### `getServiceLocator(): ServiceLocator`
307
413
 
308
414
  Returns the underlying ServiceLocator instance.
309
415
 
310
- #### `beginRequest(requestId: string, metadata?: Record<string, any>, priority?: number): RequestContextHolder`
416
+ #### `getRegistry(): Registry`
311
417
 
312
- Begins a new request context with the given parameters.
418
+ Returns the registry used by this container.
419
+
420
+ #### `beginRequest(requestId: string, metadata?: Record<string, any>, priority?: number): ScopedContainer`
421
+
422
+ Begins a new request context and returns a `ScopedContainer`.
313
423
 
314
424
  - `requestId`: Unique identifier for this request
315
425
  - `metadata`: Optional metadata for the request
316
426
  - `priority`: Priority for resolution (higher = more priority, defaults to 100)
317
427
 
318
- #### `endRequest(requestId: string): Promise<void>`
428
+ #### `getActiveRequestIds(): ReadonlySet<string>`
319
429
 
320
- Ends a request context and cleans up all associated instances.
430
+ Gets the set of active request IDs.
321
431
 
322
- #### `getCurrentRequestContext(): RequestContextHolder | null`
432
+ #### `hasActiveRequest(requestId: string): boolean`
323
433
 
324
- Gets the current request context.
434
+ Checks if a request is active.
325
435
 
326
- #### `setCurrentRequestContext(requestId: string): void`
436
+ ## ScopedContainer API
327
437
 
328
- Sets the current request context.
438
+ The `ScopedContainer` is returned by `container.beginRequest()` and provides isolated request-scoped service resolution.
329
439
 
330
- ## Error Handling
440
+ ### Methods
441
+
442
+ #### `get<T>(token: T): Promise<InstanceType<T>>`
443
+
444
+ Gets a service instance. Request-scoped services are resolved from this container's context, others are delegated to the parent.
445
+
446
+ #### `invalidate(service: unknown): Promise<void>`
447
+
448
+ Invalidates a service within the request context.
331
449
 
332
- The Container can throw various errors:
450
+ #### `endRequest(): Promise<void>`
451
+
452
+ Ends the request and cleans up all request-scoped instances.
453
+
454
+ #### `dispose(): Promise<void>`
455
+
456
+ Alias for `endRequest()`.
457
+
458
+ #### `ready(): Promise<void>`
459
+
460
+ Waits for pending operations.
461
+
462
+ #### `getMetadata(key: string): any | undefined`
463
+
464
+ Gets request metadata.
465
+
466
+ #### `setMetadata(key: string, value: any): void`
467
+
468
+ Sets request metadata.
469
+
470
+ #### `addInstance(token: InjectionToken<any, undefined>, instance: any): void`
471
+
472
+ Adds a pre-prepared instance to the request context.
473
+
474
+ #### `getRequestId(): string`
475
+
476
+ Gets the request ID.
477
+
478
+ #### `getParent(): Container`
479
+
480
+ Gets the parent container.
481
+
482
+ #### `tryGetSync<T>(token: any, args?: any): T | null`
483
+
484
+ Gets an instance synchronously if it exists and is ready.
485
+
486
+ ## Error Handling
333
487
 
334
- - `InstanceNotFoundError`: When a service is not registered
335
- - `InstanceExpiredError`: When a service instance has expired
336
- - `InstanceDestroyingError`: When trying to access a service being destroyed
337
- - `UnknownError`: For unexpected errors
488
+ The Container throws `DIError` with specific error codes:
338
489
 
339
490
  ```typescript
340
- import { InstanceNotFoundError } from '@navios/di'
491
+ import { DIError, DIErrorCode } from '@navios/di'
341
492
 
342
493
  try {
343
494
  const service = await container.get(UnregisteredService)
344
495
  } catch (error) {
345
- if (error instanceof InstanceNotFoundError) {
346
- console.error('Service not found:', error.message)
496
+ if (error instanceof DIError) {
497
+ switch (error.code) {
498
+ case DIErrorCode.FactoryNotFound:
499
+ console.error('Service not registered')
500
+ break
501
+ case DIErrorCode.InstanceDestroying:
502
+ console.error('Service is being destroyed')
503
+ break
504
+ case DIErrorCode.CircularDependency:
505
+ console.error('Circular dependency detected:', error.message)
506
+ break
507
+ case DIErrorCode.UnknownError:
508
+ console.error('Unknown error:', error.message)
509
+ break
510
+ }
347
511
  }
348
512
  }
349
513
  ```
@@ -5,10 +5,10 @@
5
5
  * where you need services that are shared within a request but isolated between requests.
6
6
  */
7
7
 
8
- import { Container } from '../../src/container.mjs'
8
+ import { Container } from '../../src/container/container.mjs'
9
9
  import { Injectable } from '../../src/decorators/injectable.decorator.mjs'
10
10
  import { InjectableScope } from '../../src/enums/index.mjs'
11
- import { inject } from '../../src/injector.mjs'
11
+ import { inject } from '../../src/injectors.mjs'
12
12
 
13
13
  // ============================================================================
14
14
  // EXAMPLE SERVICES
package/docs/factory.md CHANGED
@@ -560,14 +560,9 @@ interface FactoryOptions {
560
560
 
561
561
  ```typescript
562
562
  interface FactoryContext {
563
- inject<T>(token: T): Promise<T>
564
- locator: ServiceLocator
565
- on(event: string, listener: Function): void
566
- getDependencies(): any[]
567
- invalidate(): Promise<void>
568
- addEffect(effect: Function): void
569
- setTtl(ttl: number): void
570
- getTtl(): number | null
563
+ inject: typeof asyncInject // Inject dependencies asynchronously
564
+ locator: ServiceLocator // Access to the service locator
565
+ addDestroyListener: (listener: () => void | Promise<void>) => void // Register cleanup callback
571
566
  }
572
567
  ```
573
568
 
@@ -50,7 +50,7 @@ import { asyncInject, Container, inject, Injectable } from '@navios/di'
50
50
  Let's create a simple example with a user service that depends on an email service:
51
51
 
52
52
  ```typescript
53
- import { asyncInject, Container, inject, Injectable } from '@navios/di'
53
+ import { Container, inject, Injectable } from '@navios/di'
54
54
 
55
55
  // 1. Create an email service
56
56
  @Injectable()
@@ -66,14 +66,13 @@ class EmailService {
66
66
  // 2. Create a user service that depends on the email service
67
67
  @Injectable()
68
68
  class UserService {
69
- private readonly emailService = asyncInject(EmailService)
69
+ private readonly emailService = inject(EmailService)
70
70
 
71
71
  async createUser(name: string, email: string) {
72
72
  console.log(`Creating user: ${name}`)
73
73
 
74
- // Get the email service and send welcome email
75
- const emailService = await this.emailService
76
- await emailService.sendEmail(
74
+ // Use the injected email service
75
+ await this.emailService.sendEmail(
77
76
  email,
78
77
  'Welcome!',
79
78
  `Hello ${name}, welcome to our platform!`,
@@ -289,8 +288,39 @@ class DatabaseService implements OnServiceInit, OnServiceDestroy {
289
288
 
290
289
  **Circular dependencies:**
291
290
 
292
- - Use `asyncInject()` instead of `inject()` for circular dependencies
293
- - Consider restructuring your services to avoid circular references
291
+ The library automatically detects circular dependencies and throws a clear error:
292
+
293
+ ```typescript
294
+ // This will throw: "Circular dependency detected: ServiceA -> ServiceB -> ServiceA"
295
+ @Injectable()
296
+ class ServiceA {
297
+ private serviceB = inject(ServiceB)
298
+ }
299
+
300
+ @Injectable()
301
+ class ServiceB {
302
+ private serviceA = inject(ServiceA)
303
+ }
304
+ ```
305
+
306
+ To fix circular dependencies, use `asyncInject()` on at least one side:
307
+
308
+ ```typescript
309
+ @Injectable()
310
+ class ServiceA {
311
+ private serviceB = asyncInject(ServiceB) // Break cycle with asyncInject
312
+
313
+ async doSomething() {
314
+ const b = await this.serviceB
315
+ return b.process()
316
+ }
317
+ }
318
+
319
+ @Injectable()
320
+ class ServiceB {
321
+ private serviceA = inject(ServiceA) // This side can use inject()
322
+ }
323
+ ```
294
324
 
295
325
  **Services not found:**
296
326
 
@@ -305,5 +335,4 @@ class DatabaseService implements OnServiceInit, OnServiceDestroy {
305
335
  ### Getting Help
306
336
 
307
337
  - Check the [API Reference](./api-reference.md)
308
- - Look at [Advanced Patterns](./advanced-patterns.md)
309
338
  - Review the [Examples](./examples/) folder