@shopgate/pwa-common
Version:
Common library for the Shopgate Connect PWA.
177 lines (156 loc) • 5.27 kB
JavaScript
import "core-js/modules/es.array.reduce.js";
import "core-js/modules/es.string.replace.js";
import noop from 'lodash/noop';
import { isObject } from "../validation";
/**
* @typedef {Object} MutableFunction
* @property {Function} replace Replaces the original functionality with a custom function.
* @property {Function} restore Restores the original function.
* @property {Function} reset Restores the original function and removes all pre-processes steps.
* @property {Function} useBefore Adds a pre-processing step func.
* @property {Function} original Original function
*/
const BEFORE_ACTION_NEXT = 'next';
const BEFORE_ACTION_SKIP_REST = 'skipRest';
const BEFORE_ACTION_STOP = 'stop';
/**
* Defines parameters to be used by the next step or mutable function.
* @param {*} args Transforms an argument list to be consumed by the next step or
* @returns {Object}
*/
export function next(...args) {
return {
action: BEFORE_ACTION_NEXT,
args
};
}
/**
* Causes the stack to skip forward to processing the mutable using the given arguments.
* @param {*} [args] A number of arguments to be passed to the mutable. This is optional.
* @returns {Object}
*/
export function skipRest(...args) {
return {
action: BEFORE_ACTION_SKIP_REST,
args
};
}
/**
* Causes the stack to stop processing any further actions. On stop only one return value possible.
* @param {*} [arg] Optional final result value.
* @returns {Object}
*/
export function stop(arg) {
return {
action: BEFORE_ACTION_STOP,
args: arg
};
}
/**
* Public api export
* @type {{next: Function, stop: Function, skipRest: Function}}
*/
export const mutableActions = {
next,
skipRest,
stop
};
/**
* Takes a function and makes it mutable.
* @template {Function} T
* @param {T} func Original function to convert to a mutable
* @returns {T}
*/
export const mutable = func => {
const original = func;
let current = func;
const steps = [];
/**
* Takes the pre-processing steps and calls all step functions, as well as the mutable function
* afterwards. Each step can transform the arguments, that are passed in to the next step in the
* stack and ultimately to the mutable function.
* @param {*} args Arguments passed down to the mutable and its pre-processor actions.
* @returns {Function} A function to be consumed by redux
*/
function mutableFunc(...args) {
let mutatedArgs = args;
let runAction = true;
// Execute pre-processing steps, if any available.
if (steps.length > 0) {
mutatedArgs = steps.reduce((acc, step, i, arr) => {
// Call next step func in the pipeline with mutated args
let res = step.apply(void 0, acc);
// Keep params unchanged if the step did not perform any change action at all
if (!isObject(res)) {
return acc;
}
// Unpack arguments from the "useBefore" pre-processor with "next" action.
if (res.action === BEFORE_ACTION_NEXT) {
if (res.args && res.args.length > 0) {
res = res.args;
} else {
res = args;
}
}
// Unpack arguments from the "useBefore" pre-processor with "skipRest" action.
if (res.action === BEFORE_ACTION_SKIP_REST) {
arr.splice(-(arr.length - i));
if (res.args && res.args.length > 0) {
res = res.args;
} else {
res = args;
}
}
// Check if the step requested to stop any further processing including the action itself
if (res.action === BEFORE_ACTION_STOP) {
// Cut off all following step functions including the mutable func
arr.splice(-(arr.length - i));
runAction = false;
res = res.args;
}
// Replace arguments for the next step to get.
return res;
}, args);
}
if (!runAction) {
// the return value here gets passed to the dispatch() so it must be a function or action obj
return noop;
}
// Call the actual mutable
return current.apply(void 0, mutatedArgs);
}
/**
* Replaces the original functionality with a custom function.
* @param {Function} customFunc The function to execute instead of the original one.
*/
mutableFunc.replace = customFunc => {
current = customFunc;
// Allow access to the current mutation.
mutableFunc.current = current;
};
/**
* Restores the original func, while keeping additional functions to be called beforehand.
*/
mutableFunc.restore = () => {
current = original;
};
/**
* Resets the whole mutable to its initial state, restoring the original functionality and
* dropping all injected functions.
*/
mutableFunc.reset = () => {
current = original;
steps.length = 0;
};
/**
* Adds a pre-processing step func to a list to be called before the actual functionality
* executes. Each step can modify the arguments, passed down to the next step or the mutable.
* @param {Function} stepFunc The function to be executed before
*/
mutableFunc.useBefore = stepFunc => {
steps.push(stepFunc);
};
// Allow access to the original function.
mutableFunc.original = original;
return mutableFunc;
};