Ankh

Inflight

function
Inflight()

simply provides operations for helping
inflight promises to handle concurrent calls to the
same service

function Inflight(decorators){
    this.decorators = decorators
    this.current = {}
}
Inflight.prototype.isLockable = function(key) {
    //decorators cant be locked,
    //otherwise duplicate decorators inside an `inject`
    //will always return the first instance
    return !this.decorators.isDecorator(key)
}
Inflight.prototype.lock = function(key,promise) {
    if(!this.isLockable(key)) {
        return promise
    }
    if(this.current[key]) {
        throw new Error(key + ' is already inflight.')
    }
    return (this.current[key] = promise)
}
Inflight.prototype.unlock = function(key, returnVal) {
    if(this.isLockable(key) && !this.current[key]) {
        throw new Error(key + ' is not currently inflight.')
    }
    ;(delete this.current[key])
    return returnVal
}
Inflight.prototype.peek = function(key) {
    return this.current[key]
}

Kernel

function
Kernel()

The workhorse for resolving dependencies

function Kernel(){
    this.registrations = new Registrations()
    this.decorators = new Decorators()
    this.resolvers = {}
    this.activators = {}
    this._inflight = new Inflight(this.decorators)
    registerSystemServices.call(this)
}

decorate

method
Kernel.prototype.decorate()

Instructs resolution of targetKey to be decorated by decoratorKey.

Kernel.prototype.decorate = function(targetKey, decoratorKey) {
    this.decorators.put(targetKey,decoratorKey)
    return this
}

register

method
Kernel.prototype.register()

Option name Type Description
model ComponentModel
return Kernel

Registers a {ComponentModel} with the kernel

Kernel.prototype.register = function(model) {
    this.registrations.put(model)
    return this
}

isRegistered

method
Kernel.prototype.isRegistered()

Option name Type Description
key String

The model key to test

return Boolean

true is the model is registered; otherwise, false

Convenience method for testing if a model exists with key

Kernel.prototype.isRegistered = function(key) {
    return this.registrations.has(key)
}

promise

method
Kernel.prototype.promise()

Resolve the Promise implementation ('@Promise') to begin a promise chain

Kernel.prototype.promise = function() {
    var promise = this.registrations.get('@Promise')
    return promise.impl.resolve(promise.impl)

}

addActivator

method
Kernel.prototype.addActivator()

Option name Type Description
key String

the key to use when registering an component that uses this

impl Function

the function constructor to use as an activator. This must expose an activate function that returns either a value (the instance of impl) or a {Promise} resolving the instance

return Kernel

Registers an custom activator for instantiating a service

Kernel.prototype.addActivator = function(key, impl) {
    this.activators[key] = impl
    return this
}

addResolver

method
Kernel.prototype.addResolver()

Option name Type Description
key String

the key to use when registering an component that uses this

impl Function

the function constructor to use as an resolver. This must expose an resolve function that returns either a value or a {Promise} resolving the instance

return Kernel

Registers an custom resolver for resolving a service

Kernel.prototype.addResolver = function(key, impl) {
    this.resolvers[key] = impl
    return this
}

createContext

method
Kernel.prototype.createContext()

Option name Type Description
model ResolveableComponentModel

the registered model

[deps] Object

Values passed in to .resolve to use during instantiation

return Object

with a model, the activator, the resolver, the Promise implementation, and deps arguments to pass into the instance

Creates a resolution context to be used at runtime

Kernel.prototype.createContext = function(model,deps) {
    if(!model) {
        throw new Error('model is required')
    }
    if(!model.isResolveable) {
        throw new Error('model must be resolveable')
    }
    var context = {
            model: model
            ,activator: model.activator()
            ,Promise: undefined
            ,resolver: model.resolver()
            ,deps: (deps || {})
        }
    return this.promise()
        .then(function(Promise){
            context.Promise = Promise
            return context
        })
}
Kernel.prototype._demandResolveable = function(key) {
    var model = this.registrations.get(key)
    if(!model) {
        throw new Error('model is required')
    }
    return this.promise().then(function(){
        if(model.isResolveable) {
            return model
        }
        var resolveable = new ResolveableComponentModel(model)
        return resolveable.prepare(this)
            .then(this._decorate.bind(this))
            .then(this.register.bind(this))
            .then(function(){
                return resolveable
            })
    }.bind(this))
}
Kernel.prototype._decorate = function(resolveable) {
    //decorate if possible
    var decorators = this.decorators.get(resolveable.key)
        .map(this._demandResolveable.bind(this))

    //early return (returns value, not promise)
    if(!decorators.length) {
        return resolveable
    }
    return this.promise()
        .then(function(Promise){
            if(!decorators.length){
                //no decorators configured
                return resolveable
            }
            return Promise.all(decorators)
                .then(function(resolvedDecorators){
                    //replace resolver with decorating resolver
                    resolveable.resolver(new DecoratingResolver(resolveable
                        ,resolvedDecorators
                        ,this))
                    return resolveable
                }.bind(this))
        }.bind(this))

}

_resolveContext

method
Kernel.prototype._resolveContext()

Perform resolution given a resolveContext object
(from createContext)

