Sfioc
Inversion of Control container for Node.JS. Inspired by awilix.
Installation
With npm
:
npm install sfioc --save
Usage
You need to do three basic things: create the container, register some modules in it, and then resolve the one you need and use it.
Here is an example application code.
// Imagine that our app has an internal store...const appInternalStore = isLoggedIn: false currentUser: null // ... and we have a database that we will connect to.const ourDatabase = users: id: 1 name: `Lieutenant` id: 2 name: 'Colonel' secretData: 42 // Let's create repo that depends on our database... // Dependencies will be injected in the constructor. { thisdb = database } { const user = thisdbusers return { } } { return { } } // ... create some app operations that depends on our Repo and store.// Here dependencies will be injected inside the function.const login = { // This nested function will be used by our 'app' module after resolving... return { // ... and we will be able to access the dependencies from here. repo }} // One more operation.const showSecretData = { return { repo }} // Finally let's create a factory function with our entry point.const appFactory = { return start { if !storeisLoggedIn try await catch err console return console }} // Create the container.const container = sf // Register our app modules in the container.container // We've set everything up. Let's resolve our 'app' module.const app = container// Same as:// const app = container.get.app // Welcome, Lieutenant!// Your secret data is: 42const userId = 1appstartuserId // Could not find user!const wrongUserId = 42appstartwrongUserId
Injection modes
The injection mode determines how a function/constructor receives its dependencies.
Sfioc supports two injection modes: CLASSIC
and PROXY
.
-
InjectionMode.CLASSIC
: In this case you need to explicitly specify which components each component depends on usingdependsOn
option.{thisemailService = emailServicethislogger = logger}container -
InjectionMode.PROXY
: Injects a proxy to functions/constructors which looks like a regular object. In this case you don't need to explicitly specify dependencies.{thisemailService = emailServicethislogger = logger}container
CLASSIC
mode is slightly faster than PROXY
because it only reads the
dependencies from the constructor/function once, whereas accessing dependencies
on the Proxy may incur slight overhead for each resolve.
Lifetime management
Sfioc supports managing the lifetime of components. You can control whether objects are resolved and used once or cached for the lifetime of the process.
There are 2 lifetime types available.
Lifetime.TRANSIENT
: This is the default. The registration is resolved every time it is needed. This means if you resolve a class more than once, you will get back a new instance every time.Lifetime.SINGLETON
: The registration is always reused no matter what - that means that the resolved value is cached in the root container.
To register a module with a specific lifetime:
{} container // this is the samecontainer // or even shortercontainer
Components
Component is needed in order to wrap your module, specify options for it, and store them inside. This method is used to wrap modules and prepare them for further registration.
Groups
In addition to components you also have the ability to use groups. It's used to combine components and other groups, specify common parameters or/and namespace for them.
Imagine that you have some modules that can be assigned to the same group. For example: operations.
{} {} // Our operationsconst getUser = { return mockRepo} const sendGreetToUser = { return mailService} // Some controller that depends on operations // Sfioc generated a namespace for operations { thisoperations = operations; } { // You can access any operation through this namespace const user = thisoperations thisoperations } const container = sf container
It's also possible to specify default options for nested components as well.
container containerregistrations'operations.getUser'lifetime // SINGLETONcontainerregistrations'operations.sendGreetToUser'lifetime // SINGLETON
Note: group options do not overwrite options of nested components, if they are specified.
container containerregistrations'operations.getUser'lifetime // TRANSIENTcontainerregistrations'operations.sendGreetToUser'lifetime // SINGLETON
You can register other groups within group as well.
API
sfioc
object
The When importing sfioc
, you get the following top-level API:
createContainer
component
group
Lifetime
ResolveAs
InjectionMode
createContainer
Creates a new Sfioc container.
Args:
options
: Options object. Optional.options.injectionMode
: Determines the method for resolving dependencies. Valid modes are:CLASSIC
: (default) Dependencies must be explicitly specified viadependsOn
option.PROXY
: Injects a proxy object in module that is able to resolve its dependencies.
options.componentOptions
: Global options for all components. They can be overwrited bycontainer.register
,sfioc.group
andsfioc.component
methods.
component
Used with container.register({ moduleName: component(module) })
. Wraps
dependencies and prepares them for further registration.
Args:
target
: Your dependency.options
: Options onject. Optional.-
options.resolveAs
: tells Sfioc hot to resolve given module. Valid params:ResolveAs.FUNCTION
,ResolveAs.CLASS
,ResolveAs.VALUE
. -
options.lifetime
: sets the target's lifetime. Valid params:Lifetime.SINGLETON
,Lifetime.TRANSIENT
. -
options.dependsOn
: sets the component dependencies. Accepts the string with dependency name, or array with dependency names.dependsOn
also accepts a callback that must return the dependency name, or an array of dependency names. Sfioc injects selectors with the names of registered modules in this callback. So if you registered, for examplefirst
andsecond
modules, you can specify a dependency on them in this way:// is the same as:Note: use this option only when the
CLASSIC
injection mode is selected. Otherwise this options is useless.
-
The returned component has the following chainable API:
component(module).resolveAs(resolveAs: string)
: same as theresolveAs
option.component(module).fn()
: same ascomponent(module).resolveAs(ResolveAs.FUNCTION)
component(module).class()
: same ascomponent(module).resolveAs(ResolveAs.CLASS)
component(module).value()
: same ascomponent(module).resolveAs(ResolveAs.VALUE)
component(module).setLifetime(lifetime: string)
: same as thelifetime
option.component(module).transient()
: same ascomponent(module).setLifetime(Lifetime.TRANSIENT)
component(module).singleton()
: same ascomponent(module).setLifetime(Lifetime.SINGLETON)
component(module).dependsOn(dependencies: string | array | function)
: same as thedependsOn
option.
group
Used with:
container
Combines components, specify common parameters or/and namespace for them.
Args:
elements
: An object with components or/and groups.options
: Default options for nested components and groups. (Same as component options)
The returned group has the following chainable API:
group(components).resolveAs(resolveAs: string)
: same as theresolveAs
option.group(components).fn()
: same asgroup(components).resolveAs(ResolveAs.FUNCTION)
group(components).class()
: same asgroup(components).resolveAs(ResolveAs.CLASS)
group(components).value()
: same asgroup(components).resolveAs(ResolveAs.VALUE)
group(components).setLifetime(lifetime: string)
: same as thelifetime
option.group(components).transient()
: same asgroup(components).setLifetime(Lifetime.TRANSIENT)
group(components).singleton()
: same asgroup(components).setLifetime(Lifetime.SINGLETON)
Lifetime
Constant used with lifetime
component options and related. It contains two
values: TRANSIENT
and SINGLETON
.
ResolveAs
Constant used with resolveAs
component options and related. It contains three
values: FUNCTION
, CLASS
and VALUE
.
InjectionMode
Constant used with sfioc.container
options. It contains two values: CLASSIC
and PROXY
.
sfioc.container
object
The The container returned from createContainer
has some methods and properties.
container.get
The get
is a proxy, and all getters will trigger a container.resolve
. The
get
is actually being passed to the constructor/factory function, which is
how everything gets wired up.
container.registrations
A read-only getter that returns the internal registrations.
container.cache
Used internally for caching resolutions.
container.options
Options passed to createContainer
are stored here.
container.resolve
Resolves the registration with the given name. Used by the get
.
container container === 42containergettest === 42
container.register
Registers modules or/and groups in the container.
There are multiple syntaxes for this function, you can pick the one you like the most, or combine them.
The register
method also accepts options for nested components and group as the
last possible argument.
// Register single componentcontainer // Same, but with optionscontainer // Register single groupcontainer // Same as abovecontainer // With single namespacecontainer // Same as abovecontainer // Same as abovecontainer // Classic registrationcontainer // Same as abovecontainer // Same as abovecontainer