map-transform
Version:
Map and transform objects with mapping definitions
172 lines • 6.61 kB
JavaScript
import iterate from './iterate.js';
import pipe from './pipe.js';
import { set } from './getSet.js';
import { divide, fwd, rev } from './directionals.js';
import plug from './plug.js';
import { getStateValue, setStateValue, getTargetFromState, setTargetOnState, setValueFromState, isNonvalueState, stopIteration, } from '../utils/stateHelpers.js';
import { isTransformObject, isTransformDefinition, defToOperation, } from '../utils/definitionHelpers.js';
import { noopNext } from '../utils/stateHelpers.js';
import { isObject, isNotNullOrUndefined } from '../utils/is.js';
function pathHasModify(path) {
const index = path.indexOf('$modify');
return (index > -1 &&
(index === 0 || path[index - 1] === '.') &&
(path.length === index + 7 || path[index + 7] === '.'));
}
function isPathWithModify(pipeline) {
if (Array.isArray(pipeline)) {
return pipeline.some(isPathWithModify);
}
else if (typeof pipeline !== 'string') {
return false;
}
else {
return pathHasModify(pipeline);
}
}
function isRegularProp(entry) {
const [prop, pipeline] = entry;
return ((prop[0] !== '$' ||
(isPathWithModify(prop) && !isPathWithModify(pipeline))) &&
isTransformDefinition(pipeline));
}
function sortProps([aProp, aPipeline], [bProp, bPipeline]) {
const aIsModify = isPathWithModify(aProp) || isPathWithModify(aPipeline);
const bIsModify = isPathWithModify(bProp) || isPathWithModify(bPipeline);
return Number(aIsModify) - Number(bIsModify);
}
const checkDirection = (requiredDirection, directionKeyword, directionAlias) => requiredDirection === directionKeyword ||
(directionAlias && requiredDirection === directionAlias);
const resolveDirection = (direction, options) => checkDirection(direction, 'rev', options.revAlias)
? true
: checkDirection(direction, 'fwd', options.fwdAlias)
? false
: undefined;
function wrapInDirectional(operation, direction) {
return (options) => {
const isRev = resolveDirection(direction, options);
if (isRev === undefined) {
return operation(options);
}
else {
const wrapOp = isRev ? rev : fwd;
return wrapOp(operation)(options);
}
};
}
const mergeTargetAndValueOperation = () => (next) => async function mergeTargetAndValue(state) {
const nextState = await next(state);
const target = getTargetFromState(nextState);
const value = getStateValue(nextState);
return isObject(target) && isObject(value)
? setStateValue(nextState, { ...target, ...value })
: nextState;
};
function runOperationWithOriginalValue({ value }) {
return async (state, fn) => {
const nextState = await fn(setStateValue(state, value));
const target = getTargetFromState(state);
const nextValue = getStateValue(nextState);
const thisState = setTargetOnState(nextState, nextValue);
if (isObject(target) && !isObject(nextValue)) {
return setStateValue(thisState, target);
}
else {
return thisState;
}
};
}
const isArr = (prop) => prop.endsWith('[]') && prop[prop.length - 3] !== '\\';
const isNumeric = (value) => !Number.isNaN(Number.parseInt(value, 10));
function removeSlash(prop) {
const index = prop.indexOf('/');
if (index > -1 &&
prop[index - 1] !== '\\' &&
isNumeric(prop.slice(index + 1))) {
return prop.slice(0, index);
}
return prop;
}
function createDirectionalOperation(pipeline, onlyFwd, onlyRev) {
if (onlyRev && onlyFwd) {
return undefined;
}
else if (onlyRev) {
return divide(plug(), pipeline, true);
}
else if (onlyFwd) {
return divide(pipeline, plug(), true);
}
else {
return pipe(pipeline);
}
}
const createSetPipeline = (options) => function createSetPipeline([prop, pipeline]) {
if (isTransformObject(pipeline)) {
pipeline = [
rev(mergeTargetAndValueOperation),
{
...pipeline,
$iterate: pipeline.$iterate || isArr(prop),
},
];
}
const unslashedProp = removeSlash(prop);
const isSlashed = prop !== unslashedProp;
const onlyFwd = isPathWithModify(unslashedProp);
const onlyRev = isSlashed || isPathWithModify(pipeline);
const operations = [defToOperation(pipeline, options), set(unslashedProp)];
return createDirectionalOperation(operations, onlyFwd, onlyRev);
};
const runOperations = (stateMappers, options) => async (state) => {
if (isNonvalueState(state, options.nonvalues)) {
return state;
}
else {
const run = runOperationWithOriginalValue(state);
let nextState = state;
for (const stateMapper of stateMappers) {
nextState = await run(nextState, stateMapper(noopNext));
}
return nextState;
}
};
const setStateProps = (state, noDefaults, flip) => ({
...state,
noDefaults: noDefaults || state.noDefaults || false,
flip: flip || state.flip || false,
target: undefined,
});
const fixModifyPath = (def) => def.$modify === true ? { ...def, $modify: '.' } : def;
const createStateMappers = (def, options) => Object.entries(def)
.filter(isRegularProp)
.sort(sortProps)
.map(createSetPipeline(options))
.filter(isNotNullOrUndefined)
.map((fn) => fn(options));
function prepareOperation(def) {
return (options) => {
const nextStateMappers = createStateMappers(fixModifyPath(def), options);
if (nextStateMappers.length === 0) {
return (next) => async (state) => setStateValue(await next(state), {});
}
const run = runOperations(nextStateMappers, options);
const runWithIterateWhenNeeded = def.$iterate === true ? iterate(() => () => run)(options)(noopNext) : run;
return (next) => {
return async function doMutate(state) {
const nextState = await next(state);
if (isNonvalueState(nextState, options.nonvalues)) {
return nextState;
}
const propsState = stopIteration(setStateProps(nextState, def.$noDefaults, def.$flip));
const thisState = await runWithIterateWhenNeeded(propsState);
return setValueFromState(nextState, thisState);
};
};
};
}
export default function props(def) {
const operation = prepareOperation(def);
return wrapInDirectional(operation, def.$direction);
}
//# sourceMappingURL=props.js.map