atomic-di 0.9.1-beta.3 → 1.0.0-rc.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +468 -102
- package/dist/index.d.mts +160 -126
- package/dist/index.d.ts +160 -126
- package/dist/index.js +41 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,15 +1,64 @@
|
|
1
1
|
# atomic-di
|
2
2
|
|
3
|
-
|
3
|
+
This library implements lifetimes, scopes and mocking for pure dependency injection.
|
4
4
|
|
5
|
-
|
5
|
+
# Table of contents
|
6
6
|
|
7
|
-
|
7
|
+
- [Intro](#Intro)
|
8
|
+
- [Installation](#Installation)
|
9
|
+
- [Usage](#Usage)
|
10
|
+
- [Providers](#Providers)
|
11
|
+
- [Transient](#Transient)
|
12
|
+
- [Singleton](#Singleton)
|
13
|
+
- [Scoped](#Scoped)
|
14
|
+
- [Resolution context](#Resolution-context)
|
15
|
+
- [Mocking](#Mocking)
|
16
|
+
- [Scopes](#Scopes)
|
17
|
+
- [Bulk resolutions](#Bulk-resolutions)
|
18
|
+
- [List resolution](#List-resolution)
|
19
|
+
- [Map resolution](#Map-resolution)
|
20
|
+
- [Reference](#Reference)
|
21
|
+
- [Functions](#Functions)
|
22
|
+
- [transient](#transient)
|
23
|
+
- [singleton](#singleton)
|
24
|
+
- [scoped](#scoped)
|
25
|
+
- [createMockMap](#createMockMap)
|
26
|
+
- [createScope](#createScope)
|
27
|
+
- [Types](#Types)
|
28
|
+
- [Provider](#Provider)
|
29
|
+
- [ResolutionContext](#ResolutionContext)
|
30
|
+
- [MockMap](#MockMap)
|
31
|
+
- [Scope](#Scope)
|
8
32
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
33
|
+
# Intro
|
34
|
+
|
35
|
+
## Prerequisites
|
36
|
+
|
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
|
+
|
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 the framework at all.
|
40
|
+
|
41
|
+
This library is an attempt to provide full-featured dependency injection **without containers**.
|
42
|
+
|
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 the 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 the 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) the ability to work with a map of providers to their instances, which serves as a scope.
|
56
|
+
|
57
|
+
### Mocking
|
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 the main application build. It's enough to create a map of mock providers to providers with the same interface, and pass it to the provider call to replace the implementations in its dependencies.
|
13
62
|
|
14
63
|
# Installation
|
15
64
|
|
@@ -18,223 +67,540 @@ You can use any package manager.
|
|
18
67
|
```bash
|
19
68
|
npm add atomic-di
|
20
69
|
```
|
21
|
-
|
22
70
|
```bash
|
23
71
|
npx jsr add @ensi/di
|
24
72
|
```
|
25
73
|
|
26
74
|
# Usage
|
27
75
|
|
28
|
-
|
76
|
+
#### Table of contents
|
77
|
+
- [Providers](#Providers)
|
78
|
+
- [Transient](#Transient)
|
79
|
+
- [Singleton](#Singleton)
|
80
|
+
- [Scoped](#Scoped)
|
81
|
+
- [Resolution context](#Resolution-context)
|
82
|
+
- [Mocking](#Mocking)
|
83
|
+
- [Scopes](#Scopes)
|
84
|
+
- [Bulk resolutions](#Bulk-resolutions)
|
85
|
+
- [List resolution](#List-resolution)
|
86
|
+
- [Map resolution](#Map-resolution)
|
29
87
|
|
30
|
-
|
88
|
+
## Providers
|
31
89
|
|
32
|
-
|
90
|
+
The library provides functions that create providers with behavior typical of singletons, transients, and scopeds.
|
33
91
|
|
92
|
+
### Transient
|
93
|
+
|
94
|
+
Transient providers are created using the `transient` function:
|
34
95
|
```ts
|
35
|
-
|
96
|
+
const getThing = transient(() => createThing())
|
36
97
|
```
|
37
98
|
|
38
|
-
|
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 the other two functions, you can read about it [here](#Resolution-context).
|
100
|
+
|
101
|
+
### Singleton
|
39
102
|
|
103
|
+
Singleton providers are created using the `singleton` function:
|
40
104
|
```ts
|
41
|
-
|
105
|
+
const getA = singleton(() => createA())
|
106
|
+
const getB = transient((c) => createB(getA(c)))
|
42
107
|
```
|
43
108
|
|
44
|
-
|
109
|
+
In this case, calling `getA` will always result in the same instance, and the passed factory will only be called once:
|
110
|
+
```ts
|
111
|
+
getA() === getA() == getB().A === getB().A
|
112
|
+
```
|
45
113
|
|
46
|
-
|
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 the provider, you can read about it [here](#Resolution-context).
|
47
115
|
|
48
|
-
###
|
116
|
+
### Scoped
|
49
117
|
|
118
|
+
Scoped providers are created using the `scoped` function:
|
50
119
|
```ts
|
51
|
-
|
120
|
+
const getThing = scoped(() => createThing())
|
52
121
|
```
|
53
122
|
|
54
|
-
|
123
|
+
When calling this provider without passing a scope to the resolution context, it will create a new unique instance:
|
124
|
+
```ts
|
125
|
+
getThing() !== getThing()
|
126
|
+
```
|
127
|
+
|
128
|
+
To get resolutions within a scope, we need to pass it to the provider call in the resolution context object:
|
129
|
+
```ts
|
130
|
+
const scope = createScope()
|
131
|
+
|
132
|
+
getThing({ scope }) === getThing({ scope })
|
133
|
+
```
|
55
134
|
|
56
|
-
|
57
|
-
- `resolver`: A function that creates an instance using a [resolution context](#ResolutionContext). If the function calls other providers, the context **must** be passed to their calls.
|
135
|
+
You can read more about scopes [here](#Scopes).
|
58
136
|
|
137
|
+
## Resolution context
|
138
|
+
|
139
|
+
Each provider can accept a resolution context object. This is an object with optional `scope` and `mocks` fields that defines how the instance will be resolved.
|
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 the call chain.
|
142
|
+
|
143
|
+
#### Incorrect
|
59
144
|
```ts
|
60
|
-
const
|
61
|
-
|
62
|
-
)
|
145
|
+
const getA = singleton(() => createA())
|
146
|
+
const getB = scoped(() => createB(getA()))
|
147
|
+
const getC = scoped(() => createC(getB()))
|
63
148
|
```
|
64
149
|
|
65
|
-
|
150
|
+
In this case, the context will not propagate beyond `getC` and other providers will not know about the current scope and mocks, and `getB` will return an instance that is not related to any scopes.
|
66
151
|
|
152
|
+
#### Correct
|
67
153
|
```ts
|
68
|
-
|
154
|
+
const getA = singleton(() => createA())
|
155
|
+
const getB = scoped((c) => createB(getA(c)))
|
156
|
+
const getC = scoped((c) => createC(getB(c)))
|
69
157
|
```
|
70
158
|
|
71
|
-
|
159
|
+
In this case, `getC` will propagate the context, and `getB` and `getA` will be aware of the current mocks and scopes, resolving instances correctly.
|
160
|
+
|
161
|
+
More details on how the provider behaves depending on the passed context can be found in the sections on [mocking](#Mocking) and [scopes](#Scopes).
|
72
162
|
|
163
|
+
## Mocking
|
164
|
+
|
165
|
+
To replace implementations inside factories, we can use a mock map. To create one, we can use the `createMockMap` function:
|
73
166
|
```ts
|
74
|
-
const
|
75
|
-
|
76
|
-
)
|
167
|
+
const mockMap = createMockMap()
|
168
|
+
```
|
77
169
|
|
78
|
-
|
170
|
+
To register a mock, you need to `set` an entry with the original provider in the key and its mock in the value:
|
171
|
+
```ts
|
172
|
+
mockMap.set(getDatabase, getMockDatabase)
|
79
173
|
```
|
80
174
|
|
81
|
-
|
175
|
+
Once all mocks have been registered, this map can be passed to the provider call. If the provider finds a mock in the resolution context, it checks whether it is among the keys, and in that case returns the mock call instead of itself.
|
82
176
|
|
177
|
+
#### Direct replacement
|
83
178
|
```ts
|
84
|
-
|
179
|
+
const getA = transient(() => 1)
|
180
|
+
const getB = transient((c) => getA(c) + 1)
|
181
|
+
|
182
|
+
getB() === 2
|
85
183
|
```
|
184
|
+
```ts
|
185
|
+
const getBee = transient((c) => getA(c) + "bee")
|
186
|
+
const mocks = createMockMap().set(getB, getBee)
|
86
187
|
|
87
|
-
|
188
|
+
getB({ mocks }) === "1bee"
|
189
|
+
```
|
88
190
|
|
191
|
+
#### Direct/transitive dependency replacement
|
89
192
|
```ts
|
90
|
-
const
|
91
|
-
|
92
|
-
)
|
193
|
+
const getA = transient(() => 1)
|
194
|
+
const getB = transient((c) => getA(c) + 1)
|
195
|
+
const getC = transient((c) => getB(c) + 1)
|
93
196
|
|
94
|
-
|
197
|
+
getC() === 3
|
95
198
|
```
|
199
|
+
```ts
|
200
|
+
const getSea = transient((c) => getB(c) + "sea")
|
201
|
+
const mocks = createMockMap().set(getC, getSea)
|
96
202
|
|
97
|
-
|
203
|
+
getC({ mocks }) === "2sea"
|
204
|
+
```
|
98
205
|
|
206
|
+
## Scopes
|
207
|
+
|
208
|
+
In this library, a scope is a map of providers to their resolutions. To create one, you can use the `createScope` function:
|
99
209
|
```ts
|
100
|
-
|
210
|
+
const scope = createScope()
|
101
211
|
```
|
102
212
|
|
103
|
-
|
213
|
+
It is passed to the scoped provider call or to the call of a provider that has the scoped provider among its transitive dependencies.
|
214
|
+
- If the scoped provider finds a scope in the 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 the resolution context when calling the scoped provider, the provider will create a new instance, i.e. it will behave as a transient provider.
|
104
216
|
|
217
|
+
#### Direct scoped provider call
|
105
218
|
```ts
|
106
|
-
const
|
107
|
-
|
108
|
-
|
219
|
+
const getThing = scoped(() => createThing())
|
220
|
+
```
|
221
|
+
```ts
|
222
|
+
const thing1 = getThing()
|
223
|
+
const thing2 = getThing()
|
109
224
|
|
110
|
-
|
111
|
-
getInstance({ scope }) === getInstance({ scope })
|
225
|
+
thing1 !== thing2
|
112
226
|
```
|
227
|
+
```ts
|
228
|
+
const thing1 = getThing({ scope })
|
229
|
+
const thing2 = getThing({ scope })
|
113
230
|
|
114
|
-
|
231
|
+
thing1 === thing2
|
232
|
+
```
|
115
233
|
|
234
|
+
#### Scoped provider as direct/transitive dependency
|
116
235
|
```ts
|
117
|
-
|
236
|
+
const getScopedDependency = scoped(() => ...)
|
237
|
+
const getThing = transitive((c) =>
|
238
|
+
createThing(getScopedDependency(c))
|
239
|
+
)
|
118
240
|
```
|
241
|
+
```ts
|
242
|
+
const thing1 = getThing()
|
243
|
+
const thing2 = getThing()
|
244
|
+
|
245
|
+
thing1.scopedDependency !== thing2.scopedDependency
|
246
|
+
```
|
247
|
+
```ts
|
248
|
+
const thing1 = getThing({ scope })
|
249
|
+
const thing2 = getThing({ scope })
|
250
|
+
|
251
|
+
thing1.scopedDependency === thing2.scopedDependency
|
252
|
+
```
|
253
|
+
|
254
|
+
## Bulk resolutions
|
255
|
+
|
256
|
+
It often happens that you need to resolve instances of a large number of entities, in our case providers, with the same context. Fortunately, the library provides functions for this.
|
119
257
|
|
120
|
-
|
258
|
+
### List resolution
|
121
259
|
|
122
|
-
|
260
|
+
To resolve instances of a list of providers, you can use the `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 the list of **awaited** resolutions.
|
123
261
|
|
262
|
+
#### Only sync providers
|
124
263
|
```ts
|
125
|
-
|
264
|
+
const getA = scoped(() => createA())
|
265
|
+
const getB = scoped(() => createB())
|
266
|
+
const getC = scoped(() => createC())
|
267
|
+
|
268
|
+
const scope = createScope()
|
269
|
+
const resolutions = resolveList(
|
270
|
+
[getA, getB, getC],
|
271
|
+
{ scope }
|
272
|
+
)
|
273
|
+
```
|
274
|
+
```ts
|
275
|
+
resolutions == [
|
276
|
+
getA({ scope }),
|
277
|
+
getB({ scope }),
|
278
|
+
getC({ scope })
|
279
|
+
]
|
126
280
|
```
|
127
281
|
|
128
|
-
|
282
|
+
#### Some provider is async
|
283
|
+
```ts
|
284
|
+
const getA = scoped(() => createA())
|
285
|
+
const getB = scoped(async () => await createB())
|
286
|
+
const getC = scoped(() => createC())
|
287
|
+
|
288
|
+
const scope = createScope()
|
289
|
+
const resolutions = await resolveList(
|
290
|
+
[getA, getB, getC],
|
291
|
+
{ scope }
|
292
|
+
)
|
293
|
+
```
|
294
|
+
```ts
|
295
|
+
resolutions == [
|
296
|
+
getA({ scope }),
|
297
|
+
await getB({ scope }),
|
298
|
+
getC({ scope })
|
299
|
+
]
|
300
|
+
```
|
129
301
|
|
130
|
-
|
131
|
-
- `"singleton"` forces the resolver to create an instance once and return it in subsequent requests.
|
132
|
-
- `"scoped"` forces the resolver to take its instance from a provided [scope](#Scope) or create a new one and save it if there is none. If no scope is passed, it will create a new instance on each request.
|
302
|
+
### Map resolution
|
133
303
|
|
134
|
-
|
304
|
+
To resolve instances of a provider map, or an object with string keys and providers in the values, you can use the `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.
|
135
305
|
|
306
|
+
#### Only sync providers
|
136
307
|
```ts
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
308
|
+
const getA = scoped(() => createA())
|
309
|
+
const getB = scoped(() => createB())
|
310
|
+
const getC = scoped(() => createC())
|
311
|
+
|
312
|
+
const scope = createScope()
|
313
|
+
const resolutions = resolveMap(
|
314
|
+
{ a: getA, b: getB, c: getC },
|
315
|
+
{ scope }
|
316
|
+
)
|
317
|
+
```
|
318
|
+
```ts
|
319
|
+
resolutions == {
|
320
|
+
a: getA({ scope }),
|
321
|
+
b: getB({ scope }),
|
322
|
+
c: getC({ scope })
|
323
|
+
}
|
141
324
|
```
|
142
325
|
|
143
|
-
|
326
|
+
#### Some provider is async
|
327
|
+
```ts
|
328
|
+
const getA = scoped(() => createA())
|
329
|
+
const getB = scoped(async () => await createB())
|
330
|
+
const getC = scoped(() => createC())
|
144
331
|
|
145
|
-
|
332
|
+
const scope = createScope()
|
333
|
+
const resolutions = await resolveMap(
|
334
|
+
{ a: getA, b: getB, c: getC },
|
335
|
+
{ scope }
|
336
|
+
)
|
337
|
+
```
|
338
|
+
```ts
|
339
|
+
resolutions == {
|
340
|
+
a: getA({ scope }),
|
341
|
+
b: await getB({ scope }),
|
342
|
+
c: getC({ scope })
|
343
|
+
}
|
344
|
+
```
|
345
|
+
|
346
|
+
# Reference
|
146
347
|
|
348
|
+
#### Table of contents
|
349
|
+
- [Functions](#Functions)
|
350
|
+
- [transient](#transient)
|
351
|
+
- [singleton](#singleton)
|
352
|
+
- [scoped](#scoped)
|
353
|
+
- [createMockMap](#createMockMap)
|
354
|
+
- [createScope](#createScope)
|
355
|
+
- [Types](#Types)
|
356
|
+
- [Provider](#Provider)
|
357
|
+
- [ResolutionContext](#ResolutionContext)
|
358
|
+
- [MockMap](#MockMap)
|
359
|
+
- [Scope](#Scope)
|
360
|
+
|
361
|
+
## Functions
|
362
|
+
|
363
|
+
### `transient`
|
147
364
|
```ts
|
148
|
-
|
365
|
+
function transient<T>(resolver: Resolver<T>): Provider<T>
|
149
366
|
```
|
367
|
+
- `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
|
150
368
|
|
151
|
-
|
369
|
+
Creates a transient provider that will resolve a new instance on each call.
|
152
370
|
|
371
|
+
#### Example
|
153
372
|
```ts
|
154
|
-
const
|
155
|
-
|
373
|
+
const getThing = transient(() => createThing())
|
374
|
+
getThing() !== getThing()
|
156
375
|
```
|
157
376
|
|
158
|
-
### `
|
377
|
+
### `singleton`
|
378
|
+
```ts
|
379
|
+
function singleton<T>(resolver: Resolver<T>): Provider<T>
|
380
|
+
```
|
381
|
+
- `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
|
382
|
+
|
383
|
+
Creates a singleton provider that will resolve an instance once and return it on every call.
|
159
384
|
|
385
|
+
#### Example
|
160
386
|
```ts
|
161
|
-
|
387
|
+
const getThing = singleton(() => createThing())
|
388
|
+
getThing() === getThing()
|
162
389
|
```
|
163
390
|
|
164
|
-
|
391
|
+
### `scoped`
|
392
|
+
```ts
|
393
|
+
function scoped<T>(resolver: Resolver<T>): Provider<T>
|
394
|
+
```
|
395
|
+
- `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
|
165
396
|
|
166
|
-
|
397
|
+
Creates a scoped provider that will take its resolution from a passed scope or create a new one and save it if there is none. If no scope is passed, it will create a new instance on each call.
|
167
398
|
|
399
|
+
#### Example 1
|
168
400
|
```ts
|
169
|
-
|
170
|
-
|
171
|
-
get<T>(provider: Provider<T>): Provider<T> | undefined;
|
172
|
-
};
|
401
|
+
const getThing = scoped(() => createThing())
|
402
|
+
getThing() !== getThing()
|
173
403
|
```
|
174
404
|
|
175
|
-
|
405
|
+
#### Example 2
|
406
|
+
```ts
|
407
|
+
const getThing = scoped(() => createThing())
|
408
|
+
const scope = createScope()
|
409
|
+
getThing({ scope }) === getThing({ scope })
|
410
|
+
```
|
176
411
|
|
412
|
+
### `createMockMap`
|
177
413
|
```ts
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
414
|
+
function createMockMap(): MockMap
|
415
|
+
```
|
416
|
+
|
417
|
+
Creates a `Map` of providers to providers of the samep type which is then passed to a provider call in a resolution context object in order to replace providers with their mocks.
|
182
418
|
|
419
|
+
#### Example
|
420
|
+
```ts
|
183
421
|
const mocks = createMockMap()
|
184
|
-
|
422
|
+
.set(getConfig, getTestConfig)
|
185
423
|
|
186
|
-
|
424
|
+
getThing({ mocks })
|
187
425
|
```
|
188
426
|
|
189
|
-
### `
|
190
|
-
|
427
|
+
### `createScope`
|
191
428
|
```ts
|
192
|
-
function
|
429
|
+
function createScope(): Scope
|
193
430
|
```
|
194
431
|
|
195
|
-
Creates a
|
432
|
+
Creates a `Map` of providers to their instances that is then passed to a provider call in a resolution context object to resolve instances of scoped providers within it.
|
196
433
|
|
197
|
-
|
434
|
+
#### Example
|
435
|
+
```ts
|
436
|
+
const requestScope = createScope()
|
198
437
|
|
199
|
-
|
438
|
+
app.use(() => {
|
439
|
+
const db = getDb({ scope: requestScope })
|
440
|
+
// ...
|
441
|
+
})
|
442
|
+
```
|
200
443
|
|
444
|
+
### `resolveList`
|
201
445
|
```ts
|
202
446
|
function resolveList<const Providers extends ProviderList>(
|
203
447
|
providers: Providers,
|
204
|
-
context?: ResolutionContext
|
205
|
-
):
|
448
|
+
context?: ResolutionContext
|
449
|
+
): AwaitValuesInCollection<
|
450
|
+
InferProviderCollectionResolutions<Provider>
|
451
|
+
>
|
206
452
|
```
|
453
|
+
- `providers`: A list of providers.
|
454
|
+
- `context?`: A resolution context.
|
207
455
|
|
208
|
-
|
209
|
-
- `context`: A resolution context.
|
456
|
+
Calls every provider 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 resolution.
|
210
457
|
|
211
|
-
|
458
|
+
#### Example 1
|
459
|
+
Only sync providers:
|
460
|
+
```ts
|
461
|
+
const getA = scoped(() => createA())
|
462
|
+
const getB = scoped(() => createB())
|
463
|
+
const getC = scoped(() => createC())
|
464
|
+
|
465
|
+
const scope = createScope()
|
466
|
+
const resolutions = resolveList(
|
467
|
+
[getA, getB, getC],
|
468
|
+
{ scope }
|
469
|
+
)
|
470
|
+
```
|
471
|
+
```ts
|
472
|
+
resolutions == [
|
473
|
+
getA({ scope }),
|
474
|
+
getB({ scope }),
|
475
|
+
getC({ scope })
|
476
|
+
]
|
477
|
+
```
|
212
478
|
|
479
|
+
#### Example 2
|
480
|
+
Some provider is async:
|
481
|
+
```ts
|
482
|
+
const getA = scoped(() => createA())
|
483
|
+
const getB = scoped(async () => await createB())
|
484
|
+
const getC = scoped(() => createC())
|
485
|
+
|
486
|
+
const scope = createScope()
|
487
|
+
const resolutions = await resolveList(
|
488
|
+
[getA, getB, getC],
|
489
|
+
{ scope }
|
490
|
+
)
|
491
|
+
```
|
213
492
|
```ts
|
214
|
-
|
493
|
+
resolutions == [
|
494
|
+
getA({ scope }),
|
495
|
+
await getB({ scope }),
|
496
|
+
getC({ scope })
|
497
|
+
]
|
215
498
|
```
|
216
499
|
|
217
500
|
### `resolveMap`
|
218
|
-
|
219
501
|
```ts
|
220
502
|
function resolveMap<const Providers extends ProviderRecord>(
|
221
503
|
providers: Providers,
|
222
|
-
context?: ResolutionContext
|
223
|
-
):
|
504
|
+
context?: ResolutionContext
|
505
|
+
): AwaitValuesInCollection<
|
506
|
+
InferProviderCollectionResolutions<Provider>
|
507
|
+
>
|
224
508
|
```
|
509
|
+
- `providers`: A map of providers.
|
510
|
+
- `context?`: A resolution context.
|
225
511
|
|
226
|
-
|
227
|
-
- `context`: A resolution context.
|
512
|
+
Calls every provider 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 of awaited resolutions if there's at least one `Promise` in the resolutions.
|
228
513
|
|
229
|
-
|
514
|
+
#### Example 1
|
515
|
+
Only sync providers:
|
516
|
+
```ts
|
517
|
+
const getA = scoped(() => createA())
|
518
|
+
const getB = scoped(() => createB())
|
519
|
+
const getC = scoped(() => createC())
|
520
|
+
|
521
|
+
const scope = createScope()
|
522
|
+
const resolutions = resolveMap(
|
523
|
+
{ a: getA, b: getB, c: getC },
|
524
|
+
{ scope }
|
525
|
+
)
|
526
|
+
```
|
527
|
+
```ts
|
528
|
+
resolutions == {
|
529
|
+
a: getA({ scope }),
|
530
|
+
b: getB({ scope }),
|
531
|
+
c: getC({ scope })
|
532
|
+
}
|
533
|
+
```
|
230
534
|
|
535
|
+
#### Example 2
|
536
|
+
Some provider is async:
|
231
537
|
```ts
|
232
|
-
const
|
538
|
+
const getA = scoped(() => createA())
|
539
|
+
const getB = scoped(async () => await createB())
|
540
|
+
const getC = scoped(() => createC())
|
541
|
+
|
542
|
+
const scope = createScope()
|
543
|
+
const resolutions = await resolveMap(
|
233
544
|
{ a: getA, b: getB, c: getC },
|
234
|
-
{ scope
|
235
|
-
)
|
545
|
+
{ scope }
|
546
|
+
)
|
547
|
+
```
|
548
|
+
```ts
|
549
|
+
resolutions == {
|
550
|
+
a: getA({ scope }),
|
551
|
+
b: await getB({ scope }),
|
552
|
+
c: getC({ scope })
|
553
|
+
}
|
554
|
+
```
|
555
|
+
|
556
|
+
## Types
|
557
|
+
|
558
|
+
### `Resolver`
|
559
|
+
```ts
|
560
|
+
type Resolver<T> = (context?: ResolutionContext) => T
|
561
|
+
```
|
562
|
+
|
563
|
+
A function that returns a value of a particular type with a resolution context being passed to it.
|
564
|
+
|
565
|
+
### `Provider`
|
566
|
+
```ts
|
567
|
+
type Provider<T> = (context?: ResolutionContext) => T
|
568
|
+
```
|
569
|
+
|
570
|
+
A function that resolves an instance or a `Promise` of a particular type based on a resolution context passed to it.
|
571
|
+
|
572
|
+
### `ResolutionContext`
|
573
|
+
```ts
|
574
|
+
type ResolutionContext = {
|
575
|
+
scope?: Scope;
|
576
|
+
mocks?: MockMap;
|
577
|
+
}
|
236
578
|
```
|
237
579
|
|
580
|
+
A context used by providers to resolve instances based on current scope and mocks.
|
581
|
+
|
582
|
+
### `MockMap`
|
583
|
+
```ts
|
584
|
+
type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, "set" | "get"> & {
|
585
|
+
set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;
|
586
|
+
get<T>(provider: Resolver<T>): Resolver<T> | undefined;
|
587
|
+
};
|
588
|
+
```
|
589
|
+
- `set`: Sets a mock for a provider.
|
590
|
+
- `provider`: The original provider.
|
591
|
+
- `mock`: The mock provider.
|
592
|
+
- `get`: Retrieves a mock of a provider. Returns undefined if there's none.
|
593
|
+
- `provider`: The provider.
|
594
|
+
|
595
|
+
A `Map` of providers to providers of the same type which is then passed to a provider call in a resolution context object in order to replace providers with their mocks.
|
596
|
+
|
597
|
+
### `Scope`
|
598
|
+
```ts
|
599
|
+
type Scope = Map<Resolver<any>, any>
|
600
|
+
```
|
601
|
+
|
602
|
+
A `Map` of providers to their instances that is then passed to a provider call in a resolution context object to resolve instances of scoped providers within it.
|
603
|
+
|
238
604
|
# Contribution
|
239
605
|
|
240
606
|
This is free and open source project licensed under the [MIT License](LICENSE). You could help its development by contributing via [pull requests](https://github.com/ncor/atomic-di/fork) or [submitting an issue](https://github.com/ncor/atomic-di/issues).
|