@djodjonx/wiredi 0.0.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 (37) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +516 -0
  3. package/dist/index.cjs +931 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1139 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +1139 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.mjs +915 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/plugin/ConfigurationAnalyzer.d.ts +59 -0
  12. package/dist/plugin/ConfigurationAnalyzer.d.ts.map +1 -0
  13. package/dist/plugin/ConfigurationAnalyzer.js +321 -0
  14. package/dist/plugin/ConfigurationAnalyzer.js.map +1 -0
  15. package/dist/plugin/DependencyAnalyzer.d.ts +54 -0
  16. package/dist/plugin/DependencyAnalyzer.d.ts.map +1 -0
  17. package/dist/plugin/DependencyAnalyzer.js +208 -0
  18. package/dist/plugin/DependencyAnalyzer.js.map +1 -0
  19. package/dist/plugin/TokenNormalizer.d.ts +51 -0
  20. package/dist/plugin/TokenNormalizer.d.ts.map +1 -0
  21. package/dist/plugin/TokenNormalizer.js +208 -0
  22. package/dist/plugin/TokenNormalizer.js.map +1 -0
  23. package/dist/plugin/ValidationEngine.d.ts +53 -0
  24. package/dist/plugin/ValidationEngine.d.ts.map +1 -0
  25. package/dist/plugin/ValidationEngine.js +250 -0
  26. package/dist/plugin/ValidationEngine.js.map +1 -0
  27. package/dist/plugin/index.d.ts +2 -0
  28. package/dist/plugin/index.d.ts.map +1 -0
  29. package/dist/plugin/index.js +144 -0
  30. package/dist/plugin/index.js.map +1 -0
  31. package/dist/plugin/package.json +6 -0
  32. package/dist/plugin/types.d.ts +152 -0
  33. package/dist/plugin/types.d.ts.map +1 -0
  34. package/dist/plugin/types.js +3 -0
  35. package/dist/plugin/types.js.map +1 -0
  36. package/package.json +95 -0
  37. package/plugin.js +1 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 diligent contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,516 @@
