atomic-di 1.1.2 → 2.0.0

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,246 @@ 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
- The library provides functions that create providers with behavior typical of singletons, transients, and scopeds.
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
164
149
 
165
- To replace implementations inside factories, we can use a mock map. To create one, we can use `createMockMap` function:
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.
166
151
  ```ts
167
- const mockMap = createMockMap()
152
+ const mocks = createMockMap()
168
153
  ```
169
154
 
170
- To register a mock, you need to `set` an entry with an original provider in a key and its mock in a value:
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.
171
156
  ```ts
172
- mockMap.set(getDatabase, getMockDatabase)
157
+ const mocks = createMockMap()
158
+ .mock(getDatabase, getDatabaseMock)
159
+ .mock(getLogger, getLoggerMock)
160
+ // ...
173
161
  ```
174
162
 
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.
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.
164
+ ```ts
165
+ const getDatabaseMock = singleton(() => ({
166
+ execute: (q) => console.log("db: executing", q)
167
+ }))
168
+ const mocks = createMockMap()
169
+ .mockPartially(getDatabase, getDatabaseMock)
170
+ ```
176
171
 
177
- #### Direct replacement
172
+ ### Resolving with mocks
173
+
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
228
+
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.
207
232
 
208
- In this library, a scope is a map of providers to their resolutions. To create one, you can use `createScope` function:
233
+ ### Creating a scope
234
+
235
+ There are two ways to create a scope:
236
+ - Create a map with the correct type manually.
237
+ ```ts
238
+ const scope = new Map<Resolver<any>, any>()
239
+ ```
240
+ - Use `createScope` function.
209
241
  ```ts
210
242
  const scope = createScope()
211
243
  ```
212
244
 
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.
245
+ It is important to note that a scope must be created **once** during an entire lifecycle of a program.
246
+ ```ts
247
+ const requestScope = createScope()
216
248
 
217
- #### Direct scoped provider call
249
+ app.use(() => {
250
+ // use the scope here
251
+ })
252
+ ```
253
+
254
+ ### Resolving with a scope
255
+
256
+ 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.
257
+
258
+ #### Direct resolution
218
259
  ```ts
219
- const getThing = scoped(() => createThing())
260
+ const getEntity = scoped(() => createEntity())
220
261
  ```
221
262
  ```ts
222
- const thing1 = getThing()
223
- const thing2 = getThing()
263
+ const scope = createScope()
224
264
 
225
- thing1 === thing2
265
+ const scopeEntity = getEntity({ scope })
226
266
  ```
227
267
  ```ts
228
- const thing1 = getThing({ scope })
229
- const thing2 = getThing({ scope })
230
- const thingFallback = getThing()
231
-
232
- thing1 === thing2 !== thingFallback
268
+ scope.get(getEntity) === scopeEntity
233
269
  ```
234
270
 
235
- #### Scoped provider as direct/transitive dependency
271
+ #### Indirect resolution
236
272
  ```ts
237
- const getScopedDependency = scoped(() => ...)
238
- const getThing = transitive((c) =>
239
- createThing(getScopedDependency(c))
240
- )
273
+ const getDependency = scoped(() => createDependency())
274
+ const getEntity = transient((c) => ({
275
+ dependency: getDependency(c)
276
+ }))
241
277
  ```
242
278
  ```ts
243
- const thing1 = getThing()
244
- const thing2 = getThing()
279
+ const scope = createScope()
245
280
 
246
- thing1.scopedDependency === thing2.scopedDependency
281
+ const entity = getEntity({ scope })
247
282
  ```
248
283
  ```ts
249
- const thing1 = getThing({ scope })
250
- const thing2 = getThing({ scope })
251
- const thingWithFallback = getThing()
252
-
253
- thing1.scopedDependency === thing2.scopedDependency !== thingWithFallback
284
+ scope.get(getDependency) === entity.dependency
254
285
  ```
255
286
 
256
- ## Bulk resolutions
257
-
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.
287
+ ## Resolving collections
259
288
 
260
- ### List resolution
289
+ 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.
261
290
 
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.
291
+ ### Resolving a list
263
292
 
264
- #### Only sync providers
293
+ If you need to get a list of resolutions of different resolvers, you can use `resolveList` function.
265
294
  ```ts
266
295
  const getA = scoped(() => createA())
267
296
  const getB = scoped(() => createB())
268
297
  const getC = scoped(() => createC())
269
-
298
+ ```
299
+ ```ts
270
300
  const scope = createScope()
301
+
271
302
  const resolutions = resolveList(
272
303
  [getA, getB, getC],
273
304
  { scope }
@@ -275,19 +306,21 @@ const resolutions = resolveList(
275
306
  ```
276
307
  ```ts
277
308
  resolutions == [
278
- getA({ scope }),
279
- getB({ scope }),
309
+ getA({ scope })
310
+ getB({ scope })
280
311
  getC({ scope })
281
312
  ]
282
313
  ```
283
314
 
284
- #### Some provider is async
315
+ If one of passed resolvers returns a promise, the function will return a `Promise` of a list of awaited resolutions.
285
316
  ```ts
286
317
  const getA = scoped(() => createA())
287
- const getB = scoped(async () => await createB())
318
+ const getB = scoped(async () => createB())
288
319
  const getC = scoped(() => createC())
289
-
320
+ ```
321
+ ```ts
290
322
  const scope = createScope()
323
+
291
324
  const resolutions = await resolveList(
292
325
  [getA, getB, getC],
293
326
  { scope }
@@ -295,23 +328,23 @@ const resolutions = await resolveList(
295
328
  ```
296
329
  ```ts
297
330
  resolutions == [
298
- getA({ scope }),
299
- await getB({ scope }),
331
+ getA({ scope })
332
+ await getB({ scope })
300
333
  getC({ scope })
301
334
  ]
302
335
  ```
303
336
 
