first-di 1.0.19 → 2.0.1
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 +105 -86
- package/dist/classes/di.d.ts +5 -0
- package/dist/classes/di.js +9 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -3,35 +3,64 @@ First DI
|
|
|
3
3
|
|
|
4
4
|
Easy dependency injection for typescript applications
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Installation
|
|
7
7
|
------
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
|
|
9
|
+
For the latest stable version:
|
|
10
|
+
|
|
11
|
+
```Bash
|
|
12
|
+
npm i first-di
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Features
|
|
16
|
+
------
|
|
17
|
+
|
|
18
|
+
- Easy and powerful dependency injection for any typescript application.
|
|
19
|
+
- 2 modes of work. Optional DI - for most apps. Classic DI - for advanced apps.
|
|
20
|
+
- Support for multiple scopes.
|
|
21
|
+
- Supports multiple life cycles.
|
|
12
22
|
- Dependency Free. Dependency used only for development.
|
|
13
23
|
|
|
14
|
-
|
|
24
|
+
Setup
|
|
15
25
|
------
|
|
16
|
-
|
|
26
|
+
Install [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) package and import in root typescript file. This package is needed to support reflection and is a mandatory requirement of Typescript.
|
|
27
|
+
|
|
28
|
+
In tsconfig.json enable compiler options:
|
|
29
|
+
|
|
30
|
+
```Json
|
|
31
|
+
{
|
|
32
|
+
"compilerOptions": {
|
|
33
|
+
...
|
|
34
|
+
"emitDecoratorMetadata": true,
|
|
35
|
+
"experimentalDecorators": true,
|
|
36
|
+
...
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Using in Optional DI mode
|
|
43
|
+
------
|
|
44
|
+
|
|
45
|
+
Just write classes and inject dependencies through class constructors. When the 'resolve' function is called, all dependencies will be resolved.
|
|
17
46
|
|
|
18
47
|
```typescript
|
|
19
|
-
import {
|
|
48
|
+
import { resolve, override, reflection } from "first-di";
|
|
20
49
|
|
|
21
|
-
@reflection //
|
|
22
|
-
class ProdRepository { //
|
|
50
|
+
@reflection // Typescript will generate reflection metadata
|
|
51
|
+
class ProdRepository { // Default implementation
|
|
23
52
|
|
|
24
|
-
public async getData(): Promise<string> {
|
|
25
|
-
return
|
|
53
|
+
public async getData (): Promise<string> {
|
|
54
|
+
return Promise.resolve("production");
|
|
26
55
|
}
|
|
27
56
|
|
|
28
57
|
}
|
|
29
58
|
|
|
30
59
|
@reflection
|
|
31
|
-
class MockRepository { //
|
|
60
|
+
class MockRepository implements ProdRepository { // Mock implementation with same interface
|
|
32
61
|
|
|
33
|
-
public async getData(): Promise<string> {
|
|
34
|
-
return
|
|
62
|
+
public async getData (): Promise<string> {
|
|
63
|
+
return Promise.resolve("mock");
|
|
35
64
|
}
|
|
36
65
|
|
|
37
66
|
}
|
|
@@ -39,33 +68,38 @@ class MockRepository { // mock implementation with same interface
|
|
|
39
68
|
@reflection
|
|
40
69
|
class ProdService {
|
|
41
70
|
|
|
42
|
-
constructor(
|
|
71
|
+
public constructor (
|
|
72
|
+
private readonly prodRepository: ProdRepository
|
|
73
|
+
) { }
|
|
43
74
|
|
|
44
|
-
public async getData(): Promise<string> {
|
|
45
|
-
return
|
|
75
|
+
public async getData (): Promise<string> {
|
|
76
|
+
return this.prodRepository.getData();
|
|
46
77
|
}
|
|
47
78
|
|
|
48
79
|
}
|
|
49
80
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@autowired() // inject dependency
|
|
53
|
-
private readonly prodService!: ProdService;
|
|
81
|
+
@reflection
|
|
82
|
+
class ProdStore {
|
|
54
83
|
|
|
55
|
-
|
|
84
|
+
public constructor (
|
|
85
|
+
// Inject dependency
|
|
86
|
+
private readonly prodService: ProdService
|
|
87
|
+
) {
|
|
88
|
+
// Other logic here
|
|
89
|
+
}
|
|
56
90
|
|
|
57
|
-
public async getData(): Promise<string> {
|
|
58
|
-
return
|
|
91
|
+
public async getData (): Promise<string> {
|
|
92
|
+
return this.prodService.getData();
|
|
59
93
|
}
|
|
60
94
|
|
|
61
95
|
}
|
|
62
96
|
|
|
63
|
-
if (process.env.NODE_ENV === "test") { //
|
|
97
|
+
if (process.env.NODE_ENV === "test") { // Override in test environment
|
|
64
98
|
override(ProdRepository, MockRepository);
|
|
65
99
|
}
|
|
66
100
|
|
|
67
|
-
const
|
|
68
|
-
const data = await
|
|
101
|
+
const store = resolve(ProdStore); // Create intance by framework
|
|
102
|
+
const data = await store.getData();
|
|
69
103
|
|
|
70
104
|
if (process.env.NODE_ENV === "test") {
|
|
71
105
|
assert.strictEqual(data, "mock");
|
|
@@ -74,24 +108,25 @@ if (process.env.NODE_ENV === "test") {
|
|
|
74
108
|
}
|
|
75
109
|
```
|
|
76
110
|
|
|
77
|
-
Using in
|
|
111
|
+
Using in Classic DI mode
|
|
78
112
|
------
|
|
113
|
+
|
|
79
114
|
In professional mode Interfaces are used instead of implementations. But typescript does not generate Interfaces for working in runtime. But Interface is abstract base class. So instead of Interfaces, you need to write Abstract classes.
|
|
80
115
|
|
|
81
116
|
```typescript
|
|
82
|
-
import {
|
|
117
|
+
import { resolve, override, reflection } from "first-di";
|
|
83
118
|
|
|
84
|
-
abstract class AbstractRepository { //
|
|
119
|
+
abstract class AbstractRepository { // Abstract instead of interface
|
|
85
120
|
|
|
86
|
-
abstract getData(): Promise<string>;
|
|
121
|
+
public abstract getData (): Promise<string>;
|
|
87
122
|
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
@reflection
|
|
91
126
|
class ProdRepository implements AbstractRepository {
|
|
92
127
|
|
|
93
|
-
public async getData(): Promise<string> {
|
|
94
|
-
return
|
|
128
|
+
public async getData (): Promise<string> {
|
|
129
|
+
return Promise.resolve("production");
|
|
95
130
|
}
|
|
96
131
|
|
|
97
132
|
}
|
|
@@ -99,15 +134,15 @@ class ProdRepository implements AbstractRepository {
|
|
|
99
134
|
@reflection
|
|
100
135
|
class MockRepository implements AbstractRepository {
|
|
101
136
|
|
|
102
|
-
public async getData(): Promise<string> {
|
|
103
|
-
return
|
|
137
|
+
public async getData (): Promise<string> {
|
|
138
|
+
return Promise.resolve("mock");
|
|
104
139
|
}
|
|
105
140
|
|
|
106
141
|
}
|
|
107
142
|
|
|
108
|
-
abstract class AbstractService { //
|
|
143
|
+
abstract class AbstractService { // Abstract instead of interface
|
|
109
144
|
|
|
110
|
-
abstract getData(): Promise<string>;
|
|
145
|
+
public abstract getData (): Promise<string>;
|
|
111
146
|
|
|
112
147
|
}
|
|
113
148
|
|
|
@@ -116,25 +151,25 @@ class ProdService implements AbstractService {
|
|
|
116
151
|
|
|
117
152
|
private readonly prodRepository: AbstractRepository;
|
|
118
153
|
|
|
119
|
-
constructor(prodRepository: AbstractRepository) {
|
|
154
|
+
public constructor (prodRepository: AbstractRepository) {
|
|
120
155
|
this.prodRepository = prodRepository;
|
|
121
156
|
}
|
|
122
157
|
|
|
123
|
-
public async getData(): Promise<string> {
|
|
124
|
-
return
|
|
158
|
+
public async getData (): Promise<string> {
|
|
159
|
+
return this.prodRepository.getData();
|
|
125
160
|
}
|
|
126
161
|
|
|
127
162
|
}
|
|
128
163
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@autowired()
|
|
132
|
-
private readonly prodService!: AbstractService;
|
|
164
|
+
@reflection
|
|
165
|
+
class ProdStore {
|
|
133
166
|
|
|
134
|
-
|
|
167
|
+
public constructor (
|
|
168
|
+
private readonly prodService: AbstractService
|
|
169
|
+
) {}
|
|
135
170
|
|
|
136
|
-
public async getData(): Promise<string> {
|
|
137
|
-
return
|
|
171
|
+
public async getData (): Promise<string> {
|
|
172
|
+
return this.prodService.getData();
|
|
138
173
|
}
|
|
139
174
|
|
|
140
175
|
}
|
|
@@ -147,8 +182,8 @@ if (process.env.NODE_ENV === "test") {
|
|
|
147
182
|
override(AbstractRepository, ProdRepository);
|
|
148
183
|
}
|
|
149
184
|
|
|
150
|
-
const
|
|
151
|
-
const data = await
|
|
185
|
+
const store = resolve(ProdStore);
|
|
186
|
+
const data = await store.getData();
|
|
152
187
|
|
|
153
188
|
if (process.env.NODE_ENV === "test") {
|
|
154
189
|
assert.strictEqual(data, "mock");
|
|
@@ -157,14 +192,17 @@ if (process.env.NODE_ENV === "test") {
|
|
|
157
192
|
}
|
|
158
193
|
```
|
|
159
194
|
|
|
160
|
-
Options
|
|
195
|
+
Options
|
|
161
196
|
------
|
|
162
|
-
|
|
197
|
+
|
|
198
|
+
First DI has several points for customizing dependency options:
|
|
199
|
+
|
|
163
200
|
- **Global** - `DI.defaultOptions: AutowiredOptions`. Sets global default behavior.
|
|
164
|
-
- **Autowired** - `@autowired(options?: AutowiredOptions)`. Sets behaviors for resolve dependencies.
|
|
165
201
|
- **Override** - `override(fromClass, toClass, options?: AutowiredOptions)`. Sets behavior overrided dependency.
|
|
202
|
+
- **Resolve** - `resolve(class, options?: AutowiredOptions)`. Sets behaviors for resolve dependencies.
|
|
203
|
+
|
|
204
|
+
Options has next properties:
|
|
166
205
|
|
|
167
|
-
AutowiredOptions has next properties:
|
|
168
206
|
- **lifeTime: AutowiredLifetimes** - Sets lifeTime of dependecy.
|
|
169
207
|
|
|
170
208
|
SINGLETON - Create one instance for all resolvers.
|
|
@@ -175,8 +213,9 @@ AutowiredOptions has next properties:
|
|
|
175
213
|
|
|
176
214
|
PER_ACCESS - Create new instance on each access to resolved property.
|
|
177
215
|
|
|
178
|
-
Scopes
|
|
216
|
+
Scopes
|
|
179
217
|
------
|
|
218
|
+
|
|
180
219
|
Support multiple scopes
|
|
181
220
|
|
|
182
221
|
```typescript
|
|
@@ -186,32 +225,18 @@ import { ProductionService } from "../services/ProductionService";
|
|
|
186
225
|
const scopeA = new DI();
|
|
187
226
|
const scopeB = new DI();
|
|
188
227
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
@scopeA.autowired()
|
|
192
|
-
private readonly serviceScopeA!: ProductionService;
|
|
193
|
-
|
|
194
|
-
@scopeB.autowired()
|
|
195
|
-
private readonly serviceScopeB!: ProductionService;
|
|
228
|
+
const serviceScopeA = scopeA.resolve(ProductionService);
|
|
229
|
+
const dataA = await serviceScopeA.getData();
|
|
196
230
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
public async getDataScopeA(): Promise<string> {
|
|
200
|
-
return await this.serviceScopeA.getData();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
public async getDataScopeB(): Promise<string> {
|
|
204
|
-
return await this.serviceScopeB.getData();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
}
|
|
231
|
+
const serviceScopeB = scopeB.resolve(ProductionService);
|
|
232
|
+
const dataB = await serviceScopeB.getData();
|
|
208
233
|
```
|
|
209
234
|
|
|
210
|
-
API
|
|
235
|
+
API
|
|
211
236
|
------
|
|
212
|
-
First DI also has an API for extended use. For example, use as A Service Locator.
|
|
213
237
|
|
|
214
|
-
|
|
238
|
+
First DI also has an API for extended use.
|
|
239
|
+
|
|
215
240
|
- override - Function. Override dependency and resolve options.
|
|
216
241
|
- resolve - Function. Resolves dependence with default options or specified.
|
|
217
242
|
- singleton - Function. Resolve singleton.
|
|
@@ -221,16 +246,10 @@ First DI also has an API for extended use. For example, use as A Service Locator
|
|
|
221
246
|
Resolve, singleton, instance - can be used to implement the Service Locator.
|
|
222
247
|
|
|
223
248
|
```typescript
|
|
224
|
-
import { singleton, instance, resolve,
|
|
249
|
+
import { singleton, instance, resolve, AutowiredLifetimes } from "first-di";
|
|
225
250
|
|
|
226
251
|
class ApiDemo {
|
|
227
252
|
|
|
228
|
-
@autowired({ lifeTime: AutowiredLifetimes.SINGLETON })
|
|
229
|
-
private readonly service1!: ApiService1;
|
|
230
|
-
|
|
231
|
-
@autowired({ lifeTime: AutowiredLifetimes.PER_INSTANCE })
|
|
232
|
-
private readonly service2!: ApiService2;
|
|
233
|
-
|
|
234
253
|
private readonly service3: ApiService3 = resolve(ApiService3, { lifeTime: AutowiredLifetimes.PER_INSTANCE });
|
|
235
254
|
|
|
236
255
|
private readonly service4: ApiService4 = singleton(ApiService4);
|
|
@@ -240,10 +259,10 @@ class ApiDemo {
|
|
|
240
259
|
}
|
|
241
260
|
```
|
|
242
261
|
|
|
243
|
-
|
|
244
|
-
Extension DI:
|
|
262
|
+
Extension DI
|
|
245
263
|
------
|
|
246
|
-
|
|
264
|
+
|
|
265
|
+
First DI using OOP and SOLID design principles. Each part of DI can be override or extende after inheritance from base class.
|
|
247
266
|
|
|
248
267
|
```typescript
|
|
249
268
|
import { DI } from "first-di";
|
package/dist/classes/di.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export declare class DI {
|
|
|
13
13
|
protected singletonsList: Map<ClassConstructor<object>, object>;
|
|
14
14
|
protected overrideList: Map<OverrideConstructor<object>, OverrideOptions>;
|
|
15
15
|
constructor();
|
|
16
|
+
/**
|
|
17
|
+
* Decorator @autowired is deprecated, and will by removed in next versions
|
|
18
|
+
* @param options
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
16
21
|
protected makeAutowired(options?: AutowiredOptions): PropertyDecorator;
|
|
17
22
|
protected makeResolve<T extends object>(inConstructor: ClassConstructor<T>, inOptions?: AutowiredOptions, caller?: object, propertyKey?: string | symbol): T;
|
|
18
23
|
protected makeReset(): void;
|
package/dist/classes/di.js
CHANGED
|
@@ -18,7 +18,16 @@ export class DI {
|
|
|
18
18
|
this.makeOverride(from, to, options);
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Decorator @autowired is deprecated, and will by removed in next versions
|
|
23
|
+
* @param options
|
|
24
|
+
* @returns
|
|
25
|
+
*/
|
|
21
26
|
makeAutowired(options) {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.warn("first-di: @autowired is depreacted, " +
|
|
29
|
+
"new standart of ecmascript decorators prohibits changing the classes, " +
|
|
30
|
+
"use 'resolve' functions instaed of @autowired");
|
|
22
31
|
return (target, propertyKey) => {
|
|
23
32
|
const type = Reflect.getMetadata("design:type", target, propertyKey);
|
|
24
33
|
const { resolve } = this;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "first-di",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Easy dependency injection for typescript applications",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
"reflect-metadata": ">=0.1.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@labeg/code-style": "^4.0.
|
|
13
|
-
"@types/chai": "^4.3.
|
|
12
|
+
"@labeg/code-style": "^4.0.7",
|
|
13
|
+
"@types/chai": "^4.3.14",
|
|
14
14
|
"@types/mocha": "^10.0.6",
|
|
15
|
-
"@types/node": "^20.
|
|
16
|
-
"chai": "^
|
|
17
|
-
"mocha": "^10.
|
|
15
|
+
"@types/node": "^20.12.7",
|
|
16
|
+
"chai": "^5.1.0",
|
|
17
|
+
"mocha": "^10.4.0",
|
|
18
18
|
"ts-node": "^10.9.2",
|
|
19
|
-
"reflect-metadata": "^0.2.
|
|
20
|
-
"typescript": "^5.
|
|
19
|
+
"reflect-metadata": "^0.2.2",
|
|
20
|
+
"typescript": "^5.4.5"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"lint": "eslint --fix -c .eslintrc.cjs --ext .tsx,.ts,.jsx,.js ./src/ ./tests/",
|