1
- IoC context values provider
1
+ IoC Context Values Provider
2
2
===========================
3
3
4
+ This library allows to construct an [ IoC] context, other components can request values from.
5
+
6
+ An [ IoC] context is an object with ` get() ` method implemented. This method returns a context value by its key.
7
+
4
8
[ ![ NPM] [ npm-image ]] [ npm-url ]
5
9
[ ![ CircleCI] [ ci-image ]] [ ci-url ]
6
10
[ ![ codecov] [ codecov-image ]] [ codecov-url ]
@@ -11,3 +15,270 @@ IoC context values provider
11
15
[ ci-url] :https://circleci.com/gh/surol/context-values
12
16
[ codecov-image ] : https://codecov.io/gh/surol/context-values/branch/master/graph/badge.svg
13
17
[ codecov-url] : https://codecov.io/gh/surol/context-values
18
+ [ IoC ] : https://en.wikipedia.org/wiki/Inversion_of_control
19
+
20
+
21
+ Accessing Context Values
22
+ ------------------------
23
+
24
+ A context should implement a ` ContextValues ` interface. This interface declares a ` get() ` method accepting a
25
+ ` ContextRequest ` identifying the requested value (e.g. a ` ContextKey ` instance), and a non-mandatory options.
26
+
27
+ The following code returns a string value associated with ` key ` , or throws an exception if the value not found.
28
+ ``` typescript
29
+ import { SingleContextKey } from ' context-values' ;
30
+
31
+ const key = new SingleContextKey <string >(' my-key' );
32
+
33
+ myContext .get (key )
34
+ ```
35
+
36
+ ### Fallback Value
37
+
38
+ Normally, if the value associated with the given key can not be found, an exception is thrown. To avoid this, a fallback
39
+ value can be provided. It will be returned if the value not found.
40
+ ``` typescript
41
+ import { SingleContextKey } from ' context-values' ;
42
+
43
+ const key = new SingleContextKey <string >(' my-key' );
44
+
45
+ myContext .get (key , { or: ' empty' });
46
+ ```
47
+
48
+
49
+ ### Context Value Request
50
+
51
+ The ` get() ` method accepts not only a ` ContextKey ` instances, but arbitrary ` ContextRequest ` . The latter is just an
52
+ object with ` key ` property containing a ` ContextKey ` instance to find.
53
+
54
+ This can be handy e.g. when requesting an instance of some known type:
55
+ ``` typescript
56
+ import { ContextKey , SingleContextKey } from ' context-values' ;
57
+
58
+ class MyService {
59
+
60
+ // MyService class (not instance) implements a `ContextRequest`
61
+ static readonly key: ContextKey <MyService > = new SingleContextKey (' my-service' );
62
+
63
+ }
64
+
65
+ myContext .get (MyService ); // No need to specify `MyService.key` here
66
+ myContext .get (MyService .key ); // The same as above.
67
+ ```
68
+
69
+
70
+ Providing Context Values
71
+ ------------------------
72
+
73
+ Context values can be provided using ` ContextRegistry ` .
74
+ Then the values can be requested from ` ContextValues ` instance constructed by the ` newValues() ` method of the registry.
75
+
76
+ ``` typescript
77
+ import { ContextRegistry , SingleContextKey } from ' context-values' ;
78
+
79
+ const key1 = new SingleContextKey <string >(' key1' );
80
+ const key2 = new SingleContextKey <number >(' key2' );
81
+
82
+ const registry = new ContextRegistry ();
83
+
84
+ registry .provide ({ a: key1 , is: ' string' });
85
+ registry .provide ({ a: key2 , by : ctx => ctx .get (key1 ).length })
86
+
87
+ const context = registry .newValues ();
88
+
89
+ context .get (key1 ); // 'string'
90
+ context .get (key2 ); // 6
91
+ ```
92
+
93
+
94
+ ### Context Value Target
95
+
96
+ [ Context Value Target ] : #context-value-target
97
+
98
+ The ` provide() ` method accepts not only a ` ContextKey ` instances, but arbitrary ` ContextTarget ` . The latter is just an
99
+ object with ` key ` property containing a ` ContextKey ` to provide.
100
+
101
+
102
+ This can be handy e.g. when providing an instance of some known type:
103
+ ``` typescript
104
+ import { ContextKey , ContextRegistry , SingleContextKey } from ' context-values' ;
105
+
106
+ class MyService {
107
+
108
+ // MyService class (not instance) implements a `ContextRequest`
109
+ static readonly key: ContextKey <MyService > = new SingleContextKey (' my-service' );
110
+
111
+ }
112
+
113
+ const registry = new ContextRegistry ();
114
+
115
+ registry .provide ({ a: MyService , is: new MyService () });
116
+
117
+ const context = registry .newValues ();
118
+
119
+ context .get (MyService ); // No need to specify `MyService.key` here
120
+ ```
121
+
122
+
123
+ ### Context Value Specifier
124
+
125
+ The ` provide() ` method of ` ContextRegistry ` accepts a _ context value specifier_ as its only parameter.
126
+
127
+ This specifier defines a value (or, more precisely, the [ value sources] ). It may specify the value in a different ways:
128
+
129
+ - ` registry.provide({ a: key, is: value }) ` - provides the value explicitly.
130
+ - ` registry.provide({ a: key, by: ctx => calculateValue(ctx) }) ` - evaluates the value in most generic way. ` ctx ` here
131
+ is the target context.
132
+ - ` registry.provide({ a: key, by: (a, b) => calculateValue(a, b), with: [keyA, keyB] }) ` - evaluates the value based on
133
+ other context values with keys ` keyA ` and ` keyB ` .
134
+ - ` registry.provide({ a: key, as: MyService }) ` - constructs the value a ` new MyService(ctx) ` , where ` ctx ` is the
135
+ target context. The ` a ` property may be omitted if ` MyService ` has a static property ` key ` .
136
+ See [ Context Value Target] .
137
+ - ` registry.porvide({ a: key, as: MyService, with: [keyA, keyB] }) ` - constructs the value as ` new MyService(a, b) ` ,
138
+ where ` a ` and ` b ` are context values with keys ` keyA ` and ` keyB ` respectively. The ` a ` property may be omitted if
139
+ ` MyService ` has a static property ` key ` . See [ Context Value Target] .
140
+
141
+
142
+ Context Value Key
143
+ -----------------
144
+
145
+ Context value keys identify context values.
146
+
147
+ They extend a ` ContextKey ` abstract class. There following implementations are available:
148
+
149
+ - ` SingleContextKey ` that allows associate a single value with it, and
150
+ - ` MultiContextKey ` that allows to associate multiple values with it.
151
+
152
+ ``` typescript
153
+ import { ContextRegistry , SingleContextKey , MultiContextKey } from ' context-values' ;
154
+
155
+ const key1 = new SingleContextKey <string >(' key1' );
156
+ const key2 = new MultiContextKey <number >(' key2' );
157
+
158
+ const registry = new ContextRegistry ();
159
+
160
+ registry .provide ({ a: key1 , is: ' value1' });
161
+ registry .provide ({ a: key1 , is: ' value2' });
162
+ registry .provide ({ a: key2 , is: 1 });
163
+ registry .provide ({ a: key2 , is: 2 });
164
+
165
+ const context = registry .newValues ();
166
+
167
+ context .get (key1 ); // 'value2' - SingleContextKey uses the latest value provided
168
+ context .get (key2 ); // [1, 2] - MultiContextKey returns all provided values as an array
169
+ ```
170
+
171
+ ### Default Value
172
+
173
+ Context value key may declare a default value. It will be evaluated and returned when the value is not found and no
174
+ fallback value specified in the request.
175
+
176
+ The default value is evaluated by the function accepting a ` ContextValues ` instance as its only argument.
177
+ ``` typescript
178
+ import { ContextRegistry , SingleContextKey , MultiContextKey } from ' context-values' ;
179
+
180
+ const key1 = new SingleContextKey <string >(' key1' );
181
+ const key2 = new SingleContextKey <number >(' key2' , ctx => ctx .get (' key1' ).length );
182
+ const key3 = new MultiContextKey <number >(' key3' );
183
+
184
+ const registry = new ContextRegistry ();
185
+
186
+ registry .provide ({ a: key1 , is: ' value' });
187
+
188
+ const context = registry .newValues ();
189
+
190
+ context .get (key1 ); // 'value'
191
+ context .get (key2 ); // 6 - evaluated, as it is not provided
192
+ context .get (key2 , { or: null }); // null - fallback value always takes precedence
193
+ context .get (key3 ); // [] - MultiContextKey uses it as a default value, unless explicitly specified
194
+
195
+ registry .provide ({ a: key2 , value: 999 });
196
+
197
+ context .get (key2 ); // 999 - provided explicitly
198
+ ```
199
+
200
+ ### Custom Context Key
201
+
202
+ [ ContextKey.merge() ] : #custom-context-key
203
+
204
+ It is possible to implement a custom ` ContextKey ` .
205
+
206
+ For that extend an ` AbstractContextKey ` that implements the boilerplate. The only method left to implement then is a
207
+ ` merge() ` one.
208
+
209
+ The ` merge() ` method takes three parameters:
210
+ - a ` ContextValues ` instance (to consult other context values if necessary),
211
+ - a ` ContextSources ` instance (containing provided [ value sources] ), and
212
+ - a ` handleDefault ` function responsible for the default value selection.
213
+
214
+ The method returns a context value constructed out of the provided value sources.
215
+
216
+
217
+ #### Value Sources
218
+
219
+ [ value sources ] : #value-sources
220
+
221
+ Instead of the values themselves, the registry allows to provide value sources. Those are used by [ ContextKey.merge()]
222
+ method to construct the value.
223
+
224
+ There could be many sources per single value. And they could be of a type different from the final value.
225
+
226
+ The sources are passed to the ` merge() ` function as an [ AIterable] instance. The latter is an enhanced ` Iterable ` with
227
+ Array-like API, including ` map() ` , ` flatMap() ` , ` forEach() ` , and other methods.
228
+
229
+ [ AIterable] : https://www.npmjs.com/package/a-iterable
230
+
231
+ ``` typescript
232
+ import {
233
+ AbstractContextKey ,
234
+ ContextRegistry ,
235
+ ContextSources ,
236
+ ContextValues ,
237
+ Handler ,
238
+ DefaultContextValueHandler
239
+ } from ' context-values' ;
240
+
241
+ class ConcatContextKey <V > extends AbstractContextKey <V , string > {
242
+
243
+ constructor (name : string ) {
244
+ super (name );
245
+ }
246
+
247
+ merge(
248
+ context : ContextValues ,
249
+ sources : ContextSources <string >,
250
+ handleDefault : DefaultContextValueHandler <string >): string | null | undefined {
251
+
252
+ const result = sources .reduce ((p , s ) => p != null ? ` ${p }, ${s } ` : ` ${s } ` , null );
253
+
254
+ if (result != null ) {
255
+ return result ;
256
+ }
257
+
258
+ return handleDefault (() => ' ' ); // No sources provided. Returning empty string, unless a fallback value provided.
259
+ }
260
+
261
+ }
262
+
263
+ const key1 = new ConcatContextKey <number >(' my-numbers' );
264
+ const key2 = new ConcatContextKey <string >(' my-string' );
265
+
266
+ const registry = new ContextRegistry ();
267
+
268
+ registry .provide ({ a: key1 , is: 1 });
269
+ registry .provide ({ a: key1 , is: 2 });
270
+ registry .provide ({ a: key1 , is: 3 });
271
+
272
+ const context = registry .newValues ();
273
+
274
+ context .get (key1 ); // '1, 2, 3' - concatenated value
275
+ context .get (key2 ); // '' - empty string by default
276
+ context .get (key2 , { or: undefined }); // undefined - fallback value
277
+ ```
278
+
279
+ A context value for particular key is constructed at most once. Thus, the ` merge() ` method is called at most once per
280
+ key.
281
+
282
+ A [ context value specifier] ( #context-value-specifier ) is consulted at most once per key. And only when the ` merge() `
283
+ method requested the source value. So, for example, if multiple sources specified for the same ` SingleContextKey ` , only
284
+ the last one will be constructed and used as a context value. The rest of them won't be constructed at all.
0 commit comments