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 CHANGED
@@ -3,35 +3,65 @@ First DI
3
3
 
4
4
  Easy dependency injection for typescript applications
5
5
 
6
- Description:
6
+ Installation
7
7
  ------
8
- - For working this library needed Metadata Reflection API. If your platform (browser/nodejs) don't support it you must use polifyll. Example: [reflect-metadata](https://www.npmjs.com/package/reflect-metadata).
9
- - For working reflection should be enabled the option emitDecoratorMetadata and experimentalDecorators in tsconfig file.
10
- - For generate reflection by typescript need to create any decorator or use @reflection decorator from this library.
11
- - Lazy Loading for resolve dependency. Each dependency will created only after request property with @autowired decorator.
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
- Using in Easy mode:
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
- Simply write code as you used to and use @autowired() decorator to implement dependencies. And for override dependencies just use method override.
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 { autowired, override, reflection } from "first-di";
49
+ import { resolve, override, reflection } from "first-di";
20
50
 
21
- @reflection // typescript will generate reflection metadata
22
- class ProdRepository { // default implementation
51
+ @reflection // Typescript will generate reflection metadata
52
+ class ProdRepository { // Default implementation
23
53
 
24
- public async getData(): Promise<string> {
25
- return await Promise.resolve("production");
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 { // mock implementation with same interface
61
+ class MockRepository implements ProdRepository { // Mock implementation with same interface
32
62
 
33
- public async getData(): Promise<string> {
34
- return await Promise.resolve("mock");
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(private readonly prodRepository: ProdRepository) { }
72
+ public constructor (
73
+ private readonly prodRepository: ProdRepository
74
+ ) { }
43
75
 
44
- public async getData(): Promise<string> {
45
- return await this.prodRepository.getData();
76
+ public async getData (): Promise<string> {
77
+ return this.prodRepository.getData();
46
78
  }
47
79
 
48
80
  }
49
81
 
50
- class ProdController {
51
-
52
- @autowired() // inject dependency
53
- private readonly prodService!: ProdService;
82
+ @reflection
83
+ class ProdStore {
54
84
 
55
- // constructor use library, don't use him for inject
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 await this.prodService.getData();
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") { // override in test environment
98
+ if (process.env.NODE_ENV === "test") { // Override in test environment
64
99
  override(ProdRepository, MockRepository);
65
100
  }
66
101
 
67
- const controllerInstance = new ProdController(); // create intance by framework
68
- const data = await controllerInstance.getData();
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 Pro mode:
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 { autowired, override, reflection } from "first-di";
118
+ import { resolve, override, reflection } from "first-di";
83
119
 
84
- abstract class AbstractRepository { // abstract instead of interface
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 await Promise.resolve("production");
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 await Promise.resolve("mock");
138
+ public async getData (): Promise<string> {
139
+ return Promise.resolve("mock");
104
140
  }
105
141
 
106
142
  }
107
143
 
108
- abstract class AbstractService { // abstract instead of interface
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 await this.prodRepository.getData();
159
+ public async getData (): Promise<string> {
160
+ return this.prodRepository.getData();
125
161
  }
126
162
 
127
163
  }
128
164
 
129
- class ProdController {
130
-
131
- @autowired()
132
- private readonly prodService!: AbstractService;
165
+ @reflection
166
+ class ProdStore {
133
167
 
134
- // constructor use library, don't use him for inject
168
+ public constructor (
169
+ private readonly prodService: AbstractService
170
+ ) {}
135
171
 
136
- public async getData(): Promise<string> {
137
- return await this.prodService.getData();
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 controllerInstance = new ProdController();
151
- const data = await controllerInstance.getData();
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
- First DI has several points for customizing dependency options.
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
- export class Controller {
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
- // constructor use library, don't use him for inject
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
- - autowired - Decorator. Assigned to property for resolve dependency.
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, autowired, AutowiredLifetimes } from "first-di";
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
- First DI using OOP and SOLID design principles. Each part of DI can be overrided or extended after inheritance from base class.
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";
@@ -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;
@@ -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": "1.0.20",
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
- "peerDependencies": {
9
- "reflect-metadata": ">=0.1.0"
11
+ "repository": {
12
+ "type": "git",
13
+ "url": " git@github.com:LabEG/first-di.git"
10
14
  },
11
- "devDependencies": {
12
- "@labeg/code-style": "^4.0.5",
13
- "@types/chai": "^4.3.11",
14
- "@types/mocha": "^10.0.6",
15
- "@types/node": "^20.10.4",
16
- "chai": "^4.3.10",
17
- "mocha": "^10.2.0",
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
- "prepublishOnly": "npm run lint && npm run build && npm run test && npm version patch"
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
- "repository": {
29
- "type": "git",
30
- "url": " git@github.com:LabEG/first-di.git"
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
  }