minska
Version:
A simple flux like store with reducers and effects
251 lines (197 loc) • 8.05 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
// Formats messages to be prefixed with `minska: `
// str => str
var formatMsg = function formatMsg(msg) {
return 'minska: ' + msg;
};
// Test if all keys in a object are functions
// obj => bool
var validateObjHasOnlyFunctions = function validateObjHasOnlyFunctions(obj) {
return Object.keys(obj).map(function (item) {
return typeof obj[item] === 'function';
}).every(function (i) {
return i === true;
});
};
var u = {
formatMsg: formatMsg,
validateObjHasOnlyFunctions: validateObjHasOnlyFunctions
};
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
// Private properties/methods
var State = Symbol('state');
var Emit = Symbol('emit');
var Store = function () {
function Store() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref$state = _ref.state,
state = _ref$state === undefined ? {} : _ref$state,
_ref$reducers = _ref.reducers,
reducers = _ref$reducers === undefined ? {} : _ref$reducers,
_ref$effects = _ref.effects,
effects = _ref$effects === undefined ? {} : _ref$effects,
_ref$onError = _ref.onError,
onError = _ref$onError === undefined ? function () {} : _ref$onError,
_ref$onAction = _ref.onAction,
onAction = _ref$onAction === undefined ? function () {} : _ref$onAction,
_ref$onChange = _ref.onChange,
onChange = _ref$onChange === undefined ? function () {} : _ref$onChange;
_classCallCheck(this, Store);
// Test all the namespaced reducers are functions
if (!u.validateObjHasOnlyFunctions(reducers)) {
throw new Error(u.formatMsg('All reducers should be functions.'));
}
// Test all the namespaced effects are functions
if (!u.validateObjHasOnlyFunctions(effects)) {
throw new Error(u.formatMsg('All effects should be functions.'));
}
// Store model
this[State] = state;
this.reducers = reducers;
this.effects = effects;
// List of subscriptions
this.subscriptions = [];
// Events you can subscribe too
this.events = ['onError', 'onAction', 'onChange'];
// (error, state)
this.onError = onError;
// (action, data, state)
this.onAction = onAction;
// (nextState, state)
this.onChange = onChange;
}
// Return the current store state
// => obj
_createClass(Store, [{
key: 'send',
// Update the state by using an effect/reducer that matches an action name
// (str, any|!func) => promise|obj
value: function send(action, data) {
if (typeof action !== 'string') {
var error = new Error(u.formatMsg('Action name must be a string.'));
this[Emit]('onError', error, this.state);
throw error;
}
if (typeof data === 'function') {
var _error = new Error(u.formatMsg('Data must be a serializable value. A function was passed.'));
this[Emit]('onError', _error, this.state);
throw _error;
}
// Emit the `onAction` hook
this[Emit]('onAction', action, data, this.state);
// Get the namespace from the action if there is one
var ns = action.includes(':') ? action.split(':')[0] : null;
// Get the slice of state that matches the namespace
var stateSlice = this.state;
if (ns && stateSlice[ns]) {
stateSlice = stateSlice[ns];
} else if (ns) {
stateSlice = {};
}
var effect = this.effects[action];
var reducer = this.reducers[action];
// If no effect or reducer can be found, then throw an error,
// and also notify the `onError` hook
if (!effect && !reducer) {
var _error2 = new Error(u.formatMsg('Can\'t find reducer or effect with name: ' + action + '.'));
this[Emit]('onError', _error2, this.state);
throw _error2;
}
// Call the matching effect. It should return a promise so they can do async things.
if (effect) {
return Promise.resolve(effect(this.state, data, this.send.bind(this)));
}
// Get the result of calling the reducer with the state slice
var reduced = Object.assign({}, reducer(stateSlice, data));
// If a namespace is present, then we should add the slice back to the global state
var nextState = ns ? Object.assign({}, this.state, _defineProperty({}, ns, reduced)) : reduced;
// Emit the `onChange` hook
this[Emit]('onChange', nextState, this.state);
// Actually change the state
this.state = nextState;
// Return the next state so send can use it for something
return nextState;
}
// Add subscriptions for events
// (str, str|num, fn) => null
}, {
key: 'subscribe',
value: function subscribe(event, id, fn) {
if (!this.events.includes(event)) {
var error = new Error(u.formatMsg(event + ' is not a valid event you can subscribe to.'));
this[Emit]('onError', error, this.state);
throw error;
}
this.subscriptions.push({ event: event, id: id, fn: fn });
}
// Remove subscriptions
// str|num => null
}, {
key: 'unsubscribe',
value: function unsubscribe(id) {
if (!this.subscriptions.find(function (s) {
return s.id === id;
})) {
var error = new Error(u.formatMsg('Can\'t find subscriber with id "' + id + '".'));
this[Emit]('onError', error, this.state);
throw error;
}
this.subscriptions = this.subscriptions.filter(function (s) {
return s.id !== id;
});
}
// Notify subscriptions of any events they listen to
// (str, any|!func) => null
}, {
key: Emit,
value: function value(event) {
for (var _len = arguments.length, data = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
data[_key - 1] = arguments[_key];
}
var hook = this[event];
if (hook) hook.apply(undefined, data);
this.subscriptions.filter(function (s) {
return s.event === event;
}).forEach(function (sub) {
sub.fn.apply(sub, data);
});
}
}, {
key: 'state',
get: function get() {
return this[State];
}
// Set the new store state
// any => null
,
set: function set(nextState) {
this.onChange(this.state, nextState);
this[State] = nextState;
}
}]);
return Store;
}();
// Combines reducers/effects with namespaces to a flattened object
// arr => obj
var combine = function combine() {
for (var _len = arguments.length, list = Array(_len), _key = 0; _key < _len; _key++) {
list[_key] = arguments[_key];
}
return list.reduce(function (memo, curr) {
var namespace = curr.namespace;
var nsKey = '' + (namespace ? namespace + ':' : '');
Object.keys(curr).forEach(function (item) {
if (item !== 'namespace') {
memo['' + nsKey + item] = curr[item]; // eslint-disable-line no-param-reassign
}
});
return memo;
}, {});
};
exports.Store = Store;
exports.combine = combine;
exports.utils = u;
//# sourceMappingURL=index.browser.js.map