@djodjonx/neo-syringe 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docs.yml +59 -0
- package/CHANGELOG.md +14 -0
- package/README.md +72 -779
- package/dist/cli/index.cjs +15 -0
- package/dist/cli/index.mjs +15 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/unplugin/index.cjs +31 -7
- package/dist/unplugin/index.d.cts +7 -5
- package/dist/unplugin/index.d.mts +7 -5
- package/dist/unplugin/index.mjs +31 -7
- package/docs/.vitepress/config.ts +109 -0
- package/docs/.vitepress/theme/custom.css +150 -0
- package/docs/.vitepress/theme/index.ts +17 -0
- package/docs/api/configuration.md +274 -0
- package/docs/api/functions.md +291 -0
- package/docs/api/types.md +158 -0
- package/docs/guide/basic-usage.md +267 -0
- package/docs/guide/cli.md +174 -0
- package/docs/guide/generated-code.md +284 -0
- package/docs/guide/getting-started.md +171 -0
- package/docs/guide/ide-plugin.md +203 -0
- package/docs/guide/injection-types.md +287 -0
- package/docs/guide/legacy-migration.md +333 -0
- package/docs/guide/lifecycle.md +223 -0
- package/docs/guide/parent-container.md +321 -0
- package/docs/guide/scoped-injections.md +271 -0
- package/docs/guide/what-is-neo-syringe.md +162 -0
- package/docs/guide/why-neo-syringe.md +219 -0
- package/docs/index.md +138 -0
- package/docs/public/logo.png +0 -0
- package/package.json +5 -3
- package/src/analyzer/types.ts +52 -52
- package/src/cli/index.ts +15 -0
- package/src/generator/Generator.ts +23 -1
- package/src/types.ts +1 -1
- package/src/unplugin/index.ts +13 -41
- package/tests/analyzer/AnalyzerDeclarative.test.ts +1 -1
- package/tests/e2e/container-integration.test.ts +19 -19
- package/tests/e2e/generated-code.test.ts +2 -2
- package/tsconfig.json +2 -1
- package/typedoc.json +0 -5
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# What is Neo-Syringe?
|
|
2
|
+
|
|
3
|
+
Neo-Syringe is a **next-generation dependency injection system** for TypeScript that shifts resolution from **runtime** to **build-time**.
|
|
4
|
+
|
|
5
|
+
## The Problem with Traditional DI
|
|
6
|
+
|
|
7
|
+
Traditional DI containers (InversifyJS, tsyringe, Awilix) all work the same way:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Traditional approach
|
|
11
|
+
@injectable()
|
|
12
|
+
class UserService {
|
|
13
|
+
constructor(@inject(TYPES.ILogger) private logger: ILogger) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// At runtime
|
|
17
|
+
container.resolve(UserService); // ← Resolution happens HERE
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This approach has several drawbacks:
|
|
21
|
+
|
|
22
|
+
| Issue | Impact |
|
|
23
|
+
|-------|--------|
|
|
24
|
+
| **Runtime overhead** | DI container code shipped to production |
|
|
25
|
+
| **Reflection required** | Need `reflect-metadata` and decorators |
|
|
26
|
+
| **Interface erasure** | Must use Symbols or string tokens manually |
|
|
27
|
+
| **Late errors** | Missing bindings only discovered at runtime |
|
|
28
|
+
| **Framework coupling** | Classes polluted with DI decorators |
|
|
29
|
+
|
|
30
|
+
## The Neo-Syringe Solution
|
|
31
|
+
|
|
32
|
+
Neo-Syringe works as a **compiler plugin** that analyzes your configuration and generates optimized code:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Your code (pure TypeScript!)
|
|
36
|
+
interface ILogger {
|
|
37
|
+
log(msg: string): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class UserService {
|
|
41
|
+
constructor(private logger: ILogger) {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Configuration
|
|
45
|
+
export const container = defineBuilderConfig({
|
|
46
|
+
injections: [
|
|
47
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
48
|
+
{ token: UserService }
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
At build time, this becomes:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Generated code (no DI library!)
|
|
57
|
+
function create_UserService(container) {
|
|
58
|
+
return new UserService(container.resolve("ILogger"));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class NeoContainer {
|
|
62
|
+
resolve(token) {
|
|
63
|
+
if (token === "ILogger") return new ConsoleLogger();
|
|
64
|
+
if (token === UserService) return create_UserService(this);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const container = new NeoContainer();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Key Advantages
|
|
72
|
+
|
|
73
|
+
### 🚀 Zero Runtime Overhead
|
|
74
|
+
|
|
75
|
+
No DI library shipped to production. Just pure factory functions that create instances directly.
|
|
76
|
+
|
|
77
|
+
### ✨ Native Interface Support
|
|
78
|
+
|
|
79
|
+
Use `useInterface<ILogger>()` instead of managing Symbols. The compiler generates unique IDs automatically.
|
|
80
|
+
|
|
81
|
+
### 🛡️ Compile-Time Safety
|
|
82
|
+
|
|
83
|
+
Errors are detected in your IDE before you even save the file:
|
|
84
|
+
|
|
85
|
+
- Circular dependencies
|
|
86
|
+
- Missing bindings
|
|
87
|
+
- Duplicate registrations
|
|
88
|
+
- Type mismatches
|
|
89
|
+
|
|
90
|
+
### 📦 Pure Classes
|
|
91
|
+
|
|
92
|
+
Your business classes have **zero DI dependencies**:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// ✅ Pure TypeScript class
|
|
96
|
+
class UserService {
|
|
97
|
+
constructor(private logger: ILogger) {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ❌ Traditional approach (polluted)
|
|
101
|
+
@injectable()
|
|
102
|
+
class UserService {
|
|
103
|
+
constructor(@inject(TYPES.ILogger) private logger: ILogger) {}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 🔄 Gradual Migration
|
|
108
|
+
|
|
109
|
+
Bridge existing containers while migrating:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
export const container = defineBuilderConfig({
|
|
113
|
+
useContainer: legacyTsyringeContainer, // ← Delegate to legacy
|
|
114
|
+
injections: [
|
|
115
|
+
{ token: NewService } // New services in Neo-Syringe
|
|
116
|
+
]
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## How It Works
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
124
|
+
│ BUILD TIME │
|
|
125
|
+
├─────────────────────────────────────────────────────────────┤
|
|
126
|
+
│ │
|
|
127
|
+
│ 1. defineBuilderConfig({...}) │
|
|
128
|
+
│ │ │
|
|
129
|
+
│ ▼ │
|
|
130
|
+
│ 2. TypeScript Plugin analyzes configuration │
|
|
131
|
+
│ │ │
|
|
132
|
+
│ ▼ │
|
|
133
|
+
│ 3. Generates optimized NeoContainer class │
|
|
134
|
+
│ │ │
|
|
135
|
+
│ ▼ │
|
|
136
|
+
│ 4. Replaces defineBuilderConfig with generated code │
|
|
137
|
+
│ │
|
|
138
|
+
└─────────────────────────────────────────────────────────────┘
|
|
139
|
+
│
|
|
140
|
+
▼
|
|
141
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
142
|
+
│ RUNTIME │
|
|
143
|
+
├─────────────────────────────────────────────────────────────┤
|
|
144
|
+
│ │
|
|
145
|
+
│ container.resolve(UserService) │
|
|
146
|
+
│ │ │
|
|
147
|
+
│ ▼ │
|
|
148
|
+
│ Direct new UserService(new ConsoleLogger()) │
|
|
149
|
+
│ │
|
|
150
|
+
│ ✅ No reflection │
|
|
151
|
+
│ ✅ No container lookup │
|
|
152
|
+
│ ✅ Just function calls │
|
|
153
|
+
│ │
|
|
154
|
+
└─────────────────────────────────────────────────────────────┘
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Next Steps
|
|
158
|
+
|
|
159
|
+
- [Getting Started](/guide/getting-started) - Install and configure Neo-Syringe
|
|
160
|
+
- [Basic Usage](/guide/basic-usage) - Learn the core concepts
|
|
161
|
+
- [Why Neo-Syringe?](/guide/why-neo-syringe) - Detailed comparison with alternatives
|
|
162
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Why Neo-Syringe?
|
|
2
|
+
|
|
3
|
+
A detailed comparison with other dependency injection solutions.
|
|
4
|
+
|
|
5
|
+
## Comparison Table
|
|
6
|
+
|
|
7
|
+
| Feature | Neo-Syringe | tsyringe | InversifyJS | Awilix |
|
|
8
|
+
|---------|:-----------:|:--------:|:-----------:|:------:|
|
|
9
|
+
| **Zero runtime overhead** | ✅ | ❌ | ❌ | ❌ |
|
|
10
|
+
| **No decorators needed** | ✅ | ❌ | ❌ | ✅ |
|
|
11
|
+
| **No reflect-metadata** | ✅ | ❌ | ❌ | ✅ |
|
|
12
|
+
| **Interface as tokens** | ✅ | ❌ | ❌ | ❌ |
|
|
13
|
+
| **Compile-time validation** | ✅ | ❌ | ❌ | ❌ |
|
|
14
|
+
| **IDE error detection** | ✅ | ❌ | ❌ | ❌ |
|
|
15
|
+
| **Tree-shakeable** | ✅ | ❌ | ❌ | ❌ |
|
|
16
|
+
| **Works in Edge/Workers** | ✅ | ⚠️ | ⚠️ | ✅ |
|
|
17
|
+
|
|
18
|
+
## The Cost of Runtime DI
|
|
19
|
+
|
|
20
|
+
Traditional DI containers add significant overhead:
|
|
21
|
+
|
|
22
|
+
### Bundle Size
|
|
23
|
+
|
|
24
|
+
| Library | Min+Gzip |
|
|
25
|
+
|---------|----------|
|
|
26
|
+
| InversifyJS | ~11 KB |
|
|
27
|
+
| tsyringe | ~4 KB |
|
|
28
|
+
| Awilix | ~8 KB |
|
|
29
|
+
| **Neo-Syringe** | **~0 KB** (generated) |
|
|
30
|
+
|
|
31
|
+
### Runtime Performance
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Traditional (tsyringe) - Runtime resolution
|
|
35
|
+
container.resolve(UserService);
|
|
36
|
+
// 1. Look up token in registry
|
|
37
|
+
// 2. Check if singleton exists
|
|
38
|
+
// 3. Resolve all dependencies recursively
|
|
39
|
+
// 4. Create instance with reflection
|
|
40
|
+
// 5. Store singleton reference
|
|
41
|
+
|
|
42
|
+
// Neo-Syringe - Direct instantiation
|
|
43
|
+
container.resolve(UserService);
|
|
44
|
+
// 1. Call generated factory function
|
|
45
|
+
// That's it!
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Code Quality Comparison
|
|
49
|
+
|
|
50
|
+
### Traditional Approach (tsyringe)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import 'reflect-metadata';
|
|
54
|
+
import { injectable, inject } from 'tsyringe';
|
|
55
|
+
|
|
56
|
+
// Must define symbols manually
|
|
57
|
+
const TYPES = {
|
|
58
|
+
ILogger: Symbol.for('ILogger'),
|
|
59
|
+
IDatabase: Symbol.for('IDatabase'),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Classes polluted with decorators
|
|
63
|
+
@injectable()
|
|
64
|
+
class UserService {
|
|
65
|
+
constructor(
|
|
66
|
+
@inject(TYPES.ILogger) private logger: ILogger,
|
|
67
|
+
@inject(TYPES.IDatabase) private db: IDatabase
|
|
68
|
+
) {}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Registration
|
|
72
|
+
container.register(TYPES.ILogger, { useClass: ConsoleLogger });
|
|
73
|
+
container.register(TYPES.IDatabase, { useClass: PostgresDatabase });
|
|
74
|
+
container.register(UserService, { useClass: UserService });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Neo-Syringe Approach
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
|
|
81
|
+
|
|
82
|
+
// Pure class - no DI imports!
|
|
83
|
+
class UserService {
|
|
84
|
+
constructor(
|
|
85
|
+
private logger: ILogger,
|
|
86
|
+
private db: IDatabase
|
|
87
|
+
) {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Clean configuration
|
|
91
|
+
export const container = defineBuilderConfig({
|
|
92
|
+
injections: [
|
|
93
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
94
|
+
{ token: useInterface<IDatabase>(), provider: PostgresDatabase },
|
|
95
|
+
{ token: UserService }
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Error Detection
|
|
101
|
+
|
|
102
|
+
### Traditional: Runtime Errors
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// tsyringe - Error at RUNTIME
|
|
106
|
+
container.resolve(UserService);
|
|
107
|
+
// Error: Attempted to resolve unregistered dependency token: "ILogger"
|
|
108
|
+
// 💥 App crashes in production!
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Neo-Syringe: Compile-Time Errors
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Neo-Syringe - Error in IDE instantly
|
|
115
|
+
export const container = defineBuilderConfig({
|
|
116
|
+
injections: [
|
|
117
|
+
{ token: UserService } // UserService needs ILogger
|
|
118
|
+
]
|
|
119
|
+
});
|
|
120
|
+
// 🔴 [Neo-Syringe] Missing binding: 'UserService' depends on 'ILogger'
|
|
121
|
+
// ✅ Fixed before you even save the file!
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
### Traditional: Complex Mocking (tsyringe example)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// ❌ tsyringe requires manual container reset and re-registration
|
|
130
|
+
import { container } from 'tsyringe';
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
container.reset(); // tsyringe API, NOT Neo-Syringe!
|
|
134
|
+
container.register(TYPES.ILogger, { useClass: MockLogger });
|
|
135
|
+
container.register(TYPES.IDatabase, { useClass: MockDatabase });
|
|
136
|
+
container.register(UserService, { useClass: UserService });
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Neo-Syringe: Natural Overrides
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// ✅ Neo-Syringe - Create a test container that overrides production services
|
|
144
|
+
import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
|
|
145
|
+
import { productionContainer } from './container';
|
|
146
|
+
|
|
147
|
+
// Test container inherits from production but overrides specific services
|
|
148
|
+
const testContainer = defineBuilderConfig({
|
|
149
|
+
useContainer: productionContainer,
|
|
150
|
+
injections: [
|
|
151
|
+
{ token: useInterface<ILogger>(), provider: MockLogger, scoped: true },
|
|
152
|
+
{ token: useInterface<IDatabase>(), provider: MockDatabase, scoped: true }
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// No reset needed - each test file can have its own container!
|
|
157
|
+
const userService = testContainer.resolve(UserService);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Edge Computing / Workers
|
|
161
|
+
|
|
162
|
+
Traditional DI often fails in edge environments:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// ❌ tsyringe in Cloudflare Workers
|
|
166
|
+
// Error: reflect-metadata requires global Reflect object
|
|
167
|
+
|
|
168
|
+
// ❌ InversifyJS in Vercel Edge
|
|
169
|
+
// Error: Cannot use decorators in Edge Runtime
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Neo-Syringe works everywhere:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// ✅ Neo-Syringe in any environment
|
|
176
|
+
// Generated code is pure JavaScript
|
|
177
|
+
export class NeoContainer {
|
|
178
|
+
resolve(token) {
|
|
179
|
+
if (token === "ILogger") return new ConsoleLogger();
|
|
180
|
+
// ... pure function calls
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Migration Path
|
|
186
|
+
|
|
187
|
+
You don't have to migrate everything at once:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Bridge legacy container
|
|
191
|
+
import { legacyContainer } from './legacy-tsyringe';
|
|
192
|
+
import { declareContainerTokens } from '@djodjonx/neo-syringe';
|
|
193
|
+
|
|
194
|
+
const legacy = declareContainerTokens<{
|
|
195
|
+
AuthService: AuthService;
|
|
196
|
+
UserRepository: UserRepository;
|
|
197
|
+
}>(legacyContainer);
|
|
198
|
+
|
|
199
|
+
// New services use Neo-Syringe
|
|
200
|
+
export const container = defineBuilderConfig({
|
|
201
|
+
useContainer: legacy, // Delegate to legacy
|
|
202
|
+
injections: [
|
|
203
|
+
{ token: NewService },
|
|
204
|
+
{ token: AnotherNewService }
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Summary
|
|
210
|
+
|
|
211
|
+
| Aspect | Traditional DI | Neo-Syringe |
|
|
212
|
+
|--------|---------------|-------------|
|
|
213
|
+
| **When errors occur** | Runtime | Compile-time |
|
|
214
|
+
| **Bundle impact** | 4-11 KB | 0 KB |
|
|
215
|
+
| **Class purity** | Polluted with decorators | 100% pure |
|
|
216
|
+
| **Interface support** | Manual Symbols | Automatic |
|
|
217
|
+
| **Edge compatibility** | Limited | Full |
|
|
218
|
+
| **Performance** | Map lookups + reflection | Direct calls |
|
|
219
|
+
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
|
|
4
|
+
hero:
|
|
5
|
+
name: Neo-Syringe
|
|
6
|
+
text: Compile-Time DI
|
|
7
|
+
tagline: Zero-overhead dependency injection that shifts resolution from Runtime to Build-Time. No reflection, no decorators, just pure TypeScript.
|
|
8
|
+
image:
|
|
9
|
+
src: /logo.png
|
|
10
|
+
alt: Neo-Syringe
|
|
11
|
+
actions:
|
|
12
|
+
- theme: brand
|
|
13
|
+
text: Get Started
|
|
14
|
+
link: /guide/getting-started
|
|
15
|
+
- theme: alt
|
|
16
|
+
text: View on GitHub
|
|
17
|
+
link: https://github.com/djodjonx/neo-syringe
|
|
18
|
+
|
|
19
|
+
features:
|
|
20
|
+
- icon: ✨
|
|
21
|
+
title: Interface as Tokens
|
|
22
|
+
details: Native support for useInterface<ILogger>() without manual Symbols. TypeScript interfaces work seamlessly.
|
|
23
|
+
|
|
24
|
+
- icon: 🚀
|
|
25
|
+
title: Zero Runtime Overhead
|
|
26
|
+
details: No reflection, no reflect-metadata. Just pure factory functions generated at build time.
|
|
27
|
+
|
|
28
|
+
- icon: 🛡️
|
|
29
|
+
title: Compile-Time Safety
|
|
30
|
+
details: Detect circular dependencies, missing bindings, and type mismatches instantly in your IDE.
|
|
31
|
+
|
|
32
|
+
- icon: 🔄
|
|
33
|
+
title: Gradual Migration
|
|
34
|
+
details: Bridge existing containers like tsyringe or InversifyJS with useContainer while migrating.
|
|
35
|
+
|
|
36
|
+
- icon: 📦
|
|
37
|
+
title: Pure Classes
|
|
38
|
+
details: Your business classes stay 100% pure - no decorators, no DI imports, no framework coupling.
|
|
39
|
+
|
|
40
|
+
- icon: 🤖
|
|
41
|
+
title: CI Validation
|
|
42
|
+
details: Standalone CLI to verify your dependency graph before deployment.
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<style>
|
|
46
|
+
:root {
|
|
47
|
+
--vp-home-hero-name-color: transparent;
|
|
48
|
+
--vp-home-hero-name-background: linear-gradient(135deg, #0d9488 0%, #f97316 100%);
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
51
|
+
|
|
52
|
+
## Why Choose Neo-Syringe?
|
|
53
|
+
|
|
54
|
+
Traditional DI containers like InversifyJS and tsyringe rely on **runtime resolution**:
|
|
55
|
+
|
|
56
|
+
- ❌ Ship DI container logic to the browser
|
|
57
|
+
- ❌ Errors happen at runtime
|
|
58
|
+
- ❌ Interfaces are erased, requiring manual Symbols
|
|
59
|
+
- ❌ Need decorators and reflect-metadata
|
|
60
|
+
|
|
61
|
+
**Neo-Syringe is different.** It works as a **compiler plugin**:
|
|
62
|
+
|
|
63
|
+
- ✅ Generate optimized factories at build time
|
|
64
|
+
- ✅ Errors detected in your IDE
|
|
65
|
+
- ✅ Automatic interface IDs
|
|
66
|
+
- ✅ Pure TypeScript, no decorators
|
|
67
|
+
|
|
68
|
+
## Quick Example
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Pure TypeScript - no decorators!
|
|
72
|
+
interface ILogger {
|
|
73
|
+
log(msg: string): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class ConsoleLogger implements ILogger {
|
|
77
|
+
log(msg: string) { console.log(msg); }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class UserService {
|
|
81
|
+
constructor(private logger: ILogger) {}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Configure the container
|
|
85
|
+
import { defineBuilderConfig, useInterface } from '@djodjonx/neo-syringe';
|
|
86
|
+
|
|
87
|
+
export const container = defineBuilderConfig({
|
|
88
|
+
name: 'AppContainer',
|
|
89
|
+
injections: [
|
|
90
|
+
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
|
|
91
|
+
{ token: UserService }
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Use it
|
|
96
|
+
const userService = container.resolve(UserService);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Generated Output
|
|
100
|
+
|
|
101
|
+
The build plugin transforms your configuration into optimized code:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// Generated at build time
|
|
105
|
+
function create_ILogger() {
|
|
106
|
+
return new ConsoleLogger();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function create_UserService(container) {
|
|
110
|
+
return new UserService(container.resolve("ILogger"));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class NeoContainer {
|
|
114
|
+
resolve(token) {
|
|
115
|
+
if (token === "ILogger") return this.getInstance("ILogger", create_ILogger);
|
|
116
|
+
if (token === UserService) return this.getInstance(UserService, create_UserService);
|
|
117
|
+
throw new Error(`Service not found: ${token}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Zero DI library shipped to production!**
|
|
123
|
+
|
|
124
|
+
<div style="text-align: center; margin-top: 3rem;">
|
|
125
|
+
<a href="./guide/getting-started" style="
|
|
126
|
+
display: inline-block;
|
|
127
|
+
padding: 12px 24px;
|
|
128
|
+
background: linear-gradient(135deg, #0d9488 0%, #14b8a6 100%);
|
|
129
|
+
color: white;
|
|
130
|
+
text-decoration: none;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
transition: transform 0.2s;
|
|
134
|
+
">
|
|
135
|
+
Get Started →
|
|
136
|
+
</a>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djodjonx/neo-syringe",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Zero-Overhead, Compile-Time Dependency Injection for TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"validate": "pnpm lint && pnpm typecheck && pnpm test",
|
|
37
37
|
"prebuild": "pnpm validate",
|
|
38
38
|
"prepublishOnly": "pnpm build",
|
|
39
|
-
"docs": "
|
|
39
|
+
"docs:dev": "vitepress dev docs",
|
|
40
|
+
"docs:build": "vitepress build docs",
|
|
41
|
+
"docs:preview": "vitepress preview docs",
|
|
40
42
|
"release": "pnpm validate && standard-version",
|
|
41
43
|
"release:dry": "standard-version --dry-run",
|
|
42
44
|
"prepare": "husky"
|
|
@@ -66,9 +68,9 @@
|
|
|
66
68
|
"standard-version": "^9.5.0",
|
|
67
69
|
"tsdown": "0.20.0-beta.3",
|
|
68
70
|
"tsyringe": "^4.10.0",
|
|
69
|
-
"typedoc": "^0.28.16",
|
|
70
71
|
"typescript": "^5.9.3",
|
|
71
72
|
"unplugin": "^2.3.11",
|
|
73
|
+
"vitepress": "^1.6.4",
|
|
72
74
|
"vitest": "^4.0.17"
|
|
73
75
|
}
|
|
74
76
|
}
|
package/src/analyzer/types.ts
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
1
|
import type { Symbol, Node } from 'typescript';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Unique identifier for a token (interface name or class name).
|
|
5
|
+
*/
|
|
6
|
+
export type TokenId = string;
|
|
4
7
|
|
|
8
|
+
/**
|
|
9
|
+
* How a service was registered in the container.
|
|
10
|
+
* - `explicit`: Provider was explicitly specified.
|
|
11
|
+
* - `autowire`: Token is both the token and provider (self-binding).
|
|
12
|
+
* - `parent`: Inherited from parent container.
|
|
13
|
+
* - `factory`: Provider is a factory function.
|
|
14
|
+
*/
|
|
5
15
|
export type RegistrationType = 'explicit' | 'autowire' | 'parent' | 'factory';
|
|
6
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Represents a single service definition in the dependency graph.
|
|
19
|
+
*/
|
|
7
20
|
export interface ServiceDefinition {
|
|
21
|
+
/** Unique identifier for this service token. */
|
|
8
22
|
tokenId: TokenId;
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
|
-
* The symbol of the concrete class implementation.
|
|
25
|
+
* The TypeScript symbol of the concrete class implementation.
|
|
11
26
|
* Undefined if the provider is a factory function.
|
|
12
27
|
*/
|
|
13
28
|
implementationSymbol?: Symbol;
|
|
14
29
|
|
|
15
30
|
/**
|
|
16
|
-
* The symbol of the token (if it is a Class/Value).
|
|
17
|
-
* Undefined if the token is a
|
|
31
|
+
* The TypeScript symbol of the token (if it is a Class/Value).
|
|
32
|
+
* Undefined if the token is a virtual interface ID.
|
|
18
33
|
*/
|
|
19
34
|
tokenSymbol?: Symbol;
|
|
20
35
|
|
|
21
|
-
/**
|
|
22
|
-
* The source node where the registration happened (for error reporting).
|
|
23
|
-
*/
|
|
36
|
+
/** The source node where the registration happened (for error reporting). */
|
|
24
37
|
registrationNode: Node;
|
|
25
38
|
|
|
39
|
+
/** How this service was registered. */
|
|
26
40
|
type: RegistrationType;
|
|
41
|
+
|
|
42
|
+
/** Lifecycle of the service instance. */
|
|
27
43
|
lifecycle: 'singleton' | 'transient';
|
|
28
44
|
|
|
29
|
-
/**
|
|
30
|
-
* True if the token is an Interface (requires string literal key).
|
|
31
|
-
* False if the token is a Class (requires reference key).
|
|
32
|
-
*/
|
|
45
|
+
/** True if the token is an interface (requires string literal key). */
|
|
33
46
|
isInterfaceToken?: boolean;
|
|
34
47
|
|
|
35
|
-
/**
|
|
36
|
-
* True if the token is a Value Token (useToken<T>('name')).
|
|
37
|
-
* Used for primitive values like string, number, boolean.
|
|
38
|
-
*/
|
|
48
|
+
/** True if the token is a value token for primitives. */
|
|
39
49
|
isValueToken?: boolean;
|
|
40
50
|
|
|
41
|
-
/**
|
|
42
|
-
* True if the provider is a factory function.
|
|
43
|
-
*/
|
|
51
|
+
/** True if the provider is a factory function. */
|
|
44
52
|
isFactory?: boolean;
|
|
45
53
|
|
|
46
|
-
/**
|
|
47
|
-
* The raw source text of the factory function (for code generation).
|
|
48
|
-
*/
|
|
54
|
+
/** The raw source text of the factory function (for code generation). */
|
|
49
55
|
factorySource?: string;
|
|
50
56
|
|
|
51
|
-
/**
|
|
52
|
-
* True if this injection is scoped to the local container.
|
|
53
|
-
* Allows overriding a token from a parent container without duplicate error.
|
|
54
|
-
*/
|
|
57
|
+
/** True if this injection is scoped to the local container. */
|
|
55
58
|
isScoped?: boolean;
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A node in the dependency graph representing a service and its dependencies.
|
|
63
|
+
*/
|
|
58
64
|
export interface DependencyNode {
|
|
65
|
+
/** The service definition. */
|
|
59
66
|
service: ServiceDefinition;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* Maps parameter index to the TokenId it depends on.
|
|
63
|
-
*/
|
|
67
|
+
|
|
68
|
+
/** Token IDs of dependencies required by this service's constructor. */
|
|
64
69
|
dependencies: TokenId[];
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Complete dependency graph for a container configuration.
|
|
74
|
+
*/
|
|
67
75
|
export interface DependencyGraph {
|
|
76
|
+
/** All service nodes indexed by their token ID. */
|
|
68
77
|
nodes: Map<TokenId, DependencyNode>;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*/
|
|
78
|
+
|
|
79
|
+
/** Root services that are explicitly requested or exported. */
|
|
72
80
|
roots: TokenId[];
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* Captured as raw source text to be injected into the generated constructor.
|
|
76
|
-
*/
|
|
81
|
+
|
|
82
|
+
/** Arguments passed to the .build() method call (raw source text). */
|
|
77
83
|
buildArguments?: string[];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Tokens provided by the parent container (useContainer).
|
|
90
|
-
* Used for validation - these tokens don't need local bindings.
|
|
91
|
-
*/
|
|
92
|
-
parentProvidedTokens?: Set<TokenId>;
|
|
93
|
-
}
|
|
84
|
+
|
|
85
|
+
/** Optional container name for debugging. */
|
|
86
|
+
containerName?: string;
|
|
87
|
+
|
|
88
|
+
/** Legacy container variable names to delegate to. */
|
|
89
|
+
legacyContainers?: string[];
|
|
90
|
+
|
|
91
|
+
/** Tokens provided by the parent container (used for validation). */
|
|
92
|
+
parentProvidedTokens?: Set<TokenId>;
|
|
93
|
+
}
|