Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| var mutate = require('xtend/mutable') | |
| var nanotick = require('nanotick') | |
| var assert = require('assert') | |
| var xtend = require('xtend') | |
| var applyHook = require('./apply-hook') | |
| module.exports = dispatcher | |
| // initialize a new barracks instance | |
| // obj -> obj | |
| function dispatcher (hooks) { | |
| hooks = hooks || {} | |
| assert.equal(typeof hooks, 'object', 'barracks: hooks should be undefined or an object') | |
| var onStateChangeHooks = [] | |
| var onActionHooks = [] | |
| var onErrorHooks = [] | |
| var subscriptionWraps = [] | |
| var initialStateWraps = [] | |
| var reducerWraps = [] | |
| var effectWraps = [] | |
| use(hooks) | |
| var reducersCalled = false | |
| var effectsCalled = false | |
| var stateCalled = false | |
| var subsCalled = false | |
| var stopped = false | |
| var subscriptions = start._subscriptions = {} | |
| var reducers = start._reducers = {} | |
| var effects = start._effects = {} | |
| var models = start._models = [] | |
| var _state = {} | |
| var tick = nanotick() | |
| start.model = setModel | |
| start.state = getState | |
| start.start = start | |
| start.stop = stop | |
| start.use = use | |
| return start | |
| // push an object of hooks onto an array | |
| // obj -> null | |
| function use (hooks) { | |
| assert.equal(typeof hooks, 'object', 'barracks.use: hooks should be an object') | |
| assert.ok(!hooks.onError || typeof hooks.onError === 'function', 'barracks.use: onError should be undefined or a function') | |
| assert.ok(!hooks.onAction || typeof hooks.onAction === 'function', 'barracks.use: onAction should be undefined or a function') | |
| assert.ok(!hooks.onStateChange || typeof hooks.onStateChange === 'function', 'barracks.use: onStateChange should be undefined or a function') | |
| if (hooks.onStateChange) onStateChangeHooks.push(hooks.onStateChange) | |
| if (hooks.onError) onErrorHooks.push(wrapOnError(hooks.onError)) | |
| if (hooks.onAction) onActionHooks.push(hooks.onAction) | |
| if (hooks.wrapSubscriptions) subscriptionWraps.push(hooks.wrapSubscriptions) | |
| if (hooks.wrapInitialState) initialStateWraps.push(hooks.wrapInitialState) | |
| if (hooks.wrapReducers) reducerWraps.push(hooks.wrapReducers) | |
| if (hooks.wrapEffects) effectWraps.push(hooks.wrapEffects) | |
| if (hooks.models) hooks.models.forEach(setModel) | |
| } | |
| // push a model to be initiated | |
| // obj -> null | |
| function setModel (model) { | |
| assert.equal(typeof model, 'object', 'barracks.store.model: model should be an object') | |
| models.push(model) | |
| } | |
| // get the current state from the store | |
| // obj? -> obj | |
| function getState (opts) { | |
| opts = opts || {} | |
| assert.equal(typeof opts, 'object', 'barracks.store.state: opts should be an object') | |
| var state = opts.state | |
| if (!opts.state && opts.freeze === false) return xtend(_state) | |
| else if (!opts.state) return Object.freeze(xtend(_state)) | |
| assert.equal(typeof state, 'object', 'barracks.store.state: state should be an object') | |
| var namespaces = [] | |
| var newState = {} | |
| // apply all fields from the model, and namespaced fields from the passed | |
| // in state | |
| models.forEach(function (model) { | |
| var ns = model.namespace | |
| namespaces.push(ns) | |
| var modelState = model.state || {} | |
| if (ns) { | |
| newState[ns] = newState[ns] || {} | |
| apply(ns, modelState, newState) | |
| newState[ns] = xtend(newState[ns], state[ns]) | |
| } else { | |
| mutate(newState, modelState) | |
| } | |
| }) | |
| // now apply all fields that weren't namespaced from the passed in state | |
| Object.keys(state).forEach(function (key) { | |
| if (namespaces.indexOf(key) !== -1) return | |
| newState[key] = state[key] | |
| }) | |
| var tmpState = xtend(_state, xtend(state, newState)) | |
| var wrappedState = wrapHook(tmpState, initialStateWraps) | |
| return (opts.freeze === false) | |
| ? wrappedState | |
| : Object.freeze(wrappedState) | |
| } | |
| // initialize the store hooks, get the send() function | |
| // obj? -> fn | |
| function start (opts) { | |
| opts = opts || {} | |
| assert.equal(typeof opts, 'object', 'barracks.store.start: opts should be undefined or an object') | |
| // register values from the models | |
| models.forEach(function (model) { | |
| var ns = model.namespace | |
| if (!stateCalled && model.state && opts.state !== false) { | |
| var modelState = model.state || {} | |
| if (ns) { | |
| _state[ns] = _state[ns] || {} | |
| apply(ns, modelState, _state) | |
| } else { | |
| mutate(_state, modelState) | |
| } | |
| } | |
| if (!reducersCalled && model.reducers && opts.reducers !== false) { | |
| apply(ns, model.reducers, reducers, function (cb) { | |
| return wrapHook(cb, reducerWraps) | |
| }) | |
| } | |
| if (!effectsCalled && model.effects && opts.effects !== false) { | |
| apply(ns, model.effects, effects, function (cb) { | |
| return wrapHook(cb, effectWraps) | |
| }) | |
| } | |
| if (!subsCalled && model.subscriptions && opts.subscriptions !== false) { | |
| apply(ns, model.subscriptions, subscriptions, function (cb, key) { | |
| var send = createSend('subscription: ' + (ns ? ns + ':' + key : key)) | |
| cb = wrapHook(cb, subscriptionWraps) | |
| cb(send, function (err) { | |
| applyHook(onErrorHooks, err, _state, createSend) | |
| }) | |
| return cb | |
| }) | |
| } | |
| }) | |
| // the state wrap is special because we want to operate on the full | |
| // state rather than indvidual chunks, so we apply it outside the loop | |
| if (!stateCalled && opts.state !== false) { | |
| _state = wrapHook(_state, initialStateWraps) | |
| } | |
| if (!opts.noSubscriptions) subsCalled = true | |
| if (!opts.noReducers) reducersCalled = true | |
| if (!opts.noEffects) effectsCalled = true | |
| if (!opts.noState) stateCalled = true | |
| if (!onErrorHooks.length) onErrorHooks.push(wrapOnError(defaultOnError)) | |
| return createSend | |
| // call an action from a view | |
| // (str, bool?) -> (str, any?, fn?) -> null | |
| function createSend (selfName, callOnError) { | |
| assert.equal(typeof selfName, 'string', 'barracks.store.start.createSend: selfName should be a string') | |
| assert.ok(!callOnError || typeof callOnError === 'boolean', 'barracks.store.start.send: callOnError should be undefined or a boolean') | |
| return function send (name, data, cb) { | |
| if (!cb && !callOnError) { | |
| cb = data | |
| data = null | |
| } | |
| data = (typeof data === 'undefined' ? null : data) | |
| assert.equal(typeof name, 'string', 'barracks.store.start.send: name should be a string') | |
| assert.ok(!cb || typeof cb === 'function', 'barracks.store.start.send: cb should be a function') | |
| var done = callOnError ? onErrorCallback : cb | |
| _send(name, data, selfName, done) | |
| function onErrorCallback (err) { | |
| err = err || null | |
| if (err) { | |
| applyHook(onErrorHooks, err, _state, function createSend (selfName) { | |
| return function send (name, data) { | |
| assert.equal(typeof name, 'string', 'barracks.store.start.send: name should be a string') | |
| data = (typeof data === 'undefined' ? null : data) | |
| _send(name, data, selfName, done) | |
| } | |
| }) | |
| } | |
| } | |
| } | |
| } | |
| // call an action | |
| // (str, str, any, fn) -> null | |
| function _send (name, data, caller, cb) { | |
| if (stopped) return | |
| assert.equal(typeof name, 'string', 'barracks._send: name should be a string') | |
| assert.equal(typeof caller, 'string', 'barracks._send: caller should be a string') | |
| assert.equal(typeof cb, 'function', 'barracks._send: cb should be a function') | |
| ;(tick(function () { | |
| var reducersCalled = false | |
| var effectsCalled = false | |
| var newState = xtend(_state) | |
| if (onActionHooks.length) { | |
| applyHook(onActionHooks, _state, data, name, caller, createSend) | |
| } | |
| // validate if a namespace exists. Namespaces are delimited by ':'. | |
| var actionName = name | |
| if (/:/.test(name)) { | |
| var arr = name.split(':') | |
| var ns = arr.shift() | |
| actionName = arr.join(':') | |
| } | |
| var _reducers = ns ? reducers[ns] : reducers | |
| if (_reducers && _reducers[actionName]) { | |
| if (ns) { | |
| var reducedState = _reducers[actionName](_state[ns], data) | |
| newState[ns] = xtend(_state[ns], reducedState) | |
| } else { | |
| mutate(newState, reducers[actionName](_state, data)) | |
| } | |
| reducersCalled = true | |
| if (onStateChangeHooks.length) { | |
| applyHook(onStateChangeHooks, newState, data, _state, actionName, createSend) | |
| } | |
| _state = newState | |
| cb(null, newState) | |
| } | |
| var _effects = ns ? effects[ns] : effects | |
| if (!reducersCalled && _effects && _effects[actionName]) { | |
| var send = createSend('effect: ' + name) | |
| if (ns) _effects[actionName](_state[ns], data, send, cb) | |
| else _effects[actionName](_state, data, send, cb) | |
| effectsCalled = true | |
| } | |
| if (!reducersCalled && !effectsCalled) { | |
| throw new Error('Could not find action ' + actionName) | |
| } | |
| }))() | |
| } | |
| } | |
| // stop an app, essentially turns | |
| // all send() calls into no-ops. | |
| // () -> null | |
| function stop () { | |
| stopped = true | |
| } | |
| } | |
| // compose an object conditionally | |
| // optionally contains a namespace | |
| // which is used to nest properties. | |
| // (str, obj, obj, fn?) -> null | |
| function apply (ns, source, target, wrap) { | |
| if (ns && !target[ns]) target[ns] = {} | |
| Object.keys(source).forEach(function (key) { | |
| var cb = wrap ? wrap(source[key], key) : source[key] | |
| if (ns) target[ns][key] = cb | |
| else target[key] = cb | |
| }) | |
| } | |
| // handle errors all the way at the top of the trace | |
| // err? -> null | |
| function defaultOnError (err) { | |
| throw err | |
| } | |
| function wrapOnError (onError) { | |
| return function onErrorWrap (err, state, createSend) { | |
| if (err) onError(err, state, createSend) | |
| } | |
| } | |
| // take a apply an array of transforms onto a value. The new value | |
| // must be returned synchronously from the transform | |
| // (any, [fn]) -> any | |
| function wrapHook (value, transforms) { | |
| transforms.forEach(function (transform) { | |
| value = transform(value) | |
| }) | |
| return value | |
| } |