@navios/di 0.2.1 → 0.3.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.
- package/README.md +299 -38
- package/docs/README.md +121 -48
- package/docs/api-reference.md +763 -0
- package/docs/container.md +274 -0
- package/docs/examples/basic-usage.mts +97 -0
- package/docs/examples/factory-pattern.mts +318 -0
- package/docs/examples/injection-tokens.mts +225 -0
- package/docs/examples/request-scope-example.mts +254 -0
- package/docs/examples/service-lifecycle.mts +359 -0
- package/docs/factory.md +584 -0
- package/docs/getting-started.md +308 -0
- package/docs/injectable.md +496 -0
- package/docs/injection-tokens.md +400 -0
- package/docs/lifecycle.md +539 -0
- package/docs/scopes.md +749 -0
- package/lib/_tsup-dts-rollup.d.mts +494 -145
- package/lib/_tsup-dts-rollup.d.ts +494 -145
- package/lib/index.d.mts +26 -12
- package/lib/index.d.ts +26 -12
- package/lib/index.js +1021 -470
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1011 -461
- package/lib/index.mjs.map +1 -1
- package/package.json +2 -2
- package/project.json +10 -2
- package/src/__tests__/container.spec.mts +1301 -0
- package/src/__tests__/factory.spec.mts +137 -0
- package/src/__tests__/injectable.spec.mts +32 -88
- package/src/__tests__/injection-token.spec.mts +333 -17
- package/src/__tests__/request-scope.spec.mts +427 -0
- package/src/__type-tests__/factory.spec-d.mts +65 -0
- package/src/__type-tests__/inject.spec-d.mts +27 -28
- package/src/__type-tests__/injectable.spec-d.mts +42 -206
- package/src/container.mts +167 -0
- package/src/decorators/factory.decorator.mts +79 -0
- package/src/decorators/index.mts +1 -0
- package/src/decorators/injectable.decorator.mts +6 -56
- package/src/enums/injectable-scope.enum.mts +5 -1
- package/src/event-emitter.mts +18 -20
- package/src/factory-context.mts +2 -10
- package/src/index.mts +3 -2
- package/src/injection-token.mts +19 -4
- package/src/injector.mts +8 -20
- package/src/interfaces/factory.interface.mts +3 -3
- package/src/interfaces/index.mts +2 -0
- package/src/interfaces/on-service-destroy.interface.mts +3 -0
- package/src/interfaces/on-service-init.interface.mts +3 -0
- package/src/registry.mts +7 -16
- package/src/request-context-holder.mts +174 -0
- package/src/service-instantiator.mts +158 -0
- package/src/service-locator-event-bus.mts +0 -28
- package/src/service-locator-instance-holder.mts +27 -16
- package/src/service-locator-manager.mts +84 -0
- package/src/service-locator.mts +548 -393
- package/src/utils/defer.mts +73 -0
- package/src/utils/get-injectors.mts +91 -78
- package/src/utils/index.mts +2 -0
- package/src/utils/types.mts +52 -0
- package/docs/concepts/injectable.md +0 -182
- package/docs/concepts/injection-token.md +0 -145
- package/src/proxy-service-locator.mts +0 -83
- package/src/resolve-service.mts +0 -41
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# Injection Tokens
|
|
2
|
+
|
|
3
|
+
Injection tokens provide a flexible way to identify and resolve dependencies in Navios DI. They allow you to decouple service implementations from their consumers, making your code more modular and testable.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Injection tokens serve as unique identifiers for services and can be used with:
|
|
8
|
+
|
|
9
|
+
- **Schemas**: Define the shape of configuration data
|
|
10
|
+
- **Bound Values**: Pre-configure tokens with specific values
|
|
11
|
+
- **Factories**: Dynamically resolve token values
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
### Creating Injection Tokens
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { InjectionToken } from '@navios/di'
|
|
19
|
+
|
|
20
|
+
// Token with schema
|
|
21
|
+
import { z } from 'zod'
|
|
22
|
+
|
|
23
|
+
// Simple token without schema
|
|
24
|
+
const USER_SERVICE_TOKEN = InjectionToken.create<UserService>('UserService')
|
|
25
|
+
|
|
26
|
+
const configSchema = z.object({
|
|
27
|
+
apiUrl: z.string(),
|
|
28
|
+
timeout: z.number(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const CONFIG_TOKEN = InjectionToken.create<Config, typeof configSchema>(
|
|
32
|
+
'APP_CONFIG',
|
|
33
|
+
configSchema,
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Using Injection Tokens
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Injectable } from '@navios/di'
|
|
41
|
+
|
|
42
|
+
@Injectable({ token: USER_SERVICE_TOKEN })
|
|
43
|
+
class UserService {
|
|
44
|
+
getUsers() {
|
|
45
|
+
return ['Alice', 'Bob', 'Charlie']
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Inject using the token
|
|
50
|
+
const userService = await container.get(USER_SERVICE_TOKEN)
|
|
51
|
+
console.log(userService.getUsers())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Token Types
|
|
55
|
+
|
|
56
|
+
### Basic Injection Token
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { Injectable, InjectionToken } from '@navios/di'
|
|
60
|
+
|
|
61
|
+
interface EmailService {
|
|
62
|
+
sendEmail(to: string, subject: string): Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const EMAIL_SERVICE_TOKEN = InjectionToken.create<EmailService>('EmailService')
|
|
66
|
+
|
|
67
|
+
@Injectable({ token: EMAIL_SERVICE_TOKEN })
|
|
68
|
+
class SmtpEmailService implements EmailService {
|
|
69
|
+
async sendEmail(to: string, subject: string) {
|
|
70
|
+
console.log(`SMTP email sent to ${to}: ${subject}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Usage
|
|
75
|
+
const emailService = await container.get(EMAIL_SERVICE_TOKEN)
|
|
76
|
+
await emailService.sendEmail('user@example.com', 'Hello')
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Token with Schema
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Injectable, InjectionToken } from '@navios/di'
|
|
83
|
+
|
|
84
|
+
import { z } from 'zod'
|
|
85
|
+
|
|
86
|
+
const databaseConfigSchema = z.object({
|
|
87
|
+
host: z.string(),
|
|
88
|
+
port: z.number(),
|
|
89
|
+
database: z.string(),
|
|
90
|
+
username: z.string(),
|
|
91
|
+
password: z.string(),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const DB_CONFIG_TOKEN = InjectionToken.create<
|
|
95
|
+
DatabaseConfig,
|
|
96
|
+
typeof databaseConfigSchema
|
|
97
|
+
>('DB_CONFIG', databaseConfigSchema)
|
|
98
|
+
|
|
99
|
+
@Injectable({ token: DB_CONFIG_TOKEN })
|
|
100
|
+
class DatabaseConfigService {
|
|
101
|
+
constructor(private config: z.infer<typeof databaseConfigSchema>) {}
|
|
102
|
+
|
|
103
|
+
getConnectionString() {
|
|
104
|
+
return `postgresql://${this.config.username}:${this.config.password}@${this.config.host}:${this.config.port}/${this.config.database}`
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Usage with configuration
|
|
109
|
+
const dbConfig = await container.get(DB_CONFIG_TOKEN, {
|
|
110
|
+
host: 'localhost',
|
|
111
|
+
port: 5432,
|
|
112
|
+
database: 'myapp',
|
|
113
|
+
username: 'postgres',
|
|
114
|
+
password: 'password',
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
console.log(dbConfig.getConnectionString())
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Bound Injection Token
|
|
121
|
+
|
|
122
|
+
Bound tokens allow you to pre-configure a token with specific values:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { InjectionToken } from '@navios/di'
|
|
126
|
+
|
|
127
|
+
const configSchema = z.object({
|
|
128
|
+
apiUrl: z.string(),
|
|
129
|
+
timeout: z.number(),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const CONFIG_TOKEN = InjectionToken.create<Config, typeof configSchema>(
|
|
133
|
+
'APP_CONFIG',
|
|
134
|
+
configSchema,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
// Create bound token with specific values
|
|
138
|
+
const PRODUCTION_CONFIG = InjectionToken.bound(CONFIG_TOKEN, {
|
|
139
|
+
apiUrl: 'https://api.production.com',
|
|
140
|
+
timeout: 10000,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const DEVELOPMENT_CONFIG = InjectionToken.bound(CONFIG_TOKEN, {
|
|
144
|
+
apiUrl: 'https://api.dev.com',
|
|
145
|
+
timeout: 5000,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// Usage - no need to provide arguments
|
|
149
|
+
const prodConfig = await container.get(PRODUCTION_CONFIG)
|
|
150
|
+
const devConfig = await container.get(DEVELOPMENT_CONFIG)
|
|
151
|
+
|
|
152
|
+
console.log(prodConfig.apiUrl) // 'https://api.production.com'
|
|
153
|
+
console.log(devConfig.apiUrl) // 'https://api.dev.com'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Factory Injection Token
|
|
157
|
+
|
|
158
|
+
Factory tokens dynamically resolve values using a factory function:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { Injectable, InjectionToken } from '@navios/di'
|
|
162
|
+
|
|
163
|
+
@Injectable()
|
|
164
|
+
class EnvironmentService {
|
|
165
|
+
getEnvironment() {
|
|
166
|
+
return process.env.NODE_ENV || 'development'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getApiUrl() {
|
|
170
|
+
const env = this.getEnvironment()
|
|
171
|
+
switch (env) {
|
|
172
|
+
case 'production':
|
|
173
|
+
return 'https://api.production.com'
|
|
174
|
+
case 'staging':
|
|
175
|
+
return 'https://api.staging.com'
|
|
176
|
+
default:
|
|
177
|
+
return 'https://api.dev.com'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const configSchema = z.object({
|
|
183
|
+
apiUrl: z.string(),
|
|
184
|
+
timeout: z.number(),
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const CONFIG_TOKEN = InjectionToken.create<Config, typeof configSchema>(
|
|
188
|
+
'APP_CONFIG',
|
|
189
|
+
configSchema,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
// Create factory token
|
|
193
|
+
const DYNAMIC_CONFIG = InjectionToken.factory(CONFIG_TOKEN, async (ctx) => {
|
|
194
|
+
const envService = await ctx.inject(EnvironmentService)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
apiUrl: envService.getApiUrl(),
|
|
198
|
+
timeout: envService.getEnvironment() === 'production' ? 10000 : 5000,
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// Usage
|
|
203
|
+
const config = await container.get(DYNAMIC_CONFIG)
|
|
204
|
+
console.log(config.apiUrl) // Dynamically resolved based on environment
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Advanced Patterns
|
|
208
|
+
|
|
209
|
+
### Multiple Implementations
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { Injectable, InjectionToken } from '@navios/di'
|
|
213
|
+
|
|
214
|
+
interface PaymentProcessor {
|
|
215
|
+
processPayment(amount: number): Promise<string>
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const PAYMENT_PROCESSOR_TOKEN =
|
|
219
|
+
InjectionToken.create<PaymentProcessor>('PaymentProcessor')
|
|
220
|
+
|
|
221
|
+
// Stripe implementation
|
|
222
|
+
@Injectable({ token: PAYMENT_PROCESSOR_TOKEN })
|
|
223
|
+
class StripePaymentProcessor implements PaymentProcessor {
|
|
224
|
+
async processPayment(amount: number) {
|
|
225
|
+
return `Processed $${amount} via Stripe`
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// PayPal implementation
|
|
230
|
+
@Injectable({ token: PAYMENT_PROCESSOR_TOKEN })
|
|
231
|
+
class PayPalPaymentProcessor implements PaymentProcessor {
|
|
232
|
+
async processPayment(amount: number) {
|
|
233
|
+
return `Processed $${amount} via PayPal`
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Usage - the last registered implementation will be used
|
|
238
|
+
const paymentProcessor = await container.get(PAYMENT_PROCESSOR_TOKEN)
|
|
239
|
+
await paymentProcessor.processPayment(100)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Token with Optional Schema
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { Injectable, InjectionToken } from '@navios/di'
|
|
246
|
+
|
|
247
|
+
const optionalConfigSchema = z.object({
|
|
248
|
+
apiUrl: z.string(),
|
|
249
|
+
timeout: z.number().optional(),
|
|
250
|
+
retries: z.number().optional(),
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const OPTIONAL_CONFIG_TOKEN = InjectionToken.create<
|
|
254
|
+
OptionalConfigService,
|
|
255
|
+
typeof optionalConfigSchema
|
|
256
|
+
>('OPTIONAL_CONFIG', optionalConfigSchema)
|
|
257
|
+
|
|
258
|
+
@Injectable({ token: OPTIONAL_CONFIG_TOKEN })
|
|
259
|
+
class OptionalConfigService {
|
|
260
|
+
constructor(private config: z.infer<typeof optionalConfigSchema>) {}
|
|
261
|
+
|
|
262
|
+
getApiUrl() {
|
|
263
|
+
return this.config.apiUrl
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getTimeout() {
|
|
267
|
+
return this.config.timeout ?? 5000
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
getRetries() {
|
|
271
|
+
return this.config.retries ?? 3
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Usage with partial configuration
|
|
276
|
+
const config = await container.get(OPTIONAL_CONFIG_TOKEN, {
|
|
277
|
+
apiUrl: 'https://api.example.com',
|
|
278
|
+
// timeout and retries are optional
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
console.log(config.getTimeout()) // 5000 (default)
|
|
282
|
+
console.log(config.getRetries()) // 3 (default)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Best Practices
|
|
286
|
+
|
|
287
|
+
### 1. Use Descriptive Token Names
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// ✅ Good: Descriptive names
|
|
291
|
+
const USER_REPOSITORY_TOKEN =
|
|
292
|
+
InjectionToken.create<UserRepository>('UserRepository')
|
|
293
|
+
const EMAIL_SERVICE_TOKEN = InjectionToken.create<EmailService>('EmailService')
|
|
294
|
+
|
|
295
|
+
// ❌ Avoid: Generic names
|
|
296
|
+
const SERVICE_TOKEN = InjectionToken.create<Service>('Service')
|
|
297
|
+
const TOKEN_1 = InjectionToken.create<Service>('Token1')
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 2. Define Schemas for Configuration Tokens
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// ✅ Good: Define schema for configuration
|
|
304
|
+
const configSchema = z.object({
|
|
305
|
+
apiUrl: z.string().url(),
|
|
306
|
+
timeout: z.number().min(1000),
|
|
307
|
+
retries: z.number().min(0).max(10),
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const CONFIG_TOKEN = InjectionToken.create<Config, typeof configSchema>(
|
|
311
|
+
'APP_CONFIG',
|
|
312
|
+
configSchema,
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 3. Use Bound Tokens for Environment-Specific Configuration
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// ✅ Good: Environment-specific bound tokens
|
|
320
|
+
const PRODUCTION_CONFIG = InjectionToken.bound(CONFIG_TOKEN, {
|
|
321
|
+
apiUrl: 'https://api.production.com',
|
|
322
|
+
timeout: 10000,
|
|
323
|
+
retries: 5,
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
const DEVELOPMENT_CONFIG = InjectionToken.bound(CONFIG_TOKEN, {
|
|
327
|
+
apiUrl: 'https://api.dev.com',
|
|
328
|
+
timeout: 5000,
|
|
329
|
+
retries: 3,
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### 4. Use Factory Tokens for Dynamic Resolution
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// ✅ Good: Factory for dynamic configuration
|
|
337
|
+
const DYNAMIC_CONFIG = InjectionToken.factory(CONFIG_TOKEN, async () => {
|
|
338
|
+
const env = process.env.NODE_ENV || 'development'
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
apiUrl:
|
|
342
|
+
env === 'production' ? 'https://api.prod.com' : 'https://api.dev.com',
|
|
343
|
+
timeout: env === 'production' ? 10000 : 5000,
|
|
344
|
+
retries: env === 'production' ? 5 : 3,
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 5. Group Related Tokens
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// ✅ Good: Group related tokens
|
|
353
|
+
export const DATABASE_TOKENS = {
|
|
354
|
+
CONFIG: InjectionToken.create<DatabaseConfig>('DatabaseConfig'),
|
|
355
|
+
CONNECTION: InjectionToken.create<DatabaseConnection>('DatabaseConnection'),
|
|
356
|
+
REPOSITORY: InjectionToken.create<UserRepository>('UserRepository'),
|
|
357
|
+
} as const
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## API Reference
|
|
361
|
+
|
|
362
|
+
### InjectionToken.create
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Simple token
|
|
366
|
+
static create<T>(name: string | symbol): InjectionToken<T, undefined>
|
|
367
|
+
|
|
368
|
+
// Token with schema
|
|
369
|
+
static create<T, S extends InjectionTokenSchemaType>(
|
|
370
|
+
name: string | symbol,
|
|
371
|
+
schema: S
|
|
372
|
+
): InjectionToken<T, S>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### InjectionToken.bound
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
static bound<T, S extends InjectionTokenSchemaType>(
|
|
379
|
+
token: InjectionToken<T, S>,
|
|
380
|
+
value: z.input<S>
|
|
381
|
+
): BoundInjectionToken<T, S>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### InjectionToken.factory
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
static factory<T, S extends InjectionTokenSchemaType>(
|
|
388
|
+
token: InjectionToken<T, S>,
|
|
389
|
+
factory: (ctx: FactoryContext) => Promise<z.input<S>>
|
|
390
|
+
): FactoryInjectionToken<T, S>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Token Types
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
type InjectionTokenType =
|
|
397
|
+
| InjectionToken<any, any>
|
|
398
|
+
| BoundInjectionToken<any, any>
|
|
399
|
+
| FactoryInjectionToken<any, any>
|
|
400
|
+
```
|