microcosm
Advanced tools
Comparing version 11.6.0 to 12.0.0-alpha.0
@@ -7,225 +7,171 @@ 'use strict'; | ||
var react = require('react'); | ||
var Microcosm = require('../microcosm.js'); | ||
var Microcosm__default = _interopDefault(Microcosm); | ||
var react = require('react'); | ||
var EMPTY = {}; | ||
/** | ||
* PureComponent was only added recently, so fallback to the regular | ||
* component in cases where it doesn't exist | ||
*/ | ||
var BaseComponent = react.PureComponent || react.Component; | ||
function passChildren() { | ||
return this.props.children ? react.Children.only(this.props.children) : null; | ||
} | ||
/** | ||
* A general component abstraction for high-responsibility React | ||
* components that interact with non-presentational logic so that the | ||
* majority of view code does not have to. | ||
*/ | ||
function Presenter (props, context) { | ||
BaseComponent.apply(this, arguments); | ||
function Presenter(props, context) { | ||
react.PureComponent.apply(this, arguments); | ||
// Do not overriding render, generate the context wrapper upon creation | ||
if (this.view === Presenter.prototype.view && | ||
this.render !== Presenter.prototype.render) { | ||
this.view = this.render.bind(this); | ||
this.render = Presenter.prototype.render.bind(this); | ||
if (this.render !== Presenter.prototype.render) { | ||
this.defaultRender = this.render; | ||
this.render = Presenter.prototype.render; | ||
} else { | ||
this.defaultRender = passChildren; | ||
} | ||
} | ||
Microcosm.inherit(Presenter, BaseComponent, { | ||
Microcosm.inherit(Presenter, react.PureComponent, { | ||
_beginSetup: function _beginSetup(mediator) { | ||
this.repo = mediator.repo; | ||
this.mediator = mediator; | ||
getRepo: function getRepo (repo, props) { | ||
return repo ? repo.fork() : new Microcosm__default() | ||
this.setup(this.repo, this.props, this.state); | ||
this.model = this._prepareModel(); | ||
this.ready(this.repo, this.props, this.state); | ||
}, | ||
_beginTeardown: function _beginTeardown() { | ||
this.teardown(this.repo, this.props, this.state); | ||
}, | ||
_requestRepo: function _requestRepo(contextRepo) { | ||
var givenRepo = this.props.repo || contextRepo; | ||
var workingRepo = this.getRepo(givenRepo, this.props, this.state); | ||
_setRepo: function _setRepo (repo) { | ||
this.repo = repo; | ||
this.didFork = workingRepo !== givenRepo; | ||
this.setup(repo, this.props, this.props.state); | ||
return workingRepo; | ||
}, | ||
_prepareModel: function _prepareModel() { | ||
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props; | ||
var state = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.state; | ||
_connectSend: function _connectSend (send) { | ||
this.send = send; | ||
return this.mediator.updateModel(props, state); | ||
}, | ||
/** | ||
* Called when a presenter is created, before it has calculated a view model. | ||
* Useful for fetching data and other prep-work. | ||
*/ | ||
setup: function setup (repo, props, state) { | ||
setup: function setup(repo, props, state) { | ||
// NOOP | ||
}, | ||
/** | ||
* Called when a presenter gets new props. This is useful for secondary | ||
* data fetching and other work that must happen when a Presenter receives | ||
* new information | ||
*/ | ||
update: function update (repo, props, state) { | ||
ready: function ready(repo, props, state) { | ||
// NOOP | ||
}, | ||
componentWillUpdate: function componentWillUpdate (next, state) { | ||
this.update(this.repo, next, state); | ||
}, | ||
/** | ||
* Opposite of setup. Useful for cleaning up side-effects. | ||
*/ | ||
teardown: function teardown (repo, props, state) { | ||
update: function update(repo, props, state) { | ||
// NOOP | ||
}, | ||
/** | ||
* Expose "intent" subscriptions to child components. This is used with the <Form /> | ||
* add-on to improve the ergonomics of presenter/view communication (though this only | ||
* occurs from the view to the presenter). | ||
*/ | ||
register: function register () { | ||
teardown: function teardown(repo, props, state) { | ||
// NOOP | ||
}, | ||
/** | ||
* Used by the presenter to calculate it's internal state. This function must return | ||
* an object who's keys will be assigned to state, and who's values are functions that | ||
* are given the repo state and can return a specific point in that state. | ||
* | ||
* If none of the keys have changed, `this.updateState` will not set a new state. | ||
*/ | ||
model: function model (props, state, repo) { | ||
return EMPTY | ||
intercept: function intercept() { | ||
return EMPTY; | ||
}, | ||
componentWillUpdate: function componentWillUpdate(props, state) { | ||
this.model = this._prepareModel(props, state); | ||
this.update(this.repo, props, state); | ||
}, | ||
getRepo: function getRepo(repo, props) { | ||
return repo ? repo.fork() : new Microcosm__default(); | ||
}, | ||
send: function send() { | ||
var _mediator; | ||
view: function view (model) { | ||
return this.props.children ? react.Children.only(this.props.children) : null | ||
return (_mediator = this.mediator).send.apply(_mediator, arguments); | ||
}, | ||
render: function render () { | ||
return ( | ||
react.createElement(PresenterContext, { | ||
parentProps : this.props, | ||
parentState : this.state, | ||
presenter : this, | ||
view : this.view, | ||
repo : this.props.repo | ||
}) | ||
) | ||
getModel: function getModel(repo, props, state) { | ||
return EMPTY; | ||
}, | ||
render: function render() { | ||
return react.createElement(PresenterMediator, { | ||
presenter: this, | ||
parentState: this.state | ||
}); | ||
} | ||
}); | ||
function PresenterContext (props, context) { | ||
BaseComponent.apply(this, arguments); | ||
function PresenterMediator(props, context) { | ||
react.PureComponent.apply(this, arguments); | ||
this.repo = this.getRepo(); | ||
this.state = {}; | ||
this.presenter = props.presenter; | ||
this.repo = this.presenter._requestRepo(context.repo); | ||
this.send = this.send.bind(this); | ||
props.presenter._connectSend(this.send); | ||
this.state = { repo: this.repo, send: this.send }; | ||
} | ||
Microcosm.inherit(PresenterContext, BaseComponent, { | ||
getChildContext: function getChildContext () { | ||
Microcosm.inherit(PresenterMediator, react.PureComponent, { | ||
getChildContext: function getChildContext() { | ||
return { | ||
repo : this.repo, | ||
send : this.send | ||
repo: this.repo, | ||
send: this.send | ||
}; | ||
}, | ||
componentWillMount: function componentWillMount() { | ||
if (this.presenter.getModel !== Presenter.prototype.getModel) { | ||
this.repo.on('change', this.setModel, this); | ||
} | ||
}, | ||
componentWillMount: function componentWillMount () { | ||
this.props.presenter._setRepo(this.repo); | ||
this.recalculate(this.props); | ||
this.presenter._beginSetup(this); | ||
}, | ||
componentDidMount: function componentDidMount () { | ||
this.repo.on('change', this.updateState, this); | ||
this.props.presenter.refs = this.refs; | ||
componentDidMount: function componentDidMount() { | ||
this.presenter.refs = this.refs; | ||
}, | ||
componentWillUnmount: function componentWillUnmount() { | ||
this.presenter.refs = this.refs; | ||
componentDidUpdate: function componentDidUpdate () { | ||
this.props.presenter.refs = this.refs; | ||
}, | ||
this.repo.off('change', this.setModel, this); | ||
componentWillUnmount: function componentWillUnmount () { | ||
var ref = this.props; | ||
var presenter = ref.presenter; | ||
var parentProps = ref.parentProps; | ||
var parentState = ref.parentState; | ||
var repo = ref.repo; | ||
presenter.teardown(this.repo, parentProps, parentState); | ||
if (this.repo !== (repo || this.context.repo)) { | ||
if (this.presenter.didFork) { | ||
this.repo.teardown(); | ||
} | ||
}, | ||
componentWillReceiveProps: function componentWillReceiveProps (next) { | ||
this.recalculate(next); | ||
this.presenter._beginTeardown(); | ||
}, | ||
render: function render() { | ||
// setState might have been called before the model | ||
// can get assigned | ||
this.presenter.model = this.state; | ||
render: function render () { | ||
var ref = this.props; | ||
var presenter = ref.presenter; | ||
var parentProps = ref.parentProps; | ||
// Views can be getters, so pluck it out so that it is only evaluated once | ||
var view = this.presenter.view; | ||
var model = Microcosm.merge(parentProps, { send: this.send, repo: this.repo }, this.state); | ||
if (presenter.hasOwnProperty('view') || presenter.view.prototype.isReactComponent) { | ||
return react.createElement(presenter.view, model) | ||
if (view != null) { | ||
return react.createElement(view, Microcosm.merge(this.presenter.props, this.state)); | ||
} | ||
return presenter.view(model) | ||
return this.presenter.defaultRender(); | ||
}, | ||
updateModel: function updateModel(props, state) { | ||
var model = this.presenter.getModel(props, state); | ||
var data = this.repo.state; | ||
var next = {}; | ||
getRepo: function getRepo () { | ||
var ref = this.props; | ||
var presenter = ref.presenter; | ||
var parentProps = ref.parentProps; | ||
var repo = ref.repo; | ||
this.propMap = {}; | ||
return presenter.getRepo(repo || this.context.repo, parentProps) | ||
}, | ||
for (var key in model) { | ||
var entry = model[key]; | ||
updatePropMap: function updatePropMap (ref) { | ||
var presenter = ref.presenter; | ||
var parentProps = ref.parentProps; | ||
var parentState = ref.parentState; | ||
if (typeof entry === 'function') { | ||
this.propMap[key] = entry; | ||
next[key] = entry(data); | ||
} else { | ||
next[key] = entry; | ||
} | ||
} | ||
this.propMap = presenter.model(parentProps, parentState, this.repo); | ||
this.propMapKeys = Object.keys(this.propMap || EMPTY); | ||
}, | ||
this.setState(next); | ||
recalculate: function recalculate (props) { | ||
this.updatePropMap(props); | ||
this.updateState(); | ||
return Microcosm.merge(this.state, next); | ||
}, | ||
updateState: function updateState () { | ||
var next = this.getState(); | ||
if (next) { | ||
this.setState(next); | ||
} | ||
}, | ||
getState: function getState () { | ||
var this$1 = this; | ||
var repoState = this.repo.state; | ||
if (typeof this.propMap === 'function') { | ||
return this.propMap(repoState) | ||
} | ||
setModel: function setModel(state) { | ||
var last = this.state; | ||
var next = null; | ||
for (var i = this.propMapKeys.length - 1; i >= 0; --i) { | ||
var key = this$1.propMapKeys[i]; | ||
var entry = this$1.propMap[key]; | ||
for (var key in this.propMap) { | ||
var value = this.propMap[key](state); | ||
var value = typeof entry === 'function' ? entry(repoState) : entry; | ||
if (this$1.state[key] !== value) { | ||
if (last[key] !== value) { | ||
next = next || {}; | ||
@@ -236,64 +182,50 @@ next[key] = value; | ||
return next | ||
if (next !== null) { | ||
this.setState(next); | ||
} | ||
}, | ||
hasParent: function hasParent() { | ||
// Do not allow transfer across repos. Check to for inheritence by comparing | ||
// the common history object shared between repos | ||
return Microcosm.get(this.repo, 'history') === Microcosm.get(this.context, ['repo', 'history']); | ||
}, | ||
send: function send(intent) { | ||
for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
params[_key - 1] = arguments[_key]; | ||
} | ||
/** | ||
* Provides a way for views to send messages back to the presenter | ||
* in a way that does not require passing callbacks down through the | ||
* view. This method is exposed by the Presenter via the React context | ||
* API. | ||
* | ||
* Send bubbles. If the closest presenter does not implement the intent, | ||
* it will check it's parent presenter. This repeats until there is no parent, | ||
* in which case it pushes to the repo. | ||
* | ||
* @param {string} intent - The name of a method the view wishes to invoke | ||
* @param {...any} params - Arguments to invoke the named method with | ||
*/ | ||
send: function send (intent) { | ||
var params = [], len = arguments.length - 1; | ||
while ( len-- > 0 ) params[ len ] = arguments[ len + 1 ]; | ||
var interceptors = this.presenter.intercept(); | ||
var ref = this.props; | ||
var presenter = ref.presenter; | ||
// A presenter's register goes through the same registration steps | ||
var handler = Microcosm.getRegistration(interceptors, Microcosm.tag(intent)); | ||
var registry = presenter.register(); | ||
// Tag intents so that they register the same way in the Presenter | ||
// and Microcosm instance | ||
intent = Microcosm.tag(intent); | ||
// Does the presenter register to this intent? | ||
if (registry && registry.hasOwnProperty(intent)) { | ||
return (ref$1 = registry[intent]).call.apply(ref$1, [ presenter, this.repo ].concat( params )) | ||
if (handler) { | ||
return handler.call.apply(handler, [this.presenter, this.repo].concat(params)); | ||
} | ||
// No: try the parent presenter | ||
if (this.context.send) { | ||
// Do not allow transfer across repos | ||
if (Microcosm.get(this.repo, 'history') === Microcosm.get(this.context, ['repo', 'history'])) { | ||
return this.context.send.apply(null, arguments) | ||
} | ||
if (this.hasParent()) { | ||
return this.context.send.apply(null, arguments); | ||
} | ||
// If we hit the top, push the intent into the Microcosm instance | ||
return this.repo.push.apply(this.repo, arguments) | ||
var ref$1; | ||
return this.repo.push.apply(this.repo, arguments); | ||
} | ||
}); | ||
PresenterContext.propTypes = { | ||
repo : react.PropTypes.object | ||
PresenterMediator.propTypes = { | ||
repo: react.PropTypes.object | ||
}; | ||
PresenterContext.contextTypes = { | ||
repo : react.PropTypes.object, | ||
send : react.PropTypes.func | ||
PresenterMediator.contextTypes = { | ||
repo: react.PropTypes.object, | ||
send: react.PropTypes.func | ||
}; | ||
PresenterContext.childContextTypes = { | ||
repo : react.PropTypes.object, | ||
send : react.PropTypes.func | ||
PresenterMediator.childContextTypes = { | ||
repo: react.PropTypes.object, | ||
send: react.PropTypes.func | ||
}; | ||
exports['default'] = Presenter; |
@@ -1,1 +0,1 @@ | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}function t(e,r){i.apply(this,arguments),this.view===t.prototype.view&&this.render!==t.prototype.render&&(this.view=this.render.bind(this),this.render=t.prototype.render.bind(this))}function r(e,t){i.apply(this,arguments),this.repo=this.getRepo(),this.state={},this.send=this.send.bind(this),e.presenter._connectSend(this.send)}Object.defineProperty(exports,"__esModule",{value:!0});var p=require("react"),n=require("../microcosm.js"),o=e(n),s={},i=p.PureComponent||p.Component;n.inherit(t,i,{getRepo:function(e,t){return e?e.fork():new o},_setRepo:function(e){this.repo=e,this.setup(e,this.props,this.props.state)},_connectSend:function(e){this.send=e},setup:function(e,t,r){},update:function(e,t,r){},componentWillUpdate:function(e,t){this.update(this.repo,e,t)},teardown:function(e,t,r){},register:function(){},model:function(e,t,r){return s},view:function(e){return this.props.children?p.Children.only(this.props.children):null},render:function(){return p.createElement(r,{parentProps:this.props,parentState:this.state,presenter:this,view:this.view,repo:this.props.repo})}}),n.inherit(r,i,{getChildContext:function(){return{repo:this.repo,send:this.send}},componentWillMount:function(){this.props.presenter._setRepo(this.repo),this.recalculate(this.props)},componentDidMount:function(){this.repo.on("change",this.updateState,this),this.props.presenter.refs=this.refs},componentDidUpdate:function(){this.props.presenter.refs=this.refs},componentWillUnmount:function(){var e=this.props,t=e.presenter,r=e.parentProps,p=e.parentState,n=e.repo;t.teardown(this.repo,r,p),this.repo!==(n||this.context.repo)&&this.repo.teardown()},componentWillReceiveProps:function(e){this.recalculate(e)},render:function(){var e=this.props,t=e.presenter,r=e.parentProps,o=n.merge(r,{send:this.send,repo:this.repo},this.state);return t.hasOwnProperty("view")||t.view.prototype.isReactComponent?p.createElement(t.view,o):t.view(o)},getRepo:function(){var e=this.props,t=e.presenter,r=e.parentProps,p=e.repo;return t.getRepo(p||this.context.repo,r)},updatePropMap:function(e){var t=e.presenter,r=e.parentProps,p=e.parentState;this.propMap=t.model(r,p,this.repo),this.propMapKeys=Object.keys(this.propMap||s)},recalculate:function(e){this.updatePropMap(e),this.updateState()},updateState:function(){var e=this.getState();e&&this.setState(e)},getState:function(){var e=this,t=this.repo.state;if("function"==typeof this.propMap)return this.propMap(t);for(var r=null,p=this.propMapKeys.length-1;p>=0;--p){var n=e.propMapKeys[p],o=e.propMap[n],s="function"==typeof o?o(t):o;e.state[n]!==s&&(r=r||{},r[n]=s)}return r},send:function(e){for(var t=[],r=arguments.length-1;r-- >0;)t[r]=arguments[r+1];var p=this.props,o=p.presenter,s=o.register();return e=n.tag(e),s&&s.hasOwnProperty(e)?(i=s[e]).call.apply(i,[o,this.repo].concat(t)):this.context.send&&n.get(this.repo,"history")===n.get(this.context,["repo","history"])?this.context.send.apply(null,arguments):this.repo.push.apply(this.repo,arguments);var i}}),r.propTypes={repo:p.PropTypes.object},r.contextTypes={repo:p.PropTypes.object,send:p.PropTypes.func},r.childContextTypes={repo:p.PropTypes.object,send:p.PropTypes.func},exports.default=t; | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}function t(){return this.props.children?o.Children.only(this.props.children):null}function r(e,n){o.PureComponent.apply(this,arguments),this.render!==r.prototype.render?(this.defaultRender=this.render,this.render=r.prototype.render):this.defaultRender=t}function n(e,t){o.PureComponent.apply(this,arguments),this.presenter=e.presenter,this.repo=this.presenter._requestRepo(t.repo),this.send=this.send.bind(this),this.state={repo:this.repo,send:this.send}}Object.defineProperty(exports,"__esModule",{value:!0});var s=require("../microcosm.js"),i=e(s),o=require("react"),p={};s.inherit(r,o.PureComponent,{_beginSetup:function(e){this.repo=e.repo,this.mediator=e,this.setup(this.repo,this.props,this.state),this.model=this._prepareModel(),this.ready(this.repo,this.props,this.state)},_beginTeardown:function(){this.teardown(this.repo,this.props,this.state)},_requestRepo:function(e){var t=this.props.repo||e,r=this.getRepo(t,this.props,this.state);return this.didFork=r!==t,r},_prepareModel:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.props,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.state;return this.mediator.updateModel(e,t)},setup:function(e,t,r){},ready:function(e,t,r){},update:function(e,t,r){},teardown:function(e,t,r){},intercept:function(){return p},componentWillUpdate:function(e,t){this.model=this._prepareModel(e,t),this.update(this.repo,e,t)},getRepo:function(e,t){return e?e.fork():new i},send:function(){var e;return(e=this.mediator).send.apply(e,arguments)},getModel:function(e,t,r){return p},render:function(){return o.createElement(n,{presenter:this,parentState:this.state})}}),s.inherit(n,o.PureComponent,{getChildContext:function(){return{repo:this.repo,send:this.send}},componentWillMount:function(){this.presenter.getModel!==r.prototype.getModel&&this.repo.on("change",this.setModel,this),this.presenter._beginSetup(this)},componentDidMount:function(){this.presenter.refs=this.refs},componentWillUnmount:function(){this.presenter.refs=this.refs,this.repo.off("change",this.setModel,this),this.presenter.didFork&&this.repo.teardown(),this.presenter._beginTeardown()},render:function(){this.presenter.model=this.state;var e=this.presenter.view;return null!=e?o.createElement(e,s.merge(this.presenter.props,this.state)):this.presenter.defaultRender()},updateModel:function(e,t){var r=this.presenter.getModel(e,t),n=this.repo.state,i={};this.propMap={};for(var o in r){var p=r[o];"function"==typeof p?(this.propMap[o]=p,i[o]=p(n)):i[o]=p}return this.setState(i),s.merge(this.state,i)},setModel:function(e){var t=this.state,r=null;for(var n in this.propMap){var s=this.propMap[n](e);t[n]!==s&&(r=r||{},r[n]=s)}null!==r&&this.setState(r)},hasParent:function(){return s.get(this.repo,"history")===s.get(this.context,["repo","history"])},send:function(e){for(var t=arguments.length,r=Array(t>1?t-1:0),n=1;n<t;n++)r[n-1]=arguments[n];var i=this.presenter.intercept(),o=s.getRegistration(i,s.tag(e));return o?o.call.apply(o,[this.presenter,this.repo].concat(r)):this.hasParent()?this.context.send.apply(null,arguments):this.repo.push.apply(this.repo,arguments)}}),n.propTypes={repo:o.PropTypes.object},n.contextTypes={repo:o.PropTypes.object,send:o.PropTypes.func},n.childContextTypes={repo:o.PropTypes.object,send:o.PropTypes.func},exports.default=r; |
170
CHANGELOG.md
# Changelog | ||
## 12.0.0 | ||
- `merge` helper skips over nully values. For example, `merge(null, {})` will | ||
start with the second argument | ||
- Renamed `Presenter::model` to `Presenter::getModel`. | ||
- Renamed `Presenter::register` to `Presenter::intercept` | ||
- Added `Presenter::ready`, which fires after `::setup` | ||
- Added a `model` property to Presenters. This behaves similarly to `props` or | ||
`state`, and is available after `setup` executes | ||
- `Presenter::render` is now the primary rendering method for Presenters | ||
- `Presenter::view` always gets called with `React.createElement` | ||
- Removed deprecated `Action::send` | ||
- Added nested action registrations in domains. See the Domains component of | ||
the upgrading section later. | ||
- `Microcosm:toJSON` only serializes domains that implement `::serialize` | ||
- `Microcosm::reset` only operate on keys managed by the specific Microcosm. | ||
`reset` effects the entire tree of forks. | ||
- `Microcosm::patch` only operate on keys managed by the specific Microcosm. | ||
`patch` effects the entire tree of forks. | ||
- Removed `Domain::commit`, which consistently added needless complexity to our | ||
applications. | ||
- All instances of `intent` have been replaced with `action`. They are the | ||
exact same thing under the hood, and it is a common source of confusion. | ||
- Renamed `IntentButton` to `ActionButton`. Import from `microcosm/addons/action-button` | ||
- Renamed `Form` to `ActionForm` Import from `microcosm/addons/action-form` | ||
- Renamed `withIntent` to `withSend`. Import from `microcosm/addons/with-send` | ||
- Added `update` data utility, which calls `set` on the result of a function that | ||
is passed the result of `get`. | ||
### Upgrading | ||
#### Microcosm | ||
`deserialize`, `serialize`, `reset`, and `patch` only operate on keys managed | ||
by a particular Microcosm. Verify that, where you are using these methods, your | ||
application is not relying on them to inject arbitrary application state. | ||
These methods now return the merged result of calling all the way up the | ||
hierarchy of Microcosm forks. In practice, this means that Microcosms only have | ||
to deal with the keys for domains they were assigned, which is more in line with | ||
the behavior we expect from forks. | ||
#### Actions | ||
With the exception of removing `send`, which was replaced with `update`, | ||
actions have not changed. If you have removed all deprecated `action.send` | ||
calls after upgrading to 11.6.0, there should be no further change required. | ||
#### Domains | ||
#### No more commit | ||
Domains no longer support `commit()`, and subsequently `shouldCommit()`. We | ||
found, while useful for serializing libraries such as ImmutableJS, that it our | ||
usage of `commit` turned into a convenience method for always writing state in a | ||
specific way. This created an awkwardness with serializing data, and could be | ||
a source of performance problems as they continually write new object references | ||
from things like `filter` or `slice`. | ||
So we removed it. We recommend moving this sort of behavior to `getModel` in | ||
the Presenter add-on. | ||
#### Nested action registrations | ||
Domains may now nest action statuses as an object: | ||
```javascript | ||
class Domain { | ||
register () { | ||
return { | ||
[action]: { | ||
open : this.setLoading, | ||
error : this.setError, | ||
done : this.setDone | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
### Presenters | ||
#### `getModel` is the new `model` | ||
We frequently found ourselves wanting to access the latest model inside of our | ||
presenter. What if we wanted to fetch extra data from records pulled out of a | ||
model, or render differently if the record was missing? | ||
Presenters now have a `model` method, which can be accessed after `setup` has | ||
completed: | ||
```javascript | ||
class MyPresenter extends Presenter { | ||
getModel () { | ||
return { count: state => state.count } | ||
} | ||
render () { | ||
return ( | ||
<ActionButton action={step} value={1}> | ||
{this.model.count} | ||
</ActionButton> | ||
) | ||
} | ||
} | ||
``` | ||
#### `ready` | ||
`setup` can not have access to `this.model` because repo specific setup | ||
behavior might cause the model to be recalculated excessively. So we've added a | ||
`ready` method. Both `ready` and `update` have access to the last calculated | ||
model, which makes them ideal for performing some work based on it: | ||
```javascript | ||
class MyPresenter extends Presenter { | ||
getModel (props) { | ||
return { | ||
user: data => data.users.find(u => u.id === props.id) | ||
} | ||
} | ||
ready (repo, props) | ||
if (this.model.user == null) { | ||
repo.push(this.fetchUser, props.id) | ||
} | ||
} | ||
} | ||
``` | ||
You can still do this sort of fetching inside of `setup`, there just won't be a | ||
model to access. Not much of a change from `11.6.0`, where `this.model` was not | ||
available. | ||
#### `render` is the new `view` | ||
We (Nate) got this wrong. By not using render, too much distance was created | ||
between the underlying React Component behavior and the "special treatment" | ||
received by `view`. | ||
`render` now works just like `React.Component::render`, as it should be. Still, | ||
we haven't gotten rid of `view`, which is useful in a couple of places, like as | ||
a getter to switch over some piece of model state: | ||
```javascript | ||
class MyPresenter extends Presenter { | ||
getModel (props) { | ||
return { | ||
user: data => data.users.find(u => u.id === props.id) | ||
} | ||
} | ||
get view () { | ||
return this.model.user ? MyUserView : My404View | ||
} | ||
} | ||
``` | ||
`view` is always invoked with `React.createElement`. `render` is always called | ||
in the context of the Presenter. It is a plain-old React render method (for | ||
great justice). | ||
#### `intercept` is the new `register` | ||
`Presenter::register` was a confusing name for the what it did. | ||
`Presenter::register` allows you to catch messages sent from child view | ||
components. Catch is a reserved word, so we've renamed it `intercept`. | ||
In the future, `Presenter::register` might behave more like an Effect, which is | ||
what several users have mistaken it for. | ||
## 11.6.0 | ||
@@ -4,0 +174,0 @@ |
@@ -79,3 +79,3 @@ # Actions | ||
Action creators that return functions grant full access to the action | ||
that represents it. If we were to a vanilla version of the Promise | ||
that represents it. If we were to write a lower level version of the Promise | ||
example earlier: | ||
@@ -82,0 +82,0 @@ |
@@ -36,3 +36,3 @@ # Domains | ||
register() { | ||
register () { | ||
return { | ||
@@ -62,3 +62,3 @@ [action.open] : this.setLoading, | ||
var Planets = { | ||
getInitialState() { | ||
getInitialState () { | ||
return [] | ||
@@ -91,6 +91,6 @@ } | ||
const Planets = { | ||
getInitialState() { | ||
getInitialState () { | ||
return Immutable.List() | ||
}, | ||
serialize(planets) { | ||
serialize (planets) { | ||
return planets.toJSON() | ||
@@ -109,9 +109,9 @@ } | ||
const Planets = { | ||
getInitialState() { | ||
getInitialState () { | ||
return Immutable.List() | ||
}, | ||
serialize(planets) { | ||
serialize (planets) { | ||
return planets.toJSON() | ||
}, | ||
deserialize(raw) { | ||
deserialize (raw) { | ||
return Immutable.List(raw) | ||
@@ -132,3 +132,3 @@ } | ||
//... | ||
register() { | ||
register () { | ||
return { | ||
@@ -138,3 +138,3 @@ [addPlanet]: this.append | ||
}, | ||
append(planets, params) { | ||
append (planets, params) { | ||
return planets.concat(params) | ||
@@ -146,44 +146,1 @@ } | ||
``` | ||
### `commit(next, staged)` | ||
Think of this as: How should a domin write to the "public" `repo.state` (to be consumed by your React components) when storing a different "private" representation of your data in your domain? | ||
For example: if you want to serialize a complex data structure, such as a `Map`, into a form easier for public consumption: | ||
```javascript | ||
import Immutable from 'immutable' | ||
const Planets = { | ||
getInitialState() { | ||
return Immutable.Map() | ||
}, | ||
commit(next) { | ||
return Array.from(next.values()) | ||
} | ||
} | ||
``` | ||
### `shouldCommit(last, next)` | ||
Based on the next and last state, should `commit` be called? Useful for | ||
custom change management behavior. | ||
```javascript | ||
import Immutable from 'immutable' | ||
const Planets = { | ||
getInitialState() { | ||
return Immutable.Map() | ||
}, | ||
shouldCommit(last, next) { | ||
return Immutable.is(last, next) | ||
} | ||
commit(next) { | ||
return Array.from(next.values()) | ||
} | ||
} | ||
``` |
@@ -29,4 +29,6 @@ # Effects | ||
```javascript | ||
// /src/effects/location.js | ||
import url from 'url' | ||
import {patch} from '../actions/query' | ||
import {patchQuery} from '../actions/query' | ||
@@ -75,2 +77,4 @@ class Location { | ||
```javascript | ||
// /src/effects/planets.js | ||
import { addPlanet } from '../actions/planets' | ||
@@ -91,3 +95,4 @@ | ||
repo.push(Actions.add, { name: 'earth' }) // this will add Earth | ||
repo.addEffect(Planets) | ||
repo.push(addPlanet, { name: 'earth' }) // this will add Earth | ||
``` |
@@ -5,3 +5,3 @@ # Presenter | ||
2. [Computed Properties](#computed-properties) | ||
3. [Receiving Intents](#receiving-intents) | ||
3. [Receiving Actions](#receiving-actions) | ||
4. [API](#api) | ||
@@ -12,10 +12,12 @@ | ||
The Presenter add-on makes it easier to keep application logic high | ||
within a component tree. It is designed specifically to extract and | ||
compute properties coming from a Microcosm instance and efficiently | ||
send them down as `props` to child "passive view" React components. | ||
within a component tree. It subscribes to state changes via a | ||
`getModel` method, designed specifically to extract and compute | ||
properties coming from a Microcosm instance. When state changes, model | ||
keys are efficiently sent down as props to child “passive view” React | ||
components. | ||
Presenters also make it easy for components deep within a component | ||
tree to communicate without passing a long chain of props. The | ||
`withIntent` and `<Form />` may be used to broadcast messages called | ||
"intents" to parent Presenter components, or straight to a Microcosm | ||
`withSend` and `<Form />` may be used to broadcast messages called | ||
"actions" to parent Presenter components, or straight to a Microcosm | ||
repo itself if no Presenter intercepts the message. | ||
@@ -42,3 +44,3 @@ | ||
model (props, state) { | ||
getModel (props, state) { | ||
return { | ||
@@ -49,4 +51,6 @@ planets: data => data.planets | ||
view ({ planets }) { | ||
return <p>{ planets.join(', ') }</p> | ||
render () { | ||
const { planets } = this.model | ||
return <p>{planets.join(', ')}</p> | ||
} | ||
@@ -64,28 +68,10 @@ | ||
### Listening to all state changes | ||
## Receiving Actions | ||
While we do not recommend it for large repos, some times it's simply easier | ||
to subscribe to all repo changes. `model` can also return a function, which | ||
will be called with state: | ||
```javascript | ||
class PlanetsPresenter extends Presenter { | ||
model () { | ||
return data => data | ||
} | ||
view ({ planets }) { | ||
return <p>{ planets.join(', ') }</p> | ||
} | ||
} | ||
``` | ||
## Receiving Intents | ||
Though explicit, passing event callbacks down through a deep component | ||
hierarchy can be cumbersome and brittle. Presenters expose a method on | ||
`context` that enable child components to declare `intents` receivable | ||
`context` that enable child components to declare `actions` receivable | ||
by Presenters. | ||
The Form add-on can be used to broadcast intents to Presenters: | ||
The Form add-on can be used to broadcast actions to Presenters: | ||
@@ -110,3 +96,3 @@ ```javascript | ||
}, | ||
register() { | ||
intercept() { | ||
return { | ||
@@ -120,3 +106,3 @@ [increaseCount] : this.increase | ||
return ( | ||
<Form intent="increaseCount"> | ||
<Form action="increaseCount"> | ||
<input type="hidden" name="amount" value="1" /> | ||
@@ -130,3 +116,3 @@ <p>The current count is { count }</p> | ||
class CountPresenter extends Presenter { | ||
model () { | ||
getModel () { | ||
return { | ||
@@ -137,3 +123,3 @@ count: data => data.count | ||
register () { | ||
intercept () { | ||
return { | ||
@@ -148,4 +134,6 @@ increaseCount: this.increaseCount | ||
view ({ count }) { | ||
return <StepperForm count={ count } /> | ||
render () { | ||
const { count } = this.model | ||
return <StepperForm count={count} /> | ||
} | ||
@@ -157,11 +145,9 @@ } | ||
Whenever the form is submitted, an `increaseCount` intent will bubble | ||
Whenever the form is submitted, an `increaseCount` action will bubble | ||
up to the associated Presenter including the serialized parameters of | ||
the form. Since this Presenter's register method includes `increaseCount`, it will | ||
the form. Since this Presenter's intercept method includes `increaseCount`, it will | ||
invoke the method with the associated parameters. | ||
If a Presenter does not implement an intent, it will bubble up to any | ||
parent Presenters. If no Presenter implements the intent, an exception | ||
will raise. This is useful for broadcasting action intents. For | ||
example, we could replace the prior Form example with: | ||
If a Presenter does not intercept an action, it will bubble up to any | ||
parent Presenters. If no Presenter intercepts the action, it will dispatch the action to the repo. | ||
@@ -171,3 +157,3 @@ ```javascript | ||
return ( | ||
<Form intent={ increaseCount }> | ||
<Form action={ increaseCount }> | ||
<input type="hidden" name="amount" value="1" /> | ||
@@ -183,9 +169,12 @@ <p>The current count is { count }</p> | ||
### `setup(repo, props)` | ||
### `setup(repo, props, state)` | ||
Called when a presenter is created, useful for initial data fetching and other | ||
prep work. | ||
Called when a presenter is created, useful any prep work. `setup` runs before the first `getModel` invocation. | ||
### `update(repo, props)` | ||
### `ready(repo, props, state)` | ||
Called after the presenter has run `setup` and executed the first `getModel`. This hook is useful for fetching initial data and other start tasks that need access to the model data. | ||
### `update(repo, props, state)` | ||
Called when a presenter gets new props. This is useful for secondary | ||
@@ -195,18 +184,16 @@ data fetching and other work that must happen when a Presenter receives | ||
If pure (by default) this will only get called if properties are shallowly | ||
different. | ||
`update` is always executed after the latest model has been calculated. | ||
### `teardown(repo, props)` | ||
### `teardown(repo, props, state)` | ||
Runs when the presenter unmounts. Useful for tearing down subscriptions and other setup behavior. | ||
### `model(props, state)` | ||
### `getModel(props, state)` | ||
Builds a view model for the current props and state. This must return | ||
an object of key/value pairs. If the value is a function, it will be | ||
calculated by passing in the repo's current state: | ||
an object of key/value pairs. | ||
```javascript | ||
class PlanetPresenter extends Presenter { | ||
model (props, state) { | ||
getModel (props, state) { | ||
return { | ||
@@ -220,14 +207,22 @@ planet : data => data.planets.find(p => p.id === props.planetId) | ||
If the Presenter is pure (passed in as either a prop or as an option to the | ||
associated repo), this will only update state if shallowly equal. | ||
`getModel` assigns a `model` property to the presenter, similarly to `props` or | ||
`state`. It is recalculated whenever the Presenter's `props` or `state` | ||
changes, and functions returned from model keys are invoked every time the repo | ||
changes. | ||
When not implemented, model returns all repo state. | ||
### `view` | ||
### `view(model)` | ||
If a Presenter has a `view` property, it creates the associated component | ||
instead of calling `render`. The `view` component is given the latest model | ||
data: | ||
A special render method that is given the current result of `model`. | ||
```javascript | ||
function Message ({ message }) { | ||
return <p>{message}</p> | ||
} | ||
```javascript | ||
class Greeter extends Presenter { | ||
model ({ greet }) | ||
view = Message | ||
getModel ({ greet }) | ||
return { | ||
@@ -237,29 +232,22 @@ message: "Hello, " + greet | ||
} | ||
view ({ message }) { | ||
return <p>{message}</p> | ||
} | ||
} | ||
``` | ||
Views may also be assigned as getters, or as properties: | ||
Views may also be assigned as a getter: | ||
```javascript | ||
class Example extends Presenter { | ||
class ShowPlanet extends Presenter { | ||
getModel (props) { | ||
return { | ||
planet: state => state.planets.find(p => p.id === props.id) | ||
} | ||
} | ||
get view { | ||
return MyView | ||
return this.model.planet ? PlanetView : MissingView | ||
} | ||
} | ||
// Or use newer class features: | ||
class Example extends Presenter { | ||
view = MyView | ||
} | ||
``` | ||
If a view is a React component, it will invoke it with the Presenter's | ||
model as props (including children). | ||
Views are passed the `send` method on a Presenter. This provides the | ||
exact same behavior as `withIntent`: | ||
exact same behavior as `withSend`: | ||
@@ -274,3 +262,3 @@ ```javascript | ||
register () { | ||
intercept () { | ||
return { | ||
@@ -283,7 +271,7 @@ 'test': () => alert("This is a test!") | ||
### register() | ||
### intercept() | ||
Expose "intent" subscriptions to child components. This is used with the `Form` or `withIntent` | ||
add-ons to improve the ergonomics of presenter/view communication (though this only | ||
occurs from the view to the presenter). | ||
Catch an action emitted from a child view, using an add-on `Form`, | ||
`ActionButton`, or `withSend`. These add-ons are designed to improve the | ||
ergonomics of presenter/view communication. Data down, actions up. | ||
@@ -294,3 +282,3 @@ ```javascript | ||
class HelloWorldPresenter extends Presenter { | ||
register() { | ||
intercept () { | ||
return { | ||
@@ -300,8 +288,8 @@ 'greet': this.greet | ||
} | ||
greet() { | ||
greet () { | ||
alert("hello world!") | ||
} | ||
view () { | ||
render () { | ||
return ( | ||
<Form intent="greet"> | ||
<Form action="greet"> | ||
<button>Greet</button> | ||
@@ -308,0 +296,0 @@ </Form> |
@@ -112,7 +112,7 @@ # Architecture | ||
However they can also communicate user actions back to the application using | ||
_Intents_. | ||
_Actions_. | ||
### Intents | ||
### Actions | ||
Intents provide a way for views to report on user behavior in a way that does | ||
Actions provide a way for views to report on user behavior in a way that does | ||
not couple them to specific implementation details within a Presenter. A View | ||
@@ -122,10 +122,10 @@ can simply broadcast that something has happened, allowing a Presenter (or a | ||
By wrapping a View with the `withIntent` add-on, Views receive a `send` prop | ||
that allows them to broadcast Intents. | ||
By wrapping a View with the `withSend` add-on, Views receive a `send` prop | ||
that allows them to broadcast Actions. | ||
```javascript | ||
import React from 'react' | ||
import withIntent from 'microcosm/addons/with-intent' | ||
import withSend from 'microcosm/addons/with-send' | ||
export default withIntent(function DeleteButton ({ send, id }) { | ||
export default withSend(function DeleteButton ({ send, id }) { | ||
return ( | ||
@@ -138,3 +138,3 @@ <button onClick={() => send('delete', id)}>Delete</button> | ||
By implementing a `register` method, a Presenter can subscribe to these | ||
intents, adding intermediary processing or just push an action: | ||
actions, adding intermediary processing or just push an action: | ||
@@ -151,10 +151,10 @@ ```javascript | ||
Or, Intents can also take the form of Actions: | ||
Or, Actions can also take the form of Actions: | ||
```javascript | ||
import React from 'react' | ||
import withIntent from 'microcosm/addons/with-intent' | ||
import withSend from 'microcosm/addons/with-send' | ||
import {deletePlanet} from 'actions/planets' | ||
export default withIntent(function DeleteButton ({ send, id }) { | ||
export default withSend(function DeleteButton ({ send, id }) { | ||
return ( | ||
@@ -167,3 +167,3 @@ <button onClick={() => send(deletePlanet, id)}>Delete</button> | ||
In this case, there's no need for the Presenter to intercept the event. If no | ||
Presenter registers to a given intent, it will get passed along to the Repo. | ||
Presenter registers to a given action, it will get passed along to the Repo. | ||
@@ -170,0 +170,0 @@ ## Actions |
@@ -149,3 +149,3 @@ # Quickstart | ||
view () { | ||
render () { | ||
return ( | ||
@@ -211,3 +211,3 @@ <ul> | ||
view () { | ||
render () { | ||
return <PlanetList /> | ||
@@ -222,9 +222,8 @@ } | ||
Awesome. Well, not _really_. Now the `PlanetList` view components knows | ||
Awesome. Well, not _really_. Now the `PlanetList` component knows | ||
all about the data. It shouldn't care whether or not Pluto's a _real_ | ||
planet. Let's fix that. | ||
We need to prepare data in the presenter to send down into the | ||
view. Presenters can implement a `model` method that to do just | ||
that: | ||
We need to prepare data in the presenter to send down into the `PlanetList` | ||
view. Presenters can implement a `getModel` method that to do just that: | ||
@@ -239,3 +238,3 @@ ```javascript | ||
model () { | ||
getModel () { | ||
return { | ||
@@ -246,3 +245,5 @@ planets: () => ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto'] | ||
view ({ planets }) { | ||
render () { | ||
const { planets } = this.model | ||
return <PlanetList planets={planets} /> | ||
@@ -330,3 +331,3 @@ } | ||
model () { | ||
getModel () { | ||
return { | ||
@@ -338,3 +339,5 @@ // I'm new. Pull planets out of the repo's state | ||
view ({ planets }) { | ||
render () { | ||
const { planets } = this.model | ||
return <PlanetList planets={planets} /> | ||
@@ -387,3 +390,3 @@ } | ||
const Planets = { | ||
getInitialState() { | ||
getInitialState () { | ||
// Remember, we put the planets data into the action | ||
@@ -393,7 +396,7 @@ return [] | ||
append(planets, data) { | ||
append (planets, data) { | ||
return planets.concat(data) | ||
}, | ||
register() { | ||
register () { | ||
return { | ||
@@ -427,7 +430,7 @@ // Curious? This works because Microcosm assigns a unique | ||
setup(repo) { | ||
setup (repo) { | ||
repo.push(getPlanets) | ||
} | ||
model () { | ||
getModel () { | ||
return { | ||
@@ -438,3 +441,5 @@ planets: state => state.planets | ||
view ({ planets }) { | ||
render () { | ||
const { planets } = this.model | ||
return <PlanetList planets={planets} /> | ||
@@ -441,0 +446,0 @@ } |
@@ -12,3 +12,3 @@ ## Introduction | ||
4. [Effects](api/effects.md) | ||
5. [Data Utilities](api/data-utilities.md) | ||
5. [Immutability Helpers](api/immutability-helpers.md) | ||
@@ -18,5 +18,5 @@ ## Addons | ||
1. [Presenter](api/presenter.md) | ||
2. [Form](api/form.md) | ||
3. [IntentButton](api/intent-button.md) | ||
4. [withIntent](api/with-intent.md) | ||
2. [ActionForm](api/action-form.md) | ||
3. [ActionButton](api/action-button.md) | ||
4. [withSend](api/with-send.md) | ||
@@ -28,3 +28,3 @@ ## Testing | ||
3. [Effects](testing/effects.md) | ||
4. [Intents](testing/intents.md) | ||
4. [Presenters](testing/presenters.md) | ||
@@ -35,4 +35,3 @@ ## Recipes | ||
2. [AJAX](recipes/ajax.md) | ||
3. [ImmutableJS](recipes/immutable-js.md) | ||
4. [Preact](recipes/preact.md) | ||
5. [Hydrating State](recipes/hydrating-state.md) | ||
3. [Preact](recipes/preact.md) | ||
4. [Hydrating State](recipes/hydrating-state.md) |
@@ -29,5 +29,7 @@ # AJAX | ||
return { | ||
[getSite.open] : () => 'loading', | ||
[getSite.error] : () => 'error', | ||
[getSite.done] : () => 'done' | ||
[getSite]: { | ||
open : () => 'loading', | ||
error : () => 'error', | ||
done : () => 'done' | ||
} | ||
} | ||
@@ -118,5 +120,7 @@ }) | ||
return { | ||
[getSite.open] : () => 'loading', | ||
[getSite.error] : () => 'error', | ||
[getSite.done] : () => 'done' | ||
[getSite]: { | ||
open : () => 'loading', | ||
error : () => 'error', | ||
done : () => 'done' | ||
} | ||
} | ||
@@ -123,0 +127,0 @@ }) |
@@ -133,3 +133,3 @@ # Testing Domains | ||
```javascript | ||
test('it sets a loading state when pushing getPlanets', assert => { | ||
it('it sets a loading state when pushing getPlanets', assert => { | ||
const repo = new SolarSystem() | ||
@@ -144,3 +144,3 @@ | ||
test('it sets a loading state when pushing getPlanets', assert => { | ||
it('it sets a loading state when pushing getPlanets', assert => { | ||
const repo = new SolarSystem() | ||
@@ -147,0 +147,0 @@ |
1323
microcosm.js
@@ -8,6 +8,5 @@ 'use strict'; | ||
* to utilize events. | ||
* @constructor | ||
*/ | ||
function Emitter () { | ||
function Emitter() { | ||
this._events = null; | ||
@@ -20,3 +19,3 @@ } | ||
*/ | ||
on: function on (event, fn, scope, once) { | ||
on: function on(event, fn, scope, once) { | ||
if (this._events == null) { | ||
@@ -28,5 +27,6 @@ this._events = []; | ||
return this | ||
return this; | ||
}, | ||
/** | ||
@@ -36,6 +36,7 @@ * Adds an `event` listener that will be invoked a single time then | ||
*/ | ||
once: function once (event, fn, scope) { | ||
return this.on(event, fn, scope, true) | ||
once: function once(event, fn, scope) { | ||
return this.on(event, fn, scope, true); | ||
}, | ||
/** | ||
@@ -45,7 +46,5 @@ * Unsubscribe a callback. If no event is provided, removes all callbacks. If | ||
*/ | ||
off: function off (event, fn, scope) { | ||
var this$1 = this; | ||
off: function off(event, fn, scope) { | ||
if (this._events == null) { | ||
return this | ||
return this; | ||
} | ||
@@ -57,8 +56,8 @@ | ||
while (i < this._events.length) { | ||
var cb = this$1._events[i]; | ||
var cb = this._events[i]; | ||
if (cb.event === event) { | ||
if (removeAll || (cb.fn === fn && cb.scope === scope)) { | ||
this$1._events.splice(i, 1); | ||
continue | ||
if (removeAll || cb.fn === fn && cb.scope === scope) { | ||
this._events.splice(i, 1); | ||
continue; | ||
} | ||
@@ -70,17 +69,15 @@ } | ||
return this | ||
return this; | ||
}, | ||
removeAllListeners: function removeAllListeners () { | ||
removeAllListeners: function removeAllListeners() { | ||
this._events = null; | ||
}, | ||
/** | ||
* Emit `event` with the given args. | ||
*/ | ||
_emit: function _emit (event, payload) { | ||
var this$1 = this; | ||
_emit: function _emit(event, payload) { | ||
if (this._events == null) { | ||
return this | ||
return this; | ||
} | ||
@@ -90,10 +87,10 @@ | ||
while (i < this._events.length) { | ||
var cb = this$1._events[i]; | ||
var cb = this._events[i]; | ||
if (cb.event === event) { | ||
cb.fn.call(cb.scope || this$1, payload); | ||
cb.fn.call(cb.scope || this, payload); | ||
if (cb.once) { | ||
this$1._events.splice(i, 1); | ||
continue | ||
this._events.splice(i, 1); | ||
continue; | ||
} | ||
@@ -105,66 +102,7 @@ } | ||
return this | ||
return this; | ||
} | ||
}; | ||
var uid = 0; | ||
var FALLBACK = '_action'; | ||
var toString = function () { | ||
return this.done | ||
}; | ||
/** | ||
* Uniquely tag a function. This is used to identify actions. | ||
* @param {Function} fn The target function to add action identifiers to. | ||
* @param {String} [name] An override to use instead of `fn.name`. | ||
* @return {Function} The tagged function (same as `fn`). | ||
*/ | ||
function tag (fn, name) { | ||
if (fn == null) { | ||
throw new Error(("Unable to identify " + fn + " action")) | ||
} | ||
if (fn.done) { | ||
return fn | ||
} | ||
if (typeof fn === 'string') { | ||
name = fn; | ||
fn = function (n) { return n; }; | ||
} | ||
/** | ||
* Auto-increment a stepper suffix to prevent two actions with the | ||
* same name from colliding. | ||
*/ | ||
uid += 1; | ||
/** | ||
* Function.name lacks legacy support. For these browsers, fallback | ||
* to a consistent name: | ||
*/ | ||
var symbol = name || (fn.name || FALLBACK) + '.' + uid; | ||
fn.open = symbol + '.open'; | ||
fn.loading = symbol + '.loading'; | ||
fn.update = fn.loading; | ||
fn.done = symbol; // intentional | ||
fn.resolve = fn.done; | ||
fn.error = symbol + '.error'; | ||
fn.reject = fn.error; | ||
fn.cancelled = symbol + '.cancelled'; | ||
fn.cancel = fn.cancelled; | ||
// The default state is done | ||
fn.toString = toString; | ||
return fn | ||
} | ||
/** | ||
* The central tree data structure that is used to calculate state for | ||
@@ -176,4 +114,4 @@ * a Microcosm. Each node in the tree represents an action. Branches | ||
*/ | ||
function History (limit) { | ||
if ( limit === void 0 ) limit=0; | ||
function History() { | ||
var limit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; | ||
@@ -185,7 +123,7 @@ this.repos = []; | ||
History.prototype = { | ||
root : null, | ||
focus : null, | ||
head : null, | ||
size : 0, | ||
limit : 0, | ||
root: null, | ||
focus: null, | ||
head: null, | ||
size: 0, | ||
limit: 0, | ||
@@ -196,6 +134,7 @@ /** | ||
*/ | ||
addRepo: function addRepo (repo) { | ||
addRepo: function addRepo(repo) { | ||
this.repos.push(repo); | ||
}, | ||
/** | ||
@@ -205,6 +144,9 @@ * Stop tracking a repo | ||
*/ | ||
removeRepo: function removeRepo (repo) { | ||
this.repos = this.repos.filter(function (r) { return r != repo; }); | ||
removeRepo: function removeRepo(repo) { | ||
this.repos = this.repos.filter(function (r) { | ||
return r != repo; | ||
}); | ||
}, | ||
/** | ||
@@ -215,3 +157,3 @@ * Run a method on every repo, if it implements it. | ||
*/ | ||
invoke: function invoke (method, payload) { | ||
invoke: function invoke(method, payload) { | ||
var repos = this.repos; | ||
@@ -224,2 +166,3 @@ | ||
/** | ||
@@ -231,3 +174,3 @@ * Adjust the focus point to target a different node. This has the effect of | ||
*/ | ||
checkout: function checkout (action) { | ||
checkout: function checkout(action) { | ||
this.head = action; | ||
@@ -244,5 +187,6 @@ | ||
return this | ||
return this; | ||
}, | ||
/** | ||
@@ -253,3 +197,3 @@ * Create a new action and append it to the current focus, | ||
*/ | ||
append: function append (behavior) { | ||
append: function append(behavior) { | ||
var action = new Action(behavior, this); | ||
@@ -277,5 +221,6 @@ | ||
return this.head | ||
return this.head; | ||
}, | ||
/** | ||
@@ -285,3 +230,3 @@ * Handle a change to a node that happened prior to | ||
*/ | ||
invalidate: function invalidate () { | ||
invalidate: function invalidate() { | ||
this.focus = null; | ||
@@ -294,9 +239,10 @@ | ||
/** | ||
* @param {Action} action | ||
*/ | ||
reconcile: function reconcile (action) { | ||
reconcile: function reconcile(action) { | ||
// No need to run this function if there are no active repos | ||
if (this.repos.length <= 0) { | ||
return false | ||
return false; | ||
} | ||
@@ -310,6 +256,3 @@ | ||
}, | ||
rollforward: function rollforward () { | ||
var this$1 = this; | ||
rollforward: function rollforward() { | ||
var action = this.focus ? this.focus.next : this.root; | ||
@@ -320,3 +263,3 @@ var cacheable = true; | ||
if (!action.disabled) { | ||
this$1.invoke('reconcile', action); | ||
this.invoke('reconcile', action); | ||
} | ||
@@ -327,4 +270,4 @@ | ||
if (cacheable && action.disposable) { | ||
this$1.focus = action; | ||
this$1.invoke('cache', this$1.archive()); | ||
this.focus = action; | ||
this.invoke('cache', this.archive()); | ||
} else { | ||
@@ -337,4 +280,3 @@ cacheable = false; | ||
}, | ||
archive: function archive () { | ||
archive: function archive() { | ||
var shouldArchive = this.size > this.limit; | ||
@@ -355,9 +297,10 @@ | ||
return shouldArchive | ||
return shouldArchive; | ||
}, | ||
/** | ||
* Update the current size and active branch path | ||
*/ | ||
adjustSize: function adjustSize () { | ||
adjustSize: function adjustSize() { | ||
var action = this.head; | ||
@@ -378,6 +321,3 @@ var size = this.root ? 1 : 0; | ||
}, | ||
forEach: function forEach (fn, scope) { | ||
var this$1 = this; | ||
forEach: function forEach(fn, scope) { | ||
var action = this.focus || this.root; | ||
@@ -388,4 +328,4 @@ | ||
if (action === this$1.head) { | ||
break | ||
if (action === this.head) { | ||
break; | ||
} | ||
@@ -396,4 +336,3 @@ | ||
}, | ||
map: function map (fn, scope) { | ||
map: function map(fn, scope) { | ||
var items = []; | ||
@@ -405,19 +344,115 @@ | ||
return items | ||
return items; | ||
}, | ||
toArray: function toArray() { | ||
return this.map(function (n) { | ||
return n; | ||
}); | ||
} | ||
}; | ||
toArray: function toArray () { | ||
return this.map(function (n) { return n; }) | ||
var uid = 0; | ||
var FALLBACK = '_action'; | ||
var toString = function toString() { | ||
return this.done; | ||
}; | ||
/** | ||
* Uniquely tag a function. This is used to identify actions. | ||
* @param {Function} fn The target function to add action identifiers to. | ||
* @param {String} [name] An override to use instead of `fn.name`. | ||
* @return {Function} The tagged function (same as `fn`). | ||
*/ | ||
function tag(fn, name) { | ||
if (fn == null) { | ||
throw new Error('Unable to identify ' + fn + ' action'); | ||
} | ||
if (fn.done) { | ||
return fn; | ||
} | ||
if (typeof fn === 'string') { | ||
name = fn; | ||
fn = function fn(n) { | ||
return n; | ||
}; | ||
} | ||
/** | ||
* Auto-increment a stepper suffix to prevent two actions with the | ||
* same name from colliding. | ||
*/ | ||
uid += 1; | ||
/** | ||
* Function.name lacks legacy support. For these browsers, fallback | ||
* to a consistent name: | ||
*/ | ||
var symbol = name || (fn.name || FALLBACK) + '.' + uid; | ||
fn.open = symbol + '.open'; | ||
fn.loading = symbol + '.loading'; | ||
fn.update = fn.loading; | ||
fn.done = symbol; // intentional for string actions | ||
fn.resolve = fn.done; | ||
fn.error = symbol + '.error'; | ||
fn.reject = fn.error; | ||
fn.cancel = symbol + '.cancel'; | ||
fn.cancelled = fn.cancel; | ||
// The default state is done | ||
fn.toString = toString; | ||
return fn; | ||
} | ||
/** | ||
* Actions move through a specific set of states. This manifest | ||
* controls how they should behave. | ||
*/ | ||
var ACTION_STATES = [{ key: 'open', disposable: false, once: true, listener: 'onOpen' }, { key: 'update', disposable: false, once: false, listener: 'onUpdate' }, { key: 'resolve', disposable: true, once: true, listener: 'onDone' }, { key: 'reject', disposable: true, once: true, listener: 'onError' }, { key: 'cancel', disposable: true, once: true, listener: 'onCancel' }]; | ||
// For nested registrations, track our aliases | ||
var ACTION_ALIASES = { | ||
inactive: 'inactive', | ||
open: 'open', | ||
update: 'loading', | ||
loading: 'update', | ||
done: 'resolve', | ||
resolve: 'done', | ||
reject: 'error', | ||
error: 'reject', | ||
cancel: 'cancelled', | ||
cancelled: 'cancel' | ||
}; | ||
var hasOwn = Object.prototype.hasOwnProperty; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
var EMPTY_ARRAY = []; | ||
function castPath(value) { | ||
if (Array.isArray(value)) { | ||
return value; | ||
} else if (value == null) { | ||
return EMPTY_ARRAY; | ||
} | ||
return typeof value === 'string' ? value.split('.') : [value]; | ||
} | ||
/** | ||
* Shallow copy an object | ||
*/ | ||
function clone (a) { | ||
if (Array.isArray(a)) { | ||
return a.slice(0) | ||
function clone(target) { | ||
if (Array.isArray(target)) { | ||
return target.slice(0); | ||
} else if (isObject(target) === false) { | ||
return target; | ||
} | ||
@@ -427,7 +462,7 @@ | ||
for (var key in a) { | ||
copy[key] = a[key]; | ||
for (var key in target) { | ||
copy[key] = target[key]; | ||
} | ||
return copy | ||
return copy; | ||
} | ||
@@ -438,9 +473,11 @@ | ||
*/ | ||
function merge (subject) { | ||
var arguments$1 = arguments; | ||
function merge() { | ||
var copy = null; | ||
var subject = null; | ||
var copy = subject; | ||
for (var i = 0, len = arguments.length; i < len; i++) { | ||
copy = copy || arguments[i]; | ||
subject = subject || copy; | ||
for (var i = 1, len = arguments.length; i < len; i++) { | ||
var next = arguments$1[i]; | ||
var next = arguments[i]; | ||
@@ -458,3 +495,3 @@ for (var key in next) { | ||
return copy | ||
return copy; | ||
} | ||
@@ -465,3 +502,3 @@ | ||
*/ | ||
function inherit (Child, Ancestor, proto) { | ||
function inherit(Child, Ancestor, proto) { | ||
Child.__proto__ = Ancestor; | ||
@@ -473,106 +510,97 @@ | ||
return Child | ||
return Child; | ||
} | ||
/** | ||
* Retrieve a value from an object. If no key is provided, just | ||
* return the object. | ||
* Retrieve a value from an object. If no key is provided, just return the | ||
* object. | ||
*/ | ||
function get (object, key, fallback) { | ||
function get(object, path, fallback) { | ||
if (object == null) { | ||
return fallback | ||
} else if (key == null) { | ||
return object | ||
return fallback; | ||
} | ||
if (Array.isArray(key)) { | ||
return getIn(object, key, fallback) | ||
} | ||
path = castPath(path); | ||
return hasOwn.call(object, key) ? object[key] : fallback | ||
} | ||
for (var i = 0, len = path.length; i < len; i++) { | ||
var value = object == null ? undefined : object[path[i]]; | ||
/** | ||
* Retrieve a value deeply within an object given an array of sequential | ||
* keys. | ||
*/ | ||
function getIn (object, keys, fallback) { | ||
var value = object; | ||
if (value === undefined) { | ||
i = len; | ||
value = fallback; | ||
} | ||
for (var i = 0, len = keys.length; i < len; i++) { | ||
value = get(value, keys[i], fallback); | ||
object = value; | ||
} | ||
return value | ||
return object; | ||
} | ||
/** | ||
* Immutabily assign a value to a provided object at a given key. If | ||
* the value is the same, don't do anything. Otherwise return a new | ||
* object. | ||
* Non-destructively assign a value to a provided object at a given key. If the | ||
* value is the same, don't do anything. Otherwise return a new object. | ||
*/ | ||
function set (object, key, value) { | ||
if (Array.isArray(key)) { | ||
return setIn(object, key, value) | ||
} | ||
function set(object, path, value) { | ||
// Ensure we're working with a key path, like: ['a', 'b', 'c'] | ||
path = castPath(path); | ||
// If the key path is null, there's no need to traverse the | ||
// object. Just return the value. | ||
if (key == null) { | ||
return value | ||
var len = path.length; | ||
if (len <= 0) { | ||
return value; | ||
} | ||
if (value === undefined || get(object, key) === value) { | ||
return object | ||
if (get(object, path) === value) { | ||
return object; | ||
} | ||
var copy = clone(object); | ||
var root = clone(object); | ||
var node = root; | ||
copy[key] = value; | ||
// For each key in the path... | ||
for (var i = 0; i < len; i++) { | ||
var key = path[i]; | ||
var next = value; | ||
return copy | ||
} | ||
// Are we at the end? | ||
if (i < len - 1) { | ||
// No: Check to see if the key is already assigned, | ||
if (key in node) { | ||
// If yes, clone that value | ||
next = clone(node[key]); | ||
} else { | ||
// Otherwise assign an object so that we can keep drilling down | ||
next = {}; | ||
} | ||
} | ||
/** | ||
* Deeply assign a value given a path of sequential keys. | ||
*/ | ||
function setIn (object, keys, value) { | ||
if (getIn(object, keys) === value) { | ||
return object | ||
// Assign the value, then continue on to the next iteration of the loop | ||
// using the next step down | ||
node[key] = next; | ||
node = node[key]; | ||
} | ||
var key = keys[0]; | ||
var rest = keys.slice(1); | ||
var copy = clone(object); | ||
return root; | ||
} | ||
if (rest.length) { | ||
copy[key] = (key in copy) ? setIn(copy[key], rest, value) : setIn({}, rest, value); | ||
} else { | ||
copy[key] = value; | ||
} | ||
return copy | ||
function isPromise(obj) { | ||
var type = typeof obj === 'undefined' ? 'undefined' : _typeof(obj); | ||
return !!obj && (type === 'object' || type === 'function') && typeof obj.then === 'function'; | ||
} | ||
/** | ||
* Compile a key path list for indexes | ||
*/ | ||
function splitKeyPath (string) { | ||
return string.split(/\./) | ||
function isObject(target) { | ||
return !!target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object'; | ||
} | ||
function compileKeyPaths (string) { | ||
var items = string.split(/\s*\,\s*/); | ||
function createOrClone(target, options, repo) { | ||
if (typeof target === 'function') { | ||
return new target(options, repo); | ||
} | ||
return items.map(splitKeyPath) | ||
return Object.create(target); | ||
} | ||
/** | ||
* Given a query (see above), return a subset of an object. | ||
* A helper combination of get and set | ||
*/ | ||
function extract (object, keyPaths, seed) { | ||
return keyPaths.reduce(function (memo, keyPath) { | ||
return set(memo, keyPath, get(object, keyPath)) | ||
}, seed || {}) | ||
} | ||
@@ -582,6 +610,4 @@ /** | ||
* action using `Microcosm::push`: | ||
* @constructor | ||
* @extends {Emitter} | ||
*/ | ||
function Action (behavior, history) { | ||
function Action(behavior, history) { | ||
Emitter.call(this); | ||
@@ -594,80 +620,52 @@ | ||
inherit(Action, Emitter, { | ||
type : null, | ||
payload : undefined, | ||
disabled : false, | ||
disposable : false, | ||
parent : null, | ||
first : null, | ||
next : null, | ||
sibling : null, | ||
type: null, | ||
status: 'inactive', | ||
payload: undefined, | ||
disabled: false, | ||
disposable: false, | ||
parent: null, | ||
first: null, | ||
next: null, | ||
sibling: null, | ||
/** | ||
* Given a string or State constant, determine if the `state` bitmask for | ||
* the action includes the provided type. | ||
* @private | ||
*/ | ||
is: function is (type) { | ||
return this.type === this.behavior[type] | ||
is: function is(type) { | ||
return this.behavior[this.status] === this.behavior[type]; | ||
}, | ||
toggle: function toggle() { | ||
this.disabled = !this.disabled; | ||
/** | ||
* Set the action state to "open", then set a payload if provided. Triggers | ||
* the "open" event. | ||
*/ | ||
open: function open (payload) { | ||
if (!this.disposable) { | ||
this.type = this.behavior.open; | ||
this.history.invalidate(); | ||
if (arguments.length > 0) { | ||
this.payload = payload; | ||
} | ||
this.history.reconcile(this); | ||
this._emit('open', this.payload); | ||
} | ||
return this | ||
return this; | ||
}, | ||
then: function then(pass, fail) { | ||
var _this = this; | ||
/** | ||
* Set the action state to "loading", then set a payload if provided. | ||
* Triggers the "update" event. | ||
*/ | ||
update: function update (payload) { | ||
if (!this.disposable) { | ||
this.type = this.behavior.loading; | ||
return new Promise(function (resolve, reject) { | ||
_this.onDone(resolve); | ||
_this.onError(reject); | ||
}).then(pass, fail); | ||
} | ||
}); | ||
if (arguments.length > 0) { | ||
this.payload = payload; | ||
} | ||
/** | ||
* Generate action methods for each action state | ||
*/ | ||
ACTION_STATES.forEach(function (_ref) { | ||
var key = _ref.key, | ||
disposable = _ref.disposable, | ||
once = _ref.once, | ||
listener = _ref.listener; | ||
this.history.reconcile(this); | ||
this._emit('update', this.payload); | ||
} | ||
return this | ||
}, | ||
send: function send () { | ||
if (typeof console !== 'undefined') { | ||
console.warn('`send` was deprecated in 11.6.0.', | ||
'Please use `update` instead.', | ||
'`send` will be removed in 12.0.0.'); | ||
} | ||
return this.update.apply(this, arguments) | ||
}, | ||
/** | ||
* Set the action state to "error" and marks the action for clean up, then | ||
* set a payload if provided. Triggers the "error" event. | ||
* Create a method to update the action status. For example: | ||
* action.done({ id: 'earth' }) | ||
*/ | ||
reject: function reject (payload) { | ||
Action.prototype[key] = function (payload) { | ||
if (!this.disposable) { | ||
this.type = this.behavior.error; | ||
this.disposable = true; | ||
this.status = key; | ||
this.disposable = disposable; | ||
if (arguments.length > 0) { | ||
if (arguments.length) { | ||
this.payload = payload; | ||
@@ -678,146 +676,23 @@ } | ||
this._emit('error', this.payload); | ||
this._emit(key, this.payload); | ||
} | ||
return this | ||
}, | ||
return this; | ||
}; | ||
/** | ||
* Set the action state to "done" and marks the action for clean up, then set | ||
* a payload if provided. Triggers the "done" event. | ||
* Create a method to subscribe to a status. For example: | ||
* action.onDone({ id: 'earth' }) | ||
*/ | ||
resolve: function resolve (payload) { | ||
if (!this.disposable) { | ||
this.type = this.behavior.done; | ||
this.disposable = true; | ||
if (arguments.length > 0) { | ||
this.payload = payload; | ||
Action.prototype[listener] = function (callback, scope) { | ||
if (callback) { | ||
if (once && this.is(key)) { | ||
callback.call(scope, this.payload); | ||
} else { | ||
this.once(key, callback, scope); | ||
} | ||
this.history.reconcile(this); | ||
this._emit('done', this.payload); | ||
} | ||
return this | ||
}, | ||
/** | ||
* Set the action state to "cancelled" and marks the action for clean up, | ||
* then set a payload if provided. Triggers the "cancel" event. | ||
*/ | ||
cancel: function cancel (payload) { | ||
if (!this.disposable) { | ||
this.type = this.behavior.cancelled; | ||
this.disposable = true; | ||
if (arguments.length > 0) { | ||
this.payload = payload; | ||
} | ||
this.history.reconcile(this); | ||
this._emit('cancel', this.payload); | ||
} | ||
return this | ||
}, | ||
/** | ||
* Toggles the disabled state, where the action will not dispatch. This is | ||
* useful in the Microcosm debugger to quickly enable/disable actions. | ||
* Triggers the "change" event. | ||
*/ | ||
toggle: function toggle () { | ||
this.disabled = !this.disabled; | ||
this.history.invalidate(); | ||
return this | ||
}, | ||
/** | ||
* Listen to failure. If the action has already failed, it will execute the | ||
* provided callback, otherwise it will wait and trigger upon the "error" | ||
* event. | ||
*/ | ||
onError: function onError (callback, scope) { | ||
if (!callback) { | ||
return this | ||
} | ||
if (this.is('error')) { | ||
callback.call(scope, this.payload); | ||
} else { | ||
this.once('error', callback, scope); | ||
} | ||
return this | ||
}, | ||
/** | ||
* Listen to progress. Wait and trigger a provided callback on the "update" event. | ||
*/ | ||
onUpdate: function onUpdate (callback, scope) { | ||
if (!callback) { | ||
return this | ||
} | ||
this.on('update', callback, scope); | ||
return this | ||
}, | ||
/** | ||
* Listen for completion. If the action has already completed, it will | ||
* execute the provided callback, otherwise it will wait and trigger upon the | ||
* "done" event. | ||
*/ | ||
onDone: function onDone (callback, scope) { | ||
if (!callback) { | ||
return this | ||
} | ||
if (this.is('done')) { | ||
callback.call(scope, this.payload); | ||
} else { | ||
this.once('done', callback, scope); | ||
} | ||
return this | ||
}, | ||
/** | ||
* Listen for cancel. If the action has already cancelled, it will execute | ||
* the provided callback, otherwise it will wait and trigger upon the | ||
* "cancel" event. | ||
*/ | ||
onCancel: function onCancel (callback, scope) { | ||
if (!callback) { | ||
return this | ||
} | ||
if (this.is('cancelled')) { | ||
callback.call(scope, this.payload); | ||
} else { | ||
this.once('cancel', callback, scope); | ||
} | ||
return this | ||
}, | ||
/** | ||
* For interop with promises. Returns a promise that resolves or rejects | ||
* based on the action's resolution. | ||
*/ | ||
then: function then (pass, fail) { | ||
var this$1 = this; | ||
return new Promise(function (resolve, reject) { | ||
this$1.onDone(resolve); | ||
this$1.onError(reject); | ||
}).then(pass, fail) | ||
} | ||
return this; | ||
}; | ||
}); | ||
@@ -829,3 +704,3 @@ | ||
Object.defineProperty(Action.prototype, 'children', { | ||
get: function get$$1 () { | ||
get: function get$$1() { | ||
var children = []; | ||
@@ -839,103 +714,91 @@ var node = this.first; | ||
return children | ||
return children; | ||
} | ||
}); | ||
/** | ||
* Meta Domain | ||
* A domain for managing lifecycle methods and other default behavior | ||
* for other domains. | ||
*/ | ||
function sandbox(data, deserialize) { | ||
return function (action, repo) { | ||
var payload = data; | ||
function MetaDomain () { | ||
this.reset = function (data, deserialize) { | ||
return function (action, repo) { | ||
var initial = repo.getInitialState(); | ||
var payload = data; | ||
if (deserialize) { | ||
if (deserialize) { | ||
try { | ||
payload = repo.deserialize(data); | ||
} catch (error) { | ||
action.reject(error); | ||
} | ||
} | ||
action.resolve(merge(initial, payload)); | ||
} | ||
action.resolve(payload); | ||
}; | ||
} | ||
this.patch = function (data, deserialize) { | ||
return function (action, repo) { | ||
var payload = data; | ||
var RESET = tag(function reset(data, deserialize) { | ||
return sandbox(data, deserialize); | ||
}, 'reset'); | ||
if (deserialize) { | ||
payload = repo.deserialize(payload); | ||
} | ||
var PATCH = tag(function patch(data, deserialize) { | ||
return sandbox(data, deserialize); | ||
}, 'patch'); | ||
action.resolve(payload); | ||
} | ||
}; | ||
var ADD_DOMAIN = 'addDomain'; | ||
this.rebase = function (data) { | ||
return data | ||
}; | ||
function MetaDomain(_, repo) { | ||
this.repo = repo; | ||
} | ||
MetaDomain.prototype = { | ||
handleReset: function handleReset (state, data) { | ||
return data | ||
reset: function reset(state, data) { | ||
return this.patch(this.repo.getInitialState(), data); | ||
}, | ||
handlePatch: function handlePatch (state, data) { | ||
return merge(state, data) | ||
patch: function patch(state, data) { | ||
return this.repo.realm.prune(state, data); | ||
}, | ||
register: function register() { | ||
var _ref; | ||
handleRebase: function handleRebase (state, data) { | ||
return merge(data, state) | ||
}, | ||
return _ref = {}, _ref[RESET] = this.reset, _ref[PATCH] = this.patch, _ref; | ||
} | ||
}; | ||
register: function register () { | ||
var registry = {}; | ||
var DONE = 'done'; | ||
function getRegistration(pool, behavior, status) { | ||
var alias = status ? ACTION_ALIASES[status] : DONE; | ||
// TODO: This is to work around a parse issue with Buble | ||
registry[this.reset] = this.handleReset; | ||
registry[this.patch] = this.handlePatch; | ||
registry[this.rebase] = this.handleRebase; | ||
if (alias == null) { | ||
throw new ReferenceError('Invalid action status ' + status); | ||
} | ||
return registry | ||
if (pool && behavior) { | ||
if (isObject(pool[behavior])) { | ||
return pool[behavior][alias] || pool[behavior][status]; | ||
} else { | ||
return pool[behavior[alias]]; | ||
} | ||
} | ||
}; | ||
/** | ||
* Lifecycle methods are implementated as actions. This module | ||
* enumerates through a preset list of types and creates associated | ||
* actions. | ||
*/ | ||
return null; | ||
} | ||
var lifecycle = { | ||
getInitialState : 'getInitialState', | ||
serialize : 'serialize', | ||
deserialize : 'deserialize' | ||
}; | ||
function getDomainHandlers(domains, _ref) { | ||
var behavior = _ref.behavior, | ||
status = _ref.status; | ||
function getDomainHandlers (domains, type) { | ||
var handlers = []; | ||
var isLifecycle = lifecycle[type] != null; | ||
for (var i = 0, len = domains.length; i < len; i++) { | ||
var ref = domains[i]; | ||
var key = ref[0]; | ||
var domain = ref[1]; | ||
var _domains$i = domains[i], | ||
key = _domains$i[0], | ||
domain = _domains$i[1]; | ||
var handler = null; | ||
if (isLifecycle && domain[type] != null) { | ||
handler = domain[type]; | ||
} else if (domain.register != null) { | ||
handler = domain.register()[type]; | ||
} | ||
if (domain.register) { | ||
var handler = getRegistration(domain.register(), behavior, status); | ||
if (handler) { | ||
handlers.push({ key: key, domain: domain, handler: handler, length: handler.length }); | ||
if (handler) { | ||
handlers.push({ key: key, domain: domain, handler: handler, length: handler.length }); | ||
} | ||
} | ||
} | ||
return handlers | ||
return handlers; | ||
} | ||
@@ -946,3 +809,3 @@ | ||
*/ | ||
function Realm (repo) { | ||
function Realm(repo) { | ||
this.repo = repo; | ||
@@ -957,20 +820,14 @@ this.domains = []; | ||
Realm.prototype = { | ||
register: function register(action) { | ||
var type = action.behavior[action.status]; | ||
register: function register (type) { | ||
if (this.registry[type] == null) { | ||
this.registry[type] = getDomainHandlers(this.domains, type); | ||
this.registry[type] = getDomainHandlers(this.domains, action); | ||
} | ||
return this.registry[type] | ||
return this.registry[type]; | ||
}, | ||
add: function add(key, config, options) { | ||
var domain = createOrClone(config, options, this.repo); | ||
add: function add (key, config, options) { | ||
var domain = null; | ||
if (typeof config === 'function') { | ||
domain = new config(options); | ||
} else { | ||
domain = Object.create(config); | ||
} | ||
this.domains.push([key, domain]); | ||
@@ -989,39 +846,55 @@ | ||
return domain | ||
return domain; | ||
}, | ||
reduce: function reduce(fn, state, scope) { | ||
var next = state; | ||
reset: function reset (data, deserialize) { | ||
return this.repo.push(this.meta.reset, data, deserialize) | ||
// Important: start at 1 to avoid the meta domain | ||
for (var i = 1, len = this.domains.length; i < len; i++) { | ||
var _domains$i = this.domains[i], | ||
key = _domains$i[0], | ||
domain = _domains$i[1]; | ||
next = fn.call(scope, next, key, domain); | ||
} | ||
return next; | ||
}, | ||
invoke: function invoke(method, state, seed) { | ||
return this.reduce(function (memo, key, domain) { | ||
if (domain[method]) { | ||
return set(memo, key, domain[method](get(state, key))); | ||
} | ||
patch: function patch (data, deserialize) { | ||
return this.repo.push(this.meta.patch, data, deserialize) | ||
return memo; | ||
}, seed || {}); | ||
}, | ||
prune: function prune(state, data) { | ||
return this.reduce(function (next, key) { | ||
var value = get(data, key); | ||
rebase: function rebase (data) { | ||
return this.repo.push(this.meta.rebase, data) | ||
return value === undefined ? next : set(next, key, value); | ||
}, state); | ||
} | ||
}; | ||
function createHook (repo, effect) { | ||
function createHook(repo, effect) { | ||
return function (action) { | ||
var handler = effect.register()[action.type]; | ||
return function (_ref) { | ||
var behavior = _ref.behavior, | ||
status = _ref.status, | ||
payload = _ref.payload; | ||
var handler = getRegistration(effect.register(), behavior, status); | ||
if (handler) { | ||
handler.call(effect, repo, action.payload); | ||
handler.call(effect, repo, payload); | ||
} | ||
} | ||
}; | ||
} | ||
function createEffect (repo, config, options) { | ||
var effect = null; | ||
function createEffect(repo, config, options) { | ||
var effect = createOrClone(config, options, repo); | ||
if (typeof config === 'function') { | ||
effect = new config(repo, options); | ||
} else { | ||
effect = Object.create(config); | ||
} | ||
if (effect.setup) { | ||
@@ -1038,6 +911,4 @@ effect.setup(repo, options); | ||
} | ||
} | ||
function isPromise(obj, type) { | ||
return !!obj && (type === 'object' || type === 'function') && typeof obj.then === 'function' | ||
return effect; | ||
} | ||
@@ -1048,7 +919,4 @@ | ||
* body of their associated behavior. | ||
* @private | ||
*/ | ||
function coroutine (action, body, repo) { | ||
var type = typeof body; | ||
function coroutine(action, body, repo) { | ||
/** | ||
@@ -1063,11 +931,16 @@ * Provide support for Promises: | ||
*/ | ||
if (isPromise(body, type)) { | ||
if (isPromise(body)) { | ||
action.open(); | ||
body.then( | ||
function (result) { return global.setTimeout(function () { return action.resolve(result); }, 0); }, | ||
function (error) { return global.setTimeout(function () { return action.reject(error); }, 0); } | ||
); | ||
body.then(function (result) { | ||
return global.setTimeout(function () { | ||
return action.resolve(result); | ||
}, 0); | ||
}, function (error) { | ||
return global.setTimeout(function () { | ||
return action.reject(error); | ||
}, 0); | ||
}); | ||
return action | ||
return action; | ||
} | ||
@@ -1081,21 +954,13 @@ | ||
*/ | ||
if (type === 'function') { | ||
if (typeof body === 'function') { | ||
body(action, repo); | ||
return action | ||
return action; | ||
} | ||
// Otherwise just return a resolved action | ||
return action.resolve(body) | ||
return action.resolve(body); | ||
} | ||
/** | ||
* A tree-like data structure that keeps track of the execution order of | ||
* actions that are pushed into it, sequentially folding them together to | ||
* produce an object that can be rendered by a presentation library (such as | ||
* React). | ||
* @constructor | ||
* @extends {Emitter} | ||
*/ | ||
function Microcosm (options, state, deserialize) { | ||
function Microcosm(options, state, deserialize) { | ||
Emitter.call(this); | ||
@@ -1129,5 +994,2 @@ | ||
// Track changes with a mutable flag | ||
this.dirty = false; | ||
// Microcosm is now ready. Call the setup lifecycle method | ||
@@ -1148,10 +1010,11 @@ this.setup(options); | ||
*/ | ||
setup: function setup () { | ||
setup: function setup() { | ||
// NOOP | ||
}, | ||
/** | ||
* Remove all subscriptions | ||
*/ | ||
teardown: function teardown () { | ||
teardown: function teardown() { | ||
// Trigger a teardown event before completely shutting down | ||
@@ -1167,11 +1030,12 @@ this._emit('teardown', this); | ||
/** | ||
* Generates the starting state for a Microcosm instance by asking every | ||
* domain that subscribes to `getInitialState`. | ||
* @return {Object} State object representing the initial state. | ||
*/ | ||
getInitialState: function getInitialState () { | ||
return this.dispatch({}, lifecycle.getInitialState, null) | ||
getInitialState: function getInitialState() { | ||
return this.realm.invoke('getInitialState', {}); | ||
}, | ||
/** | ||
@@ -1183,13 +1047,14 @@ * Dispatch an action to a list of domains. This is used by state management | ||
*/ | ||
dispatch: function dispatch (state, type, payload) { | ||
var handlers = this.realm.register(type); | ||
dispatch: function dispatch(state, action) { | ||
var handlers = this.realm.register(action); | ||
var current = state; | ||
for (var i = 0, len = handlers.length; i < len; i++) { | ||
var ref = handlers[i]; | ||
var key = ref.key; | ||
var domain = ref.domain; | ||
var handler = ref.handler; | ||
var length = ref.length; | ||
var _handlers$i = handlers[i], | ||
key = _handlers$i.key, | ||
domain = _handlers$i.domain, | ||
handler = _handlers$i.handler, | ||
length = _handlers$i.length; | ||
var next = null; | ||
@@ -1206,3 +1071,3 @@ | ||
default: | ||
next = handler.call(domain, get(state, key), payload); | ||
next = handler.call(domain, get(state, key), action.payload); | ||
} | ||
@@ -1213,19 +1078,22 @@ | ||
return current | ||
return current; | ||
}, | ||
/** | ||
* Roll back to the last archive | ||
*/ | ||
unarchive: function unarchive () { | ||
unarchive: function unarchive() { | ||
this.cached = this.archived; | ||
}, | ||
/** | ||
* Roll back to the last cache | ||
*/ | ||
rollback: function rollback () { | ||
rollback: function rollback() { | ||
this.staged = this.cached; | ||
}, | ||
/** | ||
@@ -1235,3 +1103,3 @@ * Update the cache point, this is called when the history tree | ||
*/ | ||
cache: function cache (archive) { | ||
cache: function cache(archive) { | ||
this.cached = this.staged; | ||
@@ -1244,234 +1112,152 @@ | ||
/** | ||
* Identify the last and next staged state, then ask the associated domain | ||
* if it should commit it. If so, roll state forward. | ||
*/ | ||
commit: function commit (staged) { | ||
var this$1 = this; | ||
var domains = this.realm.domains; | ||
var next = staged; | ||
for (var i = 0, len = domains.length; i < len; i++) { | ||
var ref = domains[i]; | ||
var key = ref[0]; | ||
var domain = ref[1]; | ||
next = this$1.write(next, key, domain); | ||
} | ||
return next | ||
}, | ||
/** | ||
* Write state | ||
*/ | ||
write: function write (state, key, domain) { | ||
if (domain.commit != null) { | ||
var next = get(state, key); | ||
// This gives libraries such as ImmutableJS a chance to serialize | ||
// into a primitive JavaScript form before being publically exposed. | ||
if (domain.shouldCommit != null) { | ||
var last = get(this.cached, key); | ||
// Revert to the current public state if not committing | ||
if (!domain.shouldCommit(last, next)) { | ||
return set(state, key, get(this.state, key)) | ||
} | ||
} | ||
return set(state, key, domain.commit(next, state)) | ||
} | ||
return state | ||
}, | ||
/** | ||
/* | ||
* Run through the action history, dispatching their associated | ||
* types and payloads to domains for processing. Emits "change". | ||
*/ | ||
reconcile: function reconcile (action) { | ||
reconcile: function reconcile(action) { | ||
if (this.follower) { | ||
this.state = this.parent.state; | ||
this.dirty = this.parent.dirty; | ||
this.staged = this.parent.staged; | ||
return this | ||
return this; | ||
} | ||
var original = this.state; | ||
// Update children with their parent's state | ||
if (this.parent) { | ||
this.staged = merge(this.staged, this.parent.state); | ||
this.state = merge(this.state, this.parent.state); | ||
this.staged = merge(this.staged, this.parent.staged); | ||
} | ||
this.staged = this.dispatch(this.staged, action.type, action.payload); | ||
this.state = this.commit(this.staged); | ||
this.staged = this.dispatch(this.staged, action); | ||
if (this.state != original) { | ||
this.dirty = true; | ||
} | ||
return this | ||
return this; | ||
}, | ||
/** | ||
* Publish a release if anything changed | ||
* @param {Action} action | ||
*/ | ||
release: function release (action) { | ||
release: function release(action) { | ||
if (this.staged !== this.state) { | ||
this.state = this.staged; | ||
this._emit('change', this.state); | ||
} | ||
if (action) { | ||
this._emit('effect', action); | ||
} | ||
}, | ||
if (this.dirty) { | ||
this.dirty = false; | ||
this._emit('change', this.state); | ||
} | ||
}, | ||
/** | ||
* Append an action to history and return it. This is used by push, | ||
* but also useful for testing action states. | ||
* @param {Function} behavior - An action function | ||
* @return {Action} action representation of the invoked function | ||
*/ | ||
append: function append (behavior) { | ||
return this.history.append(behavior) | ||
append: function append(behavior) { | ||
return this.history.append(behavior); | ||
}, | ||
/** | ||
* Push an action into Microcosm. This will trigger the lifecycle for updating | ||
* state. | ||
* @param {Function} behavior - An action function | ||
* @param {...Any} params - Parameters to invoke the type with | ||
* @return {Action} action representaftion of the invoked function | ||
*/ | ||
push: function push (behavior) { | ||
var params = [], len = arguments.length - 1; | ||
while ( len-- > 0 ) params[ len ] = arguments[ len + 1 ]; | ||
push: function push(behavior) { | ||
var action = this.append(behavior); | ||
for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
params[_key - 1] = arguments[_key]; | ||
} | ||
coroutine(action, action.behavior.apply(null, params), this); | ||
return action | ||
return action; | ||
}, | ||
/** | ||
* Partially apply push | ||
* @param {...Any} params - Parameters to invoke the type with | ||
* @return {Function} A partially applied push function | ||
*/ | ||
prepare: function prepare () { | ||
var params = [], len = arguments.length; | ||
while ( len-- ) params[ len ] = arguments[ len ]; | ||
prepare: function prepare() { | ||
var _this = this; | ||
return (ref = this.push).bind.apply(ref, [ this ].concat( params )) | ||
var ref; | ||
for (var _len2 = arguments.length, params = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
params[_key2] = arguments[_key2]; | ||
} | ||
return function () { | ||
for (var _len3 = arguments.length, extra = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { | ||
extra[_key3] = arguments[_key3]; | ||
} | ||
return _this.push.apply(_this, params.concat(extra)); | ||
}; | ||
}, | ||
/** | ||
* Adds a domain to the Microcosm instance. A domain informs the | ||
* microcosm how to process various action types. If no key | ||
* is given, the domain will operate on all application state. | ||
* @param {String|null} key - The namespace within application state for the domain. | ||
* @param {Object|Function} domain Configuration options to create a domain | ||
* @return {Microcosm} self | ||
* Microcosm how to process various action types. If no key is | ||
* given, the domain will operate on all application state. | ||
*/ | ||
addDomain: function addDomain (key, domain, options) { | ||
addDomain: function addDomain(key, config, options) { | ||
this.follower = false; | ||
this.realm.add(key, domain, options); | ||
this.rebase(); | ||
var domain = this.realm.add(key, config, options); | ||
return this | ||
this.rebase(key); | ||
return domain; | ||
}, | ||
/** | ||
* An effect is a one-time handler that fires whenever an action changes. Callbacks | ||
* will only ever fire once, and can not modify state. | ||
* @param {Object|Function} effect - Configuration options to create an effect | ||
* @param {Object} options - Options to pass to the effect | ||
* @return {Microcosm} self | ||
*/ | ||
addEffect: function addEffect (effect, options) { | ||
createEffect(this, effect, options); | ||
return this | ||
addEffect: function addEffect(config, options) { | ||
return createEffect(this, config, options); | ||
}, | ||
/** | ||
* Push an action to reset the state of the instance. This state is folded | ||
* on to the result of `getInitialState()`. | ||
* @param {Object} data - A new state object to apply to the instance | ||
* @param {Boolean} deserialize - Should the data be deserialized? | ||
* @return {Action} action - An action representing the reset operation. | ||
*/ | ||
reset: function reset (data, deserialize) { | ||
this.follower = false; | ||
return this.realm.reset(data, deserialize) | ||
reset: function reset(data, deserialize) { | ||
return this.push(RESET, data, deserialize); | ||
}, | ||
/** | ||
* Deserialize a given state and reset the instance with that | ||
* processed state object. | ||
* @param {Object} data - A raw state object to deserialize and apply to the instance | ||
* @param {Object} deserialize - Should the data be deserialized? | ||
* @return {Action} action - An action representing the patch operation. | ||
*/ | ||
patch: function patch (data, deserialize) { | ||
this.follower = false; | ||
return this.realm.patch(data, deserialize) | ||
patch: function patch(data, deserialize) { | ||
return this.push(PATCH, data, deserialize); | ||
}, | ||
deserialize: function deserialize(payload) { | ||
var base = payload; | ||
/** | ||
* Deserialize a given payload by asking every domain how to it | ||
* should process it (via the deserialize domain function). | ||
* @param {Object} payload - A raw object to deserialize. | ||
* @return {Object} The deserialized version of the provided payload. | ||
*/ | ||
deserialize: function deserialize (payload) { | ||
var base = payload ? payload : {}; | ||
if (typeof base === 'string') { | ||
if (this.parent) { | ||
base = this.parent.deserialize(payload); | ||
} else if (typeof base === 'string') { | ||
base = JSON.parse(base); | ||
} | ||
if (this.parent) { | ||
base = this.parent.deserialize(base); | ||
} | ||
return this.realm.invoke('deserialize', base, base); | ||
}, | ||
serialize: function serialize() { | ||
var base = this.parent ? this.parent.serialize() : {}; | ||
return this.dispatch(base, lifecycle.deserialize, base) | ||
return this.realm.invoke('serialize', this.staged, base); | ||
}, | ||
/** | ||
* Serialize application state by asking every domain how to | ||
* serialize the state they manage (via the serialize domain | ||
* function). | ||
* @return {Object} The serialized version of repo state. | ||
*/ | ||
serialize: function serialize () { | ||
var base = this.staged; | ||
if (this.parent) { | ||
base = merge(base, this.parent.serialize()); | ||
} | ||
return this.dispatch(base, lifecycle.serialize, null) | ||
}, | ||
/** | ||
* Alias serialize for JS interoperability. | ||
* @return {Object} result of `serialize`. | ||
*/ | ||
toJSON: function toJSON () { | ||
return this.serialize() | ||
toJSON: function toJSON() { | ||
return this.serialize(); | ||
}, | ||
/** | ||
@@ -1483,17 +1269,15 @@ * Recalculate initial state by back-filling the cache object with | ||
*/ | ||
rebase: function rebase () { | ||
var payload = this.getInitialState(); | ||
rebase: function rebase(key) { | ||
this.archived = merge(this.getInitialState(), this.archived); | ||
this.cached = merge(this.archived, this.cached); | ||
this.cached = merge(this.cached, payload); | ||
return this.realm.rebase(payload) | ||
return this.push(ADD_DOMAIN, key); | ||
}, | ||
/** | ||
* Change the focus of the history tree. This allows for features | ||
* like undo and redo. | ||
* @param {Action} action to checkout | ||
* @return {Microcosm} self | ||
*/ | ||
checkout: function checkout (action) { | ||
checkout: function checkout(action) { | ||
this.cached = this.archived; | ||
@@ -1503,108 +1287,16 @@ | ||
return this | ||
return this; | ||
}, | ||
/** | ||
* Create a copy of this Microcosm, passing in the same history and | ||
* a reference to itself. As actions are pushed into the shared history, | ||
* each Microcosm will resolve differently. | ||
* a reference to itself. As actions are pushed into the shared | ||
* history, each Microcosm will resolve differently. | ||
*/ | ||
fork: function fork () { | ||
fork: function fork() { | ||
return new Microcosm({ | ||
parent : this | ||
}) | ||
}, | ||
/** | ||
* Memoize a computation of a fragment of application state. | ||
* This may be referenced when computing properties or querying | ||
* state within Presenters. | ||
*/ | ||
index: function index (name, fragment) { | ||
var this$1 = this; | ||
var processors = [], len = arguments.length - 2; | ||
while ( len-- > 0 ) processors[ len ] = arguments[ len + 2 ]; | ||
var keyPaths = compileKeyPaths(fragment); | ||
var state = null; | ||
var subset = null; | ||
var answer = null; | ||
var query = this.indexes[name] = function () { | ||
var extra = [], len = arguments.length; | ||
while ( len-- ) extra[ len ] = arguments[ len ]; | ||
if (this$1.state !== state) { | ||
state = this$1.state; | ||
var next = extract(state, keyPaths, subset); | ||
if (next !== subset) { | ||
subset = next; | ||
answer = processors.reduce(function (value, fn) { | ||
return fn.call(this$1, value, state) | ||
}, subset); | ||
} | ||
} | ||
return extra.reduce(function (value, fn) { | ||
return fn.call(this$1, value, state) | ||
}, answer) | ||
}; | ||
return query | ||
}, | ||
lookup: function lookup (name) { | ||
var index = this.indexes[name]; | ||
if (index == null) { | ||
if (this.parent) { | ||
return this.parent.lookup(name) | ||
} else { | ||
throw new TypeError('Unable to find missing index ' + name) | ||
} | ||
} | ||
return index | ||
}, | ||
/** | ||
* Invoke an index, optionally adding additional processing. | ||
*/ | ||
compute: function compute (name) { | ||
var processors = [], len = arguments.length - 1; | ||
while ( len-- > 0 ) processors[ len ] = arguments[ len + 1 ]; | ||
return this.lookup(name).apply(void 0, processors) | ||
}, | ||
/** | ||
* Return a memoized compute function. This is useful for repeated | ||
* invocations of a computation as state changes. Useful for use inside | ||
* of Presenters. | ||
*/ | ||
memo: function memo (name) { | ||
var processors = [], len = arguments.length - 1; | ||
while ( len-- > 0 ) processors[ len ] = arguments[ len + 1 ]; | ||
var index = this.lookup(name); | ||
var last = null; | ||
var answer = null; | ||
return function () { | ||
var next = index(); | ||
if (next !== last) { | ||
last = next; | ||
answer = index.apply(void 0, processors); | ||
} | ||
return answer | ||
} | ||
parent: this | ||
}); | ||
} | ||
}); | ||
@@ -1621,1 +1313,2 @@ | ||
exports.inherit = inherit; | ||
exports.getRegistration = getRegistration; |
@@ -1,1 +0,1 @@ | ||
"use strict";function t(){this._events=null}function e(t,e){if(null==t)throw new Error("Unable to identify "+t+" action");if(t.done)return t;"string"==typeof t&&(e=t,t=function(t){return t}),x+=1;var i=e||(t.name||z)+"."+x;return t.open=i+".open",t.loading=i+".loading",t.update=t.loading,t.done=i,t.resolve=t.done,t.error=i+".error",t.reject=t.error,t.cancelled=i+".cancelled",t.cancel=t.cancelled,t.toString=k,t}function i(t){void 0===t&&(t=0),this.repos=[],this.limit=t}function n(t){if(Array.isArray(t))return t.slice(0);var e={};for(var i in t)e[i]=t[i];return e}function r(t){for(var e=arguments,i=t,r=1,s=arguments.length;r<s;r++){var o=e[r];for(var h in o)i[h]!==o[h]&&(i===t&&(i=n(t)),i[h]=o[h])}return i}function s(t,e,i){return t.__proto__=e,t.prototype=r(Object.create(e.prototype),{constructor:t},i),t}function o(t,e,i){return null==t?i:null==e?t:Array.isArray(e)?h(t,e,i):j.call(t,e)?t[e]:i}function h(t,e,i){for(var n=t,r=0,s=e.length;r<s;r++)n=o(n,e[r],i);return n}function a(t,e,i){if(Array.isArray(e))return u(t,e,i);if(null==e)return i;if(void 0===i||o(t,e)===i)return t;var r=n(t);return r[e]=i,r}function u(t,e,i){if(h(t,e)===i)return t;var r=e[0],s=e.slice(1),o=n(t);return s.length?o[r]=r in o?u(o[r],s,i):u({},s,i):o[r]=i,o}function l(t){return t.split(/\./)}function c(t){var e=t.split(/\s*\,\s*/);return e.map(l)}function f(t,e,i){return e.reduce(function(e,i){return a(e,i,o(t,i))},i||{})}function d(n,r){t.call(this),this.behavior=e(n),this.history=r||new i}function p(){this.reset=function(t,e){return function(i,n){var s=n.getInitialState(),o=t;e&&(o=n.deserialize(t)),i.resolve(r(s,o))}},this.patch=function(t,e){return function(i,n){var r=t;e&&(r=n.deserialize(r)),i.resolve(r)}},this.rebase=function(t){return t}}function v(t,e){for(var i=[],n=null!=S[e],r=0,s=t.length;r<s;r++){var o=t[r],h=o[0],a=o[1],u=null;n&&null!=a[e]?u=a[e]:null!=a.register&&(u=a.register()[e]),u&&i.push({key:h,domain:a,handler:u,length:u.length})}return i}function y(t){this.repo=t,this.domains=[],this.registry={},this.meta=this.add(null,p)}function g(t,e){return function(i){var n=e.register()[i.type];n&&n.call(e,t,i.payload)}}function m(t,e,i){var n=null;n="function"==typeof e?new e(t,i):Object.create(e),n.setup&&n.setup(t,i),n.register&&t.on("effect",g(t,n)),n.teardown&&t.on("teardown",n.teardown,n)}function b(t,e){return!!t&&("object"===e||"function"===e)&&"function"==typeof t.then}function w(t,e,i){var n=typeof e;return b(e,n)?(t.open(),e.then(function(e){return global.setTimeout(function(){return t.resolve(e)},0)},function(e){return global.setTimeout(function(){return t.reject(e)},0)}),t):"function"===n?(e(t,i),t):t.resolve(e)}function _(e,n,r){t.call(this),e=e||{},this.parent=e.parent||null,this.history=this.parent?this.parent.history:new i(e.maxHistory),this.history.addRepo(this),this.realm=new y(this),this.archived=this.parent?this.parent.archived:{},this.cached=this.parent?this.parent.cached:this.archived,this.staged=this.parent?this.parent.state:this.cached,this.state=this.parent?this.parent.state:this.staged,this.follower=!!this.parent,this.indexes={},this.dirty=!1,this.setup(e),n&&this.reset(n,r)}Object.defineProperty(exports,"__esModule",{value:!0}),t.prototype={on:function(t,e,i,n){return null==this._events&&(this._events=[]),this._events.push({event:t,fn:e,scope:i,once:n}),this},once:function(t,e,i){return this.on(t,e,i,!0)},off:function(t,e,i){var n=this;if(null==this._events)return this;for(var r=null==e,s=0;s<this._events.length;){var o=n._events[s];o.event===t&&(r||o.fn===e&&o.scope===i)?n._events.splice(s,1):s+=1}return this},removeAllListeners:function(){this._events=null},_emit:function(t,e){var i=this;if(null==this._events)return this;for(var n=0;n<this._events.length;){var r=i._events[n];r.event===t&&(r.fn.call(r.scope||i,e),r.once)?i._events.splice(n,1):n+=1}return this}};var x=0,z="_action",k=function(){return this.done};i.prototype={root:null,focus:null,head:null,size:0,limit:0,addRepo:function(t){this.repos.push(t)},removeRepo:function(t){this.repos=this.repos.filter(function(e){return e!=t})},invoke:function(t,e){for(var i=this.repos,n=0,r=i.length;n<r;n++)i[n][t](e)},checkout:function(t){return this.head=t,t?t.parent&&(t.parent.next=t):this.root=this.head=null,this.adjustSize(),this.invalidate(),this},append:function(t){var e=new d(t,this);return this.head?(e.parent=this.head,this.head.first?this.head.next.sibling=e:this.head.first=e,this.head.next=e):this.root=e,this.head=e,this.size+=1,this.head},invalidate:function(){this.focus=null,this.invoke("unarchive",null),this.reconcile()},reconcile:function(t){return!(this.repos.length<=0)&&(this.invoke("rollback",null),this.rollforward(),void this.invoke("release",t))},rollforward:function(){for(var t=this,e=this.focus?this.focus.next:this.root,i=!0;e&&e.parent!==this.head;)e.disabled||t.invoke("reconcile",e),i&&e.disposable?(t.focus=e,t.invoke("cache",t.archive())):i=!1,e=e.next},archive:function(){var t=this.size>this.limit;return t&&(this.size-=1,this.size<=0?this.root=this.head=this.focus=null:(this.root=this.root.next,this.root.parent=null)),t},adjustSize:function(){for(var t=this.head,e=this.root?1:0;t&&t.parent;){var i=t.parent;i.next=t,t=i,e+=1}this.size=e},forEach:function(t,e){for(var i=this,n=this.focus||this.root;n&&(t.call(e,n),n!==i.head);)n=n.next},map:function(t,e){var i=[];return this.forEach(function(n){i.push(t.call(e,n))}),i},toArray:function(){return this.map(function(t){return t})}};var j=Object.prototype.hasOwnProperty;s(d,t,{type:null,payload:void 0,disabled:!1,disposable:!1,parent:null,first:null,next:null,sibling:null,is:function(t){return this.type===this.behavior[t]},open:function(t){return this.disposable||(this.type=this.behavior.open,arguments.length>0&&(this.payload=t),this.history.reconcile(this),this._emit("open",this.payload)),this},update:function(t){return this.disposable||(this.type=this.behavior.loading,arguments.length>0&&(this.payload=t),this.history.reconcile(this),this._emit("update",this.payload)),this},send:function(){return"undefined"!=typeof console&&console.warn("`send` was deprecated in 11.6.0.","Please use `update` instead.","`send` will be removed in 12.0.0."),this.update.apply(this,arguments)},reject:function(t){return this.disposable||(this.type=this.behavior.error,this.disposable=!0,arguments.length>0&&(this.payload=t),this.history.reconcile(this),this._emit("error",this.payload)),this},resolve:function(t){return this.disposable||(this.type=this.behavior.done,this.disposable=!0,arguments.length>0&&(this.payload=t),this.history.reconcile(this),this._emit("done",this.payload)),this},cancel:function(t){return this.disposable||(this.type=this.behavior.cancelled,this.disposable=!0,arguments.length>0&&(this.payload=t),this.history.reconcile(this),this._emit("cancel",this.payload)),this},toggle:function(){return this.disabled=!this.disabled,this.history.invalidate(),this},onError:function(t,e){return t?(this.is("error")?t.call(e,this.payload):this.once("error",t,e),this):this},onUpdate:function(t,e){return t?(this.on("update",t,e),this):this},onDone:function(t,e){return t?(this.is("done")?t.call(e,this.payload):this.once("done",t,e),this):this},onCancel:function(t,e){return t?(this.is("cancelled")?t.call(e,this.payload):this.once("cancel",t,e),this):this},then:function(t,e){var i=this;return new Promise(function(t,e){i.onDone(t),i.onError(e)}).then(t,e)}}),Object.defineProperty(d.prototype,"children",{get:function(){for(var t=[],e=this.first;e;)t.unshift(e),e=e.sibling;return t}}),p.prototype={handleReset:function(t,e){return e},handlePatch:function(t,e){return r(t,e)},handleRebase:function(t,e){return r(e,t)},register:function(){var t={};return t[this.reset]=this.handleReset,t[this.patch]=this.handlePatch,t[this.rebase]=this.handleRebase,t}};var S={getInitialState:"getInitialState",serialize:"serialize",deserialize:"deserialize"};y.prototype={register:function(t){return null==this.registry[t]&&(this.registry[t]=v(this.domains,t)),this.registry[t]},add:function(t,e,i){var n=null;return n="function"==typeof e?new e(i):Object.create(e),this.domains.push([t,n]),this.registry={},n.setup&&n.setup(this.repo,i),n.teardown&&this.repo.on("teardown",n.teardown,n),n},reset:function(t,e){return this.repo.push(this.meta.reset,t,e)},patch:function(t,e){return this.repo.push(this.meta.patch,t,e)},rebase:function(t){return this.repo.push(this.meta.rebase,t)}},s(_,t,{setup:function(){},teardown:function(){this._emit("teardown",this),this.history.removeRepo(this),this.removeAllListeners()},getInitialState:function(){return this.dispatch({},S.getInitialState,null)},dispatch:function(t,e,i){for(var n=this.realm.register(e),r=t,s=0,h=n.length;s<h;s++){var u=n[s],l=u.key,c=u.domain,f=u.handler,d=u.length,p=null;switch(d){case 0:p=f.call(c);break;case 1:p=f.call(c,o(t,l));break;case 2:default:p=f.call(c,o(t,l),i)}r=a(r,l,p)}return r},unarchive:function(){this.cached=this.archived},rollback:function(){this.staged=this.cached},cache:function(t){this.cached=this.staged,t&&(this.archived=this.cached)},commit:function(t){for(var e=this,i=this.realm.domains,n=t,r=0,s=i.length;r<s;r++){var o=i[r],h=o[0],a=o[1];n=e.write(n,h,a)}return n},write:function(t,e,i){if(null!=i.commit){var n=o(t,e);if(null!=i.shouldCommit){var r=o(this.cached,e);if(!i.shouldCommit(r,n))return a(t,e,o(this.state,e))}return a(t,e,i.commit(n,t))}return t},reconcile:function(t){if(this.follower)return this.state=this.parent.state,this.dirty=this.parent.dirty,this;var e=this.state;return this.parent&&(this.staged=r(this.staged,this.parent.state),this.state=r(this.state,this.parent.state)),this.staged=this.dispatch(this.staged,t.type,t.payload),this.state=this.commit(this.staged),this.state!=e&&(this.dirty=!0),this},release:function(t){t&&this._emit("effect",t),this.dirty&&(this.dirty=!1,this._emit("change",this.state))},append:function(t){return this.history.append(t)},push:function(t){for(var e=[],i=arguments.length-1;i-- >0;)e[i]=arguments[i+1];var n=this.append(t);return w(n,n.behavior.apply(null,e),this),n},prepare:function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];return(i=this.push).bind.apply(i,[this].concat(t));var i},addDomain:function(t,e,i){return this.follower=!1,this.realm.add(t,e,i),this.rebase(),this},addEffect:function(t,e){return m(this,t,e),this},reset:function(t,e){return this.follower=!1,this.realm.reset(t,e)},patch:function(t,e){return this.follower=!1,this.realm.patch(t,e)},deserialize:function(t){var e=t?t:{};return"string"==typeof e&&(e=JSON.parse(e)),this.parent&&(e=this.parent.deserialize(e)),this.dispatch(e,S.deserialize,e)},serialize:function(){var t=this.staged;return this.parent&&(t=r(t,this.parent.serialize())),this.dispatch(t,S.serialize,null)},toJSON:function(){return this.serialize()},rebase:function(){var t=this.getInitialState();return this.cached=r(this.cached,t),this.realm.rebase(t)},checkout:function(t){return this.cached=this.archived,this.history.checkout(t),this},fork:function(){return new _({parent:this})},index:function(t,e){for(var i=this,n=[],r=arguments.length-2;r-- >0;)n[r]=arguments[r+2];var s=c(e),o=null,h=null,a=null,u=this.indexes[t]=function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];if(i.state!==o){o=i.state;var r=f(o,s,h);r!==h&&(h=r,a=n.reduce(function(t,e){return e.call(i,t,o)},h))}return t.reduce(function(t,e){return e.call(i,t,o)},a)};return u},lookup:function(t){var e=this.indexes[t];if(null==e){if(this.parent)return this.parent.lookup(t);throw new TypeError("Unable to find missing index "+t)}return e},compute:function(t){for(var e=[],i=arguments.length-1;i-- >0;)e[i]=arguments[i+1];return this.lookup(t).apply(void 0,e)},memo:function(t){for(var e=[],i=arguments.length-1;i-- >0;)e[i]=arguments[i+1];var n=this.lookup(t),r=null,s=null;return function(){var t=n();return t!==r&&(r=t,s=n.apply(void 0,e)),s}}}),exports.default=_,exports.Microcosm=_,exports.Action=d,exports.History=i,exports.tag=e,exports.get=o,exports.set=a,exports.merge=r,exports.inherit=s; | ||
"use strict";function t(){this._events=null}function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;this.repos=[],this.limit=t}function n(t,e){if(null==t)throw new Error("Unable to identify "+t+" action");if(t.done)return t;"string"==typeof t&&(e=t,t=function(t){return t}),_+=1;var n=e||(t.name||x)+"."+_;return t.open=n+".open",t.loading=n+".loading",t.update=t.loading,t.done=n,t.resolve=t.done,t.error=n+".error",t.reject=t.error,t.cancel=n+".cancel",t.cancelled=t.cancel,t.toString=z,t}function i(t){return Array.isArray(t)?t:null==t?E:"string"==typeof t?t.split("."):[t]}function r(t){if(Array.isArray(t))return t.slice(0);if(c(t)===!1)return t;var e={};for(var n in t)e[n]=t[n];return e}function s(){for(var t=null,e=null,n=0,i=arguments.length;n<i;n++){t=t||arguments[n],e=e||t;var s=arguments[n];for(var o in s)t[o]!==s[o]&&(t===e&&(t=r(e)),t[o]=s[o])}return t}function o(t,e,n){return t.__proto__=e,t.prototype=s(Object.create(e.prototype),{constructor:t},n),t}function a(t,e,n){if(null==t)return n;e=i(e);for(var r=0,s=e.length;r<s;r++){var o=null==t?void 0:t[e[r]];void 0===o&&(r=s,o=n),t=o}return t}function h(t,e,n){e=i(e);var s=e.length;if(s<=0)return n;if(a(t,e)===n)return t;for(var o=r(t),h=o,u=0;u<s;u++){var c=e[u],l=n;u<s-1&&(l=c in h?r(h[c]):{}),h[c]=l,h=h[c]}return o}function u(t){var e=void 0===t?"undefined":A(t);return!!t&&("object"===e||"function"===e)&&"function"==typeof t.then}function c(t){return!!t&&"object"===(void 0===t?"undefined":A(t))}function l(t,e,n){return"function"==typeof t?new t(e,n):Object.create(t)}function f(i,r){t.call(this),this.behavior=n(i),this.history=r||new e}function p(t,e){return function(n,i){var r=t;if(e)try{r=i.deserialize(t)}catch(t){n.reject(t)}n.resolve(r)}}function d(t,e){this.repo=e}function v(t,e,n){var i=n?S[n]:D;if(null==i)throw new ReferenceError("Invalid action status "+n);return t&&e?c(t[e])?t[e][i]||t[e][n]:t[e[i]]:null}function y(t,e){for(var n=e.behavior,i=e.status,r=[],s=0,o=t.length;s<o;s++){var a=t[s],h=a[0],u=a[1];if(u.register){var c=v(u.register(),n,i);c&&r.push({key:h,domain:u,handler:c,length:c.length})}}return r}function g(t){this.repo=t,this.domains=[],this.registry={},this.meta=this.add(null,d)}function b(t,e){return function(n){var i=n.behavior,r=n.status,s=n.payload,o=v(e.register(),i,r);o&&o.call(e,t,s)}}function m(t,e,n){var i=l(e,n,t);return i.setup&&i.setup(t,n),i.register&&t.on("effect",b(t,i)),i.teardown&&t.on("teardown",i.teardown,i),i}function k(t,e,n){return u(e)?(t.open(),e.then(function(e){return global.setTimeout(function(){return t.resolve(e)},0)},function(e){return global.setTimeout(function(){return t.reject(e)},0)}),t):"function"==typeof e?(e(t,n),t):t.resolve(e)}function w(n,i,r){t.call(this),n=n||{},this.parent=n.parent||null,this.history=this.parent?this.parent.history:new e(n.maxHistory),this.history.addRepo(this),this.realm=new g(this),this.archived=this.parent?this.parent.archived:{},this.cached=this.parent?this.parent.cached:this.archived,this.staged=this.parent?this.parent.state:this.cached,this.state=this.parent?this.parent.state:this.staged,this.follower=!!this.parent,this.indexes={},this.setup(n),i&&this.reset(i,r)}Object.defineProperty(exports,"__esModule",{value:!0}),t.prototype={on:function(t,e,n,i){return null==this._events&&(this._events=[]),this._events.push({event:t,fn:e,scope:n,once:i}),this},once:function(t,e,n){return this.on(t,e,n,!0)},off:function(t,e,n){if(null==this._events)return this;for(var i=null==e,r=0;r<this._events.length;){var s=this._events[r];s.event===t&&(i||s.fn===e&&s.scope===n)?this._events.splice(r,1):r+=1}return this},removeAllListeners:function(){this._events=null},_emit:function(t,e){if(null==this._events)return this;for(var n=0;n<this._events.length;){var i=this._events[n];i.event===t&&(i.fn.call(i.scope||this,e),i.once)?this._events.splice(n,1):n+=1}return this}},e.prototype={root:null,focus:null,head:null,size:0,limit:0,addRepo:function(t){this.repos.push(t)},removeRepo:function(t){this.repos=this.repos.filter(function(e){return e!=t})},invoke:function(t,e){for(var n=this.repos,i=0,r=n.length;i<r;i++)n[i][t](e)},checkout:function(t){return this.head=t,t?t.parent&&(t.parent.next=t):this.root=this.head=null,this.adjustSize(),this.invalidate(),this},append:function(t){var e=new f(t,this);return this.head?(e.parent=this.head,this.head.first?this.head.next.sibling=e:this.head.first=e,this.head.next=e):this.root=e,this.head=e,this.size+=1,this.head},invalidate:function(){this.focus=null,this.invoke("unarchive",null),this.reconcile()},reconcile:function(t){if(this.repos.length<=0)return!1;this.invoke("rollback",null),this.rollforward(),this.invoke("release",t)},rollforward:function(){for(var t=this.focus?this.focus.next:this.root,e=!0;t&&t.parent!==this.head;)t.disabled||this.invoke("reconcile",t),e&&t.disposable?(this.focus=t,this.invoke("cache",this.archive())):e=!1,t=t.next},archive:function(){var t=this.size>this.limit;return t&&(this.size-=1,this.size<=0?this.root=this.head=this.focus=null:(this.root=this.root.next,this.root.parent=null)),t},adjustSize:function(){for(var t=this.head,e=this.root?1:0;t&&t.parent;){var n=t.parent;n.next=t,t=n,e+=1}this.size=e},forEach:function(t,e){for(var n=this.focus||this.root;n&&(t.call(e,n),n!==this.head);)n=n.next},map:function(t,e){var n=[];return this.forEach(function(i){n.push(t.call(e,i))}),n},toArray:function(){return this.map(function(t){return t})}};var _=0,x="_action",z=function(){return this.done},j=[{key:"open",disposable:!1,once:!0,listener:"onOpen"},{key:"update",disposable:!1,once:!1,listener:"onUpdate"},{key:"resolve",disposable:!0,once:!0,listener:"onDone"},{key:"reject",disposable:!0,once:!0,listener:"onError"},{key:"cancel",disposable:!0,once:!0,listener:"onCancel"}],S={inactive:"inactive",open:"open",update:"loading",loading:"update",done:"resolve",resolve:"done",reject:"error",error:"reject",cancel:"cancelled",cancelled:"cancel"},A="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},E=[];o(f,t,{type:null,status:"inactive",payload:void 0,disabled:!1,disposable:!1,parent:null,first:null,next:null,sibling:null,is:function(t){return this.behavior[this.status]===this.behavior[t]},toggle:function(){return this.disabled=!this.disabled,this.history.invalidate(),this},then:function(t,e){var n=this;return new Promise(function(t,e){n.onDone(t),n.onError(e)}).then(t,e)}}),j.forEach(function(t){var e=t.key,n=t.disposable,i=t.once,r=t.listener;f.prototype[e]=function(t){return this.disposable||(this.status=e,this.disposable=n,arguments.length&&(this.payload=t),this.history.reconcile(this),this._emit(e,this.payload)),this},f.prototype[r]=function(t,n){return t&&(i&&this.is(e)?t.call(n,this.payload):this.once(e,t,n)),this}}),Object.defineProperty(f.prototype,"children",{get:function(){for(var t=[],e=this.first;e;)t.unshift(e),e=e.sibling;return t}});var O=n(function(t,e){return p(t,e)},"reset"),R=n(function(t,e){return p(t,e)},"patch"),I="addDomain";d.prototype={reset:function(t,e){return this.patch(this.repo.getInitialState(),e)},patch:function(t,e){return this.repo.realm.prune(t,e)},register:function(){var t;return t={},t[O]=this.reset,t[R]=this.patch,t}};var D="done";g.prototype={register:function(t){var e=t.behavior[t.status];return null==this.registry[e]&&(this.registry[e]=y(this.domains,t)),this.registry[e]},add:function(t,e,n){var i=l(e,n,this.repo);return this.domains.push([t,i]),this.registry={},i.setup&&i.setup(this.repo,n),i.teardown&&this.repo.on("teardown",i.teardown,i),i},reduce:function(t,e,n){for(var i=e,r=1,s=this.domains.length;r<s;r++){var o=this.domains[r],a=o[0],h=o[1];i=t.call(n,i,a,h)}return i},invoke:function(t,e,n){return this.reduce(function(n,i,r){return r[t]?h(n,i,r[t](a(e,i))):n},n||{})},prune:function(t,e){return this.reduce(function(t,n){var i=a(e,n);return void 0===i?t:h(t,n,i)},t)}},o(w,t,{setup:function(){},teardown:function(){this._emit("teardown",this),this.history.removeRepo(this),this.removeAllListeners()},getInitialState:function(){return this.realm.invoke("getInitialState",{})},dispatch:function(t,e){for(var n=this.realm.register(e),i=t,r=0,s=n.length;r<s;r++){var o=n[r],u=o.key,c=o.domain,l=o.handler,f=o.length,p=null;switch(f){case 0:p=l.call(c);break;case 1:p=l.call(c,a(t,u));break;case 2:default:p=l.call(c,a(t,u),e.payload)}i=h(i,u,p)}return i},unarchive:function(){this.cached=this.archived},rollback:function(){this.staged=this.cached},cache:function(t){this.cached=this.staged,t&&(this.archived=this.cached)},reconcile:function(t){return this.follower?(this.staged=this.parent.staged,this):(this.parent&&(this.staged=s(this.staged,this.parent.staged)),this.staged=this.dispatch(this.staged,t),this)},release:function(t){this.staged!==this.state&&(this.state=this.staged,this._emit("change",this.state)),t&&this._emit("effect",t)},append:function(t){return this.history.append(t)},push:function(t){for(var e=this.append(t),n=arguments.length,i=Array(n>1?n-1:0),r=1;r<n;r++)i[r-1]=arguments[r];return k(e,e.behavior.apply(null,i),this),e},prepare:function(){for(var t=this,e=arguments.length,n=Array(e),i=0;i<e;i++)n[i]=arguments[i];return function(){for(var e=arguments.length,i=Array(e),r=0;r<e;r++)i[r]=arguments[r];return t.push.apply(t,n.concat(i))}},addDomain:function(t,e,n){this.follower=!1;var i=this.realm.add(t,e,n);return this.rebase(t),i},addEffect:function(t,e){return m(this,t,e)},reset:function(t,e){return this.push(O,t,e)},patch:function(t,e){return this.push(R,t,e)},deserialize:function(t){var e=t;return this.parent?e=this.parent.deserialize(t):"string"==typeof e&&(e=JSON.parse(e)),this.realm.invoke("deserialize",e,e)},serialize:function(){var t=this.parent?this.parent.serialize():{};return this.realm.invoke("serialize",this.staged,t)},toJSON:function(){return this.serialize()},rebase:function(t){return this.archived=s(this.getInitialState(),this.archived),this.cached=s(this.archived,this.cached),this.push(I,t)},checkout:function(t){return this.cached=this.archived,this.history.checkout(t),this},fork:function(){return new w({parent:this})}}),exports.default=w,exports.Microcosm=w,exports.Action=f,exports.History=e,exports.tag=n,exports.get=a,exports.set=h,exports.merge=s,exports.inherit=o,exports.getRegistration=v; |
{ | ||
"name": "microcosm", | ||
"version": "11.6.0", | ||
"version": "12.0.0-alpha.0", | ||
"description": "Flux with actions at center stage. Write optimistic updates, cancel requests, and track changes with ease.", | ||
@@ -5,0 +5,0 @@ "main": "microcosm.js", |
@@ -13,2 +13,4 @@ # [![Microcosm](http://code.viget.com/microcosm/assets/microcosm.svg)](http://code.viget.com/microcosm/) | ||
```javascript | ||
import Microcosm, { get, set } from 'microcosm' | ||
let repo = new Microcosm() | ||
@@ -28,3 +30,4 @@ | ||
addUser (users, record) { | ||
return { ...users, [record.id]: record } | ||
// The set helper non-destructively assigns keys to an object | ||
return set(users, record.id, record) | ||
}, | ||
@@ -41,2 +44,8 @@ register () { | ||
action.onDone(function () { | ||
let user = get(repo.state, ['users', '2']) | ||
console.log(user) // { id: 2, name: "Bob" } | ||
}) | ||
// You could also handle errors in a domain's register method | ||
@@ -235,3 +244,3 @@ // by hooking into `getUser.error` | ||
```javascript | ||
import {send} from 'actions/chat' | ||
import { send } from 'actions/chat' | ||
@@ -362,3 +371,3 @@ const Messages = { | ||
model () { | ||
getModel () { | ||
return { | ||
@@ -369,3 +378,5 @@ page: state => state.users | ||
view ({ page }) { | ||
render () { | ||
const { page } = this.model | ||
return <UsersTable users={page} /> | ||
@@ -372,0 +383,0 @@ } |
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
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
36
406
198583
1415
6
1