Skip to content

Commit

Permalink
Merge pull request #58 from yoshuawuyts/use
Browse files Browse the repository at this point in the history
add store.use() method
  • Loading branch information
yoshuawuyts committed Jul 24, 2016
2 parents b381a97 + e20ff72 commit 192afbd
Show file tree
Hide file tree
Showing 4 changed files with 102 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
9 changes: 9 additions & 0 deletions apply-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = applyHook

// 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)
})
}
50 changes: 35 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ const mutate = require('xtend/mutable')
const assert = require('assert')
const xtend = require('xtend')

const applyHook = require('./apply-hook')

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 +32,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 +82,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 +101,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 +112,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 +138,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 +162,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 +183,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
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 192afbd

Please sign in to comment.