@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,174 @@
1
+ # CLI Validator
2
+
3
+ Validate your dependency graph in CI/CD pipelines.
4
+
5
+ ## Installation
6
+
7
+ The CLI is included with the main package:
8
+
9
+ ```bash
10
+ npx neo-syringe
11
+ # or
12
+ pnpm exec neo-syringe
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Run in your project root (where `tsconfig.json` is located):
18
+
19
+ ```bash
20
+ neo-syringe
21
+ ```
22
+
23
+ ### Options
24
+
25
+ ```bash
26
+ neo-syringe [options]
27
+
28
+ Options:
29
+ -p, --project <path> Path to tsconfig.json (default: "./tsconfig.json")
30
+ -h, --help Display help
31
+ -v, --version Display version
32
+ ```
33
+
34
+ ## Output
35
+
36
+ ### Success
37
+
38
+ ```
39
+ 🔍 Analyzing project: /path/to/tsconfig.json
40
+ Found 45 services.
41
+ 🛡️ Validating graph...
42
+ ✅ Validation passed! No circular dependencies or missing bindings found.
43
+ ```
44
+
45
+ ### Circular Dependency Detected
46
+
47
+ ```
48
+ 🔍 Analyzing project: /path/to/tsconfig.json
49
+ Found 12 services.
50
+ 🛡️ Validating graph...
51
+ ❌ Validation failed!
52
+
53
+ Error: Circular dependency detected: A -> B -> C -> A
54
+ ```
55
+
56
+ ### Missing Binding
57
+
58
+ ```
59
+ 🔍 Analyzing project: /path/to/tsconfig.json
60
+ Found 8 services.
61
+ 🛡️ Validating graph...
62
+ ❌ Validation failed!
63
+
64
+ Error: Missing binding: 'UserService' depends on 'ILogger', but no provider registered.
65
+ ```
66
+
67
+ ### Duplicate Registration
68
+
69
+ ```
70
+ 🔍 Analyzing project: /path/to/tsconfig.json
71
+ Found 15 services.
72
+ 🛡️ Validating graph...
73
+ ❌ Validation failed!
74
+
75
+ Error: Duplicate registration: 'ILogger' is already registered in the parent container.
76
+ Use 'scoped: true' to override the parent's registration intentionally.
77
+ ```
78
+
79
+ ## Exit Codes
80
+
81
+ | Code | Meaning |
82
+ |------|---------|
83
+ | 0 | Validation passed |
84
+ | 1 | Validation failed |
85
+ | 2 | Configuration error |
86
+
87
+ ## CI/CD Integration
88
+
89
+ ### GitHub Actions
90
+
91
+ ```yaml
92
+ # .github/workflows/ci.yml
93
+ name: CI
94
+
95
+ on: [push, pull_request]
96
+
97
+ jobs:
98
+ validate:
99
+ runs-on: ubuntu-latest
100
+ steps:
101
+ - uses: actions/checkout@v4
102
+
103
+ - uses: pnpm/action-setup@v4
104
+
105
+ - uses: actions/setup-node@v4
106
+ with:
107
+ node-version: '20'
108
+ cache: 'pnpm'
109
+
110
+ - run: pnpm install --frozen-lockfile
111
+
112
+ - name: Validate DI Graph
113
+ run: pnpm exec neo-syringe
114
+ ```
115
+
116
+ ### GitLab CI
117
+
118
+ ```yaml
119
+ # .gitlab-ci.yml
120
+ validate:
121
+ stage: test
122
+ script:
123
+ - pnpm install
124
+ - pnpm exec neo-syringe
125
+ ```
126
+
127
+ ### npm scripts
128
+
129
+ Add to `package.json`:
130
+
131
+ ```json
132
+ {
133
+ "scripts": {
134
+ "validate": "neo-syringe",
135
+ "prebuild": "neo-syringe",
136
+ "ci": "pnpm lint && pnpm validate && pnpm test"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## Best Practices
142
+
143
+ ### Run Before Build
144
+
145
+ Validate before building to catch errors early:
146
+
147
+ ```json
148
+ {
149
+ "scripts": {
150
+ "prebuild": "neo-syringe",
151
+ "build": "vite build"
152
+ }
153
+ }
154
+ ```
155
+
156
+ ### Run in PR Checks
157
+
158
+ Add validation to your PR workflow:
159
+
160
+ ```yaml
161
+ - name: Validate Dependencies
162
+ run: pnpm exec neo-syringe
163
+ # Fails the PR if validation fails
164
+ ```
165
+
166
+ ### Use with Husky
167
+
168
+ Validate on pre-push:
169
+
170
+ ```bash
171
+ # .husky/pre-push
172
+ pnpm exec neo-syringe
173
+ ```
174
+
@@ -0,0 +1,284 @@
1
+ # Generated Code
2
+
3
+ Understand what the compiler produces.
4
+
5
+ ## Overview
6
+
7
+ Neo-Syringe transforms your configuration into optimized TypeScript at build time. This page shows what your code looks like before and after compilation.
8
+
9
+ ## Before (Your Configuration)
10
+
11
+ ```typescript
12
+ // container.ts
13
+ import { defineBuilderConfig, useInterface, useProperty } from '@djodjonx/neo-syringe';
14
+
15
+ interface ILogger {
16
+ log(msg: string): void;
17
+ }
18
+
19
+ class ConsoleLogger implements ILogger {
20
+ log(msg: string) { console.log(msg); }
21
+ }
22
+
23
+ class ApiService {
24
+ constructor(
25
+ private logger: ILogger,
26
+ private apiUrl: string
27
+ ) {}
28
+ }
29
+
30
+ const apiUrl = useProperty<string>(ApiService, 'apiUrl');
31
+
32
+ export const container = defineBuilderConfig({
33
+ name: 'AppContainer',
34
+ injections: [
35
+ { token: useInterface<ILogger>(), provider: ConsoleLogger },
36
+ { token: apiUrl, provider: () => process.env.API_URL ?? 'http://localhost' },
37
+ { token: ApiService }
38
+ ]
39
+ });
40
+ ```
41
+
42
+ ## After (Generated Code)
43
+
44
+ The build plugin replaces the entire file:
45
+
46
+ ```typescript
47
+ // container.ts (after build)
48
+ import * as Import_0 from './container';
49
+
50
+ // -- Factories --
51
+
52
+ function create_ILogger(container: NeoContainer) {
53
+ return new Import_0.ConsoleLogger();
54
+ }
55
+
56
+ function create_ApiService_apiUrl(container: NeoContainer) {
57
+ const userFactory = () => process.env.API_URL ?? 'http://localhost';
58
+ return userFactory(container);
59
+ }
60
+
61
+ function create_ApiService(container: NeoContainer) {
62
+ return new Import_0.ApiService(
63
+ container.resolve("ILogger"),
64
+ container.resolve("PropertyToken:ApiService.apiUrl")
65
+ );
66
+ }
67
+
68
+ // -- Container --
69
+
70
+ export class NeoContainer {
71
+ private instances = new Map<any, any>();
72
+
73
+ constructor(
74
+ private parent?: any,
75
+ private legacy?: any[],
76
+ private name: string = 'AppContainer'
77
+ ) {}
78
+
79
+ public resolve(token: any): any {
80
+ // 1. Try local resolution
81
+ const result = this.resolveLocal(token);
82
+ if (result !== undefined) return result;
83
+
84
+ // 2. Delegate to parent
85
+ if (this.parent) {
86
+ try {
87
+ return this.parent.resolve(token);
88
+ } catch (e) {
89
+ // Continue to legacy
90
+ }
91
+ }
92
+
93
+ // 3. Delegate to legacy containers
94
+ if (this.legacy) {
95
+ for (const legacyContainer of this.legacy) {
96
+ try {
97
+ if (legacyContainer.resolve) {
98
+ return legacyContainer.resolve(token);
99
+ }
100
+ } catch (e) {
101
+ // Try next
102
+ }
103
+ }
104
+ }
105
+
106
+ throw new Error(`[${this.name}] Service not found: ${token}`);
107
+ }
108
+
109
+ private resolveLocal(token: any): any {
110
+ // Interface token (string-based)
111
+ if (token === "ILogger") {
112
+ if (!this.instances.has("ILogger")) {
113
+ this.instances.set("ILogger", create_ILogger(this));
114
+ }
115
+ return this.instances.get("ILogger");
116
+ }
117
+
118
+ // Property token (string-based)
119
+ if (token === "PropertyToken:ApiService.apiUrl") {
120
+ if (!this.instances.has("PropertyToken:ApiService.apiUrl")) {
121
+ this.instances.set("PropertyToken:ApiService.apiUrl", create_ApiService_apiUrl(this));
122
+ }
123
+ return this.instances.get("PropertyToken:ApiService.apiUrl");
124
+ }
125
+
126
+ // Class token (reference-based)
127
+ if (token === Import_0.ApiService) {
128
+ if (!this.instances.has(Import_0.ApiService)) {
129
+ this.instances.set(Import_0.ApiService, create_ApiService(this));
130
+ }
131
+ return this.instances.get(Import_0.ApiService);
132
+ }
133
+
134
+ return undefined;
135
+ }
136
+
137
+ public createChildContainer(): NeoContainer {
138
+ return new NeoContainer(this, this.legacy, `Child of ${this.name}`);
139
+ }
140
+
141
+ // For debugging
142
+ public get _graph() {
143
+ return ["ILogger", "PropertyToken:ApiService.apiUrl", "ApiService"];
144
+ }
145
+ }
146
+
147
+ export const container = new NeoContainer();
148
+ ```
149
+
150
+ ## Key Observations
151
+
152
+ ### Token Resolution
153
+
154
+ | Token Type | Resolution Method | Example |
155
+ |------------|-------------------|---------|
156
+ | Interface | String comparison | `token === "ILogger"` |
157
+ | Class | Reference comparison | `token === Import_0.ApiService` |
158
+ | Property | String comparison | `token === "PropertyToken:ApiService.apiUrl"` |
159
+
160
+ ### Singleton Pattern
161
+
162
+ Singletons use the `instances` Map:
163
+
164
+ ```typescript
165
+ if (!this.instances.has("ILogger")) {
166
+ this.instances.set("ILogger", create_ILogger(this));
167
+ }
168
+ return this.instances.get("ILogger");
169
+ ```
170
+
171
+ ### Transient Pattern
172
+
173
+ Transients return directly without caching:
174
+
175
+ ```typescript
176
+ if (token === "RequestContext") {
177
+ return create_RequestContext(this); // No caching!
178
+ }
179
+ ```
180
+
181
+ ### Dependency Injection
182
+
183
+ Dependencies are resolved recursively:
184
+
185
+ ```typescript
186
+ function create_ApiService(container: NeoContainer) {
187
+ return new Import_0.ApiService(
188
+ container.resolve("ILogger"), // Resolves ILogger first
189
+ container.resolve("PropertyToken:...") // Resolves property
190
+ );
191
+ }
192
+ ```
193
+
194
+ ## With Parent Container
195
+
196
+ When using `useContainer`:
197
+
198
+ ```typescript
199
+ // Configuration
200
+ const child = defineBuilderConfig({
201
+ useContainer: parent,
202
+ injections: [{ token: UserService }]
203
+ });
204
+ ```
205
+
206
+ ```typescript
207
+ // Generated
208
+ import { parent } from './parent-container';
209
+
210
+ export class NeoContainer {
211
+ constructor(
212
+ private parent: typeof parent = parent, // 👈 Parent reference
213
+ // ...
214
+ ) {}
215
+
216
+ resolve(token: any): any {
217
+ const local = this.resolveLocal(token);
218
+ if (local !== undefined) return local;
219
+
220
+ // Delegate to parent
221
+ if (this.parent) {
222
+ return this.parent.resolve(token);
223
+ }
224
+
225
+ throw new Error('...');
226
+ }
227
+ }
228
+
229
+ export const child = new NeoContainer(parent);
230
+ ```
231
+
232
+ ## With Factory Provider
233
+
234
+ ```typescript
235
+ // Configuration
236
+ {
237
+ token: useInterface<IDatabase>(),
238
+ provider: (container) => {
239
+ const config = container.resolve(useInterface<IConfig>());
240
+ return new PostgresDatabase(config.connectionString);
241
+ }
242
+ }
243
+ ```
244
+
245
+ ```typescript
246
+ // Generated
247
+ function create_IDatabase(container: NeoContainer) {
248
+ const userFactory = (container) => {
249
+ const config = container.resolve("IConfig");
250
+ return new PostgresDatabase(config.connectionString);
251
+ };
252
+ return userFactory(container);
253
+ }
254
+ ```
255
+
256
+ ## Bundle Size Impact
257
+
258
+ | Aspect | Traditional DI | Neo-Syringe |
259
+ |--------|---------------|-------------|
260
+ | Container library | 4-11 KB | 0 KB |
261
+ | reflect-metadata | ~3 KB | 0 KB |
262
+ | Generated code | N/A | ~50-200 lines |
263
+ | **Total** | **7-14 KB** | **< 1 KB** |
264
+
265
+ The generated code is:
266
+ - ✅ Tree-shakeable (unused services removed)
267
+ - ✅ Minifiable (standard JavaScript)
268
+ - ✅ No external dependencies
269
+
270
+ ## Debugging
271
+
272
+ The generated container includes a `_graph` getter for inspection:
273
+
274
+ ```typescript
275
+ console.log(container._graph);
276
+ // ["ILogger", "PropertyToken:ApiService.apiUrl", "ApiService"]
277
+ ```
278
+
279
+ Each container also has a `name` for error messages:
280
+
281
+ ```typescript
282
+ // Error: [AppContainer] Service not found: UnknownToken
283
+ ```
284
+
@@ -0,0 +1,171 @@
1
+ # Getting Started
2
+
3
+ This guide will help you install Neo-Syringe and create your first container in 5 minutes.
4
+
5
+ ## Installation
6
+
7
+ ::: code-group
8
+
9
+ ```bash [pnpm]
10
+ pnpm add @djodjonx/neo-syringe
11
+ pnpm add -D unplugin
12
+ ```
13
+
14
+ ```bash [npm]
15
+ npm install @djodjonx/neo-syringe
16
+ npm install -D unplugin
17
+ ```
18
+
19
+ ```bash [yarn]
20
+ yarn add @djodjonx/neo-syringe
21
+ yarn add -D unplugin
22
+ ```
23
+
24
+ :::
25
+
26
+ ::: info Peer Dependencies
27
+ - `typescript` >= 5.0.0
28
+ - `unplugin` (required for build plugin)
29
+ :::
30
+
31
+ ## Configure Your Bundler
32
+
33
+ Neo-Syringe works with all major bundlers through `unplugin`.
34
+
35
+ ::: code-group
36
+
37
+ ```typescript [Vite]
38
+ // vite.config.ts
39
+ import { defineConfig } from 'vite';
40
+ import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
41
+
42
+ export default defineConfig({
43
+ plugins: [neoSyringePlugin.vite()]
44
+ });
45
+ ```
46
+
47
+ ```typescript [Rollup]
48
+ // rollup.config.js
49
+ import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
50
+
51
+ export default {
52
+ plugins: [neoSyringePlugin.rollup()]
53
+ };
54
+ ```
55
+
56
+ ```javascript [Webpack]
57
+ // webpack.config.js
58
+ module.exports = {
59
+ plugins: [require('@djodjonx/neo-syringe/plugin').webpack()]
60
+ };
61
+ ```
62
+
63
+ ```typescript [esbuild]
64
+ // esbuild.config.js
65
+ import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
66
+
67
+ await esbuild.build({
68
+ plugins: [neoSyringePlugin.esbuild()]
69
+ });
70
+ ```
71
+
72
+ :::
73
+
74
+ ## Create Your First Container
75
+
76
+ ### Step 1: Define Your Services
77
+
78
+ Create pure TypeScript classes and interfaces. **No decorators needed!**
79
+
80
+ ```typescript
81
+ // services/logger.ts
82
+ export interface ILogger {
83
+ log(msg: string): void;
84
+ }
85
+
86
+ export class ConsoleLogger implements ILogger {
87
+ log(msg: string) {
88
+ console.log(`[LOG] ${msg}`);
89
+ }
90
+ }
91
+ ```
92
+
93
+ ```typescript
94
+ // services/user.service.ts
95
+ import type { ILogger } from './logger';
96
+
97
+ export class UserService {
98
+ constructor(private logger: ILogger) {}
99
+
100
+ createUser(name: string) {
101
+ this.logger.log(`Creating user: ${name}`);
102
+ return { id: crypto.randomUUID(), name };
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Step 2: Configure the Container
108
+
109
+ ::: tip Best Practice
110
+ Put your container configuration in a **dedicated file** (e.g., `container.ts`). The plugin replaces the entire file content with generated code.
111
+ :::
112
+
113
+ ```typescript
114
+ // container.ts
115
+ import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
116
+ import { ILogger, ConsoleLogger } from './services/logger';
117
+ import { UserService } from './services/user.service';
118
+
119
+ export const container = defineBuilderConfig({
120
+ name: 'AppContainer',
121
+ injections: [
122
+ // Bind interface to implementation
123
+ { token: useInterface<ILogger>(), provider: ConsoleLogger },
124
+
125
+ // Autowire class (dependencies resolved automatically)
126
+ { token: UserService }
127
+ ]
128
+ });
129
+ ```
130
+
131
+ ### Step 3: Use the Container
132
+
133
+ ```typescript
134
+ // main.ts
135
+ import { container } from './container';
136
+ import { UserService } from './services/user.service';
137
+
138
+ // Resolve and use
139
+ const userService = container.resolve(UserService);
140
+ const user = userService.createUser('John Doe');
141
+
142
+ console.log(user); // { id: 'xxx-xxx', name: 'John Doe' }
143
+ ```
144
+
145
+ ## Project Structure
146
+
147
+ Recommended project structure:
148
+
149
+ ```
150
+ src/
151
+ ├── container.ts # ✅ Container configuration
152
+ ├── services/
153
+ │ ├── logger.ts # Pure service
154
+ │ ├── user.service.ts # Pure service
155
+ │ └── index.ts # Barrel exports
156
+ └── main.ts # Application entry
157
+ ```
158
+
159
+ ## Development Mode
160
+
161
+ ✅ **The plugin works in dev mode** with full HMR support!
162
+
163
+ The `transform` hook is called on every file change, so your container is regenerated instantly during development.
164
+
165
+ ## What's Next?
166
+
167
+ - [Basic Usage](/guide/basic-usage) - Learn all injection types
168
+ - [Lifecycle](/guide/lifecycle) - Singleton vs Transient
169
+ - [Scoped Injections](/guide/scoped-injections) - Override parent tokens
170
+ - [IDE Plugin](/guide/ide-plugin) - Real-time error detection
171
+