microcosm
Advanced tools
Comparing version 1.4.0 to 2.0.0
# Changelog | ||
### 2.0.0 | ||
- Replace default `Microcosm::send` currying with partial application | ||
using `Microcosm::prepare` | ||
- Throw an error if a store is added that does not have a unique identifier | ||
- `Microcosm::set` has been replaced with `Microcosm::merge`, so far | ||
`set` has only been used internally to `Microcosm` and `merge` dries | ||
a couple of things up | ||
#### More info on removing currying | ||
Currying has been removed `Microcosm::send`. This was overly clever | ||
and somewhat malicious. If an action has default arguments, JavaScript | ||
has no way (to my knowledge) of communicating it. One (me) could get into a | ||
situation where it is unclear why an action has not fired properly | ||
(insufficient arguments when expecting fallback defaults). | ||
In a language without static typing, this can be particularly hard to debug. | ||
In most cases, partial application is sufficient. In light of this, | ||
actions can be "buffered up" up with `Microcosm::prepare`: | ||
```javascript | ||
// Old | ||
let curried = app.send(Action) | ||
// New | ||
let partial = app.prepare(Action) | ||
``` | ||
`Microcosm::prepare` is basically just `fn.bind()` under the | ||
hood. Actions should not use context whatsoever, so this should be a | ||
reasonable caveat. | ||
### 1.4.0 | ||
@@ -4,0 +38,0 @@ |
@@ -1,2 +0,2 @@ | ||
module.exports=function(t){function n(r){if(e[r])return e[r].exports;var i=e[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var e={};return n.m=t,n.c=e,n.p="",n(0)}([function(t,n,e){"use strict";n.__esModule=!0;var r=e(4);n.tag=r,n["default"]=e(2)},function(t){"use strict";var n=function(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")},e=function(){function t(){n(this,t),this._callbacks=[]}return t.prototype.ignore=function(t){this._callbacks=this._callbacks.filter(function(n){return n!==t})},t.prototype.listen=function(t){this._callbacks=this._callbacks.concat(t)},t.prototype.pump=function(){for(var t=0;t<this._callbacks.length;t++)this._callbacks[t].call(this)},t}();t.exports=e},function(t,n,e){"use strict";var r=function(t){return t&&t.__esModule?t["default"]:t},i=Object.assign||function(t){for(var n=1;n<arguments.length;n++){var e=arguments[n];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])}return t},o=function(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),n&&(t.__proto__=n)},s=function(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")},u=r(e(1)),a=r(e(3)),c=r(e(5)),f=function(t){function n(){s(this,n),t.call(this),this._stores=[],this._state=this.getInitialState()}return o(n,t),n.prototype.getInitialState=function(){return{}},n.prototype.shouldUpdate=function(t,n){return 0==c(t,n)},n.prototype.seed=function(t){this.swap(this.deserialize(t))},n.prototype.swap=function(t){this.shouldUpdate(this._state,t)&&(this._state=t,this.pump())},n.prototype.set=function(t,n){this._state=i({},this._state,function(){var e={};return e[t]=n,e}())},n.prototype.get=function(t){return this._state[t]},n.prototype.send=function(t){for(var n=this,e=arguments.length,r=Array(e>1?e-1:0),i=1;e>i;i++)r[i-1]=arguments[i];if(r.length<t.length)return this.send.bind(this,t);var o=t.apply(void 0,r);return o instanceof Promise?o.then(function(e){return n.dispatch(t,e)}):this.dispatch(t,o)},n.prototype.dispatch=function(t,n){var e=this,r=this._stores.filter(function(n){return t in n}),o=r.reduce(function(r,i){return r[i]=i[t](e.get(i),n),r},{});return this.swap(i({},this._state,o)),n},n.prototype.addStore=function(){for(var t=this,n=arguments.length,e=Array(n),r=0;n>r;r++)e[r]=arguments[r];this._stores=e.reduce(function(n,e){var r=i({},a,e);return t.set(r,r.getInitialState()),n.concat(r)},this._stores)},n.prototype.serialize=function(){var t=this;return this._stores.reduce(function(n,e){return n[e]=e.serialize(t.get(e)),n},i({},this._state))},n.prototype.deserialize=function(t){return this._stores.reduce(function(n,e){return n[e]=e.deserialize(t[e]),n},i({},t))},n.prototype.toJSON=function(){return this.serialize()},n}(u);t.exports=f},function(t){"use strict";t.exports={getInitialState:function(){return void 0},serialize:function(t){return t},deserialize:function(){var t=void 0===arguments[0]?this.getInitialState():arguments[0];return t},toString:function(){throw new Error("Stores must implement a toString() method")}}},function(t){"use strict";var n=0,e=function(t){return"function"==typeof t},r=function(t,e){var r=t.bind(null),i="_"+e+"_"+n++;return r.toString=function(){return i},r};t.exports=function(t){var n=Object.keys(t);return n.reduce(function(n,i){var o=t[i];return n[i]=e(o)?r(o,i):o,n},{})}},function(t,n,e){/*! | ||
module.exports=function(t){function r(e){if(n[e])return n[e].exports;var o=n[e]={exports:{},id:e,loaded:!1};return t[e].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var n={};return r.m=t,r.c=n,r.p="",r(0)}([function(t,r,n){"use strict";r.__esModule=!0;var e=n(7);r.tag=e,r["default"]=n(2)},function(t){"use strict";var r=function(t,r){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")},n=function(){function t(){r(this,t),this._callbacks=[]}return t.prototype.ignore=function(t){this._callbacks=this._callbacks.filter(function(r){return r!==t})},t.prototype.listen=function(t){this._callbacks=this._callbacks.concat(t)},t.prototype.pump=function(){for(var t=0;t<this._callbacks.length;t++)this._callbacks[t].call(this)},t}();t.exports=n},function(t,r,n){"use strict";var e=function(t){return t&&t.__esModule?t["default"]:t},o=function(t,r){if("function"!=typeof r&&null!==r)throw new TypeError("Super expression must either be null or a function, not "+typeof r);t.prototype=Object.create(r&&r.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),r&&(t.__proto__=r)},i=function(t,r){if(!(t instanceof r))throw new TypeError("Cannot call a class as a function")},s=e(n(1)),u=e(n(3)),a=e(n(4)),c=e(n(5)),f=e(n(8)),p=e(n(6)),l=function(t){function r(){i(this,r),t.call(this),this._stores=[],this._state=this.getInitialState()}return o(r,t),r.prototype.getInitialState=function(){return{}},r.prototype.shouldUpdate=function(t,r){return 0==f(t,r)},r.prototype.seed=function(t){this.swap(this.deserialize(t))},r.prototype.has=function(){for(var t=this,r=arguments.length,n=Array(r),e=0;r>e;e++)n[e]=arguments[e];return n.some(function(r){return t._stores.some(function(t){return""+r==""+t})})},r.prototype.get=function(t){return this._state[t]},r.prototype.swap=function(t){this.shouldUpdate(this._state,t)&&(this._state=t,this.pump())},r.prototype.merge=function(t){this.swap(c(this._state,t))},r.prototype.prepare=function(t){for(var r,n=arguments.length,e=Array(n>1?n-1:0),o=1;n>o;o++)e[o-1]=arguments[o];return(r=this.send).bind.apply(r,[this,t].concat(e))},r.prototype.send=function(t){for(var r=this,n=arguments.length,e=Array(n>1?n-1:0),o=1;n>o;o++)e[o-1]=arguments[o];var i=t.apply(this,e);return i instanceof Promise?i.then(function(n){return r.dispatch(t,n)}):this.dispatch(t,i)},r.prototype.dispatch=function(t,r){var n=this,e=this._stores.filter(function(r){return t in r}),o=p(e,function(e){return e[t](n.get(e),r)});return this.merge(o),r},r.prototype.addStore=function(){for(var t=arguments.length,r=Array(t),n=0;t>n;n++)r[n]=arguments[n];var e=r.map(function(t){return c(u,t)});a(!this.has(e),'A toString method within "'+r+'" is not unique'),this._stores=this._stores.concat(e),this.merge(p(e,function(t){return t.getInitialState()}))},r.prototype.serialize=function(){var t=this;return p(this._stores,function(r){return r.serialize(t.get(r))})},r.prototype.deserialize=function(t){return p(this._stores,function(r){return r.deserialize(t[r])})},r.prototype.toJSON=function(){return this.serialize()},r}(s);t.exports=l},function(t){"use strict";t.exports={getInitialState:function(){return void 0},serialize:function(t){return t},deserialize:function(){var t=void 0===arguments[0]?this.getInitialState():arguments[0];return t},toString:function(){throw new Error("Stores must implement a toString() method")}}},function(t){"use strict";function r(t,r){if(!t){var n=new Error(r);throw n.framesToPop=1,n}}t.exports=r},function(t){"use strict";function r(t,r){return n({},t,r)}var n=Object.assign||function(t){for(var r=1;r<arguments.length;r++){var n=arguments[r];for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(t[e]=n[e])}return t};t.exports=r},function(t){"use strict";function r(t,r){return t.reduce(function(t,n){return t[n]=r(n),t},{})}t.exports=r},function(t){"use strict";var r=0,n=function(t){return"function"==typeof t},e=function(t,n){var e=t.bind(null),o="_"+n+"_"+r++;return e.toString=function(){return o},e};t.exports=function(t){var r=Object.keys(t);return r.reduce(function(r,o){var i=t[o];return r[o]=n(i)?e(i,o):i,r},{})}},function(t,r,n){/*! | ||
* is-equal-shallow <https://github.com/jonschlinkert/is-equal-shallow> | ||
@@ -7,3 +7,3 @@ * | ||
*/ | ||
"use strict";var r=e(6);t.exports=function(t,n){if(!t&&!n)return!0;if(!t&&n||t&&!n)return!1;for(var e in n)if(!r(n[e])||!t.hasOwnProperty(e)||t[e]!==n[e])return!1;return!0}},function(t){/*! | ||
"use strict";var e=n(9);t.exports=function(t,r){if(!t&&!r)return!0;if(!t&&r||t&&!r)return!1;for(var n in r)if(!e(r[n])||!t.hasOwnProperty(n)||t[n]!==r[n])return!1;return!0}},function(t){/*! | ||
* is-primitive <https://github.com/jonschlinkert/is-primitive> | ||
@@ -10,0 +10,0 @@ * |
@@ -51,6 +51,6 @@ # Microcosm | ||
} | ||
set(key, value) { | ||
merge(object) { | ||
// How state should be re-assigned. This function is useful to | ||
// override with the particular method of assignment for the data | ||
// structure returned from `getInitialState` | ||
// override with the particular method of assignment for merging | ||
// data for whatever is returned from from `getInitialState` | ||
} | ||
@@ -62,6 +62,9 @@ get(key) { | ||
} | ||
prepare(fn, ...params) { | ||
// Returns a partially applied version of `send`. Useful | ||
// for concise callbacks in React components | ||
} | ||
send(fn, ...params) { | ||
// Responsible for pushing an action through the system. | ||
// `send` allows for currying of the `params` list is shorter than | ||
// the accepted arguments of `fn | ||
// `send`. | ||
// | ||
@@ -68,0 +71,0 @@ // If `fn(...params)` returns a promise, it will wait for this |
@@ -28,3 +28,3 @@ import AddIcon from 'icons/add' | ||
list={ list } | ||
onDelete={ flux.send(ListActions.remove) } />) | ||
onDelete={ flux.prepare(ListActions.remove) } />) | ||
}, | ||
@@ -57,3 +57,3 @@ | ||
onExit={ this._onToggle } | ||
onCreate={ flux.send(ListActions.add) } /> | ||
onCreate={ flux.prepare(ListActions.add) } /> | ||
</main> | ||
@@ -60,0 +60,0 @@ ) |
@@ -18,3 +18,3 @@ /** | ||
install(app) { | ||
let action = app.send(Route.set) | ||
let action = app.prepare(Route.set) | ||
@@ -21,0 +21,0 @@ // Create a callback for each route that pushes the event |
{ | ||
"name": "microcosm", | ||
"version": "1.4.0", | ||
"version": "2.0.0", | ||
"description": "An experimental flux implimentation", | ||
@@ -22,6 +22,6 @@ "main": "dist/Microcosm.js", | ||
"autoprefixer-core": "^5.1.7", | ||
"babel": ">= 4.7.3", | ||
"babel-loader": ">= 4.1.0", | ||
"babel": ">= 4.7.16", | ||
"babel-loader": ">= 4.2.0", | ||
"babel-runtime": "^4.7.16", | ||
"chai": "^2.1.1", | ||
"chai": "^2.1.2", | ||
"coveralls": "^2.11.2", | ||
@@ -31,6 +31,6 @@ "css-loader": "^0.9.0", | ||
"csswring": "^3.0.2", | ||
"istanbul": "^0.3.5", | ||
"istanbul": "^0.3.11", | ||
"istanbul-instrumenter-loader": "^0.1.2", | ||
"json-loader": "^0.5.1", | ||
"karma": "^0.12.31", | ||
"karma": "^0.12.32", | ||
"karma-chrome-launcher": "^0.1.7", | ||
@@ -48,8 +48,9 @@ "karma-cli": "0.0.4", | ||
"raw-loader": "^0.5.1", | ||
"react-hot-loader": "^1.2.3", | ||
"react-tools": ">= 0.12.2", | ||
"react-hot-loader": "^1.2.4", | ||
"react": ">= 0.13.1", | ||
"react-tools": ">= 0.13.1", | ||
"route-recognizer": "^0.1.5", | ||
"sass-loader": "^0.4.1", | ||
"sass-loader": "^1.0.0", | ||
"source-map-loader": "^0.1.3", | ||
"style-loader": "^0.8.2", | ||
"style-loader": "^0.9.0", | ||
"uid": "0.0.2", | ||
@@ -56,0 +57,0 @@ "webpack": "^1.7.2", |
@@ -60,3 +60,3 @@ import Action from './fixtures/Action' | ||
it ('can assign new state', function() { | ||
it ('can merge in new state', function() { | ||
let seed = { fiz: 'buz' } | ||
@@ -67,3 +67,3 @@ let m = new Microcosm({ dummy: seed }) | ||
m.set(DummyStore, { fiz: 'not-buz'}) | ||
m.merge({ [DummyStore] : { fiz: 'not-buz'} }) | ||
@@ -109,2 +109,4 @@ m.get(DummyStore).fiz.should.equal('not-buz') | ||
m.addStore({ toString: () => 'another-store' }) | ||
m.listen(function() { | ||
@@ -114,14 +116,25 @@ throw Error("Expected app to not respond but did") | ||
m.addStore({ toString: () => 'another-store' }) | ||
m.send(Action) | ||
}) | ||
it ('can curry the send method', function() { | ||
it ('can partially apply actions', function() { | ||
let m = new Microcosm() | ||
let add = (a, b) => a + b | ||
let add = (a=0, b=0) => a + b | ||
m.send(add)(2, 3).should.equal(5) | ||
m.prepare(add)(2, 3).should.equal(5) | ||
m.prepare(add, 4)(1).should.equal(5) | ||
m.prepare(add)(1).should.equal(1) | ||
}) | ||
it ('throws an error of a stores toString is not unique', function(done) { | ||
let m = new Microcosm() | ||
m.addStore({ toString() { return 'fiz' } }) | ||
try { | ||
m.addStore({ toString() { return 'fiz' } }) | ||
} catch(x) { | ||
done() | ||
} | ||
}) | ||
}) |
@@ -9,3 +9,6 @@ /** | ||
import Store from './Store' | ||
import assert from './assert' | ||
import assign from './assign' | ||
import isEqual from 'is-equal-shallow' | ||
import mapBy from './mapBy' | ||
@@ -47,2 +50,13 @@ export default class Microcosm extends Heartbeat { | ||
has(...stores) { | ||
return stores.some(a => this._stores.some(b => `${a}` === `${b}`)) | ||
} | ||
get(key) { | ||
// How state should be retrieved. This function is useful to | ||
// override with the particular method of retrieval for the data | ||
// structure returned from `getInitialState` | ||
return this._state[key] | ||
} | ||
swap(next) { | ||
@@ -56,24 +70,16 @@ // Given a next state, only trigger an event if state actually changed | ||
set(key, value) { | ||
merge(obj) { | ||
// How state should be re-assigned. This function is useful to | ||
// override with the particular method of assignment for the data | ||
// structure returned from `getInitialState` | ||
this._state = { ...this._state, [key]: value } | ||
this.swap(assign(this._state, obj)) | ||
} | ||
get(key) { | ||
// How state should be retrieved. This function is useful to | ||
// override with the particular method of retrieval for the data | ||
// structure returned from `getInitialState` | ||
return this._state[key] | ||
prepare(fn, ...buffer) { | ||
return this.send.bind(this, fn, ...buffer) | ||
} | ||
send(fn, ...params) { | ||
// Allow currying of send method for cleaner callbacks | ||
if (params.length < fn.length) { | ||
return this.send.bind(this, fn) | ||
} | ||
let request = fn.apply(this, params) | ||
let request = fn(...params) | ||
// Actions some times return promises. When this happens, wait for | ||
@@ -93,9 +99,6 @@ // them to resolve before moving on | ||
// Next build the change set | ||
let changes = answerable.reduce((state, store) => { | ||
state[store] = store[action](this.get(store), body) | ||
return state | ||
}, {}) | ||
let changes = mapBy(answerable, store => store[action](this.get(store), body)) | ||
// Produce the next state by folding changes into the current state | ||
this.swap({ ...this._state, ...changes }) | ||
// Produce the next state by mapBying changes into the current state | ||
this.merge(changes) | ||
@@ -107,27 +110,23 @@ // Send back the body to the original signaler | ||
addStore(...stores) { | ||
this._stores = stores.reduce((pool, store) => { | ||
// Make sure that the Store implements important life cycle | ||
// methods | ||
let valid = { ...Store, ...store } | ||
// Make sure that the Store implements important life cycle methods | ||
let safe = stores.map(s => assign(Store, s)) | ||
// Once verified, setup initial state | ||
this.set(valid, valid.getInitialState()) | ||
// Don't reassign stores that are already included fail hard | ||
assert(!this.has(safe), `A toString method within "${stores}" is not unique`) | ||
// Finally, add it to the pool of known stores | ||
return pool.concat(valid) | ||
}, this._stores) | ||
// Add the validated stores to the list of known entities | ||
this._stores = this._stores.concat(safe) | ||
// Once verified, setup initial state. | ||
// This is done last so that any callbacks that need to reduce | ||
// over the current state have the latest list of stores | ||
this.merge(mapBy(safe, store => store.getInitialState())) | ||
} | ||
serialize() { | ||
return this._stores.reduce((memo, store) => { | ||
memo[store] = store.serialize(this.get(store)) | ||
return memo | ||
}, { ...this._state }) | ||
return mapBy(this._stores, store => store.serialize(this.get(store))) | ||
} | ||
deserialize(data) { | ||
return this._stores.reduce(function(memo, store) { | ||
memo[store] = store.deserialize(data[store]) | ||
return memo | ||
}, { ...data }) | ||
return mapBy(this._stores, store => store.deserialize(data[store])) | ||
} | ||
@@ -134,0 +133,0 @@ |
Sorry, the diff of this file is not supported yet
95734
79
1172
35