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 CHANGED
@@ -1,15 +1,64 @@
1
1
  # atomic-di
2
2
 
3
- > **This is not an IoC container or a service locator.**
3
+ This library implements lifetimes, scopes and mocking for pure dependency injection.
4
4
 
5
- This library provides a set of tools that implement missing features such as lifetimes, scopes and mocking in a pure factory-based dependency injection.
5
+ # Table of contents
6
6
 
7
- ### Key features
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
- - **Small & fast.** The usage is based on the use of a thin, performant abstraction that wraps factories, adding a couple of operations needed for resolution in a dynamic context.
10
- - **Transparent.** All the logic for creating instances is written by the developer himself.
11
- - **Non-invasive.** Does not require adding decorators, registrations or any additional logic to the business logic of the application.
12
- - **Atomic.** There's no global container. The creation of instances occurs in factories that can be shared among themselves.
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
- Not written yet.
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
- # API
88
+ ## Providers
31
89
 
32
- ## `Provider`
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
- type Provider<T> = (context?: ResolutionContext) => T;
96
+ const getThing = transient(() => createThing())
36
97
  ```
37
98
 
38
- A function wrapper around the [resolver](#Resolver)(factory) for contextual dependency resolution. Resolves an instance by calling a resolver with an **optional** [resolution context](#ResolutionContext) that will be propagated throughout a dependency tree. Can act as a transient, singleton or scoped, which is [determined by the lifetime](#Lifetime) at creation.
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
- provider({ scope, mocks });
105
+ const getA = singleton(() => createA())
106
+ const getB = transient((c) => createB(getA(c)))
42
107
  ```
43
108
 
