Skip to content

Commit

Permalink
use: add new method
Browse files Browse the repository at this point in the history
  • Loading branch information
yoshuawuyts committed Jul 21, 2016
1 parent b381a97 commit 6bc9b11
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 28 deletions.
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
[![Downloads][downloads-image]][downloads-url]

Action dispatcher for unidirectional data flows. Creates tiny models of data
that can be accessed through actions with an API of only 5 functions
that can be accessed through actions through a small API.

## Usage
````js
const barracks = require('barracks')

const store = barracks({
const store = barracks()

store.use({
onError: (err, state, createSend) => {
console.error(`error: ${err}`)
},
Expand Down Expand Up @@ -41,27 +43,32 @@ document.addEventListener('DOMContentLoaded', () => {
````

## API
### store = barracks(handlers)
Initialize a new `barracks` instance. Takes an optional object of handlers.
Handlers can be:
### store = barracks(hooks?)
Initialize a new `barracks` instance. Takes an optional object of hooks which
is passed to `.use()`.

### store.use(hooks)
Register new hooks on the store. Hooks are little plugins that can extend
behavior or perform actions at specific points in the lifecycle. The following
hooks are possible:
- __onError(err, state, createSend):__ called when an `effect` or
`subscription` emit an error. If no handler is passed, the default handler
will `throw` on each error.
- __onAction(data, state, name, caller, createSend):__ called when an
`action` is fired.
- __onStateChange(data, state, prev, caller, createSend):__ called after a reducer
changes the `state`.
`subscription` emit an error. If no hook is passed, the default hook will
`throw` on each error.
- __onAction(data, state, name, caller, createSend):__ called when an `action`
is fired.
- __onStateChange(data, state, prev, caller, createSend):__ called after a
reducer changes the `state`.

`createSend()` is a special function that allows the creation of a new named
`send()` function. The first argument should be a string which is the name, the
second argument is a boolean `callOnError` which can be set to `true` to call
the `onError` hook istead of a provided callback. It then returns a
`send(actionName, data?)` function.

Handlers should be used with care, as they're the most powerful interface into
Hooks should be used with care, as they're the most powerful interface into
the state. For application level code it's generally recommended to delegate to
actions inside models using the `send()` call, and only shape the actions
inside the handlers.
inside the hooks.

### store.model()
Register a new model on the store. Models are optionally namespaced objects
Expand Down
56 changes: 41 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ module.exports = dispatcher

// initialize a new barracks instance
// obj -> obj
function dispatcher (handlers) {
handlers = handlers || {}
assert.equal(typeof handlers, 'object', 'barracks: handlers should be undefined or an object')
function dispatcher (hooks) {
hooks = hooks || {}
assert.equal(typeof hooks, 'object', 'barracks: hooks should be undefined or an object')

const onError = wrapOnError(handlers.onError || defaultOnError)
const onAction = handlers.onAction
const onStateChange = handlers.onStateChange
const onStateChangeHooks = []
const onActionHooks = []
const onErrorHooks = []

assert.ok(!handlers.onError || typeof handlers.onError === 'function', 'barracks: onError should be undefined or a function')
assert.ok(!onAction || typeof onAction === 'function', 'barracks: onAction should be undefined or a function')
assert.ok(!onStateChange || typeof onStateChange === 'function', 'barracks: onStateChange should be undefined or a function')
useHooks(hooks)

var reducersCalled = false
var effectsCalled = false
Expand All @@ -32,8 +30,22 @@ function dispatcher (handlers) {
start.model = setModel
start.state = getState
start.start = start
start.use = useHooks
return start

// push an object of hooks onto an array
// obj -> null
function useHooks (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.onError) onErrorHooks.push(wrapOnError(hooks.onError))
if (hooks.onAction) onActionHooks.push(hooks.onAction)
if (hooks.onStateChange) onStateChangeHooks.push(hooks.onStateChange)
}

// push a model to be initiated
// obj -> null
function setModel (model) {
Expand Down Expand Up @@ -68,7 +80,7 @@ function dispatcher (handlers) {
}
}

// initialize the store handlers, get the send() function
// initialize the store hooks, get the send() function
// obj? -> fn
function start (opts) {
opts = opts || {}
Expand All @@ -87,7 +99,9 @@ function dispatcher (handlers) {
apply(ns, model.effects, effects)
}
if (!subsCalled && model.subscriptions && opts.subscriptions !== false) {
apply(ns, model.subscriptions, subscriptions, createSend, onError)
apply(ns, model.subscriptions, subscriptions, createSend, function (err) {
applyHook(onErrorHooks, err)
})
}
})

Expand All @@ -96,6 +110,8 @@ function dispatcher (handlers) {
if (!opts.noEffects) effectsCalled = true
if (!opts.noSubscriptions) subsCalled = true

if (!onErrorHooks.length) onErrorHooks.push(wrapOnError(defaultOnError))

return createSend

// call an action from a view
Expand All @@ -120,7 +136,7 @@ function dispatcher (handlers) {
function onErrorCallback (err) {
err = err || null
if (err) {
onError(err, _state, function createSend (selfName) {
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)
Expand All @@ -144,7 +160,9 @@ function dispatcher (handlers) {
var effectsCalled = false
const newState = xtend(_state)

if (onAction) onAction(data, _state, name, caller, createSend)
if (onActionHooks.length) {
applyHook(onActionHooks, data, _state, name, caller, createSend)
}

// validate if a namespace exists. Namespaces are delimited by ':'.
var actionName = name
Expand All @@ -163,8 +181,8 @@ function dispatcher (handlers) {
mutate(newState, reducers[actionName](data, _state))
}
reducersCalled = true
if (onStateChange) {
onStateChange(data, newState, _state, actionName, createSend)
if (onStateChangeHooks.length) {
applyHook(onStateChangeHooks, data, newState, _state, actionName, createSend)
}
_state = newState
cb()
Expand Down Expand Up @@ -205,6 +223,14 @@ function apply (ns, source, target, createSend, done) {
})
}

// apply arguments onto an array of functions, useful for hooks
// (arr, any?, any?, any?, any?, any?) -> null
function applyHook (arr, arg1, arg2, arg3, arg4, arg5) {
arr.forEach(function (fn) {
fn(arg1, arg2, arg3, arg4, arg5)
})
}

// handle errors all the way at the top of the trace
// err? -> null
function defaultOnError (err) {
Expand Down
38 changes: 38 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,44 @@ tape('api: store.model()', (t) => {
})
})

tape('api: store.use()', (t) => {
t.test('should call multiples', (t) => {
t.plan(1)
const store = barracks()
const called = { first: false, second: false }

store.use({
onAction: (data, state, name, caller, createSend) => {
called.first = true
}
})

store.use({
onAction: (data, state, name, caller, createSend) => {
called.second = true
}
})

store.model({
state: {
count: 0
},
reducers: {
foo: (data, state) => ({ count: state.count + 1 })
}
})

const createSend = store.start()
const send = createSend('test', true)
send('foo', { count: 3 })

setTimeout(function () {
const expected = { first: true, second: true }
t.deepEqual(called, expected, 'all hooks were called')
}, 100)
})
})

tape('api: createSend = store.start(opts)', (t) => {
t.test('should validate input types', (t) => {
t.plan(3)
Expand Down

0 comments on commit 6bc9b11

Please sign in to comment.