@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 +520 -265
- package/dist/index.cjs +672 -529
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +534 -640
- package/dist/index.d.ts +534 -640
- package/dist/index.js +635 -491
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
2
|
xInjection <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 <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
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- [
|
|
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
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
|
|
41
|
-
|
|
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/
|
|
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
|
-
- **
|
|
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
|
-
###
|
|
112
|
+
### Quick Start
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { Injectable, ProviderModule } from '@adimm/x-injection';
|
|
94
116
|
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
const DatabaseModule = ProviderModule.create({...});
|
|
176
|
+
const UserModule = ProviderModule.create({...});
|
|
177
|
+
const CarPartsModule = ProviderModule.create({...});
|
|
178
|
+
```
|
|
117
179
|
|
|
118
|
-
|
|
119
|
-
class LoggerService {}
|
|
180
|
+
The `id` property of the `ProviderModule.options` should be the same as the `module` variable name.
|
|
120
181
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
+
#### Blueprints
|
|
134
201
|
|
|
135
|
-
|
|
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
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
>
|
|
248
|
+
> Importing the `AppModule` into any `module` will throw an error!
|
|
156
249
|
|
|
157
|
-
|
|
250
|
+
You have 2 options to access it:
|
|
158
251
|
|
|
159
252
|
```ts
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
isGlobal: true,
|
|
163
|
-
providers: [FancyService],
|
|
164
|
-
exports: [FancyService],
|
|
165
|
-
});
|
|
253
|
+
import { AppModule } from '@adimm/x-injection';
|
|
254
|
+
```
|
|
166
255
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
256
|
+
or
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { ProviderModule } from '@adimm/x-injection';
|
|
260
|
+
|
|
261
|
+
ProviderModule.APP_MODULE_REF;
|
|
173
262
|
|
|
174
|
-
|
|
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
|
-
|
|
266
|
+
Providing global services to the `AppModule`:
|
|
179
267
|
|
|
180
268
|
```ts
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
isGlobal: true,
|
|
184
|
-
providers: [FancyService],
|
|
185
|
-
exports: [FancyService],
|
|
186
|
-
});
|
|
269
|
+
@Injectable()
|
|
270
|
+
class UserService {}
|
|
187
271
|
|
|
188
|
-
AppModule.
|
|
272
|
+
AppModule.update.addProvider(UserService);
|
|
189
273
|
```
|
|
190
274
|
|
|
191
275
|
> [!NOTE]
|
|
192
276
|
>
|
|
193
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
380
|
+
## Provider Tokens
|
|
286
381
|
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
import { Injectable, InjectionScope, ProviderModule } from '@adimm/x-injection';
|
|
384
|
+
`xInjection` offers _4_ types of tokens:
|
|
291
385
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
298
|
-
export class SessionService {
|
|
299
|
-
constructor(public readonly userService: UserService) {}
|
|
395
|
+
- It can be used define the token _and_ the provider:
|
|
300
396
|
|
|
301
|
-
|
|
302
|
-
}
|
|
397
|
+
```ts
|
|
398
|
+
const HUMAN_SERVICE_PROVIDER = { provide: HumanService, useClass: FemaleService };
|
|
303
399
|
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
415
|
+
- [ProviderFactoryToken](https://adimarianmutu.github.io/x-injection/types/ProviderFactoryToken.html)
|
|
340
416
|
|
|
341
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
444
|
+
The most straight forward way to _create/instantiate_ a new `module` is:
|
|
354
445
|
|
|
355
446
|
```ts
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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
|
-
|
|
468
|
+
We could also achieve the above by using the `ProviderModule` API like this:
|
|
364
469
|
|
|
365
470
|
```ts
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
485
|
+
const InnerModule = ProviderModule.create({
|
|
486
|
+
id: 'InnerModule',
|
|
487
|
+
providers: [FirstService],
|
|
488
|
+
exports: [FirstService],
|
|
489
|
+
});
|
|
375
490
|
|
|
376
|
-
|
|
491
|
+
const OuterModule = ProviderModule.create({
|
|
492
|
+
id: 'OuterModule',
|
|
493
|
+
imports: [InnerModule],
|
|
494
|
+
});
|
|
377
495
|
|
|
378
|
-
|
|
379
|
-
|
|
496
|
+
const UnknownModule = ProviderModule.create({
|
|
497
|
+
id: 'UnknownModule',
|
|
498
|
+
providers: [SecondService],
|
|
499
|
+
exports: [SecondService],
|
|
500
|
+
});
|
|
380
501
|
|
|
381
|
-
|
|
502
|
+
InnerModule.update.addImport(UnknownModule);
|
|
382
503
|
|
|
383
|
-
|
|
384
|
-
const GarageModule = new ProviderModule(GarageModuleDefinition);
|
|
504
|
+
const secondService = OuterModule.get(SecondService);
|
|
385
505
|
```
|
|
386
506
|
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
574
|
+
### Definitions
|
|
400
575
|
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
594
|
+
const UserModule = ProviderModule.create(UserModuleBp);
|
|
417
595
|
```
|
|
418
596
|
|
|
419
|
-
|
|
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
|
-
|
|
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
|
|
425
|
-
|
|
426
|
-
providers: [
|
|
427
|
-
exports: [BankBranchService],
|
|
616
|
+
const CarModule = ProviderModule.create({
|
|
617
|
+
id: 'CarModule',
|
|
618
|
+
providers: [CarService],
|
|
428
619
|
});
|
|
429
620
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
632
|
+
const carService = CarModule.get(CarService);
|
|
633
|
+
// logs => CarService: <instance_of_car_service_here>
|
|
634
|
+
```
|
|
445
635
|
|
|
446
|
-
|
|
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
|
-
|
|
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
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
690
|
+
const transaction = TransactionModule.get('LAST_TRANSACTION');
|
|
691
|
+
// transaction => { timestamp: 1363952948, value: <Payment_instance> }
|
|
462
692
|
```
|
|
463
693
|
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
730
|
+
> It is the _developer_ responsability to catch any error down the `chain`!
|
|
475
731
|
|
|
476
|
-
|
|
732
|
+
### Internals
|
|
477
733
|
|
|
478
|
-
|
|
734
|
+
If you are not interested in understanding how `xInjection` works under the hood, you can skip this section 😌
|
|
479
735
|
|
|
480
|
-
|
|
736
|
+
#### ProviderModule
|
|
481
737
|
|
|
482
|
-
|
|
738
|
+
It is the head of everything, a `ProviderModule` is actually composed by several classes, each with its own purpose.
|
|
483
739
|
|
|
484
|
-
|
|
740
|
+
> [!TIP]
|
|
741
|
+
>
|
|
742
|
+
> You can get access to _all_ the internal instances by doing `new ProviderModule({...})` instead of `ProviderModule.create({...})`
|
|
485
743
|
|
|
486
|
-
|
|
487
|
-
const ScopedModule = new ProviderModule({
|
|
488
|
-
identifier: 'ScopedModule',
|
|
489
|
-
providers: [...],
|
|
490
|
-
exports: [...],
|
|
491
|
-
});
|
|
744
|
+
#### MiddlewaresManager
|
|
492
745
|
|
|
493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
507
|
-
|
|
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
|
-
|
|
788
|
+
---
|
|
512
789
|
|
|
513
|
-
|
|
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
|
|
532
|
-
|
|
808
|
+
const ApiModuleBp = new ProviderModule.blueprint({
|
|
809
|
+
id: 'ApiModule',
|
|
533
810
|
providers: [UserService, ApiService],
|
|
534
811
|
});
|
|
535
812
|
|
|
536
|
-
|
|
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 `
|
|
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]
|