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