@djodjonx/neo-syringe 1.1.5 → 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.
Files changed (61) hide show
  1. package/.github/workflows/ci.yml +6 -5
  2. package/.github/workflows/docs.yml +59 -0
  3. package/CHANGELOG.md +27 -0
  4. package/README.md +74 -740
  5. package/dist/{GraphValidator-G0F4QiLk.cjs → GraphValidator-CV4VoJl0.cjs} +18 -10
  6. package/dist/{GraphValidator-C8ldJtNp.mjs → GraphValidator-DXqqkNdS.mjs} +18 -10
  7. package/dist/cli/index.cjs +16 -1
  8. package/dist/cli/index.mjs +16 -1
  9. package/dist/index.d.cts +31 -5
  10. package/dist/index.d.mts +31 -5
  11. package/dist/lsp/index.cjs +1 -1
  12. package/dist/lsp/index.mjs +1 -1
  13. package/dist/unplugin/index.cjs +33 -9
  14. package/dist/unplugin/index.d.cts +7 -5
  15. package/dist/unplugin/index.d.mts +7 -5
  16. package/dist/unplugin/index.mjs +33 -9
  17. package/docs/.vitepress/config.ts +109 -0
  18. package/docs/.vitepress/theme/custom.css +150 -0
  19. package/docs/.vitepress/theme/index.ts +17 -0
  20. package/docs/api/configuration.md +274 -0
  21. package/docs/api/functions.md +291 -0
  22. package/docs/api/types.md +158 -0
  23. package/docs/guide/basic-usage.md +267 -0
  24. package/docs/guide/cli.md +174 -0
  25. package/docs/guide/generated-code.md +284 -0
  26. package/docs/guide/getting-started.md +171 -0
  27. package/docs/guide/ide-plugin.md +203 -0
  28. package/docs/guide/injection-types.md +287 -0
  29. package/docs/guide/legacy-migration.md +333 -0
  30. package/docs/guide/lifecycle.md +223 -0
  31. package/docs/guide/parent-container.md +321 -0
  32. package/docs/guide/scoped-injections.md +271 -0
  33. package/docs/guide/what-is-neo-syringe.md +162 -0
  34. package/docs/guide/why-neo-syringe.md +219 -0
  35. package/docs/index.md +138 -0
  36. package/docs/public/logo.png +0 -0
  37. package/package.json +15 -12
  38. package/src/analyzer/Analyzer.ts +20 -10
  39. package/src/analyzer/types.ts +55 -49
  40. package/src/cli/index.ts +15 -0
  41. package/src/generator/Generator.ts +24 -2
  42. package/src/generator/GraphValidator.ts +6 -2
  43. package/src/types.ts +30 -4
  44. package/src/unplugin/index.ts +13 -41
  45. package/tests/analyzer/Analyzer.test.ts +4 -4
  46. package/tests/analyzer/AnalyzerDeclarative.test.ts +1 -1
  47. package/tests/analyzer/Factory.test.ts +2 -2
  48. package/tests/analyzer/Scoped.test.ts +434 -0
  49. package/tests/cli/cli.test.ts +91 -0
  50. package/tests/e2e/container-integration.test.ts +21 -21
  51. package/tests/e2e/generated-code.test.ts +7 -7
  52. package/tests/e2e/scoped.test.ts +370 -0
  53. package/tests/e2e/snapshots.test.ts +2 -2
  54. package/tests/e2e/standalone.test.ts +2 -2
  55. package/tests/generator/ExternalGenerator.test.ts +1 -1
  56. package/tests/generator/FactoryGenerator.test.ts +6 -6
  57. package/tests/generator/Generator.test.ts +2 -2
  58. package/tests/generator/GeneratorDeclarative.test.ts +1 -1
  59. package/tests/generator/GraphValidator.test.ts +1 -1
  60. package/tsconfig.json +2 -1
  61. 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
+