@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.
Files changed (62) hide show
  1. package/README.md +299 -38
  2. package/docs/README.md +121 -48
  3. package/docs/api-reference.md +763 -0
  4. package/docs/container.md +274 -0
  5. package/docs/examples/basic-usage.mts +97 -0
  6. package/docs/examples/factory-pattern.mts +318 -0
  7. package/docs/examples/injection-tokens.mts +225 -0
  8. package/docs/examples/request-scope-example.mts +254 -0
  9. package/docs/examples/service-lifecycle.mts +359 -0
  10. package/docs/factory.md +584 -0
  11. package/docs/getting-started.md +308 -0
  12. package/docs/injectable.md +496 -0
  13. package/docs/injection-tokens.md +400 -0
  14. package/docs/lifecycle.md +539 -0
  15. package/docs/scopes.md +749 -0
  16. package/lib/_tsup-dts-rollup.d.mts +494 -145
  17. package/lib/_tsup-dts-rollup.d.ts +494 -145
  18. package/lib/index.d.mts +26 -12
  19. package/lib/index.d.ts +26 -12
  20. package/lib/index.js +1021 -470
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +1011 -461
  23. package/lib/index.mjs.map +1 -1
  24. package/package.json +2 -2
  25. package/project.json +10 -2
  26. package/src/__tests__/container.spec.mts +1301 -0
  27. package/src/__tests__/factory.spec.mts +137 -0
  28. package/src/__tests__/injectable.spec.mts +32 -88
  29. package/src/__tests__/injection-token.spec.mts +333 -17
  30. package/src/__tests__/request-scope.spec.mts +427 -0
  31. package/src/__type-tests__/factory.spec-d.mts +65 -0
  32. package/src/__type-tests__/inject.spec-d.mts +27 -28
  33. package/src/__type-tests__/injectable.spec-d.mts +42 -206
  34. package/src/container.mts +167 -0
  35. package/src/decorators/factory.decorator.mts +79 -0
  36. package/src/decorators/index.mts +1 -0
  37. package/src/decorators/injectable.decorator.mts +6 -56
  38. package/src/enums/injectable-scope.enum.mts +5 -1
  39. package/src/event-emitter.mts +18 -20
  40. package/src/factory-context.mts +2 -10
  41. package/src/index.mts +3 -2
  42. package/src/injection-token.mts +19 -4
  43. package/src/injector.mts +8 -20
  44. package/src/interfaces/factory.interface.mts +3 -3
  45. package/src/interfaces/index.mts +2 -0
  46. package/src/interfaces/on-service-destroy.interface.mts +3 -0
  47. package/src/interfaces/on-service-init.interface.mts +3 -0
  48. package/src/registry.mts +7 -16
  49. package/src/request-context-holder.mts +174 -0
  50. package/src/service-instantiator.mts +158 -0
  51. package/src/service-locator-event-bus.mts +0 -28
  52. package/src/service-locator-instance-holder.mts +27 -16
  53. package/src/service-locator-manager.mts +84 -0
  54. package/src/service-locator.mts +548 -393
  55. package/src/utils/defer.mts +73 -0
  56. package/src/utils/get-injectors.mts +91 -78
  57. package/src/utils/index.mts +2 -0
  58. package/src/utils/types.mts +52 -0
  59. package/docs/concepts/injectable.md +0 -182
  60. package/docs/concepts/injection-token.md +0 -145
  61. package/src/proxy-service-locator.mts +0 -83
  62. package/src/resolve-service.mts +0 -41
@@ -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
+ ```