304
- ### Map resolution
337
+ ### Resolving a map
305
338
 
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.
307
-
308
- #### Only sync providers
339
+ If you need to get a map of resolutions of different resolvers, you can use `resolveMap` function.
309
340
  ```ts
310
341
  const getA = scoped(() => createA())
311
342
  const getB = scoped(() => createB())
312
343
  const getC = scoped(() => createC())
313
-
344
+ ```
345
+ ```ts
314
346
  const scope = createScope()
347
+
315
348
  const resolutions = resolveMap(
316
349
  { a: getA, b: getB, c: getC },
317
350
  { scope }
@@ -319,28 +352,30 @@ const resolutions = resolveMap(
319
352
  ```
320
353
  ```ts
321
354
  resolutions == {
322
- a: getA({ scope }),
323
- b: getB({ scope }),
355
+ a: getA({ scope })
356
+ b: getB({ scope })
324
357
  c: getC({ scope })
325
358
  }
326
359
  ```
327
360
 
328
- #### Some provider is async
361
+ If one of passed resolvers returns `Promise`, the function will return a `Promise` of a map of awaited resolutions.
329
362
  ```ts
330
363
  const getA = scoped(() => createA())
331
- const getB = scoped(async () => await createB())
364
+ const getB = scoped(async () => createB())
332
365
  const getC = scoped(() => createC())
333
-
366
+ ```
367
+ ```ts
334
368
  const scope = createScope()
335
- const resolutions = await resolveMap(
369
+
370
+ const resolutions = await resolveList(
336
371
  { a: getA, b: getB, c: getC },
337
372
  { scope }
338
373
  )
339
374
  ```
340
375
  ```ts
341
376
  resolutions == {
342
- a: getA({ scope }),
343
- b: await getB({ scope }),
377
+ a: getA({ scope })
378
+ b: await getB({ scope })
344
379
  c: getC({ scope })
345
380
  }
346
381
  ```
