@adimm/x-injection 0.8.0 → 1.1.6

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 CHANGED
@@ -1,441 +1,517 @@
1
- <h1 align="center">
2
- xInjection&nbsp;<a href="https://www.npmjs.com/package/@adimm/x-injection" target="__blank"><img src="https://badgen.net/npm/v/@adimm/x-injection"></a>
3
- <img src="https://badgen.net/npm/license/@adimm/x-injection">
4
- <a href="https://app.codecov.io/gh/AdiMarianMutu/x-injection" target="__blank"><img src="https://badgen.net/codecov/c/github/AdiMarianMutu/x-injection"></a>
5
- </h1>
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
- </p>
14
-
15
- ## Table of Contents
16
-
17
- - [Table of Contents](#table-of-contents)
18
- - [Overview](#overview)
19
- - [Features](#features)
20
- - [Installation](#installation)
21
- - [TypeScript Configuration](#typescript-configuration)
22
- - [Getting Started](#getting-started)
23
- - [Bootstrapping the AppModule](#bootstrapping-the-appmodule)
24
- - [Registering Global Providers](#registering-global-providers)
25
- - [Registering Global Modules](#registering-global-modules)
26
- - [Injection Scope](#injection-scope)
27
- - [Singleton](#singleton)
28
- - [Transient](#transient)
29
- - [Request](#request)
30
- - [Custom Provider Modules](#custom-provider-modules)
31
- - [Dynamic Exports](#dynamic-exports)
32
- - [Advanced Usage](#advanced-usage)
33
- - [Unit Tests](#unit-tests)
34
- - [Documentation](#documentation)
35
- - [ReactJS Implementation](#reactjs-implementation)
36
- - [Contributing](#contributing)
37
-
38
- ## Overview
39
-
40
- **xInjection** is a robust Inversion of Control [(IoC)](https://en.wikipedia.org/wiki/Inversion_of_control) library that extends [InversifyJS](https://github.com/inversify/InversifyJS) with a modular, [NestJS](https://github.com/nestjs/nest)-inspired Dependency Injection [(DI)](https://en.wikipedia.org/wiki/Dependency_injection) system. It enables you to **encapsulate** dependencies with fine-grained control using **[ProviderModule](https://adimarianmutu.github.io/x-injection/classes/ProviderModule.html)** classes, allowing for clean **separation** of concerns and **scalable** architecture.
41
-
42
- Each `ProviderModule` manages its _own_ container, supporting easy **decoupling** and _explicit_ control over which providers are **exported** and **imported** across modules. The global **[AppModule](https://adimarianmutu.github.io/x-injection/variables/AppModule.html)** is always available, ensuring a seamless foundation for your application's DI needs.
43
-
44
- ## Features
45
-
46
- - **NestJS-inspired module system:** Import and export providers between modules.
47
- - **Granular dependency encapsulation:** Each module manages its own container.
48
- - **Flexible provider scopes:** [Singleton](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#singleton), [Request](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#request), and [Transient](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#transient) lifecycles.
49
- - **Lifecycle hooks:** [onReady](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#onready) and [onDispose](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#ondispose) for _module_ initialization and cleanup.
50
- - **Advanced container access:** Directly interact with the underlying [InversifyJS containers](https://inversify.io/docs/api/container/) if needed.
51
-
52
- ## Installation
53
-
54
- First, ensure you have [`reflect-metadata`](https://www.npmjs.com/package/reflect-metadata) installed:
55
-
56
- ```sh
57
- npm i reflect-metadata
58
- ```
59
-
60
- Then install `xInjection`:
61
-
62
- ```sh
63
- npm i @adimm/x-injection
64
- ```
65
-
66
- ### TypeScript Configuration
67
-
68
- Add the following options to your `tsconfig.json` to enable decorator metadata:
69
-
70
- ```json
71
- {
72
- "compilerOptions": {
73
- "experimentalDecorators": true,
74
- "emitDecoratorMetadata": true
75
- }
76
- }
77
- ```
78
-
79
- ## Getting Started
80
-
81
- ### Bootstrapping the AppModule
82
-
83
- In your application's entry point, import and register the global `AppModule`:
84
-
85
- ```ts
86
- import { AppModule } from '@adimm/x-injection';
87
-
88
- AppModule.register({});
89
- ```
90
-
91
- > **Note:** You must call `AppModule.register()` even if you have no global providers. Passing an empty object `{}` is valid.
92
-
93
- ### Registering Global Providers
94
-
95
- To make services available throughout your application, register them as global providers:
96
-
97
- ```ts
98
- import { AppModule, Injectable } from '@adimm/x-injection';
99
-
100
- @Injectable()
101
- class LoggerService {}
102
-
103
- @Injectable()
104
- class ConfigService {
105
- constructor(private readonly logger: LoggerService) {}
106
- }
107
-
108
- AppModule.register({
109
- providers: [LoggerService, ConfigService],
110
- });
111
- ```
112
-
113
- Now, `LoggerService` and `ConfigService` can be injected anywhere in your app, including inside all `ProviderModules`.
114
-
115
- ### Registering Global Modules
116
-
117
- You can also import entire modules into the `AppModule` like so:
118
-
119
- ```ts
120
- const SECRET_TOKEN_PROVIDER = { provide: 'SECRET_TOKEN', useValue: '123' };
121
- const SECRET_TOKEN_2_PROVIDER = { provide: 'SECRET_TOKEN_2', useValue: 123 };
122
-
123
- const ConfigModule = new ProviderModule({
124
- identifier: Symbol('ConfigModule'),
125
- markAsGlobal: true,
126
- providers: [SECRET_TOKEN_PROVIDER, SECRET_TOKEN_2_PROVIDER],
127
- exports: [SECRET_TOKEN_PROVIDER, SECRET_TOKEN_2_PROVIDER],
128
- });
129
-
130
- AppModule.register({
131
- imports: [ConfigModule],
132
- });
133
- ```
134
-
135
- > **Note:** _All modules which are imported into the `AppModule` must have the [markAsGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#markasglobal) option set to `true`, otherwise the [InjectionProviderModuleGlobalMarkError](https://adimarianmutu.github.io/x-injection/classes/InjectionProviderModuleGlobalMarkError.html) exception will be thrown!_
136
- >
137
- > **Note2:** _An [InjectionProviderModuleGlobalMarkError](https://adimarianmutu.github.io/x-injection/classes/InjectionProviderModuleGlobalMarkError.html) exception will be thrown also when importing into the `AppModule` a module which does **not** have the [markAsGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#markasglobal) flag option!_
138
-
139
- ### Injection Scope
140
-
141
- There are mainly 3 first-class ways to set the [InjectionScope](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html) of a provider, and each one has an order priority.
142
- The below list shows them in order of priority _(highest to lowest)_, meaning that if 2 _(or more)_ ways are used, the method with the highest priority will take precedence.
143
-
144
- 1. By providing the [scope](https://adimarianmutu.github.io/x-injection/interfaces/ProviderScopeOption.html) property to the [ProviderToken](https://adimarianmutu.github.io/x-injection/types/ProviderToken.html):
145
- ```ts
146
- const USER_PROVIDER: ProviderToken<UserService> = {
147
- scope: InjectionScope.Request,
148
- provide: UserService,
149
- useClass: UserService,
150
- };
151
- ```
152
- 2. Within the [@Injectable](https://adimarianmutu.github.io/x-injection/functions/Injectable.html) decorator:
153
- ```ts
154
- @Injectable(InjectionScope.Transient)
155
- class Transaction {}
156
- ```
157
- 3. By providing the [defaultScope](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#defaultscope) property when initializing a `ProviderModule`:
158
- ```ts
159
- const RainModule = new ProviderModule({
160
- identifier: Symbol('RainModule'),
161
- defaultScope: InjectionScope.Transient,
162
- });
163
- ```
164
-
165
- > **Note:** _Imported modules/providers retain their original `InjectionScope`!_
166
-
167
- #### Singleton
168
-
169
- The [Singleton](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#singleton) injection scope means that once a dependency has been resolved from within a module will be cached and further resolutions will use the value from the cache.
170
-
171
- Example:
172
-
173
- ```ts
174
- expect(MyModule.get(MyProvider)).toBe(MyModule.get(MyProvider));
175
- // true
176
- ```
177
-
178
- #### Transient
179
-
180
- The [Transient](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#transient) injection scope means that a _new_ instance of the dependency will be used whenever a resolution occurs.
181
-
182
- Example:
183
-
184
- ```ts
185
- expect(MyModule.get(MyProvider)).toBe(MyModule.get(MyProvider));
186
- // false
187
- ```
188
-
189
- #### Request
190
-
191
- The [Request](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#request) injection scope means that the _same_ instance will be used when a resolution happens in the _same_ request scope.
192
-
193
- Example:
194
-
195
- ```ts
196
- class Book {}
197
-
198
- class Box {
199
- constructor(
200
- private readonly book0: Book,
201
- private readonly book1: Book
202
- ) {}
203
- }
204
-
205
- const box0 = MyModule.get(Box);
206
- const box1 = MyModule.get(Box);
207
-
208
- expect(box0.book0).toBe(box0.book1);
209
- // true
210
-
211
- expect(box0.book0).toBe(box1.book0);
212
- // false
213
- ```
214
-
215
- ## Custom Provider Modules
216
-
217
- You can define custom modules to encapsulate related providers and manage their scope:
218
-
219
- ```ts
220
- import { Injectable, InjectionScope, ProviderModule } from '@adimm/x-injection';
221
-
222
- @Injectable()
223
- export class DatabaseService {
224
- // Implementation...
225
- }
226
-
227
- @Injectable()
228
- export class SessionService {
229
- constructor(public readonly userService: UserService) {}
230
-
231
- // Implementation...
232
- }
233
-
234
- export const DatabaseModule = new ProviderModule({
235
- identifier: Symbol('DatabaseModule'),
236
- providers: [DatabaseService],
237
- exports: [DatabaseService],
238
- onReady: async (module) => {
239
- const databaseService = module.get(DatabaseService);
240
-
241
- // Additional initialization...
242
- },
243
- onDispose: async (module) => {
244
- const databaseService = module.get(DatabaseService);
245
-
246
- databaseService.closeConnection();
247
- },
248
- });
249
-
250
- export const SessionModule = new ProviderModule({
251
- identifier: Symbol('SessionModule'),
252
- defaultScope: InjectionScope.Request,
253
- providers: [SessionService],
254
- exports: [SessionService],
255
- });
256
- ```
257
-
258
- Register these modules in your `AppModule`:
259
-
260
- ```ts
261
- AppModule.register({
262
- imports: [DatabaseModule, SessionModule],
263
- });
264
- ```
265
-
266
- > **Note:** The `AppModule.register` method can be invoked only _once_! _(You may re-invoke it only after the module has been disposed)_ Preferably during your application bootstrapping process.
267
-
268
- From now on, the `AppModule` container has the references of the `DatabaseService` and the `SessionService`.
269
- But it is important to keep in mind of some aspects:
270
-
271
- - We know that the `DatabaseService` scope is set to `Singleton`.
272
- - We know that the `SessionService` scope is set to `Request`.
273
-
274
- This means that if we do:
275
-
276
- ```ts
277
- const databaseService_a = AppModule.get(DatabaseService);
278
- const databaseService_b = AppModule.get(DatabaseService);
279
-
280
- expect(databaseService_a).toBe(databaseService_b);
281
- ```
282
-
283
- Will produce `true`, but, doing:
284
-
285
- ```ts
286
- const sessionService_a = AppModule.get(SessionService);
287
- const sessionService_b = AppModule.get(SessionService);
288
-
289
- expect(sessionService_a).toBe(sessionService_b);
290
- ```
291
-
292
- Will produce `false` because Inversify's [Request](https://inversify.io/docs/fundamentals/binding/#request) scope acts as a `Singleton` scope within the same module context
293
- and as a `Transient` scope outside the module context.
294
-
295
- **Inject multiple dependencies:**
296
-
297
- ```ts
298
- const [serviceA, serviceB] = BigModule.getMany(ServiceA, ServiceB);
299
- // or
300
- const [serviceC, serviceD] = BigModule.getMany<[ServiceC, ServiceD]>(SERVICE_TOKEN, 'SERVICE_ID');
301
- ```
302
-
303
- ### Dynamic Exports
304
-
305
- `xInjection` allows a `ProviderModule` to also dynamically choose _what_ and _when_ to export a provider/module.
306
-
307
- > **With great power comes great responsibility! 🥸**
308
-
309
- This is a very powerful feature and for the most use cases, you'll not need to use it.
310
- However, if you may ever need to use a dynamic export, here is a simple example:
311
-
312
- ```ts
313
- class WingsService {}
314
-
315
- class AnimalService {}
316
-
317
- class CatService extends AnimalService {
318
- // This line throws an error at some point.
319
- constructor(public readonly wings: WingsService) {}
320
- }
321
-
322
- class CrowService extends AnimalService {
323
- constructor(public readonly wings: WingsService) {}
324
- }
325
-
326
- const AnimalModule = new ProviderModule({
327
- identifier: Symbol('AnimalModule'),
328
- providers: [AnimalService, { provide: WingsService, useClass: WingsService, scope: InjectionScope.Transient }],
329
- exports: [AnimalService, WingsService],
330
- dynamicExports: (importerModule, moduleExports) => {
331
- // If the importer module is `CrowModule`, we'll export the entire
332
- // `exports` list, because `moduleExports` is actually the `exports` array declared above.
333
- // Meaning that the `CrowModule` container will also have access to the `WingsService`
334
- if (importerModule.toString() === 'CrowModule') return moduleExports;
335
-
336
- // Otherwise it is the `CatModule` and we are not
337
- // exporting the `WingsService` as cats don't fly, or do they fly? 🧐
338
- return [AnimalService];
339
- },
340
- });
341
-
342
- const CrowModule = new ProviderModule({
343
- identifier: Symbol('CrowModule'),
344
- imports: [AnimalModule],
345
- providers: [CrowService],
346
- exports: [CrowService],
347
- });
348
-
349
- const CatModule = new ProviderModule({
350
- identifier: Symbol('CatModule'),
351
- imports: [AnimalModule],
352
- providers: [CatService],
353
- exports: [CatService],
354
- });
355
- ```
356
-
357
- Hopefully the provided example shows how powerful the `dynamicExports` property it is, but, there are some things to keep in mind in order to avoid nasty bugs!
358
-
359
- ## Advanced Usage
360
-
361
- Each `ProviderModule` instance implements the `IProviderModule` interface for simplicity, but can be cast to `IProviderModuleNaked` for advanced operations:
362
-
363
- ```ts
364
- const nakedModule = ProviderModuleInstance.toNaked();
365
- // or: nakedModule = ProviderModuleInstance as IproviderModuleNaked;
366
- const inversifyContainer = nakedModule.container;
367
- ```
368
-
369
- You can also access the global `InversifyJS` container directly:
370
-
371
- ```ts
372
- import { AppModule, GlobalContainer } from '@adimm/x-injection';
373
-
374
- const globalContainer = GlobalContainer || AppModule.toNaked().container;
375
- ```
376
-
377
- For advanced scenarios, `IProviderModuleNaked` exposes additional methods (prefixed with `__`) that wrap InversifyJS APIs, supporting native `xInjection` provider tokens and more.
378
-
379
- ## Unit Tests
380
-
381
- It is very easy to create mock modules so you can use them in your unit tests.
382
-
383
- ```ts
384
- class ApiService {
385
- constructor(private readonly userService: UserService) {}
386
-
387
- async sendRequest<T>(location: LocationParams): Promise<T> {
388
- // Pseudo Implementation
389
- return this.sendToLocation(user, location);
390
- }
391
-
392
- private async sendToLocation(user: User, location: any): Promise<any> {}
393
- }
394
-
395
- const ApiModule = new ProviderModule({
396
- identifier: Symbol('ApiModule'),
397
- providers: [UserService, ApiService],
398
- });
399
-
400
- const ApiModuleMocked = new ProviderModule({
401
- identifier: Symbol('ApiModule_MOCK'),
402
- providers: [
403
- {
404
- provide: UserService,
405
- useClass: UserService_Mock,
406
- },
407
- {
408
- provide: ApiService,
409
- useValue: {
410
- sendRequest: async (location) => {
411
- console.log(location);
412
- },
413
- },
414
- },
415
- ],
416
- });
417
- ```
418
-
419
- Now what you have to do is just to provide the `ApiModuleMocked` instead of the `ApiModule` 😎
420
-
421
- ## Documentation
422
-
423
- Comprehensive, auto-generated documentation is available at:
424
-
425
- 👉 [https://adimarianmutu.github.io/x-injection/index.html](https://adimarianmutu.github.io/x-injection/index.html)
426
-
427
- ## ReactJS Implementation
428
-
429
- 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 ⚛️
430
-
431
- For more details check out the [GitHub Repository](https://github.com/AdiMarianMutu/x-injection-reactjs).
432
-
433
- ## Contributing
434
-
435
- Pull requests are warmly welcomed! 😃
436
-
437
- Please ensure your contributions adhere to the project's code style. See the repository for more details.
438
-
439
- ---
440
-
441
- > For questions, feature requests, or bug reports, feel free to open an [issue](https://github.com/AdiMarianMutu/x-injection/issues) on GitHub!
1
+ <h1 align="center">
2
+ xInjection&nbsp;<a href="https://www.npmjs.com/package/@adimm/x-injection" target="__blank"><img src="https://badgen.net/npm/v/@adimm/x-injection"></a>
3
+ <img src="https://badgen.net/npm/license/@adimm/x-injection">
4
+ <a href="https://app.codecov.io/gh/AdiMarianMutu/x-injection" target="__blank"><img src="https://badgen.net/codecov/c/github/AdiMarianMutu/x-injection"></a>
5
+ </h1>
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
+ </p>
14
+
15
+ ## Table of Contents
16
+
17
+ - [Table of Contents](#table-of-contents)
18
+ - [Overview](#overview)
19
+ - [Features](#features)
20
+ - [Installation](#installation)
21
+ - [TypeScript Configuration](#typescript-configuration)
22
+ - [Getting Started](#getting-started)
23
+ - [Bootstrapping the AppModule](#bootstrapping-the-appmodule)
24
+ - [Registering Global Providers](#registering-global-providers)
25
+ - [Registering Global Modules](#registering-global-modules)
26
+ - [Injection Scope](#injection-scope)
27
+ - [Singleton](#singleton)
28
+ - [Transient](#transient)
29
+ - [Request](#request)
30
+ - [Provider Modules](#provider-modules)
31
+ - [ProviderModuleDefinition](#providermoduledefinition)
32
+ - [Feed to `new ProviderModule`](#feed-to-new-providermodule)
33
+ - [Feed to `lazyImport`](#feed-to-lazyimport)
34
+ - [Why not just use the `ProviderModuleOptions` interface?](#why-not-just-use-the-providermoduleoptions-interface)
35
+ - [Lazy `imports` and `exports`](#lazy-imports-and-exports)
36
+ - [Imports](#imports)
37
+ - [Exports](#exports)
38
+ - [Advanced Usage](#advanced-usage)
39
+ - [ProviderModuleNaked Interface](#providermodulenaked-interface)
40
+ - [Strict Mode](#strict-mode)
41
+ - [Why you should not turn it off:](#why-you-should-not-turn-it-off)
42
+ - [MarkAsGlobal](#markasglobal)
43
+ - [Unit Tests](#unit-tests)
44
+ - [Documentation](#documentation)
45
+ - [ReactJS Implementation](#reactjs-implementation)
46
+ - [Contributing](#contributing)
47
+
48
+ ## Overview
49
+
50
+ **xInjection** is a robust Inversion of Control [(IoC)](https://en.wikipedia.org/wiki/Inversion_of_control) library that extends [InversifyJS](https://github.com/inversify/InversifyJS) with a modular, [NestJS](https://github.com/nestjs/nest)-inspired Dependency Injection [(DI)](https://en.wikipedia.org/wiki/Dependency_injection) system. It enables you to **encapsulate** dependencies with fine-grained control using **[ProviderModule](https://adimarianmutu.github.io/x-injection/classes/ProviderModule.html)** classes, allowing for clean **separation** of concerns and **scalable** architecture.
51
+
52
+ Each `ProviderModule` manages its _own_ container, supporting easy **decoupling** and _explicit_ control over which providers are **exported** and **imported** across modules. The global **[AppModule](https://adimarianmutu.github.io/x-injection/variables/AppModule.html)** is always available, ensuring a seamless foundation for your application's DI needs.
53
+
54
+ ## Features
55
+
56
+ - **NestJS-inspired module system:** Import and export providers between modules.
57
+ - **Granular dependency encapsulation:** Each module manages its own container.
58
+ - **Flexible provider scopes:** [Singleton](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#singleton), [Request](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#request), and [Transient](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#transient) lifecycles.
59
+ - **Lifecycle hooks:** [onReady](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#onready) and [onDispose](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#ondispose) for _module_ initialization and cleanup.
60
+ - **Advanced container access:** Directly interact with the underlying [InversifyJS containers](https://inversify.io/docs/api/container/) if needed.
61
+
62
+ ## Installation
63
+
64
+ First, ensure you have [`reflect-metadata`](https://www.npmjs.com/package/reflect-metadata) installed:
65
+
66
+ ```sh
67
+ npm i reflect-metadata
68
+ ```
69
+
70
+ Then install `xInjection`:
71
+
72
+ ```sh
73
+ npm i @adimm/x-injection
74
+ ```
75
+
76
+ ### TypeScript Configuration
77
+
78
+ Add the following options to your `tsconfig.json` to enable decorator metadata:
79
+
80
+ ```json
81
+ {
82
+ "compilerOptions": {
83
+ "experimentalDecorators": true,
84
+ "emitDecoratorMetadata": true
85
+ }
86
+ }
87
+ ```
88
+
89
+ ## Getting Started
90
+
91
+ ### Bootstrapping the AppModule
92
+
93
+ In your application's entry point, import and register the global `AppModule`:
94
+
95
+ ```ts
96
+ import { AppModule } from '@adimm/x-injection';
97
+
98
+ AppModule.register({});
99
+ ```
100
+
101
+ > **Note:** You must call `AppModule.register()` even if you have no global providers. Passing an empty object `{}` is valid.
102
+
103
+ ### Registering Global Providers
104
+
105
+ To make services available throughout your application, register them as global providers:
106
+
107
+ ```ts
108
+ import { AppModule, Injectable } from '@adimm/x-injection';
109
+
110
+ @Injectable()
111
+ class LoggerService {}
112
+
113
+ @Injectable()
114
+ class ConfigService {
115
+ constructor(private readonly logger: LoggerService) {}
116
+ }
117
+
118
+ AppModule.register({
119
+ providers: [LoggerService, ConfigService],
120
+ });
121
+ ```
122
+
123
+ Now, `LoggerService` and `ConfigService` can be injected anywhere in your app, including inside all `ProviderModules`.
124
+
125
+ ### Registering Global Modules
126
+
127
+ You can also import entire modules into the `AppModule` like so:
128
+
129
+ ```ts
130
+ const SECRET_TOKEN_PROVIDER = { provide: 'SECRET_TOKEN', useValue: '123' };
131
+ const SECRET_TOKEN_2_PROVIDER = { provide: 'SECRET_TOKEN_2', useValue: 123 };
132
+
133
+ const ConfigModule = new ProviderModule({
134
+ identifier: Symbol('ConfigModule'),
135
+ markAsGlobal: true,
136
+ providers: [SECRET_TOKEN_PROVIDER, SECRET_TOKEN_2_PROVIDER],
137
+ exports: [SECRET_TOKEN_PROVIDER, SECRET_TOKEN_2_PROVIDER],
138
+ });
139
+
140
+ AppModule.register({
141
+ imports: [ConfigModule],
142
+ });
143
+ ```
144
+
145
+ > **Note:** _All modules which are imported into the `AppModule` must have the [markAsGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#markasglobal) option set to `true`, otherwise the [InjectionProviderModuleGlobalMarkError](https://adimarianmutu.github.io/x-injection/classes/InjectionProviderModuleGlobalMarkError.html) exception will be thrown!_
146
+ >
147
+ > **Note2:** _An [InjectionProviderModuleGlobalMarkError](https://adimarianmutu.github.io/x-injection/classes/InjectionProviderModuleGlobalMarkError.html) exception will be thrown also when importing into the `AppModule` a module which does **not** have the [markAsGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#markasglobal) flag option!_
148
+
149
+ ### Injection Scope
150
+
151
+ There are mainly 3 first-class ways to set the [InjectionScope](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html) of a provider, and each one has an order priority.
152
+ The below list shows them in order of priority _(highest to lowest)_, meaning that if 2 _(or more)_ ways are used, the method with the highest priority will take precedence.
153
+
154
+ 1. By providing the [scope](https://adimarianmutu.github.io/x-injection/interfaces/ProviderScopeOption.html) property to the [ProviderToken](https://adimarianmutu.github.io/x-injection/types/ProviderToken.html):
155
+ ```ts
156
+ const USER_PROVIDER: ProviderToken<UserService> = {
157
+ scope: InjectionScope.Request,
158
+ provide: UserService,
159
+ useClass: UserService,
160
+ };
161
+ ```
162
+ 2. Within the [@Injectable](https://adimarianmutu.github.io/x-injection/functions/Injectable.html) decorator:
163
+ ```ts
164
+ @Injectable(InjectionScope.Transient)
165
+ class Transaction {}
166
+ ```
167
+ 3. By providing the [defaultScope](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#defaultscope) property when initializing a `ProviderModule`:
168
+ ```ts
169
+ const RainModule = new ProviderModule({
170
+ identifier: Symbol('RainModule'),
171
+ defaultScope: InjectionScope.Transient,
172
+ });
173
+ ```
174
+
175
+ > **Note:** _Imported modules/providers retain their original `InjectionScope`!_
176
+
177
+ #### Singleton
178
+
179
+ The [Singleton](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#singleton) injection scope means that once a dependency has been resolved from within a module will be cached and further resolutions will use the value from the cache.
180
+
181
+ Example:
182
+
183
+ ```ts
184
+ expect(MyModule.get(MyProvider)).toBe(MyModule.get(MyProvider));
185
+ // true
186
+ ```
187
+
188
+ #### Transient
189
+
190
+ The [Transient](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#transient) injection scope means that a _new_ instance of the dependency will be used whenever a resolution occurs.
191
+
192
+ Example:
193
+
194
+ ```ts
195
+ expect(MyModule.get(MyProvider)).toBe(MyModule.get(MyProvider));
196
+ // false
197
+ ```
198
+
199
+ #### Request
200
+
201
+ The [Request](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#request) injection scope means that the _same_ instance will be used when a resolution happens in the _same_ request scope.
202
+
203
+ Example:
204
+
205
+ ```ts
206
+ @Injectable(InjectionScope.Transient)
207
+ class Book {
208
+ author: string;
209
+ }
210
+
211
+ @Injectable(InjectionScope.Request)
212
+ class Metro2033 extends Book {
213
+ override author = 'Dmitry Alekseyevich Glukhovsky';
214
+ }
215
+
216
+ @Injectable(InjectionScope.Transient)
217
+ class Library {
218
+ constructor(
219
+ public readonly metro2033: Metro2033,
220
+ public readonly metro2033_reference: Metro2033
221
+ ) {}
222
+ }
223
+
224
+ const winstonLibrary = MyModule.get(Library);
225
+ const londonLibrary = MyModule.get(Library);
226
+
227
+ expect(winstonLibrary.metro2033).toBe(winstonLibrary.metro2033_reference);
228
+ expect(londonLibrary.metro2033).toBe(londonLibrary.metro2033_reference);
229
+ // true
230
+
231
+ expect(winstonLibrary.metro2033).toBe(londonLibrary.metro2033);
232
+ // false
233
+ ```
234
+
235
+ ## Provider Modules
236
+
237
+ You can define `modules` to encapsulate related providers and manage their scope:
238
+
239
+ ```ts
240
+ import { Injectable, InjectionScope, ProviderModule } from '@adimm/x-injection';
241
+
242
+ @Injectable()
243
+ export class DatabaseService {
244
+ // Implementation...
245
+ }
246
+
247
+ @Injectable()
248
+ export class SessionService {
249
+ constructor(public readonly userService: UserService) {}
250
+
251
+ // Implementation...
252
+ }
253
+
254
+ export const DatabaseModule = new ProviderModule({
255
+ identifier: Symbol('DatabaseModule'),
256
+ // or: identifier: 'DatabaseModule',
257
+ providers: [DatabaseService],
258
+ exports: [DatabaseService],
259
+ onReady: async (module) => {
260
+ const databaseService = module.get(DatabaseService);
261
+
262
+ // Additional initialization...
263
+ },
264
+ onDispose: async (module) => {
265
+ const databaseService = module.get(DatabaseService);
266
+
267
+ databaseService.closeConnection();
268
+ },
269
+ });
270
+
271
+ export const SessionModule = new ProviderModule({
272
+ identifier: Symbol('SessionModule'),
273
+ defaultScope: InjectionScope.Request,
274
+ providers: [SessionService],
275
+ exports: [SessionService],
276
+ });
277
+ ```
278
+
279
+ Register these modules in your `AppModule`:
280
+
281
+ ```ts
282
+ AppModule.register({
283
+ imports: [DatabaseModule, SessionModule],
284
+ });
285
+ ```
286
+
287
+ > **Note:** The `AppModule.register` method can be invoked only _once_! _(You may re-invoke it only after the module has been disposed)_ Preferably during your application bootstrapping process.
288
+
289
+ From now on, the `AppModule` container has the references of the `DatabaseService` and the `SessionService`.
290
+
291
+ **Inject multiple dependencies:**
292
+
293
+ ```ts
294
+ const [serviceA, serviceB] = BigModule.getMany(ServiceA, ServiceB);
295
+ // or
296
+ const [serviceC, serviceD] = BigModule.getMany<[ServiceC, ServiceD]>(SERVICE_TOKEN, 'SERVICE_ID');
297
+ ```
298
+
299
+ ### ProviderModuleDefinition
300
+
301
+ When you do:
302
+
303
+ ```ts
304
+ const MyModule = new ProviderModule({...});
305
+ ```
306
+
307
+ The `MyModule` will be eagerly instantiated, therefore creating under-the-hood an unique container for the `MyModule` instance.
308
+
309
+ In some scenarios you may need/want to avoid that, you can achieve that by using the [ProviderModuleDefinition](https://adimarianmutu.github.io/x-injection/interfaces/IProviderModuleDefinition.html) `class`. It allows you to just define a _blueprint_ of the `ProviderModule` without all the overhead of instantiating the actual module.
310
+
311
+ ```ts
312
+ const GarageModuleDefinition = new ProviderModuleDefinition({ identifier: 'GarageModuleDefinition' });
313
+
314
+ // You can always edit all the properties of the definition.
315
+
316
+ GarageModuleDefinition.imports = [...GarageModuleDefinition.imports, PorscheModule, FerrariModuleDefinition];
317
+ ```
318
+
319
+ #### Feed to `new ProviderModule`
320
+
321
+ ```ts
322
+ const GarageModule = new ProviderModule(GarageModuleDefinition);
323
+ ```
324
+
325
+ #### Feed to `lazyImport`
326
+
327
+ ```ts
328
+ ExistingModule.lazyImport(GarageModuleDefinition);
329
+ ```
330
+
331
+ > **Note:** _Providing it to the `lazyImport` method will automatically instantiate a new `ProviderModule` on-the-fly!_
332
+
333
+ #### Why not just use the `ProviderModuleOptions` interface?
334
+
335
+ That's a very good question! It means that you understood that the `ProviderModuleDefinition` is actually a `class` wrapper of the `ProviderModuleOptions`.
336
+
337
+ Theoretically you _can_ use a _plain_ `object` having the `ProviderModuleOptions` interface, however, the `ProviderModuleOptions` interface purpose is solely to _expose/shape_ the options with which a module can be instantiated, while the `ProviderModuleDefinition` purpose is to _define_ the actual `ProviderModule` _blueprint_.
338
+
339
+ ### Lazy `imports` and `exports`
340
+
341
+ You can also lazy import or export `providers`/`modules`, usually you don't need this feature, but there may be some advanced cases where you may want to be able to do so.
342
+
343
+ > The lazy nature defers the actual module resolution, this may help in breaking immediate circular reference chain under some circumstances.
344
+
345
+ #### Imports
346
+
347
+ You can lazily `import` a `module` by invoking the [lazyImport](https://adimarianmutu.github.io/x-injection/interfaces/IProviderModule.html#lazyimport) `method` at any time in your code.
348
+
349
+ ```ts
350
+ const GarageModule = new ProviderModule({
351
+ identifier: 'GarageModule',
352
+ // Eager imports happen at module initialization
353
+ imports: [FerrariModule, PorscheModule, ...]
354
+ });
355
+
356
+ // Later in your code
357
+
358
+ GarageModule.lazyImport(LamborghiniModule, BugattiModule, ...);
359
+ ```
360
+
361
+ #### Exports
362
+
363
+ You can lazily `export` a `provider` or `module` by providing a `callback` _(it can also be an `async` callback)_ as shown below:
364
+
365
+ ```ts
366
+ const SecureBankBranchModule = new ProviderModule({
367
+ identifier: 'SecureBankBranchModule',
368
+ providers: [BankBranchService],
369
+ exports: [BankBranchService],
370
+ });
371
+
372
+ const BankModule = new ProviderModule({
373
+ identifier: 'BankModule',
374
+ imports: [SecureBankBranchModule],
375
+ exports: [..., (importerModule) => {
376
+ // When the module having the identifier `UnknownBankModule` imports the `BankModule`
377
+ // it'll not be able to also import the `SecureBankBranchModule` as we are not returning it here.
378
+ if (importerModule.toString() === 'UnknownBankModule') return;
379
+
380
+ // Otherwise we safely export it
381
+ return SecureBankBranchModule;
382
+ }]
383
+ });
384
+ ```
385
+
386
+ ## Advanced Usage
387
+
388
+ ### ProviderModuleNaked Interface
389
+
390
+ Each `ProviderModule` instance implements the `IProviderModule` interface for simplicity, but can be cast to `IProviderModuleNaked` for advanced operations:
391
+
392
+ ```ts
393
+ const nakedModule = ProviderModuleInstance.toNaked();
394
+ // or: nakedModule = ProviderModuleInstance as IproviderModuleNaked;
395
+ const inversifyContainer = nakedModule.container;
396
+ ```
397
+
398
+ You can also access the global `InversifyJS` container directly:
399
+
400
+ ```ts
401
+ import { AppModule, GlobalContainer } from '@adimm/x-injection';
402
+
403
+ const globalContainer = GlobalContainer || AppModule.toNaked().container;
404
+ ```
405
+
406
+ For advanced scenarios, `IProviderModuleNaked` exposes additional methods (prefixed with `__`) that wrap InversifyJS APIs, supporting native `xInjection` provider tokens and more.
407
+
408
+ ### Strict Mode
409
+
410
+ By default the `AppModule` runs in "strict mode", a built-in mode which enforces an _opinionated_ set of rules aiming to reduce common pitfalls and edge-case bugs.
411
+
412
+ When invoking the [AppModule.register](https://adimarianmutu.github.io/x-injection/interfaces/IAppModule.html#register-1) `method` you can set the [\_strict](https://adimarianmutu.github.io/x-injection/interfaces/AppModuleOptions.html#_strict) property to `false` in order to permanentely disable those set of built-in rules.
413
+
414
+ > **Note:** _Do not open an `issue` if a bug or edge-case is caused by having the `strict` property disabled!_
415
+
416
+ #### Why you should not turn it off:
417
+
418
+ ##### MarkAsGlobal
419
+
420
+ The [markAsGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#markasglobal) flag property is used to make sure that `modules` which should be registered directly into the `AppModule` are indeed provided to the the `imports` array of the `AppModule` and the the other way around, if a `module` is imported into the `AppModule` without having the `markAsGlobal` flag property set, it'll throw an error.
421
+
422
+ This may look redundant, but it may save you _(and your team)_ some hours of debugging in understanding why some `providers` are able to make their way into other `modules`. As those `providers` are now acting as _global_ `providers`.
423
+
424
+ Imagine the following scenario:
425
+
426
+ ```ts
427
+ const ScopedModule = new ProviderModule({
428
+ identifier: 'ScopedModule',
429
+ providers: [...],
430
+ exports: [...],
431
+ });
432
+
433
+ const AnotherScopedModule = new ProviderModule({
434
+ identifier: 'AnotherScopedModule',
435
+ imports: [ScopedModule],
436
+ providers: [...],
437
+ exports: [...],
438
+ });
439
+
440
+ const GlobalModule = new ProviderModule({
441
+ identifier: 'GlobalModule',
442
+ markAsGlobal: true,
443
+ imports: [AnotherScopedModule],
444
+ });
445
+
446
+ AppModule.register({
447
+ imports: [GlobalModule],
448
+ });
449
+ ```
450
+
451
+ At first glance you may not spot/understand the issue there, but because the `GlobalModule` _(which is then imported into the `AppModule`)_ is _directly_ importing the `AnotherScopedModule`, it means that _all_ the `providers` of the `AnotherScopedModule` and `ScopedModule` _(because `AnotherScopedModule` also imports `ScopedModule`)_ will become accessible through your entire app!
452
+
453
+ Disabling `strict` mode removes this safeguard, allowing any module to be imported into the `AppModule` regardless of `markAsGlobal`, increasing risk of bugs by exposing yourself to the above example.
454
+
455
+ ## Unit Tests
456
+
457
+ It is very easy to create mock modules so you can use them in your unit tests.
458
+
459
+ ```ts
460
+ class ApiService {
461
+ constructor(private readonly userService: UserService) {}
462
+
463
+ async sendRequest<T>(location: LocationParams): Promise<T> {
464
+ // Pseudo Implementation
465
+ return this.sendToLocation(user, location);
466
+ }
467
+
468
+ private async sendToLocation(user: User, location: any): Promise<any> {}
469
+ }
470
+
471
+ const ApiModule = new ProviderModule({
472
+ identifier: Symbol('ApiModule'),
473
+ providers: [UserService, ApiService],
474
+ });
475
+
476
+ const ApiModuleMocked = new ProviderModule({
477
+ identifier: Symbol('ApiModule_MOCK'),
478
+ providers: [
479
+ {
480
+ provide: UserService,
481
+ useClass: UserService_Mock,
482
+ },
483
+ {
484
+ provide: ApiService,
485
+ useValue: {
486
+ sendRequest: async (location) => {
487
+ console.log(location);
488
+ },
489
+ },
490
+ },
491
+ ],
492
+ });
493
+ ```
494
+
495
+ Now what you have to do is just to provide the `ApiModuleMocked` instead of the `ApiModule` 😎
496
+
497
+ ## Documentation
498
+
499
+ Comprehensive, auto-generated documentation is available at:
500
+
501
+ 👉 [https://adimarianmutu.github.io/x-injection/index.html](https://adimarianmutu.github.io/x-injection/index.html)
502
+
503
+ ## ReactJS Implementation
504
+
505
+ 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 ⚛️
506
+
507
+ For more details check out the [GitHub Repository](https://github.com/AdiMarianMutu/x-injection-reactjs).
508
+
509
+ ## Contributing
510
+
511
+ Pull requests are warmly welcomed! 😃
512
+
513
+ Please ensure your contributions adhere to the project's code style. See the repository for more details.
514
+
515
+ ---
516
+
517
+ > For questions, feature requests, or bug reports, feel free to open an [issue](https://github.com/AdiMarianMutu/x-injection/issues) on GitHub!