first-di 1.0.20 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -86
- package/dist/classes/di.d.ts +5 -0
- package/dist/classes/di.js +9 -0
- package/package.json +36 -20
package/README.md
CHANGED
|
@@ -3,35 +3,65 @@ 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
|
|
25
|
+
------
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
In tsconfig.json enable compiler options:
|
|
30
|
+
|
|
31
|
+
```Json
|
|
32
|
+
{
|
|
33
|
+
"compilerOptions": {
|
|
34
|
+
...
|
|
35
|
+
"emitDecoratorMetadata": true,
|
|
36
|
+
"experimentalDecorators": true,
|
|
37
|
+
...
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Using in Optional DI mode
|
|
15
44
|
------
|
|
16
|
-
|
|
45
|
+
|
|
46
|
+
Just write classes and inject dependencies through class constructors. When the 'resolve' function is called, all dependencies will be resolved.
|
|
17
47
|
|
|
18
48
|
```typescript
|
|
19
|
-
import {
|
|
49
|
+
import { resolve, override, reflection } from "first-di";
|
|
20
50
|
|
|
21
|
-
@reflection //
|
|
22
|
-
class ProdRepository { //
|
|
51
|
+
@reflection // Typescript will generate reflection metadata
|
|
52
|
+
class ProdRepository { // Default implementation
|
|
23
53
|
|
|
24
|
-
public async getData(): Promise<string> {
|
|
25
|
-
return
|
|
54
|
+
public async getData (): Promise<string> {
|
|
55
|
+
return Promise.resolve("production");
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
}
|
|
29
59
|
|
|
30
60
|
@reflection
|
|
31
|
-
class MockRepository { //
|
|
61
|
+
class MockRepository implements ProdRepository { // Mock implementation with same interface
|
|
32
62
|
|
|
33
|
-
public async getData(): Promise<string> {
|
|
34
|
-
return
|
|
63
|
+
public async getData (): Promise<string> {
|
|
64
|
+
return Promise.resolve("mock");
|
|
35
65
|
}
|
|
36
66
|
|
|
37
67
|
}
|
|
@@ -39,33 +69,38 @@ class MockRepository { // mock implementation with same interface
|
|
|
39
69
|
@reflection
|
|
40
70
|
class ProdService {
|
|
41
71
|
|
|
42
|
-
constructor(
|
|
72
|
+
public constructor (
|
|
73
|
+
private readonly prodRepository: ProdRepository
|
|
74
|
+
) { }
|
|
43
75
|
|
|
44
|
-
public async getData(): Promise<string> {
|
|
45
|
-
return
|
|
76
|
+
public async getData (): Promise<string> {
|
|
77
|
+
return this.prodRepository.getData();
|
|
46
78
|
}
|
|
47
79
|
|
|
48
80
|
}
|
|
49
81
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@autowired() // inject dependency
|
|
53
|
-
private readonly prodService!: ProdService;
|
|
82
|
+
@reflection
|
|
83
|
+
class ProdStore {
|
|
54
84
|
|
|
55
|
-
|
|
85
|
+
public constructor (
|
|
86
|
+
// Inject dependency
|
|
87
|
+
private readonly prodService: ProdService
|
|
88
|
+
) {
|
|
89
|
+
// Other logic here
|
|
90
|
+
}
|
|
56
91
|
|
|
57
|
-
public async getData(): Promise<string> {
|
|
58
|
-
return
|
|
92
|
+
public async getData (): Promise<string> {
|
|
93
|
+
return this.prodService.getData();
|
|
59
94
|
}
|
|
60
95
|
|
|
61
96
|
}
|
|
62
97
|
|
|
63
|
-
if (process.env.NODE_ENV === "test") { //
|
|
98
|
+
if (process.env.NODE_ENV === "test") { // Override in test environment
|
|
64
99
|
override(ProdRepository, MockRepository);
|
|
65
100
|
}
|
|
66
101
|
|
|
67
|
-
const
|
|
68
|
-
const data = await
|
|
102
|
+
const store = resolve(ProdStore); // Create intance by framework
|
|
103
|
+
const data = await store.getData();
|
|
69
104
|
|
|
70
105
|
if (process.env.NODE_ENV === "test") {
|
|
71
106
|
assert.strictEqual(data, "mock");
|
|
@@ -74,24 +109,25 @@ if (process.env.NODE_ENV === "test") {
|
|
|
74
109
|
}
|
|
75
110
|
```
|
|
76
111
|
|
|
77
|
-
Using in
|
|
112
|
+
Using in Classic DI mode
|
|
78
113
|
------
|
|
114
|
+
|
|
79
115
|
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
116
|
|
|
81
117
|
```typescript
|
|
82
|
-
import {
|
|
118
|
+
import { resolve, override, reflection } from "first-di";
|
|
83
119
|
|
|
84
|
-
abstract class AbstractRepository { //
|
|
120
|
+
abstract class AbstractRepository { // Abstract instead of interface
|
|
85
121
|
|
|
86
|
-
abstract getData(): Promise<string>;
|
|
122
|
+
public abstract getData (): Promise<string>;
|
|
87
123
|
|
|
88
124
|
}
|
|
89
125
|
|
|
90
126
|
@reflection
|
|
91
127
|
class ProdRepository implements AbstractRepository {
|
|
92
128
|
|
|
93
|
-
public async getData(): Promise<string> {
|
|
94
|
-
return
|
|
129
|
+
public async getData (): Promise<string> {
|
|
130
|
+
return Promise.resolve("production");
|
|
95
131
|
}
|
|
96
132
|
|
|
97
133
|
}
|
|
@@ -99,15 +135,15 @@ class ProdRepository implements AbstractRepository {
|
|
|
99
135
|
@reflection
|
|
100
136
|
class MockRepository implements AbstractRepository {
|
|
101
137
|
|
|
102
|
-
public async getData(): Promise<string> {
|
|
103
|
-
return
|
|
138
|
+
public async getData (): Promise<string> {
|
|
139
|
+
return Promise.resolve("mock");
|
|
104
140
|
}
|
|
105
141
|
|
|
106
142
|
}
|
|
107
143
|
|
|
108
|
-
abstract class AbstractService { //
|
|
144
|
+
abstract class AbstractService { // Abstract instead of interface
|
|
109
145
|
|
|
110
|
-
abstract getData(): Promise<string>;
|
|
146
|
+
public abstract getData (): Promise<string>;
|
|
111
147
|
|
|
112
148
|
}
|
|
113
149
|
|
|
@@ -116,25 +152,25 @@ class ProdService implements AbstractService {
|
|
|
116
152
|
|
|
117
153
|
private readonly prodRepository: AbstractRepository;
|
|
118
154
|
|
|
119
|
-
constructor(prodRepository: AbstractRepository) {
|
|
155
|
+
public constructor (prodRepository: AbstractRepository) {
|
|
120
156
|
this.prodRepository = prodRepository;
|
|
121
157
|
}
|
|
122
158
|
|
|
123
|
-
public async getData(): Promise<string> {
|
|
124
|
-
return
|
|
159
|
+
public async getData (): Promise<string> {
|
|
160
|
+
return this.prodRepository.getData();
|
|
125
161
|
}
|
|
126
162
|
|
|
127
163
|
}
|
|
128
164
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@autowired()
|
|
132
|
-
private readonly prodService!: AbstractService;
|
|
165
|
+
@reflection
|
|
166
|
+
class ProdStore {
|
|
133
167
|
|
|
134
|
-
|
|
168
|
+
public constructor (
|
|
169
|
+
private readonly prodService: AbstractService
|
|
170
|
+
) {}
|
|
135
171
|
|
|
136
|
-
public async getData(): Promise<string> {
|
|
137
|
-
return
|
|
172
|
+
public async getData (): Promise<string> {
|
|
173
|
+
return this.prodService.getData();
|
|
138
174
|
}
|
|
139
175
|
|
|
140
176
|
}
|
|
@@ -147,8 +183,8 @@ if (process.env.NODE_ENV === "test") {
|
|
|
147
183
|
override(AbstractRepository, ProdRepository);
|
|
148
184
|
}
|
|
149
185
|
|
|
150
|
-
const
|
|
151
|
-
const data = await
|
|
186
|
+
const store = resolve(ProdStore);
|
|
187
|
+
const data = await store.getData();
|
|
152
188
|
|
|
153
189
|
if (process.env.NODE_ENV === "test") {
|
|
154
190
|
assert.strictEqual(data, "mock");
|
|
@@ -157,14 +193,17 @@ if (process.env.NODE_ENV === "test") {
|
|
|
157
193
|
}
|
|
158
194
|
```
|
|
159
195
|
|
|
160
|
-
Options
|
|
196
|
+
Options
|
|
161
197
|
------
|
|
162
|
-
|
|
198
|
+
|
|
199
|
+
First DI has several points for customizing dependency options:
|
|
200
|
+
|
|
163
201
|
- **Global** - `DI.defaultOptions: AutowiredOptions`. Sets global default behavior.
|
|
164
|
-
- **Autowired** - `@autowired(options?: AutowiredOptions)`. Sets behaviors for resolve dependencies.
|
|
165
202
|
- **Override** - `override(fromClass, toClass, options?: AutowiredOptions)`. Sets behavior overrided dependency.
|
|
203
|
+
- **Resolve** - `resolve(class, options?: AutowiredOptions)`. Sets behaviors for resolve dependencies.
|
|
204
|
+
|
|
205
|
+
Options has next properties:
|
|
166
206
|
|
|
167
|
-
AutowiredOptions has next properties:
|
|
168
207
|
- **lifeTime: AutowiredLifetimes** - Sets lifeTime of dependecy.
|
|
169
208
|
|
|
170
209
|
SINGLETON - Create one instance for all resolvers.
|
|
@@ -175,8 +214,9 @@ AutowiredOptions has next properties:
|
|
|
175
214
|
|
|
176
215
|
PER_ACCESS - Create new instance on each access to resolved property.
|
|
177
216
|
|
|
178
|
-
Scopes
|
|
217
|
+
Scopes
|
|
179
218
|
------
|
|
219
|
+
|
|
180
220
|
Support multiple scopes
|
|
181
221
|
|
|
182
222
|
```typescript
|
|
@@ -186,32 +226,18 @@ import { ProductionService } from "../services/ProductionService";
|
|
|
186
226
|
const scopeA = new DI();
|
|
187
227
|
const scopeB = new DI();
|
|
188
228
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
@scopeA.autowired()
|
|
192
|
-
private readonly serviceScopeA!: ProductionService;
|
|
193
|
-
|
|
194
|
-
@scopeB.autowired()
|
|
195
|
-
private readonly serviceScopeB!: ProductionService;
|
|
229
|
+
const serviceScopeA = scopeA.resolve(ProductionService);
|
|
230
|
+
const dataA = await serviceScopeA.getData();
|
|
196
231
|
|
|
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
|
-
}
|
|
232
|
+
const serviceScopeB = scopeB.resolve(ProductionService);
|
|
233
|
+
const dataB = await serviceScopeB.getData();
|
|
208
234
|
```
|
|
209
235
|
|
|
210
|
-
API
|
|
236
|
+
API
|
|
211
237
|
------
|
|
212
|
-
First DI also has an API for extended use. For example, use as A Service Locator.
|
|
213
238
|
|
|
214
|
-
|
|
239
|
+
First DI also has an API for extended use.
|
|
240
|
+
|
|
215
241
|
- override - Function. Override dependency and resolve options.
|
|
216
242
|
- resolve - Function. Resolves dependence with default options or specified.
|
|
217
243
|
- singleton - Function. Resolve singleton.
|
|
@@ -221,16 +247,10 @@ First DI also has an API for extended use. For example, use as A Service Locator
|
|
|
221
247
|
Resolve, singleton, instance - can be used to implement the Service Locator.
|
|
222
248
|
|
|
223
249
|
```typescript
|
|
224
|
-
import { singleton, instance, resolve,
|
|
250
|
+
import { singleton, instance, resolve, AutowiredLifetimes } from "first-di";
|
|
225
251
|
|
|
226
252
|
class ApiDemo {
|
|
227
253
|
|
|
228
|
-
@autowired({ lifeTime: AutowiredLifetimes.SINGLETON })
|
|
229
|
-
private readonly service1!: ApiService1;
|
|
230
|
-
|
|
231
|
-
@autowired({ lifeTime: AutowiredLifetimes.PER_INSTANCE })
|
|
232
|
-
private readonly service2!: ApiService2;
|
|
233
|
-
|
|
234
254
|
private readonly service3: ApiService3 = resolve(ApiService3, { lifeTime: AutowiredLifetimes.PER_INSTANCE });
|
|
235
255
|
|
|
236
256
|
private readonly service4: ApiService4 = singleton(ApiService4);
|
|
@@ -240,10 +260,10 @@ class ApiDemo {
|
|
|
240
260
|
}
|
|
241
261
|
```
|
|
242
262
|
|
|
243
|
-
|
|
244
|
-
Extension DI:
|
|
263
|
+
Extension DI
|
|
245
264
|
------
|
|
246
|
-
|
|
265
|
+
|
|
266
|
+
First DI using OOP and SOLID design principles. Each part of DI can be override or extende after inheritance from base class.
|
|
247
267
|
|
|
248
268
|
```typescript
|
|
249
269
|
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,39 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "first-di",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"author": "Eugene Labutin",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/LabEG/first-di#readme",
|
|
4
7
|
"description": "Easy dependency injection for typescript applications",
|
|
5
8
|
"main": "./dist/index.js",
|
|
6
9
|
"type": "module",
|
|
7
10
|
"typings": "./dist/index.d.ts",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": " git@github.com:LabEG/first-di.git"
|
|
10
14
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"ts-node": "^10.9.2",
|
|
19
|
-
"reflect-metadata": "^0.2.1",
|
|
20
|
-
"typescript": "^5.3.3"
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/LabEG/first-di/issues"
|
|
17
|
+
},
|
|
18
|
+
"lint-staged": {
|
|
19
|
+
"./tests/**/*.(ts|tsx|js|jsx)": [
|
|
20
|
+
"eslint --fix -c .eslintrc.js --ext .tsx,.ts,.jsx,.js"
|
|
21
|
+
]
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
24
|
"lint": "eslint --fix -c .eslintrc.cjs --ext .tsx,.ts,.jsx,.js ./src/ ./tests/",
|
|
24
25
|
"test": "mocha --reporter spec --require ts-node/register tests/*.test.ts",
|
|
25
26
|
"build": "rm -rf dist/ && tsc --project tsconfig.build.json && node ./dist/index.js",
|
|
26
|
-
"
|
|
27
|
+
"release": "cliff-jumper --name '@labeg/code-style' --package-path '.' --no-skip-changelog --no-skip-tag",
|
|
28
|
+
"prepublishOnly": "npm run lint && npm run build && npm run test",
|
|
29
|
+
"prepare": "husky install"
|
|
27
30
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"reflect-metadata": ">=0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@labeg/code-style": "^4.2.2",
|
|
36
|
+
"@types/chai": "^4.3.16",
|
|
37
|
+
"@types/mocha": "^10.0.6",
|
|
38
|
+
"@types/node": "^20.13.0",
|
|
39
|
+
"chai": "^5.1.1",
|
|
40
|
+
"mocha": "^10.4.0",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"reflect-metadata": "^0.2.2",
|
|
43
|
+
"typescript": "^5.4.5",
|
|
44
|
+
"@commitlint/cli": "^19.3.0",
|
|
45
|
+
"@commitlint/config-conventional": "^19.2.2",
|
|
46
|
+
"husky": "^9.0.11",
|
|
47
|
+
"lint-staged": "^15.2.5",
|
|
48
|
+
"@favware/cliff-jumper": "^3.0.3"
|
|
31
49
|
},
|
|
32
50
|
"keywords": [
|
|
33
51
|
"dependency injection",
|
|
34
52
|
"di",
|
|
35
53
|
"ioc"
|
|
36
|
-
]
|
|
37
|
-
"author": "LabEG",
|
|
38
|
-
"license": "MIT"
|
|
54
|
+
]
|
|
39
55
|
}
|