Source: scopeDuck.js

import scopeAction from "./scopeAction";
import scopeReducer from "./scopeReducer";
import scopeActionType from "./scopeActionType";

/**
 * Scopes a whole duck.
 * 
 * Takes a duck and returns a scoped version. All scrion types, action creators and the reducer are scoped.
 * 
 * @example
 * 
 * // src/valueDuck.js
 * export const SET_VALUE = "app/reducerA/SET_VALUE"
 * export const setValue = payload => ({
 *  type: SET_VALUE,
 *  payload
 * })
 * export default (state = 0, action) => action.type === SET_VALUE
 *  ? action.payload
 *  : state
 * 
 * // src/valueADuck.js
 * import * as valueDuck from "./valueDuck"
 * export const duck = scopeDuck("valueA", valueDuck)
 * 
 * // src/valueBDuck.js
 * import * as valueDuck from "./valueDuck"
 * export const duck = scopeDuck("valueB", valueDuck)
 * 
 * @param {string} scope 
 * @param {object} duck 
 * @returns {object} scoped duck - all action types/creators and the reducer are scoped
 */

const scopeDuck = (scope, duck) => {
  const isDuckObject =
    !Array.isArray(duck) && duck !== null && typeof duck === "object";
  const hasDuckReducer = duck && typeof duck.default === "function";

  return isDuckObject && hasDuckReducer
    ? Object.keys(duck).reduce((scopedDuck, key) => {
        if (key === "default") {
          scopedDuck[key] = scopeReducer(scope, duck[key]);
        } else if (typeof duck[key] === "function") {
          scopedDuck[key] = scopeAction(scope, duck[key]);
        } else if (RegExp(/^[A-Z_]/g).test(key)) {
          scopedDuck[key] = scopeActionType(scope, duck[key]);
        } else {
          scopedDuck[key] = duck[key];
        }
        return scopedDuck;
      }, {})
    : duck;
};
export default scopeDuck;