@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/dist/index.cjs ADDED
@@ -0,0 +1,931 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+ let awilix = require("awilix");
3
+
4
+ //#region src/Provider/types.ts
5
+ /**
6
+ * Dependency lifecycle options
7
+ * Independent of the underlying container implementation
8
+ */
9
+ let ProviderLifecycle = /* @__PURE__ */ function(ProviderLifecycle) {
10
+ /** Single shared instance throughout the application (default) */
11
+ ProviderLifecycle["Singleton"] = "singleton";
12
+ /** New instance created on each resolution */
13
+ ProviderLifecycle["Transient"] = "transient";
14
+ /** One instance per scope/child container */
15
+ ProviderLifecycle["Scoped"] = "scoped";
16
+ return ProviderLifecycle;
17
+ }({});
18
+
19
+ //#endregion
20
+ //#region src/Provider/ProviderManager.ts
21
+ /**
22
+ * Currently configured global provider
23
+ * @internal
24
+ */
25
+ let currentProvider = null;
26
+ /**
27
+ * Configures the DI container provider to use globally
28
+ *
29
+ * Must be called ONCE at the application entry point,
30
+ * before any calls to `useBuilder`.
31
+ *
32
+ * @param provider - The provider instance to use
33
+ * @throws Error if a provider is already configured
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // main.ts - Application entry point
38
+ * import 'reflect-metadata'
39
+ * import { container, Lifecycle } from 'tsyringe'
40
+ * import { useContainerProvider, TsyringeProvider } from '@djodjonx/wiredi'
41
+ *
42
+ * useContainerProvider(new TsyringeProvider({ container, Lifecycle }))
43
+ *
44
+ * // Now useBuilder can be used anywhere
45
+ * ```
46
+ */
47
+ function useContainerProvider(provider) {
48
+ if (currentProvider !== null) throw new Error(`[WireDI] Provider already configured (${currentProvider.name}). useContainerProvider() should only be called once at app entry point. Use resetContainerProvider() first if you need to reconfigure.`);
49
+ currentProvider = provider;
50
+ }
51
+ /**
52
+ * Retrieves the configured container provider
53
+ *
54
+ * @returns The configured provider instance
55
+ * @throws Error if no provider is configured
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const provider = getContainerProvider()
60
+ * const service = provider.resolve(MyService)
61
+ * ```
62
+ */
63
+ function getContainerProvider() {
64
+ if (currentProvider === null) throw new Error("[WireDI] No container provider configured. Call useContainerProvider(provider) at your app entry point before using useBuilder. Example: useContainerProvider(new TsyringeProvider({ container, Lifecycle }))");
65
+ return currentProvider;
66
+ }
67
+ /**
68
+ * Checks if a provider is currently configured
69
+ *
70
+ * @returns `true` if a provider is configured, `false` otherwise
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * if (!hasContainerProvider()) {
75
+ * useContainerProvider(new TsyringeProvider({ container, Lifecycle }))
76
+ * }
77
+ * ```
78
+ */
79
+ function hasContainerProvider() {
80
+ return currentProvider !== null;
81
+ }
82
+ /**
83
+ * Resets the global provider (primarily for testing)
84
+ *
85
+ * ⚠️ WARNING: Do not use in production, only for testing purposes
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * // In a test file
90
+ * beforeEach(() => {
91
+ * resetContainerProvider()
92
+ * useContainerProvider(new TsyringeProvider({ container, Lifecycle }))
93
+ * })
94
+ * ```
95
+ */
96
+ function resetContainerProvider() {
97
+ if (currentProvider !== null) try {
98
+ currentProvider.dispose();
99
+ } catch {}
100
+ currentProvider = null;
101
+ }
102
+
103
+ //#endregion
104
+ //#region src/Provider/adapters/TsyringeProvider.ts
105
+ /**
106
+ * tsyringe adapter implementing the ContainerProvider interface
107
+ *
108
+ * Provides integration between WireDI and tsyringe,
109
+ * allowing you to use tsyringe as your DI container while benefiting
110
+ * from WireDI's configuration and validation features.
111
+ */
112
+ var TsyringeProvider = class TsyringeProvider {
113
+ /** @inheritdoc */
114
+ name = "tsyringe";
115
+ /** The tsyringe container instance */
116
+ container;
117
+ /** The tsyringe Lifecycle enum reference */
118
+ Lifecycle;
119
+ /**
120
+ * Creates a new TsyringeProvider instance
121
+ *
122
+ * @param dependencies - tsyringe dependencies (container and Lifecycle)
123
+ * @param options - Configuration options
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import { container, Lifecycle } from 'tsyringe'
128
+ *
129
+ * const provider = new TsyringeProvider(
130
+ * { container, Lifecycle },
131
+ * { useChildContainer: true }
132
+ * )
133
+ * ```
134
+ */
135
+ constructor(dependencies, options = {}) {
136
+ this.Lifecycle = dependencies.Lifecycle;
137
+ this.container = options.useChildContainer ? dependencies.container.createChildContainer() : dependencies.container;
138
+ }
139
+ /** @inheritdoc */
140
+ registerValue(token, value) {
141
+ this.container.register(token, { useValue: value });
142
+ }
143
+ /** @inheritdoc */
144
+ registerFactory(token, factory) {
145
+ this.container.register(token, { useFactory: () => factory(this) });
146
+ }
147
+ /** @inheritdoc */
148
+ registerClass(token, implementation, lifecycle = ProviderLifecycle.Singleton) {
149
+ const tsyringeLifecycle = this.mapLifecycle(lifecycle);
150
+ if (this.isConstructor(token)) this.container.register(token, { useClass: implementation ?? token }, { lifecycle: tsyringeLifecycle });
151
+ else {
152
+ if (!implementation) throw new Error(`[TsyringeProvider] Implementation required when registering symbol token: ${String(token)}`);
153
+ this.container.register(token, { useClass: implementation }, { lifecycle: tsyringeLifecycle });
154
+ }
155
+ }
156
+ /** @inheritdoc */
157
+ isRegistered(token) {
158
+ return this.container.isRegistered(token);
159
+ }
160
+ /** @inheritdoc */
161
+ resolve(token) {
162
+ return this.container.resolve(token);
163
+ }
164
+ /** @inheritdoc */
165
+ createScope() {
166
+ return new TsyringeProvider({
167
+ container: this.container.createChildContainer(),
168
+ Lifecycle: this.Lifecycle
169
+ });
170
+ }
171
+ /** @inheritdoc */
172
+ dispose() {
173
+ try {
174
+ this.container.clearInstances();
175
+ } catch {}
176
+ }
177
+ /**
178
+ * Returns the underlying tsyringe DependencyContainer
179
+ * @returns The tsyringe container instance
180
+ */
181
+ getUnderlyingContainer() {
182
+ return this.container;
183
+ }
184
+ /**
185
+ * Maps WireDI lifecycle to tsyringe Lifecycle
186
+ * @internal
187
+ */
188
+ mapLifecycle(lifecycle) {
189
+ switch (lifecycle) {
190
+ case ProviderLifecycle.Singleton: return this.Lifecycle.Singleton;
191
+ case ProviderLifecycle.Transient: return this.Lifecycle.Transient;
192
+ case ProviderLifecycle.Scoped: return this.Lifecycle.ContainerScoped;
193
+ default: return this.Lifecycle.Singleton;
194
+ }
195
+ }
196
+ /**
197
+ * Type guard to check if a token is a class constructor
198
+ * @internal
199
+ */
200
+ isConstructor(token) {
201
+ return typeof token === "function" && !!token.prototype && token.prototype.constructor === token;
202
+ }
203
+ };
204
+
205
+ //#endregion
206
+ //#region src/Provider/adapters/AwilixProvider.ts
207
+ /**
208
+ * Awilix adapter implementing the ContainerProvider interface
209
+ *
210
+ * Provides integration between WireDI and Awilix,
211
+ * allowing you to use Awilix as your DI container while benefiting
212
+ * from WireDI's configuration and validation features.
213
+ */
214
+ var AwilixProvider = class AwilixProvider {
215
+ /** @inheritdoc */
216
+ name = "awilix";
217
+ /** The Awilix container instance */
218
+ container;
219
+ /** Maps tokens to Awilix string-based registration names */
220
+ tokenToName = /* @__PURE__ */ new Map();
221
+ /** Counter for generating unique token names */
222
+ nameCounter = 0;
223
+ /** Lazily loaded awilix module */
224
+ awilix = null;
225
+ /**
226
+ * Creates a new AwilixProvider instance
227
+ *
228
+ * @param options - Configuration options
229
+ *
230
+ * @remarks
231
+ * When using the constructor directly, you must call `init()` before use,
232
+ * or use `createSync()` for synchronous initialization.
233
+ */
234
+ constructor(options = {}) {
235
+ this.options = options;
236
+ this.container = null;
237
+ }
238
+ /**
239
+ * Lazily initializes the container (async)
240
+ * @internal
241
+ */
242
+ async ensureInitialized() {
243
+ if (this.container) return;
244
+ this.awilix = await import("awilix");
245
+ this.container = this.options.container ?? this.awilix.createContainer({ injectionMode: this.options.injectionMode === "CLASSIC" ? this.awilix.InjectionMode.CLASSIC : this.awilix.InjectionMode.PROXY });
246
+ }
247
+ /**
248
+ * Throws if container is not initialized
249
+ * @internal
250
+ */
251
+ ensureInitializedSync() {
252
+ if (!this.container) throw new Error("[AwilixProvider] Container not initialized. Call await provider.init() first, or pass a pre-created container in options.");
253
+ }
254
+ /**
255
+ * Initializes the provider asynchronously
256
+ *
257
+ * @remarks
258
+ * Required before using the provider if not using `createSync()`.
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * const provider = new AwilixProvider({ injectionMode: 'PROXY' })
263
+ * await provider.init()
264
+ * useContainerProvider(provider)
265
+ * ```
266
+ */
267
+ async init() {
268
+ await this.ensureInitialized();
269
+ }
270
+ /**
271
+ * Creates a pre-initialized provider synchronously
272
+ *
273
+ * @param awilix - The awilix module import
274
+ * @param options - Configuration options
275
+ * @returns Fully initialized AwilixProvider instance
276
+ *
277
+ * @remarks
278
+ * This is the recommended way to create an AwilixProvider as it
279
+ * avoids async initialization and ensures the provider is ready immediately.
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * import * as awilix from 'awilix'
284
+ *
285
+ * const provider = AwilixProvider.createSync(awilix, {
286
+ * injectionMode: 'CLASSIC'
287
+ * })
288
+ * ```
289
+ */
290
+ static createSync(awilix$1, options = {}) {
291
+ const provider = new AwilixProvider(options);
292
+ provider.awilix = awilix$1;
293
+ provider.container = options.container ?? awilix$1.createContainer({ injectionMode: options.injectionMode === "CLASSIC" ? awilix$1.InjectionMode.CLASSIC : awilix$1.InjectionMode.PROXY });
294
+ return provider;
295
+ }
296
+ /**
297
+ * Gets or creates a unique string name for a token
298
+ * Awilix uses string-based registration, so we map symbols/classes to strings
299
+ * @internal
300
+ */
301
+ getTokenName(token) {
302
+ if (!this.tokenToName.has(token)) {
303
+ const name = typeof token === "symbol" ? token.description ?? `token_${this.nameCounter++}` : token.name;
304
+ this.tokenToName.set(token, name);
305
+ }
306
+ return this.tokenToName.get(token);
307
+ }
308
+ /**
309
+ * Maps WireDI lifecycle to Awilix Lifetime
310
+ * @internal
311
+ */
312
+ mapLifecycle(lifecycle) {
313
+ this.ensureInitializedSync();
314
+ switch (lifecycle) {
315
+ case ProviderLifecycle.Transient: return awilix.Lifetime.TRANSIENT;
316
+ case ProviderLifecycle.Scoped: return awilix.Lifetime.SCOPED;
317
+ case ProviderLifecycle.Singleton:
318
+ default: return awilix.Lifetime.SINGLETON;
319
+ }
320
+ }
321
+ /** @inheritdoc */
322
+ registerValue(token, value) {
323
+ this.ensureInitializedSync();
324
+ const { asValue } = this.awilix;
325
+ const name = this.getTokenName(token);
326
+ this.container.register({ [name]: asValue(value) });
327
+ }
328
+ /** @inheritdoc */
329
+ registerFactory(token, factory) {
330
+ this.ensureInitializedSync();
331
+ const { asFunction } = this.awilix;
332
+ const name = this.getTokenName(token);
333
+ this.container.register({ [name]: asFunction(() => factory(this)).singleton() });
334
+ }
335
+ /** @inheritdoc */
336
+ registerClass(token, impl, lifecycle) {
337
+ this.ensureInitializedSync();
338
+ const { asClass } = this.awilix;
339
+ const name = this.getTokenName(token);
340
+ const ClassToRegister = impl ?? token;
341
+ const lifetime = this.mapLifecycle(lifecycle);
342
+ this.container.register({ [name]: asClass(ClassToRegister).setLifetime(lifetime) });
343
+ }
344
+ /** @inheritdoc */
345
+ isRegistered(token) {
346
+ this.ensureInitializedSync();
347
+ const name = this.getTokenName(token);
348
+ return this.container.hasRegistration(name);
349
+ }
350
+ /** @inheritdoc */
351
+ resolve(token) {
352
+ this.ensureInitializedSync();
353
+ const name = this.getTokenName(token);
354
+ return this.container.resolve(name);
355
+ }
356
+ /** @inheritdoc */
357
+ createScope() {
358
+ this.ensureInitializedSync();
359
+ const scopedContainer = this.container.createScope();
360
+ const scopedProvider = AwilixProvider.createSync(this.awilix, { container: scopedContainer });
361
+ this.tokenToName.forEach((name, token) => {
362
+ scopedProvider.tokenToName.set(token, name);
363
+ });
364
+ return scopedProvider;
365
+ }
366
+ /** @inheritdoc */
367
+ dispose() {
368
+ if (this.container) this.container.dispose();
369
+ }
370
+ /**
371
+ * Returns the underlying Awilix container instance
372
+ * @returns The AwilixContainer instance
373
+ */
374
+ getUnderlyingContainer() {
375
+ this.ensureInitializedSync();
376
+ return this.container;
377
+ }
378
+ };
379
+
380
+ //#endregion
381
+ //#region src/Provider/adapters/InversifyProvider.ts
382
+ /**
383
+ * InversifyJS adapter implementing the ContainerProvider interface
384
+ *
385
+ * Provides integration between WireDI and InversifyJS,
386
+ * allowing you to use InversifyJS as your DI container while benefiting
387
+ * from WireDI's configuration and validation features.
388
+ */
389
+ var InversifyProvider = class InversifyProvider {
390
+ /** @inheritdoc */
391
+ name = "inversify";
392
+ /** The InversifyJS container instance */
393
+ container;
394
+ /** Default scope for bindings */
395
+ defaultScope;
396
+ /** Lazily loaded inversify module */
397
+ inversify = null;
398
+ /**
399
+ * Creates a new InversifyProvider instance
400
+ *
401
+ * @param options - Configuration options
402
+ *
403
+ * @remarks
404
+ * When using the constructor directly, you must call `init()` before use,
405
+ * or use `createSync()` for synchronous initialization.
406
+ */
407
+ constructor(options = {}) {
408
+ this.options = options;
409
+ this.defaultScope = options.defaultScope ?? "Singleton";
410
+ this.container = null;
411
+ }
412
+ /**
413
+ * Lazily initializes the container (async)
414
+ * @internal
415
+ */
416
+ async ensureInitialized() {
417
+ if (this.container) return;
418
+ this.inversify = await import("inversify");
419
+ this.container = this.options.container ?? new this.inversify.Container();
420
+ }
421
+ /**
422
+ * Throws if container is not initialized
423
+ * @internal
424
+ */
425
+ ensureInitializedSync() {
426
+ if (!this.container) throw new Error("[InversifyProvider] Container not initialized. Call await provider.init() first, or pass a pre-created container in options.");
427
+ }
428
+ /**
429
+ * Initializes the provider asynchronously
430
+ *
431
+ * @remarks
432
+ * Required before using the provider if not using `createSync()`.
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * const provider = new InversifyProvider()
437
+ * await provider.init()
438
+ * useContainerProvider(provider)
439
+ * ```
440
+ */
441
+ async init() {
442
+ await this.ensureInitialized();
443
+ }
444
+ /**
445
+ * Creates a pre-initialized provider synchronously
446
+ *
447
+ * @param inversify - The inversify module import
448
+ * @param options - Configuration options
449
+ * @returns Fully initialized InversifyProvider instance
450
+ *
451
+ * @remarks
452
+ * This is the recommended way to create an InversifyProvider as it
453
+ * avoids async initialization and ensures the provider is ready immediately.
454
+ *
455
+ * @example
456
+ * ```typescript
457
+ * import * as inversify from 'inversify'
458
+ *
459
+ * const provider = InversifyProvider.createSync(inversify, {
460
+ * defaultScope: 'Transient'
461
+ * })
462
+ * ```
463
+ */
464
+ static createSync(inversify, options = {}) {
465
+ const provider = new InversifyProvider(options);
466
+ provider.inversify = inversify;
467
+ provider.container = options.container ?? new inversify.Container();
468
+ return provider;
469
+ }
470
+ /**
471
+ * Maps WireDI lifecycle to InversifyJS binding scope
472
+ * @internal
473
+ */
474
+ mapLifecycle(lifecycle) {
475
+ switch (lifecycle) {
476
+ case ProviderLifecycle.Transient: return "Transient";
477
+ case ProviderLifecycle.Scoped: return "Request";
478
+ case ProviderLifecycle.Singleton:
479
+ default: return "Singleton";
480
+ }
481
+ }
482
+ /**
483
+ * Applies the scope to an InversifyJS binding
484
+ * @internal
485
+ */
486
+ applyScope(binding, scope) {
487
+ switch (scope) {
488
+ case "Singleton":
489
+ binding.inSingletonScope();
490
+ break;
491
+ case "Transient":
492
+ binding.inTransientScope();
493
+ break;
494
+ case "Request":
495
+ binding.inRequestScope();
496
+ break;
497
+ }
498
+ }
499
+ /** @inheritdoc */
500
+ registerValue(token, value) {
501
+ this.ensureInitializedSync();
502
+ if (this.container.isBound(token)) this.container.unbind(token);
503
+ this.container.bind(token).toConstantValue(value);
504
+ }
505
+ /** @inheritdoc */
506
+ registerFactory(token, factory) {
507
+ this.ensureInitializedSync();
508
+ if (this.container.isBound(token)) this.container.unbind(token);
509
+ this.container.bind(token).toDynamicValue(() => factory(this));
510
+ }
511
+ /** @inheritdoc */
512
+ registerClass(token, impl, lifecycle) {
513
+ this.ensureInitializedSync();
514
+ const ClassToRegister = impl ?? token;
515
+ const scope = this.mapLifecycle(lifecycle);
516
+ if (this.container.isBound(token)) this.container.unbind(token);
517
+ const binding = this.container.bind(token).to(ClassToRegister);
518
+ this.applyScope(binding, scope);
519
+ }
520
+ /** @inheritdoc */
521
+ isRegistered(token) {
522
+ this.ensureInitializedSync();
523
+ return this.container.isBound(token);
524
+ }
525
+ /** @inheritdoc */
526
+ resolve(token) {
527
+ this.ensureInitializedSync();
528
+ return this.container.get(token);
529
+ }
530
+ /** @inheritdoc */
531
+ createScope() {
532
+ this.ensureInitializedSync();
533
+ const childContainer = new this.inversify.Container();
534
+ return InversifyProvider.createSync(this.inversify, {
535
+ container: childContainer,
536
+ defaultScope: this.defaultScope
537
+ });
538
+ }
539
+ /** @inheritdoc */
540
+ dispose() {
541
+ if (this.container) this.container.unbindAll();
542
+ }
543
+ /**
544
+ * Returns the underlying InversifyJS Container instance
545
+ * @returns The InversifyJS Container
546
+ */
547
+ getUnderlyingContainer() {
548
+ this.ensureInitializedSync();
549
+ return this.container;
550
+ }
551
+ };
552
+
553
+ //#endregion
554
+ //#region src/EventDispatcher/Provider/MutableEventDispatcherProvider.ts
555
+ /**
556
+ * Default EventDispatcherProvider implementation
557
+ *
558
+ * This provider stores listeners in memory and resolves them through the DI container
559
+ * when dispatching events. Each listener must implement an `onEvent(event)` method.
560
+ *
561
+ * @example Basic usage
562
+ * ```typescript
563
+ * import {
564
+ * MutableEventDispatcherProvider,
565
+ * useEventDispatcherProvider,
566
+ * getContainerProvider
567
+ * } from '@djodjonx/wiredi'
568
+ *
569
+ * const eventProvider = new MutableEventDispatcherProvider({
570
+ * containerProvider: getContainerProvider()
571
+ * })
572
+ * useEventDispatcherProvider(eventProvider)
573
+ * ```
574
+ *
575
+ * @example Dispatching events
576
+ * ```typescript
577
+ * const dispatcher = getEventDispatcherProvider()
578
+ * dispatcher.dispatch(new UserCreatedEvent(user))
579
+ * ```
580
+ */
581
+ var MutableEventDispatcherProvider = class {
582
+ /** @inheritdoc */
583
+ name = "mutable-event-dispatcher";
584
+ /** Map of event names to their registered listener tokens */
585
+ listeners = /* @__PURE__ */ new Map();
586
+ /** DI container provider for resolving listener instances */
587
+ containerProvider;
588
+ /**
589
+ * Creates a new MutableEventDispatcherProvider instance
590
+ *
591
+ * @param options - Configuration options including the container provider
592
+ */
593
+ constructor(options) {
594
+ this.containerProvider = options.containerProvider;
595
+ }
596
+ /**
597
+ * Extracts the event name from an event token (class constructor)
598
+ * @internal
599
+ */
600
+ getEventName(eventToken) {
601
+ return eventToken.name;
602
+ }
603
+ /**
604
+ * Extracts the event name from an event instance
605
+ * @internal
606
+ */
607
+ getEventNameFromInstance(event) {
608
+ return event.constructor.name;
609
+ }
610
+ /** @inheritdoc */
611
+ register(eventToken, listenerToken) {
612
+ const eventName = this.getEventName(eventToken);
613
+ if (!this.listeners.has(eventName)) this.listeners.set(eventName, []);
614
+ this.listeners.get(eventName).push(listenerToken);
615
+ }
616
+ /** @inheritdoc */
617
+ dispatch(event) {
618
+ const eventName = this.getEventNameFromInstance(event);
619
+ const listenerTokens = this.listeners.get(eventName) ?? [];
620
+ for (const listenerToken of listenerTokens) try {
621
+ this.containerProvider.resolve(listenerToken).onEvent(event);
622
+ } catch (error) {
623
+ console.error(`[MutableEventDispatcherProvider] Error dispatching event "${eventName}":`, error.stack || error.message);
624
+ throw error;
625
+ }
626
+ }
627
+ /** @inheritdoc */
628
+ hasListeners(eventToken) {
629
+ const eventName = this.getEventName(eventToken);
630
+ const listeners = this.listeners.get(eventName);
631
+ return listeners !== void 0 && listeners.length > 0;
632
+ }
633
+ /** @inheritdoc */
634
+ clearListeners(eventToken) {
635
+ const eventName = this.getEventName(eventToken);
636
+ this.listeners.delete(eventName);
637
+ }
638
+ /** @inheritdoc */
639
+ clearAllListeners() {
640
+ this.listeners.clear();
641
+ }
642
+ /**
643
+ * Returns the internal listeners map
644
+ * @returns Map of event names to listener tokens
645
+ */
646
+ getUnderlyingDispatcher() {
647
+ return this.listeners;
648
+ }
649
+ };
650
+
651
+ //#endregion
652
+ //#region src/EventDispatcher/Provider/index.ts
653
+ /**
654
+ * Currently configured global event dispatcher provider
655
+ * @internal
656
+ */
657
+ let globalEventDispatcherProvider = null;
658
+ /**
659
+ * Sets the global EventDispatcherProvider instance
660
+ *
661
+ * Call this once at application startup after setting up the container provider.
662
+ * The event dispatcher uses the container provider to resolve listener instances.
663
+ *
664
+ * @param provider - The EventDispatcherProvider implementation to use
665
+ * @throws Error if a provider is already registered
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * import {
670
+ * useContainerProvider,
671
+ * TsyringeProvider,
672
+ * useEventDispatcherProvider,
673
+ * MutableEventDispatcherProvider,
674
+ * getContainerProvider
675
+ * } from '@djodjonx/wiredi'
676
+ *
677
+ * // 1. Setup DI container first
678
+ * useContainerProvider(new TsyringeProvider({ container, Lifecycle }))
679
+ *
680
+ * // 2. Setup event dispatcher (optional)
681
+ * useEventDispatcherProvider(new MutableEventDispatcherProvider({
682
+ * containerProvider: getContainerProvider()
683
+ * }))
684
+ * ```
685
+ */
686
+ function useEventDispatcherProvider(provider) {
687
+ if (globalEventDispatcherProvider !== null) throw new Error(`[EventDispatcher] Provider already registered: "${globalEventDispatcherProvider.name}". Cannot register "${provider.name}". Call resetEventDispatcherProvider() first if you need to change it.`);
688
+ globalEventDispatcherProvider = provider;
689
+ }
690
+ /**
691
+ * Retrieves the currently registered EventDispatcherProvider
692
+ *
693
+ * @returns The registered EventDispatcherProvider instance
694
+ * @throws Error if no provider has been registered
695
+ *
696
+ * @example
697
+ * ```typescript
698
+ * const eventDispatcher = getEventDispatcherProvider()
699
+ * eventDispatcher.dispatch(new UserCreatedEvent(user))
700
+ * ```
701
+ */
702
+ function getEventDispatcherProvider() {
703
+ if (globalEventDispatcherProvider === null) throw new Error("[EventDispatcher] No provider registered. Call useEventDispatcherProvider() at application startup.");
704
+ return globalEventDispatcherProvider;
705
+ }
706
+ /**
707
+ * Checks if an EventDispatcherProvider has been registered
708
+ *
709
+ * @returns `true` if a provider is registered, `false` otherwise
710
+ *
711
+ * @example
712
+ * ```typescript
713
+ * if (hasEventDispatcherProvider()) {
714
+ * getEventDispatcherProvider().dispatch(event)
715
+ * }
716
+ * ```
717
+ */
718
+ function hasEventDispatcherProvider() {
719
+ return globalEventDispatcherProvider !== null;
720
+ }
721
+ /**
722
+ * Resets the global EventDispatcherProvider
723
+ *
724
+ * Clears all registered listeners and removes the provider.
725
+ * Useful for testing or reconfiguration scenarios.
726
+ *
727
+ * ⚠️ WARNING: This should rarely be used in production code.
728
+ *
729
+ * @example
730
+ * ```typescript
731
+ * // In a test file
732
+ * afterEach(() => {
733
+ * resetEventDispatcherProvider()
734
+ * })
735
+ * ```
736
+ */
737
+ function resetEventDispatcherProvider() {
738
+ if (globalEventDispatcherProvider !== null) globalEventDispatcherProvider.clearAllListeners();
739
+ globalEventDispatcherProvider = null;
740
+ }
741
+
742
+ //#endregion
743
+ //#region src/index.ts
744
+ /**
745
+ * Type guard to check if a token is a constructor (class).
746
+ * @param token The token to check.
747
+ * @returns True if the token is a constructor, false otherwise.
748
+ */
749
+ function isConstructor(token) {
750
+ return typeof token === "function" && !!token.prototype && token.prototype.constructor === token;
751
+ }
752
+ function registerConfig(provider, injections, context) {
753
+ injections.forEach((entry) => {
754
+ if (provider.isRegistered(entry.token)) return;
755
+ if ("value" in entry) provider.registerValue(entry.token, entry.value(context));
756
+ else if ("factory" in entry) provider.registerFactory(entry.token, () => entry.factory(provider));
757
+ else if (isConstructor(entry.token)) {
758
+ const lifecycle = entry.lifecycle ?? ProviderLifecycle.Singleton;
759
+ provider.registerClass(entry.token, entry.token, lifecycle);
760
+ } else {
761
+ if (!("provider" in entry)) throw new Error(`Provider required when registering token Symbol: ${String(entry.token)}`);
762
+ const lifecycle = entry.lifecycle ?? ProviderLifecycle.Singleton;
763
+ provider.registerClass(entry.token, entry.provider, lifecycle);
764
+ }
765
+ });
766
+ }
767
+ function registerEvent(_provider, listeners) {
768
+ if (!listeners.length) return;
769
+ if (!hasEventDispatcherProvider()) return;
770
+ const eventDispatcher = getEventDispatcherProvider();
771
+ listeners.forEach((configEntry) => {
772
+ eventDispatcher.register(configEntry.event, configEntry.listener);
773
+ });
774
+ }
775
+ /**
776
+ * Helper to define a partial configuration (injections/listeners).
777
+ *
778
+ * Partials are designed to be flat collections of dependencies. They do not support
779
+ * inheritance (`extends`) or overriding to prevent complex dependency graphs.
780
+ * All conflict resolution must happen in the main `defineBuilderConfig`.
781
+ *
782
+ * @template C The type of the context object (optional).
783
+ * @template I The specific type of the injections array (inferred).
784
+ * @param config The partial builder configuration object.
785
+ * @returns The configuration object typed as TypedPartialConfig.
786
+ */
787
+ function definePartialConfig(config) {
788
+ return config;
789
+ }
790
+ /**
791
+ * A helper function to define a builder configuration with strict type inference and inheritance.
792
+ * Use this instead of manually casting with `satisfies BuilderConfig` or `as const`
793
+ * to ensure that `useBuilder` can correctly infer the available tokens.
794
+ *
795
+ * This function now supports `extends` to inherit from `definePartialConfig` definitions.
796
+ * Token collisions are strictly forbidden - each token must be unique across all partials
797
+ * and the main configuration to prevent accidental redefinition.
798
+ *
799
+ * @template C The type of the context object (optional).
800
+ * @template Partials The tuple of partial configs to extend.
801
+ * @template LocalInjections The specific type of the local injections array (inferred).
802
+ * @param config The builder configuration object.
803
+ * @returns The configuration object typed as TypedBuilderConfig to simplify IDE display.
804
+ *
805
+ * @example
806
+ * ```typescript
807
+ * import { Lifecycle } from 'tsyringe';
808
+ *
809
+ * class MyService {}
810
+ * class MyProvider {}
811
+ * class MyEvent {}
812
+ * class MyEventListener {
813
+ * onEvent(event: MyEvent) {
814
+ * console.log('Event received:', event);
815
+ * }
816
+ * }
817
+ *
818
+ * const MY_TOKEN = Symbol('MY_TOKEN');
819
+ * const MY_VALUE_TOKEN = Symbol('MY_VALUE_TOKEN');
820
+ * const MY_FACTORY_TOKEN = Symbol('MY_FACTORY_TOKEN');
821
+ *
822
+ * // --- Partial Configuration ---
823
+ * const myPartial = definePartialConfig({
824
+ * injections: [
825
+ * { token: MyService } // Provides MyService
826
+ * ],
827
+ * listeners: [
828
+ * { event: MyEvent, listener: MyEventListener }
829
+ * ]
830
+ * });
831
+ *
832
+ * // --- Main Builder Configuration ---
833
+ * const myBuilderConfig = defineBuilderConfig({
834
+ * builderId: 'my.unique.builder',
835
+ * extends: [myPartial],
836
+ * injections: [
837
+ * // ❌ ERROR: Token collision - MyService is already defined in myPartial
838
+ * // { token: MyService },
839
+ *
840
+ * // ✅ OK: New tokens not in partials
841
+ * { token: MY_TOKEN, provider: MyProvider },
842
+ * { token: MY_TOKEN, provider: MyProvider, lifecycle: Lifecycle.Transient },
843
+ *
844
+ * // 3. Value Injection (can use optional context)
845
+ * { token: MY_VALUE_TOKEN, value: (context) => context?.someConfig ?? 'defaultValue' },
846
+ *
847
+ * // 4. Factory Injection
848
+ * { token: MY_FACTORY_TOKEN, factory: (container) => new MyService() },
849
+ * ],
850
+ * listeners: [
851
+ * // ❌ ERROR: Duplicate listener (Event + Listener pair already in myPartial)
852
+ * // { event: MyEvent, listener: MyEventListener },
853
+ *
854
+ * // ✅ OK: New listener not in partials
855
+ * { event: OtherEvent, listener: OtherEventListener },
856
+ * ],
857
+ * });
858
+ *
859
+ * // Usage:
860
+ * // const { resolve } = useBuilder(myBuilderConfig, { someConfig: 'custom' });
861
+ * // const service = resolve(MyService);
862
+ * // const value = resolve(MY_VALUE_TOKEN);
863
+ * ```
864
+ */
865
+ function defineBuilderConfig(config) {
866
+ const mergedInjections = [...config.injections, ...(config.extends || []).flatMap((p) => p.injections || [])];
867
+ const mergedListeners = [...(config.extends || []).flatMap((p) => p.listeners || []), ...config.listeners];
868
+ return {
869
+ ...config,
870
+ injections: mergedInjections,
871
+ listeners: mergedListeners
872
+ };
873
+ }
874
+ /**
875
+ * A composable function for setting up and interacting with a dependency injection container
876
+ * based on a `BuilderConfig`. It ensures that dependencies are registered only once per builderId
877
+ * and provides a type-safe `resolve` method.
878
+ *
879
+ * The `resolve` method is strictly type-checked to only allow tokens defined within the `injections`
880
+ * array of the provided `config`.
881
+ *
882
+ * ⚠️ Requires `useContainerProvider()` to be called first at app entry point.
883
+ *
884
+ * @template C The type of the context object that might be passed to value providers.
885
+ * @template Tokens The inferred tuple of allowed tokens from the config.
886
+ * @param config The typed builder configuration object.
887
+ * @param context An optional context object that can be passed to value providers in the injections.
888
+ * @returns An `IUseBuilder` instance with a type-safe `resolve` method.
889
+ *
890
+ * @example
891
+ * ```typescript
892
+ * // main.ts - Entry point
893
+ * import { useContainerProvider, TsyringeProvider } from '@djodjonx/wiredi'
894
+ * useContainerProvider(new TsyringeProvider())
895
+ *
896
+ * // anywhere.ts
897
+ * import { useBuilder } from '@djodjonx/wiredi'
898
+ * const { resolve } = useBuilder(myConfig)
899
+ * const service = resolve(MyService)
900
+ * ```
901
+ */
902
+ function useBuilder(config, context) {
903
+ const provider = getContainerProvider();
904
+ const builderIdToken = Symbol.for(`__builder__${config.builderId}`);
905
+ if (!provider.isRegistered(builderIdToken)) {
906
+ registerConfig(provider, config.injections, context);
907
+ registerEvent(provider, config.listeners);
908
+ provider.registerValue(builderIdToken, config.builderId);
909
+ }
910
+ const resolve = (token) => provider.resolve(token);
911
+ return { resolve };
912
+ }
913
+
914
+ //#endregion
915
+ exports.AwilixProvider = AwilixProvider;
916
+ exports.InversifyProvider = InversifyProvider;
917
+ exports.MutableEventDispatcherProvider = MutableEventDispatcherProvider;
918
+ exports.ProviderLifecycle = ProviderLifecycle;
919
+ exports.TsyringeProvider = TsyringeProvider;
920
+ exports.default = useBuilder;
921
+ exports.defineBuilderConfig = defineBuilderConfig;
922
+ exports.definePartialConfig = definePartialConfig;
923
+ exports.getContainerProvider = getContainerProvider;
924
+ exports.getEventDispatcherProvider = getEventDispatcherProvider;
925
+ exports.hasContainerProvider = hasContainerProvider;
926
+ exports.hasEventDispatcherProvider = hasEventDispatcherProvider;
927
+ exports.resetContainerProvider = resetContainerProvider;
928
+ exports.resetEventDispatcherProvider = resetEventDispatcherProvider;
929
+ exports.useContainerProvider = useContainerProvider;
930
+ exports.useEventDispatcherProvider = useEventDispatcherProvider;
931
+ //# sourceMappingURL=index.cjs.map