@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,584 @@
1
+ # Factory Decorator
2
+
3
+ The `@Factory` decorator is used to create factory classes that produce instances rather than being instances themselves. Factories are useful for complex object creation, configuration-based instantiation, and scenarios where you need to create multiple instances with different parameters.
4
+
5
+ ## Basic Usage
6
+
7
+ ### Simple Factory
8
+
9
+ ```typescript
10
+ import { Factory } from '@navios/di'
11
+
12
+ @Factory()
13
+ class DatabaseConnectionFactory {
14
+ create() {
15
+ return {
16
+ host: 'localhost',
17
+ port: 5432,
18
+ connected: true,
19
+ connect: () => console.log('Connected to database'),
20
+ }
21
+ }
22
+ }
23
+
24
+ // Usage
25
+ const container = new Container()
26
+ const connection = await container.get(DatabaseConnectionFactory)
27
+ console.log(connection) // { host: 'localhost', port: 5432, connected: true, connect: [Function] }
28
+ ```
29
+
30
+ ### Factory with Configuration
31
+
32
+ ```typescript
33
+ import type { FactoryContext } from '@navios/di'
34
+
35
+ import { Factory, InjectionToken } from '@navios/di'
36
+
37
+ import { z } from 'zod'
38
+
39
+ const configSchema = z.object({
40
+ host: z.string(),
41
+ port: z.number(),
42
+ database: z.string(),
43
+ })
44
+
45
+ const DB_CONFIG_TOKEN = InjectionToken.create<
46
+ DatabaseConfig,
47
+ typeof configSchema
48
+ >('DB_CONFIG', configSchema)
49
+
50
+ @Factory({ token: DB_CONFIG_TOKEN })
51
+ class DatabaseConnectionFactory {
52
+ create(ctx: FactoryContext, config: z.infer<typeof configSchema>) {
53
+ return {
54
+ host: config.host,
55
+ port: config.port,
56
+ database: config.database,
57
+ connected: false,
58
+ connect: () => {
59
+ console.log(
60
+ `Connecting to ${config.host}:${config.port}/${config.database}`,
61
+ )
62
+ return Promise.resolve()
63
+ },
64
+ }
65
+ }
66
+ }
67
+
68
+ // Usage
69
+ const connection = await container.get(DB_CONFIG_TOKEN, {
70
+ host: 'localhost',
71
+ port: 5432,
72
+ database: 'myapp',
73
+ })
74
+ ```
75
+
76
+ ## Factory vs Injectable
77
+
78
+ ### Injectable Service
79
+
80
+ ```typescript
81
+ import { Injectable } from '@navios/di'
82
+
83
+ @Injectable()
84
+ class EmailService {
85
+ sendEmail(to: string, subject: string) {
86
+ return `Email sent to ${to}: ${subject}`
87
+ }
88
+ }
89
+
90
+ // Usage - returns the service instance
91
+ const emailService = await container.get(EmailService)
92
+ await emailService.sendEmail('user@example.com', 'Hello')
93
+ ```
94
+
95
+ ### Factory Service
96
+
97
+ ```typescript
98
+ import { Factory } from '@navios/di'
99
+
100
+ @Factory()
101
+ class EmailServiceFactory {
102
+ create() {
103
+ return {
104
+ sendEmail: (to: string, subject: string) => {
105
+ return `Email sent to ${to}: ${subject}`
106
+ },
107
+ }
108
+ }
109
+ }
110
+
111
+ // Usage - returns the result of create() method
112
+ const emailService = await container.get(EmailServiceFactory)
113
+ await emailService.sendEmail('user@example.com', 'Hello')
114
+ ```
115
+
116
+ ## Advanced Patterns
117
+
118
+ ### Factory with Dependencies
119
+
120
+ ```typescript
121
+ import { Factory, inject, Injectable } from '@navios/di'
122
+
123
+ @Injectable()
124
+ class LoggerService {
125
+ log(message: string) {
126
+ console.log(`[LOG] ${message}`)
127
+ }
128
+ }
129
+
130
+ @Injectable()
131
+ class ConfigService {
132
+ getDatabaseUrl() {
133
+ return 'postgresql://localhost:5432/myapp'
134
+ }
135
+ }
136
+
137
+ @Factory()
138
+ class DatabaseConnectionFactory {
139
+ private readonly logger = inject(LoggerService)
140
+ private readonly config = inject(ConfigService)
141
+
142
+ create() {
143
+ const url = this.config.getDatabaseUrl()
144
+ this.logger.log(`Creating database connection to ${url}`)
145
+
146
+ return {
147
+ url,
148
+ connected: false,
149
+ connect: async () => {
150
+ this.logger.log('Connecting to database...')
151
+ // Simulate connection
152
+ await new Promise((resolve) => setTimeout(resolve, 100))
153
+ this.logger.log('Database connected successfully')
154
+ return { connected: true }
155
+ },
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Factory with Multiple Configurations
162
+
163
+ ```typescript
164
+ import { Factory, InjectionToken } from '@navios/di'
165
+
166
+ import { z } from 'zod'
167
+
168
+ const emailConfigSchema = z.object({
169
+ provider: z.enum(['smtp', 'sendgrid', 'ses']),
170
+ apiKey: z.string(),
171
+ fromEmail: z.string().email(),
172
+ })
173
+
174
+ const EMAIL_CONFIG_TOKEN = InjectionToken.create<
175
+ EmailConfig,
176
+ typeof emailConfigSchema
177
+ >('EMAIL_CONFIG', emailConfigSchema)
178
+
179
+ @Factory({ token: EMAIL_CONFIG_TOKEN })
180
+ class EmailServiceFactory {
181
+ create(config: z.infer<typeof emailConfigSchema>) {
182
+ switch (config.provider) {
183
+ case 'smtp':
184
+ return new SmtpEmailService(config)
185
+ case 'sendgrid':
186
+ return new SendGridEmailService(config)
187
+ case 'ses':
188
+ return new SesEmailService(config)
189
+ default:
190
+ throw new Error(`Unsupported email provider: ${config.provider}`)
191
+ }
192
+ }
193
+ }
194
+
195
+ class SmtpEmailService {
196
+ constructor(private config: EmailConfig) {}
197
+
198
+ async sendEmail(to: string, subject: string) {
199
+ return `SMTP email sent to ${to}: ${subject}`
200
+ }
201
+ }
202
+
203
+ class SendGridEmailService {
204
+ constructor(private config: EmailConfig) {}
205
+
206
+ async sendEmail(to: string, subject: string) {
207
+ return `SendGrid email sent to ${to}: ${subject}`
208
+ }
209
+ }
210
+
211
+ class SesEmailService {
212
+ constructor(private config: EmailConfig) {}
213
+
214
+ async sendEmail(to: string, subject: string) {
215
+ return `SES email sent to ${to}: ${subject}`
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### Factory with Transient Scope
221
+
222
+ ```typescript
223
+ import { Factory, InjectableScope } from '@navios/di'
224
+
225
+ @Factory({ scope: InjectableScope.Transient })
226
+ class RandomIdFactory {
227
+ create() {
228
+ return {
229
+ id: Math.random().toString(36).substr(2, 9),
230
+ createdAt: new Date(),
231
+ }
232
+ }
233
+ }
234
+
235
+ // Each call creates a new instance
236
+ const id1 = await container.get(RandomIdFactory)
237
+ const id2 = await container.get(RandomIdFactory)
238
+
239
+ console.log(id1.id) // Different random ID
240
+ console.log(id2.id) // Different random ID
241
+ ```
242
+
243
+ ## Factory Context
244
+
245
+ Factories have access to a `FactoryContext` that provides additional functionality:
246
+
247
+ ```typescript
248
+ import { Factory, Injectable } from '@navios/di'
249
+
250
+ @Injectable()
251
+ class DatabaseService {
252
+ async query(sql: string) {
253
+ return `Query result: ${sql}`
254
+ }
255
+ }
256
+
257
+ @Factory()
258
+ class UserRepositoryFactory {
259
+ create(ctx: FactoryContext) {
260
+ // Access the service locator
261
+ const locator = ctx.locator
262
+
263
+ // Inject dependencies within the factory
264
+ const dbService = await locator.getInstance(DatabaseService)
265
+
266
+ return {
267
+ async getUser(id: string) {
268
+ const db = await dbService
269
+ return db.query(`SELECT * FROM users WHERE id = ${id}`)
270
+ },
271
+
272
+ async createUser(userData: any) {
273
+ const db = await dbService
274
+ return db.query(
275
+ `INSERT INTO users VALUES (${JSON.stringify(userData)})`,
276
+ )
277
+ },
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
283
+ ## Real-World Examples
284
+
285
+ ### HTTP Client Factory
286
+
287
+ ```typescript
288
+ import type { FactoryContext } from '@navios/di'
289
+
290
+ import { Factory, InjectionToken } from '@navios/di'
291
+
292
+ import { z } from 'zod'
293
+
294
+ const httpConfigSchema = z.object({
295
+ baseUrl: z.string().url(),
296
+ timeout: z.number().default(5000),
297
+ retries: z.number().default(3),
298
+ headers: z.record(z.string()).optional(),
299
+ })
300
+
301
+ const HTTP_CONFIG_TOKEN = InjectionToken.create<
302
+ HttpClientFactory,
303
+ typeof httpConfigSchema
304
+ >('HTTP_CONFIG', httpConfigSchema)
305
+
306
+ @Factory({ token: HTTP_CONFIG_TOKEN })
307
+ class HttpClientFactory {
308
+ create(ctx: FactoryContext, config: z.infer<typeof httpConfigSchema>) {
309
+ return {
310
+ baseUrl: config.baseUrl,
311
+ timeout: config.timeout,
312
+ retries: config.retries,
313
+ headers: config.headers || {},
314
+
315
+ async get(path: string) {
316
+ console.log(`GET ${config.baseUrl}${path}`)
317
+ return { data: `Response from ${path}` }
318
+ },
319
+
320
+ async post(path: string, data: any) {
321
+ console.log(`POST ${config.baseUrl}${path}`, data)
322
+ return { data: `Created at ${path}` }
323
+ },
324
+ }
325
+ }
326
+ }
327
+
328
+ // Usage
329
+ const apiClient = await container.get(HTTP_CONFIG_TOKEN, {
330
+ baseUrl: 'https://api.example.com',
331
+ timeout: 10000,
332
+ retries: 5,
333
+ headers: {
334
+ Authorization: 'Bearer token123',
335
+ 'Content-Type': 'application/json',
336
+ },
337
+ })
338
+ ```
339
+
340
+ ### Cache Factory
341
+
342
+ ```typescript
343
+ import { Factory, InjectableScope } from '@navios/di'
344
+
345
+ @Factory({ scope: InjectableScope.Transient })
346
+ class CacheFactory {
347
+ create() {
348
+ const cache = new Map()
349
+
350
+ return {
351
+ set(key: string, value: any, ttl?: number) {
352
+ cache.set(key, {
353
+ value,
354
+ expires: ttl ? Date.now() + ttl : null,
355
+ })
356
+ },
357
+
358
+ get(key: string) {
359
+ const item = cache.get(key)
360
+ if (!item) return null
361
+
362
+ if (item.expires && Date.now() > item.expires) {
363
+ cache.delete(key)
364
+ return null
365
+ }
366
+
367
+ return item.value
368
+ },
369
+
370
+ delete(key: string) {
371
+ return cache.delete(key)
372
+ },
373
+
374
+ clear() {
375
+ cache.clear()
376
+ },
377
+
378
+ size() {
379
+ return cache.size
380
+ },
381
+ }
382
+ }
383
+ }
384
+ ```
385
+
386
+ ### Database Connection Pool Factory
387
+
388
+ ```typescript
389
+ import { Factory, inject, Injectable } from '@navios/di'
390
+
391
+ @Injectable()
392
+ class DatabaseConfigService {
393
+ getConfig() {
394
+ return {
395
+ host: process.env.DB_HOST || 'localhost',
396
+ port: parseInt(process.env.DB_PORT || '5432'),
397
+ database: process.env.DB_NAME || 'myapp',
398
+ username: process.env.DB_USER || 'postgres',
399
+ password: process.env.DB_PASSWORD || 'password',
400
+ maxConnections: parseInt(process.env.DB_MAX_CONNECTIONS || '10'),
401
+ }
402
+ }
403
+ }
404
+
405
+ @Factory()
406
+ class DatabasePoolFactory {
407
+ private readonly config = inject(DatabaseConfigService)
408
+
409
+ create() {
410
+ const config = this.config.getConfig()
411
+
412
+ return {
413
+ config,
414
+ connections: new Map(),
415
+ connectionCount: 0,
416
+
417
+ async getConnection() {
418
+ if (this.connectionCount >= config.maxConnections) {
419
+ throw new Error('Connection pool exhausted')
420
+ }
421
+
422
+ const connectionId = `conn_${Date.now()}_${Math.random()}`
423
+ const connection = {
424
+ id: connectionId,
425
+ host: config.host,
426
+ port: config.port,
427
+ database: config.database,
428
+ connected: true,
429
+ createdAt: new Date(),
430
+ }
431
+
432
+ this.connections.set(connectionId, connection)
433
+ this.connectionCount++
434
+
435
+ return connection
436
+ },
437
+
438
+ async releaseConnection(connectionId: string) {
439
+ if (this.connections.has(connectionId)) {
440
+ this.connections.delete(connectionId)
441
+ this.connectionCount--
442
+ }
443
+ },
444
+
445
+ getStats() {
446
+ return {
447
+ totalConnections: this.connectionCount,
448
+ maxConnections: config.maxConnections,
449
+ availableConnections: config.maxConnections - this.connectionCount,
450
+ }
451
+ },
452
+ }
453
+ }
454
+ }
455
+ ```
456
+
457
+ ## Best Practices
458
+
459
+ ### 1. Use Factories for Complex Object Creation
460
+
461
+ ```typescript
462
+ // ✅ Good: Use factory for complex configuration
463
+ @Factory()
464
+ class EmailServiceFactory {
465
+ create() {
466
+ return {
467
+ sendEmail: async (to: string, subject: string, body: string) => {
468
+ // Complex email sending logic
469
+ const template = await this.loadTemplate(subject)
470
+ const html = await this.renderTemplate(template, body)
471
+ return await this.sendViaProvider(to, html)
472
+ },
473
+ }
474
+ }
475
+ }
476
+ ```
477
+
478
+ ### 2. Use Transient Scope for Stateful Objects
479
+
480
+ ```typescript
481
+ // ✅ Good: Transient factory for stateful objects
482
+ @Factory({ scope: InjectableScope.Transient })
483
+ class UserSessionFactory {
484
+ create() {
485
+ return {
486
+ userId: null,
487
+ sessionId: Math.random().toString(36),
488
+ loginTime: new Date(),
489
+
490
+ login(userId: string) {
491
+ this.userId = userId
492
+ this.loginTime = new Date()
493
+ },
494
+
495
+ logout() {
496
+ this.userId = null
497
+ },
498
+ }
499
+ }
500
+ }
501
+ ```
502
+
503
+ ### 3. Use Injection Tokens for Interface-Based Factories
504
+
505
+ ```typescript
506
+ // ✅ Good: Interface-based factory
507
+ interface PaymentProcessor {
508
+ processPayment(amount: number): Promise<string>
509
+ }
510
+
511
+ const PAYMENT_PROCESSOR_TOKEN =
512
+ InjectionToken.create<PaymentProcessor>('PaymentProcessor')
513
+
514
+ @Factory({ token: PAYMENT_PROCESSOR_TOKEN })
515
+ class StripePaymentProcessorFactory {
516
+ create(): PaymentProcessor {
517
+ return {
518
+ async processPayment(amount: number) {
519
+ return `Processed $${amount} via Stripe`
520
+ },
521
+ }
522
+ }
523
+ }
524
+ ```
525
+
526
+ ### 4. Handle Errors in Factory Creation
527
+
528
+ ```typescript
529
+ // ✅ Good: Error handling in factory
530
+ @Factory()
531
+ class DatabaseConnectionFactory {
532
+ create() {
533
+ try {
534
+ return {
535
+ connect: async () => {
536
+ // Connection logic
537
+ return { connected: true }
538
+ },
539
+ }
540
+ } catch (error) {
541
+ throw new Error(`Failed to create database connection: ${error.message}`)
542
+ }
543
+ }
544
+ }
545
+ ```
546
+
547
+ ## API Reference
548
+
549
+ ### Factory Options
550
+
551
+ ```typescript
552
+ interface FactoryOptions {
553
+ scope?: InjectableScope
554
+ token?: InjectionToken<any, any>
555
+ registry?: Registry
556
+ }
557
+ ```
558
+
559
+ ### Factory Context
560
+
561
+ ```typescript
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
571
+ }
572
+ ```
573
+
574
+ ### Factory Method Signature
575
+
576
+ ```typescript
577
+ interface Factorable<T> {
578
+ create(): T
579
+ }
580
+
581
+ interface FactorableWithArgs<T, S> {
582
+ create(ctx: FactoryContext, args: z.input<S>): T
583
+ }
584
+ ```