@navios/di 0.5.1 → 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 +23 -1
- 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 +18 -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} +11 -4
- 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-2M576LCC.mjs +0 -2043
- package/lib/chunk-2M576LCC.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/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Navios DI
|
|
2
2
|
|
|
3
|
-
A powerful, type-safe dependency injection library for TypeScript applications. Navios DI provides a modern, decorator-based approach to dependency injection with support for singletons, transients, factories, injection tokens, and service lifecycle management.
|
|
3
|
+
A powerful, type-safe dependency injection library for TypeScript applications. Navios DI provides a modern, decorator-based approach to dependency injection with support for singletons, transients, request-scoped services, factories, injection tokens, and service lifecycle management.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
7
|
+
- **Type-safe**: Full TypeScript support with compile-time type checking
|
|
8
|
+
- **Decorator-based**: Clean, declarative syntax using decorators
|
|
9
|
+
- **Lifecycle Management**: Built-in support for service initialization and cleanup
|
|
10
|
+
- **Factory Pattern**: Create instances using factory classes
|
|
11
|
+
- **Injection Tokens**: Flexible token-based dependency resolution
|
|
12
|
+
- **Scoped Instances**: Singleton, transient, and request scopes
|
|
13
|
+
- **Async/Sync Injection**: Both synchronous and asynchronous dependency resolution
|
|
14
|
+
- **Container API**: Simple container-based API for dependency management
|
|
15
|
+
- **Request Context**: Manage request-scoped services with automatic cleanup via ScopedContainer
|
|
16
|
+
- **Circular Dependency Detection**: Automatic detection and helpful error messages for circular dependencies
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
@@ -74,11 +74,38 @@ await container.invalidate(service)
|
|
|
74
74
|
// Wait for all pending operations
|
|
75
75
|
await container.ready()
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
// Clean up all resources
|
|
78
|
+
await container.dispose()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### ScopedContainer (Request Context)
|
|
82
|
+
|
|
83
|
+
For request-scoped services, use `ScopedContainer` which provides isolated service resolution:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { Container, Injectable, InjectableScope } from '@navios/di'
|
|
87
|
+
|
|
88
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
89
|
+
class RequestLogger {
|
|
90
|
+
constructor() {
|
|
91
|
+
console.log('New logger for this request')
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const container = new Container()
|
|
96
|
+
|
|
97
|
+
// Begin a request context - returns a ScopedContainer
|
|
98
|
+
const scopedContainer = container.beginRequest('req-123', { userId: 456 })
|
|
99
|
+
|
|
100
|
+
// Use the scoped container for request-scoped services
|
|
101
|
+
const logger = await scopedContainer.get(RequestLogger)
|
|
102
|
+
|
|
103
|
+
// Access metadata
|
|
104
|
+
scopedContainer.setMetadata('correlationId', 'abc-123')
|
|
105
|
+
const corrId = scopedContainer.getMetadata('correlationId')
|
|
106
|
+
|
|
107
|
+
// End the request (cleanup all request-scoped instances)
|
|
108
|
+
await scopedContainer.endRequest()
|
|
82
109
|
```
|
|
83
110
|
|
|
84
111
|
### Injectable Decorator
|
|
@@ -87,8 +114,6 @@ The `@Injectable` decorator marks a class as injectable:
|
|
|
87
114
|
|
|
88
115
|
```typescript
|
|
89
116
|
import { Injectable, InjectableScope } from '@navios/di'
|
|
90
|
-
|
|
91
|
-
// With schema (for constructor arguments)
|
|
92
117
|
import { z } from 'zod'
|
|
93
118
|
|
|
94
119
|
// Singleton (default)
|
|
@@ -99,10 +124,15 @@ class SingletonService {}
|
|
|
99
124
|
@Injectable({ scope: InjectableScope.Transient })
|
|
100
125
|
class TransientService {}
|
|
101
126
|
|
|
127
|
+
// Request-scoped (new instance per request context)
|
|
128
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
129
|
+
class RequestService {}
|
|
130
|
+
|
|
102
131
|
// With custom injection token
|
|
103
132
|
@Injectable({ token: MyToken })
|
|
104
133
|
class TokenizedService {}
|
|
105
134
|
|
|
135
|
+
// With schema for constructor arguments
|
|
106
136
|
const configSchema = z.object({
|
|
107
137
|
host: z.string(),
|
|
108
138
|
port: z.number(),
|
|
@@ -141,7 +171,7 @@ class NotificationService {
|
|
|
141
171
|
|
|
142
172
|
#### `asyncInject` - Asynchronous Injection
|
|
143
173
|
|
|
144
|
-
Use `asyncInject` for async dependency resolution:
|
|
174
|
+
Use `asyncInject` for async dependency resolution, especially useful for circular dependencies:
|
|
145
175
|
|
|
146
176
|
```typescript
|
|
147
177
|
@Injectable()
|
|
@@ -155,21 +185,46 @@ class AsyncService {
|
|
|
155
185
|
}
|
|
156
186
|
```
|
|
157
187
|
|
|
188
|
+
#### `optional` - Optional Injection
|
|
189
|
+
|
|
190
|
+
Use `optional` to inject a dependency only if it's available:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
@Injectable()
|
|
194
|
+
class FeatureService {
|
|
195
|
+
private readonly analytics = optional(AnalyticsService)
|
|
196
|
+
|
|
197
|
+
track(event: string) {
|
|
198
|
+
// Only calls analytics if the service is available
|
|
199
|
+
this.analytics?.track(event)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
158
204
|
### Factory Decorator
|
|
159
205
|
|
|
160
206
|
Create instances using factory classes:
|
|
161
207
|
|
|
162
208
|
```typescript
|
|
163
|
-
import { Factory } from '@navios/di'
|
|
209
|
+
import { Factory, Factorable, FactoryContext } from '@navios/di'
|
|
164
210
|
|
|
165
211
|
@Factory()
|
|
166
|
-
class DatabaseConnectionFactory {
|
|
167
|
-
create() {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
212
|
+
class DatabaseConnectionFactory implements Factorable<Connection> {
|
|
213
|
+
async create(ctx?: FactoryContext) {
|
|
214
|
+
const config = await ctx?.inject(ConfigService)
|
|
215
|
+
|
|
216
|
+
const connection = {
|
|
217
|
+
host: config?.host ?? 'localhost',
|
|
218
|
+
port: config?.port ?? 5432,
|
|
171
219
|
connected: true,
|
|
172
220
|
}
|
|
221
|
+
|
|
222
|
+
// Register cleanup callback
|
|
223
|
+
ctx?.addDestroyListener(() => {
|
|
224
|
+
connection.connected = false
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
return connection
|
|
173
228
|
}
|
|
174
229
|
}
|
|
175
230
|
|
|
@@ -203,46 +258,9 @@ class DatabaseService implements OnServiceInit, OnServiceDestroy {
|
|
|
203
258
|
|
|
204
259
|
private async connect() {
|
|
205
260
|
// Database connection logic
|
|
206
|
-
return { connected: true }
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Request Context Management
|
|
212
|
-
|
|
213
|
-
Manage request-scoped services with automatic cleanup and priority-based resolution:
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
import { Container, Injectable, InjectionToken } from '@navios/di'
|
|
217
|
-
|
|
218
|
-
const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
|
|
219
|
-
|
|
220
|
-
@Injectable()
|
|
221
|
-
class RequestLogger {
|
|
222
|
-
private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
|
|
223
|
-
|
|
224
|
-
async log(message: string) {
|
|
225
|
-
const id = await this.requestId
|
|
226
|
-
console.log(`[${id}] ${message}`)
|
|
261
|
+
return { connected: true, close: async () => {} }
|
|
227
262
|
}
|
|
228
263
|
}
|
|
229
|
-
|
|
230
|
-
// Usage
|
|
231
|
-
const container = new Container()
|
|
232
|
-
|
|
233
|
-
// Begin a request context
|
|
234
|
-
const context = container.beginRequest('req-123', { userId: 456 }, 100)
|
|
235
|
-
context.addInstance(REQUEST_ID_TOKEN, 'req-123')
|
|
236
|
-
|
|
237
|
-
// Set as current context
|
|
238
|
-
container.setCurrentRequestContext('req-123')
|
|
239
|
-
|
|
240
|
-
// Use services within the request context
|
|
241
|
-
const logger = await container.get(RequestLogger)
|
|
242
|
-
await logger.log('Processing request') // "[req-123] Processing request"
|
|
243
|
-
|
|
244
|
-
// End the request context (automatically cleans up)
|
|
245
|
-
await container.endRequest('req-123')
|
|
246
264
|
```
|
|
247
265
|
|
|
248
266
|
### Injection Tokens
|
|
@@ -253,7 +271,6 @@ Use injection tokens for flexible dependency resolution:
|
|
|
253
271
|
|
|
254
272
|
```typescript
|
|
255
273
|
import { Container, Injectable, InjectionToken } from '@navios/di'
|
|
256
|
-
|
|
257
274
|
import { z } from 'zod'
|
|
258
275
|
|
|
259
276
|
const configSchema = z.object({
|
|
@@ -261,7 +278,7 @@ const configSchema = z.object({
|
|
|
261
278
|
timeout: z.number(),
|
|
262
279
|
})
|
|
263
280
|
|
|
264
|
-
const CONFIG_TOKEN = InjectionToken.create<
|
|
281
|
+
const CONFIG_TOKEN = InjectionToken.create<z.infer<typeof configSchema>, typeof configSchema>(
|
|
265
282
|
'APP_CONFIG',
|
|
266
283
|
configSchema,
|
|
267
284
|
)
|
|
@@ -295,7 +312,6 @@ const BoundConfig = InjectionToken.bound(CONFIG_TOKEN, {
|
|
|
295
312
|
|
|
296
313
|
// No need to provide arguments
|
|
297
314
|
const container = new Container()
|
|
298
|
-
|
|
299
315
|
const config = await container.get(BoundConfig)
|
|
300
316
|
```
|
|
301
317
|
|
|
@@ -317,13 +333,10 @@ const config = await container.get(FactoryConfig)
|
|
|
317
333
|
|
|
318
334
|
### Injectable with Schema
|
|
319
335
|
|
|
320
|
-
Instead of creating an injection token with a schema, you can directly provide a schema to the `@Injectable` decorator
|
|
321
|
-
|
|
322
|
-
#### Basic Schema Usage
|
|
336
|
+
Instead of creating an injection token with a schema, you can directly provide a schema to the `@Injectable` decorator:
|
|
323
337
|
|
|
324
338
|
```typescript
|
|
325
|
-
import {
|
|
326
|
-
|
|
339
|
+
import { Injectable } from '@navios/di'
|
|
327
340
|
import { z } from 'zod'
|
|
328
341
|
|
|
329
342
|
const databaseConfigSchema = z.object({
|
|
@@ -342,7 +355,7 @@ class DatabaseConfig {
|
|
|
342
355
|
}
|
|
343
356
|
}
|
|
344
357
|
|
|
345
|
-
// Usage with
|
|
358
|
+
// Usage with arguments
|
|
346
359
|
const container = new Container()
|
|
347
360
|
const config = await container.get(DatabaseConfig, {
|
|
348
361
|
host: 'localhost',
|
|
@@ -353,34 +366,6 @@ const config = await container.get(DatabaseConfig, {
|
|
|
353
366
|
console.log(config.getConnectionString()) // "localhost:5432"
|
|
354
367
|
```
|
|
355
368
|
|
|
356
|
-
#### Schema with Different Scopes
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
// Singleton with schema (default)
|
|
360
|
-
@Injectable({ schema: apiConfigSchema })
|
|
361
|
-
class ApiConfig {
|
|
362
|
-
constructor(public readonly config: z.output<typeof apiConfigSchema>) {}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Transient with schema
|
|
366
|
-
@Injectable({
|
|
367
|
-
schema: loggerConfigSchema,
|
|
368
|
-
scope: InjectableScope.Transient,
|
|
369
|
-
})
|
|
370
|
-
class Logger {
|
|
371
|
-
constructor(public readonly config: z.output<typeof loggerConfigSchema>) {}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Request-scoped with schema
|
|
375
|
-
@Injectable({
|
|
376
|
-
schema: userContextSchema,
|
|
377
|
-
scope: InjectableScope.Request,
|
|
378
|
-
})
|
|
379
|
-
class UserContext {
|
|
380
|
-
constructor(public readonly context: z.output<typeof userContextSchema>) {}
|
|
381
|
-
}
|
|
382
|
-
```
|
|
383
|
-
|
|
384
369
|
#### Using Schema-based Services as Dependencies
|
|
385
370
|
|
|
386
371
|
```typescript
|
|
@@ -395,7 +380,7 @@ class DatabaseConfig {
|
|
|
395
380
|
|
|
396
381
|
@Injectable()
|
|
397
382
|
class DatabaseService {
|
|
398
|
-
// Inject with bound
|
|
383
|
+
// Inject with bound arguments
|
|
399
384
|
private dbConfig = inject(DatabaseConfig, {
|
|
400
385
|
connectionString: 'postgres://localhost:5432/myapp',
|
|
401
386
|
})
|
|
@@ -404,114 +389,36 @@ class DatabaseService {
|
|
|
404
389
|
return `Connecting to ${this.dbConfig.config.connectionString}`
|
|
405
390
|
}
|
|
406
391
|
}
|
|
407
|
-
|
|
408
|
-
// Or with async injection
|
|
409
|
-
@Injectable()
|
|
410
|
-
class AsyncDatabaseService {
|
|
411
|
-
private dbConfig = asyncInject(DatabaseConfig, {
|
|
412
|
-
connectionString: 'postgres://localhost:5432/myapp',
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
async connect() {
|
|
416
|
-
const config = await this.dbConfig
|
|
417
|
-
return `Connecting to ${config.config.connectionString}`
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
392
|
```
|
|
421
393
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
```typescript
|
|
425
|
-
const appConfigSchema = z.object({
|
|
426
|
-
database: z.object({
|
|
427
|
-
host: z.string(),
|
|
428
|
-
port: z.number(),
|
|
429
|
-
credentials: z.object({
|
|
430
|
-
username: z.string(),
|
|
431
|
-
password: z.string(),
|
|
432
|
-
}),
|
|
433
|
-
}),
|
|
434
|
-
cache: z.object({
|
|
435
|
-
enabled: z.boolean(),
|
|
436
|
-
ttl: z.number(),
|
|
437
|
-
}),
|
|
438
|
-
api: z.object({
|
|
439
|
-
baseUrl: z.string(),
|
|
440
|
-
timeout: z.number(),
|
|
441
|
-
}),
|
|
442
|
-
})
|
|
394
|
+
## Advanced Usage
|
|
443
395
|
|
|
444
|
-
|
|
445
|
-
class AppConfig {
|
|
446
|
-
constructor(public readonly config: z.output<typeof appConfigSchema>) {}
|
|
396
|
+
### Circular Dependency Detection
|
|
447
397
|
|
|
448
|
-
|
|
449
|
-
return this.config.database
|
|
450
|
-
}
|
|
398
|
+
The library automatically detects circular dependencies and provides helpful error messages:
|
|
451
399
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
400
|
+
```typescript
|
|
401
|
+
@Injectable()
|
|
402
|
+
class ServiceA {
|
|
403
|
+
// Use asyncInject to break circular dependency
|
|
404
|
+
private serviceB = asyncInject(ServiceB)
|
|
455
405
|
|
|
456
|
-
|
|
457
|
-
|
|
406
|
+
async doSomething() {
|
|
407
|
+
const b = await this.serviceB
|
|
408
|
+
return b.getValue()
|
|
458
409
|
}
|
|
459
410
|
}
|
|
460
411
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
database: {
|
|
465
|
-
host: 'db.example.com',
|
|
466
|
-
port: 5432,
|
|
467
|
-
credentials: {
|
|
468
|
-
username: 'admin',
|
|
469
|
-
password: 'secret',
|
|
470
|
-
},
|
|
471
|
-
},
|
|
472
|
-
cache: {
|
|
473
|
-
enabled: true,
|
|
474
|
-
ttl: 300,
|
|
475
|
-
},
|
|
476
|
-
api: {
|
|
477
|
-
baseUrl: 'https://api.example.com',
|
|
478
|
-
timeout: 5000,
|
|
479
|
-
},
|
|
480
|
-
})
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
#### Schema vs Token with Schema
|
|
484
|
-
|
|
485
|
-
There are two ways to use schemas with `@Injectable`:
|
|
486
|
-
|
|
487
|
-
**Option 1: Direct Schema (Recommended for simplicity)**
|
|
488
|
-
|
|
489
|
-
```typescript
|
|
490
|
-
@Injectable({ schema: configSchema })
|
|
491
|
-
class ConfigService {
|
|
492
|
-
constructor(public readonly config: z.output<typeof configSchema>) {}
|
|
493
|
-
}
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
**Option 2: Token with Schema (Use when you need to share the token)**
|
|
497
|
-
|
|
498
|
-
```typescript
|
|
499
|
-
const CONFIG_TOKEN = InjectionToken.create('CONFIG', configSchema)
|
|
412
|
+
@Injectable()
|
|
413
|
+
class ServiceB {
|
|
414
|
+
private serviceA = inject(ServiceA)
|
|
500
415
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
416
|
+
getValue() {
|
|
417
|
+
return 'value from B'
|
|
418
|
+
}
|
|
504
419
|
}
|
|
505
420
|
```
|
|
506
421
|
|
|
507
|
-
The direct schema approach is simpler and creates the injection token automatically. Use the token approach when you need to:
|
|
508
|
-
|
|
509
|
-
- Share the same token across multiple classes
|
|
510
|
-
- Reference the token in multiple places
|
|
511
|
-
- Have more control over the token name
|
|
512
|
-
|
|
513
|
-
## Advanced Usage
|
|
514
|
-
|
|
515
422
|
### Custom Registry
|
|
516
423
|
|
|
517
424
|
```typescript
|
|
@@ -524,10 +431,21 @@ const container = new Container(customRegistry)
|
|
|
524
431
|
### Error Handling
|
|
525
432
|
|
|
526
433
|
```typescript
|
|
434
|
+
import { DIError, DIErrorCode } from '@navios/di'
|
|
435
|
+
|
|
527
436
|
try {
|
|
528
437
|
const service = await container.get(NonExistentService)
|
|
529
438
|
} catch (error) {
|
|
530
|
-
|
|
439
|
+
if (error instanceof DIError) {
|
|
440
|
+
switch (error.code) {
|
|
441
|
+
case DIErrorCode.FactoryNotFound:
|
|
442
|
+
console.error('Service not registered')
|
|
443
|
+
break
|
|
444
|
+
case DIErrorCode.InstanceDestroying:
|
|
445
|
+
console.error('Service is being destroyed')
|
|
446
|
+
break
|
|
447
|
+
}
|
|
448
|
+
}
|
|
531
449
|
}
|
|
532
450
|
```
|
|
533
451
|
|
|
@@ -545,14 +463,32 @@ const newService = await container.get(MyService)
|
|
|
545
463
|
|
|
546
464
|
### Container
|
|
547
465
|
|
|
548
|
-
- `get<T>(token: T): Promise<
|
|
466
|
+
- `get<T>(token: T, args?): Promise<T>` - Get an instance
|
|
549
467
|
- `invalidate(service: unknown): Promise<void>` - Invalidate a service
|
|
550
468
|
- `ready(): Promise<void>` - Wait for pending operations
|
|
469
|
+
- `dispose(): Promise<void>` - Clean up all resources
|
|
470
|
+
- `clear(): Promise<void>` - Clear all instances and bindings
|
|
471
|
+
- `isRegistered(token: any): boolean` - Check if service is registered
|
|
551
472
|
- `getServiceLocator(): ServiceLocator` - Get underlying service locator
|
|
552
|
-
- `
|
|
553
|
-
- `
|
|
554
|
-
- `
|
|
555
|
-
- `
|
|
473
|
+
- `getRegistry(): Registry` - Get the registry
|
|
474
|
+
- `beginRequest(requestId: string, metadata?, priority?): ScopedContainer` - Begin request context
|
|
475
|
+
- `getActiveRequestIds(): ReadonlySet<string>` - Get active request IDs
|
|
476
|
+
- `hasActiveRequest(requestId: string): boolean` - Check if request is active
|
|
477
|
+
|
|
478
|
+
### ScopedContainer
|
|
479
|
+
|
|
480
|
+
- `get<T>(token: T, args?): Promise<T>` - Get an instance (request-scoped or delegated)
|
|
481
|
+
- `invalidate(service: unknown): Promise<void>` - Invalidate a service
|
|
482
|
+
- `endRequest(): Promise<void>` - End request and cleanup
|
|
483
|
+
- `dispose(): Promise<void>` - Alias for endRequest()
|
|
484
|
+
- `ready(): Promise<void>` - Wait for pending operations
|
|
485
|
+
- `isRegistered(token: any): boolean` - Check if service is registered
|
|
486
|
+
- `getMetadata(key: string): any` - Get request metadata
|
|
487
|
+
- `setMetadata(key: string, value: any): void` - Set request metadata
|
|
488
|
+
- `addInstance(token: InjectionToken, instance: any): void` - Add pre-prepared instance
|
|
489
|
+
- `getRequestId(): string` - Get the request ID
|
|
490
|
+
- `getParent(): Container` - Get the parent container
|
|
491
|
+
- `getRequestContextHolder(): RequestContext` - Get the underlying request context
|
|
556
492
|
|
|
557
493
|
### Injectable Decorator
|
|
558
494
|
|
|
@@ -560,7 +496,7 @@ const newService = await container.get(MyService)
|
|
|
560
496
|
- Options:
|
|
561
497
|
- `scope?: InjectableScope` - Service scope (Singleton | Transient | Request)
|
|
562
498
|
- `token?: InjectionToken` - Custom injection token
|
|
563
|
-
- `schema?: ZodSchema` - Zod schema for constructor arguments
|
|
499
|
+
- `schema?: ZodSchema` - Zod schema for constructor arguments
|
|
564
500
|
- `registry?: Registry` - Custom registry
|
|
565
501
|
- Note: Cannot use both `token` and `schema` options together
|
|
566
502
|
|
|
@@ -574,8 +510,11 @@ const newService = await container.get(MyService)
|
|
|
574
510
|
|
|
575
511
|
### Injection Methods
|
|
576
512
|
|
|
577
|
-
- `inject<T>(token: T): T` - Synchronous injection
|
|
578
|
-
- `asyncInject<T>(token: T): Promise<T>` - Asynchronous injection
|
|
513
|
+
- `inject<T>(token: T, args?): T` - Synchronous injection
|
|
514
|
+
- `asyncInject<T>(token: T, args?): Promise<T>` - Asynchronous injection
|
|
515
|
+
- `optional<T>(token: T, args?): T | null` - Optional injection
|
|
516
|
+
- `wrapSyncInit<T>(fn: () => T): T` - Wrap synchronous initialization
|
|
517
|
+
- `provideFactoryContext<T>(ctx: FactoryContext, fn: () => T): T` - Provide factory context
|
|
579
518
|
|
|
580
519
|
### Injection Tokens
|
|
581
520
|
|
|
@@ -589,24 +528,62 @@ const newService = await container.get(MyService)
|
|
|
589
528
|
- `OnServiceInit` - Implement `onServiceInit(): Promise<void> | void`
|
|
590
529
|
- `OnServiceDestroy` - Implement `onServiceDestroy(): Promise<void> | void`
|
|
591
530
|
|
|
592
|
-
###
|
|
531
|
+
### RequestContext
|
|
532
|
+
|
|
533
|
+
- `requestId: string` - Unique request identifier
|
|
534
|
+
- `priority: number` - Priority for resolution
|
|
535
|
+
- `metadata: Map<string, any>` - Request metadata
|
|
536
|
+
- `createdAt: number` - Creation timestamp
|
|
537
|
+
- `addInstance(token, instance): void` - Add pre-prepared instance
|
|
538
|
+
- `get(instanceName): InstanceHolder | undefined` - Get instance holder
|
|
539
|
+
- `set(instanceName, holder): void` - Set instance holder
|
|
540
|
+
- `has(instanceName): boolean` - Check if instance exists
|
|
541
|
+
- `clear(): void` - Clear all instances
|
|
542
|
+
- `getMetadata(key): any` - Get metadata value
|
|
543
|
+
- `setMetadata(key, value): void` - Set metadata value
|
|
544
|
+
|
|
545
|
+
## Testing
|
|
546
|
+
|
|
547
|
+
### TestContainer
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
import { TestContainer } from '@navios/di/testing'
|
|
551
|
+
|
|
552
|
+
describe('UserService', () => {
|
|
553
|
+
let container: TestContainer
|
|
554
|
+
|
|
555
|
+
beforeEach(() => {
|
|
556
|
+
container = new TestContainer()
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
afterEach(async () => {
|
|
560
|
+
await container.dispose()
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
it('should create user', async () => {
|
|
564
|
+
// Bind mock
|
|
565
|
+
container.bind(DatabaseService).toValue({
|
|
566
|
+
save: vi.fn().mockResolvedValue({ id: '1' }),
|
|
567
|
+
})
|
|
593
568
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
569
|
+
const userService = await container.get(UserService)
|
|
570
|
+
const user = await userService.create({ name: 'John' })
|
|
571
|
+
|
|
572
|
+
expect(user.id).toBe('1')
|
|
573
|
+
})
|
|
574
|
+
})
|
|
575
|
+
```
|
|
599
576
|
|
|
600
577
|
## Best Practices
|
|
601
578
|
|
|
602
|
-
1. **Use `asyncInject` for
|
|
603
|
-
2. **Use `inject`
|
|
604
|
-
3. **
|
|
605
|
-
4. **
|
|
606
|
-
5. **
|
|
607
|
-
6. **
|
|
608
|
-
7. **
|
|
609
|
-
8. **
|
|
579
|
+
1. **Use `asyncInject` for circular dependencies** - Breaks circular dependency cycles safely
|
|
580
|
+
2. **Use `inject` for simple dependencies** - When you're certain the dependency is ready
|
|
581
|
+
3. **Use `optional` for feature flags** - Dependencies that may not be available
|
|
582
|
+
4. **Implement lifecycle hooks** - For proper resource management
|
|
583
|
+
5. **Use injection tokens** - For configuration and interface-based dependencies
|
|
584
|
+
6. **Prefer singletons** - Unless you specifically need new instances each time
|
|
585
|
+
7. **Use factories** - For complex object creation logic
|
|
586
|
+
8. **Leverage ScopedContainer** - For request-scoped data and cleanup
|
|
610
587
|
|
|
611
588
|
## License
|
|
612
589
|
|