@navios/di 0.2.0 → 0.3.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.
Files changed (62) hide show
  1. package/README.md +301 -39
  2. package/docs/README.md +122 -49
  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 +495 -150
  17. package/lib/_tsup-dts-rollup.d.ts +495 -150
  18. package/lib/index.d.mts +26 -12
  19. package/lib/index.d.ts +26 -12
  20. package/lib/index.js +993 -462
  21. package/lib/index.js.map +1 -1
  22. package/lib/index.mjs +983 -453
  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 +263 -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 +24 -9
  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 +145 -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 +550 -395
  55. package/src/utils/defer.mts +73 -0
  56. package/src/utils/get-injectors.mts +93 -80
  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,359 @@
1
+ import type { OnServiceDestroy, OnServiceInit } from '@navios/di'
2
+
3
+ import { asyncInject, Container, inject, Injectable } from '@navios/di'
4
+
5
+ const container = new Container()
6
+ /**
7
+ * Service Lifecycle Example
8
+ *
9
+ * This example demonstrates:
10
+ * - OnServiceInit interface
11
+ * - OnServiceDestroy interface
12
+ * - Service initialization and cleanup
13
+ * - Resource management
14
+ */
15
+
16
+ // 1. Service with initialization and cleanup
17
+ @Injectable()
18
+ class DatabaseService implements OnServiceInit, OnServiceDestroy {
19
+ private connection: any = null
20
+ private isConnected = false
21
+
22
+ async onServiceInit() {
23
+ console.log('🔄 Initializing database service...')
24
+
25
+ try {
26
+ this.connection = await this.connect()
27
+ this.isConnected = true
28
+ console.log('✅ Database service initialized successfully')
29
+ } catch (error) {
30
+ console.error('❌ Failed to initialize database service:', error)
31
+ throw error
32
+ }
33
+ }
34
+
35
+ async onServiceDestroy() {
36
+ console.log('🔄 Destroying database service...')
37
+
38
+ try {
39
+ if (this.connection && this.isConnected) {
40
+ await this.disconnect()
41
+ this.isConnected = false
42
+ console.log('✅ Database service destroyed successfully')
43
+ } else {
44
+ console.log('ℹ️ Database service was not connected')
45
+ }
46
+ } catch (error) {
47
+ console.error('❌ Error during database service cleanup:', error)
48
+ }
49
+ }
50
+
51
+ private async connect() {
52
+ // Simulate database connection
53
+ console.log('🔌 Connecting to database...')
54
+ await new Promise((resolve) => setTimeout(resolve, 200))
55
+
56
+ return {
57
+ host: 'localhost',
58
+ port: 5432,
59
+ database: 'myapp',
60
+ connected: true,
61
+ connectTime: new Date(),
62
+ }
63
+ }
64
+
65
+ private async disconnect() {
66
+ // Simulate database disconnection
67
+ console.log('🔌 Disconnecting from database...')
68
+ await new Promise((resolve) => setTimeout(resolve, 100))
69
+ }
70
+
71
+ async query(sql: string) {
72
+ if (!this.isConnected) {
73
+ throw new Error('Database not connected')
74
+ }
75
+
76
+ console.log(`📊 Executing query: ${sql}`)
77
+ return { rows: [], query: sql, executedAt: new Date() }
78
+ }
79
+ }
80
+
81
+ // 2. Service with multiple resources
82
+ @Injectable()
83
+ class CacheService implements OnServiceInit, OnServiceDestroy {
84
+ private cache = new Map()
85
+ private cleanupInterval: NodeJS.Timeout | null = null
86
+ private stats = {
87
+ hits: 0,
88
+ misses: 0,
89
+ sets: 0,
90
+ }
91
+
92
+ async onServiceInit() {
93
+ console.log('🔄 Initializing cache service...')
94
+
95
+ // Start cleanup interval
96
+ this.cleanupInterval = setInterval(() => {
97
+ this.cleanup()
98
+ }, 30000) // Cleanup every 30 seconds
99
+
100
+ console.log('✅ Cache service initialized successfully')
101
+ }
102
+
103
+ async onServiceDestroy() {
104
+ console.log('🔄 Destroying cache service...')
105
+
106
+ // Clear the cache
107
+ this.cache.clear()
108
+
109
+ // Clear the interval
110
+ if (this.cleanupInterval) {
111
+ clearInterval(this.cleanupInterval)
112
+ this.cleanupInterval = null
113
+ }
114
+
115
+ console.log('✅ Cache service destroyed successfully')
116
+ console.log('📊 Final cache stats:', this.stats)
117
+ }
118
+
119
+ set(key: string, value: any, ttl?: number) {
120
+ const expires = ttl ? Date.now() + ttl : null
121
+ this.cache.set(key, { value, expires })
122
+ this.stats.sets++
123
+ }
124
+
125
+ get(key: string) {
126
+ const item = this.cache.get(key)
127
+ if (!item) {
128
+ this.stats.misses++
129
+ return null
130
+ }
131
+
132
+ if (item.expires && Date.now() > item.expires) {
133
+ this.cache.delete(key)
134
+ this.stats.misses++
135
+ return null
136
+ }
137
+
138
+ this.stats.hits++
139
+ return item.value
140
+ }
141
+
142
+ private cleanup() {
143
+ const now = Date.now()
144
+ let cleaned = 0
145
+
146
+ for (const [key, item] of this.cache.entries()) {
147
+ if (item.expires && now > item.expires) {
148
+ this.cache.delete(key)
149
+ cleaned++
150
+ }
151
+ }
152
+
153
+ if (cleaned > 0) {
154
+ console.log(`🧹 Cache cleanup: removed ${cleaned} expired entries`)
155
+ }
156
+ }
157
+
158
+ getStats() {
159
+ return { ...this.stats, size: this.cache.size }
160
+ }
161
+ }
162
+
163
+ // 3. Service with conditional initialization
164
+ @Injectable()
165
+ class EmailService implements OnServiceInit, OnServiceDestroy {
166
+ private smtpConnection: any = null
167
+ private initialized = false
168
+
169
+ async onServiceInit() {
170
+ console.log('🔄 Initializing email service...')
171
+
172
+ // Check if email service should be enabled
173
+ const emailEnabled = process.env.EMAIL_ENABLED === 'true'
174
+
175
+ if (emailEnabled) {
176
+ try {
177
+ this.smtpConnection = await this.connectToSmtp()
178
+ this.initialized = true
179
+ console.log('✅ Email service initialized successfully')
180
+ } catch (error) {
181
+ console.error('❌ Failed to initialize email service:', error)
182
+ // Don't throw - let the service be created but mark it as failed
183
+ }
184
+ } else {
185
+ console.log('ℹ️ Email service disabled by configuration')
186
+ }
187
+ }
188
+
189
+ async onServiceDestroy() {
190
+ console.log('🔄 Destroying email service...')
191
+
192
+ if (this.initialized && this.smtpConnection) {
193
+ try {
194
+ await this.disconnectFromSmtp()
195
+ console.log('✅ Email service destroyed successfully')
196
+ } catch (error) {
197
+ console.error('❌ Error during email service cleanup:', error)
198
+ }
199
+ } else {
200
+ console.log('ℹ️ Email service was not initialized')
201
+ }
202
+ }
203
+
204
+ private async connectToSmtp() {
205
+ console.log('🔌 Connecting to SMTP server...')
206
+ await new Promise((resolve) => setTimeout(resolve, 150))
207
+
208
+ return {
209
+ host: 'smtp.example.com',
210
+ port: 587,
211
+ secure: false,
212
+ connected: true,
213
+ }
214
+ }
215
+
216
+ private async disconnectFromSmtp() {
217
+ console.log('🔌 Disconnecting from SMTP server...')
218
+ await new Promise((resolve) => setTimeout(resolve, 100))
219
+ }
220
+
221
+ async sendEmail(to: string, subject: string, body: string) {
222
+ if (!this.initialized) {
223
+ throw new Error('Email service not initialized')
224
+ }
225
+
226
+ console.log(`📧 Sending email to ${to}: ${subject}`)
227
+ return { success: true, messageId: Math.random().toString(36) }
228
+ }
229
+ }
230
+
231
+ // 4. Service that depends on other services with lifecycle
232
+ @Injectable()
233
+ class UserService implements OnServiceInit, OnServiceDestroy {
234
+ private readonly db = inject(DatabaseService)
235
+ private readonly cache = inject(CacheService)
236
+ private readonly email = asyncInject(EmailService)
237
+ private initialized = false
238
+
239
+ async onServiceInit() {
240
+ console.log('🔄 Initializing user service...')
241
+
242
+ // Wait for email service to be ready
243
+ await this.email
244
+ this.initialized = true
245
+
246
+ console.log('✅ User service initialized successfully')
247
+ }
248
+
249
+ async onServiceDestroy() {
250
+ console.log('🔄 Destroying user service...')
251
+
252
+ // Clear user-related cache entries
253
+ this.cache.set('users:count', null)
254
+ this.cache.set('users:active', null)
255
+
256
+ console.log('✅ User service destroyed successfully')
257
+ }
258
+
259
+ async createUser(name: string, email: string) {
260
+ if (!this.initialized) {
261
+ throw new Error('User service not initialized')
262
+ }
263
+
264
+ console.log(`👤 Creating user: ${name}`)
265
+
266
+ // Check cache first
267
+ const cached = this.cache.get(`user:${email}`)
268
+ if (cached) {
269
+ console.log('📋 User found in cache')
270
+ return cached
271
+ }
272
+
273
+ // Create user in database
274
+ const user = {
275
+ id: Math.random().toString(36),
276
+ name,
277
+ email,
278
+ createdAt: new Date(),
279
+ }
280
+
281
+ // Cache the user
282
+ this.cache.set(`user:${email}`, user, 300000) // 5 minutes TTL
283
+
284
+ // Send welcome email
285
+ try {
286
+ const emailService = await this.email
287
+ await emailService.sendEmail(
288
+ email,
289
+ 'Welcome!',
290
+ `Hello ${name}, welcome to our platform!`,
291
+ )
292
+ } catch (error) {
293
+ console.error('Failed to send welcome email:', error)
294
+ }
295
+
296
+ return user
297
+ }
298
+
299
+ async getUser(email: string) {
300
+ // Check cache first
301
+ const cached = this.cache.get(`user:${email}`)
302
+ if (cached) {
303
+ return cached
304
+ }
305
+
306
+ // Query database
307
+ const result = await this.db.query(
308
+ `SELECT * FROM users WHERE email = '${email}'`,
309
+ )
310
+ return result.rows[0] || null
311
+ }
312
+ }
313
+
314
+ // 5. Usage example
315
+ async function demonstrateLifecycle() {
316
+ console.log('=== Service Lifecycle Example ===\n')
317
+
318
+ // Create services (initialization happens automatically)
319
+ const userService = await container.get(UserService)
320
+ // oxlint-disable-next-line no-unused-vars
321
+ const dbService = await container.get(DatabaseService)
322
+ const cacheService = await container.get(CacheService)
323
+
324
+ // Use the services
325
+ const user = await userService.createUser('Alice', 'alice@example.com')
326
+ console.log('Created user:', user)
327
+
328
+ // Check cache stats
329
+ const stats = cacheService.getStats()
330
+ console.log('Cache stats:', stats)
331
+
332
+ // Simulate some work
333
+ await new Promise((resolve) => setTimeout(resolve, 1000))
334
+
335
+ // Get user from cache
336
+ const cachedUser = await userService.getUser('alice@example.com')
337
+ console.log('Retrieved user:', cachedUser)
338
+
339
+ // Check updated cache stats
340
+ const updatedStats = cacheService.getStats()
341
+ console.log('Updated cache stats:', updatedStats)
342
+
343
+ console.log('\n=== Service Cleanup ===')
344
+
345
+ // Services will be cleaned up when the application shuts down
346
+ // or when explicitly invalidated
347
+ }
348
+
349
+ // Main function
350
+ async function main() {
351
+ await demonstrateLifecycle()
352
+ }
353
+
354
+ // Run the example
355
+ if (require.main === module) {
356
+ main().catch(console.error)
357
+ }
358
+
359
+ export { DatabaseService, CacheService, EmailService, UserService }