@adimm/x-injection 1.2.1 → 2.0.4

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,7 +1,7 @@
1
1
  <h1 align="center">
2
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
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
5
  </h1>
6
6
 
7
7
  <p align="center">
@@ -20,36 +20,48 @@ xInjection&nbsp;<a href="https://www.npmjs.com/package/@adimm/x-injection" targe
20
20
  - [Installation](#installation)
21
21
  - [TypeScript Configuration](#typescript-configuration)
22
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)
23
+ - [Quick Start](#quick-start)
24
+ - [Glossary](#glossary)
25
+ - [ProviderModule](#providermodule)
26
+ - [AppModule](#appmodule)
27
+ - [Blueprint](#blueprint)
28
+ - [Definition](#definition)
29
+ - [Conventions](#conventions)
30
+ - [ProviderModule](#providermodule-1)
31
+ - [Blueprints](#blueprints)
32
+ - [ProviderToken](#providertoken)
33
+ - [AppModule](#appmodule-1)
34
+ - [ProviderModule API](#providermodule-api)
35
+ - [Injection Scope](#injection-scope)
36
+ - [Singleton](#singleton)
37
+ - [Transient](#transient)
38
+ - [Request](#request)
39
+ - [Provider Tokens](#provider-tokens)
30
40
  - [Provider Modules](#provider-modules)
31
- - [ProviderModuleDefinition](#providermoduledefinition)
32
- - [Feed to `new ProviderModule`](#feed-to-new-providermodule)
33
- - [Feed to `lazyImport`](#feed-to-lazyimport)
34
- - [Lazy `imports` and `exports`](#lazy-imports-and-exports)
35
- - [Imports](#imports)
36
- - [Exports](#exports)
41
+ - [Blueprints](#blueprints-1)
42
+ - [Import Behavior](#import-behavior)
43
+ - [isGlobal](#isglobal)
44
+ - [Definitions](#definitions)
37
45
  - [Advanced Usage](#advanced-usage)
38
- - [ProviderModuleNaked Interface](#providermodulenaked-interface)
39
- - [Strict Mode](#strict-mode)
40
- - [Why you should not turn it off:](#why-you-should-not-turn-it-off)
41
- - [isGlobal](#isglobal)
46
+ - [Events](#events)
47
+ - [Middlewares](#middlewares)
48
+ - [Internals](#internals)
49
+ - [ProviderModule](#providermodule-2)
50
+ - [MiddlewaresManager](#middlewaresmanager)
51
+ - [ModuleContainer](#modulecontainer)
52
+ - [ImportedModuleContainer](#importedmodulecontainer)
53
+ - [DynamicModuleDefinition](#dynamicmoduledefinition)
54
+ - [ProviderModuleBlueprint](#providermoduleblueprint)
55
+ - [Set of Helpers](#set-of-helpers)
42
56
  - [Unit Tests](#unit-tests)
43
57
  - [Documentation](#documentation)
44
- - [Conventions](#conventions)
45
- - [ProviderModule](#providermodule)
46
- - [ProviderModuleDefinition](#providermoduledefinition-1)
47
58
  - [ReactJS Implementation](#reactjs-implementation)
48
59
  - [Contributing](#contributing)
60
+ - [Credits](#credits)
49
61
 
50
62
  ## Overview
51
63
 
52
- **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.
64
+ **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/IProviderModule.html)** classes, allowing for clean **separation** of concerns and **scalable** architecture.
53
65
 
54
66
  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.
55
67
 
@@ -58,8 +70,11 @@ Each `ProviderModule` manages its _own_ container, supporting easy **decoupling*
58
70
  - **NestJS-inspired module system:** Import and export providers between modules.
59
71
  - **Granular dependency encapsulation:** Each module manages its own container.
60
72
  - **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.
61
- - **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.
62
- - **Advanced container access:** Directly interact with the underlying [InversifyJS containers](https://inversify.io/docs/api/container/) if needed.
73
+ - **Lifecycle hooks:** [onReady](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#onready), [onReset](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#onreset) and [onDispose](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#ondispose) for _module_ initialization and cleanup.
74
+ - **Middlewares:** Tap into the low-level implementation without any effort by just adding new `middlewares`.
75
+ - **Events:** Subscribe to internal events for maximum control.
76
+ - **Blueprints:** Plan ahead your `modules` without eagerly instantiating them.
77
+ - **Fully Agnostic:** It doesn't rely on any framework, just on [InversifyJS](https://inversify.io/) as it uses it under-the-hood to build the containers. It works the same both client side and server side.
63
78
 
64
79
  ## Installation
65
80
 
@@ -69,6 +84,10 @@ First, ensure you have [`reflect-metadata`](https://www.npmjs.com/package/reflec
69
84
  npm i reflect-metadata
70
85
  ```
71
86
 
87
+ > [!NOTE]
88
+ >
89
+ > You may have to add `import 'reflect-metadata'` at the entry point of your application.
90
+
72
91
  Then install `xInjection`:
73
92
 
74
93
  ```sh
@@ -90,113 +109,189 @@ Add the following options to your `tsconfig.json` to enable decorator metadata:
90
109
 
91
110
  ## Getting Started
92
111
 
93
- ### Bootstrapping the AppModule
112
+ ### Quick Start
113
+
114
+ ```ts
115
+ import { Injectable, ProviderModule } from '@adimm/x-injection';
94
116
 
95
- In your application's entry point, import and register the global `AppModule`:
117
+ @Injectable()
118
+ class HelloService {
119
+ sayHello() {
120
+ return 'Hello, world!';
121
+ }
122
+ }
123
+
124
+ const HelloModule = ProviderModule.create({
125
+ id: 'HelloModule',
126
+ providers: [HelloService],
127
+ exports: [HelloService],
128
+ });
129
+
130
+ const helloService = HelloModule.get(HelloService);
131
+
132
+ console.log(helloService.sayHello());
133
+ // => 'Hello, world!'
134
+ ```
135
+
136
+ ### Glossary
137
+
138
+ #### ProviderModule
139
+
140
+ The core class of `xInjection`, if you ever worked with [NestJS](https://nestjs.com/) _(or [Angular](https://angular.dev/))_, you'll find it very familiar.
96
141
 
97
142
  ```ts
98
- import { AppModule } from '@adimm/x-injection';
143
+ const GarageModule = ProviderModule.create({ id: 'GarageModule', imports: [], providers: [], exports: [] });
144
+ ```
145
+
146
+ #### AppModule
147
+
148
+ It is a special instance of the `ProviderModule` class which acts as the `root` of your `modules` graph, all _global_ modules will be automatically imported into the `AppModule` and shared across all your modules.
99
149
 
100
- AppModule.register({});
150
+ #### Blueprint
151
+
152
+ Another core class which most probably you'll end using a lot too, to keep it short, it allows you to plan ahead the `modules` without instantiating them.
153
+
154
+ ```ts
155
+ const CarModuleBlueprint = ProviderModule.blueprint({ id: 'CarModule', imports: [], providers: [], exports: [] });
101
156
  ```
102
157
 
103
- > [!WARNING]
104
- >
105
- > _You must invoke `AppModule.register()` even if you have no global providers. Passing an empty object `{}` to the method is valid._
158
+ #### Definition
106
159
 
107
- > [!NOTE]
108
- >
109
- > _You can import additional modules (later at run-time) into the `AppModule` by using the [lazyImport](https://adimarianmutu.github.io/x-injection/interfaces/IAppModule.html#lazyimport-1) `method`._
160
+ It is used to refer to the three main blocks of a module:
110
161
 
111
- ### Registering Global Providers
162
+ - [imports](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#imports)
163
+ - [providers](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#providers)
164
+ - [exports](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#exports)
112
165
 
113
- To make services available throughout your application, register them as global providers:
166
+ ### Conventions
167
+
168
+ The library has some opinionated _naming_ conventions which you should adopt too
169
+
170
+ #### ProviderModule
171
+
172
+ All variables holding an _instance_ of a `ProviderModule` should be written in [PascalCase](https://www.wikidata.org/wiki/Q9761807) and _suffixed_ with `Module`, like this:
114
173
 
115
174
  ```ts
116
- import { AppModule, Injectable } from '@adimm/x-injection';
175
+ const DatabaseModule = ProviderModule.create({...});
176
+ const UserModule = ProviderModule.create({...});
177
+ const CarPartsModule = ProviderModule.create({...});
178
+ ```
117
179
 
118
- @Injectable()
119
- class LoggerService {}
180
+ The `id` property of the `ProviderModule.options` should be the same as the `module` variable name.
120
181
 
121
- @Injectable()
122
- class ConfigService {
123
- constructor(private readonly logger: LoggerService) {}
124
- }
182
+ ```ts
183
+ const DatabaseModule = ProviderModule.create({ id: 'DatabaseModule' });
184
+ const UserModule = ProviderModule.create({ id: 'UserModule' });
185
+ const CarPartsModule = ProviderModule.create({ id: 'CarPartsModule' });
186
+ ```
125
187
 
126
- AppModule.register({
127
- providers: [LoggerService, ConfigService],
128
- });
188
+ If you are exporting a `module` from a designated file, then you should name that file as following:
189
+
190
+ ```
191
+ database.module.ts
192
+ user.module.ts
193
+ car-parts.module.ts
129
194
  ```
130
195
 
131
- Now, `LoggerService` and `ConfigService` can be injected anywhere in your app, including inside all `ProviderModules`.
196
+ > [!TIP]
197
+ >
198
+ > If you install/use the [Material Icon Theme](https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme) VS Code extension, you'll see the `*.module.ts` files with a specific icon.
132
199
 
133
- ### Registering Global Modules
200
+ #### Blueprints
134
201
 
135
- You can also import entire modules into the `AppModule` like so:
202
+ All variables holding an _instance_ of a `ProviderModuleBlueprint` should be written in [PascalCase](https://www.wikidata.org/wiki/Q9761807) too and _suffixed_ with `ModuleBp`, like this:
136
203
 
137
204
  ```ts
138
- const SECRET_TOKEN_PROVIDER = { provide: 'SECRET_TOKEN', useValue: '123' };
139
- const SECRET_TOKEN_2_PROVIDER = { provide: 'SECRET_TOKEN_2', useValue: 123 };
140
-
141
- const ConfigModuleDef = new ProviderModuleDefinition({
142
- identifier: 'ConfigModule',
143
- isGlobal: true,
144
- providers: [SECRET_TOKEN_PROVIDER, SECRET_TOKEN_2_PROVIDER],
145
- exports: [SECRET_TOKEN_PROVIDER, SECRET_TOKEN_2_PROVIDER],
146
- });
205
+ const DatabaseModuleBp = ProviderModule.blueprint({...});
206
+ const UserModuleBp = ProviderModule.blueprint({...});
207
+ const CarPartsModuleBp = ProviderModule.blueprint({...});
208
+ ```
147
209
 
148
- AppModule.register({
149
- imports: [ConfigModuleDef],
150
- });
210
+ The `id` property of the `ProviderModuleBlueprint.options` should **not** end with `Bp` because when you'll import that `blueprint` into a `module`, the exact provided `id` will be used!
211
+
212
+ ```ts
213
+ const DatabaseModuleBp = ProviderModule.create({ id: 'DatabaseModule' });
214
+ const UserModuleBp = ProviderModule.create({ id: 'UserModule' });
215
+ const CarPartsModuleBp = ProviderModule.create({ id: 'CarPartsModule' });
216
+ ```
217
+
218
+ If you are exporting a `blueprint` from a designated file, then you should name that file as following:
219
+
220
+ ```
221
+ database.module.bp.ts
222
+ user.module.bp.ts
223
+ car-parts.module.bp.ts
151
224
  ```
152
225
 
226
+ #### ProviderToken
227
+
228
+ All variables holding an _object_ representing a [ProviderToken](https://adimarianmutu.github.io/x-injection/types/ProviderToken.html) should be written in [SCREAMING_SNAKE_CASE](https://en.wikipedia.org/wiki/Snake_case) and _suffixed_ with `_PROVIDER`, like this:
229
+
230
+ ```ts
231
+ const USER_SERVICE_PROVIDER = UserService;
232
+ ```
233
+
234
+ If you are exporting a `provider token` from a designated file, then you should name that file as following:
235
+
236
+ ```
237
+ user-service.provider.ts
238
+ ```
239
+
240
+ ### AppModule
241
+
242
+ As explained above, it is the `root` module of your application, it is always available and eagerly bootstrapped.
243
+
244
+ Usually you'll not interact much with it as any `module` which is defined as _global_ will be automatically imported into it, therefore having its `exports` definition available across all your modules _out-of-the-box_. However, you can use it like any `ProviderModule` instance.
245
+
153
246
  > [!WARNING]
154
247
  >
155
- > _Importing a module marked as global into a scoped module will automatically import it into the `AppModule` rather than the scoped module itself!_
248
+ > Importing the `AppModule` into any `module` will throw an error!
156
249
 
157
- This means that you can also do the following:
250
+ You have 2 options to access it:
158
251
 
159
252
  ```ts
160
- const FancyModuleDefinition = new ProviderModuleDefinition({
161
- identifier: 'FancyModule',
162
- isGlobal: true,
163
- providers: [FancyService],
164
- exports: [FancyService],
165
- });
253
+ import { AppModule } from '@adimm/x-injection';
254
+ ```
166
255
 
167
- const ScopedModule = new ProviderModule({
168
- identifier: 'ScopedModule',
169
- // This `ScopedModule` will automatically "forward" the global `FancyModuleDefinition`
170
- // exports to the `AppModule`.
171
- imports: [FancyModuleDefinition],
172
- });
256
+ or
257
+
258
+ ```ts
259
+ import { ProviderModule } from '@adimm/x-injection';
260
+
261
+ ProviderModule.APP_MODULE_REF;
173
262
 
174
- AppModule.get(FancyService);
175
- // returns the global instance of the `FancyService` class
263
+ // This option is mostly used internally, but you can 100% safely use it as well.
176
264
  ```
177
265
 
178
- The above can also be written as:
266
+ Providing global services to the `AppModule`:
179
267
 
180
268
  ```ts
181
- const FancyModuleDefinition = new ProviderModuleDefinition({
182
- identifier: 'FancyModule',
183
- isGlobal: true,
184
- providers: [FancyService],
185
- exports: [FancyService],
186
- });
269
+ @Injectable()
270
+ class UserService {}
187
271
 
188
- AppModule.lazyImport(FancyModuleDefinition);
272
+ AppModule.update.addProvider(UserService);
189
273
  ```
190
274
 
191
275
  > [!NOTE]
192
276
  >
193
- > _All modules which are imported into the `AppModule` must have the [isGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#isglobal) option set to `true`, otherwise the [InjectionProviderModuleGlobalMarkError](https://adimarianmutu.github.io/x-injection/classes/InjectionProviderModuleGlobalMarkError.html) exception will be thrown!_
194
- >
195
- > _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 [isGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#isglobal) flag option!_
277
+ > All `providers` scope is set to [Singleton](https://adimarianmutu.github.io/x-injection/enums/InjectionScope.html#singleton) by default if not provided.
196
278
 
197
- ### Injection Scope
279
+ Yes, that's it, now you have access to the `UserService` anywhere in your app across your `modules`, meaning that you can now do:
198
280
 
199
- 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.
281
+ ```ts
282
+ const UnrelatedModule = ProviderModule.create({ id: 'UnrelatedModule' });
283
+
284
+ const userService = UnrelatedModule.get(UserService);
285
+ // returns the `userService` singleton instance.
286
+ ```
287
+
288
+ ## ProviderModule API
289
+
290
+ You can see all the available `properties` and `methods` of the `ProviderModule` [here](https://adimarianmutu.github.io/x-injection/classes/IProviderModule.html).
291
+
292
+ ## Injection Scope
293
+
294
+ 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.
200
295
  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.
201
296
 
202
297
  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):
@@ -224,7 +319,7 @@ The below list shows them in order of priority _(highest to lowest)_, meaning th
224
319
  >
225
320
  > _Imported modules/providers retain their original `InjectionScope`!_
226
321
 
227
- #### Singleton
322
+ ### Singleton
228
323
 
229
324
  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.
230
325
 
@@ -235,7 +330,7 @@ expect(MyModule.get(MyProvider)).toBe(MyModule.get(MyProvider));
235
330
  // true
236
331
  ```
237
332
 
238
- #### Transient
333
+ ### Transient
239
334
 
240
335
  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.
241
336
 
@@ -246,7 +341,7 @@ expect(MyModule.get(MyProvider)).toBe(MyModule.get(MyProvider));
246
341
  // false
247
342
  ```
248
343
 
249
- #### Request
344
+ ### Request
250
345
 
251
346
  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.
252
347
 
@@ -282,235 +377,417 @@ expect(winstonLibrary.metro2033).toBe(londonLibrary.metro2033);
282
377
  // false
283
378
  ```
284
379
 
285
- ## Provider Modules
380
+ ## Provider Tokens
286
381
 
287
- You can define `modules` to encapsulate related providers and manage their scope:
382
+ A [ProviderToken](https://adimarianmutu.github.io/x-injection/types/ProviderToken.html) is another core block of `xInjection` _(and also many other IoC/DI libs)_ which is used to define a `token` which can then be used to resolve a `provider`.
288
383
 
289
- ```ts
290
- import { Injectable, InjectionScope, ProviderModule } from '@adimm/x-injection';
384
+ `xInjection` offers _4_ types of tokens:
291
385
 
292
- @Injectable()
293
- export class DatabaseService {
294
- // Implementation...
295
- }
386
+ - [ProviderIdentifier](https://adimarianmutu.github.io/x-injection/types/ProviderIdentifier.html)
387
+ - It allows you to bind a `value` to a specific _transparent_ token, like a `Class`, `Function`, `symbol` or `string`:
388
+ ```ts
389
+ const API_SERVICE_PROVIDER = ApiService;
390
+ // or
391
+ const CONSTANT_SECRET_PROVIDER = 'Shh';
392
+ ```
393
+ - [ProviderClassToken](https://adimarianmutu.github.io/x-injection/types/ProviderClassToken.html)
296
394
 
297
- @Injectable()
298
- export class SessionService {
299
- constructor(public readonly userService: UserService) {}
395
+ - It can be used define the token _and_ the provider:
300
396
 
301
- // Implementation...
302
- }
397
+ ```ts
398
+ const HUMAN_SERVICE_PROVIDER = { provide: HumanService, useClass: FemaleService };
303
399
 
304
- export const DatabaseModuleDef = new ProviderModuleDefinitionDef({
305
- identifier: 'DatabaseModule',
306
- providers: [DatabaseService],
307
- exports: [DatabaseService],
308
- onReady: async (module) => {
309
- const databaseService = module.get(DatabaseService);
400
+ // This will bind `HumanService` as the `token` and will resolve `FemaleService` from the container.
401
+ ```
310
402
 
311
- // Additional initialization...
312
- },
313
- onDispose: () => {
314
- return {
315
- before: async (module) => {
316
- // It is invoked right before the dispose process begins.
317
- // This means that the `module` container is still available to be used.
403
+ - [ProviderValueToken](https://adimarianmutu.github.io/x-injection/types/ProviderValueToken.html)
318
404
 
319
- const databaseService = module.get(DatabaseService);
405
+ - It can be used to easily bind _constant_ values, it can be anything, but once resolved it'll be cached and re-used upon further resolutions
320
406
 
321
- databaseService.closeConnection();
322
- },
323
- after: async (module) => {
324
- // It is invoked right after the dispose process ended.
325
- // This means that the `module` container is not available anymore.
326
- },
327
- };
328
- },
329
- });
407
+ ```ts
408
+ const THEY_DONT_KNOW_PROVIDER = { provide: CONSTANT_SECRET_PROVIDER, useValue: `They'll never know` };
409
+ const THEY_MAY_KNOW_PROVIDER = { provide: CONSTANT_SECRET_PROVIDER, useValue: 'Maybe they know?' };
330
410
 
331
- export const SessionModuleDef = new ProviderModuleDefinition({
332
- identifier: 'SessionModule',
333
- defaultScope: InjectionScope.Request,
334
- providers: [SessionService],
335
- exports: [SessionService],
336
- });
337
- ```
411
+ // As you can see we now have 2 different ProviderTokens which use the same `provide` key.
412
+ // This means that resolving the `CONSTANT_SECRET_PROVIDER` will return an array of strings.
413
+ ```
338
414
 
339
- Register these modules in your `AppModule`:
415
+ - [ProviderFactoryToken](https://adimarianmutu.github.io/x-injection/types/ProviderFactoryToken.html)
340
416
 
341
- ```ts
342
- AppModule.register({
343
- imports: [DatabaseModuleDef, SessionModuleDef],
344
- });
345
- ```
417
+ - It can be used to bind a `factory` which is intended for more complex scenarios:
346
418
 
347
- > [!WARNING]
419
+ ```ts
420
+ const MAKE_PIZZA_PROVIDER = {
421
+ provide: 'MAKE_PIZZA',
422
+ useFactory: async (apiService: ApiService, pizzaService: PizzaService) => {
423
+ const typeOfPizza = await apiService.getTypeOfPizza();
424
+
425
+ if (typeOfPizza === 'margherita') return pizzaService.make.margherita;
426
+ if (typeOfPizza === 'quattro_stagioni') return pizzaService.make.quattroStagioni;
427
+ // and so on
428
+ },
429
+ // optional
430
+ inject: [API_SERVICE_PROVIDER, PizzaService],
431
+ };
432
+ ```
433
+
434
+ These are all the available `ProviderToken` you can use.
435
+
436
+ > [!NOTE]
348
437
  >
349
- > The `AppModule.register` method can be invoked only _once_, preferably during your application bootstrapping process. _(you may re-invoke it only after the module has been disposed)_
438
+ > In `NestJS` and `Angular` you can't use a `ProviderToken` to _get_ a value, `xInjection` allows this pattern, but you must understand that what it actually does, is to use the _value_ from the `provide` property.
439
+
440
+ ## Provider Modules
350
441
 
351
- From now on, the `AppModule` container has the references of the `DatabaseService` and the `SessionService`.
442
+ As you already saw till here, everything relies around the `ProviderModule` class, so let's dive a little more deep into understanding it.
352
443
 
353
- **Inject multiple dependencies:**
444
+ The most straight forward way to _create/instantiate_ a new `module` is:
354
445
 
355
446
  ```ts
356
- const [serviceA, serviceB] = BigModule.getMany(ServiceA, ServiceB);
357
- // or
358
- const [serviceC, serviceD] = BigModule.getMany<[ServiceC, ServiceD]>(SERVICE_TOKEN, 'SERVICE_ID');
447
+ const MyModule = ProviderModule.create({
448
+ id: 'MyModule',
449
+ imports: [AnotherModule, SecondModule, ThirdModule],
450
+ providers: [
451
+ { provide: CONSTANT_SECRET_PROVIDER, useValue: 'ultra secret' },
452
+ PizzaService,
453
+ { provide: HumanService, useClass: FemaleService },
454
+ ],
455
+ exports: [SecondModule, ThirdModule, PizzaService],
456
+ });
359
457
  ```
360
458
 
361
- ### ProviderModuleDefinition
459
+ From what we can see, the `MyModule` is importing into it 3 more modules, each of them may export one or more _(maybe nothing, that's valid too)_ providers, or even other `modules`.
460
+ Because we imported them into the `MyModule`, now we have access to any providers they may have chosen to export, and the same is true also for _their exported_ modules.
461
+
462
+ Then, we've chosen to _re-export_ from the `MyModule` the `SecondModule` and `ThirdModule`, meaning that if a different `module` imports `MyModule`, it'll automatically get access to those 2 modules as well. And in the end we also exported our own `PizzaService`, while the remaining other 2 providers, `CONSTANT_SECRET_PROVIDER` and `HumanService` can't be accessed when importing `MyModule`.
463
+
464
+ This is the _core_ feature of `xInjection` _(and `Angular`/`NestJS` DI system)_, being able to encapsulate the providers, so nothing can spill out without our explicit consent.
465
+
466
+ ---
362
467
 
363
- When you do:
468
+ We could also achieve the above by using the `ProviderModule` API like this:
364
469
 
365
470
  ```ts
366
- const MyModule = new ProviderModule({...});
471
+ MyModule.update.addImport(AnotherModule);
472
+ MyModule.update.addImport(SecondModule, true); // `true` means "also add to the `exports` definition"
473
+ MyModule.update.addImport(ThirdModule, true);
474
+
475
+ MyModule.update.addProvider({ provide: CONSTANT_SECRET_PROVIDER, useValue: 'ultra secret' });
476
+ MyModule.update.addProvider(PizzaService, true);
477
+ MyModule.update.addProvider({ provide: HumanService, useClass: FemaleService });
367
478
  ```
368
479
 
369
- The `MyModule` will be eagerly instantiated, therefore creating under-the-hood an unique container for the `MyModule` instance.
480
+ Now you may probably ask yourself `If we import with the 'addImport' method a new module into an already imported module, will we have access to the providers of that newly imported module?`
370
481
 
371
- 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.
482
+ The ansuwer is `yes`, we do have access thanks to the _dynamic_ nature of the `ProviderModule` class. Meaning that doing the following will work as expected:
372
483
 
373
484
  ```ts
374
- const GarageModuleDefinition = new ProviderModuleDefinition({ identifier: 'GarageModuleDefinition' });
485
+ const InnerModule = ProviderModule.create({
486
+ id: 'InnerModule',
487
+ providers: [FirstService],
488
+ exports: [FirstService],
489
+ });
375
490
 
376
- // You can always edit all the properties of the definition.
491
+ const OuterModule = ProviderModule.create({
492
+ id: 'OuterModule',
493
+ imports: [InnerModule],
494
+ });
377
495
 
378
- GarageModuleDefinition.imports = [...GarageModuleDefinition.imports, PorscheModule, FerrariModuleDefinition];
379
- ```
496
+ const UnknownModule = ProviderModule.create({
497
+ id: 'UnknownModule',
498
+ providers: [SecondService],
499
+ exports: [SecondService],
500
+ });
380
501
 
381
- #### Feed to `new ProviderModule`
502
+ InnerModule.update.addImport(UnknownModule);
382
503
 
383
- ```ts
384
- const GarageModule = new ProviderModule(GarageModuleDefinition);
504
+ const secondService = OuterModule.get(SecondService);
385
505
  ```
386
506
 
387
- #### Feed to `lazyImport`
507
+ The `OuterModule` has now access to the `UnknownModule` exports because it has been _dynamically_ imported _(later at run-time)_ into the `InnerModule` _(which has been imported into `OuterModule` during the `bootstrap` phase)_
508
+
509
+ Basically what happens is that when a `module` is imported, it takes care of _notify_ the `host` module if its _definiton_ changed.
510
+
511
+ > [!WARNING]
512
+ >
513
+ > This is a very powerful feature which comes in with some costs, _most of the time negligible_, but if you have an app which has thousand and thousand of `modules` doing this type of _dynamic_ behavior, you may incur in some performance issues which will require proper design to keep under control.
514
+ >
515
+ > _Most of the times the best solution is to leverage the nature of `blueprints`._
516
+
517
+ ---
518
+
519
+ Sometimes you may actually want to _lazy_ import a `module` from a _file_, this can be done very easily with `xInjection`:
388
520
 
389
521
  ```ts
390
- ExistingModule.lazyImport(GarageModuleDefinition);
522
+ (async () => {
523
+ await MyModule.update.addImportLazy(async () => (await import('./lazy.module')).LazyModule);
524
+
525
+ MyModule.isImportingModule('LazyModule');
526
+ // => true
527
+ })();
391
528
  ```
392
529
 
530
+ > [!TIP]
531
+ >
532
+ > This design pattern is _extremely_ powerful and useful when you may have a lot of `modules` initializing during the app bootstrap process as you can defer their initialization, or even never load them if the user never needs those specific `modules` _(this is mostly applicable on the client-side rather than the server-side)_
533
+
534
+ Keep reading to understand how you can defer initialization of the `modules` by using `blueprints`.
535
+
536
+ ## Blueprints
537
+
538
+ The [ProviderModuleBlueprint](https://adimarianmutu.github.io/x-injection/classes/ProviderModuleBlueprint.html) `class` main purpose is to encapsulate the `definitions` of a `Module`, when you do `ProviderModule.blueprint({...})` you are _not_ actually creating an instance of the `ProviderModule` class, but an instance of the `ProviderModuleBlueprint` class.
539
+
540
+ > To better understand the above concept; imagine the `blueprint` as being a _dormant_ _(static)_ `module` which is not fully awake _(dynamic)_ till it is actually _imported_ into a `module`.
541
+
542
+ ### Import Behavior
543
+
544
+ Whenever you _import_ a `blueprint` into a `module`, it'll automatically be "transformed" to a `ProviderModule` instance by the engine, this step is crucial as a `blueprint` per se does not contain a _container_, just its _definitions_.
545
+
546
+ This has a "gotcha", because the conversion happens internally, you'll "not" be able to get a reference to that imported module, not directly at least _(I may change this in a future patch)_, this means that if you don't have the reference of the `ProviderModule`, you can't remove it from the _imported_ module _(if you'll ever need to remove it)_.
547
+
393
548
  > [!NOTE]
394
549
  >
395
- > _Providing it to the `lazyImport` method will automatically instantiate a new `ProviderModule` on-the-fly!_
550
+ > There's currently an workaround for this, you can `subscribe` to the `Import` event and get from there the `ProviderModule` instance, just make sure to check the `id` of the module via `module.toString()` when doing so.
551
+
552
+ It is also important, to understand the _injection_ `scope` of an imported `blueprint`; we previously learned that when we import a `blueprint` into a `module` it automatically creates an instance of the `ProviderModule` from it, this means that all the `singleton` providers of the `blueprint` definition are now _scoped singleton_, where _scoped_ means _singleton in relation to their imported module_.
553
+
554
+ ### isGlobal
555
+
556
+ When you initialize a `blueprint` with the [isGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#isglobal) property set to `true`, the out-of-the-box behavior is to _automatically_ import the `blueprint` into the `AppModule`. You can disable this behavior by setting the [autoImportIntoAppModuleWhenGlobal](https://adimarianmutu.github.io/x-injectioninterfaces/ModuleBlueprintOptions.html#autoimportintoappmodulewhenglobal) property to `false`
557
+
558
+ ```ts
559
+ const GlobalModuleBp = ProviderModule.blueprint({..., isGlobal: true }, { autoImportIntoAppModuleWhenGlobal: false });
560
+ ```
561
+
562
+ Now you can decide when to import it into the `AppModule` by doing `AppModule.addImport(GlobalModuleBp)`.
563
+
564
+ ---
565
+
566
+ I highly recommend to take advantage of the `blueprints` nature in order to plan-ahead your `modules`;
567
+
568
+ Why?
396
569
 
397
- ### Lazy `imports` and `exports`
570
+ - To _define module configurations upfront_ without incurring the cost of immediate initialization _(even if negligible)_.
571
+ - To reuse module _definitions across_ different parts of your application while maintaining isolated instances. _(when possible/applicable)_
572
+ - To _compose modules flexibly_, allowing you to adjust module dependencies dynamically before instantiation.
398
573
 
399
- 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.
574
+ ### Definitions
400
575
 
401
- > The lazy nature defers the actual module resolution, this may help in breaking immediate circular reference chain under some circumstances.
576
+ After you have provided the _initial_ `definitons` of a `blueprint`, you can always modify them with the [updateDefinition](https://adimarianmutu.github.io/x-injection/classes/ProviderModuleBlueprint.html#updatedefinition) `method`.
402
577
 
403
- #### Imports
578
+ > [!NOTE]
579
+ >
580
+ > Updating the `definitions` of a `blueprint` after has been _imported_ into a `module` will **_not_** propagate those changes to the `module` where it has been imported.
581
+
582
+ ---
404
583
 
405
- 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.
584
+ This means that we can actually _leverage_ the `blueprints` nature to _defer_ the actual initialization of a `module` by doing so:
406
585
 
407
586
  ```ts
408
- const GarageModule = new ProviderModule({
409
- identifier: 'GarageModule',
410
- // Eager imports happen at module initialization
411
- imports: [FerrariModule, PorscheModule, ...]
587
+ const UserModuleBp = ProviderModule.blueprint({
588
+ id: 'UserModule',
589
+ ...
412
590
  });
413
591
 
414
592
  // Later in your code
415
593
 
416
- GarageModule.lazyImport(LamborghiniModule, BugattiModule, ...);
594
+ const UserModule = ProviderModule.create(UserModuleBp);
417
595
  ```
418
596
 
419
- #### Exports
597
+ The `UserModule` will be created only when _necessary_ and it'll use the same exact definitons which are available into the `UserModuleBp` at the time of the `create` invokation.
420
598
 
421
- You can lazily `export` a `provider` or `module` by providing a `callback` _(it can also be an `async` callback)_ as shown below:
599
+ ## Advanced Usage
600
+
601
+ > [!WARNING]
602
+ >
603
+ > This section covers advanced features which may add additional complexity _(or even bugs)_ to your application if you misuse them, use these features only if truly needed and after evaluating the _pros_ and _cons_ of each.
604
+
605
+ ### Events
606
+
607
+ Each `module` will emit specific events through its life-cycle and you can intercept them by using the `Module.update.subscribe` method.
608
+
609
+ > [!TIP]
610
+ >
611
+ > [Here](https://adimarianmutu.github.io/x-injection/enums/DefinitionEventType.html) you can see all the available `events`
612
+
613
+ If you'd need to intercept a `get` request, you can achieve that by doing:
422
614
 
423
615
  ```ts
424
- const SecureBankBranchModuleDef = new ProviderModuleDef({
425
- identifier: 'SecureBankBranchModule',
426
- providers: [BankBranchService],
427
- exports: [BankBranchService],
616
+ const CarModule = ProviderModule.create({
617
+ id: 'CarModule',
618
+ providers: [CarService],
428
619
  });
429
620
 
430
- const BankModuleDef = new ProviderModuleDef({
431
- identifier: 'BankModule',
432
- imports: [SecureBankBranchModuleDef],
433
- exports: [..., (importerModule) => {
434
- // When the module having the identifier `UnknownBankModule` imports the `BankModule`
435
- // it'll not be able to also import the `SecureBankBranchModule` as we are not returning it here.
436
- if (importerModule.toString() === 'UnknownBankModule') return;
437
-
438
- // Otherwise we safely export it
439
- return SecureBankBranchModule;
440
- }]
621
+ CarModule.update.subscribe(({ type, change }) => {
622
+ // We are interested only in the `GetProvider` event.
623
+ if (type !== DefinitionEventType.GetProvider) return;
624
+
625
+ // As our `CarModule` has only one provider, it is safe to assume
626
+ // that the `change` will always be the `CarService` instance.
627
+ const carService = change as CarService;
628
+
629
+ console.log('CarService: ', carService);
441
630
  });
442
- ```
443
631
 
444
- ## Advanced Usage
632
+ const carService = CarModule.get(CarService);
633
+ // logs => CarService: <instance_of_car_service_here>
634
+ ```
445
635
 
446
- ### ProviderModuleNaked Interface
636
+ > [!WARNING]
637
+ >
638
+ > After subscribing to a `ProviderModule` signal emission, you should make sure to also `unsubscribe` if you don't need anymore to intercept the changes, not doing
639
+ > so may cause memory leaks if you have lots of `subscriptions` which do heavy computations!
447
640
 
448
- Each `ProviderModule` instance implements the `IProviderModule` interface for simplicity, but can be cast to `IProviderModuleNaked` for advanced operations:
641
+ The `subscribe` method will _always_ return a `method` having the signature `() => void`, when you invoke it, it'll close the pipe which intercepts the signal emitted by the `module`:
449
642
 
450
643
  ```ts
451
- const nakedModule = ProviderModuleInstance.toNaked();
452
- // or: nakedModule = ProviderModuleInstance as IproviderModuleNaked;
453
- const inversifyContainer = nakedModule.container;
644
+ const unsubscribe = CarModule.update.subscribe(({ type, change }) => {
645
+ /* heavy computation here */
646
+ });
647
+
648
+ // later in your code
649
+
650
+ unsubscribe();
454
651
  ```
455
652
 
456
- You can also access the global `InversifyJS` container directly:
653
+ > [!NOTE]
654
+ >
655
+ > Events are _always_ invoked _after_ middlewares
656
+
657
+ ### Middlewares
658
+
659
+ Using middlewares is not encouraged as it allows you to tap into very deep low-level code which can cause unexpected bugs if not implemented carefully, however, `middlewares` are the perfect choice if you want to extend/alter the standard behavior of `module` as it allows you to decide what should happen with a resolved value _before_ it is returned to the `consumer`.
660
+
661
+ > [!TIP]
662
+ >
663
+ > [Here](https://adimarianmutu.github.io/x-injection/enums/MiddlewareType.html) you can see all the available `middlewares`
664
+
665
+ Let's say that you want to wrap all the returned values of a specific `module` within an object having this signature `{ timestamp: number; value: any }`. By using the `GetProvider` event will not do the trick because it _doesn't_ allow you to alter/change the actual returned value to the `consumer`, you can indeed alter the _content_ via reference, but not the _actual_ result.
666
+
667
+ So the easiest way to achieve that is by using the `BeforeGet` middleware as shown below:
457
668
 
458
669
  ```ts
459
- import { AppModule, GlobalContainer } from '@adimm/x-injection';
670
+ const TransactionModule = ProviderModule.create(TransactionModuleBp);
671
+
672
+ TransactionModule.middlewares.add(MiddlewareType.BeforeGet, (provider, providerToken, inject) => {
673
+ // We are interested only in the `providers` instances which are from the `Payment` class
674
+ if (!(provider instanceof Payment)) return true;
675
+ // or
676
+ if (providerToken !== 'LAST_TRANSACTION') return true;
677
+
678
+ // DON'T do this as you'll encounter an infinite loop
679
+ const transactionService = TransactionModule.get(TransactionService);
680
+ // If you have to inject into the middleware `context` from the `module`
681
+ // use the `inject` parameter
682
+ const transactionService = inject(TransactionService);
683
+
684
+ return {
685
+ timestamp: transactionService.getTimestamp(),
686
+ value: provider,
687
+ };
688
+ });
460
689
 
461
- const globalContainer = GlobalContainer || AppModule.toNaked().container;
690
+ const transaction = TransactionModule.get('LAST_TRANSACTION');
691
+ // transaction => { timestamp: 1363952948, value: <Payment_instance> }
462
692
  ```
463
693
 
464
- For advanced scenarios, `IProviderModuleNaked` exposes additional methods (prefixed with `__`) that wrap InversifyJS APIs, supporting native `xInjection` provider tokens and more.
694
+ 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.
465
695
 
466
- ### Strict Mode
696
+ ```ts
697
+ const UnauthorizedBranchBankModule = ProviderModule.create({ id: 'UnauthorizedBranchBankModule' });
698
+ const SensitiveBankDataModule = ProviderModule.create({
699
+ id: 'SensitiveBankDataModule',
700
+ providers: [SensitiveBankDataService, NonSensitiveBankDataService],
701
+ exports: [SensitiveBankDataService, NonSensitiveBankDataService],
702
+ });
467
703
 
468
- 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.
704
+ SensitiveBankDataModule.middlewares.add(MiddlewareType.OnExportAccess, (importerModule, currentExport) => {
705
+ // We want to deny access to our `SensitiveBankDataService` from the `exports` definition if the importer module is `UnauthorizedBranchBankModule`
706
+ if (importerModule.toString() === 'UnauthorizedBranchBankModule' && currentExport === SensitiveBankDataService)
707
+ return false;
469
708
 
470
- 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.
709
+ // Remaining module are able to import all our `export` definition
710
+ // The `UnauthorizedBranchBankModule` is unable to import the `SensitiveBankDataService`
711
+ return true;
712
+ });
713
+ ```
714
+
715
+ > [!CAUTION]
716
+ >
717
+ > Returning `false` in a `middleware` will abort the chain, meaning that for the above example, no value would be returned.
718
+ > 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)_
719
+ >
720
+ > Meanwhile returning `true` means _"return the value without changing it"_.
721
+ >
722
+ > 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.
723
+
724
+ 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.
725
+
726
+ If no error is thrown down the chain, all the registered middleware `callback` will be supplied with the necessary values.
471
727
 
472
728
  > [!WARNING]
473
729
  >
474
- > _Do not open an `issue` if a bug or edge-case is caused by having the `strict` property disabled!_
730
+ > It is the _developer_ responsability to catch any error down the `chain`!
475
731
 
476
- #### Why you should not turn it off:
732
+ ### Internals
477
733
 
478
- ##### isGlobal
734
+ If you are not interested in understanding how `xInjection` works under the hood, you can skip this section 😌
479
735
 
480
- When using the [isGlobal](https://adimarianmutu.github.io/x-injection/interfaces/ProviderModuleOptions.html#isglobal) property if a `module` is imported into the `AppModule` without having the `isGlobal` flag property set, it'll throw an error.
736
+ #### ProviderModule
481
737
 
482
- 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`.
738
+ It is the head of everything, a `ProviderModule` is actually composed by several classes, each with its own purpose.
483
739
 
484
- Imagine the following scenario:
740
+ > [!TIP]
741
+ >
742
+ > You can get access to _all_ the internal instances by doing `new ProviderModule({...})` instead of `ProviderModule.create({...})`
485
743
 
486
- ```ts
487
- const ScopedModule = new ProviderModule({
488
- identifier: 'ScopedModule',
489
- providers: [...],
490
- exports: [...],
491
- });
744
+ #### MiddlewaresManager
492
745
 
493
- const AnotherScopedModule = new ProviderModule({
494
- identifier: 'AnotherScopedModule',
495
- imports: [ScopedModule],
496
- providers: [...],
497
- exports: [...],
498
- });
746
+ 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).
499
747
 
500
- const GlobalModule = new ProviderModule({
501
- identifier: 'GlobalModule',
502
- isGlobal: true,
503
- imports: [AnotherScopedModule],
504
- });
748
+ Not much to say about it as its main role is to _register_ and _build_ the `middleware` chain.
505
749
 
506
- AppModule.register({
507
- imports: [GlobalModule],
508
- });
750
+ #### ModuleContainer
751
+
752
+ 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).
753
+
754
+ 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.
755
+
756
+ #### ImportedModuleContainer
757
+
758
+ 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).
759
+
760
+ 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.
761
+
762
+ 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`.
763
+
764
+ 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.
765
+
766
+ > "With great power comes great responsibility."
767
+
768
+ #### DynamicModuleDefinition
769
+
770
+ 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).
771
+
772
+ 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`.
773
+
774
+ It also take care of managing the `events` bubbling by checking cirular references and so on.
775
+
776
+ #### ProviderModuleBlueprint
777
+
778
+ 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).
779
+
780
+ #### Set of Helpers
781
+
782
+ The library does also export a set of useful helpers in the case you may need it:
783
+
784
+ ```ts
785
+ import { ProviderModuleHelpers, ProviderTokenHelpers } from '@adimm/x-injection';
509
786
  ```
510
787
 
511
- 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!
788
+ ---
512
789
 
513
- Disabling `strict` mode removes this safeguard, allowing any module to be imported into the `AppModule` regardless of `isGlobal`, increasing risk of bugs by exposing yourself to the above example.
790
+ This covers pretty much everything about how `xInjection` is built and how it works.
514
791
 
515
792
  ## Unit Tests
516
793
 
@@ -528,12 +805,13 @@ class ApiService {
528
805
  private async sendToLocation(user: User, location: any): Promise<any> {}
529
806
  }
530
807
 
531
- const ApiModuleDefinition = new ProviderModuleDefinition({
532
- identifier: 'ApiModule',
808
+ const ApiModuleBp = new ProviderModule.blueprint({
809
+ id: 'ApiModule',
533
810
  providers: [UserService, ApiService],
534
811
  });
535
812
 
536
- const ApiModuleDefinitionMocked = ApiModule.clone({
813
+ // Clone returns a `deep` clone and wraps all the `methods` to break their reference!
814
+ const ApiModuleBpMocked = ApiModuleBp.clone().updateDefinition({
537
815
  identifier: 'ApiModuleMocked',
538
816
  providers: [
539
817
  {
@@ -552,7 +830,7 @@ const ApiModuleDefinitionMocked = ApiModule.clone({
552
830
  });
553
831
  ```
554
832
 
555
- Now what you have to do is just to provide the `ApiModuleDefinitionMocked` instead of the `ApiModuleDefinition` 😎
833
+ Now what you have to do is just to provide the `ApiModuleBpMocked` instead of the `ApiModuleBp` 😎
556
834
 
557
835
  ## Documentation
558
836
 
@@ -560,34 +838,6 @@ Comprehensive, auto-generated documentation is available at:
560
838
 
561
839
  👉 [https://adimarianmutu.github.io/x-injection/index.html](https://adimarianmutu.github.io/x-injection/index.html)
562
840
 
563
- ## Conventions
564
-
565
- To create a stable experience across different projects, the following _conventions_ should be followed.
566
-
567
- ### ProviderModule
568
-
569
- When instantiating a new `ProviderModule` class you should append `Module` to the `variable` name and its `identifier`:
570
-
571
- ```ts
572
- const DatabaseModule = new ProviderModule({
573
- identifier: 'DatabaseModule',
574
- });
575
- ```
576
-
577
- ### ProviderModuleDefinition
578
-
579
- When instantiating a new `ProviderModuleDefinition` class you should append `ModuleDef` to the `variable` name:
580
-
581
- > [!NOTE]
582
- >
583
- > _Do not append `Def` to the `identifier` as that will be inherit by the actual `ProviderModule`!_
584
-
585
- ```ts
586
- const DatabaseModuleDef = new ProviderModuleDefinition({
587
- identifier: 'DatabaseModule', // No `Def` here!
588
- });
589
- ```
590
-
591
841
  ## ReactJS Implementation
592
842
 
593
843
  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 ⚛️
@@ -600,6 +850,11 @@ Pull requests are warmly welcomed! 😃
600
850
 
601
851
  Please ensure your contributions adhere to the project's code style. See the repository for more details.
602
852
 
853
+ ## Credits
854
+
855
+ - [Adi-Marian Mutu](https://www.linkedin.com/in/mutu-adi-marian/) - Author of `xInjection`
856
+ - [InversifyJS](https://github.com/inversify/monorepo) - Base lib
857
+
603
858
  ---
604
859
 
605
860
  > [!NOTE]