1
+ <p align="center">
2
+ <img src="logo/wiredi-banner-dark.svg" alt="WireDI" width="600">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@djodjonx/wiredi"><img src="https://img.shields.io/npm/v/@djodjonx/wiredi.svg?style=flat-square" alt="npm version"></a>
7
+ <a href="https://djodjonx.github.io/wiredi/"><img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue?style=flat-square" alt="Documentation"></a>
8
+ <a href="https://github.com/djodjonx/wiredi/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/djodjonx/wiredi/ci.yml?style=flat-square" alt="CI"></a>
9
+ <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.0+-blue.svg?style=flat-square" alt="TypeScript"></a>
10
+ <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>
11
+ </p>
12
+
13
+ <h1 align="center">Wire your dependency injection with type safety</h1>
14
+
15
+ <p align="center">
16
+ <strong>WireDI</strong> is an abstraction layer on top of popular DI containers (tsyringe, Awilix, InversifyJS) that wires your dependencies with compile-time validation.
17
+ </p>
18
+
19
+ ---
20
+
21
+ `@djodjonx/wiredi` allows you to:
22
+
23
+ - ✅ **Detect missing dependencies** before runtime
24
+ - ✅ **Verify type consistency** between interfaces and their implementations
25
+ - ✅ **Compose configurations** with a reusable partials system
26
+ - ✅ **Switch DI containers** without changing your business code
27
+
28
+ ## 📚 Documentation
29
+
30
+ - **[Full API Documentation](https://djodjonx.github.io/wiredi/)** - Complete TypeDoc API reference
31
+ - **[Getting Started Guide](#quick-start)** - Start using WireDI in 5 minutes
32
+ - **[Examples](./examples)** - Real-world integration examples
33
+
34
+ ## Why WireDI?
35
+
36
+ Dependency injection containers like tsyringe or InversifyJS are powerful, but they fail **at runtime** when a dependency is missing or misconfigured.
37
+
38
+ **With WireDI**, these errors are detected **in your IDE** before you even run the code:
39
+
40
+ ```typescript
41
+ // ❌ Error detected in IDE: "Logger" is not registered
42
+ const config = defineBuilderConfig({
43
+ builderId: 'app',
44
+ injections: [
45
+ { token: UserService }, // UserService depends on Logger
46
+ ],
47
+ listeners: [],
48
+ })
49
+ ```
50
+
51
+ ### Type Checking Without Decorators
52
+
53
+ Unlike traditional DI containers, **WireDI's type checking works without decorators**:
54
+
55
+ - ✅ Type validation at **configuration time**, not runtime
56
+ - ✅ Works with **plain TypeScript classes**
57
+ - ✅ No need for `@injectable` or `@inject` decorators
58
+ - ✅ Framework-agnostic type safety
59
+
60
+ **Learn more**: [Type Checking Without Decorators](docs/Agent/TYPE_CHECKING_WITHOUT_DECORATORS.md)
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ # With npm
66
+ npm install @djodjonx/wiredi
67
+
68
+ # With pnpm
69
+ pnpm add @djodjonx/wiredi
70
+
71
+ # With yarn
72
+ yarn add @djodjonx/wiredi
73
+ ```
74
+
75
+ ### Install a DI Container
76
+
77
+ `@djodjonx/wiredi` supports multiple containers. Install the one of your choice:
78
+
79
+ ```bash
80
+ # Option 1: tsyringe (recommended)
81
+ npm install tsyringe reflect-metadata
82
+
83
+ # Option 2: Awilix
84
+ npm install awilix
85
+
86
+ # Option 3: InversifyJS
87
+ npm install inversify reflect-metadata
88
+ ```
89
+
90
+ ## Quick Start
91
+
92
+ ### 1. Configure the provider (once at startup)
93
+
94
+ <details>
95
+ <summary><strong>With tsyringe (recommended)</strong></summary>
96
+
97
+ ```typescript
98
+ // main.ts
99
+ import 'reflect-metadata'
100
+ import { container, Lifecycle } from 'tsyringe'
101
+ import { useContainerProvider, TsyringeProvider } from '@djodjonx/wiredi'
102
+
103
+ useContainerProvider(new TsyringeProvider({ container, Lifecycle }))
104
+ ```
105
+
106
+ </details>
107
+
108
+ <details>
109
+ <summary><strong>With Awilix</strong></summary>
110
+
111
+ ```typescript
112
+ // main.ts
113
+ import * as awilix from 'awilix'
114
+ import { useContainerProvider, AwilixProvider } from '@djodjonx/wiredi'
115
+
116
+ useContainerProvider(AwilixProvider.createSync(awilix, {
117
+ injectionMode: 'PROXY', // or 'CLASSIC'
118
+ }))
119
+ ```
120
+
121
+ </details>
122
+
123
+ <details>
124
+ <summary><strong>With InversifyJS</strong></summary>
125
+
126
+ ```typescript
127
+ // main.ts
128
+ import 'reflect-metadata'
129
+ import * as inversify from 'inversify'
130
+ import { useContainerProvider, InversifyProvider } from '@djodjonx/wiredi'
131
+
132
+ useContainerProvider(InversifyProvider.createSync(inversify))
133
+ ```
134
+
135
+ </details>
136
+
137
+ ### 2. Define your services and tokens
138
+
139
+ ```typescript
140
+ // services.ts
141
+ import { injectable, inject } from 'tsyringe' // or your container's decorators
142
+
143
+ // Interfaces
144
+ interface LoggerInterface {
145
+ log(message: string): void
146
+ }
147
+
148
+ interface UserRepositoryInterface {
149
+ findById(id: string): Promise<User | null>
150
+ }
151
+
152
+ // Implementations
153
+ @injectable()
154
+ class ConsoleLogger implements LoggerInterface {
155
+ log(message: string) {
156
+ console.log(`[LOG] ${message}`)
157
+ }
158
+ }
159
+
160
+ @injectable()
161
+ class UserRepository implements UserRepositoryInterface {
162
+ async findById(id: string) {
163
+ // ... implementation
164
+ }
165
+ }
166
+
167
+ @injectable()
168
+ class UserService {
169
+ constructor(
170
+ @inject(TOKENS.Logger) private logger: LoggerInterface,
171
+ @inject(TOKENS.UserRepository) private repo: UserRepositoryInterface,
172
+ ) {}
173
+
174
+ async getUser(id: string) {
175
+ this.logger.log(`Fetching user ${id}`)
176
+ return this.repo.findById(id)
177
+ }
178
+ }
179
+
180
+ // Injection tokens
181
+ export const TOKENS = {
182
+ Logger: Symbol('Logger'),
183
+ UserRepository: Symbol('UserRepository'),
184
+ } as const
185
+ ```
186
+
187
+ ### 3. Create the container configuration
188
+
189
+ ```typescript
190
+ // config.ts
191
+ import { defineBuilderConfig, definePartialConfig } from '@djodjonx/wiredi'
192
+
193
+ // Reusable partial configuration
194
+ const loggingPartial = definePartialConfig({
195
+ injections: [
196
+ { token: TOKENS.Logger, provider: ConsoleLogger },
197
+ ],
198
+ listeners: [],
199
+ })
200
+
201
+ // Main configuration
202
+ export const appConfig = defineBuilderConfig({
203
+ builderId: 'app.main',
204
+ extends: [loggingPartial], // Inherits injections from partial
205
+ injections: [
206
+ { token: TOKENS.UserRepository, provider: UserRepository },
207
+ { token: UserService }, // Class used as token
208
+ ],
209
+ listeners: [],
210
+ })
211
+ ```
212
+
213
+ ### 4. Use the builder
214
+
215
+ ```typescript
216
+ // anywhere.ts
217
+ import useBuilder from '@djodjonx/wiredi'
218
+ import { appConfig } from './config'
219
+
220
+ const { resolve } = useBuilder(appConfig)
221
+
222
+ // Resolve dependencies with automatic typing
223
+ const userService = resolve(UserService)
224
+ const logger = resolve(TOKENS.Logger)
225
+ ```
226
+
227
+ ## IDE Plugin for Real-Time Validation
228
+
229
+ The TypeScript Language Service plugin detects configuration errors **directly in your IDE**.
230
+
231
+ ### Plugin Installation
232
+
233
+ 1. **Add the plugin** to your `tsconfig.json`:
234
+
235
+ ```json
236
+ {
237
+ "compilerOptions": {
238
+ "plugins": [
239
+ {
240
+ "name": "@djodjonx/wiredi/plugin"
241
+ }
242
+ ]
243
+ }
244
+ }
245
+ ```
246
+
247
+ 2. **Configure your IDE** to use the project's TypeScript version:
248
+
249
+ **VS Code:**
250
+ - `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux)
251
+ - Type "TypeScript: Select TypeScript Version"
252
+ - Choose **"Use Workspace Version"**
253
+
254
+ **IntelliJ IDEA / WebStorm:**
255
+ - **Settings** → **Languages & Frameworks** → **TypeScript**
256
+ - Ensure TypeScript points to `node_modules/typescript`
257
+ - Check **"Use TypeScript Language Service"**
258
+ - Restart the IDE
259
+
260
+ ### Detected Errors
261
+
262
+ | Error Type | Description |
263
+ |------------|-------------|
264
+ | 🔴 Missing dependency | A service requires an unregistered token |
265
+ | 🔴 Type mismatch | The provider doesn't implement the expected interface |
266
+ | 🔴 Unregistered @inject token | A decorator references a missing token |
267
+
268
+ ### Error Example
269
+
270
+ ```typescript
271
+ // ❌ ERROR: ConsoleLogger doesn't implement UserRepositoryInterface
272
+ const config = defineBuilderConfig({
273
+ builderId: 'app',
274
+ injections: [
275
+ { token: TOKENS.UserRepository, provider: ConsoleLogger }, // Error here!
276
+ ],
277
+ listeners: [],
278
+ })
279
+ ```
280
+
281
+ The error appears on the provider line, even if it's defined in a separate partial file.
282
+
283
+ ### Plugin Options
284
+
285
+ ```json
286
+ {
287
+ "compilerOptions": {
288
+ "plugins": [
289
+ {
290
+ "name": "@djodjonx/wiredi/plugin",
291
+ "verbose": true // Enable debug logs
292
+ }
293
+ ]
294
+ }
295
+ }
296
+ ```
297
+
298
+ ## Injection Types
299
+
300
+ ### Class as token
301
+
302
+ ```typescript
303
+ { token: UserService }
304
+ ```
305
+
306
+ ### Symbol with provider
307
+
308
+ ```typescript
309
+ { token: TOKENS.Logger, provider: ConsoleLogger }
310
+ ```
311
+
312
+ ### With custom lifecycle
313
+
314
+ ```typescript
315
+ import { ProviderLifecycle } from '@djodjonx/wiredi'
316
+
317
+ { token: UserService, lifecycle: ProviderLifecycle.Transient }
318
+ ```
319
+
320
+ | Lifecycle | Description |
321
+ |-----------|-------------|
322
+ | `Singleton` | Single instance (default) |
323
+ | `Transient` | New instance on each resolution |
324
+ | `Scoped` | One instance per scope/request |
325
+
326
+ ### Value injection
327
+
328
+ ```typescript
329
+ { token: TOKENS.ApiUrl, value: (context) => 'https://api.example.com' }
330
+ ```
331
+
332
+ ### Factory
333
+
334
+ ```typescript
335
+ {
336
+ token: TOKENS.HttpClient,
337
+ factory: (provider) => new HttpClient(provider.resolve(TOKENS.ApiUrl))
338
+ }
339
+ ```
340
+
341
+ ## Partials System
342
+
343
+ Partials allow you to **reuse configurations** across multiple builders:
344
+
345
+ ```typescript
346
+ // partials/logging.ts
347
+ export const loggingPartial = definePartialConfig({
348
+ injections: [
349
+ { token: TOKENS.Logger, provider: ConsoleLogger },
350
+ ],
351
+ listeners: [],
352
+ })
353
+
354
+ // partials/repositories.ts
355
+ export const repositoriesPartial = definePartialConfig({
356
+ injections: [
357
+ { token: TOKENS.UserRepository, provider: PostgresUserRepository },
358
+ { token: TOKENS.ProductRepository, provider: PostgresProductRepository },
359
+ ],
360
+ listeners: [],
361
+ })
362
+
363
+ // config.ts
364
+ export const appConfig = defineBuilderConfig({
365
+ builderId: 'app.main',
366
+ extends: [loggingPartial, repositoriesPartial],
367
+ injections: [
368
+ { token: UserService },
369
+ { token: ProductService },
370
+ ],
371
+ listeners: [],
372
+ })
373
+ ```
374
+
375
+ ### Token Uniqueness
376
+
377
+ **Important**: Each token must be unique across all partials and the main configuration.
378
+
379
+ ```typescript
380
+ // ❌ ERROR: Token collision
381
+ const loggingPartial = definePartialConfig({
382
+ injections: [
383
+ { token: TOKENS.Logger, provider: ConsoleLogger }
384
+ ],
385
+ listeners: []
386
+ })
387
+
388
+ export const appConfig = defineBuilderConfig({
389
+ builderId: 'app.main',
390
+ extends: [loggingPartial],
391
+ injections: [
392
+ // ❌ This will cause a TypeScript error - token already defined in partial
393
+ { token: TOKENS.Logger, provider: FileLogger }
394
+ ],
395
+ listeners: []
396
+ })
397
+ ```
398
+
399
+ **For testing**, create a separate configuration without the conflicting partial:
400
+
401
+ ```typescript
402
+ // ✅ Correct approach for testing
403
+ export const testConfig = defineBuilderConfig({
404
+ builderId: 'app.test',
405
+ extends: [], // Don't extend the partial with production logger
406
+ injections: [
407
+ { token: TOKENS.Logger, provider: MockLogger }, // ✅ OK - no collision
408
+ { token: UserService },
409
+ ],
410
+ listeners: []
411
+ })
412
+ ```
413
+
414
+ ## Creating a Custom Provider
415
+
416
+ To use an unsupported DI container, implement the `ContainerProvider` interface:
417
+
418
+ ```typescript
419
+ import type { ContainerProvider, ProviderLifecycle } from '@djodjonx/wiredi'
420
+
421
+ class MyCustomProvider implements ContainerProvider {
422
+ readonly name = 'my-provider'
423
+
424
+ registerValue<T>(token: symbol, value: T): void { /* ... */ }
425
+ registerFactory<T>(token: symbol, factory: (p: ContainerProvider) => T): void { /* ... */ }
426
+ registerClass<T>(token: symbol | Constructor<T>, impl?: Constructor<T>, lifecycle?: ProviderLifecycle): void { /* ... */ }
427
+ isRegistered(token: symbol | Constructor): boolean { /* ... */ }
428
+ resolve<T>(token: symbol | Constructor<T>): T { /* ... */ }
429
+ createScope(): ContainerProvider { /* ... */ }
430
+ dispose(): void { /* ... */ }
431
+ getUnderlyingContainer(): unknown { /* ... */ }
432
+ }
433
+ ```
434
+
435
+ ## Full Examples
436
+
437
+ Check the [`examples/`](./examples) folder for comprehensive examples:
438
+
439
+ ### DI Container Integration
440
+ - [tsyringe](./examples/di-containers/with-tsyringe.ts) - Microsoft's lightweight DI container
441
+ - [Awilix](./examples/di-containers/with-awilix.ts) - Powerful proxy-based injection
442
+ - [InversifyJS](./examples/di-containers/with-inversify.ts) - Feature-rich IoC container
443
+
444
+ ### Event Dispatcher Implementations
445
+ - [RxJS Provider](./examples/event-dispatcher/RxJsEventDispatcherProvider.ts) - Reactive programming
446
+ - [EventEmitter Provider](./examples/event-dispatcher/EventEmitterDispatcherProvider.ts) - Node.js built-in
447
+ - [Priority Provider](./examples/event-dispatcher/AsyncPriorityEventDispatcherProvider.ts) - Ordered workflows
448
+
449
+ **See**: [Examples Guide](./examples/README.md) for detailed documentation and learning path.
450
+
451
+ ## API Reference
452
+
453
+ ### Provider Management
454
+
455
+ ```typescript
456
+ useContainerProvider(provider: ContainerProvider): void // Configure the global provider
457
+ getContainerProvider(): ContainerProvider // Get the provider
458
+ hasContainerProvider(): boolean // Check if a provider is configured
459
+ resetContainerProvider(): void // Reset (for tests)
460
+ ```
461
+
462
+ ### Event Dispatcher (optional)
463
+
464
+ ```typescript
465
+ import {
466
+ useEventDispatcherProvider,
467
+ MutableEventDispatcherProvider,
468
+ getEventDispatcherProvider
469
+ } from '@djodjonx/wiredi'
470
+
471
+ // Configuration
472
+ useEventDispatcherProvider(new MutableEventDispatcherProvider({
473
+ containerProvider: getContainerProvider(),
474
+ }))
475
+
476
+ // Dispatch events
477
+ getEventDispatcherProvider().dispatch(new UserCreatedEvent(user))
478
+ ```
479
+
480
+ ## Documentation
481
+
482
+ ### API Documentation
483
+
484
+ Full API documentation is available online and can be generated locally:
485
+
486
+ **Online**: [View API Documentation](https://[your-username].github.io/WireDI/) *(configure GitHub Pages)*
487
+
488
+ **Generate locally**:
489
+ ```bash
490
+ pnpm docs
491
+ open docs/api/index.html
492
+ ```
493
+
494
+ ### Guides
495
+
496
+ - [Quick Start Guide](./README.md#quick-start) - Get started in 4 steps
497
+ - [Plugin Installation](./README.md#ide-plugin-for-real-time-validation) - IDE integration
498
+ - [Provider Examples](./examples/) - Integration with tsyringe, Awilix, InversifyJS
499
+ - [JSDoc Summary](docs/Agent/JSDOC_SUMMARY.md) - Documentation standards
500
+
501
+ ## Troubleshooting
502
+
503
+ ### The plugin doesn't detect errors
504
+
505
+ 1. Verify that TypeScript uses the workspace version
506
+ 2. Restart the TypeScript server (`Cmd+Shift+P` → "TypeScript: Restart TS Server")
507
+ 3. Enable `verbose` mode to see logs
508
+
509
+ ### Symbol tokens cause false positives
510
+
511
+ TypeScript sees all `Symbol()` as the same type. To avoid type collisions with partials, use classes as tokens or define your tokens without `as const`.
512
+
513
+ ## License
514
+
515
+ MIT
516
+