This aims resolving dependencies and notifying lacks by type errors, by representing dependencies required by an object or a function to work as typing.
On Ether's system, at first creates a tag used by Ether (a Symbol with some type info actually) with invoking Ether.newEtherSymbol with a type parameter of your type definition (mostly your interfaces). In this way, let's make symbols for each abstract such as database persistence, randomized value generator, communication, and decision meditator. Even type parameters are same, they are treated as different if unequal symbols.
For instance, representing two "Repository which persists articles ArticleRepository" and "Request handler which decides and registers (req: Req) => Promise<void>" will be the following code:
For the symbols defined above, let's make the actual instances satisfying requirements usable on Ether's system. You can do this by calling newEther function. For a symbol, you can define any objects Ether<D, T> satisfying the underlying type T.
At the first parameter, you pass the symbol to associate. And at the second parameter, you pass the function to make an object satisfying the requirement T. This function will provide objects depended by others.
At the optional third parameter, you specify the dependencies with name as key and value as the symbol corresponding to its type. In the function that you passed at the second parameter, the resolved dependencies which specified at the third parameter will be passed.
Ether expresses the needed dependencies as type information. The Ether<D, T> type means that it can construct an object of type T by using dependencies D (key-value type of each variable name and symbol).
Using Ether.compose function, you can inject an Ether into another Ether. If it is a required dependency actually, D of injected will be reduced only the entry corresponding to the symbol of injecting.
// `Cat` is useful to inject multiple dependencies. constmultiInjected = Cat.cat(service) .feed(Ether.compose(mockRepository)) .feed(Ether.compose(otherService)) .feed(Ether.compose(xorShiftRng)) .value;
// It will occur type errors if the dependencies not enough. constresolved = Ether.runEther(multiInjected);
Only no dependencies required, you can get the object of type T by Ether.runEther function. When dependencies became empty applying compose, D will be equivalent to Record<string, never> and you can use Ether.runEther. Conversely, if the dependencies are not enough, it will occurs type errors and shows the variable name of lacking dependency.
However, you can't represent the circular dependencies with Ether because it occurs an infinite recursion.
If Ether is all, the dependency which can be built only in Promise such as cryptography module doesn't work well. If the built object is wrapped in a Promise, extracting it is so annoying.
In the situation, EtherT<D, M, T> which is the monad transformer version of Ether<D, T> helps you. This allows you to handle dependencies wrapped in a monad without notice of wrapped in, by the monad power.
For example, describing the case building over Promise. Usage of newEtherT and runEtherT function is same as the functions for Ether.
Only composeT injecting an dependency, it is modified to require the monad instance as a parameter. When injecting dependencies over Promise, you will pass the monad instance of Promise. And mixing the normal Ether and EtherT, you need to convert Ethers into EtherTs of same monad environment M with liftEther.
This package provides a dependency container combinator
EtherT
/Ether
and associated functions.Guide
This aims resolving dependencies and notifying lacks by type errors, by representing dependencies required by an object or a function to work as typing.
Symbol association and type information
On
Ether
's system, at first creates a tag used byEther
(aSymbol
with some type info actually) with invokingEther.newEtherSymbol
with a type parameter of your type definition (mostly yourinterface
s). In this way, let's make symbols for each abstract such as database persistence, randomized value generator, communication, and decision meditator. Even type parameters are same, they are treated as different if unequal symbols.For instance, representing two "Repository which persists articles
ArticleRepository
" and "Request handler which decides and registers(req: Req) => Promise<void>
" will be the following code:Dependencies definition and receiving other dependencies over
For the symbols defined above, let's make the actual instances satisfying requirements usable on
Ether
's system. You can do this by callingnewEther
function. For a symbol, you can define any objectsEther<D, T>
satisfying the underlying typeT
.At the first parameter, you pass the symbol to associate. And at the second parameter, you pass the function to make an object satisfying the requirement
T
. This function will provide objects depended by others.At the optional third parameter, you specify the dependencies with name as key and value as the symbol corresponding to its type. In the function that you passed at the second parameter, the resolved dependencies which specified at the third parameter will be passed.
Dependency injection process and condition to work
Ether
expresses the needed dependencies as type information. TheEther<D, T>
type means that it can construct an object of typeT
by using dependenciesD
(key-value type of each variable name and symbol).Using
Ether.compose
function, you can inject anEther
into anotherEther
. If it is a required dependency actually,D
of injected will be reduced only the entry corresponding to the symbol of injecting.Only no dependencies required, you can get the object of type
T
byEther.runEther
function. When dependencies became empty applyingcompose
,D
will be equivalent toRecord<string, never>
and you can useEther.runEther
. Conversely, if the dependencies are not enough, it will occurs type errors and shows the variable name of lacking dependency.However, you can't represent the circular dependencies with
Ether
because it occurs an infinite recursion.Conclusion
You can use
Ether
's system as the following steps.newEtherSymbol
.Ether
objects corresponding the symbol types withnewEther
.Ether
s into anEther
withcompose
.Cat
is useful to do it.runEther
, or fix type errors about dependencies.EtherT
for dependencies over a monad such asPromise
If
Ether
is all, the dependency which can be built only inPromise
such as cryptography module doesn't work well. If the built object is wrapped in aPromise
, extracting it is so annoying.In the situation,
EtherT<D, M, T>
which is the monad transformer version ofEther<D, T>
helps you. This allows you to handle dependencies wrapped in a monad without notice of wrapped in, by the monad power.For example, describing the case building over
Promise
. Usage ofnewEtherT
andrunEtherT
function is same as the functions forEther
.Only
composeT
injecting an dependency, it is modified to require the monad instance as a parameter. When injecting dependencies overPromise
, you will pass the monad instance ofPromise
. And mixing the normalEther
andEtherT
, you need to convertEther
s intoEtherT
s of same monad environmentM
withliftEther
.