@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
package/README.md CHANGED
@@ -4,781 +4,143 @@
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/@djodjonx/neo-syringe"><img src="https://img.shields.io/npm/v/@djodjonx/neo-syringe.svg?style=flat-square" alt="npm version"></a>
7
- <a href="https://github.com/djodjonx/neo-syringe/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/djodjonx/neo-syringe/ci.yml?style=flat-square" alt="CI"></a>
7
+ <a href="https://github.com/djodjonx/neo-syringe/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/djodjonx/neo-syringe/ci.yml?style=flat-square&label=tests" alt="Tests"></a>
8
8
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.0+-blue.svg?style=flat-square" alt="TypeScript"></a>
9
9
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT"></a>
10
+ <a href="https://djodjonx.github.io/neo-syringe/"><img src="https://img.shields.io/badge/docs-VitePress-0d9488.svg?style=flat-square" alt="Documentation"></a>
10
11
  </p>
11
12
 
12
13
  <h1 align="center">Zero-Overhead, Compile-Time Dependency Injection</h1>
13
14
 
14
15
  <p align="center">
15
- <strong>Neo-Syringe</strong> is a next-generation DI system that shifts resolution from <strong>Runtime</strong> to <strong>Build-Time</strong>. It eliminates reflection and metadata in favor of generated, optimized JavaScript factories.
16
+ <strong>Neo-Syringe</strong> shifts DI resolution from <strong>Runtime</strong> to <strong>Build-Time</strong>.<br>
17
+ No reflection, no decorators, just pure TypeScript.
16
18
  </p>
17
19
 
