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 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
- - [Providers](#Providers)
10
+ - [Creating resolvers](#Creating-resolvers)
11
11
  - [Transient](#Transient)
12
12
  - [Singleton](#Singleton)
13
13
  - [Scoped](#Scoped)
14
- - [Resolution context](#Resolution-context)
14
+ - [Propagating a context](#Propagating-a-context)
15
15
  - [Mocking](#Mocking)
16
- - [Scoping](#Scoping)
17
- - [Bulk resolutions](#Bulk-resolutions)
18
- - [List resolution](#List-resolution)
19
- - [Map resolution](#Map-resolution)
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
- - [Provider](#Provider)
29
- - [ResolutionContext](#ResolutionContext)
30
- - [MockMap](#MockMap)
31
- - [Scope](#Scope)
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 classic pure dependency injection, you should definitely consider other solutions, or not use a framework at all.
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
- - [Providers](#Providers)
62
+ - [Creating resolvers](#Creating-resolvers)
78
63
  - [Transient](#Transient)
79
64
  - [Singleton](#Singleton)
80
65
  - [Scoped](#Scoped)
81
- - [Resolution context](#Resolution-context)
66
+ - [Propagating a context](#Propagating-a-context)
82
67
  - [Mocking](#Mocking)
83
- - [Scoping](#Scoping)
84
- - [Bulk resolutions](#Bulk-resolutions)
85
- - [List resolution](#List-resolution)
86
- - [Map resolution](#Map-resolution)
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
- ## Providers
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
- A provider is a factory of instances with additional functionality. The library provides functions that create providers with behavior typical of singletons, transients, and scopes.
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
- Transient providers are created using `transient` function:
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 getThing = transient(() => createThing())
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
- Singleton providers are created using `singleton` function:
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 getA = singleton(() => createA())
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
- getA() === getA() == getB().A === getB().A
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
- Scoped providers are created using `scoped` function:
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 getThing = scoped(() => createThing())
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
- getThing() === getThing()
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
- const scope = createScope()
131
-
132
- getThing({ scope }) === getThing({ scope })
113
+ getRandom({ scope: myScope }) === getRandom({ scope: myScope })
114
+ ```
115
+ ```ts
116
+ getRandom() !== getRandom({ scope: myScope })
133
117
  ```
134
118
 
135
- You can read more about scopes [here](#Scoping).
119
+ A detailed explanation of the scope mechanism and its use is described in [this](#Scopes) section.
136
120
 
137
- ## Resolution context
121
+ ## Propagating a context
138
122
 
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.
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 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.
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 getA = singleton(() => createA())
146
- const getB = scoped(() => createB(getA()))
147
- const getC = scoped(() => createC(getB()))
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 getA = singleton(() => createA())
155
- const getB = scoped((c) => createB(getA(c)))
156
- const getC = scoped((c) => createC(getB(c)))
136
+ const getDependency = transient(() => createDependency())
137
+ const getEntity = transient((c) =>
138
+ createEntity(getDependency(c)) // context is propagated
139
+ )
157
140
  ```
158
141
 
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.
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
- More details on how provider behaves depending on a passed context can be found in sections about [mocking](#Mocking) and [scoping](#Scoping).
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
- ## Mocking
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 replace implementations inside factories, we can use a mock map. To create one, we can use `createMockMap` function:
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 mockMap = createMockMap()
157
+ const mocks = createMockMap()
158
+ .mock(getDatabase, getDatabaseMock)
159
+ .mock(getLogger, getLoggerMock)
160
+ // ...
168
161
  ```
169
162
 
170
- To register a mock, you need to `set` an entry with an original provider in a key and its mock in a value:
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
- mockMap.set(getDatabase, getMockDatabase)
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
- 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.
172
+ ### Resolving with mocks
176
173
 
177
- #### Direct replacement
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
- const getA = transient(() => 1)
180
- const getB = transient((c) => getA(c) + 1)
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
- getB() === 2
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 getBee = transient((c) => getA(c) + "bee")
186
- const mocks = createMockMap().set(getB, getBee)
189
+ const getDependencyMock = transitive(() => "dependencyMock")
187
190
 
188
- getB({ mocks }) === "1bee"
191
+ const mocks = createMockMap()
192
+ .mock(getDependency, getDependencyMock
189
193
  ```
190
-
191
- #### Direct/transitive dependency replacement
192
194
  ```ts
193
- const getA = transient(() => 1)
194
- const getB = transient((c) => getA(c) + 1)
195
- const getC = transient((c) => getB(c) + 1)
195
+ getEntity({ mocks }) == {
196
+ dependency: "dependencyMock"
197
+ }
198
+ ```
196
199
 
197
- getC() === 3
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 getSea = transient((c) => getB(c) + "sea")
201
- const mocks = createMockMap().set(getC, getSea)
211
+ const getDependencyMock = transitive(() => ({
212
+ value: "dependencyMock"
213
+ }))
202
214
 
203
- getC({ mocks }) === "2sea"
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
- ## Scoping
227
+ ## Scopes
207
228
 
208
- In this library, a scope is a map of providers to their resolutions. To create one, you can use `createScope` function:
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
- 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 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
- #### Direct scoped provider call
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 getThing = scoped(() => createThing())
252
+ const getEntity = scoped(() => createEntity())
220
253
  ```
221
254
  ```ts
222
- const thing1 = getThing()
223
- const thing2 = getThing()
255
+ const scope = createScope()
224
256
 
225
- thing1 === thing2
257
+ const scopeEntity = getEntity({ scope })
226
258
  ```
227
259
  ```ts
228
- const thing1 = getThing({ scope })
229
- const thing2 = getThing({ scope })
230
- const thingFallback = getThing()
231
-
232
- thing1 === thing2 !== thingFallback
260
+ scope.get(getEntity) === scopeEntity
233
261
  ```
234
262
 
235
- #### Scoped provider as direct/transitive dependency
263
+ #### Indirect resolution
236
264
  ```ts
237
- const getScopedDependency = scoped(() => ...)
238
- const getThing = transitive((c) =>
239
- createThing(getScopedDependency(c))
240
- )
265
+ const getDependency = scoped(() => createDependency())
266
+ const getEntity = transient((c) => ({
267
+ dependency: getDependency(c)
268
+ }))
241
269
  ```
242
270
  ```ts
243
- const thing1 = getThing()
244
- const thing2 = getThing()
271
+ const scope = createScope()
245
272
 
246
- thing1.scopedDependency === thing2.scopedDependency
273
+ const entity = getEntity({ scope })
247
274
  ```
248
275
  ```ts
249
- const thing1 = getThing({ scope })
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
- ## Bulk resolutions
279
+ ## Resolving collections
257
280
 
258
- 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.
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
- ### List resolution
283
+ ### Resolving a list
261
284
 
262
- 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.
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
- #### Some provider is async
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 () => await createB())
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
- ### Map resolution
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
- #### Only sync providers
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
- #### Some provider is async
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 () => await createB())
356
+ const getB = scoped(async () => createB())
332
357
  const getC = scoped(() => createC())
333
-
358
+ ```
359
+ ```ts
334
360
  const scope = createScope()
335
- const resolutions = await resolveMap(
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
- - [Provider](#Provider)
359
- - [ResolutionContext](#ResolutionContext)
360
- - [MockMap](#MockMap)
361
- - [Scope](#Scope)
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>(resolver: Resolver<T>): Provider<T>
397
+ function transient<T>(fn: ResolverFn<T>): Resolver<T>
368
398
  ```
369
- - `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
399
+ - `resolver`: A function that takes a resolution context and returns a value of some type.
370
400
 
371
- Creates a transient provider that will resolve a new instance on each call.
401
+ Creates a resolver that creates a new resolution on each call.
372
402
 
373
403
  #### Example
374
404
  ```ts
375
- const getThing = transient(() => createThing())
376
- getThing() !== getThing()
405
+ const getEntity = transient(() => createEntity())
406
+ getEntity() !== getEntity()
377
407
  ```
378
408
 
379
409
  ### `singleton`
380
410
  ```ts
381
- function singleton<T>(resolver: Resolver<T>): Provider<T>
411
+ function singleton<T>(resolver: ResolverFn<T>): Resolver<T>
382
412
  ```
383
- - `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
413
+ - `resolver`: A function that takes a resolution context and returns a value of some type.
384
414
 
385
- Creates a singleton provider that will resolve an instance once and return it on every call.
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 getThing = singleton(() => createThing())
390
- getThing() === getThing()
419
+ const getEntity = singleton(() => createEntity())
420
+ getEntity() === getEntity()
391
421
  ```
392
422
 
393
423
  ### `scoped`
394
424
  ```ts
395
- function scoped<T>(resolver: Resolver<T>): Provider<T>
425
+ function scoped<T>(resolver: ResolverFn<T>): Resolver<T>
396
426
  ```
397
- - `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
427
+ - `resolver`: A function that takes a resolution context and returns a value of some type.
398
428
 
399
- 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 act as a singleton
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 getThing = scoped(() => createThing())
404
- getThing() === getThing()
433
+ const getEntity = scoped(() => createEntity())
434
+ getEntity() === getEntity()
405
435
  ```
406
436
 
407
437
  #### Example 2
408
438
  ```ts
409
- const getThing = scoped(() => createThing())
439
+ const getEntity = scoped(() => createEntity())
410
440
  const scope = createScope()
411
- getThing({ scope }) === getThing({ scope }) !== getThing()
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 `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.
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
- .set(getConfig, getTestConfig)
454
+ .mock(getDependency, getDependencyMock)
455
+ .mockPartially(
456
+ getOtherDependency,
457
+ transient(() => ({ someField: "mock" }))
458
+ )
425
459
 
426
- getThing({ mocks })
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 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.
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 Providers extends ProviderList>(
449
- providers: Providers,
481
+ function resolveList<const Resolvers extends ResolverList>(
482
+ resolvers: Resolvers,
450
483
  context?: ResolutionContext
451
484
  ): AwaitValuesInCollection<
452
- InferProviderCollectionResolutions<Provider>
485
+ InferResolverCollectionResolutions<Resolvers>
453
486
  >
454
487
  ```
455
- - `providers`: A list of providers.
488
+ - `resolvers`: A list of resolvers.
456
489
  - `context?`: A resolution context.
457
490
 
458
- 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.
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 providers:
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 provider is async:
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 Providers extends ProviderRecord>(
505
- providers: Providers,
537
+ function resolveMap<const Resolvers extends ResolverRecord>(
538
+ resolvers: Resolvers,
506
539
  context?: ResolutionContext
507
540
  ): AwaitValuesInCollection<
508
- InferProviderCollectionResolutions<Provider>
541
+ InferResolverCollectionResolutions<Resolvers>
509
542
  >
510
543
  ```
511
- - `providers`: A map of providers.
544
+ - `resolvers`: A map of resolvers.
512
545
  - `context?`: A resolution context.
513
546
 
514
- 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.
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 providers:
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 provider is async:
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
- ### `Resolver`
593
+ ### `ResolverFn`
561
594
  ```ts
562
- type Resolver<T> = (context?: ResolutionContext) => T
595
+ type ResolverFn<T> = (context?: ResolutionContext) => T
563
596
  ```
564
597
 
565
- A function that returns a value of a particular type with a resolution context being passed to it.
598
+ A function that takes a resolution context and returns a value of some type.
566
599
 
567
- ### `Provider`
600
+ ### `Resolver`
568
601
  ```ts
569
- type Provider<T> = Resolver<T> & {
570
- __brand: "provider"
571
- }
602
+ type Resolver<T> = (context?: ResolutionContext) => T
572
603
  ```
573
604
 
574
- A function that resolves an instance or a `Promise` of a particular type based on a resolution context passed to it.
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 providers to resolve instances based on current scope and mocks.
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 = Omit<Map<Resolver<any>, Resolver<any>>, "set" | "get"> & {
589
- set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;
590
- get<T>(provider: Resolver<T>): Resolver<T> | undefined;
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
- - `set`: Sets a mock for a provider.
594
- - `provider`: The original provider.
595
- - `mock`: The mock provider.
596
- - `get`: Retrieves a mock of a provider. Returns undefined if there's none.
597
- - `provider`: The provider.
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
- 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.
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 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.
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