@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.
- package/.github/workflows/ci.yml +6 -5
- package/.github/workflows/docs.yml +59 -0
- package/CHANGELOG.md +27 -0
- package/README.md +74 -740
- package/dist/{GraphValidator-G0F4QiLk.cjs → GraphValidator-CV4VoJl0.cjs} +18 -10
- package/dist/{GraphValidator-C8ldJtNp.mjs → GraphValidator-DXqqkNdS.mjs} +18 -10
- package/dist/cli/index.cjs +16 -1
- package/dist/cli/index.mjs +16 -1
- package/dist/index.d.cts +31 -5
- package/dist/index.d.mts +31 -5
- package/dist/lsp/index.cjs +1 -1
- package/dist/lsp/index.mjs +1 -1
- package/dist/unplugin/index.cjs +33 -9
- package/dist/unplugin/index.d.cts +7 -5
- package/dist/unplugin/index.d.mts +7 -5
- package/dist/unplugin/index.mjs +33 -9
- 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 +15 -12
- package/src/analyzer/Analyzer.ts +20 -10
- package/src/analyzer/types.ts +55 -49
- package/src/cli/index.ts +15 -0
- package/src/generator/Generator.ts +24 -2
- package/src/generator/GraphValidator.ts +6 -2
- package/src/types.ts +30 -4
- package/src/unplugin/index.ts +13 -41
- package/tests/analyzer/Analyzer.test.ts +4 -4
- package/tests/analyzer/AnalyzerDeclarative.test.ts +1 -1
- package/tests/analyzer/Factory.test.ts +2 -2
- package/tests/analyzer/Scoped.test.ts +434 -0
- package/tests/cli/cli.test.ts +91 -0
- package/tests/e2e/container-integration.test.ts +21 -21
- package/tests/e2e/generated-code.test.ts +7 -7
- package/tests/e2e/scoped.test.ts +370 -0
- package/tests/e2e/snapshots.test.ts +2 -2
- package/tests/e2e/standalone.test.ts +2 -2
- package/tests/generator/ExternalGenerator.test.ts +1 -1
- package/tests/generator/FactoryGenerator.test.ts +6 -6
- package/tests/generator/Generator.test.ts +2 -2
- package/tests/generator/GeneratorDeclarative.test.ts +1 -1
- package/tests/generator/GraphValidator.test.ts +1 -1
- package/tsconfig.json +2 -1
- 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
|
+
|