atomic-di 1.2.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +262 -224
- package/dist/index.d.mts +101 -78
- package/dist/index.d.ts +101 -78
- package/dist/index.js +51 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
@@ -7,59 +7,44 @@ This library implements lifetimes, scopes and mocking for pure dependency inject
|
|
7
7
|
- [Intro](#Intro)
|
8
8
|
- [Installation](#Installation)
|
9
9
|
- [Usage](#Usage)
|
10
|
-
- [
|
10
|
+
- [Creating resolvers](#Creating-resolvers)
|
11
11
|
- [Transient](#Transient)
|
12
12
|
- [Singleton](#Singleton)
|
13
13
|
- [Scoped](#Scoped)
|
14
|
-
- [
|
14
|
+
- [Propagating a context](#Propagating-a-context)
|
15
15
|
- [Mocking](#Mocking)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
- [
|
16
|
+
- [Registering mocks](#Registering-mocks)
|
17
|
+
- [Resolving with mocks](#Resolving-with-mocks)
|
18
|
+
- [Scopes](#Scopes)
|
19
|
+
- [Creating a scope](#Scope)
|
20
|
+
- [Resolving with a scope](#Resolving-with-a-scope)
|
21
|
+
- [Resolving collections](#Resolving-collections)
|
22
|
+
- [Resolving a list](#Resolving-a-list)
|
23
|
+
- [Resolving a map](#Resolving-a-map)
|
20
24
|
- [Reference](#Reference)
|
21
25
|
- [Functions](#Functions)
|
22
|
-
- [transient](#transient)
|
23
|
-
- [singleton](#singleton)
|
24
|
-
- [scoped](#scoped)
|
25
|
-
- [createMockMap](#createMockMap)
|
26
|
-
- [createScope](#createScope)
|
26
|
+
- [`transient`](#transient)
|
27
|
+
- [`singleton`](#singleton)
|
28
|
+
- [`scoped`](#scoped)
|
29
|
+
- [`createMockMap`](#createMockMap)
|
30
|
+
- [`createScope`](#createScope)
|
31
|
+
- [`resolveList`](#resolveList)
|
32
|
+
- [`resolveMap`](#resolveMap)
|
27
33
|
- [Types](#Types)
|
28
|
-
- [
|
29
|
-
- [
|
30
|
-
- [
|
31
|
-
- [
|
34
|
+
- [`ResolverFn`](#ResolverFn)
|
35
|
+
- [`Resolver`](#Resolver)
|
36
|
+
- [`ResolutionContext`](#ResolutionContext)
|
37
|
+
- [`MockMap`](#MockMap)
|
38
|
+
- [`Scope`](#Scope)
|
32
39
|
|
33
40
|
# Intro
|
34
41
|
|
35
|
-
## Prerequisites
|
36
|
-
|
37
42
|
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
43
|
|
39
|
-
If you need a container to build your application, or you are satisfied with
|
44
|
+
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
45
|
|
41
46
|
This library is an attempt to provide full-featured dependency injection **without containers**.
|
42
47
|
|
43
|
-
## Problems and solutions
|
44
|
-
|
45
|
-
### Lifetimes
|
46
|
-
|
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
|
-
|
49
|
-
This library solves this problem by allowing to resolve instances once using the same factory technique.
|
50
|
-
|
51
|
-
### Scopes
|
52
|
-
|
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
|
-
|
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
|
-
|
57
|
-
### Testing
|
58
|
-
|
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
|
-
|
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
|
-
|
63
48
|
# Installation
|
64
49
|
|
65
50
|
You can use any package manager.
|
@@ -74,200 +59,238 @@ npx jsr add @ensi/di
|
|
74
59
|
# Usage
|
75
60
|
|
76
61
|
#### Table of contents
|
77
|
-
- [
|
62
|
+
- [Creating resolvers](#Creating-resolvers)
|
78
63
|
- [Transient](#Transient)
|
79
64
|
- [Singleton](#Singleton)
|
80
65
|
- [Scoped](#Scoped)
|
81
|
-
- [
|
66
|
+
- [Propagating a context](#Propagating-a-context)
|
82
67
|
- [Mocking](#Mocking)
|
83
|
-
- [
|
84
|
-
- [
|
85
|
-
|
86
|
-
- [
|
68
|
+
- [Registering mocks](#Registering-mocks)
|
69
|
+
- [Resolving with mocks](#Resolving-with-mocks)
|
70
|
+
- [Scopes](#Scopes)
|
71
|
+
- [Creating a scope](#Scope)
|
72
|
+
- [Resolving with a scope](#Resolving-with-a-scope)
|
73
|
+
- [Resolving collections](#Resolving-collections)
|
74
|
+
- [Resolving a list](#Resolving-a-list)
|
75
|
+
- [Resolving a map](#Resolving-a-map)
|
76
|
+
|
77
|
+
## Creating resolvers
|
87
78
|
|
88
|
-
|
79
|
+
The approach to dependency injection in this library is factories. It consists of a factory creating an instance of a certain type by calling other factories that resolve dependencies for it.
|
89
80
|
|
90
|
-
|
81
|
+
To implement lifetimes, scope, and mocking mechanisms, the library provides functions that create factories with functionality specific to a particular lifetime, such factories are called **resolvers**. They all have some functionality in common, but first let's look at the functions that create them.
|
91
82
|
|
92
83
|
### Transient
|
93
84
|
|
94
|
-
|
85
|
+
The `transient` function creates a basic resolver that does not contain any logic that controls a lifetime of a resolution. This means that this resolver will call a passed factory and return a new instance each time it is called.
|
95
86
|
```ts
|
96
|
-
const
|
87
|
+
const getRandom = transient(Math.random)
|
88
|
+
```
|
89
|
+
```ts
|
90
|
+
getRandom() !== getRandom()
|
97
91
|
```
|
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 other two functions, you can read about it [here](#Resolution-context).
|
100
92
|
|
101
93
|
### Singleton
|
102
94
|
|
103
|
-
|
95
|
+
The `singleton` function creates a resolver that contains a logic specific to singletons. This means that a singleton resolver will only call a passed factory once, and will return a single instance created each time it is called.
|
104
96
|
```ts
|
105
|
-
const
|
106
|
-
const getB = transient((c) => createB(getA(c)))
|
97
|
+
const getRandom = singleton(Math.random)
|
107
98
|
```
|
108
|
-
|
109
|
-
In this case, calling `getA` will always result in a same instance, and a passed factory will only be called once:
|
110
99
|
```ts
|
111
|
-
|
100
|
+
getRandom() === getRandom()
|
112
101
|
```
|
113
102
|
|
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
|
-
|
116
103
|
### Scoped
|
117
104
|
|
118
|
-
|
105
|
+
The `scoped` function creates a resolver that contains logic specific to scoped registrations, often supported by IoC containers. These resolvers operate on scope instances that are passed into a resolution context when called. They check whether their instance is in a scope, and depending on this, save a new instance or return an existing one within that scope. If a resolver is not passed a scope when called, it will behave as a singleton, simulating a global scope.
|
119
106
|
```ts
|
120
|
-
const
|
107
|
+
const getRandom = scoped(Math.random)
|
121
108
|
```
|
122
|
-
|
123
|
-
When calling this provider without passing a scope to a resolution context, it will act as a singleton, resulting in a same instance:
|
124
109
|
```ts
|
125
|
-
|
110
|
+
getRandom() === getRandom()
|
126
111
|
```
|
127
|
-
|
128
|
-
To get resolutions within a scope, we need to pass it to a provider call in a resolution context object:
|
129
112
|
```ts
|
130
|
-
|
131
|
-
|
132
|
-
|
113
|
+
getRandom({ scope: myScope }) === getRandom({ scope: myScope })
|
114
|
+
```
|
115
|
+
```ts
|
116
|
+
getRandom() !== getRandom({ scope: myScope })
|
133
117
|
```
|
134
118
|
|
135
|
-
|
119
|
+
A detailed explanation of the scope mechanism and its use is described in [this](#Scopes) section.
|
136
120
|
|
137
|
-
##
|
121
|
+
## Propagating a context
|
138
122
|
|
139
|
-
Each
|
123
|
+
Each resolver takes an optional resolution context. This is an object that can contain a scope and a mock map. Based on this context, resolvers determine how to resolve an instance.
|
140
124
|
|
141
|
-
In
|
125
|
+
In order for a resolution context to correctly influence a current resolution, it **must** be propagated up a resolver call chain so that each resolver is aware of a current context. Therefore, if a factory uses other resolvers, it **must** pass a resolution context it receives into **each** resolver call.
|
142
126
|
|
143
127
|
#### Incorrect
|
144
128
|
```ts
|
145
|
-
const
|
146
|
-
const
|
147
|
-
|
129
|
+
const getDependency = transient(() => createDependency())
|
130
|
+
const getEntity = transient(() =>
|
131
|
+
createEntity(getDependency())
|
132
|
+
)
|
148
133
|
```
|
149
|
-
|
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
|
-
|
152
134
|
#### Correct
|
153
135
|
```ts
|
154
|
-
const
|
155
|
-
const
|
156
|
-
|
136
|
+
const getDependency = transient(() => createDependency())
|
137
|
+
const getEntity = transient((c) =>
|
138
|
+
createEntity(getDependency(c)) // context is propagated
|
139
|
+
)
|
157
140
|
```
|
158
141
|
|
159
|
-
|
142
|
+
## Mocking
|
143
|
+
|
144
|
+
Mocking is a common mechanism in testing whereby some implementation being used is replaced with a replica to prevent side effects or reduce testing load.
|
160
145
|
|
161
|
-
|
146
|
+
This library implements this mechanism by adding logic to each resolver responsible for replacing **itself** (not a resolution) when its own mock is present in a resolution context. A definition of mocks in a resolution context is done by passing a mock map to this resolution context.
|
162
147
|
|
163
|
-
|
148
|
+
### Registering mocks
|
149
|
+
|
150
|
+
A mock map is an immutable object similar to `Map` that implements an interface for registering and receiving mocks of some resolvers. To create one, you must use `createMockMap` function.
|
151
|
+
```ts
|
152
|
+
const mocks = createMockMap()
|
153
|
+
```
|
164
154
|
|
165
|
-
To
|
155
|
+
To register a mock resolver, use `mock` method, passing an original resolver and its mock. It will create a new mock map with this registration.
|
166
156
|
```ts
|
167
|
-
const
|
157
|
+
const mocks = createMockMap()
|
158
|
+
.mock(getDatabase, getDatabaseMock)
|
159
|
+
.mock(getLogger, getLoggerMock)
|
160
|
+
// ...
|
168
161
|
```
|
169
162
|
|
170
|
-
|
163
|
+
If you need to partially replace an implementation, i.e. replace some fields in a resolution, use `mockPartially` method. Both original and mock resolver must return an object or a `Promise` of an object.
|
171
164
|
```ts
|
172
|
-
|
165
|
+
const getDatabaseMock = singleton(() => ({
|
166
|
+
execute: (q) => console.log("db: executing", q)
|
167
|
+
}))
|
168
|
+
const mocks = createMockMap()
|
169
|
+
.mockPartially(getDatabase, getDatabaseMock)
|
173
170
|
```
|
174
171
|
|
175
|
-
|
172
|
+
### Resolving with mocks
|
176
173
|
|
177
|
-
|
174
|
+
To resolve an instance with mocks, you must pass a previously defined mock map to a resolution context when calling any resolver.
|
178
175
|
```ts
|
179
|
-
|
180
|
-
|
176
|
+
resolver({ mocks: myMockMap })
|
177
|
+
```
|
178
|
+
|
179
|
+
If resolver's direct or transitive dependencies or the resolver itself have their mock registered in a mock map, they will replace themselves with this mock, depending on a type of a mock. This behavior is clearly demonstrated in examples below.
|
181
180
|
|
182
|
-
|
181
|
+
#### Full mock
|
182
|
+
```ts
|
183
|
+
const getDependency = transitive(() => "dependency")
|
184
|
+
const getEntity = transitive((c) => ({
|
185
|
+
dependency: getDependency(c)
|
186
|
+
}))
|
183
187
|
```
|
184
188
|
```ts
|
185
|
-
const
|
186
|
-
const mocks = createMockMap().set(getB, getBee)
|
189
|
+
const getDependencyMock = transitive(() => "dependencyMock")
|
187
190
|
|
188
|
-
|
191
|
+
const mocks = createMockMap()
|
192
|
+
.mock(getDependency, getDependencyMock
|
189
193
|
```
|
190
|
-
|
191
|
-
#### Direct/transitive dependency replacement
|
192
194
|
```ts
|
193
|
-
|
194
|
-
|
195
|
-
|
195
|
+
getEntity({ mocks }) == {
|
196
|
+
dependency: "dependencyMock"
|
197
|
+
}
|
198
|
+
```
|
196
199
|
|
197
|
-
|
200
|
+
#### Partial mock
|
201
|
+
```ts
|
202
|
+
const getDependency = transitive(() => ({
|
203
|
+
value: "dependency",
|
204
|
+
origin: "getDependency"
|
205
|
+
}))
|
206
|
+
const getEntity = transitive((c) => ({
|
207
|
+
dependency: getDependency(c)
|
208
|
+
}))
|
198
209
|
```
|
199
210
|
```ts
|
200
|
-
const
|
201
|
-
|
211
|
+
const getDependencyMock = transitive(() => ({
|
212
|
+
value: "dependencyMock"
|
213
|
+
}))
|
202
214
|
|
203
|
-
|
215
|
+
const mocks = createMockMap()
|
216
|
+
.mockPartially(getDependency, getDependencyMock
|
217
|
+
```
|
218
|
+
```ts
|
219
|
+
getEntity({ mocks }) == {
|
220
|
+
dependency: {
|
221
|
+
value: "dependencyMock", // replaced
|
222
|
+
origin: "getDependency" // original value
|
223
|
+
}
|
224
|
+
}
|
204
225
|
```
|
205
226
|
|
206
|
-
##
|
227
|
+
## Scopes
|
207
228
|
|
208
|
-
|
229
|
+
Sometimes you need to create and save resolutions for different areas of your program, such as a request or a thread. Scopes solve this problem.
|
230
|
+
|
231
|
+
IoC containers implement this by defining copies of a container in different parts of a program. Within this library, a scope is simply a map of resolvers to their resolutions. This map is used by scoped resolvers described earlier.
|
232
|
+
|
233
|
+
### Creating a scope
|
234
|
+
|
235
|
+
There are two ways to create a scope:
|
236
|
+
- By calling `createScope` function.
|
209
237
|
```ts
|
210
238
|
const scope = createScope()
|
211
239
|
```
|
240
|
+
- By creating a `Map` with the correct type manually.
|
241
|
+
```ts
|
242
|
+
const scope = new Map<Resolver<any>, any>()
|
243
|
+
```
|
244
|
+
```
|
212
245
|
|
213
|
-
|
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 always result in a same instance, and a passed factory will only be called once, i.e. it will behave as a singleton provider.
|
246
|
+
### Resolving with a scope
|
216
247
|
|
217
|
-
|
248
|
+
To get a scoped resolver resolution within a scope, a scoped resolver, or a resolver that has a scoped resolver in its direct or transitive dependencies, must be called with a scope passed to a resolution context.
|
249
|
+
|
250
|
+
#### Direct resolution
|
218
251
|
```ts
|
219
|
-
const
|
252
|
+
const getEntity = scoped(() => createEntity())
|
220
253
|
```
|
221
254
|
```ts
|
222
|
-
const
|
223
|
-
const thing2 = getThing()
|
255
|
+
const scope = createScope()
|
224
256
|
|
225
|
-
|
257
|
+
const scopeEntity = getEntity({ scope })
|
226
258
|
```
|
227
259
|
```ts
|
228
|
-
|
229
|
-
const thing2 = getThing({ scope })
|
230
|
-
const thingFallback = getThing()
|
231
|
-
|
232
|
-
thing1 === thing2 !== thingFallback
|
260
|
+
scope.get(getEntity) === scopeEntity
|
233
261
|
```
|
234
262
|
|
235
|
-
####
|
263
|
+
#### Indirect resolution
|
236
264
|
```ts
|
237
|
-
const
|
238
|
-
const
|
239
|
-
|
240
|
-
)
|
265
|
+
const getDependency = scoped(() => createDependency())
|
266
|
+
const getEntity = transient((c) => ({
|
267
|
+
dependency: getDependency(c)
|
268
|
+
}))
|
241
269
|
```
|
242
270
|
```ts
|
243
|
-
const
|
244
|
-
const thing2 = getThing()
|
271
|
+
const scope = createScope()
|
245
272
|
|
246
|
-
|
273
|
+
const entity = getEntity({ scope })
|
247
274
|
```
|
248
275
|
```ts
|
249
|
-
|
250
|
-
const thing2 = getThing({ scope })
|
251
|
-
const thingWithFallback = getThing()
|
252
|
-
|
253
|
-
thing1.scopedDependency === thing2.scopedDependency !== thingWithFallback
|
276
|
+
scope.get(getDependency) === entity.dependency
|
254
277
|
```
|
255
278
|
|
256
|
-
##
|
279
|
+
## Resolving collections
|
257
280
|
|
258
|
-
|
281
|
+
Often you may need to get resolutions of a large number of resolvers within a single context at once. Doing this manually is inefficient, so the library provides functions specifically for this.
|
259
282
|
|
260
|
-
###
|
283
|
+
### Resolving a list
|
261
284
|
|
262
|
-
|
263
|
-
|
264
|
-
#### Only sync providers
|
285
|
+
If you need to get a list of resolutions of different resolvers, you can use `resolveList` function.
|
265
286
|
```ts
|
266
287
|
const getA = scoped(() => createA())
|
267
288
|
const getB = scoped(() => createB())
|
268
289
|
const getC = scoped(() => createC())
|
269
|
-
|
290
|
+
```
|
291
|
+
```ts
|
270
292
|
const scope = createScope()
|
293
|
+
|
271
294
|
const resolutions = resolveList(
|
272
295
|
[getA, getB, getC],
|
273
296
|
{ scope }
|
@@ -275,19 +298,21 @@ const resolutions = resolveList(
|
|
275
298
|
```
|
276
299
|
```ts
|
277
300
|
resolutions == [
|
278
|
-
getA({ scope })
|
279
|
-
getB({ scope })
|
301
|
+
getA({ scope })
|
302
|
+
getB({ scope })
|
280
303
|
getC({ scope })
|
281
304
|
]
|
282
305
|
```
|
283
306
|
|
284
|
-
|
307
|
+
If one of passed resolvers returns a promise, the function will return a `Promise` of a list of awaited resolutions.
|
285
308
|
```ts
|
286
309
|
const getA = scoped(() => createA())
|
287
|
-
const getB = scoped(async () =>
|
310
|
+
const getB = scoped(async () => createB())
|
288
311
|
const getC = scoped(() => createC())
|
289
|
-
|
312
|
+
```
|
313
|
+
```ts
|
290
314
|
const scope = createScope()
|
315
|
+
|
291
316
|
const resolutions = await resolveList(
|
292
317
|
[getA, getB, getC],
|
293
318
|
{ scope }
|
@@ -295,23 +320,23 @@ const resolutions = await resolveList(
|
|
295
320
|
```
|
296
321
|
```ts
|
297
322
|
resolutions == [
|
298
|
-
getA({ scope })
|
299
|
-
await getB({ scope })
|
323
|
+
getA({ scope })
|
324
|
+
await getB({ scope })
|
300
325
|
getC({ scope })
|
301
326
|
]
|
302
327
|
```
|
303
328
|
|
304
|
-
###
|
305
|
-
|
306
|
-
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.
|
329
|
+
### Resolving a map
|
307
330
|
|
308
|
-
|
331
|
+
If you need to get a map of resolutions of different resolvers, you can use `resolveMap` function.
|
309
332
|
```ts
|
310
333
|
const getA = scoped(() => createA())
|
311
334
|
const getB = scoped(() => createB())
|
312
335
|
const getC = scoped(() => createC())
|
313
|
-
|
336
|
+
```
|
337
|
+
```ts
|
314
338
|
const scope = createScope()
|
339
|
+
|
315
340
|
const resolutions = resolveMap(
|
316
341
|
{ a: getA, b: getB, c: getC },
|
317
342
|
{ scope }
|
@@ -319,28 +344,30 @@ const resolutions = resolveMap(
|
|
319
344
|
```
|
320
345
|
```ts
|
321
346
|
resolutions == {
|
322
|
-
a: getA({ scope })
|
323
|
-
b: getB({ scope })
|
347
|
+
a: getA({ scope })
|
348
|
+
b: getB({ scope })
|
324
349
|
c: getC({ scope })
|
325
350
|
}
|
326
351
|
```
|
327
352
|
|
328
|
-
|
353
|
+
If one of passed resolvers returns `Promise`, the function will return a `Promise` of a map of awaited resolutions.
|
329
354
|
```ts
|
330
355
|
const getA = scoped(() => createA())
|
331
|
-
const getB = scoped(async () =>
|
356
|
+
const getB = scoped(async () => createB())
|
332
357
|
const getC = scoped(() => createC())
|
333
|
-
|
358
|
+
```
|
359
|
+
```ts
|
334
360
|
const scope = createScope()
|
335
|
-
|
361
|
+
|
362
|
+
const resolutions = await resolveList(
|
336
363
|
{ a: getA, b: getB, c: getC },
|
337
364
|
{ scope }
|
338
365
|
)
|
339
366
|
```
|
340
367
|
```ts
|
341
368
|
resolutions == {
|
342
|
-
a: getA({ scope })
|
343
|
-
b: await getB({ scope })
|
369
|
+
a: getA({ scope })
|
370
|
+
b: await getB({ scope })
|
344
371
|
c: getC({ scope })
|
345
372
|
}
|
346
373
|
```
|
@@ -349,66 +376,69 @@ resolutions == {
|
|
349
376
|
|
350
377
|
#### Table of contents
|
351
378
|
- [Functions](#Functions)
|
352
|
-
- [transient](#transient)
|
353
|
-
- [singleton](#singleton)
|
354
|
-
- [scoped](#scoped)
|
355
|
-
- [createMockMap](#createMockMap)
|
356
|
-
- [createScope](#createScope)
|
379
|
+
- [`transient`](#transient)
|
380
|
+
- [`singleton`](#singleton)
|
381
|
+
- [`scoped`](#scoped)
|
382
|
+
- [`createMockMap`](#createMockMap)
|
383
|
+
- [`createScope`](#createScope)
|
384
|
+
- [`resolveList`](#resolveList)
|
385
|
+
- [`resolveMap`](#resolveMap)
|
357
386
|
- [Types](#Types)
|
358
|
-
- [
|
359
|
-
- [
|
360
|
-
- [
|
361
|
-
- [
|
387
|
+
- [`ResolverFn`](#ResolverFn)
|
388
|
+
- [`Resolver`](#Resolver)
|
389
|
+
- [`ResolutionContext`](#ResolutionContext)
|
390
|
+
- [`MockMap`](#MockMap)
|
391
|
+
- [`Scope`](#Scope)
|
362
392
|
|
363
393
|
## Functions
|
364
394
|
|
365
395
|
### `transient`
|
366
396
|
```ts
|
367
|
-
function transient<T>(
|
397
|
+
function transient<T>(fn: ResolverFn<T>): Resolver<T>
|
368
398
|
```
|
369
|
-
- `resolver`: A function that
|
399
|
+
- `resolver`: A function that takes a resolution context and returns a value of some type.
|
370
400
|
|
371
|
-
Creates a
|
401
|
+
Creates a resolver that creates a new resolution on each call.
|
372
402
|
|
373
403
|
#### Example
|
374
404
|
```ts
|
375
|
-
const
|
376
|
-
|
405
|
+
const getEntity = transient(() => createEntity())
|
406
|
+
getEntity() !== getEntity()
|
377
407
|
```
|
378
408
|
|
379
409
|
### `singleton`
|
380
410
|
```ts
|
381
|
-
function singleton<T>(resolver:
|
411
|
+
function singleton<T>(resolver: ResolverFn<T>): Resolver<T>
|
382
412
|
```
|
383
|
-
- `resolver`: A function that
|
413
|
+
- `resolver`: A function that takes a resolution context and returns a value of some type.
|
384
414
|
|
385
|
-
Creates a
|
415
|
+
Creates a resolver that creates a resolution once and return it on each call.
|
386
416
|
|
387
417
|
#### Example
|
388
418
|
```ts
|
389
|
-
const
|
390
|
-
|
419
|
+
const getEntity = singleton(() => createEntity())
|
420
|
+
getEntity() === getEntity()
|
391
421
|
```
|
392
422
|
|
393
423
|
### `scoped`
|
394
424
|
```ts
|
395
|
-
function scoped<T>(resolver:
|
425
|
+
function scoped<T>(resolver: ResolverFn<T>): Resolver<T>
|
396
426
|
```
|
397
|
-
- `resolver`: A function that
|
427
|
+
- `resolver`: A function that takes a resolution context and returns a value of some type.
|
398
428
|
|
399
|
-
Creates a
|
429
|
+
Creates a resolver that takes its resolution from a scope or create a new one and save it if there is none. If no scope was passed in a resolution context, it will act as a singleton.
|
400
430
|
|
401
431
|
#### Example 1
|
402
432
|
```ts
|
403
|
-
const
|
404
|
-
|
433
|
+
const getEntity = scoped(() => createEntity())
|
434
|
+
getEntity() === getEntity()
|
405
435
|
```
|
406
436
|
|
407
437
|
#### Example 2
|
408
438
|
```ts
|
409
|
-
const
|
439
|
+
const getEntity = scoped(() => createEntity())
|
410
440
|
const scope = createScope()
|
411
|
-
|
441
|
+
getEntity({ scope }) === getEntity({ scope }) !== getEntity()
|
412
442
|
```
|
413
443
|
|
414
444
|
### `createMockMap`
|
@@ -416,14 +446,18 @@ getThing({ scope }) === getThing({ scope }) !== getThing()
|
|
416
446
|
function createMockMap(): MockMap
|
417
447
|
```
|
418
448
|
|
419
|
-
Creates a
|
449
|
+
Creates a mock map, an immutable map that registers and provides mocks. Is passed in a resolution context and used by resolvers to replace or partially replace themselves with a mock if one is defined.
|
420
450
|
|
421
451
|
#### Example
|
422
452
|
```ts
|
423
453
|
const mocks = createMockMap()
|
424
|
-
.
|
454
|
+
.mock(getDependency, getDependencyMock)
|
455
|
+
.mockPartially(
|
456
|
+
getOtherDependency,
|
457
|
+
transient(() => ({ someField: "mock" }))
|
458
|
+
)
|
425
459
|
|
426
|
-
|
460
|
+
const entityWithMocks = getEntity({ mocks })
|
427
461
|
```
|
428
462
|
|
429
463
|
### `createScope`
|
@@ -431,7 +465,7 @@ getThing({ mocks })
|
|
431
465
|
function createScope(): Scope
|
432
466
|
```
|
433
467
|
|
434
|
-
Creates a `Map` of
|
468
|
+
Creates a `Map` of resolvers to their resolutions. Is passed in a resolution context and used by scoped resolvers to retrieve or save resolution within it.
|
435
469
|
|
436
470
|
#### Example
|
437
471
|
```ts
|
@@ -439,26 +473,25 @@ const requestScope = createScope()
|
|
439
473
|
|
440
474
|
app.use(() => {
|
441
475
|
const db = getDb({ scope: requestScope })
|
442
|
-
// ...
|
443
476
|
})
|
444
477
|
```
|
445
478
|
|
446
479
|
### `resolveList`
|
447
480
|
```ts
|
448
|
-
function resolveList<const
|
449
|
-
|
481
|
+
function resolveList<const Resolvers extends ResolverList>(
|
482
|
+
resolvers: Resolvers,
|
450
483
|
context?: ResolutionContext
|
451
484
|
): AwaitValuesInCollection<
|
452
|
-
|
485
|
+
InferResolverCollectionResolutions<Resolvers>
|
453
486
|
>
|
454
487
|
```
|
455
|
-
- `
|
488
|
+
- `resolvers`: A list of resolvers.
|
456
489
|
- `context?`: A resolution context.
|
457
490
|
|
458
|
-
Calls every
|
491
|
+
Calls every resolver in a list with a provided resolution context and returns a list of resolutions. Returns a `Promise` of a list of awaited resolutions if there's at least one `Promise` in the resolutions.
|
459
492
|
|
460
493
|
#### Example 1
|
461
|
-
Only sync
|
494
|
+
Only sync resolvers:
|
462
495
|
```ts
|
463
496
|
const getA = scoped(() => createA())
|
464
497
|
const getB = scoped(() => createB())
|
@@ -479,7 +512,7 @@ resolutions == [
|
|
479
512
|
```
|
480
513
|
|
481
514
|
#### Example 2
|
482
|
-
Some
|
515
|
+
Some resolver is async:
|
483
516
|
```ts
|
484
517
|
const getA = scoped(() => createA())
|
485
518
|
const getB = scoped(async () => await createB())
|
@@ -501,20 +534,20 @@ resolutions == [
|
|
501
534
|
|
502
535
|
### `resolveMap`
|
503
536
|
```ts
|
504
|
-
function resolveMap<const
|
505
|
-
|
537
|
+
function resolveMap<const Resolvers extends ResolverRecord>(
|
538
|
+
resolvers: Resolvers,
|
506
539
|
context?: ResolutionContext
|
507
540
|
): AwaitValuesInCollection<
|
508
|
-
|
541
|
+
InferResolverCollectionResolutions<Resolvers>
|
509
542
|
>
|
510
543
|
```
|
511
|
-
- `
|
544
|
+
- `resolvers`: A map of resolvers.
|
512
545
|
- `context?`: A resolution context.
|
513
546
|
|
514
|
-
Calls every
|
547
|
+
Calls every resolver in a map with a provided resolution context and returns a map with identical keys but with resolutions in values instead. Returns a `Promise` of a map awaited resolutions if there's at least one `Promise` in the resolutions.
|
515
548
|
|
516
549
|
#### Example 1
|
517
|
-
Only sync
|
550
|
+
Only sync resolvers:
|
518
551
|
```ts
|
519
552
|
const getA = scoped(() => createA())
|
520
553
|
const getB = scoped(() => createB())
|
@@ -535,7 +568,7 @@ resolutions == {
|
|
535
568
|
```
|
536
569
|
|
537
570
|
#### Example 2
|
538
|
-
Some
|
571
|
+
Some resolver is async:
|
539
572
|
```ts
|
540
573
|
const getA = scoped(() => createA())
|
541
574
|
const getB = scoped(async () => await createB())
|
@@ -557,21 +590,19 @@ resolutions == {
|
|
557
590
|
|
558
591
|
## Types
|
559
592
|
|
560
|
-
### `
|
593
|
+
### `ResolverFn`
|
561
594
|
```ts
|
562
|
-
type
|
595
|
+
type ResolverFn<T> = (context?: ResolutionContext) => T
|
563
596
|
```
|
564
597
|
|
565
|
-
A function that
|
598
|
+
A function that takes a resolution context and returns a value of some type.
|
566
599
|
|
567
|
-
### `
|
600
|
+
### `Resolver`
|
568
601
|
```ts
|
569
|
-
type
|
570
|
-
__brand: "provider"
|
571
|
-
}
|
602
|
+
type Resolver<T> = (context?: ResolutionContext) => T
|
572
603
|
```
|
573
604
|
|
574
|
-
A function that
|
605
|
+
A function that returns a value of some type based on a resolution context.
|
575
606
|
|
576
607
|
### `ResolutionContext`
|
577
608
|
```ts
|
@@ -581,29 +612,36 @@ type ResolutionContext = {
|
|
581
612
|
}
|
582
613
|
```
|
583
614
|
|
584
|
-
A context used by
|
615
|
+
A context used by resolvers that defines the behaviour of the resolver with the passed mocks and scope.
|
585
616
|
|
586
617
|
### `MockMap`
|
587
618
|
```ts
|
588
|
-
type MockMap =
|
589
|
-
|
590
|
-
|
619
|
+
type MockMap = {
|
620
|
+
mock<T>(original: Resolver<T>, mock: Resolver<T>): MockMap;
|
621
|
+
mockPartially<T extends object>(
|
622
|
+
original: Resolver<T>,
|
623
|
+
mock: Resolver<PromiseAwarePartial<T>>,
|
624
|
+
): MockMap;
|
625
|
+
get<T>(original: Resolver<T>): Mock<T> | undefined;
|
591
626
|
};
|
592
627
|
```
|
593
|
-
- `
|
594
|
-
- `
|
595
|
-
- `mock`: The mock
|
596
|
-
- `
|
597
|
-
- `
|
628
|
+
- `mock`: Registers a mock for a resolver, creating a new `MockMap` with this registration.
|
629
|
+
- `original`: The original resolver.
|
630
|
+
- `mock`: The mock resolver.
|
631
|
+
- `mockPartially`: Registers a partial mock for a resolver, creating a new `MockMap` with this registration. In this case, the mock resolver's resoluton object will be merged with the original resolver's resolution object, overwriting certain fields.
|
632
|
+
- `original`: The original resolver.
|
633
|
+
- `mock`: The mock resolver.
|
634
|
+
- `get`: Returns a mock of a resolver or `undefined` if one is not registered.
|
635
|
+
- `original`: The original resolver.
|
598
636
|
|
599
|
-
|
637
|
+
Immutable map that registers and provides mocks. Is passed in a resolution context and used by resolvers to replace or partially replace themselves with a mock if one is defined.
|
600
638
|
|
601
639
|
### `Scope`
|
602
640
|
```ts
|
603
641
|
type Scope = Map<Resolver<any>, any>
|
604
642
|
```
|
605
643
|
|
606
|
-
A `Map` of
|
644
|
+
A `Map` of resolvers to their resolutions. Is passed in a resolution context and used by scoped resolvers to retrieve or save resolution within it.
|
607
645
|
|
608
646
|
# Contribution
|
609
647
|
|