From 9623bbabbf6d34c2fd415962ebcf6580301b733b Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Mon, 18 Jul 2016 16:30:48 +0200 Subject: [PATCH 1/6] wrap-handlers: add docs --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 353a86b..fe2b715 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,12 @@ hooks are possible: - __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` + reducer changes the `state`. +- __wrapSubscriptions(fn):__ wraps a `subscription` to add custom behavior +- __wrapReducers(fn):__ wraps a `reducer` to add custom behavior +- __wrapEffects(fn):__ wraps an `effect` to add custom behavior +- __wrapInitialState(fn):__ wraps the combined initial `state` to add custom + behavior - useful to mutate the state before starting up `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 From dfa9c1571615246931b5e28286525dac493f071a Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 21 Jul 2016 13:35:44 +0200 Subject: [PATCH 2/6] wrap-handlers: add tests --- test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test.js b/test.js index 155a431..d441949 100644 --- a/test.js +++ b/test.js @@ -547,3 +547,8 @@ tape('hooks: onError', (t) => { t.test('should have a default err handler') t.test('should not call itself') }) + +tape('wrappers: wrapSubscriptions') +tape('wrappers: wrapReducers') +tape('wrappers: wrapEffects') +tape('wrappers: wrapInitialState') From 9ff5612c9d5c9efd31526d945bccfad0a87371c9 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 22 Jul 2016 04:45:34 +0200 Subject: [PATCH 3/6] wrap-handlers: wrap the things --- index.js | 51 +++++++++++++++++++++++++++++++++------------------ wrap-hook.js | 10 ++++++++++ 2 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 wrap-hook.js diff --git a/index.js b/index.js index 1033ea8..aeda566 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ const assert = require('assert') const xtend = require('xtend') const applyHook = require('./apply-hook') +const wrapHook = require('./wrap-hook') module.exports = dispatcher @@ -16,7 +17,12 @@ function dispatcher (hooks) { const onActionHooks = [] const onErrorHooks = [] - useHooks(hooks) + const subscriptionWraps = [] + const initialStateWraps = [] + const reducerWraps = [] + const effectWraps = [] + + use(hooks) var reducersCalled = false var effectsCalled = false @@ -32,12 +38,12 @@ function dispatcher (hooks) { start.model = setModel start.state = getState start.start = start - start.use = useHooks + start.use = use return start // push an object of hooks onto an array // obj -> null - function useHooks (hooks) { + 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') @@ -46,6 +52,10 @@ function dispatcher (hooks) { if (hooks.onError) onErrorHooks.push(wrapOnError(hooks.onError)) if (hooks.onAction) onActionHooks.push(hooks.onAction) if (hooks.onStateChange) onStateChangeHooks.push(hooks.onStateChange) + if (hooks.wrapSubscriptions) subscriptionWraps.push(hooks.wrapSubscriptions) + if (hooks.wrapReducers) reducerWraps.push(hooks.wrapReducers) + if (hooks.initialState) initialStateWraps.push(hooks.initialState) + if (hooks.wrapEffects) effectWraps.push(hooks.wrapEffects) } // push a model to be initiated @@ -92,17 +102,28 @@ function dispatcher (hooks) { models.forEach(function (model) { const ns = model.namespace if (!stateCalled && model.state && opts.state !== false) { - apply(ns, model.state, _state) + apply(ns, model.state, _state, function (cb) { + return wrapHook(cb, initialStateWraps) + }) } if (!reducersCalled && model.reducers && opts.reducers !== false) { - apply(ns, model.reducers, reducers) + apply(ns, model.reducers, reducers, function (cb) { + return wrapHook(cb, reducerWraps) + }) } if (!effectsCalled && model.effects && opts.effects !== false) { - apply(ns, model.effects, effects) + apply(ns, model.effects, effects, function (cb) { + return wrapHook(cb, effectWraps) + }) } if (!subsCalled && model.subscriptions && opts.subscriptions !== false) { - apply(ns, model.subscriptions, subscriptions, createSend, function (err) { - applyHook(onErrorHooks, err) + apply(ns, model.subscriptions, subscriptions, function (cb, key) { + const send = createSend('subscription: ' + (ns ? ns + ':' + key : key)) + cb = wrapHook(cb, subscriptionWraps) + cb(send, function (err) { + applyHook(onErrorHooks, err) + }) + return cb }) } }) @@ -210,18 +231,12 @@ function dispatcher (hooks) { // optionally contains a namespace // which is used to nest properties. // (str, obj, obj, fn?) -> null -function apply (ns, source, target, createSend, done) { +function apply (ns, source, target, wrap) { if (ns && !target[ns]) target[ns] = {} Object.keys(source).forEach(function (key) { - if (ns) { - target[ns][key] = source[key] - } else { - target[key] = source[key] - } - if (createSend && done) { - const send = createSend('subscription: ' + (ns ? ns + ':' + key : key)) - source[key](send, done) - } + const cb = wrap ? wrap(source[key], key) : source[key] + if (ns) target[ns][key] = cb + else target[key] = cb }) } diff --git a/wrap-hook.js b/wrap-hook.js new file mode 100644 index 0000000..e86f7dd --- /dev/null +++ b/wrap-hook.js @@ -0,0 +1,10 @@ +module.exports = wrapHook + +// fold an array of wrappers around a function recursively +// (fn, arr) -> fn +function wrapHook (fn, arr) { + arr.forEach(function (cb) { + fn = cb(fn) + }) + return fn +} From 27a66adbee0a8467bc27f16b916e2cd61fddd22b Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 4 Aug 2016 09:11:11 +0200 Subject: [PATCH 4/6] fixup! ueehhhhh --- README.md | 2 +- index.js | 20 ++++++++++---------- test.js | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index fe2b715..fcab9e8 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ hooks are possible: - __wrapSubscriptions(fn):__ wraps a `subscription` to add custom behavior - __wrapReducers(fn):__ wraps a `reducer` to add custom behavior - __wrapEffects(fn):__ wraps an `effect` to add custom behavior -- __wrapInitialState(fn):__ wraps the combined initial `state` to add custom +- __wrapInitialState(fn):__ mutate the initial `state` to add custom behavior - useful to mutate the state before starting up `createSend()` is a special function that allows the creation of a new named diff --git a/index.js b/index.js index aeda566..6eb6477 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,6 @@ const assert = require('assert') const xtend = require('xtend') const applyHook = require('./apply-hook') -const wrapHook = require('./wrap-hook') module.exports = dispatcher @@ -49,12 +48,12 @@ function dispatcher (hooks) { 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.onStateChange) onStateChangeHooks.push(hooks.onStateChange) if (hooks.wrapSubscriptions) subscriptionWraps.push(hooks.wrapSubscriptions) + if (hooks.initialState) initialStateWraps.push(hooks.wrapInitialState) if (hooks.wrapReducers) reducerWraps.push(hooks.wrapReducers) - if (hooks.initialState) initialStateWraps.push(hooks.initialState) if (hooks.wrapEffects) effectWraps.push(hooks.wrapEffects) } @@ -101,11 +100,6 @@ function dispatcher (hooks) { // register values from the models models.forEach(function (model) { const ns = model.namespace - if (!stateCalled && model.state && opts.state !== false) { - apply(ns, model.state, _state, function (cb) { - return wrapHook(cb, initialStateWraps) - }) - } if (!reducersCalled && model.reducers && opts.reducers !== false) { apply(ns, model.reducers, reducers, function (cb) { return wrapHook(cb, reducerWraps) @@ -128,10 +122,9 @@ function dispatcher (hooks) { } }) - if (!opts.noState) stateCalled = true + if (!opts.noSubscriptions) subsCalled = true if (!opts.noReducers) reducersCalled = true if (!opts.noEffects) effectsCalled = true - if (!opts.noSubscriptions) subsCalled = true if (!onErrorHooks.length) onErrorHooks.push(wrapOnError(defaultOnError)) @@ -251,3 +244,10 @@ function wrapOnError (onError) { if (err) onError(err, state, createSend) } } + +function wrapHook (fn, arr) { + arr.forEach(function (cb) { + fn = cb(fn) + }) + return fn +} diff --git a/test.js b/test.js index d441949..2b49cbd 100644 --- a/test.js +++ b/test.js @@ -551,4 +551,42 @@ tape('hooks: onError', (t) => { tape('wrappers: wrapSubscriptions') tape('wrappers: wrapReducers') tape('wrappers: wrapEffects') -tape('wrappers: wrapInitialState') + +tape('wrappers: wrapInitialState', (t) => { + t.test('should wrap initial state in start', (t) => { + t.plan(1) + const store = barracks() + store.use({ + wrapInitialState: (state) => { + console.log('oi') + console.log('state', state) + } + }) + + store.model({ + state: { foo: 'bar' } + }) + + store.start() + const state = store.state() + console.log('yo', state) + }) + + t.test('should wrap initial state in getState', (t) => { + t.plan(1) + const store = barracks() + store.use({ + wrapInitialState: (state) => { + console.log('oi') + console.log('state', state) + } + }) + + store.model({ + state: { foo: 'bar' } + }) + + const state = store.state() + console.log('yo', state) + }) +}) From e6c28e0d459ba543b747e315a9a4c2d539c24de6 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Wed, 7 Sep 2016 17:21:29 +0200 Subject: [PATCH 5/6] fixup! fix statewrap --- index.js | 70 +++++++++++++++++++++++++++++++++++++------------------- test.js | 13 +++++++---- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index 6eb6477..e311e82 100644 --- a/index.js +++ b/index.js @@ -52,7 +52,7 @@ function dispatcher (hooks) { if (hooks.onError) onErrorHooks.push(wrapOnError(hooks.onError)) if (hooks.onAction) onActionHooks.push(hooks.onAction) if (hooks.wrapSubscriptions) subscriptionWraps.push(hooks.wrapSubscriptions) - if (hooks.initialState) initialStateWraps.push(hooks.wrapInitialState) + if (hooks.wrapInitialState) initialStateWraps.push(hooks.wrapInitialState) if (hooks.wrapReducers) reducerWraps.push(hooks.wrapReducers) if (hooks.wrapEffects) effectWraps.push(hooks.wrapEffects) } @@ -69,25 +69,30 @@ function dispatcher (hooks) { function getState (opts) { opts = opts || {} assert.equal(typeof opts, 'object', 'barracks.store.state: opts should be an object') - if (opts.state) { - const initialState = {} - const nsState = {} - models.forEach(function (model) { - const ns = model.namespace - const modelState = model.state || {} - if (ns) { - nsState[ns] = {} - apply(ns, modelState, nsState) - nsState[ns] = xtend(nsState[ns], opts.state[ns]) - } else { - apply(model.namespace, modelState, initialState) - } - }) - return xtend(_state, xtend(opts.state, nsState)) - } else if (opts.freeze === false) { - return xtend(_state) + + const 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') + + const nsState = {} + + models.forEach(function (model) { + const ns = model.namespace + const modelState = model.state || {} + if (ns) { + nsState[ns] = {} + apply(ns, modelState, nsState) + nsState[ns] = xtend(nsState[ns], state[ns]) + } else { + mutate(nsState, modelState) + } + }) + + if (opts.freeze === false) { + return xtend(_state, xtend(state, nsState)) } else { - return Object.freeze(xtend(_state)) + return Object.freeze(xtend(_state, xtend(state, nsState))) } } @@ -100,6 +105,15 @@ function dispatcher (hooks) { // register values from the models models.forEach(function (model) { const ns = model.namespace + if (!stateCalled && model.state && opts.state !== false) { + const 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) @@ -122,9 +136,16 @@ function dispatcher (hooks) { } }) + // 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)) @@ -245,9 +266,12 @@ function wrapOnError (onError) { } } -function wrapHook (fn, arr) { - arr.forEach(function (cb) { - fn = cb(fn) +// 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 fn + return value } diff --git a/test.js b/test.js index 2b49cbd..40311f8 100644 --- a/test.js +++ b/test.js @@ -1,4 +1,5 @@ const barracks = require('./') +const xtend = require('xtend') const noop = require('noop2') const tape = require('tape') @@ -554,12 +555,12 @@ tape('wrappers: wrapEffects') tape('wrappers: wrapInitialState', (t) => { t.test('should wrap initial state in start', (t) => { - t.plan(1) + t.plan(2) const store = barracks() store.use({ wrapInitialState: (state) => { - console.log('oi') - console.log('state', state) + t.deepEqual(state, { foo: 'bar' }, 'initial state is correct') + return xtend(state, { beep: 'boop' }) } }) @@ -568,8 +569,10 @@ tape('wrappers: wrapInitialState', (t) => { }) store.start() - const state = store.state() - console.log('yo', state) + process.nextTick(() => { + const state = store.state() + t.deepEqual(state, { foo: 'bar', beep: 'boop' }, 'wrapped state correct') + }) }) t.test('should wrap initial state in getState', (t) => { From e72f758f9ae5edd4cf69435daf7a0803be44ce44 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Wed, 7 Sep 2016 17:31:21 +0200 Subject: [PATCH 6/6] fixup! fix statewrap for create --- index.js | 11 ++++++----- test.js | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index e311e82..3ec6254 100644 --- a/index.js +++ b/index.js @@ -89,11 +89,12 @@ function dispatcher (hooks) { } }) - if (opts.freeze === false) { - return xtend(_state, xtend(state, nsState)) - } else { - return Object.freeze(xtend(_state, xtend(state, nsState))) - } + const tmpState = xtend(_state, xtend(state, nsState)) + const wrappedState = wrapHook(tmpState, initialStateWraps) + + return (opts.freeze === false) + ? wrappedState + : Object.freeze(wrappedState) } // initialize the store hooks, get the send() function diff --git a/test.js b/test.js index 40311f8..da580d3 100644 --- a/test.js +++ b/test.js @@ -580,8 +580,7 @@ tape('wrappers: wrapInitialState', (t) => { const store = barracks() store.use({ wrapInitialState: (state) => { - console.log('oi') - console.log('state', state) + return xtend(state, { beep: 'boop' }) } }) @@ -589,7 +588,17 @@ tape('wrappers: wrapInitialState', (t) => { state: { foo: 'bar' } }) - const state = store.state() - console.log('yo', state) + process.nextTick(() => { + const opts = { + state: { bin: 'baz' } + } + const expected = { + foo: 'bar', + beep: 'boop', + bin: 'baz' + } + const state = store.state(opts) + t.deepEqual(state, expected, 'wrapped state correct') + }) }) })