@navios/di 0.3.0 → 0.4.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 +67 -6
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +2659 -0
- package/coverage/coverage-final.json +46 -0
- package/coverage/docs/examples/basic-usage.mts.html +376 -0
- package/coverage/docs/examples/factory-pattern.mts.html +1039 -0
- package/coverage/docs/examples/index.html +176 -0
- package/coverage/docs/examples/injection-tokens.mts.html +760 -0
- package/coverage/docs/examples/request-scope-example.mts.html +847 -0
- package/coverage/docs/examples/service-lifecycle.mts.html +1162 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +236 -0
- package/coverage/lib/_tsup-dts-rollup.d.mts.html +2806 -0
- package/coverage/lib/index.d.mts.html +310 -0
- package/coverage/lib/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/src/container.mts.html +586 -0
- package/coverage/src/decorators/factory.decorator.mts.html +322 -0
- package/coverage/src/decorators/index.html +146 -0
- package/coverage/src/decorators/index.mts.html +91 -0
- package/coverage/src/decorators/injectable.decorator.mts.html +394 -0
- package/coverage/src/enums/index.html +146 -0
- package/coverage/src/enums/index.mts.html +91 -0
- package/coverage/src/enums/injectable-scope.enum.mts.html +127 -0
- package/coverage/src/enums/injectable-type.enum.mts.html +97 -0
- package/coverage/src/errors/errors.enum.mts.html +109 -0
- package/coverage/src/errors/factory-not-found.mts.html +109 -0
- package/coverage/src/errors/factory-token-not-resolved.mts.html +115 -0
- package/coverage/src/errors/index.html +221 -0
- package/coverage/src/errors/index.mts.html +106 -0
- package/coverage/src/errors/instance-destroying.mts.html +109 -0
- package/coverage/src/errors/instance-expired.mts.html +109 -0
- package/coverage/src/errors/instance-not-found.mts.html +109 -0
- package/coverage/src/errors/unknown-error.mts.html +130 -0
- package/coverage/src/event-emitter.mts.html +400 -0
- package/coverage/src/factory-context.mts.html +109 -0
- package/coverage/src/index.html +296 -0
- package/coverage/src/index.mts.html +139 -0
- package/coverage/src/injection-token.mts.html +571 -0
- package/coverage/src/injector.mts.html +133 -0
- package/coverage/src/interfaces/factory.interface.mts.html +121 -0
- package/coverage/src/interfaces/index.html +161 -0
- package/coverage/src/interfaces/index.mts.html +94 -0
- package/coverage/src/interfaces/on-service-destroy.interface.mts.html +94 -0
- package/coverage/src/interfaces/on-service-init.interface.mts.html +94 -0
- package/coverage/src/registry.mts.html +247 -0
- package/coverage/src/request-context-holder.mts.html +607 -0
- package/coverage/src/service-instantiator.mts.html +559 -0
- package/coverage/src/service-locator-event-bus.mts.html +289 -0
- package/coverage/src/service-locator-instance-holder.mts.html +307 -0
- package/coverage/src/service-locator-manager.mts.html +604 -0
- package/coverage/src/service-locator.mts.html +2911 -0
- package/coverage/src/symbols/index.html +131 -0
- package/coverage/src/symbols/index.mts.html +88 -0
- package/coverage/src/symbols/injectable-token.mts.html +88 -0
- package/coverage/src/utils/defer.mts.html +304 -0
- package/coverage/src/utils/get-injectable-token.mts.html +142 -0
- package/coverage/src/utils/get-injectors.mts.html +691 -0
- package/coverage/src/utils/index.html +176 -0
- package/coverage/src/utils/index.mts.html +97 -0
- package/coverage/src/utils/types.mts.html +241 -0
- package/docs/README.md +5 -2
- package/docs/api-reference.md +38 -0
- package/docs/container.md +75 -0
- package/docs/getting-started.md +4 -3
- package/docs/injectable.md +4 -3
- package/docs/migration.md +177 -0
- package/docs/request-contexts.md +364 -0
- package/lib/_tsup-dts-rollup.d.mts +180 -35
- package/lib/_tsup-dts-rollup.d.ts +180 -35
- package/lib/index.d.mts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +485 -279
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +485 -280
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/defer.spec.mts +166 -0
- package/src/__tests__/errors.spec.mts +61 -0
- package/src/__tests__/event-emitter.spec.mts +163 -0
- package/src/__tests__/get-injectors.spec.mts +70 -0
- package/src/__tests__/registry.spec.mts +335 -0
- package/src/__tests__/request-scope.spec.mts +167 -4
- package/src/__tests__/service-instantiator.spec.mts +408 -0
- package/src/__tests__/service-locator-event-bus.spec.mts +242 -0
- package/src/__tests__/service-locator-manager.spec.mts +370 -0
- package/src/__tests__/unified-api.spec.mts +130 -0
- package/src/base-instance-holder-manager.mts +175 -0
- package/src/index.mts +1 -0
- package/src/request-context-holder.mts +85 -27
- package/src/service-locator-manager.mts +12 -70
- package/src/service-locator.mts +421 -226
package/docs/container.md
CHANGED
|
@@ -143,6 +143,41 @@ const serviceLocator = container.getServiceLocator()
|
|
|
143
143
|
const instance = await serviceLocator.getOrThrowInstance(UserService)
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
## Request Context Management
|
|
147
|
+
|
|
148
|
+
The Container provides built-in support for request contexts, allowing you to manage request-scoped services:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const container = new Container()
|
|
152
|
+
|
|
153
|
+
// Begin a request context
|
|
154
|
+
const context = container.beginRequest('req-123', { userId: 456 }, 100)
|
|
155
|
+
|
|
156
|
+
// Add request-specific instances
|
|
157
|
+
const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
|
|
158
|
+
context.addInstance(REQUEST_ID_TOKEN, 'req-123')
|
|
159
|
+
|
|
160
|
+
// Set as current context
|
|
161
|
+
container.setCurrentRequestContext('req-123')
|
|
162
|
+
|
|
163
|
+
// Services can now access request-scoped data
|
|
164
|
+
@Injectable()
|
|
165
|
+
class RequestService {
|
|
166
|
+
private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
|
|
167
|
+
|
|
168
|
+
async process() {
|
|
169
|
+
const id = await this.requestId
|
|
170
|
+
console.log(`Processing in request: ${id}`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const service = await container.get(RequestService)
|
|
175
|
+
await service.process() // "Processing in request: req-123"
|
|
176
|
+
|
|
177
|
+
// Clean up when done
|
|
178
|
+
await container.endRequest('req-123')
|
|
179
|
+
```
|
|
180
|
+
|
|
146
181
|
## Best Practices
|
|
147
182
|
|
|
148
183
|
### 1. Use Container for Application Setup
|
|
@@ -215,6 +250,26 @@ const userContainer = new Container(userRegistry)
|
|
|
215
250
|
const paymentContainer = new Container(paymentRegistry)
|
|
216
251
|
```
|
|
217
252
|
|
|
253
|
+
### 5. Manage Request Contexts Properly
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// Always clean up request contexts
|
|
257
|
+
async function handleRequest(req, res) {
|
|
258
|
+
const requestId = generateRequestId()
|
|
259
|
+
const context = container.beginRequest(requestId, {
|
|
260
|
+
ip: req.ip,
|
|
261
|
+
userAgent: req.get('User-Agent'),
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
container.setCurrentRequestContext(requestId)
|
|
266
|
+
await processRequest(req, res)
|
|
267
|
+
} finally {
|
|
268
|
+
await container.endRequest(requestId)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
218
273
|
## API Reference
|
|
219
274
|
|
|
220
275
|
### Constructor
|
|
@@ -252,6 +307,26 @@ Waits for all pending operations to complete.
|
|
|
252
307
|
|
|
253
308
|
Returns the underlying ServiceLocator instance.
|
|
254
309
|
|
|
310
|
+
#### `beginRequest(requestId: string, metadata?: Record<string, any>, priority?: number): RequestContextHolder`
|
|
311
|
+
|
|
312
|
+
Begins a new request context with the given parameters.
|
|
313
|
+
|
|
314
|
+
- `requestId`: Unique identifier for this request
|
|
315
|
+
- `metadata`: Optional metadata for the request
|
|
316
|
+
- `priority`: Priority for resolution (higher = more priority, defaults to 100)
|
|
317
|
+
|
|
318
|
+
#### `endRequest(requestId: string): Promise<void>`
|
|
319
|
+
|
|
320
|
+
Ends a request context and cleans up all associated instances.
|
|
321
|
+
|
|
322
|
+
#### `getCurrentRequestContext(): RequestContextHolder | null`
|
|
323
|
+
|
|
324
|
+
Gets the current request context.
|
|
325
|
+
|
|
326
|
+
#### `setCurrentRequestContext(requestId: string): void`
|
|
327
|
+
|
|
328
|
+
Sets the current request context.
|
|
329
|
+
|
|
255
330
|
## Error Handling
|
|
256
331
|
|
|
257
332
|
The Container can throw various errors:
|
package/docs/getting-started.md
CHANGED
|
@@ -66,13 +66,14 @@ class EmailService {
|
|
|
66
66
|
// 2. Create a user service that depends on the email service
|
|
67
67
|
@Injectable()
|
|
68
68
|
class UserService {
|
|
69
|
-
private readonly emailService =
|
|
69
|
+
private readonly emailService = asyncInject(EmailService)
|
|
70
70
|
|
|
71
71
|
async createUser(name: string, email: string) {
|
|
72
72
|
console.log(`Creating user: ${name}`)
|
|
73
73
|
|
|
74
|
-
//
|
|
75
|
-
await this.emailService
|
|
74
|
+
// Get the email service and send welcome email
|
|
75
|
+
const emailService = await this.emailService
|
|
76
|
+
await emailService.sendEmail(
|
|
76
77
|
email,
|
|
77
78
|
'Welcome!',
|
|
78
79
|
`Hello ${name}, welcome to our platform!`,
|
package/docs/injectable.md
CHANGED
|
@@ -20,7 +20,7 @@ class UserService {
|
|
|
20
20
|
### Service with Dependencies
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
|
-
import {
|
|
23
|
+
import { asyncInject, Injectable } from '@navios/di'
|
|
24
24
|
|
|
25
25
|
@Injectable()
|
|
26
26
|
class DatabaseService {
|
|
@@ -31,10 +31,11 @@ class DatabaseService {
|
|
|
31
31
|
|
|
32
32
|
@Injectable()
|
|
33
33
|
class UserService {
|
|
34
|
-
private readonly db =
|
|
34
|
+
private readonly db = asyncInject(DatabaseService)
|
|
35
35
|
|
|
36
36
|
async getUsers() {
|
|
37
|
-
const
|
|
37
|
+
const dbService = await this.db
|
|
38
|
+
const connection = await dbService.connect()
|
|
38
39
|
return `Users from ${connection}`
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
This guide helps you migrate between different versions of Navios DI.
|
|
4
|
+
|
|
5
|
+
## Migrating to v0.3.x
|
|
6
|
+
|
|
7
|
+
### New Features
|
|
8
|
+
|
|
9
|
+
#### Request Context Management
|
|
10
|
+
|
|
11
|
+
The biggest addition in v0.3.x is request context management. This feature allows you to manage request-scoped services with automatic cleanup.
|
|
12
|
+
|
|
13
|
+
**New APIs:**
|
|
14
|
+
|
|
15
|
+
- `Container.beginRequest(requestId, metadata?, priority?)`
|
|
16
|
+
- `Container.endRequest(requestId)`
|
|
17
|
+
- `Container.getCurrentRequestContext()`
|
|
18
|
+
- `Container.setCurrentRequestContext(requestId)`
|
|
19
|
+
- `RequestContextHolder` interface
|
|
20
|
+
|
|
21
|
+
**Example:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// New request context API
|
|
25
|
+
const container = new Container()
|
|
26
|
+
const context = container.beginRequest('req-123', { userId: 456 })
|
|
27
|
+
container.setCurrentRequestContext('req-123')
|
|
28
|
+
|
|
29
|
+
// Add request-scoped data
|
|
30
|
+
const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
|
|
31
|
+
context.addInstance(REQUEST_ID_TOKEN, 'req-123')
|
|
32
|
+
|
|
33
|
+
// Use in services
|
|
34
|
+
@Injectable()
|
|
35
|
+
class RequestService {
|
|
36
|
+
private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
|
|
37
|
+
|
|
38
|
+
async process() {
|
|
39
|
+
const id = await this.requestId
|
|
40
|
+
console.log(`Processing request: ${id}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Clean up when done
|
|
45
|
+
await container.endRequest('req-123')
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Recommended Changes
|
|
49
|
+
|
|
50
|
+
#### Prefer `asyncInject` over `inject`
|
|
51
|
+
|
|
52
|
+
While `inject` still works, `asyncInject` is now the recommended approach for most use cases as it's safer and handles async dependencies better.
|
|
53
|
+
|
|
54
|
+
**Before:**
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
@Injectable()
|
|
58
|
+
class UserService {
|
|
59
|
+
private readonly db = inject(DatabaseService)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**After:**
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
@Injectable()
|
|
67
|
+
class UserService {
|
|
68
|
+
private readonly db = asyncInject(DatabaseService)
|
|
69
|
+
|
|
70
|
+
async getUsers() {
|
|
71
|
+
const database = await this.db
|
|
72
|
+
return database.query('SELECT * FROM users')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Breaking Changes
|
|
78
|
+
|
|
79
|
+
None in v0.3.x - this is a feature release with full backward compatibility.
|
|
80
|
+
|
|
81
|
+
## Migrating from v0.2.x to v0.3.x
|
|
82
|
+
|
|
83
|
+
### New Dependencies
|
|
84
|
+
|
|
85
|
+
No new peer dependencies were added.
|
|
86
|
+
|
|
87
|
+
### Updated Type Definitions
|
|
88
|
+
|
|
89
|
+
Some internal type definitions were improved for better TypeScript support, but no breaking changes to public APIs.
|
|
90
|
+
|
|
91
|
+
## Future Migration Notes
|
|
92
|
+
|
|
93
|
+
### Planned for v0.4.x
|
|
94
|
+
|
|
95
|
+
- Enhanced request context lifecycle hooks
|
|
96
|
+
- Performance optimizations for high-throughput scenarios
|
|
97
|
+
- Additional factory pattern improvements
|
|
98
|
+
|
|
99
|
+
### Best Practices for Forward Compatibility
|
|
100
|
+
|
|
101
|
+
1. **Use `asyncInject` for new code** - This is the future-preferred injection method
|
|
102
|
+
2. **Implement lifecycle hooks** - `OnServiceInit` and `OnServiceDestroy` for proper resource management
|
|
103
|
+
3. **Use request contexts** - For web applications and request-scoped data
|
|
104
|
+
4. **Prefer injection tokens** - For configuration and interface-based dependencies
|
|
105
|
+
|
|
106
|
+
## Common Migration Issues
|
|
107
|
+
|
|
108
|
+
### Issue: Services not found in request context
|
|
109
|
+
|
|
110
|
+
**Problem:** Services can't be resolved when using request contexts.
|
|
111
|
+
|
|
112
|
+
**Solution:** Make sure you've set the current request context:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
container.setCurrentRequestContext('your-request-id')
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Issue: Async dependencies not ready
|
|
119
|
+
|
|
120
|
+
**Problem:** Using `inject` with dependencies that aren't immediately available.
|
|
121
|
+
|
|
122
|
+
**Solution:** Switch to `asyncInject`:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Instead of this:
|
|
126
|
+
private readonly service = inject(AsyncService)
|
|
127
|
+
|
|
128
|
+
// Use this:
|
|
129
|
+
private readonly service = asyncInject(AsyncService)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Issue: Memory leaks with request contexts
|
|
133
|
+
|
|
134
|
+
**Problem:** Request contexts not being cleaned up.
|
|
135
|
+
|
|
136
|
+
**Solution:** Always call `endRequest()`:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
try {
|
|
140
|
+
const context = container.beginRequest('req-123')
|
|
141
|
+
// ... process request
|
|
142
|
+
} finally {
|
|
143
|
+
await container.endRequest('req-123')
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Getting Help
|
|
148
|
+
|
|
149
|
+
If you encounter issues during migration:
|
|
150
|
+
|
|
151
|
+
1. Check the [API Reference](./api-reference.md) for detailed method signatures
|
|
152
|
+
2. Review the [examples](./examples/) for common patterns
|
|
153
|
+
3. Check the GitHub issues for known migration problems
|
|
154
|
+
4. Create a new issue if you find a bug or need help
|
|
155
|
+
|
|
156
|
+
## Changelog Summary
|
|
157
|
+
|
|
158
|
+
### v0.3.1
|
|
159
|
+
|
|
160
|
+
- Added request context management
|
|
161
|
+
- Improved TypeScript type definitions
|
|
162
|
+
- Enhanced container API with request context methods
|
|
163
|
+
- New `RequestContextHolder` interface
|
|
164
|
+
- Better error messages for injection failures
|
|
165
|
+
|
|
166
|
+
### v0.3.0
|
|
167
|
+
|
|
168
|
+
- Initial release with request context support
|
|
169
|
+
- Container API improvements
|
|
170
|
+
- Better async injection handling
|
|
171
|
+
|
|
172
|
+
### v0.2.x
|
|
173
|
+
|
|
174
|
+
- Basic dependency injection functionality
|
|
175
|
+
- Injectable and Factory decorators
|
|
176
|
+
- Injection tokens with Zod validation
|
|
177
|
+
- Service lifecycle hooks
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Request Contexts
|
|
2
|
+
|
|
3
|
+
Request contexts in Navios DI provide a powerful way to manage request-scoped services with automatic cleanup and priority-based resolution. This is particularly useful in web applications where you need to maintain request-specific data and ensure proper cleanup after each request.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A request context is a scoped container that can hold pre-prepared instances and metadata for a specific request. When a request context is active, services can access request-specific data through dependency injection.
|
|
8
|
+
|
|
9
|
+
### Key Features
|
|
10
|
+
|
|
11
|
+
- **Request-scoped instances**: Services that exist only for the duration of a request
|
|
12
|
+
- **Automatic cleanup**: All request-scoped instances are automatically cleaned up when the request ends
|
|
13
|
+
- **Priority-based resolution**: Multiple contexts can exist with different priorities
|
|
14
|
+
- **Metadata support**: Attach arbitrary metadata to request contexts
|
|
15
|
+
- **Thread-safe**: Safe to use in concurrent environments
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
### Creating and Managing Request Contexts
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Container, Injectable, InjectionToken } from '@navios/di'
|
|
23
|
+
|
|
24
|
+
const container = new Container()
|
|
25
|
+
|
|
26
|
+
// Begin a new request context
|
|
27
|
+
const context = container.beginRequest('req-123', { userId: 456 }, 100)
|
|
28
|
+
|
|
29
|
+
// Set it as the current context
|
|
30
|
+
container.setCurrentRequestContext('req-123')
|
|
31
|
+
|
|
32
|
+
// End the request context when done
|
|
33
|
+
await container.endRequest('req-123')
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Using Request-Scoped Data
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const REQUEST_ID_TOKEN = InjectionToken.create<string>('REQUEST_ID')
|
|
40
|
+
const USER_ID_TOKEN = InjectionToken.create<number>('USER_ID')
|
|
41
|
+
|
|
42
|
+
@Injectable()
|
|
43
|
+
class RequestLogger {
|
|
44
|
+
private readonly requestId = asyncInject(REQUEST_ID_TOKEN)
|
|
45
|
+
private readonly userId = asyncInject(USER_ID_TOKEN)
|
|
46
|
+
|
|
47
|
+
async log(message: string) {
|
|
48
|
+
const reqId = await this.requestId
|
|
49
|
+
const uid = await this.userId
|
|
50
|
+
console.log(`[${reqId}] User ${uid}: ${message}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Setup request context
|
|
55
|
+
const context = container.beginRequest('req-123')
|
|
56
|
+
context.addInstance(REQUEST_ID_TOKEN, 'req-123')
|
|
57
|
+
context.addInstance(USER_ID_TOKEN, 456)
|
|
58
|
+
|
|
59
|
+
container.setCurrentRequestContext('req-123')
|
|
60
|
+
|
|
61
|
+
// Use the service
|
|
62
|
+
const logger = await container.get(RequestLogger)
|
|
63
|
+
await logger.log('Processing request') // "[req-123] User 456: Processing request"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Advanced Features
|
|
67
|
+
|
|
68
|
+
### Priority-Based Resolution
|
|
69
|
+
|
|
70
|
+
When multiple request contexts exist, the one with the highest priority is used:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// High priority context (e.g., admin request)
|
|
74
|
+
const adminContext = container.beginRequest('admin-req', {}, 200)
|
|
75
|
+
|
|
76
|
+
// Normal priority context
|
|
77
|
+
const userContext = container.beginRequest('user-req', {}, 100)
|
|
78
|
+
|
|
79
|
+
// Admin context will be used due to higher priority
|
|
80
|
+
container.setCurrentRequestContext('admin-req')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Request Metadata
|
|
84
|
+
|
|
85
|
+
Request contexts can carry metadata that services can access:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
@Injectable()
|
|
89
|
+
class AuditService {
|
|
90
|
+
private readonly context = asyncInject(Container)
|
|
91
|
+
|
|
92
|
+
async logAction(action: string) {
|
|
93
|
+
const container = await this.context
|
|
94
|
+
const requestContext = container.getCurrentRequestContext()
|
|
95
|
+
|
|
96
|
+
if (requestContext) {
|
|
97
|
+
const userId = requestContext.getMetadata('userId')
|
|
98
|
+
const traceId = requestContext.getMetadata('traceId')
|
|
99
|
+
|
|
100
|
+
console.log(`User ${userId} performed ${action} (trace: ${traceId})`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Setup with metadata
|
|
106
|
+
const context = container.beginRequest('req-123', {
|
|
107
|
+
userId: 456,
|
|
108
|
+
traceId: 'abc-123',
|
|
109
|
+
userAgent: 'Mozilla/5.0...',
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Pre-prepared Instances
|
|
114
|
+
|
|
115
|
+
You can add pre-prepared instances to a request context:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
@Injectable()
|
|
119
|
+
class DatabaseConnection {
|
|
120
|
+
constructor(private connectionString: string) {}
|
|
121
|
+
|
|
122
|
+
async query(sql: string) {
|
|
123
|
+
return `Executing: ${sql} on ${this.connectionString}`
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create a request-specific database connection
|
|
128
|
+
const dbConnection = new DatabaseConnection('user-specific-db')
|
|
129
|
+
const context = container.beginRequest('req-123')
|
|
130
|
+
context.addInstance('DatabaseConnection', dbConnection)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Web Framework Integration
|
|
134
|
+
|
|
135
|
+
### Express.js Example
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { Container, Injectable, InjectionToken } from '@navios/di'
|
|
139
|
+
|
|
140
|
+
import express from 'express'
|
|
141
|
+
|
|
142
|
+
const REQUEST_TOKEN = InjectionToken.create<express.Request>('REQUEST')
|
|
143
|
+
const RESPONSE_TOKEN = InjectionToken.create<express.Response>('RESPONSE')
|
|
144
|
+
|
|
145
|
+
@Injectable()
|
|
146
|
+
class RequestHandler {
|
|
147
|
+
private readonly req = asyncInject(REQUEST_TOKEN)
|
|
148
|
+
private readonly res = asyncInject(RESPONSE_TOKEN)
|
|
149
|
+
|
|
150
|
+
async handleRequest() {
|
|
151
|
+
const request = await this.req
|
|
152
|
+
const response = await this.res
|
|
153
|
+
|
|
154
|
+
response.json({
|
|
155
|
+
message: 'Hello!',
|
|
156
|
+
path: request.path,
|
|
157
|
+
method: request.method,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const app = express()
|
|
163
|
+
const container = new Container()
|
|
164
|
+
|
|
165
|
+
app.use('*', async (req, res, next) => {
|
|
166
|
+
const requestId = `req-${Date.now()}-${Math.random()}`
|
|
167
|
+
|
|
168
|
+
// Create request context
|
|
169
|
+
const context = container.beginRequest(requestId, {
|
|
170
|
+
path: req.path,
|
|
171
|
+
method: req.method,
|
|
172
|
+
userAgent: req.get('User-Agent'),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Add request-specific instances
|
|
176
|
+
context.addInstance(REQUEST_TOKEN, req)
|
|
177
|
+
context.addInstance(RESPONSE_TOKEN, res)
|
|
178
|
+
|
|
179
|
+
// Set as current context
|
|
180
|
+
container.setCurrentRequestContext(requestId)
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const handler = await container.get(RequestHandler)
|
|
184
|
+
await handler.handleRequest()
|
|
185
|
+
} finally {
|
|
186
|
+
// Clean up request context
|
|
187
|
+
await container.endRequest(requestId)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Fastify Example
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { Container, Injectable, InjectionToken } from '@navios/di'
|
|
196
|
+
|
|
197
|
+
import fastify from 'fastify'
|
|
198
|
+
|
|
199
|
+
const REQUEST_TOKEN = InjectionToken.create<any>('FASTIFY_REQUEST')
|
|
200
|
+
|
|
201
|
+
const app = fastify()
|
|
202
|
+
const container = new Container()
|
|
203
|
+
|
|
204
|
+
app.addHook('preHandler', async (request, reply) => {
|
|
205
|
+
const requestId = `req-${request.id}`
|
|
206
|
+
|
|
207
|
+
const context = container.beginRequest(requestId, {
|
|
208
|
+
ip: request.ip,
|
|
209
|
+
userAgent: request.headers['user-agent'],
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
context.addInstance(REQUEST_TOKEN, request)
|
|
213
|
+
container.setCurrentRequestContext(requestId)
|
|
214
|
+
|
|
215
|
+
// Store requestId for cleanup
|
|
216
|
+
request.requestId = requestId
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
app.addHook('onResponse', async (request, reply) => {
|
|
220
|
+
if (request.requestId) {
|
|
221
|
+
await container.endRequest(request.requestId)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Best Practices
|
|
227
|
+
|
|
228
|
+
### 1. Always Clean Up
|
|
229
|
+
|
|
230
|
+
Always ensure request contexts are properly cleaned up:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const requestId = generateRequestId()
|
|
234
|
+
const context = container.beginRequest(requestId)
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// Process request
|
|
238
|
+
await processRequest()
|
|
239
|
+
} finally {
|
|
240
|
+
// Always clean up, even on errors
|
|
241
|
+
await container.endRequest(requestId)
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 2. Use Meaningful Request IDs
|
|
246
|
+
|
|
247
|
+
Use descriptive request IDs that help with debugging:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const requestId = `${req.method}-${req.path}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 3. Set Appropriate Priorities
|
|
254
|
+
|
|
255
|
+
Use priorities to ensure correct resolution order:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// System/admin requests - highest priority
|
|
259
|
+
const adminContext = container.beginRequest('admin-req', {}, 1000)
|
|
260
|
+
|
|
261
|
+
// Authenticated user requests
|
|
262
|
+
const userContext = container.beginRequest('user-req', {}, 500)
|
|
263
|
+
|
|
264
|
+
// Anonymous requests - lowest priority
|
|
265
|
+
const anonContext = container.beginRequest('anon-req', {}, 100)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 4. Leverage Metadata
|
|
269
|
+
|
|
270
|
+
Use metadata for cross-cutting concerns:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
const context = container.beginRequest('req-123', {
|
|
274
|
+
traceId: generateTraceId(),
|
|
275
|
+
correlationId: req.headers['x-correlation-id'],
|
|
276
|
+
userId: req.user?.id,
|
|
277
|
+
tenantId: req.tenant?.id,
|
|
278
|
+
startTime: Date.now(),
|
|
279
|
+
})
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 5. Combine with Lifecycle Hooks
|
|
283
|
+
|
|
284
|
+
Use lifecycle hooks for request-scoped resource management:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
@Injectable()
|
|
288
|
+
class DatabaseTransaction implements OnServiceInit, OnServiceDestroy {
|
|
289
|
+
private transaction: any = null
|
|
290
|
+
|
|
291
|
+
async onServiceInit() {
|
|
292
|
+
this.transaction = await db.beginTransaction()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async onServiceDestroy() {
|
|
296
|
+
if (this.transaction) {
|
|
297
|
+
await this.transaction.rollback()
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async commit() {
|
|
302
|
+
await this.transaction.commit()
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Error Handling
|
|
308
|
+
|
|
309
|
+
Request contexts handle errors gracefully:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
try {
|
|
313
|
+
const context = container.beginRequest('req-123')
|
|
314
|
+
container.setCurrentRequestContext('req-123')
|
|
315
|
+
|
|
316
|
+
// If this throws, cleanup will still happen
|
|
317
|
+
await processRequest()
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('Request failed:', error)
|
|
320
|
+
throw error
|
|
321
|
+
} finally {
|
|
322
|
+
// Context cleanup happens automatically
|
|
323
|
+
await container.endRequest('req-123')
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## API Reference
|
|
328
|
+
|
|
329
|
+
### Container Methods
|
|
330
|
+
|
|
331
|
+
- `beginRequest(requestId: string, metadata?: Record<string, any>, priority?: number): RequestContextHolder`
|
|
332
|
+
- `endRequest(requestId: string): Promise<void>`
|
|
333
|
+
- `getCurrentRequestContext(): RequestContextHolder | null`
|
|
334
|
+
- `setCurrentRequestContext(requestId: string): void`
|
|
335
|
+
|
|
336
|
+
### RequestContextHolder Interface
|
|
337
|
+
|
|
338
|
+
- `requestId: string` - Unique identifier for this request
|
|
339
|
+
- `priority: number` - Priority for resolution
|
|
340
|
+
- `metadata: Map<string, any>` - Request-specific metadata
|
|
341
|
+
- `createdAt: number` - Timestamp when context was created
|
|
342
|
+
- `addInstance(token: InjectionToken<any>, instance: any): void`
|
|
343
|
+
- `getMetadata(key: string): any | undefined`
|
|
344
|
+
- `setMetadata(key: string, value: any): void`
|
|
345
|
+
- `clear(): void` - Clear all instances and metadata
|
|
346
|
+
|
|
347
|
+
## Performance Considerations
|
|
348
|
+
|
|
349
|
+
- Request contexts are lightweight and designed for high-throughput scenarios
|
|
350
|
+
- Cleanup is asynchronous and won't block request processing
|
|
351
|
+
- Use appropriate priorities to avoid unnecessary context switching
|
|
352
|
+
- Consider pooling request contexts for very high-frequency scenarios
|
|
353
|
+
|
|
354
|
+
## Troubleshooting
|
|
355
|
+
|
|
356
|
+
### Common Issues
|
|
357
|
+
|
|
358
|
+
**Context not found**: Make sure you've called `setCurrentRequestContext()` before accessing request-scoped services.
|
|
359
|
+
|
|
360
|
+
**Wrong priority resolution**: Check that your priority values are set correctly (higher = more priority).
|
|
361
|
+
|
|
362
|
+
**Memory leaks**: Always call `endRequest()` to clean up contexts, preferably in a `finally` block.
|
|
363
|
+
|
|
364
|
+
**Service not found in context**: Ensure you've added the instance to the context with `addInstance()`.
|