brownturtle
Advanced tools
Comparing version 3.0.1 to 3.2.0
602
lib/index.js
"use strict"; | ||
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
@@ -10,571 +8,51 @@ | ||
}); | ||
exports["default"] = exports.connect = exports.Provider = exports.useMiddleware = exports.useReducer = void 0; | ||
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); | ||
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); | ||
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); | ||
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); | ||
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); | ||
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); | ||
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); | ||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
var _react = _interopRequireWildcard(require("react")); | ||
var _redux = require("redux"); | ||
var _connect = _interopRequireDefault(require("react-redux/es/connect/connect")); | ||
var _Provider = _interopRequireDefault(require("react-redux/es/components/Provider")); | ||
var _reduxSaga = _interopRequireDefault(require("redux-saga")); | ||
var _effects = require("redux-saga/effects"); | ||
var helpers = _interopRequireWildcard(require("./helpers")); | ||
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return (0, _typeof2["default"])(key) === "symbol" ? key : String(key); } | ||
function _toPrimitive(input, hint) { if ((0, _typeof2["default"])(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if ((0, _typeof2["default"])(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
var combinedInitialState = {}; | ||
/* =================================== */ | ||
/* STORE | ||
/* =================================== */ | ||
var store = { | ||
reducers: {}, | ||
sagas: [], | ||
middlewares: [], | ||
create: function create() { | ||
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var sagaMiddleware = (0, _reduxSaga["default"])(); | ||
var sagaEnhancer = (0, _redux.applyMiddleware)(sagaMiddleware); | ||
var devTools = (0, _redux.compose)(window.devToolsExtension ? window.devToolsExtension() : function (foo) { | ||
return foo; | ||
}); | ||
this.middlewares = [].concat((0, _toConsumableArray2["default"])(this.middlewares), [sagaEnhancer, devTools]); | ||
this.storeInstance = (0, _redux.createStore)(this.getRootReducer(initialState), _redux.compose.apply(void 0, (0, _toConsumableArray2["default"])(this.middlewares))); | ||
this.sagas.forEach(function (saga) { | ||
return sagaMiddleware.run(saga); | ||
}); | ||
return this.storeInstance; | ||
}, | ||
getRootReducer: function getRootReducer() { | ||
var _this = this; | ||
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var reducers = _objectSpread({}, this.reducers); | ||
if (Object.keys(reducers).length === 0 || process.env.NODE_ENV === 'test') { | ||
reducers.$_foo = function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
return state; | ||
}; // default reducer | ||
} | ||
var rootReducer = (0, _redux.combineReducers)(reducers); | ||
return function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState; | ||
var action = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
// start updating the state | ||
_this.$updatingState = true; // clear getState calls queue | ||
_this.getStateCallbacks = []; // get the new state object | ||
var newState = rootReducer(state, action); // invoke each getState call in the queue with the new state | ||
_this.$updatingState = false; | ||
while (_this.getStateCallbacks.length) { | ||
_this.getStateCallbacks.shift()(newState); | ||
} // return the new state | ||
return newState; | ||
}; | ||
}, | ||
getState: function getState(query) { | ||
var _this2 = this; | ||
if (this.$updatingState === false) { | ||
return Promise.resolve(this.queryState(query, this.storeInstance.getState())); | ||
} | ||
return new Promise(function (resolve) { | ||
_this2.getStateCallbacks.push(function (state) { | ||
resolve(_this2.queryState(query, state)); | ||
}); | ||
}); | ||
}, | ||
queryState: function queryState(query, state) { | ||
// handle query strings | ||
if (helpers.getObjectType(query) === 'string') { | ||
return helpers.findPropInObject(state, query); | ||
} // handle query objects | ||
if (helpers.getObjectType(query) === 'object') { | ||
return Object.keys(query).reduce(function (prev, next) { | ||
return _objectSpread({}, prev, (0, _defineProperty2["default"])({}, next, helpers.findPropInObject(state, query[next]))); | ||
}, {}); | ||
} | ||
return state; | ||
Object.defineProperty(exports, "Provider", { | ||
enumerable: true, | ||
get: function get() { | ||
return _reactRedux.Provider; | ||
} | ||
}; | ||
}); | ||
exports.createStore = void 0; | ||
var useReducer = function useReducer(name, reducer) { | ||
store.reducers[name] = reducer; | ||
}; | ||
var _store = _interopRequireDefault(require("./store")); | ||
exports.useReducer = useReducer; | ||
var _reactRedux = require("react-redux"); | ||
var useMiddleware = function useMiddleware(middleware) { | ||
store.middlewares.unshift((0, _redux.applyMiddleware)(middleware)); | ||
}; | ||
/* =================================== */ | ||
/** | ||
* Dependency imports. | ||
*/ | ||
// import React from 'react'; | ||
// import { Provider as ReduxProvider } from 'react-redux'; | ||
// import { applyMiddleware } from 'redux'; | ||
/* PROVIDER | ||
/* =================================== */ | ||
/** | ||
* Local imports. | ||
*/ | ||
// import { combinedInitialState } from './connect'; | ||
/** | ||
* Re-exports. | ||
*/ | ||
// export { default as connect } from './connect'; | ||
exports.useMiddleware = useMiddleware; | ||
/** | ||
* Adds a reducer function to be used by the root reducer. | ||
* @param {String} key Reducer unique identifier key | ||
* @param {Function} reducer Reducer function. | ||
*/ | ||
// export const useReducer = (name, reducer) => { | ||
// store.reducers[name] = reducer; | ||
// }; | ||
var Provider = function Provider(props) { | ||
return _react["default"].createElement(_Provider["default"], (0, _extends2["default"])({ | ||
store: store.create(combinedInitialState) | ||
}, props)); | ||
/** | ||
* Allows registering middleware functions such as Router and other middlewares. | ||
* @param {Function} middleWare Middleware function to use | ||
*/ | ||
// export const useMiddleware = (middleware) => { | ||
// store.middlewares.unshift(applyMiddleware(middleware)); | ||
// }; | ||
var createStore = function createStore() { | ||
return _store["default"].create({}); | ||
}; | ||
/* =================================== */ | ||
/* MODULE | ||
/* =================================== */ | ||
exports.Provider = Provider; | ||
var Module = function Module(config) { | ||
var _this3 = this; | ||
(0, _classCallCheck2["default"])(this, Module); | ||
(0, _defineProperty2["default"])(this, "build", function () { | ||
/* build action creators ---------- */ | ||
Object.entries(_this3.config.actions || {}).forEach(function (_ref) { | ||
var _ref2 = (0, _slicedToArray2["default"])(_ref, 2), | ||
name = _ref2[0], | ||
callback = _ref2[1]; | ||
var camelCaseName = helpers.toCamelCase(name); | ||
var actionName = helpers.toSnakeCase(camelCaseName).toUpperCase(); | ||
var actionType = "@@".concat(_this3.name, "/").concat(actionName); | ||
var argNames = helpers.getArgNames(callback); | ||
_this3.actionCreators[camelCaseName] = function () { | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
// build the payload object | ||
var payload = argNames.reduce(function (prev, next, index) { | ||
return _objectSpread({}, prev, (0, _defineProperty2["default"])({}, next, args[index])); | ||
}, {}); // then use it to build the action object | ||
var actionObject = { | ||
type: actionType, | ||
payload: payload | ||
}; | ||
return actionObject; | ||
}; | ||
_this3.actionToReducerMap[actionType] = _this3.createSubReducer(actionType, callback, argNames, 'create'); | ||
_this3.sagas[actionType] = _this3.createSaga(actionType); | ||
}); | ||
/* build handlers ----------------- */ | ||
Object.entries(_this3.config.handlers || {}).forEach(function (_ref3) { | ||
var _ref4 = (0, _slicedToArray2["default"])(_ref3, 2), | ||
name = _ref4[0], | ||
callback = _ref4[1]; | ||
var actionType = name; | ||
if (/^(.*?)\.(.*?)$/.test(actionType)) { | ||
var _actionType$split = actionType.split('.'), | ||
_actionType$split2 = (0, _slicedToArray2["default"])(_actionType$split, 2), | ||
moduleName = _actionType$split2[0], | ||
camelCaseName = _actionType$split2[1]; | ||
var actionName = helpers.toSnakeCase(camelCaseName).toUpperCase(); | ||
actionType = "@@".concat(moduleName, "/").concat(actionName); | ||
} | ||
var argNames = helpers.getArgNames(callback); | ||
_this3.actionToReducerMap[actionType] = _this3.createSubReducer(actionType, callback, argNames, 'handle'); | ||
_this3.sagas[actionType] = _this3.createSaga(actionType); | ||
}); | ||
/* build reducer ------------------ */ | ||
_this3.reducer = function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var action = arguments.length > 1 ? arguments[1] : undefined; | ||
// the action type might be in normal form, such as: '@@prefix/ACTION_NAME' | ||
// or it may contain a sub action type: '@@prefix/ACTION_NAME/SUB_ACTION_NAME' | ||
var actionType = action.type; | ||
var mainActionType = (actionType.match(/@@(.*?)\/((.*?)(?=\/)|(.*?)$)/) || [])[0] || actionType; | ||
var subActionType = actionType.replace(mainActionType, '').slice(1); | ||
var actionName = mainActionType.replace(/^@@(.*?)\//, ''); | ||
var newState = state; // if the sub action is 'update', just update the state with the payload object | ||
if (mainActionType === "@@".concat(_this3.name, "/").concat(actionName) && subActionType === 'UPDATE') { | ||
newState = helpers.mergeObjects(state, action.payload || {}); | ||
} // if it's a main action, look for a sub reducer that can handle this action | ||
_this3.getActionTypeMatchers(actionType).forEach(function (matcher) { | ||
if (_this3.actionToReducerMap[matcher]) { | ||
newState = _this3.actionToReducerMap[matcher](newState, action); | ||
} | ||
}); | ||
return newState; | ||
}; | ||
/* map state to props ------------- */ | ||
_this3.mapStateToProps = function (state) { | ||
var _ref5; | ||
var _this3$name = _this3.name, | ||
ownState = state[_this3$name], | ||
globalState = (0, _objectWithoutProperties2["default"])(state, [_this3$name].map(_toPropertyKey)); | ||
return _ref5 = {}, (0, _defineProperty2["default"])(_ref5, _this3.stateKey, ownState), (0, _defineProperty2["default"])(_ref5, _this3.globalStateKey, globalState), _ref5; | ||
}; | ||
/* map dispatch to props ---------- */ | ||
_this3.mapDispatchToProps = function (dispatch) { | ||
return (0, _redux.bindActionCreators)(_this3.actionCreators, dispatch); | ||
}; | ||
/* combine props ------------------ */ | ||
_this3.combineProps = function (stateProps, dispatchProps, ownProps) { | ||
return _objectSpread({}, ownProps, {}, stateProps, (0, _defineProperty2["default"])({}, _this3.actionsKey, _objectSpread({}, dispatchProps))); | ||
}; | ||
}); | ||
(0, _defineProperty2["default"])(this, "createSubReducer", function (actionType, callback, argNames, mode) { | ||
return function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var action = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
var matchers = _this3.getActionTypeMatchers(action.type); | ||
if (matchers.includes(actionType)) { | ||
var callbackResult = _this3.executeCallback(callback, action, argNames, mode); | ||
var callbackResultType = helpers.getObjectType(callbackResult); | ||
var stateFragment = callbackResultType === 'object' ? callbackResult : {}; // the saga handler will be called right after the reducer so instead of the saga | ||
// handler executing the callback again, pass it the cached result | ||
_this3.cachedCallbackResult = _this3.cachedCallbackResult || {}; | ||
_this3.cachedCallbackResult[action.type] = callbackResult; | ||
return helpers.mergeObjects(state, stateFragment); | ||
} | ||
return state; | ||
}; | ||
}); | ||
(0, _defineProperty2["default"])(this, "createSaga", function (actionType) { | ||
return ( | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function saga() { | ||
return _regenerator["default"].wrap(function saga$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
this.workerSagas[actionType] = | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function workerSaga() { | ||
var result, data, isDone, breakAfter, _loop; | ||
return _regenerator["default"].wrap(function workerSaga$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
result = this.cachedCallbackResult && this.cachedCallbackResult[actionType]; // check if the callback return value is an iterable (usually a generator function) | ||
// if it is an iterable then consume it | ||
if (!(result && typeof result[Symbol.iterator] === 'function')) { | ||
_context2.next = 19; | ||
break; | ||
} | ||
_context2.prev = 2; | ||
// `data` will be assigned to each `next()` call | ||
// `isDone` will be true when `next()` returns done as true | ||
isDone = false; // the while loop will break after a maximum of 50 calls | ||
breakAfter = 50; | ||
_loop = | ||
/*#__PURE__*/ | ||
_regenerator["default"].mark(function _loop() { | ||
var next, nextResult; | ||
return _regenerator["default"].wrap(function _loop$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
next = result.next(data); | ||
nextResult = next.value; | ||
isDone = next.done; // if the yielded value is a Promise, resolve it then continue | ||
if (!(nextResult instanceof Promise)) { | ||
_context.next = 9; | ||
break; | ||
} | ||
_context.next = 6; | ||
return (0, _effects.call)(function () { | ||
return nextResult; | ||
}); | ||
case 6: | ||
data = _context.sent; | ||
_context.next = 12; | ||
break; | ||
case 9: | ||
if (!(helpers.getObjectType(nextResult) === 'object')) { | ||
_context.next = 12; | ||
break; | ||
} | ||
_context.next = 12; | ||
return (0, _effects.put)({ | ||
type: "".concat(actionType, "/UPDATE"), | ||
payload: nextResult | ||
}); | ||
case 12: | ||
breakAfter -= 1; // safety break | ||
if (!(breakAfter === 0)) { | ||
_context.next = 15; | ||
break; | ||
} | ||
throw new Error('An async action handler yielded more than 50 values.'); | ||
case 15: | ||
case "end": | ||
return _context.stop(); | ||
} | ||
} | ||
}, _loop); | ||
}); | ||
case 6: | ||
if (isDone) { | ||
_context2.next = 10; | ||
break; | ||
} | ||
return _context2.delegateYield(_loop(), "t0", 8); | ||
case 8: | ||
_context2.next = 6; | ||
break; | ||
case 10: | ||
_context2.next = 12; | ||
return (0, _effects.put)({ | ||
type: "".concat(actionType, "/COMPLETE") | ||
}); | ||
case 12: | ||
_context2.next = 19; | ||
break; | ||
case 14: | ||
_context2.prev = 14; | ||
_context2.t1 = _context2["catch"](2); | ||
window.console.error(_context2.t1); | ||
_context2.next = 19; | ||
return (0, _effects.put)({ | ||
type: "".concat(actionType, "/ERROR"), | ||
message: _context2.t1.message | ||
}); | ||
case 19: | ||
case "end": | ||
return _context2.stop(); | ||
} | ||
} | ||
}, workerSaga, this, [[2, 14]]); | ||
}).bind(this); | ||
_context3.next = 3; | ||
return (0, _effects.takeLatest)(actionType, this.workerSagas[actionType]); | ||
case 3: | ||
case "end": | ||
return _context3.stop(); | ||
} | ||
} | ||
}, saga, this); | ||
}).bind(_this3) | ||
); | ||
}); | ||
(0, _defineProperty2["default"])(this, "executeCallback", function (callback, action, argNames, mode) { | ||
var callbackArgs = mode === 'create' ? argNames.map(function (arg) { | ||
return action.payload[arg]; | ||
}) : [action]; | ||
return callback.apply(_this3.getCallbackContext(), callbackArgs); | ||
}); | ||
(0, _defineProperty2["default"])(this, "getCallbackContext", function () { | ||
var self = _this3; | ||
return _objectSpread({}, self.config.actions, { | ||
getState: self.getState, | ||
get state() { | ||
return self.getState(); | ||
} | ||
}); | ||
}); | ||
(0, _defineProperty2["default"])(this, "getState", function _callee(query) { | ||
var state; | ||
return _regenerator["default"].async(function _callee$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
_context4.next = 2; | ||
return _regenerator["default"].awrap(store.getState()); | ||
case 2: | ||
_context4.t0 = _this3.name; | ||
state = _context4.sent[_context4.t0]; | ||
if (!(helpers.getObjectType(query) === 'string')) { | ||
_context4.next = 6; | ||
break; | ||
} | ||
return _context4.abrupt("return", helpers.findPropInObject(state, query)); | ||
case 6: | ||
if (!(helpers.getObjectType(query) === 'object')) { | ||
_context4.next = 8; | ||
break; | ||
} | ||
return _context4.abrupt("return", Object.keys(query).reduce(function (prev, next) { | ||
return _objectSpread({}, prev, (0, _defineProperty2["default"])({}, next, helpers.findPropInObject(state, query[next]))); | ||
}, {})); | ||
case 8: | ||
return _context4.abrupt("return", state); | ||
case 9: | ||
case "end": | ||
return _context4.stop(); | ||
} | ||
} | ||
}); | ||
}); | ||
(0, _defineProperty2["default"])(this, "getActionTypeMatchers", function (actionType) { | ||
var regex = /@@(.+?)\/(.+)/; | ||
var moduleName = ''; | ||
var actionName = actionType; | ||
if (regex.test(actionType)) { | ||
var _actionType$match = actionType.match(regex); | ||
var _actionType$match2 = (0, _slicedToArray2["default"])(_actionType$match, 3); | ||
moduleName = _actionType$match2[1]; | ||
actionName = _actionType$match2[2]; | ||
} | ||
return [actionType, // exact action | ||
"@@".concat(moduleName), // any action by the module | ||
"@@".concat(moduleName, "/"), // any action by the module (alias) | ||
"@@".concat(moduleName, "/*"), // any action by the module (alias) | ||
"@@*/".concat(actionName), // same action dispatched by any module | ||
"*/".concat(actionName), // same action dispatched by any module (alias) | ||
'*' // any action | ||
]; | ||
}); | ||
this.config = config; | ||
this.name = config.name; | ||
this.stateKey = config.stateKey || 'state'; | ||
this.actionsKey = config.actionsKey || 'actions'; | ||
this.globalStateKey = config.globalStateKey || 'globalState'; | ||
this.actionCreators = {}; | ||
this.actionToReducerMap = {}; | ||
this.sagas = {}; | ||
this.workerSagas = {}; | ||
this.reducer = function (state) { | ||
return state; | ||
}; | ||
this.build(); | ||
}; | ||
/* =================================== */ | ||
/* CONNECT | ||
/* =================================== */ | ||
var connect = function connect(component) { | ||
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
if (typeof component !== 'function' && Object.getPrototypeOf(component) !== _react.Component) { | ||
throw new Error('Expected the first parameter to be a pure function or a valid React component class.'); | ||
} | ||
if (helpers.getObjectType(config) !== 'object') { | ||
throw new Error('Module configuration must be an object'); | ||
} | ||
var moduleConfig = _objectSpread({}, config); | ||
if (!moduleConfig.name) { | ||
moduleConfig.name = helpers.getComponentName(component); | ||
} | ||
if (typeof connect.moduleNames === 'undefined') { | ||
connect.moduleNames = {}; | ||
} | ||
if (connect.moduleNames[moduleConfig.name] === true) { | ||
throw new Error("Name '".concat(moduleConfig.name, "' has already been used by another module, please use a different name")); | ||
} else { | ||
connect.moduleNames[moduleConfig.name] = true; | ||
} | ||
var module = new Module(moduleConfig); | ||
combinedInitialState[module.name] = moduleConfig.initialState || moduleConfig.state || {}; | ||
store.reducers[module.name] = module.reducer; | ||
var connectedComponent = (0, _connect["default"])(module.mapStateToProps, module.mapDispatchToProps, module.combineProps)(component); | ||
store.sagas = [].concat((0, _toConsumableArray2["default"])(store.sagas), (0, _toConsumableArray2["default"])(Object.values(module.sagas))); | ||
return connectedComponent; | ||
}; | ||
exports.connect = connect; | ||
var _default = {}; | ||
exports["default"] = _default; | ||
exports.createStore = createStore; |
178
lib/store.js
"use strict"; | ||
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); | ||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -8,25 +12,173 @@ value: true | ||
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); | ||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
var _redux = require("redux"); | ||
var _reduxSaga = _interopRequireDefault(require("redux-saga")); | ||
var helpers = _interopRequireWildcard(require("./helpers")); | ||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
/** | ||
* Dependency imports. | ||
* Creates the saga middleware function. | ||
* @type {Function} | ||
*/ | ||
var getRootReducer = function getRootReducer(initialState) { | ||
return function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState; | ||
var action = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
var sagaMiddleware = (0, _reduxSaga["default"])(); | ||
/** | ||
* Creates the saga store enhancer. | ||
* @type {Function} | ||
*/ | ||
if (action) { | ||
console.log(action); | ||
var sagaEnhancer = (0, _redux.applyMiddleware)(sagaMiddleware); | ||
/** | ||
* Creates a middleware function that is used to enable Redux devTools. | ||
* in the browser. | ||
* @type {Function} | ||
*/ | ||
var devTools = (0, _redux.compose)(window.devToolsExtension ? window.devToolsExtension() : function (foo) { | ||
return foo; | ||
}); | ||
/** | ||
* This is not the actual store object. This is a wrapper object | ||
* that manages the Redux store instance. Use `store.getInstance()` | ||
* to get a reference to the Redux store. | ||
*/ | ||
var store = { | ||
/** | ||
* An object that is used as a map to store references to registered | ||
* reducers. This object is used by `getRootReducer()` to create the | ||
* root reducer for the store. | ||
* @type {Object} | ||
*/ | ||
reducers: {}, | ||
sagas: [], | ||
/** | ||
* An array of middlewares to use when creating the store. | ||
* Use exported method `useMiddleware()` to add other middleware | ||
* functions to this list. | ||
* @type {Array} | ||
*/ | ||
middlewares: [sagaEnhancer, devTools], | ||
/** | ||
* Creates a new Redux store instance and updates the reference. | ||
*/ | ||
create: function create() { | ||
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
if (this.storeInstance) return this.storeInstance; | ||
this.storeInstance = (0, _redux.createStore)(this.getRootReducer(initialState), _redux.compose.apply(void 0, (0, _toConsumableArray2["default"])(this.middlewares))); | ||
this.sagas.forEach(function (saga) { | ||
return sagaMiddleware.run(saga); | ||
}); | ||
return this.storeInstance; | ||
}, | ||
/** | ||
* Combines all registered reducers and returns a single reducer function. | ||
* @param {Object} initialState The initial state for the app | ||
*/ | ||
getRootReducer: function getRootReducer() { | ||
var _this = this; | ||
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var reducers = _objectSpread({}, this.reducers); | ||
if (Object.keys(reducers).length === 0 || process.env.NODE_ENV === 'test') { | ||
reducers.$_foo = function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
return state; | ||
}; // default reducer | ||
} | ||
var rootReducer = (0, _redux.combineReducers)(reducers); | ||
return function () { | ||
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState; | ||
var action = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
// start updating the state | ||
_this.$updatingState = true; // clear getState calls queue | ||
_this.getStateCallbacks = []; // get the new state object | ||
var newState = rootReducer(state, action); // invoke each getState call in the queue with the new state | ||
_this.$updatingState = false; | ||
while (_this.getStateCallbacks.length) { | ||
_this.getStateCallbacks.shift()(newState); | ||
} // return the new state | ||
return newState; | ||
}; | ||
}, | ||
/** | ||
* Returns the complete state object or part of it based on a given query. If the | ||
* query parameter is a string that uses dot notation, it will return the resolved | ||
* value of the given key. If the query is an object, it will return an object that | ||
* has the same structure but contains the resolved values. If the query parameter | ||
* is not provided, the complete state object will be returned. | ||
* @param {String|Object} query A query string or a query object that represents | ||
* part of the state object that needs to be fetched. | ||
* This parameter is not required. | ||
* @return {Promise} A promise that eventually resolves with the state | ||
* object, part of it or a value in the state object. | ||
*/ | ||
getState: function getState(query) { | ||
var _this2 = this; | ||
if (this.$updatingState === false) { | ||
return Promise.resolve(this.getStateSync(query)); | ||
} | ||
return new Promise(function (resolve) { | ||
_this2.getStateCallbacks.push(function (state) { | ||
resolve(_this2.queryState(query, state)); | ||
}); | ||
}); | ||
}, | ||
getStateSync: function getStateSync(query) { | ||
return this.queryState(query, this.storeInstance.getState()); | ||
}, | ||
/** | ||
* Queries a state object for a specific value. | ||
* @param {String} query Query string. | ||
* @param {Object} state State object to query. | ||
* @return {Object} The state object, part of it or a value in the state object. | ||
*/ | ||
queryState: function queryState(query, state) { | ||
// handle query strings | ||
if (helpers.getObjectType(query) === 'string') { | ||
return helpers.findPropInObject(state, query); | ||
} // handle query objects | ||
if (helpers.getObjectType(query) === 'object') { | ||
return Object.keys(query).reduce(function (prev, next) { | ||
return _objectSpread({}, prev, (0, _defineProperty2["default"])({}, next, helpers.findPropInObject(state, query[next]))); | ||
}, {}); | ||
} | ||
return state; | ||
}; | ||
}; | ||
}, | ||
var _default = function _default() { | ||
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
return (0, _redux.createStore)(getRootReducer(initialState)); | ||
/** | ||
* Returns an reference to the Redux store instance. | ||
*/ | ||
getInstance: function getInstance() { | ||
return this.storeInstance; | ||
} | ||
}; | ||
var _default = store; | ||
exports["default"] = _default; |
{ | ||
"name": "brownturtle", | ||
"version": "3.0.1", | ||
"description": "An opinionated state management library based on Redux.", | ||
"version": "3.2.0", | ||
"description": "", | ||
"main": "lib/index.js", | ||
@@ -16,9 +16,7 @@ "scripts": { | ||
"posttest": "npm run lint", | ||
"prepublishOnly": "npm test && npm run build", | ||
"storybook": "start-storybook -p 6006", | ||
"build-storybook": "build-storybook" | ||
"prepublishOnly": "npm test && npm run build" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/teefouad/brownturtle.git" | ||
"url": "git+https://github.com/teefouad/speedux.git" | ||
}, | ||
@@ -30,18 +28,8 @@ "files": [ | ||
], | ||
"keywords": [ | ||
"react", | ||
"reactjs", | ||
"flux", | ||
"redux", | ||
"action", | ||
"reducer", | ||
"javascript", | ||
"nodejs" | ||
], | ||
"author": "Mostafa <tee.fouad@gmail.com>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/teefouad/brownturtle/issues" | ||
"url": "https://github.com/teefouad/speedux/issues" | ||
}, | ||
"homepage": "https://github.com/teefouad/brownturtle#readme", | ||
"homepage": "https://github.com/teefouad/speedux#readme", | ||
"devDependencies": { | ||
@@ -53,12 +41,4 @@ "@babel/cli": "^7.2.3", | ||
"@babel/preset-env": "^7.3.4", | ||
"@babel/preset-react": "^7.7.0", | ||
"@storybook/addon-actions": "^5.2.6", | ||
"@storybook/addon-links": "^5.2.6", | ||
"@storybook/addons": "^5.2.6", | ||
"@storybook/cli": "^5.2.6", | ||
"@storybook/react": "^5.2.6", | ||
"@types/node": "^12.12.7", | ||
"babel-eslint": "^10.0.1", | ||
"babel-jest": "^24.4.0", | ||
"babel-loader": "^8.0.6", | ||
"coveralls": "^3.0.3", | ||
@@ -72,12 +52,13 @@ "cross-env": "^5.2.0", | ||
"jest": "^24.4.0", | ||
"uglifyjs-webpack-plugin": "^2.2.0", | ||
"react": "^16.12.0", | ||
"react-dom": "^16.12.0", | ||
"react-redux": "^7.1.3", | ||
"redux": "^4.0.4", | ||
"redux-saga": "^1.1.3", | ||
"webpack": "^4.29.6", | ||
"webpack-cli": "^3.2.3" | ||
}, | ||
"dependencies": { | ||
"react": "^16.11.0", | ||
"react-redux": "^7.1.3", | ||
"redux": "^4.0.4", | ||
"redux-saga": "^1.1.3" | ||
"peerDependencies": { | ||
"react": "^16.8.3" | ||
} | ||
} |
# speedux | ||
An opinionated state management library based on Redux. | ||
[![Version](https://img.shields.io/npm/v/speedux.svg?style=flat-square)](https://www.npmjs.com/package/speedux) | ||
@@ -5,0 +5,0 @@ [![License](https://img.shields.io/npm/l/speedux.svg?style=flat-square)](https://www.npmjs.com/package/speedux) |
443
src/index.js
@@ -1,416 +0,37 @@ | ||
import React, { Component } from 'react'; | ||
import { | ||
createStore as createReduxStore, | ||
bindActionCreators, | ||
applyMiddleware, | ||
compose, | ||
combineReducers, | ||
} from 'redux'; | ||
import reduxConnect from 'react-redux/es/connect/connect'; | ||
import ReduxProvider from 'react-redux/es/components/Provider'; | ||
import createSagaMiddleware from 'redux-saga'; | ||
import { | ||
takeLatest, | ||
put, | ||
call, | ||
} from 'redux-saga/effects'; | ||
import * as helpers from './helpers'; | ||
/** | ||
* Dependency imports. | ||
*/ | ||
// import React from 'react'; | ||
// import { Provider as ReduxProvider } from 'react-redux'; | ||
// import { applyMiddleware } from 'redux'; | ||
const combinedInitialState = {}; | ||
/** | ||
* Local imports. | ||
*/ | ||
import store from './store'; | ||
// import { combinedInitialState } from './connect'; | ||
/* =================================== */ | ||
/* STORE | ||
/* =================================== */ | ||
/** | ||
* Re-exports. | ||
*/ | ||
export { Provider } from 'react-redux'; | ||
// export { default as connect } from './connect'; | ||
const store = { | ||
reducers: {}, | ||
sagas: [], | ||
middlewares: [], | ||
/** | ||
* Adds a reducer function to be used by the root reducer. | ||
* @param {String} key Reducer unique identifier key | ||
* @param {Function} reducer Reducer function. | ||
*/ | ||
// export const useReducer = (name, reducer) => { | ||
// store.reducers[name] = reducer; | ||
// }; | ||
create(initialState = {}) { | ||
const sagaMiddleware = createSagaMiddleware(); | ||
const sagaEnhancer = applyMiddleware(sagaMiddleware); | ||
const devTools = compose(window.devToolsExtension ? window.devToolsExtension() : foo => foo); | ||
/** | ||
* Allows registering middleware functions such as Router and other middlewares. | ||
* @param {Function} middleWare Middleware function to use | ||
*/ | ||
// export const useMiddleware = (middleware) => { | ||
// store.middlewares.unshift(applyMiddleware(middleware)); | ||
// }; | ||
this.middlewares = [...this.middlewares, sagaEnhancer, devTools]; | ||
this.storeInstance = createReduxStore( | ||
this.getRootReducer(initialState), | ||
compose(...this.middlewares), | ||
); | ||
this.sagas.forEach(saga => sagaMiddleware.run(saga)); | ||
return this.storeInstance; | ||
}, | ||
getRootReducer(initialState = {}) { | ||
const reducers = { ...this.reducers }; | ||
if (Object.keys(reducers).length === 0 || process.env.NODE_ENV === 'test') { | ||
reducers.$_foo = (state = {}) => state; // default reducer | ||
} | ||
const rootReducer = combineReducers(reducers); | ||
return (state = initialState, action = null) => { | ||
// start updating the state | ||
this.$updatingState = true; | ||
// clear getState calls queue | ||
this.getStateCallbacks = []; | ||
// get the new state object | ||
const newState = rootReducer(state, action); | ||
// invoke each getState call in the queue with the new state | ||
this.$updatingState = false; | ||
while (this.getStateCallbacks.length) this.getStateCallbacks.shift()(newState); | ||
// return the new state | ||
return newState; | ||
}; | ||
}, | ||
getState(query) { | ||
if (this.$updatingState === false) { | ||
return Promise.resolve(this.queryState(query, this.storeInstance.getState())); | ||
} | ||
return new Promise((resolve) => { | ||
this.getStateCallbacks.push((state) => { | ||
resolve(this.queryState(query, state)); | ||
}); | ||
}); | ||
}, | ||
queryState(query, state) { | ||
// handle query strings | ||
if (helpers.getObjectType(query) === 'string') { | ||
return helpers.findPropInObject(state, query); | ||
} | ||
// handle query objects | ||
if (helpers.getObjectType(query) === 'object') { | ||
return Object.keys(query).reduce((prev, next) => ({ | ||
...prev, | ||
[next]: helpers.findPropInObject(state, query[next]), | ||
}), {}); | ||
} | ||
return state; | ||
}, | ||
}; | ||
export const useReducer = (name, reducer) => { | ||
store.reducers[name] = reducer; | ||
}; | ||
export const useMiddleware = (middleware) => { | ||
store.middlewares.unshift(applyMiddleware(middleware)); | ||
}; | ||
/* =================================== */ | ||
/* PROVIDER | ||
/* =================================== */ | ||
export const Provider = props => ( | ||
<ReduxProvider | ||
store={store.create(combinedInitialState)} | ||
{...props} | ||
/> | ||
); | ||
/* =================================== */ | ||
/* MODULE | ||
/* =================================== */ | ||
class Module { | ||
constructor(config) { | ||
this.config = config; | ||
this.name = config.name; | ||
this.stateKey = config.stateKey || 'state'; | ||
this.actionsKey = config.actionsKey || 'actions'; | ||
this.globalStateKey = config.globalStateKey || 'globalState'; | ||
this.actionCreators = {}; | ||
this.actionToReducerMap = {}; | ||
this.sagas = {}; | ||
this.workerSagas = {}; | ||
this.reducer = state => state; | ||
this.build(); | ||
} | ||
build = () => { | ||
/* build action creators ---------- */ | ||
Object.entries(this.config.actions || {}).forEach(([name, callback]) => { | ||
const camelCaseName = helpers.toCamelCase(name); | ||
const actionName = helpers.toSnakeCase(camelCaseName).toUpperCase(); | ||
const actionType = `@@${this.name}/${actionName}`; | ||
const argNames = helpers.getArgNames(callback); | ||
this.actionCreators[camelCaseName] = (...args) => { | ||
// build the payload object | ||
const payload = argNames.reduce((prev, next, index) => ({ | ||
...prev, | ||
[next]: args[index], | ||
}), {}); | ||
// then use it to build the action object | ||
const actionObject = { | ||
type: actionType, | ||
payload, | ||
}; | ||
return actionObject; | ||
}; | ||
this.actionToReducerMap[actionType] = this.createSubReducer(actionType, callback, argNames, 'create'); | ||
this.sagas[actionType] = this.createSaga(actionType); | ||
}); | ||
/* build handlers ----------------- */ | ||
Object.entries(this.config.handlers || {}).forEach(([name, callback]) => { | ||
let actionType = name; | ||
if (/^(.*?)\.(.*?)$/.test(actionType)) { | ||
const [moduleName, camelCaseName] = actionType.split('.'); | ||
const actionName = helpers.toSnakeCase(camelCaseName).toUpperCase(); | ||
actionType = `@@${moduleName}/${actionName}`; | ||
} | ||
const argNames = helpers.getArgNames(callback); | ||
this.actionToReducerMap[actionType] = this.createSubReducer(actionType, callback, argNames, 'handle'); | ||
this.sagas[actionType] = this.createSaga(actionType); | ||
}); | ||
/* build reducer ------------------ */ | ||
this.reducer = (state = {}, action) => { | ||
// the action type might be in normal form, such as: '@@prefix/ACTION_NAME' | ||
// or it may contain a sub action type: '@@prefix/ACTION_NAME/SUB_ACTION_NAME' | ||
const actionType = action.type; | ||
const mainActionType = (actionType.match(/@@(.*?)\/((.*?)(?=\/)|(.*?)$)/) || [])[0] || actionType; | ||
const subActionType = actionType.replace(mainActionType, '').slice(1); | ||
const actionName = mainActionType.replace(/^@@(.*?)\//, ''); | ||
let newState = state; | ||
// if the sub action is 'update', just update the state with the payload object | ||
if (mainActionType === `@@${this.name}/${actionName}` && subActionType === 'UPDATE') { | ||
newState = helpers.mergeObjects(state, action.payload || {}); | ||
} | ||
// if it's a main action, look for a sub reducer that can handle this action | ||
this.getActionTypeMatchers(actionType).forEach((matcher) => { | ||
if (this.actionToReducerMap[matcher]) { | ||
newState = this.actionToReducerMap[matcher](newState, action); | ||
} | ||
}); | ||
return newState; | ||
}; | ||
/* map state to props ------------- */ | ||
this.mapStateToProps = (state) => { | ||
const { [this.name]: ownState, ...globalState } = state; | ||
return { | ||
[this.stateKey]: ownState, | ||
[this.globalStateKey]: globalState, | ||
}; | ||
}; | ||
/* map dispatch to props ---------- */ | ||
this.mapDispatchToProps = dispatch => bindActionCreators(this.actionCreators, dispatch); | ||
/* combine props ------------------ */ | ||
this.combineProps = (stateProps, dispatchProps, ownProps) => ({ | ||
...ownProps, | ||
...stateProps, | ||
[this.actionsKey]: { ...dispatchProps }, | ||
}); | ||
} | ||
createSubReducer = (actionType, callback, argNames, mode) => (state = {}, action = null) => { | ||
const matchers = this.getActionTypeMatchers(action.type); | ||
if (matchers.includes(actionType)) { | ||
const callbackResult = this.executeCallback(callback, action, argNames, mode); | ||
const callbackResultType = helpers.getObjectType(callbackResult); | ||
const stateFragment = (callbackResultType === 'object' ? callbackResult : {}); | ||
// the saga handler will be called right after the reducer so instead of the saga | ||
// handler executing the callback again, pass it the cached result | ||
this.cachedCallbackResult = this.cachedCallbackResult || {}; | ||
this.cachedCallbackResult[action.type] = callbackResult; | ||
return helpers.mergeObjects(state, stateFragment); | ||
} | ||
return state; | ||
}; | ||
createSaga = actionType => function* saga() { | ||
this.workerSagas[actionType] = function* workerSaga() { | ||
const result = this.cachedCallbackResult && this.cachedCallbackResult[actionType]; | ||
// check if the callback return value is an iterable (usually a generator function) | ||
// if it is an iterable then consume it | ||
if (result && typeof result[Symbol.iterator] === 'function') { | ||
try { | ||
// `data` will be assigned to each `next()` call | ||
let data; | ||
// `isDone` will be true when `next()` returns done as true | ||
let isDone = false; | ||
// the while loop will break after a maximum of 50 calls | ||
let breakAfter = 50; | ||
while (!isDone) { | ||
const next = result.next(data); | ||
const nextResult = next.value; | ||
isDone = next.done; | ||
// if the yielded value is a Promise, resolve it then continue | ||
if (nextResult instanceof Promise) { | ||
data = yield call(() => nextResult); | ||
} else | ||
// if the yielded value is an object, use it to update the state | ||
if (helpers.getObjectType(nextResult) === 'object') { | ||
yield put({ | ||
type: `${actionType}/UPDATE`, | ||
payload: nextResult, | ||
}); | ||
} | ||
breakAfter -= 1; | ||
// safety break | ||
if (breakAfter === 0) { | ||
throw new Error('An async action handler yielded more than 50 values.'); | ||
} | ||
} | ||
// indicate that the async action has completed by dispatching | ||
// a COMPLETE sub action | ||
yield put({ | ||
type: `${actionType}/COMPLETE`, | ||
}); | ||
} catch (e) { | ||
window.console.error(e); | ||
yield put({ | ||
type: `${actionType}/ERROR`, | ||
message: e.message, | ||
}); | ||
} | ||
} | ||
}.bind(this); | ||
yield takeLatest(actionType, this.workerSagas[actionType]); | ||
}.bind(this); | ||
executeCallback = (callback, action, argNames, mode) => { | ||
const callbackArgs = mode === 'create' ? argNames.map(arg => action.payload[arg]) : [action]; | ||
return callback.apply(this.getCallbackContext(), callbackArgs); | ||
} | ||
getCallbackContext = () => { | ||
const self = this; | ||
return { | ||
...self.config.actions, | ||
getState: self.getState, | ||
get state() { return self.getState(); }, | ||
}; | ||
} | ||
getState = async (query) => { | ||
const state = (await store.getState())[this.name]; | ||
// handle query strings | ||
if (helpers.getObjectType(query) === 'string') { | ||
return helpers.findPropInObject(state, query); | ||
} | ||
// handle query objects | ||
if (helpers.getObjectType(query) === 'object') { | ||
return Object.keys(query).reduce((prev, next) => ({ | ||
...prev, | ||
[next]: helpers.findPropInObject(state, query[next]), | ||
}), {}); | ||
} | ||
return state; | ||
} | ||
getActionTypeMatchers = (actionType) => { | ||
const regex = /@@(.+?)\/(.+)/; | ||
let moduleName = ''; | ||
let actionName = actionType; | ||
if (regex.test(actionType)) { | ||
[, moduleName, actionName] = actionType.match(regex); | ||
} | ||
return [ | ||
actionType, // exact action | ||
`@@${moduleName}`, // any action by the module | ||
`@@${moduleName}/`, // any action by the module (alias) | ||
`@@${moduleName}/*`, // any action by the module (alias) | ||
`@@*/${actionName}`, // same action dispatched by any module | ||
`*/${actionName}`, // same action dispatched by any module (alias) | ||
'*', // any action | ||
]; | ||
} | ||
} | ||
/* =================================== */ | ||
/* CONNECT | ||
/* =================================== */ | ||
export const connect = (component, config = {}) => { | ||
if (typeof component !== 'function' && Object.getPrototypeOf(component) !== Component) { | ||
throw new Error('Expected the first parameter to be a pure function or a valid React component class.'); | ||
} | ||
if (helpers.getObjectType(config) !== 'object') { | ||
throw new Error('Module configuration must be an object'); | ||
} | ||
const moduleConfig = { ...config }; | ||
if (!moduleConfig.name) { | ||
moduleConfig.name = helpers.getComponentName(component); | ||
} | ||
if (typeof connect.moduleNames === 'undefined') { | ||
connect.moduleNames = {}; | ||
} | ||
if (connect.moduleNames[moduleConfig.name] === true) { | ||
throw new Error(`Name '${moduleConfig.name}' has already been used by another module, please use a different name`); | ||
} else { | ||
connect.moduleNames[moduleConfig.name] = true; | ||
} | ||
const module = new Module(moduleConfig); | ||
combinedInitialState[module.name] = moduleConfig.initialState || moduleConfig.state || {}; | ||
store.reducers[module.name] = module.reducer; | ||
const connectedComponent = reduxConnect( | ||
module.mapStateToProps, | ||
module.mapDispatchToProps, | ||
module.combineProps, | ||
)(component); | ||
store.sagas = [ | ||
...store.sagas, | ||
...Object.values(module.sagas), | ||
]; | ||
return connectedComponent; | ||
}; | ||
export default {}; | ||
export const createStore = () => store.create({}); |
167
src/store.js
@@ -6,15 +6,164 @@ /** | ||
createStore as createReduxStore, | ||
// combineReducers, | ||
// applyMiddleware, | ||
// compose, | ||
applyMiddleware, | ||
compose, | ||
combineReducers, | ||
} from 'redux'; | ||
import createSagaMiddleware from 'redux-saga'; | ||
const getRootReducer = initialState => (state = initialState, action = null) => { | ||
if (action) { | ||
console.log(action); | ||
} | ||
/** | ||
* Local imports. | ||
*/ | ||
import * as helpers from './helpers'; | ||
return state; | ||
/** | ||
* Creates the saga middleware function. | ||
* @type {Function} | ||
*/ | ||
const sagaMiddleware = createSagaMiddleware(); | ||
/** | ||
* Creates the saga store enhancer. | ||
* @type {Function} | ||
*/ | ||
const sagaEnhancer = applyMiddleware(sagaMiddleware); | ||
/** | ||
* Creates a middleware function that is used to enable Redux devTools. | ||
* in the browser. | ||
* @type {Function} | ||
*/ | ||
const devTools = compose(window.devToolsExtension ? window.devToolsExtension() : foo => foo); | ||
/** | ||
* This is not the actual store object. This is a wrapper object | ||
* that manages the Redux store instance. Use `store.getInstance()` | ||
* to get a reference to the Redux store. | ||
*/ | ||
const store = { | ||
/** | ||
* An object that is used as a map to store references to registered | ||
* reducers. This object is used by `getRootReducer()` to create the | ||
* root reducer for the store. | ||
* @type {Object} | ||
*/ | ||
reducers: {}, | ||
sagas: [], | ||
/** | ||
* An array of middlewares to use when creating the store. | ||
* Use exported method `useMiddleware()` to add other middleware | ||
* functions to this list. | ||
* @type {Array} | ||
*/ | ||
middlewares: [sagaEnhancer, devTools], | ||
/** | ||
* Creates a new Redux store instance and updates the reference. | ||
*/ | ||
create(initialState = {}) { | ||
if (this.storeInstance) return this.storeInstance; | ||
this.storeInstance = createReduxStore( | ||
this.getRootReducer(initialState), | ||
compose(...this.middlewares), | ||
); | ||
this.sagas.forEach(saga => sagaMiddleware.run(saga)); | ||
return this.storeInstance; | ||
}, | ||
/** | ||
* Combines all registered reducers and returns a single reducer function. | ||
* @param {Object} initialState The initial state for the app | ||
*/ | ||
getRootReducer(initialState = {}) { | ||
const reducers = { ...this.reducers }; | ||
if (Object.keys(reducers).length === 0 || process.env.NODE_ENV === 'test') { | ||
reducers.$_foo = (state = {}) => state; // default reducer | ||
} | ||
const rootReducer = combineReducers(reducers); | ||
return (state = initialState, action = null) => { | ||
// start updating the state | ||
this.$updatingState = true; | ||
// clear getState calls queue | ||
this.getStateCallbacks = []; | ||
// get the new state object | ||
const newState = rootReducer(state, action); | ||
// invoke each getState call in the queue with the new state | ||
this.$updatingState = false; | ||
while (this.getStateCallbacks.length) this.getStateCallbacks.shift()(newState); | ||
// return the new state | ||
return newState; | ||
}; | ||
}, | ||
/** | ||
* Returns the complete state object or part of it based on a given query. If the | ||
* query parameter is a string that uses dot notation, it will return the resolved | ||
* value of the given key. If the query is an object, it will return an object that | ||
* has the same structure but contains the resolved values. If the query parameter | ||
* is not provided, the complete state object will be returned. | ||
* @param {String|Object} query A query string or a query object that represents | ||
* part of the state object that needs to be fetched. | ||
* This parameter is not required. | ||
* @return {Promise} A promise that eventually resolves with the state | ||
* object, part of it or a value in the state object. | ||
*/ | ||
getState(query) { | ||
if (this.$updatingState === false) { | ||
return Promise.resolve(this.getStateSync(query)); | ||
} | ||
return new Promise((resolve) => { | ||
this.getStateCallbacks.push((state) => { | ||
resolve(this.queryState(query, state)); | ||
}); | ||
}); | ||
}, | ||
getStateSync(query) { | ||
return this.queryState(query, this.storeInstance.getState()); | ||
}, | ||
/** | ||
* Queries a state object for a specific value. | ||
* @param {String} query Query string. | ||
* @param {Object} state State object to query. | ||
* @return {Object} The state object, part of it or a value in the state object. | ||
*/ | ||
queryState(query, state) { | ||
// handle query strings | ||
if (helpers.getObjectType(query) === 'string') { | ||
return helpers.findPropInObject(state, query); | ||
} | ||
// handle query objects | ||
if (helpers.getObjectType(query) === 'object') { | ||
return Object.keys(query).reduce((prev, next) => ({ | ||
...prev, | ||
[next]: helpers.findPropInObject(state, query[next]), | ||
}), {}); | ||
} | ||
return state; | ||
}, | ||
/** | ||
* Returns an reference to the Redux store instance. | ||
*/ | ||
getInstance() { | ||
return this.storeInstance; | ||
}, | ||
}; | ||
export default (initialState = {}) => createReduxStore(getRootReducer(initialState)); | ||
export default store; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
1611427
1
22
6526
0
11
- Removedreact@^16.11.0
- Removedreact-redux@^7.1.3
- Removedredux@^4.0.4
- Removedredux-saga@^1.1.3
- Removed@babel/runtime@7.26.0(transitive)
- Removed@redux-saga/core@1.3.0(transitive)
- Removed@redux-saga/deferred@1.2.1(transitive)
- Removed@redux-saga/delay-p@1.2.1(transitive)
- Removed@redux-saga/is@1.1.3(transitive)
- Removed@redux-saga/symbols@1.1.3(transitive)
- Removed@redux-saga/types@1.2.1(transitive)
- Removed@types/hoist-non-react-statics@3.3.5(transitive)
- Removed@types/prop-types@15.7.13(transitive)
- Removed@types/react@18.3.12(transitive)
- Removed@types/react-redux@7.1.34(transitive)
- Removedcsstype@3.1.3(transitive)
- Removedhoist-non-react-statics@3.3.2(transitive)
- Removedreact-is@17.0.2(transitive)
- Removedreact-redux@7.2.9(transitive)
- Removedredux@4.2.1(transitive)
- Removedredux-saga@1.3.0(transitive)
- Removedregenerator-runtime@0.14.1(transitive)
- Removedtypescript-compare@0.0.2(transitive)
- Removedtypescript-logic@0.0.0(transitive)
- Removedtypescript-tuple@2.2.1(transitive)