atomic-di 1.0.0-rc.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +33 -33
- package/package.json +1 -1
package/README.md
CHANGED
@@ -13,7 +13,7 @@ This library implements lifetimes, scopes and mocking for pure dependency inject
|
|
13
13
|
- [Scoped](#Scoped)
|
14
14
|
- [Resolution context](#Resolution-context)
|
15
15
|
- [Mocking](#Mocking)
|
16
|
-
- [
|
16
|
+
- [Scoping](#Scoping)
|
17
17
|
- [Bulk resolutions](#Bulk-resolutions)
|
18
18
|
- [List resolution](#List-resolution)
|
19
19
|
- [Map resolution](#Map-resolution)
|
@@ -36,7 +36,7 @@ This library implements lifetimes, scopes and mocking for pure dependency inject
|
|
36
36
|
|
37
37
|
Before reading, it's highly recommended that you familiarize yourself with the concepts of inversion of control (IoC) and dependency injection (DI), as well as DI techniques.
|
38
38
|
|
39
|
-
If you need a container to build your application, or you are satisfied with pure dependency injection, you should definitely consider other solutions, or not use
|
39
|
+
If you need a container to build your application, or you are satisfied with pure dependency injection, you should definitely consider other solutions, or not use a framework at all.
|
40
40
|
|
41
41
|
This library is an attempt to provide full-featured dependency injection **without containers**.
|
42
42
|
|
@@ -44,21 +44,21 @@ This library is an attempt to provide full-featured dependency injection **witho
|
|
44
44
|
|
45
45
|
### Lifetimes
|
46
46
|
|
47
|
-
We can implement lifetime using static initializations together with factory functions that create instances on demand. However, this can introduce inconsistency into
|
47
|
+
We can implement lifetime using static initializations together with factory functions that create instances on demand. However, this can introduce inconsistency into a composition code.
|
48
48
|
|
49
49
|
This library solves this problem by allowing to resolve instances once using the same factory technique.
|
50
50
|
|
51
51
|
### Scopes
|
52
52
|
|
53
|
-
Often in your application you may need to resolve instances separately for different "scopes" of
|
53
|
+
Often in your application you may need to resolve instances separately for different "scopes" of a program, be it a request, a transaction or a worker thread. This behavior can be achieved by correctly distributing transient resolutions, but at scale the complexity of this approach will only increase.
|
54
54
|
|
55
|
-
This library solves this problem by introducing into factories (hereinafter referred to as providers)
|
55
|
+
This library solves this problem by introducing into factories (hereinafter referred to as providers) an ability to work with a map of providers to their instances, which serves as a scope.
|
56
56
|
|
57
|
-
###
|
57
|
+
### Testing
|
58
58
|
|
59
59
|
Testability is an important part of every application. IoC handles this very well, but to perform a unit test we still need to resolve modules. To ensure testing without side effects, developers often use mocking - replacing implementations with others with the same behavior. We can rebuild modules manually for each unit test or group of unit tests, but at scale this approach can introduce a lot of extra manual work without any significant benefit.
|
60
60
|
|
61
|
-
This library solves this problem by allowing you to use factories that have been defined for
|
61
|
+
This library solves this problem by allowing you to use factories that have been defined for a main application build. It's enough to create a map of mock providers to providers with the same interface, and pass it to a provider call to replace implementations in its dependencies.
|
62
62
|
|
63
63
|
# Installation
|
64
64
|
|
@@ -80,7 +80,7 @@ npx jsr add @ensi/di
|
|
80
80
|
- [Scoped](#Scoped)
|
81
81
|
- [Resolution context](#Resolution-context)
|
82
82
|
- [Mocking](#Mocking)
|
83
|
-
- [
|
83
|
+
- [Scoping](#Scoping)
|
84
84
|
- [Bulk resolutions](#Bulk-resolutions)
|
85
85
|
- [List resolution](#List-resolution)
|
86
86
|
- [Map resolution](#Map-resolution)
|
@@ -91,54 +91,54 @@ The library provides functions that create providers with behavior typical of si
|
|
91
91
|
|
92
92
|
### Transient
|
93
93
|
|
94
|
-
Transient providers are created using
|
94
|
+
Transient providers are created using `transient` function:
|
95
95
|
```ts
|
96
96
|
const getThing = transient(() => createThing())
|
97
97
|
```
|
98
98
|
|
99
|
-
Transient providers are no different from regular factories except for additional logic required for scopes and mocks to work correctly. This logic is also present in
|
99
|
+
Transient providers are no different from regular factories except for additional logic required for scopes and mocks to work correctly. This logic is also present in other two functions, you can read about it [here](#Resolution-context).
|
100
100
|
|
101
101
|
### Singleton
|
102
102
|
|
103
|
-
Singleton providers are created using
|
103
|
+
Singleton providers are created using `singleton` function:
|
104
104
|
```ts
|
105
105
|
const getA = singleton(() => createA())
|
106
106
|
const getB = transient((c) => createB(getA(c)))
|
107
107
|
```
|
108
108
|
|
109
|
-
In this case, calling `getA` will always result in
|
109
|
+
In this case, calling `getA` will always result in a same instance, and a passed factory will only be called once:
|
110
110
|
```ts
|
111
111
|
getA() === getA() == getB().A === getB().A
|
112
112
|
```
|
113
113
|
|
114
|
-
You may have noticed that the `getB` provider factory uses a certain `c` argument. This is a context that can optionally be passed when calling
|
114
|
+
You may have noticed that the `getB` provider factory uses a certain `c` argument. This is a context that can optionally be passed when calling a provider, you can read about it [here](#Resolution-context).
|
115
115
|
|
116
116
|
### Scoped
|
117
117
|
|
118
|
-
Scoped providers are created using
|
118
|
+
Scoped providers are created using `scoped` function:
|
119
119
|
```ts
|
120
120
|
const getThing = scoped(() => createThing())
|
121
121
|
```
|
122
122
|
|
123
|
-
When calling this provider without passing a scope to
|
123
|
+
When calling this provider without passing a scope to a resolution context, it will create a new unique instance:
|
124
124
|
```ts
|
125
125
|
getThing() !== getThing()
|
126
126
|
```
|
127
127
|
|
128
|
-
To get resolutions within a scope, we need to pass it to
|
128
|
+
To get resolutions within a scope, we need to pass it to a provider call in a resolution context object:
|
129
129
|
```ts
|
130
130
|
const scope = createScope()
|
131
131
|
|
132
132
|
getThing({ scope }) === getThing({ scope })
|
133
133
|
```
|
134
134
|
|
135
|
-
You can read more about scopes [here](#
|
135
|
+
You can read more about scopes [here](#Scoping).
|
136
136
|
|
137
137
|
## Resolution context
|
138
138
|
|
139
|
-
Each provider can accept a resolution context object. This is an object with optional `scope` and `mocks` fields that defines how
|
139
|
+
Each provider can accept a resolution context object. This is an object with optional `scope` and `mocks` fields that defines how an instance will be resolved.
|
140
140
|
|
141
|
-
In all provider factories that have dependencies, this context **must** be passed into all calls to other providers to ensure it is propagated up
|
141
|
+
In all provider factories that have dependencies, this context **must** be passed into all calls to other providers to ensure it is propagated up a call chain.
|
142
142
|
|
143
143
|
#### Incorrect
|
144
144
|
```ts
|
@@ -147,7 +147,7 @@ const getB = scoped(() => createB(getA()))
|
|
147
147
|
const getC = scoped(() => createC(getB()))
|
148
148
|
```
|
149
149
|
|
150
|
-
In this case,
|
150
|
+
In this case, a context will not propagate beyond `getC` and other providers will not know about a current scope and mocks, and `getB` will return an instance that is not related to any scopes.
|
151
151
|
|
152
152
|
#### Correct
|
153
153
|
```ts
|
@@ -156,23 +156,23 @@ const getB = scoped((c) => createB(getA(c)))
|
|
156
156
|
const getC = scoped((c) => createC(getB(c)))
|
157
157
|
```
|
158
158
|
|
159
|
-
In this case, `getC` will propagate
|
159
|
+
In this case, `getC` will propagate a context, and `getB` and `getA` will be aware of a current mocks and scopes, resolving instances correctly.
|
160
160
|
|
161
|
-
More details on how
|
161
|
+
More details on how provider behaves depending on a passed context can be found in sections about [mocking](#Mocking) and [scoping](#Scoping).
|
162
162
|
|
163
163
|
## Mocking
|
164
164
|
|
165
|
-
To replace implementations inside factories, we can use a mock map. To create one, we can use
|
165
|
+
To replace implementations inside factories, we can use a mock map. To create one, we can use `createMockMap` function:
|
166
166
|
```ts
|
167
167
|
const mockMap = createMockMap()
|
168
168
|
```
|
169
169
|
|
170
|
-
To register a mock, you need to `set` an entry with
|
170
|
+
To register a mock, you need to `set` an entry with an original provider in a key and its mock in a value:
|
171
171
|
```ts
|
172
172
|
mockMap.set(getDatabase, getMockDatabase)
|
173
173
|
```
|
174
174
|
|
175
|
-
Once all mocks have been registered, this map can be passed to
|
175
|
+
Once all mocks have been registered, this map can be passed to a provider call. If a provider finds a mock in a resolution context, it checks whether it is among the keys, and in that case returns a mock call instead of itself.
|
176
176
|
|
177
177
|
#### Direct replacement
|
178
178
|
```ts
|
@@ -203,16 +203,16 @@ const mocks = createMockMap().set(getC, getSea)
|
|
203
203
|
getC({ mocks }) === "2sea"
|
204
204
|
```
|
205
205
|
|
206
|
-
##
|
206
|
+
## Scoping
|
207
207
|
|
208
|
-
In this library, a scope is a map of providers to their resolutions. To create one, you can use
|
208
|
+
In this library, a scope is a map of providers to their resolutions. To create one, you can use `createScope` function:
|
209
209
|
```ts
|
210
210
|
const scope = createScope()
|
211
211
|
```
|
212
212
|
|
213
|
-
It is passed to
|
214
|
-
- If
|
215
|
-
- If a scope is not passed to
|
213
|
+
It is passed to a scoped provider call or to a call of a provider that has the scoped provider among its transitive dependencies.
|
214
|
+
- If a scoped provider finds a scope in a resolution context, it first tries to get its own resolution from it. If there is none, it creates a new resolution and places it in the scope below itself.
|
215
|
+
- If a scope is not passed to a resolution context when calling a scoped provider, the provider will create a new instance, i.e. it will behave as a transient provider.
|
216
216
|
|
217
217
|
#### Direct scoped provider call
|
218
218
|
```ts
|
@@ -253,11 +253,11 @@ thing1.scopedDependency === thing2.scopedDependency
|
|
253
253
|
|
254
254
|
## Bulk resolutions
|
255
255
|
|
256
|
-
It often happens that you need to resolve instances of a large number of entities, in our case providers, with
|
256
|
+
It often happens that you need to resolve instances of a large number of entities, in our case providers, with a same context. Fortunately, the library provides functions for this.
|
257
257
|
|
258
258
|
### List resolution
|
259
259
|
|
260
|
-
To resolve instances of a list of providers, you can use
|
260
|
+
To resolve instances of a list of providers, you can use `resolveList` function, which takes a list of providers and a common resolution context. If at least one provider in the passed list of providers returns a `Promise`, the function will return a `Promise` of a list of **awaited** resolutions.
|
261
261
|
|
262
262
|
#### Only sync providers
|
263
263
|
```ts
|
@@ -301,7 +301,7 @@ resolutions == [
|
|
301
301
|
|
302
302
|
### Map resolution
|
303
303
|
|
304
|
-
To resolve instances of a provider map, or an object with string keys and providers in
|
304
|
+
To resolve instances of a provider map, or an object with string keys and providers in a values, you can use `resolveMap` function, which takes a provider map and a common resolution context. If at least one provider in the values of the passed provider map returns a `Promise`, the function will return a `Promise` of a map of **awaited** resolutions.
|
305
305
|
|
306
306
|
#### Only sync providers
|
307
307
|
```ts
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "atomic-di",
|
3
|
-
"version": "1.0.0
|
3
|
+
"version": "1.0.0",
|
4
4
|
"description": "This library implements lifetimes, scopes and mocking for pure dependency injection.",
|
5
5
|
"repository": "https://github.com/ncor/atomic-di",
|
6
6
|
"bugs": "https://github.com/ncor/atomic-di/issues",
|