@djodjonx/wiredi 0.0.11 → 0.0.12
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/README.md +147 -20
- package/dist/index.cjs +12 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -17
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +70 -17
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +12 -4
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/ConfigurationAnalyzer.d.ts +59 -0
- package/dist/plugin/ConfigurationAnalyzer.d.ts.map +1 -0
- package/dist/plugin/ConfigurationAnalyzer.js +321 -0
- package/dist/plugin/ConfigurationAnalyzer.js.map +1 -0
- package/dist/plugin/DependencyAnalyzer.d.ts +54 -0
- package/dist/plugin/DependencyAnalyzer.d.ts.map +1 -0
- package/dist/plugin/DependencyAnalyzer.js +208 -0
- package/dist/plugin/DependencyAnalyzer.js.map +1 -0
- package/dist/plugin/TokenNormalizer.d.ts +51 -0
- package/dist/plugin/TokenNormalizer.d.ts.map +1 -0
- package/dist/plugin/TokenNormalizer.js +208 -0
- package/dist/plugin/TokenNormalizer.js.map +1 -0
- package/dist/plugin/ValidationEngine.d.ts +53 -0
- package/dist/plugin/ValidationEngine.d.ts.map +1 -0
- package/dist/plugin/ValidationEngine.js +250 -0
- package/dist/plugin/ValidationEngine.js.map +1 -0
- package/dist/plugin/index.d.ts +2 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +144 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/package.json +6 -0
- package/dist/plugin/types.d.ts +152 -0
- package/dist/plugin/types.d.ts.map +1 -0
- package/dist/plugin/types.js +3 -0
- package/dist/plugin/types.js.map +1 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<h1 align="center">Wire your dependency injection with type safety</h1>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
|
-
<strong>WireDI</strong> is
|
|
16
|
+
<strong>WireDI</strong> is a declarative, type-safe Dependency Injection builder. It eliminates "autowire" magic in favor of explicit, compile-time validated definitions, ensuring your application is free from missing dependencies and type mismatches.
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
---
|
|
@@ -33,9 +33,24 @@
|
|
|
33
33
|
|
|
34
34
|
## Why WireDI?
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Traditional DI containers (like tsyringe, InversifyJS) are powerful but often rely on runtime "magic" (autowiring) that can lead to:
|
|
37
|
+
- 💥 **Runtime errors** when dependencies are missing.
|
|
38
|
+
- 🐛 **Silent failures** when incorrect types are injected.
|
|
37
39
|
|
|
38
|
-
**
|
|
40
|
+
**WireDI** solves this by shifting validation to **compile-time**:
|
|
41
|
+
|
|
42
|
+
### 1. Declarative & Explicit
|
|
43
|
+
WireDI ignores autowiring in favor of **explicit declarations**. You define exactly what is injected. This ensures you never have a "missing dependency" error in production and your dependency graph is transparent.
|
|
44
|
+
|
|
45
|
+
### 2. Smart & Safe Registration
|
|
46
|
+
- **Singleton by Default**: All dependencies are registered as singletons, ensuring state consistency across your application.
|
|
47
|
+
- **Idempotent Builder**: The `useBuilder` system prevents double registration. If multiple builders try to register the same token in the same container, WireDI respects the existing one. This allows you to safely compose overlapping modules without conflicts.
|
|
48
|
+
|
|
49
|
+
### 3. Modular "Extend" Pattern
|
|
50
|
+
Build your app like Lego. Create **partial configurations** for specific domains (e.g., Auth, Database, Logging) and **extend** them in your main application builder. This separation of concerns makes your config maintainable and testable.
|
|
51
|
+
|
|
52
|
+
### 4. Real-Time Validation
|
|
53
|
+
Errors are detected **in your IDE** instantly:
|
|
39
54
|
|
|
40
55
|
```typescript
|
|
41
56
|
// ❌ Error detected in IDE: "Logger" is not registered
|
|
@@ -44,7 +59,7 @@ const config = defineBuilderConfig({
|
|
|
44
59
|
injections: [
|
|
45
60
|
{ token: UserService }, // UserService depends on Logger
|
|
46
61
|
],
|
|
47
|
-
listeners
|
|
62
|
+
// listeners is optional
|
|
48
63
|
})
|
|
49
64
|
```
|
|
50
65
|
|
|
@@ -57,7 +72,7 @@ Unlike traditional DI containers, **WireDI's type checking works without decorat
|
|
|
57
72
|
- ✅ No need for `@injectable` or `@inject` decorators
|
|
58
73
|
- ✅ Framework-agnostic type safety
|
|
59
74
|
|
|
60
|
-
**Learn more**: [Type Checking Without Decorators](docs/
|
|
75
|
+
**Learn more**: [Type Checking Without Decorators](docs/TYPE_CHECKING_WITHOUT_DECORATORS.md)
|
|
61
76
|
|
|
62
77
|
## Installation
|
|
63
78
|
|
|
@@ -195,7 +210,7 @@ const loggingPartial = definePartialConfig({
|
|
|
195
210
|
injections: [
|
|
196
211
|
{ token: TOKENS.Logger, provider: ConsoleLogger },
|
|
197
212
|
],
|
|
198
|
-
listeners
|
|
213
|
+
// listeners is optional - omit if you don't need event handling
|
|
199
214
|
})
|
|
200
215
|
|
|
201
216
|
// Main configuration
|
|
@@ -206,9 +221,10 @@ export const appConfig = defineBuilderConfig({
|
|
|
206
221
|
{ token: TOKENS.UserRepository, provider: UserRepository },
|
|
207
222
|
{ token: UserService }, // Class used as token
|
|
208
223
|
],
|
|
209
|
-
listeners
|
|
224
|
+
// listeners is optional - only add if you need event handling
|
|
210
225
|
})
|
|
211
226
|
```
|
|
227
|
+
```
|
|
212
228
|
|
|
213
229
|
### 4. Use the builder
|
|
214
230
|
|
|
@@ -259,11 +275,13 @@ The TypeScript Language Service plugin detects configuration errors **directly i
|
|
|
259
275
|
|
|
260
276
|
### Detected Errors
|
|
261
277
|
|
|
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
|
-
| 🔴
|
|
278
|
+
| Error Type | Description | Error Message |
|
|
279
|
+
|------------|-------------|---------------|
|
|
280
|
+
| 🔴 Missing dependency | A service requires an unregistered token | `[WireDI] Missing dependency: ...` |
|
|
281
|
+
| 🔴 Type mismatch | The provider doesn't implement the expected interface | `[WireDI] Type incompatible: ...` |
|
|
282
|
+
| 🔴 Token collision | Token already registered in a partial | `[WireDI] This token is already registered in a partial` |
|
|
283
|
+
| 🔴 Duplicate listener | Same (event, listener) pair registered twice | `[WireDI] Duplicate listener in the same configuration` |
|
|
284
|
+
| 🔴 Listener collision | Listener already registered in a partial | `[WireDI] This event listener is already registered in a partial` |
|
|
267
285
|
|
|
268
286
|
### Error Example
|
|
269
287
|
|
|
@@ -274,7 +292,7 @@ const config = defineBuilderConfig({
|
|
|
274
292
|
injections: [
|
|
275
293
|
{ token: TOKENS.UserRepository, provider: ConsoleLogger }, // Error here!
|
|
276
294
|
],
|
|
277
|
-
listeners
|
|
295
|
+
// listeners is optional
|
|
278
296
|
})
|
|
279
297
|
```
|
|
280
298
|
|
|
@@ -348,7 +366,6 @@ export const loggingPartial = definePartialConfig({
|
|
|
348
366
|
injections: [
|
|
349
367
|
{ token: TOKENS.Logger, provider: ConsoleLogger },
|
|
350
368
|
],
|
|
351
|
-
listeners: [],
|
|
352
369
|
})
|
|
353
370
|
|
|
354
371
|
// partials/repositories.ts
|
|
@@ -357,7 +374,6 @@ export const repositoriesPartial = definePartialConfig({
|
|
|
357
374
|
{ token: TOKENS.UserRepository, provider: PostgresUserRepository },
|
|
358
375
|
{ token: TOKENS.ProductRepository, provider: PostgresProductRepository },
|
|
359
376
|
],
|
|
360
|
-
listeners: [],
|
|
361
377
|
})
|
|
362
378
|
|
|
363
379
|
// config.ts
|
|
@@ -368,7 +384,6 @@ export const appConfig = defineBuilderConfig({
|
|
|
368
384
|
{ token: UserService },
|
|
369
385
|
{ token: ProductService },
|
|
370
386
|
],
|
|
371
|
-
listeners: [],
|
|
372
387
|
})
|
|
373
388
|
```
|
|
374
389
|
|
|
@@ -382,7 +397,6 @@ const loggingPartial = definePartialConfig({
|
|
|
382
397
|
injections: [
|
|
383
398
|
{ token: TOKENS.Logger, provider: ConsoleLogger }
|
|
384
399
|
],
|
|
385
|
-
listeners: []
|
|
386
400
|
})
|
|
387
401
|
|
|
388
402
|
export const appConfig = defineBuilderConfig({
|
|
@@ -392,7 +406,6 @@ export const appConfig = defineBuilderConfig({
|
|
|
392
406
|
// ❌ This will cause a TypeScript error - token already defined in partial
|
|
393
407
|
{ token: TOKENS.Logger, provider: FileLogger }
|
|
394
408
|
],
|
|
395
|
-
listeners: []
|
|
396
409
|
})
|
|
397
410
|
```
|
|
398
411
|
|
|
@@ -407,10 +420,125 @@ export const testConfig = defineBuilderConfig({
|
|
|
407
420
|
{ token: TOKENS.Logger, provider: MockLogger }, // ✅ OK - no collision
|
|
408
421
|
{ token: UserService },
|
|
409
422
|
],
|
|
410
|
-
listeners: []
|
|
411
423
|
})
|
|
412
424
|
```
|
|
413
425
|
|
|
426
|
+
### Listener Uniqueness
|
|
427
|
+
|
|
428
|
+
Similar to tokens, **each (event, listener) pair must be unique** across all partials and the main configuration:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
// ❌ ERROR: Duplicate listener in the same configuration
|
|
432
|
+
const config = defineBuilderConfig({
|
|
433
|
+
builderId: 'app',
|
|
434
|
+
injections: [],
|
|
435
|
+
listeners: [
|
|
436
|
+
{ event: UserCreatedEvent, listener: EmailNotificationListener },
|
|
437
|
+
{ event: UserCreatedEvent, listener: EmailNotificationListener }, // ❌ Duplicate!
|
|
438
|
+
],
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// ❌ ERROR: Listener already in partial
|
|
442
|
+
const eventPartial = definePartialConfig({
|
|
443
|
+
listeners: [
|
|
444
|
+
{ event: UserCreatedEvent, listener: EmailNotificationListener }
|
|
445
|
+
],
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
const config = defineBuilderConfig({
|
|
449
|
+
builderId: 'app',
|
|
450
|
+
extends: [eventPartial],
|
|
451
|
+
injections: [],
|
|
452
|
+
listeners: [
|
|
453
|
+
{ event: UserCreatedEvent, listener: EmailNotificationListener }, // ❌ Already in partial!
|
|
454
|
+
],
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
// ✅ OK: Different listener for the same event
|
|
458
|
+
const validConfig = defineBuilderConfig({
|
|
459
|
+
builderId: 'app',
|
|
460
|
+
injections: [],
|
|
461
|
+
listeners: [
|
|
462
|
+
{ event: UserCreatedEvent, listener: EmailNotificationListener },
|
|
463
|
+
{ event: UserCreatedEvent, listener: SmsNotificationListener }, // ✅ Different listener
|
|
464
|
+
],
|
|
465
|
+
})
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Event Programming
|
|
469
|
+
|
|
470
|
+
> **Note**: The `listeners` property is **optional**. If your application doesn't use events, you can omit it entirely from your configuration.
|
|
471
|
+
|
|
472
|
+
### Why Centralized Event Listeners?
|
|
473
|
+
In traditional event-driven architectures, event listeners are often scattered across the codebase (e.g., manual `dispatcher.on(...)` calls inside constructors or initialization scripts). This makes it hard to visualize the system's reactive flow.
|
|
474
|
+
|
|
475
|
+
WireDI treats event listeners as **part of your application's structural configuration**. By declaring them alongside your dependency injections, you achieve:
|
|
476
|
+
- **🔍 Visibility**: See exactly **who listens to what** in a single configuration file.
|
|
477
|
+
- **🧩 Decoupling**: Services don't need to know about the dispatcher; they just implement `onEvent`.
|
|
478
|
+
- **🛡️ Safety**: Compile-time validation ensures your listener is compatible with the event.
|
|
479
|
+
|
|
480
|
+
### Usage
|
|
481
|
+
WireDI allows you to bind events to listeners declaratively:
|
|
482
|
+
|
|
483
|
+
### 1. Enable Event Support
|
|
484
|
+
First, configure the `EventDispatcherProvider` at startup (after the container provider):
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import {
|
|
488
|
+
useEventDispatcherProvider,
|
|
489
|
+
MutableEventDispatcherProvider,
|
|
490
|
+
getContainerProvider
|
|
491
|
+
} from '@djodjonx/wiredi'
|
|
492
|
+
|
|
493
|
+
useEventDispatcherProvider(new MutableEventDispatcherProvider({
|
|
494
|
+
containerProvider: getContainerProvider(),
|
|
495
|
+
}))
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### 2. Define Events and Listeners
|
|
499
|
+
Events are simple classes. Listeners are services that implement an `onEvent` method.
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
// events/UserCreatedEvent.ts
|
|
503
|
+
export class UserCreatedEvent {
|
|
504
|
+
constructor(public readonly user: User) {}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// listeners/SendWelcomeEmail.ts
|
|
508
|
+
export class SendWelcomeEmail {
|
|
509
|
+
constructor(private mailer: MailerService) {}
|
|
510
|
+
|
|
511
|
+
onEvent(event: UserCreatedEvent) {
|
|
512
|
+
this.mailer.send(event.user.email, 'Welcome!')
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### 3. Wire in Configuration
|
|
518
|
+
Bind them in your builder configuration using the `listeners` property:
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
const appConfig = defineBuilderConfig({
|
|
522
|
+
builderId: 'app',
|
|
523
|
+
injections: [
|
|
524
|
+
{ token: SendWelcomeEmail }, // Register the listener itself
|
|
525
|
+
{ token: MailerService },
|
|
526
|
+
],
|
|
527
|
+
listeners: [
|
|
528
|
+
// Bind event -> listener
|
|
529
|
+
{ event: UserCreatedEvent, listener: SendWelcomeEmail },
|
|
530
|
+
],
|
|
531
|
+
})
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Now, when you dispatch an event:
|
|
535
|
+
```typescript
|
|
536
|
+
import { getEventDispatcherProvider } from '@djodjonx/wiredi'
|
|
537
|
+
|
|
538
|
+
getEventDispatcherProvider().dispatch(new UserCreatedEvent(newUser))
|
|
539
|
+
// -> SendWelcomeEmail.onEvent() is automatically called
|
|
540
|
+
```
|
|
541
|
+
|
|
414
542
|
## Creating a Custom Provider
|
|
415
543
|
|
|
416
544
|
To use an unsupported DI container, implement the `ContainerProvider` interface:
|
|
@@ -496,7 +624,6 @@ open docs/api/index.html
|
|
|
496
624
|
- [Quick Start Guide](./README.md#quick-start) - Get started in 4 steps
|
|
497
625
|
- [Plugin Installation](./README.md#ide-plugin-for-real-time-validation) - IDE integration
|
|
498
626
|
- [Provider Examples](./examples/) - Integration with tsyringe, Awilix, InversifyJS
|
|
499
|
-
- [JSDoc Summary](docs/Agent/JSDOC_SUMMARY.md) - Documentation standards
|
|
500
627
|
|
|
501
628
|
## Troubleshooting
|
|
502
629
|
|
package/dist/index.cjs
CHANGED
|
@@ -620,7 +620,8 @@ var MutableEventDispatcherProvider = class {
|
|
|
620
620
|
for (const listenerToken of listenerTokens) try {
|
|
621
621
|
this.containerProvider.resolve(listenerToken).onEvent(event);
|
|
622
622
|
} catch (error) {
|
|
623
|
-
|
|
623
|
+
const errorMessage = error instanceof Error ? error.stack || error.message : String(error);
|
|
624
|
+
console.error(`[MutableEventDispatcherProvider] Error dispatching event "${eventName}":`, errorMessage);
|
|
624
625
|
throw error;
|
|
625
626
|
}
|
|
626
627
|
}
|
|
@@ -631,6 +632,13 @@ var MutableEventDispatcherProvider = class {
|
|
|
631
632
|
return listeners !== void 0 && listeners.length > 0;
|
|
632
633
|
}
|
|
633
634
|
/** @inheritdoc */
|
|
635
|
+
hasListener(eventToken, listenerToken) {
|
|
636
|
+
const eventName = this.getEventName(eventToken);
|
|
637
|
+
const listeners = this.listeners.get(eventName);
|
|
638
|
+
if (!listeners) return false;
|
|
639
|
+
return listeners.includes(listenerToken);
|
|
640
|
+
}
|
|
641
|
+
/** @inheritdoc */
|
|
634
642
|
clearListeners(eventToken) {
|
|
635
643
|
const eventName = this.getEventName(eventToken);
|
|
636
644
|
this.listeners.delete(eventName);
|
|
@@ -765,11 +773,11 @@ function registerConfig(provider, injections, context) {
|
|
|
765
773
|
});
|
|
766
774
|
}
|
|
767
775
|
function registerEvent(_provider, listeners) {
|
|
768
|
-
if (!listeners.length) return;
|
|
776
|
+
if (!listeners || !listeners.length) return;
|
|
769
777
|
if (!hasEventDispatcherProvider()) return;
|
|
770
778
|
const eventDispatcher = getEventDispatcherProvider();
|
|
771
779
|
listeners.forEach((configEntry) => {
|
|
772
|
-
eventDispatcher.register(configEntry.event, configEntry.listener);
|
|
780
|
+
if (!eventDispatcher.hasListener(configEntry.event, configEntry.listener)) eventDispatcher.register(configEntry.event, configEntry.listener);
|
|
773
781
|
});
|
|
774
782
|
}
|
|
775
783
|
/**
|
|
@@ -864,7 +872,7 @@ function definePartialConfig(config) {
|
|
|
864
872
|
*/
|
|
865
873
|
function defineBuilderConfig(config) {
|
|
866
874
|
const mergedInjections = [...config.injections, ...(config.extends || []).flatMap((p) => p.injections || [])];
|
|
867
|
-
const mergedListeners = [...(config.extends || []).flatMap((p) => p.listeners || []), ...config.listeners];
|
|
875
|
+
const mergedListeners = [...(config.extends || []).flatMap((p) => p.listeners || []), ...config.listeners || []];
|
|
868
876
|
return {
|
|
869
877
|
...config,
|
|
870
878
|
injections: mergedInjections,
|