@djodjonx/neo-syringe 1.2.0 → 1.2.2
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/.github/workflows/docs.yml +59 -0
- package/CHANGELOG.md +14 -0
- package/README.md +72 -779
- package/dist/cli/index.cjs +15 -0
- package/dist/cli/index.mjs +15 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/unplugin/index.cjs +31 -7
- package/dist/unplugin/index.d.cts +7 -5
- package/dist/unplugin/index.d.mts +7 -5
- package/dist/unplugin/index.mjs +31 -7
- package/docs/.vitepress/config.ts +109 -0
- package/docs/.vitepress/theme/custom.css +150 -0
- package/docs/.vitepress/theme/index.ts +17 -0
- package/docs/api/configuration.md +274 -0
- package/docs/api/functions.md +291 -0
- package/docs/api/types.md +158 -0
- package/docs/guide/basic-usage.md +267 -0
- package/docs/guide/cli.md +174 -0
- package/docs/guide/generated-code.md +284 -0
- package/docs/guide/getting-started.md +171 -0
- package/docs/guide/ide-plugin.md +203 -0
- package/docs/guide/injection-types.md +287 -0
- package/docs/guide/legacy-migration.md +333 -0
- package/docs/guide/lifecycle.md +223 -0
- package/docs/guide/parent-container.md +321 -0
- package/docs/guide/scoped-injections.md +271 -0
- package/docs/guide/what-is-neo-syringe.md +162 -0
- package/docs/guide/why-neo-syringe.md +219 -0
- package/docs/index.md +138 -0
- package/docs/public/logo.png +0 -0
- package/package.json +5 -3
- package/src/analyzer/types.ts +52 -52
- package/src/cli/index.ts +15 -0
- package/src/generator/Generator.ts +23 -1
- package/src/types.ts +1 -1
- package/src/unplugin/index.ts +13 -41
- package/tests/analyzer/AnalyzerDeclarative.test.ts +1 -1
- package/tests/e2e/container-integration.test.ts +19 -19
- package/tests/e2e/generated-code.test.ts +2 -2
- package/tsconfig.json +2 -1
- package/typedoc.json +0 -5
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Parent Container
|
|
2
|
+
|
|
3
|
+
Build hierarchical container architectures with `useContainer`.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Neo-Syringe supports parent containers for:
|
|
8
|
+
|
|
9
|
+
- **SharedKernel pattern**: Core services shared across bounded contexts
|
|
10
|
+
- **Modular architecture**: Each module has its own container
|
|
11
|
+
- **Testing**: Override production services with mocks
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
|
|
17
|
+
|
|
18
|
+
// Parent container
|
|
19
|
+
const sharedKernel = defineBuilderConfig({
|
|
20
|
+
name: 'SharedKernel',
|
|
21
|
+
injections: [
|
|
22
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
23
|
+
{ token: useInterface<IEventBus>(), provider: InMemoryEventBus }
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Child container
|
|
28
|
+
const userModule = defineBuilderConfig({
|
|
29
|
+
name: 'UserModule',
|
|
30
|
+
useContainer: sharedKernel, // 👈 Inherit from parent
|
|
31
|
+
injections: [
|
|
32
|
+
{ token: UserRepository },
|
|
33
|
+
{ token: UserService } // Can use ILogger and IEventBus!
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Resolution Order
|
|
39
|
+
|
|
40
|
+
When you call `resolve()`, Neo-Syringe looks up the token in this order:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
1. Local container (this container's injections)
|
|
44
|
+
└── Found? → Return instance
|
|
45
|
+
└── Not found? ↓
|
|
46
|
+
|
|
47
|
+
2. Parent container (useContainer)
|
|
48
|
+
└── Found? → Return instance
|
|
49
|
+
└── Not found? ↓
|
|
50
|
+
|
|
51
|
+
3. Throw "Service not found" error
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## SharedKernel Architecture
|
|
55
|
+
|
|
56
|
+
Perfect for Domain-Driven Design with shared infrastructure:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// shared-kernel/container.ts
|
|
60
|
+
export interface ILogger {
|
|
61
|
+
log(msg: string): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface IEventBus {
|
|
65
|
+
publish(event: any): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class ConsoleLogger implements ILogger {
|
|
69
|
+
log(msg: string) { console.log(`[LOG] ${msg}`); }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class InMemoryEventBus implements IEventBus {
|
|
73
|
+
publish(event: any) { console.log('Event:', event); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const sharedKernel = defineBuilderConfig({
|
|
77
|
+
name: 'SharedKernel',
|
|
78
|
+
injections: [
|
|
79
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
80
|
+
{ token: useInterface<IEventBus>(), provider: InMemoryEventBus }
|
|
81
|
+
]
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// user-module/container.ts
|
|
87
|
+
import { sharedKernel, ILogger, IEventBus } from '../shared-kernel';
|
|
88
|
+
|
|
89
|
+
class UserRepository {
|
|
90
|
+
constructor(private logger: ILogger) {}
|
|
91
|
+
|
|
92
|
+
findById(id: string) {
|
|
93
|
+
this.logger.log(`Finding user ${id}`);
|
|
94
|
+
return { id, name: 'John' };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class UserService {
|
|
99
|
+
constructor(
|
|
100
|
+
private logger: ILogger, // From SharedKernel
|
|
101
|
+
private eventBus: IEventBus, // From SharedKernel
|
|
102
|
+
private repo: UserRepository // Local
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
createUser(name: string) {
|
|
106
|
+
const user = { id: crypto.randomUUID(), name };
|
|
107
|
+
this.logger.log(`Creating user: ${name}`);
|
|
108
|
+
this.eventBus.publish({ type: 'UserCreated', user });
|
|
109
|
+
return user;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const userModule = defineBuilderConfig({
|
|
114
|
+
name: 'UserModule',
|
|
115
|
+
useContainer: sharedKernel,
|
|
116
|
+
injections: [
|
|
117
|
+
{ token: UserRepository },
|
|
118
|
+
{ token: UserService }
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// order-module/container.ts
|
|
125
|
+
import { sharedKernel, ILogger } from '../shared-kernel';
|
|
126
|
+
|
|
127
|
+
class OrderService {
|
|
128
|
+
constructor(private logger: ILogger) {} // From SharedKernel
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const orderModule = defineBuilderConfig({
|
|
132
|
+
name: 'OrderModule',
|
|
133
|
+
useContainer: sharedKernel,
|
|
134
|
+
injections: [
|
|
135
|
+
{ token: OrderService }
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// main.ts
|
|
142
|
+
import { userModule } from './user-module/container';
|
|
143
|
+
import { orderModule } from './order-module/container';
|
|
144
|
+
|
|
145
|
+
// Both modules share the same ILogger and IEventBus instances!
|
|
146
|
+
const userService = userModule.resolve(UserService);
|
|
147
|
+
const orderService = orderModule.resolve(OrderService);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Multi-Level Hierarchy
|
|
151
|
+
|
|
152
|
+
Chain containers for complex architectures:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Level 1: Infrastructure
|
|
156
|
+
const infrastructure = defineBuilderConfig({
|
|
157
|
+
name: 'Infrastructure',
|
|
158
|
+
injections: [
|
|
159
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
160
|
+
{ token: useInterface<IDatabase>(), provider: PostgresDatabase }
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Level 2: Domain (inherits Infrastructure)
|
|
165
|
+
const domain = defineBuilderConfig({
|
|
166
|
+
name: 'Domain',
|
|
167
|
+
useContainer: infrastructure,
|
|
168
|
+
injections: [
|
|
169
|
+
{ token: UserRepository },
|
|
170
|
+
{ token: OrderRepository }
|
|
171
|
+
]
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Level 3: Application (inherits Domain + Infrastructure)
|
|
175
|
+
const application = defineBuilderConfig({
|
|
176
|
+
name: 'Application',
|
|
177
|
+
useContainer: domain, // Gets Domain AND Infrastructure!
|
|
178
|
+
injections: [
|
|
179
|
+
{ token: UserService },
|
|
180
|
+
{ token: OrderService }
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Resolution traverses the chain
|
|
185
|
+
application.resolve(UserService); // Local
|
|
186
|
+
application.resolve(UserRepository); // From Domain
|
|
187
|
+
application.resolve(useInterface<ILogger>()); // From Infrastructure
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Validation
|
|
191
|
+
|
|
192
|
+
Neo-Syringe validates parent containers at compile-time:
|
|
193
|
+
|
|
194
|
+
### Duplicate Detection
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const parent = defineBuilderConfig({
|
|
198
|
+
injections: [
|
|
199
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger }
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ❌ Error: Duplicate registration
|
|
204
|
+
const child = defineBuilderConfig({
|
|
205
|
+
useContainer: parent,
|
|
206
|
+
injections: [
|
|
207
|
+
{ token: useInterface<ILogger>(), provider: FileLogger }
|
|
208
|
+
]
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Solution**: Use `scoped: true` for intentional overrides.
|
|
213
|
+
|
|
214
|
+
### Missing Dependencies
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const parent = defineBuilderConfig({
|
|
218
|
+
injections: [
|
|
219
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger }
|
|
220
|
+
]
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
class UserService {
|
|
224
|
+
constructor(
|
|
225
|
+
private logger: ILogger,
|
|
226
|
+
private db: IDatabase // Not in parent!
|
|
227
|
+
) {}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ❌ Error: Missing binding 'IDatabase'
|
|
231
|
+
const child = defineBuilderConfig({
|
|
232
|
+
useContainer: parent,
|
|
233
|
+
injections: [
|
|
234
|
+
{ token: UserService }
|
|
235
|
+
]
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Generated Code
|
|
240
|
+
|
|
241
|
+
The parent relationship is preserved in generated code:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Configuration
|
|
245
|
+
export const child = defineBuilderConfig({
|
|
246
|
+
name: 'ChildContainer',
|
|
247
|
+
useContainer: parent,
|
|
248
|
+
injections: [
|
|
249
|
+
{ token: UserService }
|
|
250
|
+
]
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Generated code
|
|
254
|
+
import { parent } from './parent';
|
|
255
|
+
|
|
256
|
+
class NeoContainer {
|
|
257
|
+
constructor(
|
|
258
|
+
private parent = parent // 👈 Reference to parent
|
|
259
|
+
) {}
|
|
260
|
+
|
|
261
|
+
resolve(token) {
|
|
262
|
+
const local = this.resolveLocal(token);
|
|
263
|
+
if (local !== undefined) return local;
|
|
264
|
+
|
|
265
|
+
// Delegate to parent
|
|
266
|
+
if (this.parent) {
|
|
267
|
+
return this.parent.resolve(token);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw new Error('Service not found');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export const child = new NeoContainer();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Best Practices
|
|
278
|
+
|
|
279
|
+
### 1. Name Your Containers
|
|
280
|
+
|
|
281
|
+
Names appear in error messages for easier debugging:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
defineBuilderConfig({
|
|
285
|
+
name: 'UserModule', // ✅ Shows in errors
|
|
286
|
+
// ...
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Error: [UserModule] Service not found: XYZ
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 2. Keep SharedKernel Minimal
|
|
293
|
+
|
|
294
|
+
Only put truly shared services in the SharedKernel:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// ✅ Good: Cross-cutting concerns
|
|
298
|
+
{ token: useInterface<ILogger>() }
|
|
299
|
+
{ token: useInterface<IEventBus>() }
|
|
300
|
+
{ token: useInterface<IDateTime>() }
|
|
301
|
+
|
|
302
|
+
// ❌ Bad: Domain-specific services
|
|
303
|
+
{ token: UserService }
|
|
304
|
+
{ token: OrderService }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 3. Use Scoped for Overrides
|
|
308
|
+
|
|
309
|
+
When you need a different implementation locally:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
const child = defineBuilderConfig({
|
|
313
|
+
useContainer: parent,
|
|
314
|
+
injections: [
|
|
315
|
+
{ token: useInterface<ILogger>(), provider: FileLogger, scoped: true }
|
|
316
|
+
]
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
See [Scoped Injections](/guide/scoped-injections) for details.
|
|
321
|
+
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Scoped Injections
|
|
2
|
+
|
|
3
|
+
Override parent container tokens with local resolution using `scoped: true`.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
When you use a parent container with `useContainer`, you might want to **override** a token for your specific module:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const parent = defineBuilderConfig({
|
|
11
|
+
injections: [
|
|
12
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger }
|
|
13
|
+
]
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// ❌ This throws an error!
|
|
17
|
+
const child = defineBuilderConfig({
|
|
18
|
+
useContainer: parent,
|
|
19
|
+
injections: [
|
|
20
|
+
{ token: useInterface<ILogger>(), provider: FileLogger } // Duplicate!
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
// Error: Duplicate registration: 'ILogger' is already registered in parent
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## The Solution: `scoped: true`
|
|
27
|
+
|
|
28
|
+
Use `scoped: true` to tell Neo-Syringe that this token should be **resolved locally** instead of delegating to the parent:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const child = defineBuilderConfig({
|
|
32
|
+
useContainer: parent,
|
|
33
|
+
injections: [
|
|
34
|
+
{
|
|
35
|
+
token: useInterface<ILogger>(),
|
|
36
|
+
provider: FileLogger,
|
|
37
|
+
scoped: true // ✅ Resolved in THIS container
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How It Works
|
|
44
|
+
|
|
45
|
+
### Without `scoped: true`
|
|
46
|
+
|
|
47
|
+
Resolution delegates to the parent:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
child.resolve(ILogger)
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
Not found locally
|
|
54
|
+
│
|
|
55
|
+
▼
|
|
56
|
+
Delegate to parent
|
|
57
|
+
│
|
|
58
|
+
▼
|
|
59
|
+
parent.resolve(ILogger)
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
Returns ConsoleLogger
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### With `scoped: true`
|
|
66
|
+
|
|
67
|
+
Resolution stays local:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
child.resolve(ILogger)
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
Found locally (scoped)
|
|
74
|
+
│
|
|
75
|
+
▼
|
|
76
|
+
Returns FileLogger ✅
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Visual Comparison
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Without scoped: true With scoped: true
|
|
83
|
+
┌─────────────────────┐ ┌─────────────────────┐
|
|
84
|
+
│ ChildContainer │ │ ChildContainer │
|
|
85
|
+
│ resolve(ILogger) │ │ resolve(ILogger) │
|
|
86
|
+
│ │ │ │ │ │
|
|
87
|
+
│ ▼ │ │ ┌────▼────┐ │
|
|
88
|
+
│ Not found locally │ │ │ LOCAL │ │
|
|
89
|
+
│ │ │ │ │FileLogger│ │
|
|
90
|
+
│ ▼ │ │ └─────────┘ │
|
|
91
|
+
│ ┌──────────────┐ │ │ ✅ Returns local │
|
|
92
|
+
│ │ Delegate to │ │ │ instance │
|
|
93
|
+
│ │ Parent │ │ └─────────────────────┘
|
|
94
|
+
│ └──────┬───────┘ │
|
|
95
|
+
│ ▼ │
|
|
96
|
+
│ ConsoleLogger │
|
|
97
|
+
│ from parent │
|
|
98
|
+
└─────────────────────┘
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Behavior Summary
|
|
102
|
+
|
|
103
|
+
| Aspect | Without `scoped` | With `scoped: true` |
|
|
104
|
+
|--------|------------------|---------------------|
|
|
105
|
+
| Token in parent | ❌ Duplicate error | ✅ Override allowed |
|
|
106
|
+
| Resolution | Delegates to parent | **Resolved locally** |
|
|
107
|
+
| Instance | Parent's instance | Container's own instance |
|
|
108
|
+
| Lifecycle | Parent's lifecycle | Can define different lifecycle |
|
|
109
|
+
|
|
110
|
+
## Use Cases
|
|
111
|
+
|
|
112
|
+
### 🧪 Testing
|
|
113
|
+
|
|
114
|
+
Override production services with mocks:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Production container
|
|
118
|
+
const production = defineBuilderConfig({
|
|
119
|
+
injections: [
|
|
120
|
+
{ token: useInterface<IDatabase>(), provider: PostgresDatabase },
|
|
121
|
+
{ token: useInterface<IEmailService>(), provider: SendGridService },
|
|
122
|
+
{ token: UserService }
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Test container - override external services
|
|
127
|
+
const testing = defineBuilderConfig({
|
|
128
|
+
useContainer: production,
|
|
129
|
+
injections: [
|
|
130
|
+
{ token: useInterface<IDatabase>(), provider: InMemoryDatabase, scoped: true },
|
|
131
|
+
{ token: useInterface<IEmailService>(), provider: MockEmailService, scoped: true }
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// UserService uses mocked dependencies
|
|
136
|
+
const userService = testing.resolve(UserService);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 🔧 Module Isolation
|
|
140
|
+
|
|
141
|
+
Each module has its own instance of a shared token:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const sharedKernel = defineBuilderConfig({
|
|
145
|
+
injections: [
|
|
146
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger }
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// User module - wants file logging
|
|
151
|
+
const userModule = defineBuilderConfig({
|
|
152
|
+
useContainer: sharedKernel,
|
|
153
|
+
injections: [
|
|
154
|
+
{ token: useInterface<ILogger>(), provider: FileLogger, scoped: true },
|
|
155
|
+
{ token: UserService } // Uses FileLogger
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Order module - wants console logging (from parent)
|
|
160
|
+
const orderModule = defineBuilderConfig({
|
|
161
|
+
useContainer: sharedKernel,
|
|
162
|
+
injections: [
|
|
163
|
+
{ token: OrderService } // Uses ConsoleLogger from parent
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### ⚙️ Different Lifecycle
|
|
169
|
+
|
|
170
|
+
Parent uses singleton, child uses transient:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const parent = defineBuilderConfig({
|
|
174
|
+
injections: [
|
|
175
|
+
{ token: useInterface<IRequestContext>(), provider: RequestContext, lifecycle: 'singleton' }
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Child needs new instance per request
|
|
180
|
+
const requestScoped = defineBuilderConfig({
|
|
181
|
+
useContainer: parent,
|
|
182
|
+
injections: [
|
|
183
|
+
{
|
|
184
|
+
token: useInterface<IRequestContext>(),
|
|
185
|
+
provider: RequestContext,
|
|
186
|
+
lifecycle: 'transient', // Different lifecycle!
|
|
187
|
+
scoped: true
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 🔒 Encapsulation
|
|
194
|
+
|
|
195
|
+
Keep a local version without affecting other consumers:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const shared = defineBuilderConfig({
|
|
199
|
+
injections: [
|
|
200
|
+
{ token: useInterface<ICache>(), provider: RedisCache }
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// This module needs its own cache
|
|
205
|
+
const isolatedModule = defineBuilderConfig({
|
|
206
|
+
useContainer: shared,
|
|
207
|
+
injections: [
|
|
208
|
+
{
|
|
209
|
+
token: useInterface<ICache>(),
|
|
210
|
+
provider: MemoryCache, // Local cache only
|
|
211
|
+
scoped: true
|
|
212
|
+
},
|
|
213
|
+
{ token: SensitiveService }
|
|
214
|
+
]
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Other modules still use RedisCache
|
|
218
|
+
const otherModule = defineBuilderConfig({
|
|
219
|
+
useContainer: shared,
|
|
220
|
+
injections: [
|
|
221
|
+
{ token: OtherService } // Uses RedisCache
|
|
222
|
+
]
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Multi-Level Hierarchy
|
|
227
|
+
|
|
228
|
+
`scoped: true` works at any level:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Level 1: Infrastructure
|
|
232
|
+
const infrastructure = defineBuilderConfig({
|
|
233
|
+
injections: [
|
|
234
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
235
|
+
{ token: useInterface<IDatabase>(), provider: PostgresDatabase }
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Level 2: Domain (inherits all)
|
|
240
|
+
const domain = defineBuilderConfig({
|
|
241
|
+
useContainer: infrastructure,
|
|
242
|
+
injections: [
|
|
243
|
+
{ token: UserRepository }
|
|
244
|
+
]
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Level 3: Test (overrides only ILogger)
|
|
248
|
+
const test = defineBuilderConfig({
|
|
249
|
+
useContainer: domain,
|
|
250
|
+
injections: [
|
|
251
|
+
{ token: useInterface<ILogger>(), provider: MockLogger, scoped: true }
|
|
252
|
+
// IDatabase still comes from infrastructure
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test.resolve(useInterface<ILogger>()); // MockLogger (scoped)
|
|
257
|
+
test.resolve(useInterface<IDatabase>()); // PostgresDatabase (from infrastructure)
|
|
258
|
+
test.resolve(UserRepository); // From domain
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Error Messages
|
|
262
|
+
|
|
263
|
+
When you forget `scoped: true`:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
Error: Duplicate registration: 'ILogger' is already registered in the parent container.
|
|
267
|
+
Use 'scoped: true' to override the parent's registration intentionally.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The error message tells you exactly what to do! ✅
|
|
271
|
+
|