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/Etherand 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(aSymbolwith some type info actually) with invokingEther.newEtherSymbolwith a type parameter of your type definition (mostly yourinterfaces). 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 callingnewEtherfunction. 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
Etherexpresses the needed dependencies as type information. TheEther<D, T>type means that it can construct an object of typeTby using dependenciesD(key-value type of each variable name and symbol).Using
Ether.composefunction, you can inject anEtherinto anotherEther. If it is a required dependency actually,Dof injected will be reduced only the entry corresponding to the symbol of injecting.Only no dependencies required, you can get the object of type
TbyEther.runEtherfunction. When dependencies became empty applyingcompose,Dwill 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
Etherbecause it occurs an infinite recursion.Conclusion
You can use
Ether's system as the following steps.newEtherSymbol.Etherobjects corresponding the symbol types withnewEther.Ethers into anEtherwithcompose.Catis useful to do it.runEther, or fix type errors about dependencies.EtherTfor dependencies over a monad such asPromiseIf
Etheris all, the dependency which can be built only inPromisesuch 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 ofnewEtherTandrunEtherTfunction is same as the functions forEther.Only
composeTinjecting 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 normalEtherandEtherT, you need to convertEthers intoEtherTs of same monad environmentMwithliftEther.