map-transform
Version:
Map and transform objects with mapping definitions
294 lines • 10.1 kB
JavaScript
import mapAny from 'map-any';
import modify from './modify.js';
import { getStateValue, setStateValue, getTargetFromState, setTargetOnState, getLastContext, getRootFromState, isNonvalue, revFromState, clearUntouched, } from '../utils/stateHelpers.js';
import { isObject } from '../utils/is.js';
import { ensureArray, indexOfIfArray } from '../utils/array.js';
const adjustIsSet = (isSet, state) => revFromState(state, isSet);
function flatMapAny(fn) {
return (value, target) => Array.isArray(value)
? value.flatMap((value) => fn(value, target))
: fn(value, target);
}
function handleArrayPath(path) {
if (path.endsWith('][')) {
return [path.slice(0, path.length - 2), false, true];
}
const pos = path.indexOf('[');
if (path[pos - 1] === '\\') {
return [path.replace('\\[', '['), false, false];
}
else {
const isArr = path[pos + 1] === ']';
return [path.slice(0, pos), isArr, false];
}
}
function preparePath(path) {
if (typeof path === 'string') {
if (path.includes('[')) {
return handleArrayPath(path);
}
else if (path.startsWith('\\$')) {
return [path.slice(1), false, false];
}
}
return [path, false, false];
}
function getSetProp(path) {
if (path === '') {
return (value) => value;
}
const getFn = flatMapAny((value) => isObject(value) ? value[path] : undefined);
const setFn = (value, target) => isObject(target) ? { ...target, [path]: value } : { [path]: value };
return (value, isSet, target) => {
if (isSet) {
return setFn(value, target);
}
else {
return getFn(value);
}
};
}
const calculateIndex = (index, arr) => index >= 0 ? index : arr.length + index;
function getSetIndex(index) {
return (value, isSet, target) => {
if (isSet) {
const arr = Array.isArray(target) ? [...target] : [];
arr[calculateIndex(index, arr)] = value;
return arr;
}
else {
return Array.isArray(value)
? value[calculateIndex(index, value)]
: undefined;
}
};
}
function getParent(state) {
const nextValue = getLastContext(state);
const nextContext = state.context.slice(0, -1);
return { ...state, context: nextContext, value: nextValue };
}
function getRoot(state) {
const nextValue = getRootFromState(state);
return { ...state, context: [], value: nextValue };
}
function getSetParentOrRoot(path, isSet) {
const getFn = path[1] === '^' ? getRoot : getParent;
return () => (next) => async (state) => {
const nextState = await next(state);
if (adjustIsSet(isSet, state)) {
return setStateValue(nextState, state.target);
}
else {
return getFn(nextState);
}
};
}
const modifyOnSet = (isSet) => (options) => function modifyOnSet(next) {
const modifyFn = modify('.')(options)(next);
return async (state) => {
return adjustIsSet(isSet, state)
? await modifyFn(state)
: setStateValue(await next(state), undefined);
};
};
function doModifyGetValue(value, state, options) {
const { modifyGetValue } = options;
return typeof modifyGetValue === 'function'
? modifyGetValue(value, state, options)
: value;
}
function getSet(isSet = false) {
return (path) => {
if (typeof path === 'string') {
if (path === '$modify') {
return modifyOnSet(isSet);
}
else if (path[0] === '^') {
return getSetParentOrRoot(path, isSet);
}
}
const [basePath, isArr, isIndexProp] = preparePath(path);
const isIndex = typeof basePath === 'number';
const getSetFn = isIndex ? getSetIndex(basePath) : getSetProp(basePath);
return (options) => (next) => async function doGetSet(state) {
if (adjustIsSet(isSet, state)) {
const target = getTargetFromState(state);
const nextTarget = getSetFn(target, false);
const nextState = await next(setTargetOnState({ ...state, iterate: state.iterate || isArr }, nextTarget));
const setIt = (value, index) => getSetFn(value, true, indexOfIfArray(target, index));
const nextValue = getStateValue(nextState);
if (state.noDefaults && isNonvalue(nextValue, options.nonvalues)) {
return setStateValue(state, target);
}
const value = isArr
? ensureArray(nextValue, options.nonvalues)
: nextValue;
const thisValue = nextState.iterate && !isArr && !isIndexProp
? mapAny(setIt, value)
: setIt(value);
return setStateValue(state, thisValue);
}
else {
const nextState = await next(state);
const thisValue = getSetFn(getStateValue(nextState), false);
const modifiedValue = doModifyGetValue(thisValue, nextState, options);
const value = state.noDefaults && isNonvalue(modifiedValue, options.nonvalues)
? undefined
: isArr
? ensureArray(modifiedValue, options.nonvalues)
: modifiedValue;
return setStateValue(nextState, value, true);
}
};
};
}
function resolveArrayNotation(path, pos) {
const index = Number.parseInt(path.slice(pos + 1), 10);
if (!Number.isNaN(index)) {
const basePath = path.slice(0, pos).trim();
return basePath ? [`${basePath}][`, index] : [index];
}
else {
return path.trim();
}
}
function resolveParentNotation(path) {
if (path.startsWith('^^') && path.length > 2) {
return ['^^', path.slice(2).trim()];
}
else if (path.length > 1 && path !== '^^') {
return ['^^', path.slice(1).trim()];
}
else {
return path.trim();
}
}
function splitUpArrayAndParentNotation(path) {
const pos = path.indexOf('[');
if (pos > -1 && path[pos - 1] !== '\\') {
return resolveArrayNotation(path, pos);
}
else if (path.startsWith('^')) {
return resolveParentNotation(path);
}
else {
return path.trim();
}
}
function pathToNextOperations(path, isSet = false) {
if (!path || path === '.') {
return [
() => (next) => async (state) => clearUntouched(await next(state)),
];
}
if (path[0] === '>') {
path = path.slice(1);
isSet = true;
}
const parts = path.split('.').flatMap(splitUpArrayAndParentNotation);
const operations = parts.map(getSet(isSet));
if (isSet) {
operations.reverse();
}
return operations;
}
const getByPart = (part, isArr) => (value) => {
if (typeof part === 'string') {
if (isObject(value)) {
const nextValue = value[part];
return isArr ? ensureArray(nextValue) : nextValue;
}
}
else if (typeof part === 'number' && Array.isArray(value)) {
return value[calculateIndex(part, value)];
}
return isArr ? [] : undefined;
};
function prepareGetFn([part, isArr]) {
if (typeof part === 'string' && part[0] === '^') {
const isRoot = part[1] === '^';
return (_value, state) => {
const nextState = isRoot ? getRoot(state) : getParent(state);
return [nextState.value, nextState];
};
}
else if (typeof part === 'number') {
const fn = getByPart(part, isArr);
return (value) => [fn(value), undefined];
}
else {
const fn = flatMapAny(getByPart(part, isArr));
return (value) => [fn(value), undefined];
}
}
const setOnObject = (part) => (value) => part ? { [part]: value } : value;
const setByPart = (part, isArr, doIterate) => (value) => {
const data = isArr ? ensureArray(value) : value;
if (typeof part === 'number') {
const arr = [];
const index = part < 0 ? 0 : part;
arr[index] = data;
return arr;
}
else {
return doIterate
? mapAny(setOnObject(part), data)
: setOnObject(part)(data);
}
};
function getDoIterateFromLastPart(parts) {
if (parts.length === 0) {
return false;
}
const lastPart = parts[parts.length - 1];
return lastPart[1] || lastPart[2];
}
const setDoIterateOnParts = (parts, [part, isArr]) => [
...parts,
[part, isArr, isArr ? false : getDoIterateFromLastPart(parts)],
];
export function pathGetter(path, _options = {}) {
if (!path || path === '.') {
return (value) => value;
}
const parts = path
.split('.')
.flatMap(splitUpArrayAndParentNotation)
.map(preparePath)
.map(prepareGetFn);
return function getFromPath(value, startState) {
let data = value;
let state = startState;
for (const partOrGetFn of parts) {
;
[data, state = state] = partOrGetFn(data, state);
}
return data;
};
}
export function pathSetter(path, options = {}) {
if (typeof path !== 'string' || path === '' || path === '.') {
return (value) => value;
}
else if (path[0] === '^') {
return () => undefined;
}
const setFns = path
.split('.')
.flatMap(splitUpArrayAndParentNotation)
.map(preparePath)
.reduce(setDoIterateOnParts, [])
.map(([part, isArr, doIterate]) => setByPart(part, isArr, doIterate));
return function setToPath(value, state) {
if (state.noDefaults && isNonvalue(value, options.nonvalues)) {
return undefined;
}
else {
return setFns.reduceRight((value, setFn) => setFn(value), value);
}
};
}
export const get = (path) => pathToNextOperations(path, false);
export const set = (path) => pathToNextOperations(path, true);
//# sourceMappingURL=getSet.js.map