Kernel.prototype._resolveContext = function(context) {
    var model = context.model
        ,deps = context.deps
        ,inject = model.inject
    return this.resolveAll(inject,deps)
        .then(function doResolve(resolvedDeps){
            try {
                var finish = context.Promise.resolve
                if(!finish) {
                    throw new Error('missing `resolve` on ' + model.key + ' resolver.')
                }
                return finish(context.resolver.resolve(context, resolvedDeps))
            } catch(err) {
                error('error trying to resolve',model.key,err.message,err.stack)
                throw err
            }
        }.bind(this))
        .then(this._initializeInstance.bind(this,context,deps))
}
Kernel.prototype._initializeInstance = function(context,deps,instance){
    var initializable = context.model.initializable
        ,Promise = context.Promise
        ,deps = deps
        ;
    if(!initializable) {
        return instance
    }
    var fn = instance[initializable]
    if(!fn){
        throw new Error(context.model.key + ' was designated as initializable, but ' +
                        initializable + ' function could not be located.')
    }
    return this.invoke(instance, fn, deps)
        .then(function(result){
            return instance
        })
}

resolve

method
Kernel.prototype.resolve()

Option name Type Description
key String

the service name to resolve

deps Object

a key/value map to use for dynamically passing arguments into the resolved instance.

return Promise

resolving the service

Resolves a service by key while passing in the deps as values

Kernel.prototype.resolve = function(key, deps) {
    var p  = this._inflight.peek(key)
    if(p) {
        return p
    }
    //lazily make the component resolveable
    p = this._demandResolveable(key)
        .then(function(model){
            return this.createContext(model, deps)
                .then(this._resolveContext.bind(this))
                .then(this._inflight.unlock.bind(this._inflight,key))
        }.bind(this))
    return this._inflight.lock(key,p)
}

resolveAll

method
Kernel.prototype.resolveAll()

Option name Type Description
keys Array

Array of string keys to resolve

deps Object

Key/value map to use, just like in resolve method

return Promise

resolving all instances of the services in order of keys

Resolves all dependencies in keys, using deps to pass into each dependency

Kernel.prototype.resolveAll = function(keys,deps) {
    keys = (keys || [])
    deps = (deps || {})
    var elements = keys.map(function mapKeys(key){
        //if the passed in deps is found, then use that value
        //even if it is falsy
        if(Object.hasOwnProperty.call(deps,key)) {
            return deps[key]
        }
        return this.resolve(key,deps)
    }.bind(this))
    return this.promise()
        .then(function(Promise){
            return Promise.all(elements)
        })
}

invoke

method
Kernel.prototype.invoke()

Option name Type Description
context Any

The this to use during invocation.

fn Function

The function to invoke

[deps] Object

The key/value map to use, just like in resolve method

return Any

The result of the invocation

Kernel.prototype.invoke = function(context, fn, deps){
    assert(context,'context is required. this is the context for invoking the fn.')
    assert(fn,'fn is required')

    return this.resolveAll(fn.inject || [], deps)
        .then(function(resolvedDeps){
            return fn.apply(context,resolvedDeps)
        })

}

_startInstances

method
Kernel.prototype._startInstances()

  1. order start fns by their .inject statements, appending non-dependents according to
    their original ordering (from registrations)
  2. invoke each fn in serial
Kernel.prototype._startInstances = function(startableModels, resolved) {
    //convenience
    var lookup = {}
    var startableKeys = startableModels.map(function(model){
        return model.key
    })
    var startables = startableModels.map(function(model,index){
        var startable = new StartableModel(model,resolved[index])
        lookup[startable.key] = startable
        return startable
    })

    var include = startableKeys
        ,exclude = Registrations.RESERVED_KEYS
        ,map = startables
            .filter(function(model){
                //only graph those models which have deps
                return model.hasDeps
            })
            .reduce(function(m,model) {
                m[model.key] = model.inject //start fn inject
                return m
            },{})
    var graph = new DependencyGraph(map,include,exclude)
    //map to invocation
    //placing those startables without dependencies LAST
    //to give their dependents a change to consume them
    var promises = graph.legal()
        //reverse the ordering so that dependents can manipulate their dependencies
        .reverse()
        .concat(startableKeys)
        .filter(function(key,index,arr){
            //unique
            return arr.indexOf(key) === index
        })
        .map(function(key){
            return lookup[key]
        })
        .map(function(startable){
         return startable.start.bind(startable,this.invoke.bind(this))
        },this)
        ;
    //invoke them in series
    return chain(promises,this.promise())
}

start

method
Kernel.prototype.start()

Starts the container, invoking all startable implementations.
While registrations provides startable models ordered by their
model inject settings, this further resolves each of these models
and orders their respective startable functions according to
their inject settings. Startable functions which do not have dependencies
are appended to the end of execution order.
Finally, each startable function is invoked (in series) finally
resolving this kernel instance.

Kernel.prototype.start = function(){
    var startableModels = this.registrations.startables()
    var keys = startableModels.map(function(model){
        return model.key
    })
    //first resolve all the default ordered
    return this.resolveAll(keys)
        .then(this._startInstances.bind(this,startableModels))
        .then(function(){
            return this
        }.bind(this))
}

Kernel.prototype.validate = function() {
    return this.registrations.validate()
}