UNPKG

map-transform

Version:

Map and transform objects with mapping definitions

172 lines 6.61 kB
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