@navios/di 0.2.1 → 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.
- 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 +490 -145
- package/lib/_tsup-dts-rollup.d.ts +490 -145
- package/lib/index.d.mts +26 -12
- package/lib/index.d.ts +26 -12
- package/lib/index.js +993 -462
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +983 -453
- 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 +263 -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 +145 -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,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 }
|