@proc7ts/context-values
v7.1.1
Published
IoC context values provider
Downloads
1,884
Readme
IoC Context Values
IoC context allows accessing its values identified by their entries and provided somewhere else.
Features:
Type-safe API.
Each context entry has definite value type specified as generic type parameter. So, when accessing context value with get() method, the returned value has that exact type, rather
any
.Clean distinction between assets provided for context entries, and their values.
E.g. entry value is an array, while its assets are elements of this array.
Customizable relation between entry assets and its value.
This relation established by entry implementation. Typically, the entry builds its value out of its assets.
Reusable context entry implementations suitable for the majority of use cases.
E.g. cxSingle() for single-valued entries, or cxArray() for array-valued ones.
Dynamically updatable context values.
The cxSingle() and cxArray() entries build the value once on the first access, and return it on each subsequent access. In contrast, the cxRecent() and cxDynamic() are able to track asset changes and reflect these changes in entry value.
Context derivation.
A context may derive another context to make the values from the latter available in the former.
Project Modules
- @proc7ts/context-values library declares context API, and provides basic context entries implementations.
- @proc7ts/context-builder builds IoC contexts, and provides basic context entry assets implementations.
- @proc7ts/context-modules brings support for dynamically loadable (and un-loadable) context modules.
Accessing Context Values
An IoC context implements a CxValues interface. This interface declares a single get() method accepting requested entry instance (CxEntry), and optional request (CxRequest).
The following code returns a string value of myEntry
entry, or throws an exception if the entry has no value.
import { CxEntry, cxSingle } from '@proc7ts/context-values';
const myEntry: CxEntry<string> = {
perContext: cxSingle(),
toString: () => '[CxEntry myEntry]',
};
myContext.get(myEntry);
Fallback Value
Normally, if context entry has no value, an exception thrown. To avoid this, a fallback value can be provided. It will be returned in case there is no value.
import { CxEntry, cxSingle } from '@proc7ts/context-values';
const myEntry: CxEntry<string> = {
perContext: cxSingle(),
toString: () => '[CxEntry myEntry]',
};
myContext.get(myEntry, { or: 'empty' }); // 'empty'
A null
value can be used as a fallback one.
Default Value
Context entry may have a default value that will be returned, unless the entry has an explicitly provided one or a fallback value specified. It is possible to request the default value directly:
import { CxEntry, CxRequestMethod, cxSingle } from '@proc7ts/context-values';
const myEntry: CxEntry<string> = {
perContext: cxSingle({ byDefault: () => 'default' }),
toString: () => '[CxEntry myEntry]',
};
myContext.get(myEntry, { by: CxRequestMethod.Defaults }); // 'default'
It is also possible to request explicitly provided value with { by: CxRequestMethod.Assets }
request.
Request Callback
In addition to returning from get() method call, the context value can be passed to a callback:
import { CxEntry, CxRequestMethod, cxSingle } from '@proc7ts/context-values';
const myEntry: CxEntry<string> = {
perContext: cxSingle({ byDefault: () => 'default' }),
toString: () => '[CxEntry myEntry]',
};
let myValue: string | undefined;
let myValueBy: CxRequestMethod | undefined;
myContext.get(myEntry, { set: (value, by) => { myValue = value; myValueBy = my });
// myValue === 'default'
// myValueBy === CxRequestMethod.Defaults
The callback won't be called if the entry has no value.
Note that this is the only way to know the origin of the value received. I.e. whether it is
- an explicitly provided value (
CxRequestMethod.Assets
), - a default value (
CxRequestMethod.Defaults
), or - a fallback value (
CxRequestMethod.Fallback
).
Providing Context Values
The @proc7ts/context-builder contains everything needed to build a context and to provide assets for it.
import { CxBuilder, cxBuildAsset, cxConstAsset } from '@proc7ts/context-builder';
import { CxEntry, cxSingle } from '@proc7ts/context-values';
const entry1: CxEntry<string> = { perContext: cxSingle(), toString: () => '[CxEntry entry1]' };
const entry2: CxEntry<number> = { perContext: cxSingle(), toString: () => '[CxEntry entry2]' };
// Create context builder.
const cxBuilder = new CxBuilder(get => ({ get }) /* create context instance with `get` method */);
// Provide asset for `entry1` as constant.
cxBuilder.provide(cxConstAsset(entry1, 'string'));
// Evaluate asset for `entry2` as the length `entry1` value.
cxBuilder.provide(cxBuildAsset(entry2, target => target.get(entry1).length));
// Obtain context instance.
const context = registry.context;
// Obtain context values.
context.get(entry1); // 'string'
context.get(entry2); // 6
Context Entry
Context entry identifies the value to obtain when passed to context's get() method. It is also responsible for combining provided assets into context value.
Context entry implements CxEntry interface containing a single perContext
method. It also a good practice to
override a toString()
method, so that error messages contain a string representation of the entry.
There are several standard customizable CxEntry implementations. It is typically enough to use one of them:
- cxArray() - Lazily evaluated array-valued context entry.
- cxDynamic() - Dynamically updating context entry reflecting the changes in entry assets.
- cxEvaluated() - Context entry with value lazily evaluated by the given function.
- cxRecent() - Dynamically updating context entry reflecting the changes in the most recent entry asset.
- cxSingle() - Lazily evaluated single-valued context entry.
These functions create entry definers. I.e. functions that can be used as perContext
implementations.
import { CxBuilder, cxConstAsset } from '@proc7ts/context-builder';
import { cxDynamic, CxEntry } from '@proc7ts/context-values';
// Context value is a readonly array of strings,
// wwhile its assets are stringhs.
const myEntry: CxEntry<readonly string[], string> = {
perContext: cxDynamic(),
toString: () => '[CxEntry myEntry]',
};
const cxBuilder = new CxBuilder(get => ({ get }));
const context = cxBuilder.context;
context.get(myEntry); // []
// Provide first asset.
const supply = cxBuilder.provide(cxConstAsset(myEntry, 'foo'));
context.get(myEntry); // ['foo']
// Provide second asset.
cxBuilder.provide(cxConstAsset(myEntry, 'bar'));
context.get(myEntry); // ['foo', 'bar']
// Revoke the first asset.
supply.off();
context.get(myEntry); // ['bar']
Context Scopes
When combining multiple contexts (i.e. making one context derive another one), it is often required to obtain some entry from particular context only.
To make it possible a context may provide itself as its own context value. Then the entry implementation may obtain this context, and then obtain its value from that context.
A context entry containing a context as its value called scope. There are two context entry implementations able to handle scopes:
- cxScoped() - Scoped context entry.
- cxDefaultScoped() - Context entry with scoped default value.