@navios/di 0.5.0 → 0.6.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.
- package/CHANGELOG.md +146 -0
- package/README.md +196 -219
- package/docs/README.md +69 -11
- package/docs/api-reference.md +281 -117
- package/docs/container.md +220 -56
- package/docs/examples/request-scope-example.mts +2 -2
- package/docs/factory.md +3 -8
- package/docs/getting-started.md +37 -8
- package/docs/migration.md +318 -37
- package/docs/request-contexts.md +263 -175
- package/docs/scopes.md +79 -42
- package/lib/browser/index.d.mts +1577 -0
- package/lib/browser/index.d.mts.map +1 -0
- package/lib/browser/index.mjs +3012 -0
- package/lib/browser/index.mjs.map +1 -0
- package/lib/index-S_qX2VLI.d.mts +1211 -0
- package/lib/index-S_qX2VLI.d.mts.map +1 -0
- package/lib/index-fKPuT65j.d.cts +1206 -0
- package/lib/index-fKPuT65j.d.cts.map +1 -0
- package/lib/index.cjs +389 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +376 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +371 -78
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +325 -63
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +9 -0
- package/lib/testing/index.d.cts +2 -0
- package/lib/testing/index.d.mts +2 -2
- package/lib/testing/index.mjs +2 -72
- package/lib/testing-BMGmmxH7.cjs +2895 -0
- package/lib/testing-BMGmmxH7.cjs.map +1 -0
- package/lib/testing-DCXz8AJD.mjs +2655 -0
- package/lib/testing-DCXz8AJD.mjs.map +1 -0
- package/package.json +26 -4
- package/project.json +2 -2
- package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
- package/src/__tests__/async-local-storage.spec.mts +333 -0
- package/src/__tests__/container.spec.mts +30 -25
- package/src/__tests__/e2e.browser.spec.mts +790 -0
- package/src/__tests__/e2e.spec.mts +1222 -0
- package/src/__tests__/errors.spec.mts +6 -6
- package/src/__tests__/factory.spec.mts +1 -1
- package/src/__tests__/get-injectors.spec.mts +1 -1
- package/src/__tests__/injectable.spec.mts +1 -1
- package/src/__tests__/injection-token.spec.mts +1 -1
- package/src/__tests__/library-findings.spec.mts +563 -0
- package/src/__tests__/registry.spec.mts +2 -2
- package/src/__tests__/request-scope.spec.mts +266 -274
- package/src/__tests__/service-instantiator.spec.mts +19 -17
- package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
- package/src/__tests__/service-locator-manager.spec.mts +15 -15
- package/src/__tests__/service-locator.spec.mts +167 -244
- package/src/__tests__/unified-api.spec.mts +27 -27
- package/src/__type-tests__/factory.spec-d.mts +2 -2
- package/src/__type-tests__/inject.spec-d.mts +2 -2
- package/src/__type-tests__/injectable.spec-d.mts +1 -1
- package/src/browser.mts +16 -0
- package/src/container/container.mts +319 -0
- package/src/container/index.mts +2 -0
- package/src/container/scoped-container.mts +350 -0
- package/src/decorators/factory.decorator.mts +4 -4
- package/src/decorators/injectable.decorator.mts +5 -5
- package/src/errors/di-error.mts +13 -7
- package/src/errors/index.mts +0 -8
- package/src/index.mts +156 -15
- package/src/interfaces/container.interface.mts +82 -0
- package/src/interfaces/factory.interface.mts +2 -2
- package/src/interfaces/index.mts +1 -0
- package/src/internal/context/async-local-storage.mts +120 -0
- package/src/internal/context/factory-context.mts +18 -0
- package/src/internal/context/index.mts +3 -0
- package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
- package/src/internal/context/resolution-context.mts +63 -0
- package/src/internal/context/sync-local-storage.mts +51 -0
- package/src/internal/core/index.mts +5 -0
- package/src/internal/core/instance-resolver.mts +641 -0
- package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
- package/src/internal/core/invalidator.mts +437 -0
- package/src/internal/core/service-locator.mts +202 -0
- package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
- package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
- package/src/internal/holder/holder-manager.mts +85 -0
- package/src/internal/holder/holder-storage.interface.mts +116 -0
- package/src/internal/holder/index.mts +6 -0
- package/src/internal/holder/instance-holder.mts +109 -0
- package/src/internal/holder/request-storage.mts +134 -0
- package/src/internal/holder/singleton-storage.mts +105 -0
- package/src/internal/index.mts +4 -0
- package/src/internal/lifecycle/circular-detector.mts +77 -0
- package/src/internal/lifecycle/index.mts +2 -0
- package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +12 -5
- package/src/testing/__tests__/test-container.spec.mts +2 -2
- package/src/testing/test-container.mts +4 -4
- package/src/token/index.mts +2 -0
- package/src/{injection-token.mts → token/injection-token.mts} +1 -1
- package/src/{registry.mts → token/registry.mts} +1 -1
- package/src/utils/get-injectable-token.mts +1 -1
- package/src/utils/get-injectors.mts +32 -15
- package/src/utils/types.mts +1 -1
- package/tsdown.config.mts +67 -0
- package/lib/_tsup-dts-rollup.d.mts +0 -1283
- package/lib/_tsup-dts-rollup.d.ts +0 -1283
- package/lib/chunk-44F3LXW5.mjs +0 -2043
- package/lib/chunk-44F3LXW5.mjs.map +0 -1
- package/lib/index.d.ts +0 -78
- package/lib/index.js +0 -2127
- package/lib/index.js.map +0 -1
- package/lib/testing/index.d.ts +0 -2
- package/lib/testing/index.js +0 -2060
- package/lib/testing/index.js.map +0 -1
- package/lib/testing/index.mjs.map +0 -1
- package/src/container.mts +0 -227
- package/src/factory-context.mts +0 -8
- package/src/instance-resolver.mts +0 -559
- package/src/request-context-manager.mts +0 -149
- package/src/service-invalidator.mts +0 -429
- package/src/service-locator-instance-holder.mts +0 -70
- package/src/service-locator-manager.mts +0 -85
- package/src/service-locator.mts +0 -246
- package/tsup.config.mts +0 -12
- /package/src/{injector.mts → injectors.mts} +0 -0
package/docs/README.md
CHANGED
|
@@ -11,8 +11,7 @@ Welcome to the comprehensive documentation for Navios DI, a powerful dependency
|
|
|
11
11
|
- [Injection Tokens](./injection-tokens.md) - Token-based dependency resolution
|
|
12
12
|
- [Request Contexts](./request-contexts.md) - Request-scoped services and cleanup
|
|
13
13
|
- [Service Lifecycle](./lifecycle.md) - Initialization and cleanup hooks
|
|
14
|
-
- [Scopes](./scopes.md) - Singleton and
|
|
15
|
-
- [Advanced Patterns](./advanced-patterns.md) - Complex usage scenarios
|
|
14
|
+
- [Scopes](./scopes.md) - Singleton, transient, and request service scopes
|
|
16
15
|
- [API Reference](./api-reference.md) - Complete API documentation
|
|
17
16
|
- [Migration Guide](./migration.md) - Upgrading from older versions
|
|
18
17
|
|
|
@@ -30,15 +29,17 @@ Welcome to the comprehensive documentation for Navios DI, a powerful dependency
|
|
|
30
29
|
|
|
31
30
|
- **Type Safety** - Full TypeScript support with compile-time checking
|
|
32
31
|
- **Lifecycle Management** - Built-in hooks for service initialization and cleanup
|
|
33
|
-
- **Multiple Scopes** - Singleton and
|
|
32
|
+
- **Multiple Scopes** - Singleton, transient, and request service lifetimes
|
|
34
33
|
- **Async/Sync Injection** - Both synchronous and asynchronous dependency resolution
|
|
34
|
+
- **Circular Dependency Detection** - Automatic detection with clear error messages
|
|
35
35
|
- **Factory Pattern** - Complex object creation with factory classes
|
|
36
36
|
- **Request Contexts** - Request-scoped services with priority resolution and automatic cleanup
|
|
37
|
+
- **Cross-Platform** - Works in Node.js, Bun, Deno, and browsers
|
|
37
38
|
|
|
38
39
|
### Getting Started
|
|
39
40
|
|
|
40
41
|
```typescript
|
|
41
|
-
import {
|
|
42
|
+
import { inject, Container, Injectable } from '@navios/di'
|
|
42
43
|
|
|
43
44
|
@Injectable()
|
|
44
45
|
class DatabaseService {
|
|
@@ -49,11 +50,11 @@ class DatabaseService {
|
|
|
49
50
|
|
|
50
51
|
@Injectable()
|
|
51
52
|
class UserService {
|
|
52
|
-
private readonly db =
|
|
53
|
+
private readonly db = inject(DatabaseService)
|
|
53
54
|
|
|
54
55
|
async getUsers() {
|
|
55
|
-
const
|
|
56
|
-
return
|
|
56
|
+
const users = await this.db.query('SELECT * FROM users')
|
|
57
|
+
return users
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -68,10 +69,12 @@ console.log(await userService.getUsers())
|
|
|
68
69
|
Navios DI follows a modern, decorator-based architecture:
|
|
69
70
|
|
|
70
71
|
1. **Services** are marked with `@Injectable()` decorator
|
|
71
|
-
2. **Dependencies** are injected using `
|
|
72
|
+
2. **Dependencies** are injected using `inject()` or `asyncInject()`
|
|
72
73
|
3. **Container** manages service instances and their lifecycle
|
|
73
|
-
4. **
|
|
74
|
-
5. **
|
|
74
|
+
4. **ScopedContainer** provides request-scoped isolation
|
|
75
|
+
5. **Injection Tokens** provide flexible dependency resolution
|
|
76
|
+
6. **Factories** handle complex object creation
|
|
77
|
+
7. **CircularDetector** prevents circular dependency deadlocks
|
|
75
78
|
|
|
76
79
|
## Design Principles
|
|
77
80
|
|
|
@@ -80,6 +83,16 @@ Navios DI follows a modern, decorator-based architecture:
|
|
|
80
83
|
- **Flexible Resolution** - Support both class-based and token-based injection
|
|
81
84
|
- **Lifecycle Awareness** - Built-in support for service initialization and cleanup
|
|
82
85
|
- **Performance Optimized** - Efficient instance management and caching
|
|
86
|
+
- **Safe by Default** - Automatic circular dependency detection
|
|
87
|
+
|
|
88
|
+
## Platform Support
|
|
89
|
+
|
|
90
|
+
| Platform | AsyncLocalStorage | Notes |
|
|
91
|
+
| -------- | ----------------- | ---------------------------------- |
|
|
92
|
+
| Node.js | Native | Full async tracking support |
|
|
93
|
+
| Bun | Native | Full async tracking support |
|
|
94
|
+
| Deno | Native | Via Node compatibility layer |
|
|
95
|
+
| Browser | Polyfill | Sync-only tracking (SyncLocalStorage) |
|
|
83
96
|
|
|
84
97
|
## Examples
|
|
85
98
|
|
|
@@ -149,9 +162,54 @@ class DatabaseService implements OnServiceInit, OnServiceDestroy {
|
|
|
149
162
|
}
|
|
150
163
|
```
|
|
151
164
|
|
|
165
|
+
### Handling Circular Dependencies
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Use asyncInject to break circular dependencies
|
|
169
|
+
@Injectable()
|
|
170
|
+
class ServiceA {
|
|
171
|
+
private serviceB = asyncInject(ServiceB) // Use asyncInject to break cycle
|
|
172
|
+
|
|
173
|
+
async doSomething() {
|
|
174
|
+
const b = await this.serviceB
|
|
175
|
+
return b.process()
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Injectable()
|
|
180
|
+
class ServiceB {
|
|
181
|
+
private serviceA = inject(ServiceA) // This side can use inject()
|
|
182
|
+
|
|
183
|
+
process() {
|
|
184
|
+
return 'processed'
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Request-Scoped Services
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
193
|
+
class RequestContext {
|
|
194
|
+
userId?: string
|
|
195
|
+
correlationId?: string
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// In HTTP middleware
|
|
199
|
+
app.use(async (req, res, next) => {
|
|
200
|
+
const scoped = container.beginRequest(req.id, { userId: req.userId })
|
|
201
|
+
req.container = scoped
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await next()
|
|
205
|
+
} finally {
|
|
206
|
+
await scoped.endRequest()
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
```
|
|
210
|
+
|
|
152
211
|
## Need Help?
|
|
153
212
|
|
|
154
213
|
- Check the [API Reference](./api-reference.md) for detailed method documentation
|
|
155
|
-
- Look at [Advanced Patterns](./advanced-patterns.md) for complex scenarios
|
|
156
214
|
- Review the [Migration Guide](./migration.md) if upgrading from older versions
|
|
157
215
|
- See the [Examples](./examples/) folder for complete working examples
|
package/docs/api-reference.md
CHANGED
|
@@ -9,9 +9,14 @@ Complete API reference for Navios DI library.
|
|
|
9
9
|
The main entry point for dependency injection.
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
class Container {
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
12
|
+
class Container implements IContainer {
|
|
13
|
+
constructor(
|
|
14
|
+
registry?: Registry,
|
|
15
|
+
logger?: Console | null,
|
|
16
|
+
injectors?: Injectors
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// Service resolution
|
|
15
20
|
get<T>(token: T): Promise<InstanceType<T>>
|
|
16
21
|
get<T, S extends InjectionTokenSchemaType>(
|
|
17
22
|
token: InjectionToken<T, S>,
|
|
@@ -21,18 +26,26 @@ class Container {
|
|
|
21
26
|
get<T>(token: BoundInjectionToken<T, any>): Promise<T>
|
|
22
27
|
get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
|
|
23
28
|
|
|
29
|
+
// Lifecycle
|
|
24
30
|
invalidate(service: unknown): Promise<void>
|
|
25
31
|
ready(): Promise<void>
|
|
32
|
+
dispose(): Promise<void>
|
|
33
|
+
clear(): Promise<void>
|
|
34
|
+
|
|
35
|
+
// Introspection
|
|
36
|
+
isRegistered(token: any): boolean
|
|
26
37
|
getServiceLocator(): ServiceLocator
|
|
38
|
+
getRegistry(): Registry
|
|
39
|
+
tryGetSync<T>(token: any, args?: any): T | null
|
|
27
40
|
|
|
28
41
|
// Request Context Management
|
|
29
42
|
beginRequest(
|
|
30
43
|
requestId: string,
|
|
31
44
|
metadata?: Record<string, any>,
|
|
32
45
|
priority?: number,
|
|
33
|
-
):
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
): ScopedContainer
|
|
47
|
+
getActiveRequestIds(): ReadonlySet<string>
|
|
48
|
+
hasActiveRequest(requestId: string): boolean
|
|
36
49
|
}
|
|
37
50
|
```
|
|
38
51
|
|
|
@@ -40,20 +53,72 @@ class Container {
|
|
|
40
53
|
|
|
41
54
|
- `registry?: Registry` - Optional registry instance (defaults to global registry)
|
|
42
55
|
- `logger?: Console | null` - Optional logger for debugging
|
|
56
|
+
- `injectors?: Injectors` - Optional custom injectors
|
|
43
57
|
|
|
44
58
|
**Methods:**
|
|
45
59
|
|
|
46
|
-
- `get<T>(token: T)` - Get a service instance
|
|
60
|
+
- `get<T>(token: T)` - Get a service instance (throws error for request-scoped services)
|
|
47
61
|
- `invalidate(service: unknown)` - Invalidate a service and its dependencies
|
|
48
62
|
- `ready()` - Wait for all pending operations to complete
|
|
63
|
+
- `dispose()` - Clean up all resources
|
|
64
|
+
- `clear()` - Clear all instances and bindings
|
|
65
|
+
- `isRegistered(token: any)` - Check if a service is registered
|
|
49
66
|
- `getServiceLocator()` - Get the underlying ServiceLocator instance
|
|
67
|
+
- `getRegistry()` - Get the registry
|
|
68
|
+
- `tryGetSync<T>(token, args?)` - Get instance synchronously if it exists
|
|
50
69
|
|
|
51
70
|
**Request Context Management:**
|
|
52
71
|
|
|
53
|
-
- `beginRequest(requestId
|
|
54
|
-
- `
|
|
55
|
-
- `
|
|
56
|
-
|
|
72
|
+
- `beginRequest(requestId, metadata?, priority?)` - Begin a new request context, returns `ScopedContainer`
|
|
73
|
+
- `getActiveRequestIds()` - Get set of active request IDs
|
|
74
|
+
- `hasActiveRequest(requestId)` - Check if a request is active
|
|
75
|
+
|
|
76
|
+
### ScopedContainer
|
|
77
|
+
|
|
78
|
+
Request-scoped container for isolated request-scoped service resolution.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
class ScopedContainer implements IContainer {
|
|
82
|
+
readonly requestId: string
|
|
83
|
+
|
|
84
|
+
// Service resolution
|
|
85
|
+
get<T>(token: T): Promise<InstanceType<T>>
|
|
86
|
+
get<T, S extends InjectionTokenSchemaType>(
|
|
87
|
+
token: InjectionToken<T, S>,
|
|
88
|
+
args: z.input<S>,
|
|
89
|
+
): Promise<T>
|
|
90
|
+
|
|
91
|
+
// Lifecycle
|
|
92
|
+
invalidate(service: unknown): Promise<void>
|
|
93
|
+
endRequest(): Promise<void>
|
|
94
|
+
dispose(): Promise<void> // Alias for endRequest()
|
|
95
|
+
ready(): Promise<void>
|
|
96
|
+
|
|
97
|
+
// Introspection
|
|
98
|
+
isRegistered(token: any): boolean
|
|
99
|
+
getParent(): Container
|
|
100
|
+
getRequestId(): string
|
|
101
|
+
getRequestContextHolder(): RequestContext
|
|
102
|
+
getHolderStorage(): IHolderStorage
|
|
103
|
+
tryGetSync<T>(token: any, args?: any): T | null
|
|
104
|
+
|
|
105
|
+
// Metadata
|
|
106
|
+
getMetadata(key: string): any | undefined
|
|
107
|
+
setMetadata(key: string, value: any): void
|
|
108
|
+
addInstance(token: InjectionToken<any, undefined>, instance: any): void
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Methods:**
|
|
113
|
+
|
|
114
|
+
- `get<T>(token: T)` - Get a service instance (request-scoped or delegated to parent)
|
|
115
|
+
- `invalidate(service: unknown)` - Invalidate a service
|
|
116
|
+
- `endRequest()` - End request and cleanup all request-scoped instances
|
|
117
|
+
- `dispose()` - Alias for `endRequest()`
|
|
118
|
+
- `ready()` - Wait for pending operations
|
|
119
|
+
- `getMetadata(key)` - Get request metadata
|
|
120
|
+
- `setMetadata(key, value)` - Set request metadata
|
|
121
|
+
- `addInstance(token, instance)` - Add pre-prepared instance to request context
|
|
57
122
|
|
|
58
123
|
### InjectionToken
|
|
59
124
|
|
|
@@ -317,85 +382,103 @@ interface FactorableWithArgs<T, S> {
|
|
|
317
382
|
}
|
|
318
383
|
```
|
|
319
384
|
|
|
320
|
-
###
|
|
385
|
+
### RequestContext
|
|
321
386
|
|
|
322
387
|
Interface for managing request-scoped instances.
|
|
323
388
|
|
|
324
389
|
```typescript
|
|
325
|
-
interface
|
|
390
|
+
interface RequestContext {
|
|
326
391
|
readonly requestId: string
|
|
327
|
-
readonly holders: Map<string,
|
|
392
|
+
readonly holders: Map<string, InstanceHolder>
|
|
328
393
|
readonly priority: number
|
|
329
394
|
readonly metadata: Map<string, any>
|
|
330
395
|
readonly createdAt: number
|
|
331
396
|
|
|
397
|
+
addInstance(token: InjectionToken<any, undefined>, instance: any): void
|
|
332
398
|
addInstance(
|
|
333
399
|
instanceName: string,
|
|
334
400
|
instance: any,
|
|
335
|
-
holder:
|
|
401
|
+
holder: InstanceHolder,
|
|
336
402
|
): void
|
|
337
|
-
|
|
338
|
-
|
|
403
|
+
get(instanceName: string): InstanceHolder | undefined
|
|
404
|
+
set(instanceName: string, holder: InstanceHolder): void
|
|
339
405
|
has(instanceName: string): boolean
|
|
340
406
|
clear(): void
|
|
341
407
|
getMetadata(key: string): any | undefined
|
|
342
408
|
setMetadata(key: string, value: any): void
|
|
343
|
-
|
|
344
|
-
// Inherited from BaseInstanceHolderManager
|
|
345
409
|
filter(
|
|
346
|
-
predicate: (
|
|
347
|
-
|
|
348
|
-
key: string,
|
|
349
|
-
) => boolean,
|
|
350
|
-
): Map<string, ServiceLocatorInstanceHolder>
|
|
410
|
+
predicate: (value: InstanceHolder<any>, key: string) => boolean,
|
|
411
|
+
): Map<string, InstanceHolder>
|
|
351
412
|
delete(name: string): boolean
|
|
352
413
|
size(): number
|
|
353
414
|
isEmpty(): boolean
|
|
354
415
|
}
|
|
355
416
|
```
|
|
356
417
|
|
|
418
|
+
**Deprecated alias:** `RequestContextHolder`
|
|
419
|
+
|
|
357
420
|
### FactoryContext
|
|
358
421
|
|
|
359
422
|
Context provided to factory methods.
|
|
360
423
|
|
|
361
424
|
```typescript
|
|
362
425
|
interface FactoryContext {
|
|
363
|
-
inject
|
|
426
|
+
inject: typeof asyncInject
|
|
364
427
|
locator: ServiceLocator
|
|
365
|
-
|
|
366
|
-
getDependencies(): any[]
|
|
367
|
-
invalidate(): Promise<void>
|
|
368
|
-
addEffect(effect: Function): void
|
|
369
|
-
setTtl(ttl: number): void
|
|
370
|
-
getTtl(): number | null
|
|
428
|
+
addDestroyListener: (listener: () => void | Promise<void>) => void
|
|
371
429
|
}
|
|
372
430
|
```
|
|
373
431
|
|
|
374
|
-
###
|
|
432
|
+
### IContainer
|
|
375
433
|
|
|
376
|
-
|
|
434
|
+
Common interface for Container and ScopedContainer.
|
|
377
435
|
|
|
378
436
|
```typescript
|
|
379
|
-
interface
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
437
|
+
interface IContainer {
|
|
438
|
+
get<T>(token: T, args?: any): Promise<T>
|
|
439
|
+
invalidate(service: unknown): Promise<void>
|
|
440
|
+
isRegistered(token: any): boolean
|
|
441
|
+
dispose(): Promise<void>
|
|
442
|
+
ready(): Promise<void>
|
|
443
|
+
tryGetSync<T>(token: any, args?: any): T | null
|
|
444
|
+
}
|
|
445
|
+
```
|
|
385
446
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
447
|
+
### InstanceHolder
|
|
448
|
+
|
|
449
|
+
Represents a managed service instance with its lifecycle state.
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
interface InstanceHolder<T = unknown> {
|
|
453
|
+
name: string
|
|
454
|
+
instance: T | null
|
|
455
|
+
status: InstanceStatus
|
|
456
|
+
type: InjectableType
|
|
457
|
+
scope: InjectableScope
|
|
458
|
+
deps: Set<string> // Services this holder depends on
|
|
459
|
+
waitingFor: Set<string> // Services this holder is waiting for (cycle detection)
|
|
460
|
+
destroyListeners: InstanceDestroyListener[]
|
|
461
|
+
createdAt: number
|
|
462
|
+
creationPromise: Promise<[undefined, T]> | null
|
|
463
|
+
destroyPromise: Promise<void> | null
|
|
396
464
|
}
|
|
397
465
|
```
|
|
398
466
|
|
|
467
|
+
**Deprecated alias:** `ServiceLocatorInstanceHolder`
|
|
468
|
+
|
|
469
|
+
### InstanceStatus
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
enum InstanceStatus {
|
|
473
|
+
Creating = 'creating',
|
|
474
|
+
Created = 'created',
|
|
475
|
+
Destroying = 'destroying',
|
|
476
|
+
Error = 'error',
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Deprecated alias:** `ServiceLocatorInstanceHolderStatus`
|
|
481
|
+
|
|
399
482
|
## Functions
|
|
400
483
|
|
|
401
484
|
### asyncInject
|
|
@@ -448,6 +531,66 @@ function inject<T>(token: BoundInjectionToken<T, any>): T
|
|
|
448
531
|
function inject<T>(token: FactoryInjectionToken<T, any>): T
|
|
449
532
|
```
|
|
450
533
|
|
|
534
|
+
### optional
|
|
535
|
+
|
|
536
|
+
Optional dependency injection (returns null if not registered).
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
function optional<T extends ClassType>(
|
|
540
|
+
token: T,
|
|
541
|
+
): InstanceType<T> | null
|
|
542
|
+
function optional<T>(token: InjectionToken<T, any>): T | null
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### wrapSyncInit
|
|
546
|
+
|
|
547
|
+
Wraps a synchronous initialization function.
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
function wrapSyncInit<T>(fn: () => T): T
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### provideFactoryContext
|
|
554
|
+
|
|
555
|
+
Provides a factory context for the duration of a function execution.
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
function provideFactoryContext<T>(ctx: FactoryContext, fn: () => T): T
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### withResolutionContext
|
|
562
|
+
|
|
563
|
+
Runs a function within a resolution context for cycle detection.
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
function withResolutionContext<T>(
|
|
567
|
+
waiterHolder: InstanceHolder,
|
|
568
|
+
getHolder: (name: string) => InstanceHolder | undefined,
|
|
569
|
+
fn: () => T
|
|
570
|
+
): T
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### getCurrentResolutionContext
|
|
574
|
+
|
|
575
|
+
Gets the current resolution context if any.
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
function getCurrentResolutionContext(): ResolutionContextData | undefined
|
|
579
|
+
|
|
580
|
+
interface ResolutionContextData {
|
|
581
|
+
waiterHolder: InstanceHolder
|
|
582
|
+
getHolder: (name: string) => InstanceHolder | undefined
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### withoutResolutionContext
|
|
587
|
+
|
|
588
|
+
Runs a function outside of any resolution context (used by asyncInject).
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
function withoutResolutionContext<T>(fn: () => T): T
|
|
592
|
+
```
|
|
593
|
+
|
|
451
594
|
## Types
|
|
452
595
|
|
|
453
596
|
### ClassType
|
|
@@ -553,73 +696,73 @@ type InjectionTokenType =
|
|
|
553
696
|
|
|
554
697
|
### Injection Method Compatibility
|
|
555
698
|
|
|
556
|
-
| Scope | inject
|
|
557
|
-
| --------- |
|
|
558
|
-
| Singleton | ✅ Supported
|
|
559
|
-
| Transient |
|
|
560
|
-
| Request | ✅ Supported
|
|
561
|
-
|
|
562
|
-
**Note:** The `inject` function only works with Singleton and Request scopes because it requires synchronous resolution. Transient services must use `asyncInject` since they create new instances on each injection.
|
|
563
|
-
|
|
564
|
-
## Error Classes
|
|
565
|
-
|
|
566
|
-
### InstanceNotFoundError
|
|
699
|
+
| Scope | inject | asyncInject | optional |
|
|
700
|
+
| --------- | ------------ | ------------ | ------------ |
|
|
701
|
+
| Singleton | ✅ Supported | ✅ Supported | ✅ Supported |
|
|
702
|
+
| Transient | ✅ Supported | ✅ Supported | ✅ Supported |
|
|
703
|
+
| Request | ✅ Supported | ✅ Supported | ✅ Supported |
|
|
567
704
|
|
|
568
|
-
|
|
705
|
+
**Notes:**
|
|
706
|
+
- `inject()` works with all scopes but returns a proxy for dependencies not yet initialized
|
|
707
|
+
- `asyncInject()` is recommended for circular dependencies as it runs outside the resolution context
|
|
708
|
+
- `optional()` returns `null` if the service is not registered
|
|
569
709
|
|
|
570
|
-
|
|
571
|
-
class InstanceNotFoundError extends Error {
|
|
572
|
-
constructor(message: string)
|
|
573
|
-
}
|
|
574
|
-
```
|
|
710
|
+
## Error Handling
|
|
575
711
|
|
|
576
|
-
###
|
|
712
|
+
### DIError
|
|
577
713
|
|
|
578
|
-
|
|
714
|
+
Base error class for all DI-related errors.
|
|
579
715
|
|
|
580
716
|
```typescript
|
|
581
|
-
class
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
### InstanceDestroyingError
|
|
717
|
+
class DIError extends Error {
|
|
718
|
+
readonly code: DIErrorCode
|
|
587
719
|
|
|
588
|
-
|
|
720
|
+
constructor(code: DIErrorCode, message: string)
|
|
589
721
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
722
|
+
// Static factory methods
|
|
723
|
+
static factoryNotFound(message: string): DIError
|
|
724
|
+
static factoryTokenNotResolved(message: string): DIError
|
|
725
|
+
static instanceNotFound(message: string): DIError
|
|
726
|
+
static instanceDestroying(message: string): DIError
|
|
727
|
+
static circularDependency(message: string): DIError
|
|
728
|
+
static unknown(message: string): DIError
|
|
593
729
|
}
|
|
594
730
|
```
|
|
595
731
|
|
|
596
|
-
###
|
|
597
|
-
|
|
598
|
-
Thrown when a factory is not found.
|
|
732
|
+
### DIErrorCode
|
|
599
733
|
|
|
600
734
|
```typescript
|
|
601
|
-
|
|
602
|
-
|
|
735
|
+
enum DIErrorCode {
|
|
736
|
+
FactoryNotFound = 'FACTORY_NOT_FOUND',
|
|
737
|
+
FactoryTokenNotResolved = 'FACTORY_TOKEN_NOT_RESOLVED',
|
|
738
|
+
InstanceNotFound = 'INSTANCE_NOT_FOUND',
|
|
739
|
+
InstanceDestroying = 'INSTANCE_DESTROYING',
|
|
740
|
+
CircularDependency = 'CIRCULAR_DEPENDENCY',
|
|
741
|
+
UnknownError = 'UNKNOWN_ERROR',
|
|
603
742
|
}
|
|
604
743
|
```
|
|
605
744
|
|
|
606
|
-
###
|
|
607
|
-
|
|
608
|
-
Thrown when a factory token cannot be resolved.
|
|
745
|
+
### Error Handling Example
|
|
609
746
|
|
|
610
747
|
```typescript
|
|
611
|
-
|
|
612
|
-
constructor(message: string)
|
|
613
|
-
}
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### UnknownError
|
|
748
|
+
import { DIError, DIErrorCode } from '@navios/di'
|
|
617
749
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
750
|
+
try {
|
|
751
|
+
const service = await container.get(UnregisteredService)
|
|
752
|
+
} catch (error) {
|
|
753
|
+
if (error instanceof DIError) {
|
|
754
|
+
switch (error.code) {
|
|
755
|
+
case DIErrorCode.FactoryNotFound:
|
|
756
|
+
console.error('Service not registered')
|
|
757
|
+
break
|
|
758
|
+
case DIErrorCode.InstanceDestroying:
|
|
759
|
+
console.error('Service is being destroyed')
|
|
760
|
+
break
|
|
761
|
+
case DIErrorCode.CircularDependency:
|
|
762
|
+
console.error('Circular dependency detected:', error.message)
|
|
763
|
+
break
|
|
764
|
+
}
|
|
765
|
+
}
|
|
623
766
|
}
|
|
624
767
|
```
|
|
625
768
|
|
|
@@ -764,22 +907,13 @@ class RequestContext {
|
|
|
764
907
|
@Injectable({ scope: InjectableScope.Request })
|
|
765
908
|
class UserSession {
|
|
766
909
|
private readonly context = inject(RequestContext)
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
constructor(userId: string) {
|
|
770
|
-
this.userId = userId
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
getUserId() {
|
|
774
|
-
return this.userId
|
|
775
|
-
}
|
|
910
|
+
userId?: string
|
|
776
911
|
|
|
777
|
-
|
|
778
|
-
const ctx = await this.context
|
|
912
|
+
getRequestInfo() {
|
|
779
913
|
return {
|
|
780
914
|
userId: this.userId,
|
|
781
|
-
requestId:
|
|
782
|
-
duration:
|
|
915
|
+
requestId: this.context.getRequestId(),
|
|
916
|
+
duration: this.context.getDuration(),
|
|
783
917
|
}
|
|
784
918
|
}
|
|
785
919
|
}
|
|
@@ -787,15 +921,45 @@ class UserSession {
|
|
|
787
921
|
// Usage
|
|
788
922
|
const container = new Container()
|
|
789
923
|
|
|
790
|
-
// Begin request context
|
|
791
|
-
container.beginRequest('req-123', { userId: 'user123' })
|
|
924
|
+
// Begin request context - returns a ScopedContainer
|
|
925
|
+
const scoped = container.beginRequest('req-123', { userId: 'user123' })
|
|
792
926
|
|
|
793
|
-
// Get request-scoped instances
|
|
794
|
-
const session1 = await
|
|
795
|
-
const session2 = await
|
|
927
|
+
// Get request-scoped instances from the ScopedContainer
|
|
928
|
+
const session1 = await scoped.get(UserSession)
|
|
929
|
+
const session2 = await scoped.get(UserSession)
|
|
796
930
|
|
|
797
931
|
console.log(session1 === session2) // true - same instance within request
|
|
798
932
|
|
|
799
|
-
// End request context
|
|
800
|
-
await
|
|
933
|
+
// End request context and cleanup
|
|
934
|
+
await scoped.endRequest()
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### Circular Dependency Handling
|
|
938
|
+
|
|
939
|
+
```typescript
|
|
940
|
+
import { asyncInject, inject, Injectable } from '@navios/di'
|
|
941
|
+
|
|
942
|
+
// Use asyncInject to break circular dependencies
|
|
943
|
+
@Injectable()
|
|
944
|
+
class ServiceA {
|
|
945
|
+
private serviceB = asyncInject(ServiceB) // Break cycle here
|
|
946
|
+
|
|
947
|
+
async doSomething() {
|
|
948
|
+
const b = await this.serviceB
|
|
949
|
+
return b.process()
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
@Injectable()
|
|
954
|
+
class ServiceB {
|
|
955
|
+
private serviceA = inject(ServiceA) // This side can use inject
|
|
956
|
+
|
|
957
|
+
process() {
|
|
958
|
+
return 'processed'
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const container = new Container()
|
|
963
|
+
const serviceA = await container.get(ServiceA)
|
|
964
|
+
await serviceA.doSomething() // Works!
|
|
801
965
|
```
|