microcosm
Advanced tools
Comparing version 1.0.0 to 1.1.0
# Changelog | ||
### 1.1.0 | ||
- Better seeding. Added `Microcosm::seed` which accepts an | ||
object. For each known key, Microcosm will the associated store's | ||
`getInitialState` function and set the returned value. | ||
- Exposed `Microcosm::getInitialState` to configure the starting value | ||
of the instance. This is useful for those wishing to use the | ||
`immutable` npm package by Facebook. | ||
- Microcosm will not emit changes on dispatch unless the new state | ||
fails a shallow equality check. This can be configured with | ||
`Microcosm::shouldUpdate` | ||
- `Microcosm::send` is now curried. | ||
### 1.0.0 | ||
@@ -4,0 +17,0 @@ |
``` | ||
|--> [Store] ---| | ||
[Signal] --------> [Processing] -----> [Multicast] ----+--> [Store] ---+------> [Update Global State] | ||
^ |- Auth |--> [Store] ---| | | ||
| |- Routing v | ||
| [Render] | ||
| | | ||
| | | ||
+------------------------- [Signal] ------------------------------------------+ | ||
| | ||
^ | ||
| | ||
[Background Services] | ||
|- History API | ||
|- Window Resize | ||
|- Mouse move | ||
|- Server request (for Progression) | ||
|- Firebase sync | ||
|--> [Store] ---| | ||
[app.send] ------> [Action] ------> [Dispatcher] ---+--> [Store] ---+--> [app.shouldUpdate?] | ||
^ |--> [Store] ---| | | ||
| | | ||
| v | ||
[External Services] <--------------------------------------------------------- [YES] | ||
|- User Interface | ||
|- Router | ||
|- Firebase sync | ||
``` |
@@ -1,2 +0,14 @@ | ||
module.exports=function(t){function n(r){if(e[r])return e[r].exports;var o=e[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,n),o.loaded=!0,o.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._tick=null,this._callbacks=[]}return t.prototype._pump=function(){for(var t=0;t<this._callbacks.length;t++)this._callbacks[t]()},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(){this._callbacks.length>0&&(cancelAnimationFrame(this._tick),this._tick=requestAnimationFrame(this._pump.bind(this)))},t}();t.exports=e},function(t,n,e){"use strict";var r=function(t){return t&&t.__esModule?t["default"]:t},o=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},i=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=function(t){function n(e){s(this,n),t.call(this),this.stores=[],this._state=this.getInitialState(e)}return i(n,t),n.prototype.getInitialState=function(){var t=void 0===arguments[0]?{}:arguments[0];return o({},t)},n.prototype.set=function(t,n){this._state=o({},this._state,function(){var e={};return e[t]=n,e}())},n.prototype.get=function(t){return this._state[t]||t.getInitialState()},n.prototype.send=function(t,n){var e=this,r=a(t(n));return r.then(function(n){return e.dispatch(t,n)})},n.prototype.dispatch=function(t,n){var e=this;return this._state=this.stores.reduce(function(r,o){return t in o&&(r[o]=o[t](e.get(o),n)),r},o({},this._state)),this.pump(),n},n.prototype.addStore=function(){for(var t=arguments.length,n=Array(t),e=0;t>e;e++)n[e]=arguments[e];this.stores=this.stores.concat(n)},n.prototype.serialize=function(){var t=this;return this.stores.reduce(function(n,e){return n[e]=t.get(e),n},{})},n}(u);t.exports=c},function(t){"use strict";t.exports=function(t){return t instanceof Promise?t:Promise.resolve(t)}},function(t,n,e){"use strict";var r=function(t){return t&&t.__esModule?t["default"]:t};n.__esModule=!0;var o=r(e(5)),i=0,s=function(t){var n=t.bind(null),e="_microcosm-"+i++;return n.toString=function(){return e},n};n.infuse=s,n["default"]=function(t){return o(t,s)}},function(t){"use strict";t.exports=function(t,n){var e=void 0===arguments[0]?{}:arguments[0],r=void 0===arguments[2]?{}:arguments[2],o=Object.keys(e);return o.reduce(function(t,r){return t[r]=n(e[r],r),t},r)}}]); | ||
module.exports=function(t){function n(e){if(r[e])return r[e].exports;var o=r[e]={exports:{},id:e,loaded:!1};return t[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var r={};return n.m=t,n.c=r,n.p="",n(0)}([function(t,n,r){"use strict";n.__esModule=!0;var e=r(3);n.tag=e,n["default"]=r(2)},function(t){"use strict";var n=function(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")},r=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]()},t}();t.exports=r},function(t,n,r){"use strict";var e=function(t){return t&&t.__esModule?t["default"]:t},o=Object.assign||function(t){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var e in r)Object.prototype.hasOwnProperty.call(r,e)&&(t[e]=r[e])}return t},i=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=e(r(1)),a=e(r(5)),c=function(t){function n(){s(this,n),t.call(this),this._stores=[],this._state=this.getInitialState()}return i(n,t),n.prototype.shouldUpdate=function(t,n){return 0==a(t,n)},n.prototype.getInitialState=function(){return{}},n.prototype.seed=function(t){var n=this._stores.filter(function(n){return t[n]});n.forEach(function(n){this.set(n,n.getInitialState(t[n]))},this)},n.prototype.set=function(t,n){this._state=o({},this._state,function(){var r={};return r[t]=n,r}())},n.prototype.has=function(t){return this._stores.indexOf(t)>-1},n.prototype.get=function(t,n){return this._state[t]||t.getInitialState(n)},n.prototype.send=function(t){for(var n=this,r=arguments.length,e=Array(r>1?r-1:0),o=1;r>o;o++)e[o-1]=arguments[o];if(e.length<t.length)return this.send.bind(this,t);var i=t.apply(void 0,e);return i instanceof Promise?i.then(function(r){return n.dispatch(t,r)}):this.dispatch(t,i)},n.prototype.dispatch=function(t,n){var r=this,e=this._stores.reduce(function(e,o){return t in o&&(e[o]=o[t](r.get(o),n)),e},o({},this._state));return this.shouldUpdate(this._state,e)&&(this._state=e,this.pump()),n},n.prototype.addStore=function(){for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];this._stores=this._stores.concat(n)},n.prototype.toJSON=function(){return this.serialize()},n.prototype.serialize=function(){var t=this;return this._stores.reduce(function(n,r){return n[r]=t.get(r),n},{})},n}(u);t.exports=c},function(t,n,r){"use strict";var e=function(t){return t&&t.__esModule?t["default"]:t};n.__esModule=!0;var o=e(r(4)),i=0,s=function(t){var n=t.bind(null),r="_microcosm-"+i++;return n.toString=function(){return r},n};n.infuse=s,n["default"]=function(t){return o(t,s)}},function(t){"use strict";t.exports=function(t,n){var r=void 0===arguments[0]?{}:arguments[0],e=void 0===arguments[2]?{}:arguments[2],o=Object.keys(r);return o.reduce(function(t,e){return t[e]=n(r[e],e),t},e)}},function(t,n,r){/*! | ||
* is-equal-shallow <https://github.com/jonschlinkert/is-equal-shallow> | ||
* | ||
* Copyright (c) 2015, Jon Schlinkert. | ||
* Licensed under the MIT License. | ||
*/ | ||
"use strict";var e=r(6);t.exports=function(t,n){if(!t&&!n)return!0;if(!t&&n||t&&!n)return!1;for(var r in n)if(!e(n[r])||!t.hasOwnProperty(r)||t[r]!==n[r])return!1;return!0}},function(t){/*! | ||
* is-primitive <https://github.com/jonschlinkert/is-primitive> | ||
* | ||
* Copyright (c) 2014 Jon Schlinkert, contributors. | ||
* Licensed under the MIT License | ||
*/ | ||
"use strict";t.exports=function(t){switch(typeof t){case"string":case"number":case"boolean":case"symbol":return!0}return null==t}}]); | ||
//# sourceMappingURL=Microcosm.js.map |
@@ -27,3 +27,3 @@ import Items from 'stores/Items' | ||
componentDidMount() { | ||
this.props.flux.listen(_ => this.setState(this.getState)) | ||
this.props.flux.listen(_ => this.setState(this.getState())) | ||
}, | ||
@@ -30,0 +30,0 @@ |
@@ -8,3 +8,2 @@ import AddIcon from 'icons/add' | ||
import TaskList from 'fragments/TaskList' | ||
import { Link } from 'react-router' | ||
@@ -25,10 +24,13 @@ let Home = React.createClass({ | ||
getList(list) { | ||
let items = this.props.items.filter(i => i.list === list.id) | ||
let { items, flux } = this.props | ||
return ( | ||
<ListItem key={ list.id } items={ items } list={ list } onDelete={ this._onDestroy } /> | ||
) | ||
return (<ListItem key={ list.id } | ||
items={ items.filter(i => i.list === list.id) } | ||
list={ list } | ||
onDelete={ flux.send(ListActions.remove) } />) | ||
}, | ||
render() { | ||
let { flux } = this.props | ||
return ( | ||
@@ -56,3 +58,3 @@ <main role="main"> | ||
onExit={ this._onToggle } | ||
onCreate={ this._onCreate } /> | ||
onCreate={ flux.send(ListActions.add) } /> | ||
</main> | ||
@@ -64,10 +66,2 @@ ) | ||
this.setState({ openCreate: !this.state.openCreate }) | ||
}, | ||
_onDestroy(id) { | ||
this.props.flux.send(ListActions.remove, id) | ||
}, | ||
_onCreate(props) { | ||
this.props.flux.send(ListActions.add, props) | ||
} | ||
@@ -74,0 +68,0 @@ |
@@ -46,3 +46,3 @@ import BackIcon from 'icons/arrow-back' | ||
this.props.flux.send(ListActions.remove, this.props.params.id) | ||
.then(() => page('/')) | ||
page('/') | ||
}, | ||
@@ -49,0 +49,0 @@ |
import 'style/app' | ||
import App from './App' | ||
import Router from './services/router' | ||
import Route from './actions/route' | ||
import App from 'App' | ||
import Layout from 'components/Layout' | ||
import React from 'react' | ||
import Router from 'services/router' | ||
import Storage from 'services/storage' | ||
let flux = new App() | ||
let el = document.getElementById('app') | ||
// Each app is a unique instance. | ||
// It will get its own state, useful for having multiple apps on | ||
// the same page or for independence between requests | ||
let app = new App() | ||
// Services | ||
Router.install(flux, Route.set) | ||
// Services are basically just listeners | ||
// that operate on an app instance | ||
requestAnimationFrame(function() { | ||
React.render(<Layout flux={ flux } />, document.getElementById('app')) | ||
}) | ||
// On change, this will save to local stoage | ||
Storage.install(app) | ||
// Pushes route actions as they occur | ||
Router.install(app) | ||
React.render(<Layout flux={ app } />, document.getElementById('app')) |
@@ -7,2 +7,3 @@ /** | ||
import Route from 'actions/route' | ||
import page from 'page' | ||
@@ -17,6 +18,6 @@ | ||
install(flux, action) { | ||
install(flux) { | ||
Object.keys(routes).forEach(route => { | ||
page(route, function({ params }) { | ||
flux.send(action, { handler: routes[route], params }) | ||
flux.send(Route.set, { handler: routes[route], params }) | ||
}) | ||
@@ -23,0 +24,0 @@ }) |
import Items from 'actions/items' | ||
import Lists from 'actions/lists' | ||
import uid from 'uid' | ||
export default { | ||
getInitialState() { | ||
return [] | ||
getInitialState(seed=[]) { | ||
return seed | ||
}, | ||
[Items.add](state, { list, name }) { | ||
let record = { id: state.length, list: list.id, name } | ||
let record = { | ||
id : uid(), | ||
list : list.id, | ||
name : name | ||
} | ||
@@ -13,0 +18,0 @@ return state.concat(record) |
import Lists from 'actions/lists' | ||
import contrast from 'contrast' | ||
import uid from 'uid' | ||
export default { | ||
getInitialState() { | ||
return [] | ||
getInitialState(seed=[]) { | ||
return seed | ||
}, | ||
[Lists.add](state, params) { | ||
let record = { color : '#aaaaaa', id : state.length, ...params } | ||
let record = { | ||
id : uid(), | ||
color : '#aaaaaa', | ||
...params | ||
} | ||
@@ -13,0 +18,0 @@ record.contrast = contrast(record.color) |
{ | ||
"name": "microcosm", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "An experimental flux implimentation", | ||
@@ -17,3 +17,5 @@ "main": "dist/Microcosm.js", | ||
"license": "MIT", | ||
"dependencies": {}, | ||
"dependencies": { | ||
"is-equal-shallow": "^0.1.1" | ||
}, | ||
"devDependencies": { | ||
@@ -51,2 +53,3 @@ "autoprefixer-core": "^5.1.7", | ||
"style-loader": "^0.8.2", | ||
"uid": "0.0.2", | ||
"webpack": "^1.7.2", | ||
@@ -53,0 +56,0 @@ "webpack-dev-server": "^1.7.0" |
@@ -37,5 +37,4 @@ Important! This is largely an exploratory repo, used to vet some ideas | ||
updated by returning a new value. | ||
3. All Actions return promises when called. This allows error | ||
validation for forms and easy prefetching of information | ||
when rendering on the server. | ||
3. All Actions that return promises will wait to resolve before | ||
dispatching. | ||
4. It should be easily to embed in libraries. Additional features | ||
@@ -42,0 +41,0 @@ should be able to layer on top. |
@@ -5,4 +5,4 @@ import Action from './Action' | ||
getInitialState() { | ||
return 'test' | ||
getInitialState(seed='test') { | ||
return seed | ||
}, | ||
@@ -9,0 +9,0 @@ |
@@ -8,12 +8,2 @@ import Heartbeat from '../Heartbeat' | ||
it ('does not flush if there are no callbacks', function() { | ||
let spy = sinon.spy(window, 'requestAnimationFrame') | ||
heart.pump() | ||
spy.should.not.have.been.called | ||
spy.restore() | ||
}) | ||
it ('can listen to callbacks', function(done) { | ||
@@ -24,30 +14,13 @@ heart.listen(done) | ||
it ('batches subscriptions', function(done) { | ||
it ('can ignore callbacks', function() { | ||
let stub = sinon.stub() | ||
heart.listen(stub) | ||
heart.ignore(stub) | ||
for (var i = 100; i > 0; i--) { | ||
heart.pump() | ||
} | ||
requestAnimationFrame(() => { | ||
stub.should.have.been.calledOnce | ||
done() | ||
}) | ||
}) | ||
it ('can ignore callbacks', function(done) { | ||
let stub = sinon.stub() | ||
heart.listen(stub) | ||
heart.ignore(stub) | ||
heart.pump() | ||
requestAnimationFrame(() => { | ||
stub.should.not.have.been.called | ||
done() | ||
}) | ||
stub.should.not.have.been.called | ||
}) | ||
}) |
@@ -12,3 +12,3 @@ import Action from './fixtures/Action' | ||
m.stores.length.should.equal(1) | ||
m.has(DummyStore).should.equal(true) | ||
}) | ||
@@ -28,3 +28,3 @@ | ||
m.addStore(DummyStore) | ||
m.serialize().should.have.property('dummy', 'test') | ||
m.toJSON().should.have.property('dummy', 'test') | ||
}) | ||
@@ -34,6 +34,8 @@ | ||
let seed = { fiz: 'buz' } | ||
let m = new Microcosm({ dummy: seed }) | ||
let m = new Microcosm() | ||
m.addStore(DummyStore) | ||
m.seed({ dummy: seed }) | ||
m.get(DummyStore).should.equal(seed) | ||
@@ -53,3 +55,3 @@ }) | ||
it ('can send messages to the dispatcher', function(done) { | ||
it ('can send sync messages to the dispatcher', function() { | ||
let m = new Microcosm() | ||
@@ -59,4 +61,14 @@ | ||
m.send(Action).then(function() { | ||
m.dispatch.should.have.been.calledWith(Action, true) | ||
m.send(Action) | ||
m.dispatch.should.have.been.calledWith(Action, true) | ||
}) | ||
it ('can send async messages to the dispatcher', function(done) { | ||
let m = new Microcosm() | ||
let Async = () => Promise.resolve(true) | ||
sinon.spy(m, 'dispatch') | ||
m.send(Async).then(function() { | ||
m.dispatch.should.have.been.calledWith(Async, true) | ||
done() | ||
@@ -73,16 +85,27 @@ }) | ||
m.send(Action).then(function() { | ||
spy.should.have.been.called | ||
spy.restore() | ||
done() | ||
}) | ||
m.send(Action) | ||
spy.should.have.been.called | ||
spy.restore() | ||
done() | ||
}) | ||
it('can handle actions without store responses', function(done) { | ||
it('does not emit a change if no handler responds', function() { | ||
let m = new Microcosm() | ||
m.addStore({ toString:() => 'another-store' }) | ||
m.send(Action).then(_ => done()) | ||
m.listen(function() { | ||
throw Error("Expected app to not respond but did") | ||
}) | ||
m.addStore({ toString: () => 'another-store' }) | ||
m.send(Action) | ||
}) | ||
it ('can curry the send method', function() { | ||
let m = new Microcosm() | ||
let add = (a, b) => a + b | ||
m.send(add)(2, 3).should.equal(5) | ||
}) | ||
}) |
@@ -7,18 +7,30 @@ /** | ||
import Heartbeat from 'Heartbeat' | ||
import promiseWrap from 'promiseWrap' | ||
import Heartbeat from 'Heartbeat' | ||
import isEqual from 'is-equal-shallow' | ||
export default class Microcosm extends Heartbeat { | ||
constructor(seed) { | ||
constructor() { | ||
super() | ||
this.stores = [] | ||
this._state = this.getInitialState(seed) | ||
this._stores = [] | ||
this._state = this.getInitialState() | ||
} | ||
getInitialState(seed={}) { | ||
return { ...seed } | ||
shouldUpdate(prev, next) { | ||
return isEqual(prev, next) == false | ||
} | ||
getInitialState() { | ||
return {} | ||
} | ||
seed(data) { | ||
let insert = this._stores.filter(i => data[i]) | ||
insert.forEach(function(store) { | ||
this.set(store, store.getInitialState(data[store])) | ||
}, this) | ||
} | ||
set(key, value) { | ||
@@ -28,14 +40,27 @@ this._state = { ...this._state, [key]: value } | ||
get(store) { | ||
return this._state[store] || store.getInitialState() | ||
has(store) { | ||
return this._stores.indexOf(store) > -1 | ||
} | ||
send(fn, params) { | ||
let request = promiseWrap(fn(params)) | ||
get(store, seed) { | ||
return this._state[store] || store.getInitialState(seed) | ||
} | ||
return request.then(body => this.dispatch(fn, body)) | ||
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(...params) | ||
if (request instanceof Promise) { | ||
return request.then(body => this.dispatch(fn, body)) | ||
} | ||
return this.dispatch(fn, request) | ||
} | ||
dispatch(type, body) { | ||
this._state = this.stores.reduce((state, store) => { | ||
let next = this._stores.reduce((state, store) => { | ||
if (type in store) { | ||
@@ -47,3 +72,6 @@ state[store] = store[type](this.get(store), body) | ||
this.pump() | ||
if (this.shouldUpdate(this._state, next)) { | ||
this._state = next | ||
this.pump() | ||
} | ||
@@ -54,7 +82,11 @@ return body | ||
addStore(...store) { | ||
this.stores = this.stores.concat(store) | ||
this._stores = this._stores.concat(store) | ||
} | ||
toJSON() { | ||
return this.serialize() | ||
} | ||
serialize() { | ||
return this.stores.reduce((memo, store) => { | ||
return this._stores.reduce((memo, store) => { | ||
memo[store] = this.get(store) | ||
@@ -61,0 +93,0 @@ return memo |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
65610
945
0
1
34
70
117
+ Addedis-equal-shallow@^0.1.1
+ Addedis-equal-shallow@0.1.3(transitive)
+ Addedis-primitive@2.0.0(transitive)