You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

README.md 5.3KB

flitter-di

Agnostic, unopinionated dependency-injection for Node

The Flitter framework is a sophisticated, service-based framework for building web applications. In an attempt to keep the functionality provided by this framework modular and reusable, its functionality is broken down into services called Units. These Units are run in a particular order to start your Flitter application.

Flitter-DI is the second-generation dependency-injector used by Flitter to make this work. It was pulled from the libflitter project in the hope that it can remain independent from the rest of Flitter and can be used in other projects.

Injectable classes specify a static array of service names. Then, these static classes are run through the DependencyInjector#make() method which injects instances of the requested services. Flitter-DI supports a robust, single-instance, cyclically-dependent service structure.

Quick Start

Install with Yarn

yarn add flitter-di

Create a Service

As a trivial example, we’re going to define a hello-world service to greet the server:

const { Service } = require('flitter-di')

class HelloWorldService extends Service {
    hi() {
        console.log('Hello, World!')
    }
}

Create the Container & Dependency Injector

Now, we need to create a service container with our HelloWorldService class. We’re going to set the name of that service to hello_world:

const { Container, DependencyInjector } = require('flitter-di')

const container = new Container({
    hello_world: require('./HelloWorld.service')
})

const di = new DependencyInjector(container)

Use!

Now, we can use that shiny new dependency injector to access services from Injectable classes:

const { Injectable } = require('flitter-di')
class SomeUsefulClass extends Injectable {
    static get services() {
        return ['hello_world']
    }
    
    run() {
        this.hello_world.hi()
    }
}

Back in our start code:

const SomeUsefulClass = di.make(require('./SomeUsefulClass'))
const useful = new SomeUsefulClass()

useful.run() // Hello, World!

Alternatively, we can fetch services from the dependency injector directly:

// Fetch the service directly
di.service('hello_world').hi() // Hello, World!

// Or use the service object
const service = di.service()
service.hello_world.hi() // Hello, World!

Circular Dependencies

For the curious, yes. Flitter-DI will resolve circular dependencies. It’s still not recommended however:

const { Container, DependencyInjector, Service } = require('flitter-di')

class A extends Service {
    static get services() {
        return ['b']
    }
}

class B extends Service {
    static get services() {
        return ['a']
    }
}

const container = new Container({ a: A, b: B })
const di = new DependencyInjector(container)

const service = di.service()

service.a // A { }
service.a.b // B { }
service.b.a // A { }

service.a.foo = "Bar" // A { foo: "Bar" }
service.a.b.a.b.a.b.a.foo // "Bar"

Deferred Services

Sometimes, it may be that services don’t always load in an order that ensures that all injectable classes are loaded after the services themselves. In these cases, Flitter DI will defer the injection of the missing services until they are registered with the service container. When they are registered, the class will be injected with those services, as well as any instances of the class that were created before the service was registered. A basic example:

const { Service, Injectable, DependencyInjector } = require('flitter-di')

class A extends Service {
    number = 3.141
}

class B extends Service {
    number = (22/7)
}

class MyClass extends Injectable {
    static get services() {
        return ['a', 'b']
    }
}

const di = new DependencyInjector()
di.register('a', A)

di.make(MyClass)
const my_class = new MyClass()

console.log(my_class.a) // => A { number: 3.141 }
console.log(my_class.b) // => B { }

di.register('b', B)

console.log(my_class.a) // => A { number: 3.141 }
console.log(my_class.b) // => B { number: 3.142 }

// Note that you can disable deferred injection like so:
class BuzzKill extends Injectable {
    static _di_allow_defer = false
}

License

Copyright 2019 Garrett Mills

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.