@@ -349,66 +384,69 @@ resolutions == {
349
384
 
350
385
  #### Table of contents
351
386
  - [Functions](#Functions)
352
- - [transient](#transient)
353
- - [singleton](#singleton)
354
- - [scoped](#scoped)
355
- - [createMockMap](#createMockMap)
356
- - [createScope](#createScope)
387
+ - [`transient`](#transient)
388
+ - [`singleton`](#singleton)
389
+ - [`scoped`](#scoped)
390
+ - [`createMockMap`](#createMockMap)
391
+ - [`createScope`](#createScope)
392
+ - [`resolveList`](#resolveList)
393
+ - [`resolveMap`](#resolveMap)
357
394
  - [Types](#Types)
358
- - [Provider](#Provider)
359
- - [ResolutionContext](#ResolutionContext)
360
- - [MockMap](#MockMap)
361
- - [Scope](#Scope)
395
+ - [`ResolverFn`](#ResolverFn)
396
+ - [`Resolver`](#Resolver)
397
+ - [`ResolutionContext`](#ResolutionContext)
398
+ - [`MockMap`](#MockMap)
399
+ - [`Scope`](#Scope)
362
400
 
363
401
  ## Functions
364
402
 
365
403
  ### `transient`
366
404
  ```ts
367
- function transient<T>(resolver: Resolver<T>): Provider<T>
405
+ function transient<T>(fn: ResolverFn<T>): Resolver<T>
368
406
  ```
369
- - `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
407
+ - `resolver`: A function that takes a resolution context and returns a value of some type.
370
408
 
371
- Creates a transient provider that will resolve a new instance on each call.
409
+ Creates a resolver that creates a new resolution on each call.
372
410
 
373
411
  #### Example
374
412
  ```ts
375
- const getThing = transient(() => createThing())
376
- getThing() !== getThing()
413
+ const getEntity = transient(() => createEntity())
414
+ getEntity() !== getEntity()
377
415
  ```
378
416
 
379
417
  ### `singleton`
380
418
  ```ts
381
- function singleton<T>(resolver: Resolver<T>): Provider<T>
419
+ function singleton<T>(resolver: ResolverFn<T>): Resolver<T>
382
420
  ```
383
- - `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
421
+ - `resolver`: A function that takes a resolution context and returns a value of some type.
384
422
 
385
- Creates a singleton provider that will resolve an instance once and return it on every call.
423
+ Creates a resolver that creates a resolution once and return it on each call.
386
424
 
387
425
  #### Example
388
426
  ```ts
389
- const getThing = singleton(() => createThing())
390
- getThing() === getThing()
427
+ const getEntity = singleton(() => createEntity())
428
+ getEntity() === getEntity()
391
429
  ```
392
430
 
393
431
  ### `scoped`
394
432
  ```ts
395
- function scoped<T>(resolver: Resolver<T>): Provider<T>
433
+ function scoped<T>(resolver: ResolverFn<T>): Resolver<T>
396
434
  ```
397
- - `resolver`: A function that returns a value of a particular type with a resolution context being passed to it.
435
+ - `resolver`: A function that takes a resolution context and returns a value of some type.
398
436
 
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
437
+ 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
438
 
401
439
  #### Example 1
402
440
  ```ts
403
- const getThing = scoped(() => createThing())
404
- getThing() === getThing()
441
+ const getEntity = scoped(() => createEntity())
442
+ getEntity() === getEntity()
405
443
  ```
406
444
 
407
445
  #### Example 2
408
446
  ```ts
409
- const getThing = scoped(() => createThing())
447
+ const getEntity = scoped(() => createEntity())
410
448
  const scope = createScope()
411
- getThing({ scope }) === getThing({ scope }) !== getThing()
449
+ getEntity({ scope }) === getEntity({ scope }) !== getEntity()
412
450
  ```
413
451
 
414
452
  ### `createMockMap`
@@ -416,14 +454,18 @@ getThing({ scope }) === getThing({ scope }) !== getThing()
416
454
  function createMockMap(): MockMap
417
455
  ```
418
456
 
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.
457
+ 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
458
 
421
459
  #### Example
422
460
  ```ts
423
461
  const mocks = createMockMap()
424
- .set(getConfig, getTestConfig)
462
+ .mock(getDependency, getDependencyMock)
463
+ .mockPartially(
464
+ getOtherDependency,
465
+ transient(() => ({ someField: "mock" }))
466
+ )
425
467
 
426
- getThing({ mocks })
468
+ const entityWithMocks = getEntity({ mocks })
427
469
  ```
428
470
 
429
471
  ### `createScope`
@@ -431,7 +473,7 @@ getThing({ mocks })
431
473
  function createScope(): Scope
432
474
  ```
433
475
 
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.
476
+ 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
477
 
436
478
  #### Example
437
479
  ```ts
@@ -439,26 +481,25 @@ const requestScope = createScope()
439
481
 
440
482
  app.use(() => {
441
483
  const db = getDb({ scope: requestScope })
442
- // ...
443
484
  })
444
485
  ```
445
486
 
446
487
  ### `resolveList`
447
488
  ```ts
448
- function resolveList<const Providers extends ProviderList>(
449
- providers: Providers,
489
+ function resolveList<const Resolvers extends ResolverList>(
490
+ resolvers: Resolvers,
450
491
  context?: ResolutionContext
451
492
  ): AwaitValuesInCollection<
452
- InferProviderCollectionResolutions<Provider>
493
+ InferResolverCollectionResolutions<Resolvers>
453
494
  >
454
495
  ```
455
- - `providers`: A list of providers.
496
+ - `resolvers`: A list of resolvers.
456
497
  - `context?`: A resolution context.
457
498
 
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.
499
+ 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
500
 
460
501
  #### Example 1
461
- Only sync providers:
502
+ Only sync resolvers:
462
503
  ```ts
463
504
  const getA = scoped(() => createA())
464
505
  const getB = scoped(() => createB())
@@ -479,7 +520,7 @@ resolutions == [
479
520
  ```
480
521
 
481
522
  #### Example 2
482
- Some provider is async:
523
+ Some resolver is async:
483
524
  ```ts
484
525
  const getA = scoped(() => createA())
485
526
  const getB = scoped(async () => await createB())
@@ -501,20 +542,20 @@ resolutions == [
501
542
 
502
543
  ### `resolveMap`
503
544
  ```ts
504
- function resolveMap<const Providers extends ProviderRecord>(
505
- providers: Providers,
545
+ function resolveMap<const Resolvers extends ResolverRecord>(
546
+ resolvers: Resolvers,
506
547
  context?: ResolutionContext
507
548
  ): AwaitValuesInCollection<
508
- InferProviderCollectionResolutions<Provider>
549
+ InferResolverCollectionResolutions<Resolvers>
509
550
  >
510
551
  ```
511
- - `providers`: A map of providers.
552
+ - `resolvers`: A map of resolvers.
512
553
  - `context?`: A resolution context.
513
554
 
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.
555
+ 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
556
 
516
557
  #### Example 1
517
- Only sync providers:
558
+ Only sync resolvers:
518
559
  ```ts
519
560
  const getA = scoped(() => createA())
520
561
  const getB = scoped(() => createB())
@@ -535,7 +576,7 @@ resolutions == {
535
576
  ```
536
577
 
537
578
  #### Example 2
538
- Some provider is async:
579
+ Some resolver is async:
539
580
  ```ts
540
581
  const getA = scoped(() => createA())
541
582
  const getB = scoped(async () => await createB())
@@ -557,19 +598,19 @@ resolutions == {
557
598
 
558
599
  ## Types
559
600
 
560
- ### `Resolver`
601
+ ### `ResolverFn`
561
602
  ```ts
562
- type Resolver<T> = (context?: ResolutionContext) => T
603
+ type ResolverFn<T> = (context?: ResolutionContext) => T
563
604
  ```
564
605
 
565
- A function that returns a value of a particular type with a resolution context being passed to it.
606
+ A function that takes a resolution context and returns a value of some type.
566
607
 
567
- ### `Provider`
608
+ ### `Resolver`
568
609
  ```ts
569
- type Provider<T> = (context?: ResolutionContext) => T
610
+ type Resolver<T> = (context?: ResolutionContext) => T
570
611
  ```
571
612
 
572
- A function that resolves an instance or a `Promise` of a particular type based on a resolution context passed to it.
613
+ A function that returns a value of some type based on a resolution context.
573
614
 
574
615
  ### `ResolutionContext`
575
616
  ```ts
@@ -579,29 +620,36 @@ type ResolutionContext = {
579
620
  }
580
621
  ```
581
622
 
582
- A context used by providers to resolve instances based on current scope and mocks.
623
+ A context used by resolvers that defines the behaviour of the resolver with the passed mocks and scope.
583
624
 
584
625
  ### `MockMap`
585
626
  ```ts
586
- type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, "set" | "get"> & {
587
- set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;
588
- get<T>(provider: Resolver<T>): Resolver<T> | undefined;
627
+ type MockMap = {
628
+ mock<T>(original: Resolver<T>, mock: Resolver<T>): MockMap;
629
+ mockPartially<T extends object>(
630
+ original: Resolver<T>,
631
+ mock: Resolver<PromiseAwarePartial<T>>,
632
+ ): MockMap;
633
+ get<T>(original: Resolver<T>): Mock<T> | undefined;
589
634
  };
590
635
  ```
591
- - `set`: Sets a mock for a provider.
592
- - `provider`: The original provider.
593
- - `mock`: The mock provider.
594
- - `get`: Retrieves a mock of a provider. Returns undefined if there's none.
595
- - `provider`: The provider.
636
+ - `mock`: Registers a mock for a resolver, creating a new `MockMap` with this registration.
637
+ - `original`: The original resolver.
638
+ - `mock`: The mock resolver.
639
+ - `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.
640
+ - `original`: The original resolver.
641
+ - `mock`: The mock resolver.
642
+ - `get`: Returns a mock of a resolver or `undefined` if one is not registered.
643
+ - `original`: The original resolver.
596
644
 
597
- 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.
645
+ 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.
598
646
 
599
647
  ### `Scope`
600
648
  ```ts
601
649
  type Scope = Map<Resolver<any>, any>
602
650
  ```
603
651
 
604
- 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.
652
+ 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.
605
653
 
606
654
  # Contribution
607
655
 
package/dist/index.d.mts CHANGED
@@ -69,7 +69,9 @@ type Resolver<T> = (context?: ResolutionContext) => T;
69
69
  * A function that resolves an instance or a `Promise` of a particular type
70
70
  * based on a resolution context passed to it.
71
71
  */
72
- type Provider<T> = Resolver<T>;
72
+ type Provider<T> = Resolver<T> & {
73
+ __brand: "provider";
74
+ };
73
75
  /**
74
76
  * A context used by providers to resolve instances
75
77
  * based on current scope and mocks.
package/dist/index.d.ts CHANGED
@@ -69,7 +69,9 @@ type Resolver<T> = (context?: ResolutionContext) => T;
69
69
  * A function that resolves an instance or a `Promise` of a particular type
70
70
  * based on a resolution context passed to it.
71
71
  */
72
- type Provider<T> = Resolver<T>;
72
+ type Provider<T> = Resolver<T> & {
73
+ __brand: "provider";
74
+ };
73
75
  /**
74
76
  * A context used by providers to resolve instances
75
77
  * based on current scope and mocks.
package/dist/index.js CHANGED
@@ -30,20 +30,20 @@ __export(src_exports, {
30
30
  module.exports = __toCommonJS(src_exports);
31
31
 
32
32
  // src/provider.ts
33
- var mockable = (resolver) => {
33
+ var createProvider = (resolver) => {
34
34
  const instance = (context) => {
35
35
  var _a;
36
36
  const maybeMock = (_a = context == null ? void 0 : context.mocks) == null ? void 0 : _a.get(instance);
37
37
  if (maybeMock) return maybeMock(context);
38
38
  return resolver(context);
39
39
  };
40
- return instance;
40
+ return Object.assign(instance, { __brand: "provider" });
41
41
  };
42
- var transient = mockable;
42
+ var transient = createProvider;
43
43
  var singleton = (resolver) => {
44
44
  let resolved = false;
45
45
  let resolution;
46
- const instance = mockable((context) => {
46
+ const instance = createProvider((context) => {
47
47
  if (resolved) return resolution;
48
48
  resolution = resolver(context);
49
49
  resolved = true;
@@ -53,10 +53,10 @@ var singleton = (resolver) => {
53
53
  };
54
54
  var scoped = (resolver) => {
55
55
  const singletonFallback = singleton(resolver);
56
- const instance = mockable((context) => {
56
+ const instance = createProvider((context) => {
57
57
  if (!(context == null ? void 0 : context.scope)) return singletonFallback(context);
58
- const resolution = context.scope.has(resolver) ? context.scope.get(resolver) : resolver(context);
59
- context.scope.set(resolver, resolution);
58
+ const resolution = context.scope.has(instance) ? context.scope.get(instance) : resolver(context);
59
+ context.scope.set(instance, resolution);
60
60
  return resolution;
61
61
  });
62
62
  return instance;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["export * from \"./provider\";\nexport * from \"./scope\";\nexport * from \"./mock-map\";\nexport * from \"./collection-resolution\";\n","import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n */\nexport type Resolver<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that resolves an instance or a `Promise` of a particular type\n * based on a resolution context passed to it.\n */\nexport type Provider<T> = Resolver<T>;\n\n/**\n * A context used by providers to resolve instances\n * based on current scope and mocks.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\nconst mockable = <T>(resolver: Resolver<T>): Provider<T> => {\n const instance: Provider<T> = (context) => {\n const maybeMock = context?.mocks?.get(instance);\n if (maybeMock) return maybeMock(context);\n\n return resolver(context);\n };\n\n return instance;\n};\n\n/**\n * Creates a transient provider that will resolve a new instance on each call.\n *\n * @example\n * ```ts\n * const getThing = transient(() => createThing())\n * getThing() !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The transient provider.\n */\nexport const transient = mockable;\n\n/**\n * Creates a singleton provider that will resolve an instance once\n * and return it on every call.\n *\n * @example\n * ```ts\n * const getThing = singleton(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The singleton provider.\n */\nexport const singleton = <T>(resolver: Resolver<T>): Provider<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance: Resolver<T> = mockable((context) => {\n if (resolved) return resolution!;\n\n resolution = resolver(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a scoped provider that will take its resolution from a passed scope\n * or create a new one and save it if there is none.\n * If no scope is passed, it will act as a singleton.\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * const scope = createScope()\n * getThing({ scope }) === getThing({ scope }) !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The scoped provider.\n */\nexport const scoped = <T>(resolver: Resolver<T>): Provider<T> => {\n const singletonFallback = singleton(resolver);\n\n const instance: Resolver<T> = mockable((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(resolver)\n ? context.scope.get(resolver)\n : resolver(context);\n context.scope.set(resolver, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * // ...\n * })\n * ```\n *\n * @returns The map instance.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n */\nexport type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, \"set\" | \"get\"> & {\n /**\n * Sets a mock for a provider.\n *\n * @param provider - The original provider.\n * @param mock - The mock provider.\n */\n set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Retrieves a mock of a provider. Returns undefined if there's none.\n *\n * @param provider - The provider.\n */\n get<T>(provider: Resolver<T>): Resolver<T> | undefined;\n};\n\n/**\n * Creates a `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .set(getConfig, getTestConfig)\n *\n * getThing({ mocks })\n * ```\n *\n * @returns The map instance.\n */\nexport const createMockMap = (): MockMap => new Map();\n","import { Provider, ResolutionContext } from \"./provider\";\n\ntype ProviderList = Provider<any>[];\ntype ProviderRecord = Record<string, Provider<any>>;\n\ntype InferProviderCollectionResolutions<\n Providers extends ProviderList | ProviderRecord,\n> = {\n [K in keyof Providers]: Providers[K] extends Provider<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every provider in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param providers - The list of providers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Providers extends ProviderList>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutions = providers.map((provider) => provider(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every provider in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param providers - The map of providers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Providers extends ProviderRecord>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutionMapEntries = Object.entries(providers).map(\n ([key, provider]) => [key, provider(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwBA,IAAM,WAAW,CAAI,aAAuC;AACxD,QAAM,WAAwB,CAAC,YAAY;AAzB/C;AA0BQ,UAAM,aAAY,wCAAS,UAAT,mBAAgB,IAAI;AACtC,QAAI,UAAW,QAAO,UAAU,OAAO;AAEvC,WAAO,SAAS,OAAO;AAAA,EAC3B;AAEA,SAAO;AACX;AAiBO,IAAM,YAAY;AAkBlB,IAAM,YAAY,CAAI,aAAuC;AAChE,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAwB,SAAS,CAAC,YAAY;AAChD,QAAI,SAAU,QAAO;AAErB,iBAAa,SAAS,OAAO;AAC7B,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA0BO,IAAM,SAAS,CAAI,aAAuC;AAC7D,QAAM,oBAAoB,UAAU,QAAQ;AAE5C,QAAM,WAAwB,SAAS,CAAC,YAAY;AAChD,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,SAAS,OAAO;AACtB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;ACjGO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACYzC,IAAM,gBAAgB,MAAe,oBAAI,IAAI;;;ACmC7C,IAAM,cAAc,CACvB,WACA,YAGC;AACD,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YAGC;AACD,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["export * from \"./provider\";\nexport * from \"./scope\";\nexport * from \"./mock-map\";\nexport * from \"./collection-resolution\";\n","import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n */\nexport type Resolver<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that resolves an instance or a `Promise` of a particular type\n * based on a resolution context passed to it.\n */\nexport type Provider<T> = Resolver<T> & { __brand: \"provider\" };\n\n/**\n * A context used by providers to resolve instances\n * based on current scope and mocks.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\n/**\n * Creating a nominal type value and introducing common functionality.\n */\nconst createProvider = <T>(resolver: Resolver<T>): Provider<T> => {\n const instance: Resolver<T> = (context) => {\n const maybeMock = context?.mocks?.get(instance);\n if (maybeMock) return maybeMock(context);\n\n return resolver(context);\n };\n\n return Object.assign(instance, { __brand: \"provider\" as const });\n};\n\n/**\n * Creates a transient provider that will resolve a new instance on each call.\n *\n * @example\n * ```ts\n * const getThing = transient(() => createThing())\n * getThing() !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The transient provider.\n */\nexport const transient = createProvider;\n\n/**\n * Creates a singleton provider that will resolve an instance once\n * and return it on every call.\n *\n * @example\n * ```ts\n * const getThing = singleton(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The singleton provider.\n */\nexport const singleton = <T>(resolver: Resolver<T>): Provider<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance = createProvider((context) => {\n if (resolved) return resolution!;\n\n resolution = resolver(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a scoped provider that will take its resolution from a passed scope\n * or create a new one and save it if there is none.\n * If no scope is passed, it will act as a singleton.\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * const scope = createScope()\n * getThing({ scope }) === getThing({ scope }) !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The scoped provider.\n */\nexport const scoped = <T>(resolver: Resolver<T>): Provider<T> => {\n const singletonFallback = singleton(resolver);\n\n const instance = createProvider((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(instance)\n ? context.scope.get(instance)\n : resolver(context);\n context.scope.set(instance, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * // ...\n * })\n * ```\n *\n * @returns The map instance.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n */\nexport type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, \"set\" | \"get\"> & {\n /**\n * Sets a mock for a provider.\n *\n * @param provider - The original provider.\n * @param mock - The mock provider.\n */\n set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Retrieves a mock of a provider. Returns undefined if there's none.\n *\n * @param provider - The provider.\n */\n get<T>(provider: Resolver<T>): Resolver<T> | undefined;\n};\n\n/**\n * Creates a `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .set(getConfig, getTestConfig)\n *\n * getThing({ mocks })\n * ```\n *\n * @returns The map instance.\n */\nexport const createMockMap = (): MockMap => new Map();\n","import { Provider, ResolutionContext } from \"./provider\";\n\ntype ProviderList = Provider<any>[];\ntype ProviderRecord = Record<string, Provider<any>>;\n\ntype InferProviderCollectionResolutions<\n Providers extends ProviderList | ProviderRecord,\n> = {\n [K in keyof Providers]: Providers[K] extends Provider<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every provider in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param providers - The list of providers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Providers extends ProviderList>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutions = providers.map((provider) => provider(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every provider in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param providers - The map of providers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Providers extends ProviderRecord>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutionMapEntries = Object.entries(providers).map(\n ([key, provider]) => [key, provider(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC2BA,IAAM,iBAAiB,CAAI,aAAuC;AAC9D,QAAM,WAAwB,CAAC,YAAY;AA5B/C;AA6BQ,UAAM,aAAY,wCAAS,UAAT,mBAAgB,IAAI;AACtC,QAAI,UAAW,QAAO,UAAU,OAAO;AAEvC,WAAO,SAAS,OAAO;AAAA,EAC3B;AAEA,SAAO,OAAO,OAAO,UAAU,EAAE,SAAS,WAAoB,CAAC;AACnE;AAiBO,IAAM,YAAY;AAkBlB,IAAM,YAAY,CAAI,aAAuC;AAChE,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,SAAU,QAAO;AAErB,iBAAa,SAAS,OAAO;AAC7B,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA0BO,IAAM,SAAS,CAAI,aAAuC;AAC7D,QAAM,oBAAoB,UAAU,QAAQ;AAE5C,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,SAAS,OAAO;AACtB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;ACpGO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACYzC,IAAM,gBAAgB,MAAe,oBAAI,IAAI;;;ACmC7C,IAAM,cAAc,CACvB,WACA,YAGC;AACD,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YAGC;AACD,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
package/dist/index.mjs CHANGED
@@ -1,18 +1,18 @@
1
1
  // src/provider.ts
2
- var mockable = (resolver) => {
2
+ var createProvider = (resolver) => {
3
3
  const instance = (context) => {
4
4
  var _a;
5
5
  const maybeMock = (_a = context == null ? void 0 : context.mocks) == null ? void 0 : _a.get(instance);
6
6
  if (maybeMock) return maybeMock(context);
7
7
  return resolver(context);
8
8
  };
9
- return instance;
9
+ return Object.assign(instance, { __brand: "provider" });
10
10
  };
11
- var transient = mockable;
11
+ var transient = createProvider;
12
12
  var singleton = (resolver) => {
13
13
  let resolved = false;
14
14
  let resolution;
15
- const instance = mockable((context) => {
15
+ const instance = createProvider((context) => {
16
16
  if (resolved) return resolution;
17
17
  resolution = resolver(context);
18
18
  resolved = true;
@@ -22,10 +22,10 @@ var singleton = (resolver) => {
22
22
  };
23
23
  var scoped = (resolver) => {
24
24
  const singletonFallback = singleton(resolver);
25
- const instance = mockable((context) => {
25
+ const instance = createProvider((context) => {
26
26
  if (!(context == null ? void 0 : context.scope)) return singletonFallback(context);
27
- const resolution = context.scope.has(resolver) ? context.scope.get(resolver) : resolver(context);
28
- context.scope.set(resolver, resolution);
27
+ const resolution = context.scope.has(instance) ? context.scope.get(instance) : resolver(context);
28
+ context.scope.set(instance, resolution);
29
29
  return resolution;
30
30
  });
31
31
  return instance;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n */\nexport type Resolver<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that resolves an instance or a `Promise` of a particular type\n * based on a resolution context passed to it.\n */\nexport type Provider<T> = Resolver<T>;\n\n/**\n * A context used by providers to resolve instances\n * based on current scope and mocks.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\nconst mockable = <T>(resolver: Resolver<T>): Provider<T> => {\n const instance: Provider<T> = (context) => {\n const maybeMock = context?.mocks?.get(instance);\n if (maybeMock) return maybeMock(context);\n\n return resolver(context);\n };\n\n return instance;\n};\n\n/**\n * Creates a transient provider that will resolve a new instance on each call.\n *\n * @example\n * ```ts\n * const getThing = transient(() => createThing())\n * getThing() !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The transient provider.\n */\nexport const transient = mockable;\n\n/**\n * Creates a singleton provider that will resolve an instance once\n * and return it on every call.\n *\n * @example\n * ```ts\n * const getThing = singleton(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The singleton provider.\n */\nexport const singleton = <T>(resolver: Resolver<T>): Provider<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance: Resolver<T> = mockable((context) => {\n if (resolved) return resolution!;\n\n resolution = resolver(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a scoped provider that will take its resolution from a passed scope\n * or create a new one and save it if there is none.\n * If no scope is passed, it will act as a singleton.\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * const scope = createScope()\n * getThing({ scope }) === getThing({ scope }) !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The scoped provider.\n */\nexport const scoped = <T>(resolver: Resolver<T>): Provider<T> => {\n const singletonFallback = singleton(resolver);\n\n const instance: Resolver<T> = mockable((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(resolver)\n ? context.scope.get(resolver)\n : resolver(context);\n context.scope.set(resolver, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * // ...\n * })\n * ```\n *\n * @returns The map instance.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n */\nexport type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, \"set\" | \"get\"> & {\n /**\n * Sets a mock for a provider.\n *\n * @param provider - The original provider.\n * @param mock - The mock provider.\n */\n set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Retrieves a mock of a provider. Returns undefined if there's none.\n *\n * @param provider - The provider.\n */\n get<T>(provider: Resolver<T>): Resolver<T> | undefined;\n};\n\n/**\n * Creates a `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .set(getConfig, getTestConfig)\n *\n * getThing({ mocks })\n * ```\n *\n * @returns The map instance.\n */\nexport const createMockMap = (): MockMap => new Map();\n","import { Provider, ResolutionContext } from \"./provider\";\n\ntype ProviderList = Provider<any>[];\ntype ProviderRecord = Record<string, Provider<any>>;\n\ntype InferProviderCollectionResolutions<\n Providers extends ProviderList | ProviderRecord,\n> = {\n [K in keyof Providers]: Providers[K] extends Provider<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every provider in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param providers - The list of providers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Providers extends ProviderList>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutions = providers.map((provider) => provider(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every provider in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param providers - The map of providers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Providers extends ProviderRecord>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutionMapEntries = Object.entries(providers).map(\n ([key, provider]) => [key, provider(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";AAwBA,IAAM,WAAW,CAAI,aAAuC;AACxD,QAAM,WAAwB,CAAC,YAAY;AAzB/C;AA0BQ,UAAM,aAAY,wCAAS,UAAT,mBAAgB,IAAI;AACtC,QAAI,UAAW,QAAO,UAAU,OAAO;AAEvC,WAAO,SAAS,OAAO;AAAA,EAC3B;AAEA,SAAO;AACX;AAiBO,IAAM,YAAY;AAkBlB,IAAM,YAAY,CAAI,aAAuC;AAChE,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAwB,SAAS,CAAC,YAAY;AAChD,QAAI,SAAU,QAAO;AAErB,iBAAa,SAAS,OAAO;AAC7B,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA0BO,IAAM,SAAS,CAAI,aAAuC;AAC7D,QAAM,oBAAoB,UAAU,QAAQ;AAE5C,QAAM,WAAwB,SAAS,CAAC,YAAY;AAChD,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,SAAS,OAAO;AACtB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;ACjGO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACYzC,IAAM,gBAAgB,MAAe,oBAAI,IAAI;;;ACmC7C,IAAM,cAAc,CACvB,WACA,YAGC;AACD,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YAGC;AACD,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
1
+ {"version":3,"sources":["../src/provider.ts","../src/scope.ts","../src/mock-map.ts","../src/collection-resolution.ts"],"sourcesContent":["import { MockMap } from \"./mock-map\";\nimport { Scope } from \"./scope\";\n\n/**\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n */\nexport type Resolver<T> = (context?: ResolutionContext) => T;\n\n/**\n * A function that resolves an instance or a `Promise` of a particular type\n * based on a resolution context passed to it.\n */\nexport type Provider<T> = Resolver<T> & { __brand: \"provider\" };\n\n/**\n * A context used by providers to resolve instances\n * based on current scope and mocks.\n */\nexport type ResolutionContext = {\n scope?: Scope;\n mocks?: MockMap;\n};\n\n/**\n * Creating a nominal type value and introducing common functionality.\n */\nconst createProvider = <T>(resolver: Resolver<T>): Provider<T> => {\n const instance: Resolver<T> = (context) => {\n const maybeMock = context?.mocks?.get(instance);\n if (maybeMock) return maybeMock(context);\n\n return resolver(context);\n };\n\n return Object.assign(instance, { __brand: \"provider\" as const });\n};\n\n/**\n * Creates a transient provider that will resolve a new instance on each call.\n *\n * @example\n * ```ts\n * const getThing = transient(() => createThing())\n * getThing() !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The transient provider.\n */\nexport const transient = createProvider;\n\n/**\n * Creates a singleton provider that will resolve an instance once\n * and return it on every call.\n *\n * @example\n * ```ts\n * const getThing = singleton(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The singleton provider.\n */\nexport const singleton = <T>(resolver: Resolver<T>): Provider<T> => {\n let resolved = false;\n let resolution: T | undefined;\n\n const instance = createProvider((context) => {\n if (resolved) return resolution!;\n\n resolution = resolver(context);\n resolved = true;\n\n return resolution;\n });\n\n return instance;\n};\n\n/**\n * Creates a scoped provider that will take its resolution from a passed scope\n * or create a new one and save it if there is none.\n * If no scope is passed, it will act as a singleton.\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * getThing() === getThing()\n * ```\n *\n * @example\n * ```ts\n * const getThing = scoped(() => createThing())\n * const scope = createScope()\n * getThing({ scope }) === getThing({ scope }) !== getThing()\n * ```\n *\n * @param resolver\n * A function that returns a value of a particular type\n * with a resolution context being passed to it.\n *\n * @returns The scoped provider.\n */\nexport const scoped = <T>(resolver: Resolver<T>): Provider<T> => {\n const singletonFallback = singleton(resolver);\n\n const instance = createProvider((context) => {\n if (!context?.scope) return singletonFallback(context);\n\n const resolution = context.scope.has(instance)\n ? context.scope.get(instance)\n : resolver(context);\n context.scope.set(instance, resolution);\n\n return resolution;\n });\n\n return instance;\n};\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n */\nexport type Scope = Map<Resolver<any>, any>;\n\n/**\n * Creates a `Map` of providers to their instances\n * that is then passed to a provider call in a resolution context object\n * to resolve instances of scoped providers within it.\n *\n * @example\n * ```ts\n * const requestScope = createScope()\n *\n * app.use(() => {\n * const db = getDb({ scope: requestScope })\n * // ...\n * })\n * ```\n *\n * @returns The map instance.\n */\nexport const createScope = (): Scope => new Map();\n","import { Resolver } from \"./provider\";\n\n/**\n * A `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n */\nexport type MockMap = Omit<Map<Resolver<any>, Resolver<any>>, \"set\" | \"get\"> & {\n /**\n * Sets a mock for a provider.\n *\n * @param provider - The original provider.\n * @param mock - The mock provider.\n */\n set<T>(provider: Resolver<T>, mock: Resolver<T>): MockMap;\n /**\n * Retrieves a mock of a provider. Returns undefined if there's none.\n *\n * @param provider - The provider.\n */\n get<T>(provider: Resolver<T>): Resolver<T> | undefined;\n};\n\n/**\n * Creates a `Map` of providers to providers of the same type\n * which is then passed to a provider call in a resolution context object\n * in order to replace providers with their mocks.\n *\n * @example\n * ```ts\n * const mocks = createMockMap()\n * .set(getConfig, getTestConfig)\n *\n * getThing({ mocks })\n * ```\n *\n * @returns The map instance.\n */\nexport const createMockMap = (): MockMap => new Map();\n","import { Provider, ResolutionContext } from \"./provider\";\n\ntype ProviderList = Provider<any>[];\ntype ProviderRecord = Record<string, Provider<any>>;\n\ntype InferProviderCollectionResolutions<\n Providers extends ProviderList | ProviderRecord,\n> = {\n [K in keyof Providers]: Providers[K] extends Provider<infer T> ? T : never;\n};\n\n/**\n * Awaits all promises and wraps the collection in a promise\n * if there'ss at least one `Promise` in the collection,\n * otherwise returns an untouched type.\n */\ntype AwaitValuesInCollection<T extends any[] | Record<any, any>> =\n Promise<any> extends T[keyof T]\n ? Promise<{\n [I in keyof T]: T[I] extends Promise<infer T> ? T : T[I];\n }>\n : T;\n\n/**\n * Calls every provider in a list with a provided resolution context\n * and returns a list of resolutions. Returns a `Promise` of a list\n * of awaited resolutions if there's at least one `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveList(\n * [getA, getB, getC],\n * { scope }\n * )\n *\n * resolutions == [\n * getA({ scope }),\n * await getB({ scope }),\n * getC({ scope })\n * ]\n * ```\n *\n * @param providers - The list of providers.\n * @param context - The resolution context.\n *\n * @returns The list of resolutions.\n */\nexport const resolveList = <const Providers extends ProviderList>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutions = providers.map((provider) => provider(context));\n\n const hasPromises = resolutions.some(\n (resolution) => resolution instanceof Promise,\n );\n\n return (hasPromises ? Promise.all(resolutions) : resolutions) as any;\n};\n\n/**\n * Calls every provider in a map with a provided resolution context\n * and returns a map with identical keys but with resolutions in values instead.\n * Returns a `Promise` of a map of awaited resolutions if there's at least one\n * `Promise` in the resolutions.\n *\n * @example\n * Only sync providers:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(() => createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @example\n * Some provider is async:\n * ```ts\n * const getA = scoped(() => createA())\n * const getB = scoped(async () => await createB())\n * const getC = scoped(() => createC())\n *\n * const scope = createScope()\n * const resolutions = await resolveMap(\n * { a: getA, b: getB, c: getC },\n * { scope }\n * )\n *\n * resolutions == {\n * a: getA({ scope }),\n * b: await getB({ scope }),\n * c: getC({ scope })\n * }\n * ```\n *\n * @param providers - The map of providers.\n * @param context - The resolution context.\n *\n * @returns The map of resolutions.\n */\nexport const resolveMap = <const Providers extends ProviderRecord>(\n providers: Providers,\n context?: ResolutionContext,\n): AwaitValuesInCollection<\n InferProviderCollectionResolutions<Providers>\n> => {\n const resolutionMapEntries = Object.entries(providers).map(\n ([key, provider]) => [key, provider(context)],\n );\n\n const hasPromises = resolutionMapEntries.some(\n ([, resolution]) => resolution instanceof Promise,\n );\n\n if (hasPromises) {\n return (async () => {\n const awaitedEntries = await Promise.all(\n resolutionMapEntries.map(async ([key, resolution]) => [\n key,\n await resolution,\n ]),\n );\n return Object.fromEntries(awaitedEntries);\n })() as any;\n }\n\n return Object.fromEntries(resolutionMapEntries);\n};\n"],"mappings":";AA2BA,IAAM,iBAAiB,CAAI,aAAuC;AAC9D,QAAM,WAAwB,CAAC,YAAY;AA5B/C;AA6BQ,UAAM,aAAY,wCAAS,UAAT,mBAAgB,IAAI;AACtC,QAAI,UAAW,QAAO,UAAU,OAAO;AAEvC,WAAO,SAAS,OAAO;AAAA,EAC3B;AAEA,SAAO,OAAO,OAAO,UAAU,EAAE,SAAS,WAAoB,CAAC;AACnE;AAiBO,IAAM,YAAY;AAkBlB,IAAM,YAAY,CAAI,aAAuC;AAChE,MAAI,WAAW;AACf,MAAI;AAEJ,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,SAAU,QAAO;AAErB,iBAAa,SAAS,OAAO;AAC7B,eAAW;AAEX,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;AA0BO,IAAM,SAAS,CAAI,aAAuC;AAC7D,QAAM,oBAAoB,UAAU,QAAQ;AAE5C,QAAM,WAAW,eAAe,CAAC,YAAY;AACzC,QAAI,EAAC,mCAAS,OAAO,QAAO,kBAAkB,OAAO;AAErD,UAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,IACvC,QAAQ,MAAM,IAAI,QAAQ,IAC1B,SAAS,OAAO;AACtB,YAAQ,MAAM,IAAI,UAAU,UAAU;AAEtC,WAAO;AAAA,EACX,CAAC;AAED,SAAO;AACX;;;ACpGO,IAAM,cAAc,MAAa,oBAAI,IAAI;;;ACYzC,IAAM,gBAAgB,MAAe,oBAAI,IAAI;;;ACmC7C,IAAM,cAAc,CACvB,WACA,YAGC;AACD,QAAM,cAAc,UAAU,IAAI,CAAC,aAAa,SAAS,OAAO,CAAC;AAEjE,QAAM,cAAc,YAAY;AAAA,IAC5B,CAAC,eAAe,sBAAsB;AAAA,EAC1C;AAEA,SAAQ,cAAc,QAAQ,IAAI,WAAW,IAAI;AACrD;AAqDO,IAAM,aAAa,CACtB,WACA,YAGC;AACD,QAAM,uBAAuB,OAAO,QAAQ,SAAS,EAAE;AAAA,IACnD,CAAC,CAAC,KAAK,QAAQ,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC;AAAA,EAChD;AAEA,QAAM,cAAc,qBAAqB;AAAA,IACrC,CAAC,CAAC,EAAE,UAAU,MAAM,sBAAsB;AAAA,EAC9C;AAEA,MAAI,aAAa;AACb,YAAQ,YAAY;AAChB,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACjC,qBAAqB,IAAI,OAAO,CAAC,KAAK,UAAU,MAAM;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AACA,aAAO,OAAO,YAAY,cAAc;AAAA,IAC5C,GAAG;AAAA,EACP;AAEA,SAAO,OAAO,YAAY,oBAAoB;AAClD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atomic-di",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "Lifetimes, scopes and mocking for pure dependency injection.",
5
5
  "repository": "https://github.com/ncor/atomic-di",
6
6
  "bugs": "https://github.com/ncor/atomic-di/issues",