18
- ---
19
-
20
- `neo-syringe` allows you to:
21
-
22
- - ✨ **Use Interfaces as Tokens**: Native support for `useInterface<ILogger>()` without manual Symbols
23
- - 🚀 **Zero Runtime Overhead**: No reflection, no `reflect-metadata`, just pure factory functions
24
- - 🛡️ **Compile-Time Safety**: Detect circular dependencies, missing bindings, and type mismatches instantly
25
- - 🔄 **Migrate Gradually**: Use `useContainer` to bridge existing containers (like `tsyringe`)
26
- - 🤖 **Validate in CI**: Standalone CLI to verify your dependency graph before deployment
27
-
28
- ## 📚 Documentation
29
-
30
- - **[Quick Start](#quick-start)** - Wire your first application in 5 minutes
31
- - **[Injection Types](#injection-types)** - All ways to define dependencies
32
- - **[Generated Code](#generated-code-example)** - See what the compiler produces
33
- - **[Parent Container](#parent-container-usecontainer)** - SharedKernel and modular architecture
34
- - **[Legacy Migration](#legacy-migration-usecontainer-1)** - Bridge existing containers (tsyringe, etc.)
35
- - **[CLI Validator](#cli-validator)** - Ensure graph integrity in CI/CD
36
- - **[IDE Support](#ide-plugin-for-real-time-validation)** - Get real-time errors in VS Code
37
-
38
- ## Why Neo-Syringe?
39
-
40
- Traditional containers (InversifyJS, tsyringe) or modern wrappers (WireDI) rely on **Runtime Resolution**. This means:
41
-
42
- - ❌ You ship the DI container logic to the browser
43
- - ❌ Errors (missing bindings) happen at runtime
44
- - ❌ Interfaces are erased, requiring manual Symbols
45
-
46
- **Neo-Syringe is different.** It works as a **Compiler Plugin**:
47
-
48
- ### 1. Code Generation
49
- Neo-Syringe analyzes your configuration and generates a specialized TypeScript class with hardcoded `new Service(new Dep())` calls. **Zero runtime resolution overhead**.
20
+ <p align="center">
21
+ <a href="https://djodjonx.github.io/neo-syringe/"><strong>📚 Read the Documentation →</strong></a>
22
+ </p>
50
23
 
51
- ### 2. Interface Support
52
- It automatically generates unique IDs for interfaces at build time. Write `useInterface<ILogger>()` instead of managing Symbols manually.
24
+ <p align="center">
25
+ <a href="https://djodjonx.github.io/neo-syringe/guide/getting-started">Getting Started</a>
26
+ <a href="https://djodjonx.github.io/neo-syringe/guide/why-neo-syringe">Why Neo-Syringe?</a> •
27
+ <a href="https://djodjonx.github.io/neo-syringe/api/types">API Reference</a>
28
+ </p>
53
29
 
54
- ### 3. Pure Classes
55
- Your business classes stay **100% pure** - no decorators, no DI imports, no framework coupling.
30
+ ---
56
31
 
57
- ### 4. Real-Time Validation
58
- Errors are detected **in your IDE** instantly:
32
+ ## Features
59
33
 
60
- ```typescript
61
- // Error detected in IDE: "ILogger" has no registered provider
62
- const config = defineBuilderConfig({
63
- injections: [
64
- { token: UserService }, // UserService depends on ILogger
65
- ],
66
- })
67
- ```
34
+ - **Use Interfaces as Tokens** - `useInterface<ILogger>()` without manual Symbols
35
+ - **Zero Runtime Overhead** - Generated factory functions, no DI library shipped
36
+ - **Compile-Time Safety** - Errors detected in your IDE, not at runtime
37
+ - **Pure Classes** - No decorators, no DI imports in your business code
38
+ - **Gradual Migration** - Bridge existing containers (tsyringe, InversifyJS)
39
+ - **CI Validation** - CLI to verify your dependency graph
68
40
 
69
- ## Installation
41
+ ## 📦 Installation
70
42
 
71
43
  ```bash
72
- # With npm
44
+ # npm
73
45
  npm install @djodjonx/neo-syringe
46
+ npm install -D unplugin
74
47
 
75
- # With pnpm
48
+ # pnpm
76
49
  pnpm add @djodjonx/neo-syringe
77
- ```
78
-
79
- > **Note**: `typescript` (>=5.0.0) is a required peer dependency.
80
-
81
- ### Build System Integration (Recommended)
82
-
83
- For compile-time code generation, install `unplugin`:
84
-
85
- ```bash
86
50
  pnpm add -D unplugin
87
51
  ```
88
52
 
89
- <details>
90
- <summary><strong>Vite</strong></summary>
91
-
92
- ```typescript
93
- // vite.config.ts
94
- import { defineConfig } from 'vite';
95
- import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
96
-
97
- export default defineConfig({
98
- plugins: [neoSyringePlugin.vite()]
99
- });
100
- ```
101
-
102
- </details>
103
-
104
- <details>
105
- <summary><strong>Rollup</strong></summary>
53
+ ## 🚀 Quick Example
106
54
 
107
55
  ```typescript
108
- // rollup.config.js
109
- import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
110
-
111
- export default {
112
- plugins: [neoSyringePlugin.rollup()]
113
- };
114
- ```
115
-
116
- </details>
117
-
118
- <details>
119
- <summary><strong>Webpack</strong></summary>
120
-
121
- ```typescript
122
- // webpack.config.js
123
- module.exports = {
124
- plugins: [require('@djodjonx/neo-syringe/plugin').webpack()]
125
- };
126
- ```
127
-
128
- </details>
129
-
130
- ### Dev Mode & HMR Support
131
-
132
- ✅ **The plugin works in dev mode** (Vite, Rollup watch, Webpack dev server).
133
-
134
- The `transform` hook is called on every file change, so your container is regenerated instantly during development with full Hot Module Replacement (HMR) support.
135
-
136
- > ⚠️ **Best Practice**: Put your container configuration in a **dedicated file** (e.g., `container.ts`). The plugin replaces the entire file content with generated code, so mixing container config with other exports may cause issues.
137
-
138
- ```
139
- src/
140
- ├── container.ts # ✅ Dedicated file for defineBuilderConfig
141
- ├── services/
142
- │ ├── logger.ts
143
- │ └── user.service.ts
144
- └── main.ts
145
- ```
146
-
147
- ## Quick Start
148
-
149
- ### 1. Define Services (Pure TypeScript)
150
-
151
- No decorators required. Just plain classes and interfaces.
152
-
153
- ```typescript
154
- // logger.ts
155
- export interface ILogger {
56
+ // Pure TypeScript - no decorators!
57
+ interface ILogger {
156
58
  log(msg: string): void;
157
59
  }
158
60
 
159
- export class ConsoleLogger implements ILogger {
61
+ class ConsoleLogger implements ILogger {
160
62
  log(msg: string) { console.log(msg); }
161
63
  }
162
64
 
163
- // user.service.ts
164
- export class UserService {
165
- constructor(private logger: ILogger) {} // Dependency is an Interface!
65
+ class UserService {
66
+ constructor(private logger: ILogger) {}
166
67
  }
167
68
  ```
168
69
 
169
- ### 2. Configure the Container
170
-
171
- Use `defineBuilderConfig` to wire your graph.
172
-
173
70
  ```typescript
174
- // config.ts
71
+ // container.ts
175
72
  import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
176
- import { ILogger, ConsoleLogger } from './logger';
177
- import { UserService } from './user.service';
178
-
179
- export const appConfig = defineBuilderConfig({
180
- name: 'AppModule',
181
- injections: [
182
- // Bind Interface -> Implementation
183
- { token: useInterface<ILogger>(), provider: ConsoleLogger },
184
-
185
- // Autowire Class
186
- { token: UserService }
187
- ]
188
- });
189
- ```
190
-
191
- ### 3. Use the Container
192
-
193
- The compiler plugin replaces the configuration with a generated container class.
194
-
195
- ```typescript
196
- // main.ts
197
- import { appConfig } from './config';
198
-
199
- const container = appConfig; // This IS the container at runtime!
200
- const userService = container.resolve(UserService);
201
- ```
202
-
203
- ---
204
-
205
- ## Injection Types
206
-
207
- ### Class Token (Autowire)
208
-
209
- ```typescript
210
- { token: UserService }
211
- ```
212
-
213
- ### Interface Token
214
-
215
- ```typescript
216
- { token: useInterface<ILogger>(), provider: ConsoleLogger }
217
- ```
218
-
219
- ### Explicit Provider
220
-
221
- ```typescript
222
- { token: UserService, provider: MockUserService }
223
- ```
224
-
225
- ### Factory Provider
226
-
227
- Use factory functions for dynamic instantiation or container access.
228
-
229
- ```typescript
230
- // Auto-detected: arrow functions are treated as factories
231
- {
232
- token: useInterface<IConfig>(),
233
- provider: (container) => ({
234
- apiUrl: process.env.API_URL ?? 'http://localhost',
235
- timeout: 5000
236
- })
237
- }
238
-
239
- // Explicit factory flag
240
- {
241
- token: useInterface<IDatabase>(),
242
- provider: createDatabaseConnection,
243
- useFactory: true
244
- }
245
-
246
- // Factory with dependencies
247
- {
248
- token: useInterface<IService>(),
249
- provider: (container) => {
250
- const logger = container.resolve(useInterface<ILogger>());
251
- return new MyService(logger);
252
- }
253
- }
254
- ```
255
-
256
- ### Primitive Values with `useProperty`
257
-
258
- Inject primitives (string, number, boolean) while keeping classes **pure**.
259
-
260
- ```typescript
261
- import { defineBuilderConfig, useProperty } from '@djodjonx/neo-syringe';
262
-
263
- // Pure class - no DI imports!
264
- class ApiService {
265
- constructor(
266
- private apiUrl: string,
267
- private maxRetries: number
268
- ) {}
269
- }
270
-
271
- // Define property tokens
272
- const apiUrl = useProperty<string>(ApiService, 'apiUrl');
273
- const maxRetries = useProperty<number>(ApiService, 'maxRetries');
274
-
275
- export const config = defineBuilderConfig({
276
- injections: [
277
- { token: apiUrl, provider: () => process.env.API_URL ?? 'http://localhost' },
278
- { token: maxRetries, provider: () => 5 },
279
- { token: ApiService } // Auto-wires primitives!
280
- ]
281
- });
282
- ```
283
-
284
- **Benefits:**
285
- - ✅ No collision: `useProperty(ApiService, 'url')` ≠ `useProperty(AuthService, 'url')`
286
- - ✅ LSP validation: Detects if parameter doesn't exist
287
- - ✅ Refactoring-friendly: Rename parameter → LSP error
288
-
289
- ### Scopes
290
-
291
- ```typescript
292
- { token: UserService, scope: 'transient' } // Default is 'singleton'
293
-
294
- // Factories support scopes too
295
- {
296
- token: useInterface<IRequest>(),
297
- provider: () => ({ id: crypto.randomUUID() }),
298
- scope: 'transient'
299
- }
300
- ```
301
-
302
- ---
303
-
304
- ## Generated Code Example
305
-
306
- To understand how Neo-Syringe works, here's what your code looks like **before** and **after** compilation.
307
-
308
- ### Before (Your Configuration)
309
-
310
- ```typescript
311
- // config.ts
312
- import { defineBuilderConfig, useInterface, useProperty } from '@djodjonx/neo-syringe';
313
-
314
- interface ILogger { log(msg: string): void; }
315
- class ConsoleLogger implements ILogger {
316
- log(msg: string) { console.log(msg); }
317
- }
318
-
319
- class ApiService {
320
- constructor(
321
- private logger: ILogger,
322
- private apiUrl: string
323
- ) {}
324
- }
325
-
326
- const apiUrl = useProperty<string>(ApiService, 'apiUrl');
327
73
 
328
74
  export const container = defineBuilderConfig({
329
- name: 'AppContainer',
330
75
  injections: [
331
76
  { token: useInterface<ILogger>(), provider: ConsoleLogger },
332
- { token: apiUrl, provider: () => process.env.API_URL ?? 'http://localhost' },
333
- { token: ApiService }
334
- ]
335
- });
336
- ```
337
-
338
- ### After (Generated Code)
339
-
340
- The build plugin replaces `defineBuilderConfig(...)` with an optimized container class:
341
-
342
- ```typescript
343
- // config.ts (after build)
344
- import * as Import_0 from './services';
345
-
346
- // -- Factories (generated) --
347
- function create_ILogger(container: NeoContainer) {
348
- return new Import_0.ConsoleLogger();
349
- }
350
-
351
- function create_ApiService_apiUrl(container: NeoContainer) {
352
- const userFactory = () => process.env.API_URL ?? 'http://localhost';
353
- return userFactory(container);
354
- }
355
-
356
- function create_ApiService(container: NeoContainer) {
357
- return new Import_0.ApiService(
358
- container.resolve("ILogger"),
359
- container.resolve("PropertyToken:ApiService.apiUrl")
360
- );
361
- }
362
-
363
- // -- Container (generated) --
364
- export class NeoContainer {
365
- private instances = new Map<any, any>();
366
-
367
- constructor(
368
- private parent?: any,
369
- private legacy?: any[],
370
- private name: string = 'AppContainer'
371
- ) {}
372
-
373
- public resolve(token: any): any {
374
- const result = this.resolveLocal(token);
375
- if (result !== undefined) return result;
376
-
377
- if (this.parent) {
378
- try { return this.parent.resolve(token); }
379
- catch (e) { /* fallback */ }
380
- }
381
-
382
- throw new Error(`[${this.name}] Service not found: ${token}`);
383
- }
384
-
385
- private resolveLocal(token: any): any {
386
- // Interface token (string)
387
- if (token === "ILogger") {
388
- if (!this.instances.has("ILogger")) {
389
- this.instances.set("ILogger", create_ILogger(this));
390
- }
391
- return this.instances.get("ILogger");
392
- }
393
-
394
- // PropertyToken (string)
395
- if (token === "PropertyToken:ApiService.apiUrl") {
396
- if (!this.instances.has("PropertyToken:ApiService.apiUrl")) {
397
- this.instances.set("PropertyToken:ApiService.apiUrl", create_ApiService_apiUrl(this));
398
- }
399
- return this.instances.get("PropertyToken:ApiService.apiUrl");
400
- }
401
-
402
- // Class token (reference)
403
- if (token === Import_0.ApiService) {
404
- if (!this.instances.has(Import_0.ApiService)) {
405
- this.instances.set(Import_0.ApiService, create_ApiService(this));
406
- }
407
- return this.instances.get(Import_0.ApiService);
408
- }
409
-
410
- return undefined;
411
- }
412
- }
413
-
414
- export const container = new NeoContainer();
415
- ```
416
-
417
- ### Key Observations
418
-
419
- | Aspect | Before | After |
420
- |--------|--------|-------|
421
- | **Resolution** | Runtime lookup | Direct `new` calls |
422
- | **Interfaces** | Type-erased | String IDs generated |
423
- | **Dependencies** | Analyzed at runtime | Hardcoded in factories |
424
- | **Container size** | Full DI library | ~50 lines of code |
425
- | **Performance** | Map lookups + reflection | Direct instantiation |
426
-
427
- > 💡 **The generated code has zero dependencies** - no DI library shipped to production!
428
-
429
- ---
430
-
431
- ## Advanced Features
432
-
433
- ### Parent Container (`useContainer`)
434
-
435
- Neo-Syringe supports **hierarchical containers**. A child container can delegate resolution to a parent container for tokens it doesn't define locally.
436
-
437
- #### Example: SharedKernel Architecture
438
-
439
- Perfect for modular applications where core services are shared across bounded contexts.
440
-
441
- ```typescript
442
- // shared-kernel/container.ts
443
- import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
444
-
445
- // Shared interfaces
446
- export interface ILogger { log(msg: string): void; }
447
- export interface IEventBus { publish(event: any): void; }
448
-
449
- class ConsoleLogger implements ILogger {
450
- log(msg: string) { console.log(`[LOG] ${msg}`); }
451
- }
452
-
453
- class InMemoryEventBus implements IEventBus {
454
- publish(event: any) { console.log('Event:', event); }
455
- }
456
-
457
- // SharedKernel container - reusable across modules
458
- export const sharedKernel = defineBuilderConfig({
459
- name: 'SharedKernel',
460
- injections: [
461
- { token: useInterface<ILogger>(), provider: ConsoleLogger },
462
- { token: useInterface<IEventBus>(), provider: InMemoryEventBus }
463
- ]
464
- });
465
- ```
466
-
467
- ```typescript
468
- // user-module/container.ts
469
- import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
470
- import { sharedKernel, ILogger, IEventBus } from '../shared-kernel/container';
471
-
472
- // User module services
473
- class UserRepository {
474
- findById(id: string) { return { id, name: 'John' }; }
475
- }
476
-
477
- class UserService {
478
- constructor(
479
- private logger: ILogger, // From SharedKernel!
480
- private eventBus: IEventBus, // From SharedKernel!
481
- private repo: UserRepository // Local to this module
482
- ) {}
483
- }
484
-
485
- export const userModule = defineBuilderConfig({
486
- name: 'UserModule',
487
- useContainer: sharedKernel, // 👈 Inherit from SharedKernel
488
- injections: [
489
- { token: UserRepository },
490
77
  { token: UserService }
491
78
  ]
492
79
  });
493
- ```
494
-
495
- ```typescript
496
- // order-module/container.ts
497
- import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
498
- import { sharedKernel, ILogger } from '../shared-kernel/container';
499
-
500
- class OrderService {
501
- constructor(private logger: ILogger) {} // From SharedKernel!
502
- }
503
-
504
- export const orderModule = defineBuilderConfig({
505
- name: 'OrderModule',
506
- useContainer: sharedKernel, // 👈 Same SharedKernel, different module
507
- injections: [
508
- { token: OrderService }
509
- ]
510
- });
511
- ```
512
-
513
- ```typescript
514
- // main.ts
515
- import { userModule } from './user-module/container';
516
- import { orderModule } from './order-module/container';
517
-
518
- // Both modules share the same ILogger and IEventBus instances!
519
- const userService = userModule.resolve(UserService);
520
- const orderService = orderModule.resolve(OrderService);
521
- ```
522
-
523
- #### Multi-Level Hierarchy
524
-
525
- You can chain containers for complex architectures:
526
-
527
- ```typescript
528
- // Level 1: Infrastructure
529
- const infrastructure = defineBuilderConfig({
530
- name: 'Infrastructure',
531
- injections: [
532
- { token: useInterface<ILogger>(), provider: ConsoleLogger },
533
- { token: useInterface<IDatabase>(), provider: PostgresDatabase }
534
- ]
535
- });
536
-
537
- // Level 2: Domain (inherits Infrastructure)
538
- const domain = defineBuilderConfig({
539
- name: 'Domain',
540
- useContainer: infrastructure,
541
- injections: [
542
- { token: UserRepository },
543
- { token: OrderRepository }
544
- ]
545
- });
546
-
547
- // Level 3: Application (inherits Domain + Infrastructure)
548
- const application = defineBuilderConfig({
549
- name: 'Application',
550
- useContainer: domain, // Gets Domain AND Infrastructure!
551
- injections: [
552
- { token: UserService },
553
- { token: OrderService }
554
- ]
555
- });
556
- ```
557
-
558
- ---
559
-
560
- ### Legacy Migration (`useContainer`)
561
-
562
- The same `useContainer` mechanism works with legacy DI containers (tsyringe, InversifyJS, Awilix). Neo-Syringe can delegate resolution to any container that has a `resolve()` method.
563
-
564
- #### Example with tsyringe
565
-
566
- ```typescript
567
- // legacy-container.ts (existing tsyringe setup)
568
- import 'reflect-metadata';
569
- import { container, injectable } from 'tsyringe';
570
-
571
- @injectable()
572
- export class AuthService {
573
- validateToken(token: string) { return true; }
574
- }
575
-
576
- @injectable()
577
- export class LegacyUserRepository {
578
- findById(id: string) { return { id, name: 'John' }; }
579
- }
580
-
581
- // Register in tsyringe
582
- container.registerSingleton(AuthService);
583
- container.registerSingleton(LegacyUserRepository);
584
-
585
- export { container as legacyContainer };
586
- ```
587
-
588
- ```typescript
589
- // container.ts (neo-syringe bridging to tsyringe)
590
- import { defineBuilderConfig, declareContainerTokens, useInterface } from '@djodjonx/neo-syringe';
591
- import { legacyContainer, AuthService, LegacyUserRepository } from './legacy-container';
592
-
593
- // Declare what the legacy container provides (for type-safety)
594
- const legacy = declareContainerTokens<{
595
- AuthService: AuthService;
596
- LegacyUserRepository: LegacyUserRepository;
597
- }>(legacyContainer);
598
-
599
- // New services using Neo-Syringe
600
- interface ILogger { log(msg: string): void; }
601
- class ConsoleLogger implements ILogger {
602
- log(msg: string) { console.log(msg); }
603
- }
604
-
605
- class UserService {
606
- constructor(
607
- private auth: AuthService, // From legacy container!
608
- private repo: LegacyUserRepository, // From legacy container!
609
- private logger: ILogger // From neo-syringe
610
- ) {}
611
- }
612
-
613
- export const appContainer = defineBuilderConfig({
614
- name: 'AppContainer',
615
- useContainer: legacy, // 👈 Bridge to legacy container
616
- injections: [
617
- { token: useInterface<ILogger>(), provider: ConsoleLogger },
618
- { token: UserService } // Dependencies resolved from both containers!
619
- ]
620
- });
621
- ```
622
-
623
- ```typescript
624
- // main.ts
625
- import { appContainer } from './container';
626
80
 
627
- const userService = appContainer.resolve(UserService);
628
- // AuthService and LegacyUserRepository come from tsyringe
629
- // ✅ ILogger comes from neo-syringe
81
+ // Use it
82
+ const userService = container.resolve(UserService);
630
83
  ```
631
84
 
632
- #### Example with InversifyJS
85
+ At build time, this generates optimized factory functions. **Zero DI library shipped to production!**
633
86
 
634
- ```typescript
635
- // legacy-inversify.ts
636
- import 'reflect-metadata';
637
- import { Container, injectable } from 'inversify';
87
+ ## 📖 Documentation
638
88
 
639
- @injectable()
640
- class DatabaseConnection {
641
- query(sql: string) { return []; }
642
- }
89
+ For complete documentation, visit **[djodjonx.github.io/neo-syringe](https://djodjonx.github.io/neo-syringe/)**
643
90
 
644
- const inversifyContainer = new Container();
645
- inversifyContainer.bind(DatabaseConnection).toSelf().inSingletonScope();
91
+ | Topic | Description |
92
+ |-------|-------------|
93
+ | [Getting Started](https://djodjonx.github.io/neo-syringe/guide/getting-started) | Installation and first container |
94
+ | [Why Neo-Syringe?](https://djodjonx.github.io/neo-syringe/guide/why-neo-syringe) | Comparison with traditional DI |
95
+ | [Injection Types](https://djodjonx.github.io/neo-syringe/guide/injection-types) | Classes, interfaces, factories, primitives |
96
+ | [Lifecycle](https://djodjonx.github.io/neo-syringe/guide/lifecycle) | Singleton vs transient |
97
+ | [Scoped Injections](https://djodjonx.github.io/neo-syringe/guide/scoped-injections) | Override parent container tokens |
98
+ | [Parent Container](https://djodjonx.github.io/neo-syringe/guide/parent-container) | SharedKernel architecture |
99
+ | [Legacy Migration](https://djodjonx.github.io/neo-syringe/guide/legacy-migration) | Bridge tsyringe, InversifyJS |
100
+ | [Generated Code](https://djodjonx.github.io/neo-syringe/guide/generated-code) | What the compiler produces |
101
+ | [CLI Validator](https://djodjonx.github.io/neo-syringe/guide/cli) | Validate in CI/CD |
102
+ | [IDE Plugin](https://djodjonx.github.io/neo-syringe/guide/ide-plugin) | Real-time error detection |
103
+ | [API Reference](https://djodjonx.github.io/neo-syringe/api/types) | Types and functions |
104
+
105
+ ## 🔧 Build Plugin Setup
646
106
 
647
- export { inversifyContainer, DatabaseConnection };
648
- ```
107
+ <details>
108
+ <summary><strong>Vite</strong></summary>
649
109
 
650
110
  ```typescript
651
- // container.ts
652
- import { defineBuilderConfig, declareContainerTokens } from '@djodjonx/neo-syringe';
653
- import { inversifyContainer, DatabaseConnection } from './legacy-inversify';
654
-
655
- const legacy = declareContainerTokens<{
656
- DatabaseConnection: DatabaseConnection;
657
- }>(inversifyContainer);
658
-
659
- class ReportService {
660
- constructor(private db: DatabaseConnection) {} // From Inversify!
661
- }
111
+ import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
662
112
 
663
- export const container = defineBuilderConfig({
664
- useContainer: legacy,
665
- injections: [
666
- { token: ReportService }
667
- ]
113
+ export default defineConfig({
114
+ plugins: [neoSyringePlugin.vite()]
668
115
  });
669
116
  ```
117
+ </details>
670
118
 
671
- #### How Resolution Works
672
-
673
- When `container.resolve(Token)` is called:
674
-
675
- ```
676
- 1. Neo-Syringe checks its own registrations
677
- └── Found? → Return instance
678
- └── Not found? ↓
679
-
680
- 2. Delegate to parent container (useContainer)
681
- └── Found? → Return instance
682
- └── Not found? ↓
683
-
684
- 3. Throw "Service not found" error
685
- ```
686
-
687
- > 💡 **Migration Strategy**: Start by bridging your entire legacy container, then gradually move services to Neo-Syringe. Once all services are migrated, remove `useContainer`.
688
-
689
- #### How Legacy Bridging Works Internally
690
-
691
- **At Compile-Time:**
692
-
693
- 1. `declareContainerTokens<T>()` is analyzed
694
- 2. The type `T` properties are extracted (e.g., `{ AuthService, UserRepo }`)
695
- 3. These tokens are added to `parentProvidedTokens`
696
- 4. The GraphValidator accepts these as valid dependencies
697
- 5. The Generator outputs: `new NeoContainer(undefined, [legacyContainer], 'MyApp')`
698
-
699
- **At Runtime:**
700
-
701
- ```typescript
702
- // Generated code (simplified)
703
- class NeoContainer {
704
- constructor(
705
- private parent?: any,
706
- private legacy?: any[], // ← Your tsyringe/inversify container
707
- private name?: string
708
- ) {}
709
-
710
- resolve(token: any): any {
711
- // 1. Try local resolution
712
- const local = this.resolveLocal(token);
713
- if (local !== undefined) return local;
714
-
715
- // 2. Delegate to legacy containers
716
- if (this.legacy) {
717
- for (const container of this.legacy) {
718
- try {
719
- return container.resolve(token); // ← Calls tsyringe.resolve()!
720
- } catch (e) { /* try next */ }
721
- }
722
- }
723
-
724
- throw new Error(`Service not found: ${token}`);
725
- }
726
- }
727
- ```
728
-
729
- **Validation Features:**
730
-
731
- | Check | Description |
732
- |-------|-------------|
733
- | ✅ Missing binding | Error if dependency not in local OR legacy container |
734
- | ✅ Duplicate detection | Error if token already registered in parent/legacy |
735
- | ✅ Type safety | `declareContainerTokens<T>()` provides TypeScript types |
736
-
737
- ### Partials (Modular Config)
738
-
739
- Split configuration into reusable blocks.
119
+ <details>
120
+ <summary><strong>Rollup</strong></summary>
740
121
 
741
122
  ```typescript
742
- // logging.partial.ts
743
- export const loggingConfig = definePartialConfig({
744
- injections: [
745
- { token: useInterface<ILogger>(), provider: ConsoleLogger }
746
- ]
747
- });
123
+ import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
748
124
 
749
- // app.config.ts
750
- export const appConfig = defineBuilderConfig({
751
- extends: [loggingConfig], // Inherit injections
752
- injections: [
753
- { token: UserService }
754
- ]
755
- });
125
+ export default {
126
+ plugins: [neoSyringePlugin.rollup()]
127
+ };
756
128
  ```
129
+ </details>
757
130
 
758
- ### CLI Validator
759
-
760
- Validate your dependency graph in CI/CD pipelines.
761
-
762
- ```bash
763
- npx neo-syringe
764
- ```
131
+ <details>
132
+ <summary><strong>Webpack</strong></summary>
765
133
 
766
- **Output:**
767
- ```
768
- Analyzing project: /path/to/tsconfig.json
769
- 🔍 Extracting dependency graph...
770
- Found 45 services.
771
- 🛡️ Validating graph...
772
- ✅ Validation passed! No circular dependencies or missing bindings found.
134
+ ```javascript
135
+ module.exports = {
136
+ plugins: [require('@djodjonx/neo-syringe/plugin').webpack()]
137
+ };
773
138
  ```
139
+ </details>
774
140
 
775
- ---
776
-
777
- ## IDE Plugin for Real-Time Validation
778
-
779
- Get immediate feedback on configuration errors.
141
+ ## 🛡️ IDE Support
780
142
 
781
- ### 1. Add the plugin to `tsconfig.json`
143
+ Add to `tsconfig.json` for real-time error detection:
782
144
 
783
145
  ```json
784
146
  {
@@ -790,35 +152,7 @@ Get immediate feedback on configuration errors.
790
152
  }
791
153
  ```
792
154
 
793
- ### 2. Use Workspace TypeScript
794
-
795
- In VS Code: `Ctrl+Shift+P` → "TypeScript: Select TypeScript Version" → "Use Workspace Version"
796
-
797
- ### Detected Errors
798
-
799
- | Error | Message |
800
- | :--- | :--- |
801
- | 🔴 **Circular Dependency** | `[Neo-Syringe] Circular dependency detected: A -> B -> A` |
802
- | 🔴 **Missing Binding** | `[Neo-Syringe] Missing binding: 'Service' depends on 'I', but no provider registered.` |
803
- | 🔴 **Duplicate** | `[Neo-Syringe] Duplicate registration: 'Service' is already registered.` |
804
-
805
- ---
806
-
807
- ## Troubleshooting
808
-
809
- ### The plugin doesn't detect errors
810
-
811
- 1. Verify TypeScript uses the workspace version
812
- 2. Restart the TypeScript server (`Cmd+Shift+P` → "TypeScript: Restart TS Server")
813
- 3. Check that `@djodjonx/neo-syringe/lsp` is in your `tsconfig.json` plugins
814
-
815
- ### Build plugin not working
816
-
817
- Ensure `unplugin` is installed and the plugin is correctly configured in your bundler.
818
-
819
- ---
820
-
821
- ## License
155
+ ## 📄 License
822
156
 
823
157
  MIT
824
158