UNPKG

minska

Version:

A simple flux like store with reducers and effects

251 lines (197 loc) 8.05 kB
'use strict'; 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