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 +270 -222
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
@@ -7,59 +7,44 @@ This library implements lifetimes, scopes and mocking for pure dependency inject
|
|
7
7
|
- [Intro](#Intro)
|
8
8
|
- [Installation](#Installation)
|
9
9
|
- [Usage](#Usage)
|
10
|
-
- [
|
10
|
+
- [Creating resolvers](#Creating-resolvers)
|
11
11
|
- [Transient](#Transient)
|
12
12
|
- [Singleton](#Singleton)
|
13
13
|
- [Scoped](#Scoped)
|
14
|
-
- [
|
14
|
+
- [Propagating a context](#Propagating-a-context)
|
15
15
|
- [Mocking](#Mocking)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
- [
|
16
|
+
- [Registering mocks](#Registering-mocks)
|
17
|
+
- [Resolving with mocks](#Resolving-with-mocks)
|
18
|
+
- [Scopes](#Scopes)
|
19
|
+
- [Creating a scope](#Scope)
|
20
|
+
- [Resolving with a scope](#Resolving-with-a-scope)
|
21
|
+
- [Resolving collections](#Resolving-collections)
|
22
|
+
- [Resolving a list](#Resolving-a-list)
|
23
|
+
- [Resolving a map](#Resolving-a-map)
|
20
24
|
- [Reference](#Reference)
|
21
25
|
- [Functions](#Functions)
|
22
|
-
- [transient](#transient)
|
23
|
-
- [singleton](#singleton)
|
24
|
-
- [scoped](#scoped)
|
25
|
-
- [createMockMap](#createMockMap)
|
26
|
-
- [createScope](#createScope)
|
26
|
+
- [`transient`](#transient)
|
27
|
+
- [`singleton`](#singleton)
|
28
|
+
- [`scoped`](#scoped)
|
29
|
+
- [`createMockMap`](#createMockMap)
|
30
|
+
- [`createScope`](#createScope)
|
31
|
+
- [`resolveList`](#resolveList)
|
32
|
+
- [`resolveMap`](#resolveMap)
|
27
33
|
- [Types](#Types)
|
28
|
-
- [
|
29
|
-
- [
|
30
|
-
- [
|
31
|
-
- [
|
34
|
+
- [`ResolverFn`](#ResolverFn)
|
35
|
+
- [`Resolver`](#Resolver)
|
36
|
+
- [`ResolutionContext`](#ResolutionContext)
|
37
|
+
- [`MockMap`](#MockMap)
|
38
|
+
- [`Scope`](#Scope)
|
32
39
|
|
33
40
|
# Intro
|
34
41
|
|
35
|
-
## Prerequisites
|
36
|
-
|
37
42
|
Before reading, it's highly recommended that you familiarize yourself with the concepts of inversion of control (IoC) and dependency injection (DI), as well as DI techniques.
|
38
43
|
|
39
|
-
If you need a container to build your application, or you are satisfied with
|
44
|
+
If you need a container to build your application, or you are satisfied with pure dependency injection, you should definitely consider other solutions, or not use a framework at all.
|
40
45
|
|
41
46
|
This library is an attempt to provide full-featured dependency injection **without containers**.
|
42
47
|
|
43
|
-
## Problems and solutions
|
44
|
-
|
45
|
-
### Lifetimes
|
46
|
-
|
47
|
-
We can implement lifetime using static initializations together with factory functions that create instances on demand. However, this can introduce inconsistency into a composition code.
|
48
|
-
|
49
|
-
This library solves this problem by allowing to resolve instances once using the same factory technique.
|
50
|
-
|
51
|
-
### Scopes
|
52
|
-
|
53
|
-
Often in your application you may need to resolve instances separately for different "scopes" of a program, be it a request, a transaction or a worker thread. This behavior can be achieved by correctly distributing transient resolutions, but at scale the complexity of this approach will only increase.
|
54
|
-
|
55
|
-
This library solves this problem by introducing into factories (hereinafter referred to as providers) an ability to work with a map of providers to their instances, which serves as a scope.
|
56
|
-
|
57
|
-
### Testing
|
58
|
-
|
59
|
-
Testability is an important part of every application. IoC handles this very well, but to perform a unit test we still need to resolve modules. To ensure testing without side effects, developers often use mocking - replacing implementations with others with the same behavior. We can rebuild modules manually for each unit test or group of unit tests, but at scale this approach can introduce a lot of extra manual work without any significant benefit.
|
60
|
-
|
61
|
-
This library solves this problem by allowing you to use factories that have been defined for a main application build. It's enough to create a map of mock providers to providers with the same interface, and pass it to a provider call to replace implementations in its dependencies.
|
62
|
-
|
63
48
|
# Installation
|
64
49
|
|
65
50
|
You can use any package manager.
|
@@ -74,200 +59,246 @@ npx jsr add @ensi/di
|
|
74
59
|
# Usage
|
75
60
|
|
76
61
|
#### Table of contents
|
77
|
-
- [
|
62
|
+
- [Creating resolvers](#Creating-resolvers)
|
78
63
|
- [Transient](#Transient)
|
79
64
|
- [Singleton](#Singleton)
|
80
65
|
- [Scoped](#Scoped)
|
81
|
-
- [
|
66
|
+
- [Propagating a context](#Propagating-a-context)
|
82
67
|
- [Mocking](#Mocking)
|
83
|
-
- [
|
84
|
-
- [
|
85
|
-
|
86
|
-
- [
|
68
|
+
- [Registering mocks](#Registering-mocks)
|
69
|
+
- [Resolving with mocks](#Resolving-with-mocks)
|
70
|
+
- [Scopes](#Scopes)
|
71
|
+
- [Creating a scope](#Scope)
|
72
|
+
- [Resolving with a scope](#Resolving-with-a-scope)
|
73
|
+
- [Resolving collections](#Resolving-collections)
|
74
|
+
- [Resolving a list](#Resolving-a-list)
|
75
|
+
- [Resolving a map](#Resolving-a-map)
|
76
|
+
|
77
|
+
## Creating resolvers
|
87
78
|
|
88
|
-
|
79
|
+
The approach to dependency injection in this library is factories. It consists of a factory creating an instance of a certain type by calling other factories that resolve dependencies for it.
|
89
80
|
|
90
|
-
|
81
|
+
To implement lifetimes, scope, and mocking mechanisms, the library provides functions that create factories with functionality specific to a particular lifetime, such factories are called **resolvers**. They all have some functionality in common, but first let's look at the functions that create them.
|
91
82
|
|
92
83
|
### Transient
|
93
84
|
|
94
|
-
|
85
|
+
The `transient` function creates a basic resolver that does not contain any logic that controls a lifetime of a resolution. This means that this resolver will call a passed factory and return a new instance each time it is called.
|
95
86
|
```ts
|
96
|
-
const
|
87
|
+
const getRandom = transient(Math.random)
|
88
|
+
```
|
89
|
+
```ts
|
90
|
+
getRandom() !== getRandom()
|
97
91
|
```
|
98
|
-
|
99
|
-
Transient providers are no different from regular factories except for additional logic required for scopes and mocks to work correctly. This logic is also present in other two functions, you can read about it [here](#Resolution-context).
|
100
92
|
|
101
93
|
### Singleton
|
102
94
|
|
103
|
-
|
95
|
+
The `singleton` function creates a resolver that contains a logic specific to singletons. This means that a singleton resolver will only call a passed factory once, and will return a single instance created each time it is called.
|
104
96
|
```ts
|
105
|
-
const
|
106
|
-
const getB = transient((c) => createB(getA(c)))
|
97
|
+
const getRandom = singleton(Math.random)
|
107
98
|
```
|
108
|
-
|
109
|
-
In this case, calling `getA` will always result in a same instance, and a passed factory will only be called once:
|
110
99
|
```ts
|
111
|
-
|
100
|
+
getRandom() === getRandom()
|
112
101
|
```
|
113
102
|
|
114
|
-
You may have noticed that the `getB` provider factory uses a certain `c` argument. This is a context that can optionally be passed when calling a provider, you can read about it [here](#Resolution-context).
|
115
|
-
|
116
103
|
### Scoped
|
117
104
|
|
118
|
-
|
105
|
+
The `scoped` function creates a resolver that contains logic specific to scoped registrations, often supported by IoC containers. These resolvers operate on scope instances that are passed into a resolution context when called. They check whether their instance is in a scope, and depending on this, save a new instance or return an existing one within that scope. If a resolver is not passed a scope when called, it will behave as a singleton, simulating a global scope.
|
119
106
|
```ts
|
120
|
-
const
|
107
|
+
const getRandom = scoped(Math.random)
|
121
108
|
```
|
122
|
-
|
123
|
-
When calling this provider without passing a scope to a resolution context, it will act as a singleton, resulting in a same instance:
|
124
109
|
```ts
|
125
|
-
|
110
|
+
getRandom() === getRandom()
|
126
111
|
```
|
127
|
-
|
128
|
-
To get resolutions within a scope, we need to pass it to a provider call in a resolution context object:
|
129
112
|
```ts
|
130
|
-
|
131
|
-
|
132
|
-
|
113
|
+
getRandom({ scope: myScope }) === getRandom({ scope: myScope })
|
114
|
+
```
|
115
|
+
```ts
|
116
|
+
getRandom() !== getRandom({ scope: myScope })
|
133
117
|
```
|
134
118
|
|
135
|
-
|
119
|
+
A detailed explanation of the scope mechanism and its use is described in [this](#Scopes) section.
|
136
120
|
|
137
|
-
##
|
121
|
+
## Propagating a context
|
138
122
|
|
139
|
-
Each
|
123
|
+
Each resolver takes an optional resolution context. This is an object that can contain a scope and a mock map. Based on this context, resolvers determine how to resolve an instance.
|
140
124
|
|
141
|
-
In
|
125
|
+
In order for a resolution context to correctly influence a current resolution, it **must** be propagated up a resolver call chain so that each resolver is aware of a current context. Therefore, if a factory uses other resolvers, it **must** pass a resolution context it receives into **each** resolver call.
|
142
126
|
|
143
127
|
#### Incorrect
|
144
128
|
```ts
|
145
|
-
const
|
146
|
-
const
|
147
|
-
|
129
|
+
const getDependency = transient(() => createDependency())
|
130
|
+
const getEntity = transient(() =>
|
131
|
+
createEntity(getDependency())
|
132
|
+
)
|
148
133
|
```
|
149
|
-
|
150
|
-
In this case, a context will not propagate beyond `getC` and other providers will not know about a current scope and mocks, and `getB` will return an instance that is not related to any scopes.
|
151
|
-
|
152
134
|
#### Correct
|
153
135
|
```ts
|
154
|
-
const
|
155
|
-
const
|
156
|
-
|
136
|
+
const getDependency = transient(() => createDependency())
|
137
|
+
const getEntity = transient((c) =>
|
138
|
+
createEntity(getDependency(c)) // context is propagated
|
139
|
+
)
|
157
140
|
```
|
158
141
|
|
159
|
-
|
142
|
+
## Mocking
|
143
|
+
|
144
|
+
Mocking is a common mechanism in testing whereby some implementation being used is replaced with a replica to prevent side effects or reduce testing load.
|
160
145
|
|
161
|
-
|
146
|
+
This library implements this mechanism by adding logic to each resolver responsible for replacing **itself** (not a resolution) when its own mock is present in a resolution context. A definition of mocks in a resolution context is done by passing a mock map to this resolution context.
|
162
147
|
|
163
|
-
|
148
|
+
### Registering mocks
|
164
149
|
|
165
|
-
|
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
|
152
|
+
const mocks = createMockMap()
|
168
153
|
```
|
169
154
|
|
170
|
-
To register a mock,
|
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
|
-
|
157
|
+
const mocks = createMockMap()
|
158
|
+
.mock(getDatabase, getDatabaseMock)
|
159
|
+
.mock(getLogger, getLoggerMock)
|
160
|
+
// ...
|
173
161
|
```
|
174
162
|
|
175
|
-
|
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
|
-
|
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
|
-
|
180
|
-
|
176
|
+
resolver({ mocks: myMockMap })
|
177
|
+
```
|
178
|
+
|
179
|
+
If resolver's direct or transitive dependencies or the resolver itself have their mock registered in a mock map, they will replace themselves with this mock, depending on a type of a mock. This behavior is clearly demonstrated in examples below.
|
181
180
|
|
182
|
-
|
181
|
+
#### Full mock
|
182
|
+
```ts
|
183
|
+
const getDependency = transitive(() => "dependency")
|
184
|
+
const getEntity = transitive((c) => ({
|
185
|
+
dependency: getDependency(c)
|
186
|
+
}))
|
183
187
|
```
|
184
188
|
```ts
|
185
|
-
const
|
186
|
-
const mocks = createMockMap().set(getB, getBee)
|
189
|
+
const getDependencyMock = transitive(() => "dependencyMock")
|
187
190
|
|
188
|
-
|
191
|
+
const mocks = createMockMap()
|
192
|
+
.mock(getDependency, getDependencyMock
|
189
193
|
```
|
190
|
-
|
191
|
-
#### Direct/transitive dependency replacement
|
192
194
|
```ts
|
193
|
-
|
194
|
-
|
195
|
-
|
195
|
+
getEntity({ mocks }) == {
|
196
|
+
dependency: "dependencyMock"
|
197
|
+
}
|
198
|
+
```
|
196
199
|
|
197
|
-
|
200
|
+
#### Partial mock
|
201
|
+
```ts
|
202
|
+
const getDependency = transitive(() => ({
|
203
|
+
value: "dependency",
|
204
|
+
origin: "getDependency"
|
205
|
+
}))
|
206
|
+
const getEntity = transitive((c) => ({
|
207
|
+
dependency: getDependency(c)
|
208
|
+
}))
|
198
209
|
```
|
199
210
|
```ts
|
200
|
-
const
|
201
|
-
|
211
|
+
const getDependencyMock = transitive(() => ({
|
212
|
+
value: "dependencyMock"
|
213
|
+
}))
|
202
214
|
|
203
|
-
|
215
|
+
const mocks = createMockMap()
|
216
|
+
.mockPartially(getDependency, getDependencyMock
|
217
|
+
```
|
218
|
+
```ts
|
219
|
+
getEntity({ mocks }) == {
|
220
|
+
dependency: {
|
221
|
+
value: "dependencyMock", // replaced
|
222
|
+
origin: "getDependency" // original value
|
223
|
+
}
|
224
|
+
}
|
204
225
|
```
|
205
226
|
|
206
|
-
##
|
227
|
+
## Scopes
|
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
|
-
|
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
|
214
|
-
|
215
|
-
|
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
|
-
|
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
|
260
|
+
const getEntity = scoped(() => createEntity())
|
220
261
|
```
|
221
262
|
```ts
|
222
|
-
const
|
223
|
-
const thing2 = getThing()
|
263
|
+
const scope = createScope()
|
224
264
|
|
225
|
-
|
265
|
+
const scopeEntity = getEntity({ scope })
|
226
266
|
```
|
227
267
|
```ts
|
228
|
-
|
229
|
-
const thing2 = getThing({ scope })
|
230
|
-
const thingFallback = getThing()
|
231
|
-
|
232
|
-
thing1 === thing2 !== thingFallback
|
268
|
+
scope.get(getEntity) === scopeEntity
|
233
269
|
```
|
234
270
|
|
235
|
-
####
|
271
|
+
#### Indirect resolution
|
236
272
|
```ts
|
237
|
-
const
|
238
|
-
const
|
239
|
-
|
240
|
-
)
|
273
|
+
const getDependency = scoped(() => createDependency())
|
274
|
+
const getEntity = transient((c) => ({
|
275
|
+
dependency: getDependency(c)
|
276
|
+
}))
|
241
277
|
```
|
242
278
|
```ts
|
243
|
-
const
|
244
|
-
const thing2 = getThing()
|
279
|
+
const scope = createScope()
|
245
280
|
|
246
|
-
|
281
|
+
const entity = getEntity({ scope })
|
247
282
|
```
|
248
283
|
```ts
|
249
|
-
|
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
|
-
##
|
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
|
-
|
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
|
-
|
291
|
+
### Resolving a list
|
263
292
|
|
264
|
-
|
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
|
-
|
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 () =>
|
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
|
-
###
|
337
|
+
### Resolving a map
|
305
338
|
|
306
|
-
|
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
|
-
|
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 () =>
|
364
|
+
const getB = scoped(async () => createB())
|
332
365
|
const getC = scoped(() => createC())
|
333
|
-
|
366
|
+
```
|
367
|
+
```ts
|
334
368
|
const scope = createScope()
|
335
|
-
|
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
|
-
- [
|
359
|
-
- [
|
360
|
-
- [
|
361
|
-
- [
|
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>(
|
405
|
+
function transient<T>(fn: ResolverFn<T>): Resolver<T>
|
368
406
|
```
|
369
|
-
- `resolver`: A function that
|
407
|
+
- `resolver`: A function that takes a resolution context and returns a value of some type.
|
370
408
|
|
371
|
-
Creates a
|
409
|
+
Creates a resolver that creates a new resolution on each call.
|
372
410
|
|
373
411
|
#### Example
|
374
412
|
```ts
|
375
|
-
const
|
376
|
-
|
413
|
+
const getEntity = transient(() => createEntity())
|
414
|
+
getEntity() !== getEntity()
|
377
415
|
```
|
378
416
|
|
379
417
|
### `singleton`
|
380
418
|
```ts
|
381
|
-
function singleton<T>(resolver:
|
419
|
+
function singleton<T>(resolver: ResolverFn<T>): Resolver<T>
|
382
420
|
```
|
383
|
-
- `resolver`: A function that
|
421
|
+
- `resolver`: A function that takes a resolution context and returns a value of some type.
|
384
422
|
|
385
|
-
Creates a
|
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
|
390
|
-
|
427
|
+
const getEntity = singleton(() => createEntity())
|
428
|
+
getEntity() === getEntity()
|
391
429
|
```
|
392
430
|
|
393
431
|
### `scoped`
|
394
432
|
```ts
|
395
|
-
function scoped<T>(resolver:
|
433
|
+
function scoped<T>(resolver: ResolverFn<T>): Resolver<T>
|
396
434
|
```
|
397
|
-
- `resolver`: A function that
|
435
|
+
- `resolver`: A function that takes a resolution context and returns a value of some type.
|
398
436
|
|
399
|
-
Creates a
|
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
|
404
|
-
|
441
|
+
const getEntity = scoped(() => createEntity())
|
442
|
+
getEntity() === getEntity()
|
405
443
|
```
|
406
444
|
|
407
445
|
#### Example 2
|
408
446
|
```ts
|
409
|
-
const
|
447
|
+
const getEntity = scoped(() => createEntity())
|
410
448
|
const scope = createScope()
|
411
|
-
|
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
|
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
|
-
.
|
462
|
+
.mock(getDependency, getDependencyMock)
|
463
|
+
.mockPartially(
|
464
|
+
getOtherDependency,
|
465
|
+
transient(() => ({ someField: "mock" }))
|
466
|
+
)
|
425
467
|
|
426
|
-
|
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
|
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
|
449
|
-
|
489
|
+
function resolveList<const Resolvers extends ResolverList>(
|
490
|
+
resolvers: Resolvers,
|
450
491
|
context?: ResolutionContext
|
451
492
|
): AwaitValuesInCollection<
|
452
|
-
|
493
|
+
InferResolverCollectionResolutions<Resolvers>
|
453
494
|
>
|
454
495
|
```
|
455
|
-
- `
|
496
|
+
- `resolvers`: A list of resolvers.
|
456
497
|
- `context?`: A resolution context.
|
457
498
|
|
458
|
-
Calls every
|
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
|
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
|
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
|
505
|
-
|
545
|
+
function resolveMap<const Resolvers extends ResolverRecord>(
|
546
|
+
resolvers: Resolvers,
|
506
547
|
context?: ResolutionContext
|
507
548
|
): AwaitValuesInCollection<
|
508
|
-
|
549
|
+
InferResolverCollectionResolutions<Resolvers>
|
509
550
|
>
|
510
551
|
```
|
511
|
-
- `
|
552
|
+
- `resolvers`: A map of resolvers.
|
512
553
|
- `context?`: A resolution context.
|
513
554
|
|
514
|
-
Calls every
|
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
|
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
|
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
|
-
### `
|
601
|
+
### `ResolverFn`
|
561
602
|
```ts
|
562
|
-
type
|
603
|
+
type ResolverFn<T> = (context?: ResolutionContext) => T
|
563
604
|
```
|
564
605
|
|
565
|
-
A function that
|
606
|
+
A function that takes a resolution context and returns a value of some type.
|
566
607
|
|
567
|
-
### `
|
608
|
+
### `Resolver`
|
568
609
|
```ts
|
569
|
-
type
|
610
|
+
type Resolver<T> = (context?: ResolutionContext) => T
|
570
611
|
```
|
571
612
|
|
572
|
-
A function that
|
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
|
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 =
|
587
|
-
|
588
|
-
|
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
|
-
- `
|
592
|
-
- `
|
593
|
-
- `mock`: The mock
|
594
|
-
- `
|
595
|
-
- `
|
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
|
-
|
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
|
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
|
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 =
|
42
|
+
var transient = createProvider;
|
43
43
|
var singleton = (resolver) => {
|
44
44
|
let resolved = false;
|
45
45
|
let resolution;
|
46
|
-
const instance =
|
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 =
|
56
|
+
const instance = createProvider((context) => {
|
57
57
|
if (!(context == null ? void 0 : context.scope)) return singletonFallback(context);
|
58
|
-
const resolution = context.scope.has(
|
59
|
-
context.scope.set(
|
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
|
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 =
|
11
|
+
var transient = createProvider;
|
12
12
|
var singleton = (resolver) => {
|
13
13
|
let resolved = false;
|
14
14
|
let resolution;
|
15
|
-
const instance =
|
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 =
|
25
|
+
const instance = createProvider((context) => {
|
26
26
|
if (!(context == null ? void 0 : context.scope)) return singletonFallback(context);
|
27
|
-
const resolution = context.scope.has(
|
28
|
-
context.scope.set(
|
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;
|
package/dist/index.mjs.map
CHANGED
@@ -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