44
- When passing a [scope](#Scope) it will try to get an instance from it or create a new one and put it there.
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
- When passing [mocks](#MockMap), it will try to get its own mock version and if there is one, it will use it instead of itself.
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
- ### `provide`
116
+ ### Scoped
49
117
 
118
+ Scoped providers are created using the `scoped` function:
50
119
  ```ts
51
- function provide<T>(lifetime: Lifetime, resolver: Resolver<T>): Provider<T>;
120
+ const getThing = scoped(() => createThing())
52
121
  ```
53
122
 
54
- Creates a provider instance.
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
- - `lifetime`: A resolution [lifetime](#Lifetime).
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 getInstance = provide("transient", (context) =>
61
- createInstance(getOtherInstance(context)),
62
- );
145
+ const getA = singleton(() => createA())
146
+ const getB = scoped(() => createB(getA()))
147
+ const getC = scoped(() => createC(getB()))
63
148
  ```
64
149
 
65
- ### `transient`
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
- function transient<T>(resolver: Resolver<T>): Provider<T>;
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
- An alias for [provide](#provide) with `"transient"` lifetime. Creates a [transient](#Lifetime) provider instance.
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 getInstance = transient((context) =>
75
- createInstance(...)
76
- )
167
+ const mockMap = createMockMap()
168
+ ```
77
169
 
78
- getInstance() !== getInstance()
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
- ### `singleton`
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
- function singleton<T>(resolver: Resolver<T>): Provider<T>;
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
- An alias for [provide](#provide) with `"singleton"` lifetime. Creates a [singleton](#Lifetime) provider instance.
188
+ getB({ mocks }) === "1bee"
189
+ ```
88
190
 
191
+ #### Direct/transitive dependency replacement
89
192
  ```ts
90
- const getInstance = singleton((context) =>
91
- createInstance(...)
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
- getInstance() === getInstance()
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
- ### `scoped`
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
- function scoped<T>(resolver: Resolver<T>): Provider<T>;
210
+ const scope = createScope()
101
211
  ```
102
212
 
103
- An alias for [provide](#provide) with `"scoped"` lifetime. Creates a [scoped](#Lifetime) provider instance.
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 getInstance = scoped((context) =>
107
- createInstance(...)
108
- )
219
+ const getThing = scoped(() => createThing())
220
+ ```
221
+ ```ts
222
+ const thing1 = getThing()
223
+ const thing2 = getThing()
109
224
 
110
- getInstance() !== getInstance()
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
- ## `Resolver`
231
+ thing1 === thing2
232
+ ```
115
233
 
234
+ #### Scoped provider as direct/transitive dependency
116
235
  ```ts
117
- type Resolver<T> = (context?: ResolutionContext) => T;
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
- A function that creates an instance using a [resolution context](#ResolutionContext).
258
+ ### List resolution
121
259
 
122
- ## `Lifetime`
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
- type Lifetime = "transient" | "singleton" | "scoped";
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
- A resolution lifetime. Passed when [creating a provider](#provide) to determine its behaviour.
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
- - `"transient"` doesn't provide any modifications to a resolver behaviour, so the resolver will create a new instance on each request.
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
- ## `ResolutionContext`
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
- type ResolutionContext = {
138
- scope?: Scope;
139
- mocks?: MockMap;
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
- An object that holds information about a [scope](#Scope) and provider [mocks](#MockMap). Passed to the [provider call](#Provider) to resolve scope instances and mock providers.
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
- ## `Scope`
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
- type Scope = Map<Provider<any>, any>;
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
- A `Map` of [providers](#Provider) to their instances. Passed to a provider call in a resolution context object to resolve instances of scoped providers within it.
369
+ Creates a transient provider that will resolve a new instance on each call.
152
370
 
371
+ #### Example
153
372
  ```ts
154
- const scope = createScope();
155
- provider({ scope });
373
+ const getThing = transient(() => createThing())
374
+ getThing() !== getThing()
156
375
  ```
157
376
 
158
- ### `createScope`
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
- function createScope(): Scope;
387
+ const getThing = singleton(() => createThing())
388
+ getThing() === getThing()
162
389
  ```
163
390
 
164
- Creates a scope instance.
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
- ## `MockMap`
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
- type MockMap = Omit<Map<Provider<any>, Provider<any>>, "set" | "get"> & {
170
- set<T>(provider: Provider<T>, mock: Provider<T>): MockMap;
171
- get<T>(provider: Provider<T>): Provider<T> | undefined;
172
- };
401
+ const getThing = scoped(() => createThing())
402
+ getThing() !== getThing()
173
403
  ```
174
404
 
175
- A `Map` of [providers](#Provider) to providers of the same type. [Lifetime](#Lifetime) is not a part of `Provider` type, so you can use a different one if necessary. Passed to a provider call in a [resolution context](#ResolutionContext) object in order to replace providers with their mocks.
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
- const otherProvider =
179
- transitive(() => ...)
180
- const otherProviderMock: typeof otherProvider =
181
- scoped(() => ...)
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
- mocks.set(otherProvider, otherProviderMock)
422
+ .set(getConfig, getTestConfig)
185
423
 
186
- provider({ mocks })
424
+ getThing({ mocks })
187
425
  ```
188
426
 
189
- ### `createMockMap`
190
-
427
+ ### `createScope`
191
428
  ```ts
192
- function createMockMap(): MockMap;
429
+ function createScope(): Scope
193
430
  ```
194
431
 
195
- Creates a mock map instance.
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
- ## Bulk resolutions
434
+ #### Example
435
+ ```ts
436
+ const requestScope = createScope()
198
437
 
199
- ### `resolveList`
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
- ): AwaitAllValuesInCollection<InferProviderCollectionResolutions<Providers>>;
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
- - `providers`: A list of providers.
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
- 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 resolutions.
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
- const resolutions = resolveList([getA, getB, getC], { scope, mocks });
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
- ): AwaitAllValuesInCollection<InferProviderCollectionResolutions<Providers>>;
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
- - `providers`: A map of providers.
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
- Calsl 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.
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 resolutionMap = resolveMap(
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, mocks },
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).