@adimm/x-injection 2.1.2 → 3.0.0
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 +683 -860
- package/dist/index.cjs +232 -245
- package/dist/index.d.cts +53 -42
- package/dist/index.d.ts +53 -42
- package/dist/index.js +255 -266
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,860 +1,683 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img width="260px" height="auto" alt="xInjection Logo" src="https://raw.githubusercontent.com/AdiMarianMutu/x-injection/main/assets/logo.png"><br /><a href="https://www.npmjs.com/package/@adimm/x-injection" target="__blank"><img src="https://badgen.net/npm/v/@adimm/x-injection"></a>
|
|
3
|
-
<a href="https://app.codecov.io/gh/AdiMarianMutu/x-injection" target="__blank"><img src="https://badgen.net/codecov/c/github/AdiMarianMutu/x-injection"></a>
|
|
4
|
-
<img src="https://badgen.net/npm/license/@adimm/x-injection">
|
|
5
|
-
</p>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="https://github.com/AdiMarianMutu/x-injection/actions/workflows/ci.yml?query=branch%3Amain" target="__blank"><img src="https://github.com/AdiMarianMutu/x-injection/actions/workflows/ci.yml/badge.svg?branch=main"></a>
|
|
9
|
-
<a href="https://github.com/AdiMarianMutu/x-injection/actions/workflows/publish.yml" target="__blank"><img src="https://github.com/AdiMarianMutu/x-injection/actions/workflows/publish.yml/badge.svg"></a>
|
|
10
|
-
<br>
|
|
11
|
-
<img src="https://badgen.net/bundlephobia/minzip/@adimm/x-injection">
|
|
12
|
-
<a href="https://www.npmjs.com/package/@adimm/x-injection" target="__blank"><img src="https://badgen.net/npm/dm/@adimm/x-injection"></a>
|
|
13
|
-
|
|
14
|
-
</p>
|
|
15
|
-
|
|
16
|
-
## Table of Contents
|
|
17
|
-
|
|
18
|
-
- [Table of Contents](#table-of-contents)
|
|
19
|
-
- [Overview](#overview)
|
|
20
|
-
- [Features](#features)
|
|
21
|
-
- [Installation](#installation)
|
|
22
|
-
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
You
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
> [!
|
|
576
|
-
>
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
```ts
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
};
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
const transaction = TransactionModule.get('LAST_TRANSACTION');
|
|
688
|
-
// transaction => { timestamp: 1363952948, value: <Payment_instance> }
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
One more example is to add a `middleware` in order to _dynamically_ control which `modules` can import a specific `module` by using the [OnExportAccess](https://adimarianmutu.github.io/x-injection/enums/MiddlewareType.html#onexportaccess) flag.
|
|
692
|
-
|
|
693
|
-
```ts
|
|
694
|
-
const UnauthorizedBranchBankModule = ProviderModule.create({ id: 'UnauthorizedBranchBankModule' });
|
|
695
|
-
const SensitiveBankDataModule = ProviderModule.create({
|
|
696
|
-
id: 'SensitiveBankDataModule',
|
|
697
|
-
providers: [SensitiveBankDataService, NonSensitiveBankDataService],
|
|
698
|
-
exports: [SensitiveBankDataService, NonSensitiveBankDataService],
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
SensitiveBankDataModule.middlewares.add(MiddlewareType.OnExportAccess, (importerModule, currentExport) => {
|
|
702
|
-
// We want to deny access to our `SensitiveBankDataService` from the `exports` definition if the importer module is `UnauthorizedBranchBankModule`
|
|
703
|
-
if (importerModule.toString() === 'UnauthorizedBranchBankModule' && currentExport === SensitiveBankDataService)
|
|
704
|
-
return false;
|
|
705
|
-
|
|
706
|
-
// Remaining module are able to import all our `export` definition
|
|
707
|
-
// The `UnauthorizedBranchBankModule` is unable to import the `SensitiveBankDataService`
|
|
708
|
-
return true;
|
|
709
|
-
});
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
> [!CAUTION]
|
|
713
|
-
>
|
|
714
|
-
> Returning `false` in a `middleware` will abort the chain, meaning that for the above example, no value would be returned.
|
|
715
|
-
> If you have to explicitly return a `false` boolean value, you may have to wrap your provider value as an workaround. _(`null` is accepted as a return value)_
|
|
716
|
-
>
|
|
717
|
-
> Meanwhile returning `true` means _"return the value without changing it"_.
|
|
718
|
-
>
|
|
719
|
-
> In the future this behavior may change, so if your business logic relies a lot on `middlewares` make sure to stay up-to-date with the latest changes.
|
|
720
|
-
|
|
721
|
-
It is also worth mentioning that you can apply _multiple_ middlewares by just invoking the `middlewares.add` method multiple times, they are executed in the same exact order as you applied them, meaning that the first invokation to `middlewares.add` will actually be the `root` of the chain.
|
|
722
|
-
|
|
723
|
-
If no error is thrown down the chain, all the registered middleware `callback` will be supplied with the necessary values.
|
|
724
|
-
|
|
725
|
-
> [!WARNING]
|
|
726
|
-
>
|
|
727
|
-
> It is the _developer_ responsability to catch any error down the `chain`!
|
|
728
|
-
|
|
729
|
-
### Internals
|
|
730
|
-
|
|
731
|
-
If you are not interested in understanding how `xInjection` works under the hood, you can skip this section 😌
|
|
732
|
-
|
|
733
|
-
#### ProviderModule
|
|
734
|
-
|
|
735
|
-
It is the head of everything, a `ProviderModule` is actually composed by several classes, each with its own purpose.
|
|
736
|
-
|
|
737
|
-
> [!TIP]
|
|
738
|
-
>
|
|
739
|
-
> You can get access to _all_ the internal instances by doing `new ProviderModule({...})` instead of `ProviderModule.create({...})`
|
|
740
|
-
|
|
741
|
-
#### MiddlewaresManager
|
|
742
|
-
|
|
743
|
-
It is the `class` which takes care of managing the registered `middlewares`, check it out [here](https://adimarianmutu.github.io/x-injection/classes/MiddlewaresManager.html).
|
|
744
|
-
|
|
745
|
-
Not much to say about it as its main role is to _register_ and _build_ the `middleware` chain.
|
|
746
|
-
|
|
747
|
-
#### ModuleContainer
|
|
748
|
-
|
|
749
|
-
It is the `class` which takes care of managing the `inversify` container, check it out [here](https://adimarianmutu.github.io/x-injection/classes/ModuleContainer.html).
|
|
750
|
-
|
|
751
|
-
Its main purpose is to initialize the module raw _([InversifyJS Container](https://inversify.io/docs/api/container/))_ `class` and to _bind_ the providers to it.
|
|
752
|
-
|
|
753
|
-
#### ImportedModuleContainer
|
|
754
|
-
|
|
755
|
-
It is the `class` which takes care of managing the _imported_ modules, check it out [here](https://adimarianmutu.github.io/x-injection/classes/ImportedModuleContainer.html).
|
|
756
|
-
|
|
757
|
-
Because `modules` can be imported into other modules, therefore creating a _complex_ `graph` of modules, the purpose of this class is to keep track and sync the changes of the `exports` definition of the _imported_ module.
|
|
758
|
-
|
|
759
|
-
The `ProviderModule` API is simple yet very powerful, you may not realize that doing `addImport` will cause _(based on how deep is the imported module)_ a chain reaction which the `ImportedModuleContainer` must keep track of in order to make sure that the _consumer_ `module` which imported the _consumed_ `module` has access only to the `providers`/`modules` explicitly exported by the _consumed_ `module`.
|
|
760
|
-
|
|
761
|
-
Therefore it is encouraged to keep things mostly static, as each `addProvider`, `addImport`, `removeImport` and so on have a penality cost on your application performance. This cost in most cases is negligible, however it highly depends on how the _developer_ uses the feature `xInjection` offers.
|
|
762
|
-
|
|
763
|
-
> "With great power comes great responsibility."
|
|
764
|
-
|
|
765
|
-
#### DynamicModuleDefinition
|
|
766
|
-
|
|
767
|
-
It is the `class` which takes care of managing the _updates_ and _event_ emissions of the `module`, check it out [here](https://adimarianmutu.github.io/x-injection/classes/DynamicModuleDefinition.html).
|
|
768
|
-
|
|
769
|
-
This class is actually the "parent" of the `ImportedModuleContainer` instances, its purpose is to _build_ the _initial_ definition graph, and while doing so it also instantiate _for each_ imported module a new `ImportedModuleContainer`.
|
|
770
|
-
|
|
771
|
-
It also take care of managing the `events` bubbling by checking cirular references and so on.
|
|
772
|
-
|
|
773
|
-
#### ProviderModuleBlueprint
|
|
774
|
-
|
|
775
|
-
It's the "metadata" counterpart of the `ProviderModule` class, as its only purpose is to carry the definitions. Check it out [here](https://adimarianmutu.github.io/x-injection/classes/ProviderModuleBlueprint.html).
|
|
776
|
-
|
|
777
|
-
#### Set of Helpers
|
|
778
|
-
|
|
779
|
-
The library does also export a set of useful helpers in the case you may need it:
|
|
780
|
-
|
|
781
|
-
```ts
|
|
782
|
-
import { ProviderModuleHelpers, ProviderTokenHelpers } from '@adimm/x-injection';
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
---
|
|
786
|
-
|
|
787
|
-
This covers pretty much everything about how `xInjection` is built and how it works.
|
|
788
|
-
|
|
789
|
-
## Unit Tests
|
|
790
|
-
|
|
791
|
-
It is very easy to create mock modules so you can use them in your unit tests.
|
|
792
|
-
|
|
793
|
-
```ts
|
|
794
|
-
class ApiService {
|
|
795
|
-
constructor(private readonly userService: UserService) {}
|
|
796
|
-
|
|
797
|
-
async sendRequest<T>(location: LocationParams): Promise<T> {
|
|
798
|
-
// Pseudo Implementation
|
|
799
|
-
return this.sendToLocation(user, location);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
private async sendToLocation(user: User, location: any): Promise<any> {}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
const ApiModuleBp = new ProviderModule.blueprint({
|
|
806
|
-
id: 'ApiModule',
|
|
807
|
-
providers: [UserService, ApiService],
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
// Clone returns a `deep` clone and wraps all the `methods` to break their reference!
|
|
811
|
-
const ApiModuleBpMocked = ApiModuleBp.clone().updateDefinition({
|
|
812
|
-
id: 'ApiModuleMocked',
|
|
813
|
-
providers: [
|
|
814
|
-
{
|
|
815
|
-
provide: UserService,
|
|
816
|
-
useClass: UserService_Mock,
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
provide: ApiService,
|
|
820
|
-
useValue: {
|
|
821
|
-
sendRequest: async (location) => {
|
|
822
|
-
console.log(location);
|
|
823
|
-
},
|
|
824
|
-
},
|
|
825
|
-
},
|
|
826
|
-
],
|
|
827
|
-
});
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
Now what you have to do is just to provide the `ApiModuleBpMocked` instead of the `ApiModuleBp` 😎
|
|
831
|
-
|
|
832
|
-
## Documentation
|
|
833
|
-
|
|
834
|
-
Comprehensive, auto-generated documentation is available at:
|
|
835
|
-
|
|
836
|
-
👉 [https://adimarianmutu.github.io/x-injection/index.html](https://adimarianmutu.github.io/x-injection/index.html)
|
|
837
|
-
|
|
838
|
-
## ReactJS Implementation
|
|
839
|
-
|
|
840
|
-
You want to use it within a [ReactJS](https://react.dev/) project? Don't worry, the library does already have an official implementation for React ⚛️
|
|
841
|
-
|
|
842
|
-
For more details check out the [GitHub Repository](https://github.com/AdiMarianMutu/x-injection-reactjs).
|
|
843
|
-
|
|
844
|
-
## Contributing
|
|
845
|
-
|
|
846
|
-
Pull requests are warmly welcomed! 😃
|
|
847
|
-
|
|
848
|
-
Please ensure your contributions adhere to the project's code style. See the repository for more details.
|
|
849
|
-
|
|
850
|
-
## Credits
|
|
851
|
-
|
|
852
|
-
- [Adi-Marian Mutu](https://www.linkedin.com/in/mutu-adi-marian/) - Author of `xInjection`
|
|
853
|
-
- [InversifyJS](https://github.com/inversify/monorepo) - Base lib
|
|
854
|
-
- [Alexandru Turica](https://www.linkedin.com/in/alexandru-turica-82215522b/) - Official Logo
|
|
855
|
-
|
|
856
|
-
---
|
|
857
|
-
|
|
858
|
-
> [!NOTE]
|
|
859
|
-
>
|
|
860
|
-
> **For questions, feature requests, or bug reports, feel free to open an [issue](https://github.com/AdiMarianMutu/x-injection/issues) on GitHub.**
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img width="260px" height="auto" alt="xInjection Logo" src="https://raw.githubusercontent.com/AdiMarianMutu/x-injection/main/assets/logo.png"><br /><a href="https://www.npmjs.com/package/@adimm/x-injection" target="__blank"><img src="https://badgen.net/npm/v/@adimm/x-injection"></a>
|
|
3
|
+
<a href="https://app.codecov.io/gh/AdiMarianMutu/x-injection" target="__blank"><img src="https://badgen.net/codecov/c/github/AdiMarianMutu/x-injection"></a>
|
|
4
|
+
<img src="https://badgen.net/npm/license/@adimm/x-injection">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/AdiMarianMutu/x-injection/actions/workflows/ci.yml?query=branch%3Amain" target="__blank"><img src="https://github.com/AdiMarianMutu/x-injection/actions/workflows/ci.yml/badge.svg?branch=main"></a>
|
|
9
|
+
<a href="https://github.com/AdiMarianMutu/x-injection/actions/workflows/publish.yml" target="__blank"><img src="https://github.com/AdiMarianMutu/x-injection/actions/workflows/publish.yml/badge.svg"></a>
|
|
10
|
+
<br>
|
|
11
|
+
<img src="https://badgen.net/bundlephobia/minzip/@adimm/x-injection">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@adimm/x-injection" target="__blank"><img src="https://badgen.net/npm/dm/@adimm/x-injection"></a>
|
|
13
|
+
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [Table of Contents](#table-of-contents)
|
|
19
|
+
- [Overview](#overview)
|
|
20
|
+
- [Features](#features)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Quick Start](#quick-start)
|
|
23
|
+
- [OOP-Style Modules](#oop-style-modules)
|
|
24
|
+
- [Basic OOP Module](#basic-oop-module)
|
|
25
|
+
- [Advanced OOP Patterns](#advanced-oop-patterns)
|
|
26
|
+
- [When to Use OOP vs Functional](#when-to-use-oop-vs-functional)
|
|
27
|
+
- [Core Concepts](#core-concepts)
|
|
28
|
+
- [ProviderModule](#providermodule)
|
|
29
|
+
- [AppModule](#appmodule)
|
|
30
|
+
- [Blueprints](#blueprints)
|
|
31
|
+
- [Provider Tokens](#provider-tokens)
|
|
32
|
+
- [Injection Scopes](#injection-scopes)
|
|
33
|
+
- [Singleton (Default)](#singleton-default)
|
|
34
|
+
- [Transient](#transient)
|
|
35
|
+
- [Request](#request)
|
|
36
|
+
- [Module System](#module-system)
|
|
37
|
+
- [Import/Export Pattern](#importexport-pattern)
|
|
38
|
+
- [Re-exporting Modules](#re-exporting-modules)
|
|
39
|
+
- [Dynamic Module Updates](#dynamic-module-updates)
|
|
40
|
+
- [Global Modules](#global-modules)
|
|
41
|
+
- [Advanced Features](#advanced-features)
|
|
42
|
+
- [Events](#events)
|
|
43
|
+
- [Middlewares](#middlewares)
|
|
44
|
+
- [Testing](#testing)
|
|
45
|
+
- [Resources](#resources)
|
|
46
|
+
- [Contributing](#contributing)
|
|
47
|
+
- [Credits](#credits)
|
|
48
|
+
- [License](#license)
|
|
49
|
+
|
|
50
|
+
## Overview
|
|
51
|
+
|
|
52
|
+
**xInjection** is a powerful [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) library built on [InversifyJS](https://github.com/inversify/InversifyJS), inspired by [NestJS](https://github.com/nestjs/nest)'s modular architecture. It provides fine-grained control over dependency encapsulation through a module-based system where each module manages its own container with explicit import/export boundaries.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- **Modular Architecture** - NestJS-style import/export system for clean dependency boundaries
|
|
57
|
+
- **Isolated Containers** - Each module manages its own InversifyJS container
|
|
58
|
+
- **Flexible Scopes** - Singleton, Transient, and Request-scoped providers
|
|
59
|
+
- **Lazy Loading** - Blueprint pattern for deferred module instantiation
|
|
60
|
+
- **Lifecycle Hooks** - `onReady`, `onReset`, `onDispose` for module lifecycle management
|
|
61
|
+
- **Events & Middlewares** - Deep customization through event subscriptions and middleware chains
|
|
62
|
+
- **Framework Agnostic** - Works in Node.js and browser environments
|
|
63
|
+
- **TypeScript First** - Full type safety with decorator support
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @adimm/x-injection reflect-metadata
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**TypeScript Configuration** (`tsconfig.json`):
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"compilerOptions": {
|
|
76
|
+
"experimentalDecorators": true,
|
|
77
|
+
"emitDecoratorMetadata": true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Import `reflect-metadata` at your application's entry point:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import 'reflect-metadata';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { Injectable, ProviderModule } from '@adimm/x-injection';
|
|
92
|
+
|
|
93
|
+
@Injectable()
|
|
94
|
+
class UserService {
|
|
95
|
+
getUser(id: string) {
|
|
96
|
+
return { id, name: 'John Doe' };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Injectable()
|
|
101
|
+
class AuthService {
|
|
102
|
+
constructor(private userService: UserService) {}
|
|
103
|
+
|
|
104
|
+
login(userId: string) {
|
|
105
|
+
const user = this.userService.getUser(userId);
|
|
106
|
+
return `Logged in as ${user.name}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const AuthModule = ProviderModule.create({
|
|
111
|
+
id: 'AuthModule',
|
|
112
|
+
providers: [UserService, AuthService],
|
|
113
|
+
exports: [AuthService],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const authService = AuthModule.get(AuthService);
|
|
117
|
+
console.log(authService.login('123')); // "Logged in as John Doe"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## OOP-Style Modules
|
|
121
|
+
|
|
122
|
+
For developers who prefer class-based architecture, xInjection provides `ProviderModuleClass` - a composition-based wrapper that prevents naming conflicts between your custom methods and the DI container methods.
|
|
123
|
+
|
|
124
|
+
### Basic OOP Module
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { Injectable, ProviderModuleClass } from '@adimm/x-injection';
|
|
128
|
+
|
|
129
|
+
@Injectable()
|
|
130
|
+
class UserService {
|
|
131
|
+
get(id: string) {
|
|
132
|
+
return { id, name: 'John Doe' };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Injectable()
|
|
137
|
+
class AuthService {
|
|
138
|
+
constructor(private userService: UserService) {}
|
|
139
|
+
|
|
140
|
+
login(userId: string) {
|
|
141
|
+
const user = this.userService.get(userId);
|
|
142
|
+
return `Logged in as ${user.name}`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// OOP-style module extending ProviderModuleClass
|
|
147
|
+
class AuthModule extends ProviderModuleClass {
|
|
148
|
+
constructor() {
|
|
149
|
+
super({
|
|
150
|
+
id: 'AuthModule',
|
|
151
|
+
providers: [UserService, AuthService],
|
|
152
|
+
exports: [AuthService],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
authenticateUser(userId: string): string {
|
|
157
|
+
const authService = this.module.get(AuthService);
|
|
158
|
+
return authService.login(userId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getUserById(userId: string) {
|
|
162
|
+
const userService = this.module.get(UserService);
|
|
163
|
+
return userService.get(userId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Instantiate and use
|
|
168
|
+
const authModule = new AuthModule();
|
|
169
|
+
console.log(authModule.authenticateUser('123')); // "Logged in as John Doe"
|
|
170
|
+
|
|
171
|
+
// All ProviderModule methods are available through the `.module` property
|
|
172
|
+
const authService = authModule.module.get(AuthService);
|
|
173
|
+
authModule.module.update.addProvider(NewService);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Advanced OOP Patterns
|
|
177
|
+
|
|
178
|
+
**Module with Initialization Logic:**
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
class DatabaseModule extends ProviderModuleClass {
|
|
182
|
+
private isConnected = false;
|
|
183
|
+
|
|
184
|
+
constructor() {
|
|
185
|
+
super({
|
|
186
|
+
id: 'DatabaseModule',
|
|
187
|
+
providers: [DatabaseService, ConnectionPool],
|
|
188
|
+
exports: [DatabaseService],
|
|
189
|
+
onReady: async (module) => {
|
|
190
|
+
console.log('DatabaseModule ready!');
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async connect(): Promise<void> {
|
|
196
|
+
const dbService = this.module.get(DatabaseService);
|
|
197
|
+
await dbService.connect();
|
|
198
|
+
this.isConnected = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getConnectionStatus(): boolean {
|
|
202
|
+
return this.isConnected;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const dbModule = new DatabaseModule();
|
|
207
|
+
await dbModule.connect();
|
|
208
|
+
console.log(dbModule.getConnectionStatus()); // true
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Module with Computed Properties:**
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
class ApiModule extends ProviderModuleClass {
|
|
215
|
+
constructor() {
|
|
216
|
+
super({
|
|
217
|
+
id: 'ApiModule',
|
|
218
|
+
imports: [ConfigModule, LoggerModule],
|
|
219
|
+
providers: [ApiService, HttpClient],
|
|
220
|
+
exports: [ApiService],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Computed property - lazy evaluation
|
|
225
|
+
get apiService(): ApiService {
|
|
226
|
+
return this.module.get(ApiService);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get httpClient(): HttpClient {
|
|
230
|
+
return this.module.get(HttpClient);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Business logic using multiple services
|
|
234
|
+
async makeAuthenticatedRequest(url: string, token: string) {
|
|
235
|
+
const client = this.httpClient;
|
|
236
|
+
return client.request(url, {
|
|
237
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const apiModule = new ApiModule();
|
|
243
|
+
const response = await apiModule.makeAuthenticatedRequest('/users', 'token');
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Module Composition:**
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
class BaseModule extends ProviderModuleClass {
|
|
250
|
+
protected logAction(action: string): void {
|
|
251
|
+
const logger = this.module.get(LoggerService);
|
|
252
|
+
logger.log(`[${String(this.module.id)}] ${action}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class UserModule extends BaseModule {
|
|
257
|
+
constructor() {
|
|
258
|
+
super({
|
|
259
|
+
id: 'UserModule',
|
|
260
|
+
providers: [UserService, UserRepository],
|
|
261
|
+
exports: [UserService],
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
createUser(name: string) {
|
|
266
|
+
this.logAction(`Creating user: ${name}`);
|
|
267
|
+
const userService = this.module.get(UserService);
|
|
268
|
+
return userService.create(name);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
deleteUser(id: string) {
|
|
272
|
+
this.logAction(`Deleting user: ${id}`);
|
|
273
|
+
const userService = this.module.get(UserService);
|
|
274
|
+
return userService.delete(id);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### When to Use OOP vs Functional
|
|
280
|
+
|
|
281
|
+
**Use OOP-style (`extends ProviderModuleClass`) when:**
|
|
282
|
+
|
|
283
|
+
- You need custom business logic methods on the module itself
|
|
284
|
+
- You prefer class-based architecture
|
|
285
|
+
- You want computed properties or getters for providers
|
|
286
|
+
- You need initialization logic or state management in the module
|
|
287
|
+
- You're building a complex module with multiple related operations
|
|
288
|
+
|
|
289
|
+
**Use Functional-style (`ProviderModule.create()`) when:**
|
|
290
|
+
|
|
291
|
+
- You only need dependency injection without custom logic
|
|
292
|
+
- You prefer functional composition
|
|
293
|
+
- You want simpler, more concise code
|
|
294
|
+
- You're creating straightforward provider containers
|
|
295
|
+
|
|
296
|
+
**Key Point:** Both styles are fully compatible and can be mixed within the same application. `ProviderModuleClass` uses composition (contains a `ProviderModule` as `this.module`), preventing method name conflicts while providing identical DI functionality.
|
|
297
|
+
|
|
298
|
+
## Core Concepts
|
|
299
|
+
|
|
300
|
+
### ProviderModule
|
|
301
|
+
|
|
302
|
+
The fundamental building block of xInjection. Similar to NestJS modules, each `ProviderModule` encapsulates related providers with explicit control over what's exposed.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const DatabaseModule = ProviderModule.create({
|
|
306
|
+
id: 'DatabaseModule',
|
|
307
|
+
imports: [ConfigModule], // Modules to import
|
|
308
|
+
providers: [DatabaseService], // Services to register
|
|
309
|
+
exports: [DatabaseService], // What to expose to importers
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Key Methods:**
|
|
314
|
+
|
|
315
|
+
- `Module.get(token)` - Resolve a provider instance
|
|
316
|
+
- `Module.update.addProvider()` - Dynamically add providers
|
|
317
|
+
- `Module.update.addImport()` - Import other modules at runtime
|
|
318
|
+
- `Module.dispose()` - Clean up module resources
|
|
319
|
+
|
|
320
|
+
[Full API Documentation →](https://adimarianmutu.github.io/x-injection/classes/IProviderModule.html)
|
|
321
|
+
|
|
322
|
+
### AppModule
|
|
323
|
+
|
|
324
|
+
The global root module, automatically available in every application. Global modules are auto-imported into `AppModule`.
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import { AppModule } from '@adimm/x-injection';
|
|
328
|
+
|
|
329
|
+
// Add global providers
|
|
330
|
+
AppModule.update.addProvider(LoggerService);
|
|
331
|
+
|
|
332
|
+
// Access from any module
|
|
333
|
+
const anyModule = ProviderModule.create({ id: 'AnyModule' });
|
|
334
|
+
const logger = anyModule.get(LoggerService);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Blueprints
|
|
338
|
+
|
|
339
|
+
Blueprints allow you to define module configurations without instantiating them, enabling lazy loading and template reuse.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// Define blueprint
|
|
343
|
+
const DatabaseModuleBp = ProviderModule.blueprint({
|
|
344
|
+
id: 'DatabaseModule',
|
|
345
|
+
providers: [DatabaseService],
|
|
346
|
+
exports: [DatabaseService],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Import blueprint (auto-converts to module)
|
|
350
|
+
const AppModule = ProviderModule.create({
|
|
351
|
+
id: 'AppModule',
|
|
352
|
+
imports: [DatabaseModuleBp],
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Or create module from blueprint later
|
|
356
|
+
const DatabaseModule = ProviderModule.create(DatabaseModuleBp);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Benefits:**
|
|
360
|
+
|
|
361
|
+
- Deferred instantiation for better startup performance
|
|
362
|
+
- Reusable module templates across your application
|
|
363
|
+
- Scoped singletons per importing module
|
|
364
|
+
|
|
365
|
+
### Provider Tokens
|
|
366
|
+
|
|
367
|
+
xInjection supports four types of provider tokens:
|
|
368
|
+
|
|
369
|
+
**1. Class Token** (simplest):
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
@Injectable()
|
|
373
|
+
class ApiService {}
|
|
374
|
+
|
|
375
|
+
providers: [ApiService];
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**2. Class Token with Substitution**:
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
providers: [{ provide: ApiService, useClass: MockApiService }];
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**3. Value Token** (constants):
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
providers: [{ provide: 'API_KEY', useValue: 'secret-key-123' }];
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**4. Factory Token** (dynamic):
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
providers: [
|
|
394
|
+
{
|
|
395
|
+
provide: 'DATABASE_CONNECTION',
|
|
396
|
+
useFactory: (config: ConfigService) => createConnection(config.dbUrl),
|
|
397
|
+
inject: [ConfigService],
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Injection Scopes
|
|
403
|
+
|
|
404
|
+
Control provider lifecycle with three scope types (priority order: token > decorator > module default):
|
|
405
|
+
|
|
406
|
+
### Singleton (Default)
|
|
407
|
+
|
|
408
|
+
Cached after first resolution - same instance every time:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
@Injectable() // Singleton by default
|
|
412
|
+
class DatabaseService {}
|
|
413
|
+
|
|
414
|
+
Module.get(DatabaseService) === Module.get(DatabaseService); // true
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Transient
|
|
418
|
+
|
|
419
|
+
New instance on every resolution:
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
@Injectable(InjectionScope.Transient)
|
|
423
|
+
class RequestLogger {}
|
|
424
|
+
|
|
425
|
+
Module.get(RequestLogger) === Module.get(RequestLogger); // false
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Request
|
|
429
|
+
|
|
430
|
+
Single instance per resolution tree (useful for request-scoped data):
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
@Injectable(InjectionScope.Request)
|
|
434
|
+
class RequestContext {}
|
|
435
|
+
|
|
436
|
+
@Injectable(InjectionScope.Transient)
|
|
437
|
+
class Controller {
|
|
438
|
+
constructor(
|
|
439
|
+
public ctx1: RequestContext,
|
|
440
|
+
public ctx2: RequestContext
|
|
441
|
+
) {}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const controller = Module.get(Controller);
|
|
445
|
+
controller.ctx1 === controller.ctx2; // true (same resolution)
|
|
446
|
+
|
|
447
|
+
const controller2 = Module.get(Controller);
|
|
448
|
+
controller.ctx1 === controller2.ctx1; // false (different resolution)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Setting Scopes:**
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
// 1. In provider token (highest priority)
|
|
455
|
+
providers: [{ provide: Service, useClass: Service, scope: InjectionScope.Transient }];
|
|
456
|
+
|
|
457
|
+
// 2. In @Injectable decorator
|
|
458
|
+
@Injectable(InjectionScope.Request)
|
|
459
|
+
class Service {}
|
|
460
|
+
|
|
461
|
+
// 3. Module default (lowest priority)
|
|
462
|
+
ProviderModule.create({
|
|
463
|
+
id: 'MyModule',
|
|
464
|
+
defaultScope: InjectionScope.Transient,
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Module System
|
|
469
|
+
|
|
470
|
+
### Import/Export Pattern
|
|
471
|
+
|
|
472
|
+
Modules explicitly control dependency boundaries through imports and exports:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
const DatabaseModule = ProviderModule.create({
|
|
476
|
+
id: 'DatabaseModule',
|
|
477
|
+
providers: [DatabaseService, InternalCacheService],
|
|
478
|
+
exports: [DatabaseService], // Only DatabaseService is accessible
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const ApiModule = ProviderModule.create({
|
|
482
|
+
id: 'ApiModule',
|
|
483
|
+
imports: [DatabaseModule], // Gets access to DatabaseService
|
|
484
|
+
providers: [ApiService],
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// ✅ Works
|
|
488
|
+
const dbService = ApiModule.get(DatabaseService);
|
|
489
|
+
|
|
490
|
+
// ❌ Error - InternalCacheService not exported
|
|
491
|
+
const cache = ApiModule.get(InternalCacheService);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Re-exporting Modules
|
|
495
|
+
|
|
496
|
+
Modules can re-export imported modules to create aggregation modules:
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
const CoreModule = ProviderModule.create({
|
|
500
|
+
id: 'CoreModule',
|
|
501
|
+
imports: [DatabaseModule, ConfigModule],
|
|
502
|
+
exports: [DatabaseModule, ConfigModule], // Re-export both
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Consumers get both DatabaseModule and ConfigModule
|
|
506
|
+
const AppModule = ProviderModule.create({
|
|
507
|
+
imports: [CoreModule],
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Dynamic Module Updates
|
|
512
|
+
|
|
513
|
+
Modules support runtime modifications (use sparingly for performance):
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
const module = ProviderModule.create({ id: 'DynamicModule' });
|
|
517
|
+
|
|
518
|
+
// Add providers dynamically
|
|
519
|
+
module.update.addProvider(NewService);
|
|
520
|
+
module.update.addProvider(AnotherService, true); // true = also export
|
|
521
|
+
|
|
522
|
+
// Add imports dynamically
|
|
523
|
+
module.update.addImport(DatabaseModule, true); // true = also export
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Important:** Dynamic imports propagate automatically - if `ModuleA` imports `ModuleB`, and `ModuleB` dynamically imports `ModuleC` (with export), `ModuleA` automatically gets access to `ModuleC`'s exports.
|
|
527
|
+
|
|
528
|
+
### Global Modules
|
|
529
|
+
|
|
530
|
+
Mark modules as global to auto-import into `AppModule`:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
const LoggerModule = ProviderModule.create({
|
|
534
|
+
id: 'LoggerModule',
|
|
535
|
+
isGlobal: true,
|
|
536
|
+
providers: [LoggerService],
|
|
537
|
+
exports: [LoggerService],
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// LoggerService now available in all modules without explicit import
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Advanced Features
|
|
544
|
+
|
|
545
|
+
> [!WARNING]
|
|
546
|
+
> These features provide deep customization but can add complexity. Use them only when necessary.
|
|
547
|
+
|
|
548
|
+
### Events
|
|
549
|
+
|
|
550
|
+
Subscribe to module lifecycle events for monitoring and debugging:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { DefinitionEventType } from '@adimm/x-injection';
|
|
554
|
+
|
|
555
|
+
const module = ProviderModule.create({
|
|
556
|
+
id: 'MyModule',
|
|
557
|
+
providers: [MyService],
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const unsubscribe = module.update.subscribe(({ type, change }) => {
|
|
561
|
+
if (type === DefinitionEventType.GetProvider) {
|
|
562
|
+
console.log('Provider resolved:', change);
|
|
563
|
+
}
|
|
564
|
+
if (type === DefinitionEventType.Import) {
|
|
565
|
+
console.log('Module imported:', change);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Clean up when done
|
|
570
|
+
unsubscribe();
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Available Events:** `GetProvider`, `Import`, `Export`, `AddProvider`, `RemoveProvider`, `ExportModule` - [Full list →](https://adimarianmutu.github.io/x-injection/enums/DefinitionEventType.html)
|
|
574
|
+
|
|
575
|
+
> [!WARNING]
|
|
576
|
+
> Always unsubscribe to prevent memory leaks. Events fire **after** middlewares.
|
|
577
|
+
|
|
578
|
+
### Middlewares
|
|
579
|
+
|
|
580
|
+
Intercept and transform provider resolution before values are returned:
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import { MiddlewareType } from '@adimm/x-injection';
|
|
584
|
+
|
|
585
|
+
const module = ProviderModule.create({
|
|
586
|
+
id: 'MyModule',
|
|
587
|
+
providers: [PaymentService],
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// Transform resolved values
|
|
591
|
+
module.middlewares.add(MiddlewareType.BeforeGet, (provider, token, inject) => {
|
|
592
|
+
// Pass through if not interested
|
|
593
|
+
if (!(provider instanceof PaymentService)) return true;
|
|
594
|
+
|
|
595
|
+
// Use inject() to avoid infinite loops
|
|
596
|
+
const logger = inject(LoggerService);
|
|
597
|
+
logger.log('Payment service accessed');
|
|
598
|
+
|
|
599
|
+
// Transform the value
|
|
600
|
+
return {
|
|
601
|
+
timestamp: Date.now(),
|
|
602
|
+
value: provider,
|
|
603
|
+
};
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const payment = module.get(PaymentService);
|
|
607
|
+
// { timestamp: 1234567890, value: PaymentService }
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Control export access:**
|
|
611
|
+
|
|
612
|
+
```ts
|
|
613
|
+
module.middlewares.add(MiddlewareType.OnExportAccess, (importerModule, exportToken) => {
|
|
614
|
+
// Restrict access based on importer
|
|
615
|
+
if (importerModule.id === 'UntrustedModule' && exportToken === SensitiveService) {
|
|
616
|
+
return false; // Deny access
|
|
617
|
+
}
|
|
618
|
+
return true; // Allow
|
|
619
|
+
});
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**Available Middlewares:** `BeforeGet`, `BeforeAddProvider`, `BeforeAddImport`, `OnExportAccess` - [Full list →](https://adimarianmutu.github.io/x-injection/enums/MiddlewareType.html)
|
|
623
|
+
|
|
624
|
+
> [!CAUTION]
|
|
625
|
+
>
|
|
626
|
+
> - Returning `false` aborts the chain (no value returned)
|
|
627
|
+
> - Returning `true` passes value unchanged
|
|
628
|
+
> - Middlewares execute in registration order
|
|
629
|
+
> - Always handle errors in middleware chains
|
|
630
|
+
|
|
631
|
+
## Testing
|
|
632
|
+
|
|
633
|
+
Create mock modules easily using blueprint cloning:
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
// Production module
|
|
637
|
+
const ApiModuleBp = ProviderModule.blueprint({
|
|
638
|
+
id: 'ApiModule',
|
|
639
|
+
providers: [UserService, ApiService],
|
|
640
|
+
exports: [ApiService],
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// Test module - clone and override
|
|
644
|
+
const ApiModuleMock = ApiModuleBp.clone().updateDefinition({
|
|
645
|
+
id: 'ApiModuleMock',
|
|
646
|
+
providers: [
|
|
647
|
+
{ provide: UserService, useClass: MockUserService },
|
|
648
|
+
{
|
|
649
|
+
provide: ApiService,
|
|
650
|
+
useValue: {
|
|
651
|
+
sendRequest: jest.fn().mockResolvedValue({ data: 'test' }),
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Use in tests
|
|
658
|
+
const testModule = ProviderModule.create({
|
|
659
|
+
imports: [ApiModuleMock],
|
|
660
|
+
});
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
## Resources
|
|
664
|
+
|
|
665
|
+
📚 **[Full API Documentation](https://adimarianmutu.github.io/x-injection/index.html)** - Complete TypeDoc reference
|
|
666
|
+
|
|
667
|
+
⚛️ **[React Integration](https://github.com/AdiMarianMutu/x-injection-reactjs)** - Official React hooks and providers
|
|
668
|
+
|
|
669
|
+
💡 **[GitHub Issues](https://github.com/AdiMarianMutu/x-injection/issues)** - Bug reports and feature requests
|
|
670
|
+
|
|
671
|
+
## Contributing
|
|
672
|
+
|
|
673
|
+
Contributions welcome! Please ensure code follows the project style guidelines.
|
|
674
|
+
|
|
675
|
+
## Credits
|
|
676
|
+
|
|
677
|
+
**Author:** [Adi-Marian Mutu](https://www.linkedin.com/in/mutu-adi-marian/)
|
|
678
|
+
**Built on:** [InversifyJS](https://github.com/inversify/monorepo)
|
|
679
|
+
**Logo:** [Alexandru Turica](https://www.linkedin.com/in/alexandru-turica-82215522b/)
|
|
680
|
+
|
|
681
|
+
## License
|
|
682
|
+
|
|
683
|
+
MIT © Adi-Marian Mutu
|