map-transform-cjs
Version:
MapTransform with CJS support
1 lines • 93.5 kB
Source Map (JSON)
{"version":3,"sources":["../../src/operations/root.ts","../../src/utils/xor.ts","../../src/utils/stateHelpers.ts","../../src/utils/modifyOperationObject.ts","../../src/utils/is.ts","../../src/operations/getSet.ts","../../src/operations/modify.ts","../../src/utils/array.ts","../../src/operations/iterate.ts","../../src/operations/plug.ts","../../src/operations/props.ts","../../src/operations/transform.ts","../../src/operations/filter.ts","../../src/operations/ifelse.ts","../../src/operations/apply.ts","../../src/operations/alt.ts","../../src/operations/merge.ts","../../src/operations/concat.ts","../../src/operations/lookup.ts","../../src/utils/escape.ts","../../src/utils/definitionHelpers.ts","../../src/operations/directionals.ts","../../src/transformers/flatten.ts","../../src/operations/pipe.ts"],"sourcesContent":["import pipe from './pipe.js'\nimport { get } from './getSet.js'\nimport { defToOperations } from '../utils/definitionHelpers.js'\nimport type { TransformDefinition, Operation } from '../types.js'\n\nexport default function (def: TransformDefinition): Operation {\n return (options) => {\n const pipeline = [get('^^'), defToOperations(def, options)].flat()\n return pipe(pipeline)(options)\n }\n}\n","export default function xor(a = false, b = false) {\n return a ? !b : b\n}\n","import xor from './xor.js'\nimport type { State, InitialState } from '../types.js'\n\n// Context\n\nexport const getLastContext = (state: State) =>\n state.context[state.context.length - 1]\n\nexport const removeLastContext = (state: State) => ({\n ...state,\n context: state.context.slice(0, -1),\n})\n\nexport const pushContext = (state: State, value: unknown) => ({\n ...state,\n context: [...state.context, value],\n})\n\nexport const popContext = (state: State) => ({\n ...state,\n context: state.context.slice(0, -1),\n value: state.context[state.context.length - 1],\n})\n\n// Root\n\nexport const getRootFromState = (state: State) =>\n state.context.length === 0 ? state.value : state.context[0]\n\n// Target\n\nexport const getTargetFromState = (state: State) => state.target\n\nexport function setTargetOnState(state: State, target: unknown): State {\n return {\n ...state,\n target,\n }\n}\n\n// State value\n\nexport const setStateValue = (\n { untouched, ...state }: State, // Clear untouched every time we set a value\n value: unknown,\n shouldPushContext = false,\n): State =>\n shouldPushContext\n ? {\n ...pushContext(state, state.value),\n value,\n }\n : { ...state, value }\n\nexport const getStateValue = (state: State): unknown => state.value\n\nexport const setValueFromState = (\n state: State,\n { value }: State,\n shouldPushContext = false,\n): State => setStateValue(state, value, shouldPushContext)\n\nexport const isNonvalue = (\n value: unknown,\n nonvalues: unknown[] = [undefined],\n) => nonvalues.includes(value)\n\nexport const isNonvalueState = (\n state: State,\n nonvalues: unknown[] = [undefined],\n) => isNonvalue(state.value, nonvalues)\n\nexport const markAsUntouched = (state: State) => ({ ...state, untouched: true })\nexport const clearUntouched = ({ untouched, ...state }: State) => state\nexport const isUntouched = ({ untouched }: State) => !!untouched\n\n// State\n\nexport const populateState = (\n data: unknown,\n { rev = false, noDefaults = false, target = undefined }: InitialState,\n): State => ({\n context: [],\n value: data,\n target,\n rev,\n noDefaults,\n})\n\nexport const goForward = (state: State) =>\n state.rev || state.flip\n ? {\n ...state,\n rev: false,\n flip: false,\n }\n : state\n\nexport const flipState = (state: State, flip = true) => ({\n ...state,\n flip: xor(state.flip, flip),\n})\n\nexport const stopIteration = (state: State) => ({ ...state, iterate: false })\n\nexport const noopNext = async (state: State) => state\n\nexport const revFromState = (state: State, flip = false) =>\n flip ? xor(state.rev, !state.flip) : xor(state.rev, state.flip)\n","const createTransformWithPath = (\n $transform: string,\n path: unknown,\n operator?: string\n) => ({\n $transform,\n path,\n ...(operator && { operator }),\n})\n\nconst createValueTransform = ({\n $value,\n ...rest\n}: Record<string, unknown>) => ({\n ...rest,\n $transform: 'value',\n value: $value,\n})\n\nconst createAndTransform = ({ $and, ...rest }: Record<string, unknown>) => ({\n ...rest,\n ...createTransformWithPath('logical', $and, 'AND'),\n})\n\nconst createOrTransform = ({ $or, ...rest }: Record<string, unknown>) => ({\n ...rest,\n ...createTransformWithPath('logical', $or, 'OR'),\n})\n\nconst createNotTransform = ({ $not, ...rest }: Record<string, unknown>) => ({\n ...rest,\n ...createTransformWithPath('not', $not),\n})\n\nconst createMergeTransform = ({\n $merge,\n ...rest\n}: Record<string, unknown>) => ({\n ...rest,\n ...createTransformWithPath('merge', $merge),\n})\n\nexport default function modifyOperationObject(\n rawOperation: Record<string, unknown>,\n modify?: (operation: Record<string, unknown>) => Record<string, unknown>\n): Record<string, unknown> {\n const operation =\n typeof modify === 'function' ? modify(rawOperation) : rawOperation\n\n if (operation.hasOwnProperty('$value')) {\n return createValueTransform(operation)\n } else if (operation.hasOwnProperty('$and')) {\n return createAndTransform(operation)\n } else if (operation.hasOwnProperty('$or')) {\n return createOrTransform(operation)\n } else if (operation.hasOwnProperty('$not')) {\n return createNotTransform(operation)\n } else if (operation.hasOwnProperty('$merge')) {\n return createMergeTransform(operation)\n }\n\n return operation\n}\n","import type { Path } from '../types.js'\n\nexport const isObject = (value: unknown): value is Record<string, unknown> =>\n Object.prototype.toString.call(value) === '[object Object]'\n\nexport const isString = (value: unknown): value is string =>\n typeof value === 'string'\n\nexport const isPath = (value: unknown): value is Path =>\n typeof value === 'string'\n\nexport const isArrayPath = (value: unknown): value is Path =>\n isPath(value) && value.endsWith('[]')\n\nexport const isNullOrUndefined = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\nexport const isNotNullOrUndefined = <T>(value: T): value is NonNullable<T> =>\n !isNullOrUndefined(value)\n\nexport const isNonEmptyArray = <T>(value: unknown): value is T[] =>\n Array.isArray(value) && value.length > 0\n","/* eslint-disable security/detect-object-injection */\nimport mapAny from 'map-any-cjs'\nimport modify from './modify.js'\nimport {\n getStateValue,\n setStateValue,\n getTargetFromState,\n setTargetOnState,\n getLastContext,\n getRootFromState,\n isNonvalue,\n revFromState,\n clearUntouched,\n} from '../utils/stateHelpers.js'\nimport { isObject } from '../utils/is.js'\nimport { ensureArray, indexOfIfArray } from '../utils/array.js'\nimport type {\n Path,\n Operation,\n State,\n StateMapper,\n DataMapperWithState,\n Options,\n} from '../types.js'\n\nconst adjustIsSet = (isSet: boolean, state: State) => revFromState(state, isSet) // `isSet` will work as `flip` here\n\nfunction flatMapAny(fn: (value: unknown, target?: unknown) => unknown) {\n return (value: unknown, target?: unknown) =>\n Array.isArray(value)\n ? value.flatMap((value) => fn(value, target))\n : fn(value, target)\n}\n\nfunction handleArrayPath(path: string): [string | number, boolean, boolean] {\n if (path.endsWith('][')) {\n // This is an index notation\n return [path.slice(0, path.length - 2), false, true /* isIndexProp */]\n }\n const pos = path.indexOf('[')\n if (path[pos - 1] === '\\\\') {\n // We have an escaped [, return the path with the backslash removed\n return [path.replace('\\\\[', '['), false, false]\n } else {\n // This is an array notation if the next char is ]\n const isArr = path[pos + 1] === ']'\n return [path.slice(0, pos), isArr, false]\n }\n}\n\n// Get rid of some special characters in the path and return the clean path and\n// some flags to indicate if we're dealing with an array or index notation\nfunction preparePath(\n path: string | number,\n): [string | number, boolean, boolean] {\n if (typeof path === 'string') {\n if (path.includes('[')) {\n // We have an array notation\n return handleArrayPath(path)\n } else if (path.startsWith('\\\\$')) {\n // This is an escaped $, remove the backslash and return the path with $\n return [path.slice(1), false, false]\n }\n }\n\n // We have just a plain path\n return [path, false, false]\n}\n\nfunction getSetProp(path: string) {\n if (path === '') {\n // We have an empty path, return the value as is\n return (value: unknown) => value\n }\n\n const getFn = flatMapAny((value) =>\n isObject(value) ? value[path] : undefined,\n )\n const setFn = (value: unknown, target?: unknown) =>\n isObject(target) ? { ...target, [path]: value } : { [path]: value }\n\n return (value: unknown, isSet: boolean, target?: unknown) => {\n if (isSet) {\n return setFn(value, target)\n } else {\n return getFn(value)\n }\n }\n}\n\nconst calculateIndex = (index: number, arr: unknown[]) =>\n index >= 0 ? index : arr.length + index\n\nfunction getSetIndex(index: number) {\n return (value: unknown, isSet: boolean, target?: unknown) => {\n if (isSet) {\n const arr = Array.isArray(target) ? [...target] : []\n arr[calculateIndex(index, arr)] = value\n return arr\n } else {\n return Array.isArray(value)\n ? value[calculateIndex(index, value)]\n : undefined\n }\n }\n}\n\nfunction getParent(state: State) {\n const nextValue = getLastContext(state)\n const nextContext = state.context.slice(0, -1)\n return { ...state, context: nextContext, value: nextValue }\n}\n\nfunction getRoot(state: State) {\n const nextValue = getRootFromState(state)\n return { ...state, context: [], value: nextValue }\n}\n\nfunction getSetParentOrRoot(path: string, isSet: boolean): Operation {\n const getFn = path[1] === '^' ? getRoot : getParent\n return () => (next) => async (state) => {\n const nextState = await next(state)\n if (adjustIsSet(isSet, state)) {\n // Simple return target instead of setting anything\n return setStateValue(nextState, state.target)\n } else {\n // Get root or parent\n return getFn(nextState)\n }\n }\n}\n\nconst modifyOnSet =\n (isSet: boolean): Operation =>\n (options) =>\n function modifyOnSet(next) {\n const modifyFn = modify('.')(options)(next)\n return async (state) => {\n return adjustIsSet(isSet, state)\n ? await modifyFn(state) // Modify when we're setting\n : setStateValue(await next(state), undefined) // Return undefined value when we're getting\n }\n }\n\nfunction doModifyGetValue(value: unknown, state: State, options: Options) {\n const { modifyGetValue } = options\n return typeof modifyGetValue === 'function'\n ? modifyGetValue(value, state, options)\n : value\n}\n\nfunction getSet(isSet = false) {\n return (path: string | number): Operation => {\n if (typeof path === 'string') {\n if (path === '$modify') {\n return modifyOnSet(isSet)\n } else if (path[0] === '^') {\n return getSetParentOrRoot(path, isSet)\n }\n }\n\n const [basePath, isArr, isIndexProp] = preparePath(path)\n const isIndex = typeof basePath === 'number'\n const getSetFn = isIndex ? getSetIndex(basePath) : getSetProp(basePath)\n\n return (options) => (next) =>\n async function doGetSet(state) {\n if (adjustIsSet(isSet, state)) {\n // Set\n // We'll go backwards first. Start by preparing target for the next set\n const target = getTargetFromState(state)\n const nextTarget = getSetFn(target, false)\n\n // Invoke the \"previous\" path part with the right target, iterate if array\n const nextState = await next(\n setTargetOnState(\n { ...state, iterate: state.iterate || isArr },\n nextTarget,\n ),\n )\n\n // Now it's our turn. Set the state value - and iterate if necessary\n const setIt = (value: unknown, index?: number) =>\n getSetFn(value, true, indexOfIfArray(target, index))\n\n const nextValue = getStateValue(nextState)\n if (state.noDefaults && isNonvalue(nextValue, options.nonvalues)) {\n return setStateValue(state, target)\n }\n const value = isArr\n ? ensureArray(nextValue, options.nonvalues)\n : nextValue\n\n const thisValue =\n nextState.iterate && !isArr && !isIndexProp\n ? mapAny(setIt, value)\n : setIt(value)\n\n // Return the value\n return setStateValue(state, thisValue)\n } else {\n // Get\n // Go backwards\n const nextState = await next(state)\n const thisValue = getSetFn(getStateValue(nextState), false)\n const modifiedValue = doModifyGetValue(thisValue, nextState, options)\n\n const value =\n state.noDefaults && isNonvalue(modifiedValue, options.nonvalues)\n ? undefined\n : isArr\n ? ensureArray(modifiedValue, options.nonvalues)\n : modifiedValue\n\n return setStateValue(\n nextState,\n value,\n true, // Push to context\n )\n }\n }\n }\n}\n\nfunction resolveArrayNotation(path: string, pos: number) {\n const index = Number.parseInt(path.slice(pos + 1), 10)\n if (!Number.isNaN(index)) {\n const basePath = path.slice(0, pos).trim()\n return basePath ? [`${basePath}][`, index] : [index] // `][` is our crazy notation for an index prop\n } else {\n return path.trim()\n }\n}\n\nfunction resolveParentNotation(path: string) {\n if (path.startsWith('^^') && path.length > 2) {\n return ['^^', path.slice(2).trim()]\n } else if (path.length > 1 && path !== '^^') {\n return ['^^', path.slice(1).trim()]\n } else {\n return path.trim()\n }\n}\n\nfunction splitUpArrayAndParentNotation(path: string) {\n const pos = path.indexOf('[')\n if (pos > -1 && path[pos - 1] !== '\\\\') {\n return resolveArrayNotation(path, pos)\n } else if (path.startsWith('^')) {\n return resolveParentNotation(path)\n } else {\n return path.trim()\n }\n}\n\n// Prepare a get or set path, depending on isSet. It's essentially the same,\n// only with reversed order and setting or getting by default. When ran in\n// reverse mode, it will run the other way.\nfunction pathToNextOperations(path: Path, isSet = false): Operation[] {\n if (!path || path === '.') {\n return [\n () => (next: StateMapper) => async (state: State) =>\n clearUntouched(await next(state)), // We don't change the value, but we still need to clear untouched\n ]\n }\n\n // Treat as a set if it starts with >\n if (path[0] === '>') {\n path = path.slice(1)\n isSet = true\n }\n\n // Split the path into parts, and get the operations for each part\n const parts = path.split('.').flatMap(splitUpArrayAndParentNotation)\n const operations = parts.map(getSet(isSet))\n\n // Reverse order when we're setting\n if (isSet) {\n operations.reverse()\n }\n\n return operations\n}\n\nconst getByPart =\n (part: string | number, isArr: boolean) => (value: unknown) => {\n if (typeof part === 'string') {\n if (isObject(value)) {\n const nextValue = value[part]\n return isArr ? ensureArray(nextValue) : nextValue\n }\n } else if (typeof part === 'number' && Array.isArray(value)) {\n return value[calculateIndex(part, value)]\n }\n return isArr ? [] : undefined\n }\n\nfunction prepareGetFn([part, isArr]: [string | number, boolean, boolean]): (\n value: unknown,\n state: State,\n) => [unknown, State | undefined] {\n if (typeof part === 'string' && part[0] === '^') {\n // This part is a parent or a root, so we'll return the value and the next state\n const isRoot = part[1] === '^'\n return (_value, state) => {\n const nextState = isRoot ? getRoot(state) : getParent(state)\n return [nextState.value, nextState]\n }\n } else if (typeof part === 'number') {\n // This is an index part\n const fn = getByPart(part, isArr)\n return (value) => [fn(value), undefined]\n } else {\n // This is a regular part\n const fn = flatMapAny(getByPart(part, isArr))\n return (value) => [fn(value), undefined]\n }\n}\n\nconst setOnObject = (part: string) => (value: unknown) =>\n part ? { [part]: value } : value\n\nconst setByPart =\n (part: string | number, isArr: boolean, doIterate: boolean) =>\n (value: unknown) => {\n const data = isArr ? ensureArray(value) : value\n if (typeof part === 'number') {\n const arr = []\n const index = part < 0 ? 0 : part // Negative index will always be 0\n arr[index] = data\n return arr\n } else {\n return doIterate\n ? mapAny(setOnObject(part), data)\n : setOnObject(part)(data)\n }\n }\n\nfunction getDoIterateFromLastPart(\n parts: [string | number, boolean, boolean][],\n) {\n if (parts.length === 0) {\n return false\n }\n\n const lastPart = parts[parts.length - 1]\n return lastPart[1] || lastPart[2]\n}\n\nconst setDoIterateOnParts = (\n parts: [string | number, boolean, boolean][],\n [part, isArr]: [string | number, boolean, boolean],\n): [string | number, boolean, boolean][] => [\n ...parts,\n [part, isArr, isArr ? false : getDoIterateFromLastPart(parts)],\n]\n\n/**\n * Get a value at the given path.\n */\nexport function pathGetter(\n path?: string | null,\n _options: Options = {},\n): DataMapperWithState {\n if (!path || path === '.') {\n return (value) => value\n }\n\n const parts = path\n .split('.')\n .flatMap(splitUpArrayAndParentNotation)\n .map(preparePath)\n .map(prepareGetFn)\n\n return function getFromPath(value, startState) {\n let data = value\n let state = startState\n for (const partOrGetFn of parts) {\n ;[data, state = state] = partOrGetFn(data, state)\n }\n return data\n }\n}\n\n/**\n * Set a value to the given path.\n */\nexport function pathSetter(\n path?: string | null,\n options: Options = {},\n): DataMapperWithState {\n if (typeof path !== 'string' || path === '' || path === '.') {\n return (value) => value // Just return the value\n } else if (path[0] === '^') {\n return () => undefined // We won't set on parent or root\n }\n\n const setFns = path\n .split('.')\n .flatMap(splitUpArrayAndParentNotation)\n .map(preparePath)\n .reduce(setDoIterateOnParts, [])\n .map(([part, isArr, doIterate]) => setByPart(part, isArr, doIterate))\n\n return function setToPath(value, state) {\n if (state.noDefaults && isNonvalue(value, options.nonvalues)) {\n // We should not set a nonvalue, and this is a nonvalue, so return `undefined`\n return undefined\n } else {\n // Set on path by running the parts in reverse order (from the innermost to the outermost)\n return setFns.reduceRight((value, setFn) => setFn(value), value)\n }\n }\n}\n\n/**\n * Run a get path.\n */\nexport const get = (path: Path) => pathToNextOperations(path, false)\n\n/**\n * Run a set path.\n */\nexport const set = (path: Path) => pathToNextOperations(path, true)\n","import type { Operation, TransformDefinition } from '../types.js'\nimport {\n getStateValue,\n setStateValue,\n getTargetFromState,\n goForward,\n} from '../utils/stateHelpers.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { isObject } from '../utils/is.js'\nimport { noopNext } from '../utils/stateHelpers.js'\n\nexport default function modify(def: TransformDefinition): Operation {\n return (options) => {\n const runFn = defToOperation(def, options)\n return (next) => async (state) => {\n const nextState = await next(state)\n const thisState = await runFn(options)(noopNext)(goForward(nextState))\n\n const target = getTargetFromState(nextState)\n const value = getStateValue(thisState)\n\n return setStateValue(\n nextState,\n isObject(target) && isObject(value)\n ? { ...value, ...target }\n : isObject(value)\n ? value\n : target,\n )\n }\n }\n}\n","import { isNonvalue } from '../utils/stateHelpers.js'\n\nexport const ensureArray = <T = unknown>(\n value: T | T[],\n nonvalues?: unknown[]\n): T[] =>\n Array.isArray(value) ? value : isNonvalue(value, nonvalues) ? [] : [value]\n\nexport const cloneAsArray = (value: unknown) => ensureArray(value).slice()\n\nexport const indexOfIfArray = (arr: unknown, index?: number) =>\n Array.isArray(arr) && typeof index === 'number' ? arr[index] : arr // eslint-disable-line security/detect-object-injection\n","import type {\n Operation,\n TransformDefinition,\n State,\n StateMapper,\n} from '../types.js'\nimport {\n pushContext,\n getStateValue,\n setStateValue,\n getTargetFromState,\n setTargetOnState,\n} from '../utils/stateHelpers.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { indexOfIfArray } from '../utils/array.js'\nimport { noopNext } from '../utils/stateHelpers.js'\n\nconst runIterationStep = async (\n fn: StateMapper,\n state: State,\n value: unknown,\n index: number,\n target: unknown,\n) =>\n getStateValue(\n await fn(\n setTargetOnState(\n { ...state, index, value },\n indexOfIfArray(target, index),\n ),\n ),\n )\n\nexport const iterateState =\n (fn: StateMapper) => async (state: State, target: unknown) => {\n const values = getStateValue(state)\n\n if (!Array.isArray(values)) {\n // This is not an array, so we just run the mapper function on the value\n return await runIterationStep(fn, state, values, 0, target)\n }\n\n const nextState = pushContext(state, values)\n const nextValue: unknown[] = []\n for (const [index, value] of values.entries()) {\n // Iterate through the values and run the mapper function on each value\n nextValue.push(\n await runIterationStep(fn, nextState, value, index, target),\n )\n }\n return nextValue\n }\n\nexport default function iterate(def: TransformDefinition): Operation {\n if (!def || (typeof def === 'object' && Object.keys(def).length === 0)) {\n return (_options) => (next) => async (state) =>\n setStateValue(await next(state), undefined)\n }\n return (options) => {\n const fn = defToOperation(def, options)(options)\n return (next) => {\n const runIteration = iterateState(fn(noopNext))\n return async function doIterate(state) {\n const nextState = await next(state)\n\n return setStateValue(\n nextState,\n await runIteration(nextState, getTargetFromState(nextState)),\n )\n }\n }\n }\n}\n","import type { Operation } from '../types.js'\nimport { setStateValue, getTargetFromState } from '../utils/stateHelpers.js'\n\nexport default function plug(): Operation {\n return () => (_next) => async (state) =>\n setStateValue(state, getTargetFromState(state))\n}\n","import iterate from './iterate.js'\nimport pipe from './pipe.js'\nimport { set } from './getSet.js'\nimport { divide, fwd, rev } from './directionals.js'\nimport plug from './plug.js'\nimport {\n getStateValue,\n setStateValue,\n getTargetFromState,\n setTargetOnState,\n setValueFromState,\n isNonvalueState,\n stopIteration,\n} from '../utils/stateHelpers.js'\nimport {\n isTransformObject,\n isTransformDefinition,\n defToOperation,\n} from '../utils/definitionHelpers.js'\nimport { noopNext } from '../utils/stateHelpers.js'\nimport { isObject, isNotNullOrUndefined } from '../utils/is.js'\nimport type {\n Operation,\n State,\n TransformObject,\n Options,\n TransformDefinition,\n StateMapper,\n NextStateMapper,\n Pipeline,\n} from '../types.js'\n\nfunction pathHasModify(path: string) {\n const index = path.indexOf('$modify')\n return (\n index > -1 && // We have a $modify\n (index === 0 || path[index - 1] === '.') && // It's either the first char, or preceded by a dot\n (path.length === index + 7 || path[index + 7] === '.') // It's either the last char, or followed by a dot\n )\n}\n\nfunction isPathWithModify(pipeline: unknown) {\n if (Array.isArray(pipeline)) {\n return pipeline.some(isPathWithModify)\n } else if (typeof pipeline !== 'string') {\n return false\n } else {\n return pathHasModify(pipeline)\n }\n}\n\n// Keep props that don't start with a $ and have a transform definition as\n// value. We'll also keep props with a `$modify` path, unless it also have a\n// `$modify` path in the pipeline, in which case it won't do anything anyway, so\n// we remove it.\nfunction isRegularProp(\n entry: [string, unknown]\n): entry is [string, TransformDefinition] {\n const [prop, pipeline] = entry\n return (\n (prop[0] !== '$' ||\n (isPathWithModify(prop) && !isPathWithModify(pipeline))) &&\n isTransformDefinition(pipeline)\n )\n}\n\n// Sort props and pipelines with a $modify path last\nfunction sortProps(\n [aProp, aPipeline]: [string, unknown],\n [bProp, bPipeline]: [string, unknown]\n) {\n const aIsModify = isPathWithModify(aProp) || isPathWithModify(aPipeline)\n const bIsModify = isPathWithModify(bProp) || isPathWithModify(bPipeline)\n return Number(aIsModify) - Number(bIsModify) // Sort any $modify path last\n}\n\nconst checkDirection = (\n requiredDirection: unknown,\n directionKeyword: string,\n directionAlias?: string\n) =>\n requiredDirection === directionKeyword ||\n (directionAlias && requiredDirection === directionAlias)\n\nconst resolveDirection = (direction: unknown, options: Options) =>\n checkDirection(direction, 'rev', options.revAlias)\n ? true\n : checkDirection(direction, 'fwd', options.fwdAlias)\n ? false\n : undefined\n\n// Wraps the given operation in `fwd` or `rev` if a direction is specified.\nfunction wrapInDirectional(operation: Operation, direction: unknown) {\n return (options: Options) => {\n const isRev = resolveDirection(direction, options)\n if (isRev === undefined) {\n return operation(options) // Run in both directions\n } else {\n const wrapOp = isRev ? rev : fwd\n return wrapOp(operation)(options)\n }\n }\n}\n\n// Merge target and state if they are both objects\nconst mergeTargetAndValueOperation: Operation = () => (next) =>\n async function mergeTargetAndValue(state) {\n const nextState = await next(state)\n const target = getTargetFromState(nextState)\n const value = getStateValue(nextState)\n return isObject(target) && isObject(value)\n ? setStateValue(nextState, { ...target, ...value })\n : nextState\n }\n\nfunction runOperationWithOriginalValue({ value }: State) {\n return async (state: State, fn: StateMapper) => {\n const nextState = await fn(setStateValue(state, value))\n\n // Get the current state target and set the value as the target\n const target = getTargetFromState(state)\n const nextValue = getStateValue(nextState)\n const thisState = setTargetOnState(nextState, nextValue)\n\n if (isObject(target) && !isObject(nextValue)) {\n // If the pipeline returns a non-object value, but the target is an\n // object, we return the target. The reason behind this is that we're\n // building an object here, and when a pipeline returns a non-object, it's\n // usually because of a `value()` intended for only one direction.\n return setStateValue(thisState, target)\n } else {\n // The normal case -- return what the pipeline returned\n return thisState\n }\n }\n}\n\nconst isArr = (prop: string) =>\n prop.endsWith('[]') && prop[prop.length - 3] !== '\\\\'\n\nconst isNumeric = (value: string) => !Number.isNaN(Number.parseInt(value, 10))\n\nfunction removeSlash(prop: string) {\n const index = prop.indexOf('/')\n if (\n index > -1 &&\n prop[index - 1] !== '\\\\' &&\n isNumeric(prop.slice(index + 1))\n ) {\n return prop.slice(0, index)\n }\n\n return prop\n}\n\nfunction createDirectionalOperation(\n pipeline: Pipeline,\n onlyFwd: boolean,\n onlyRev: boolean\n) {\n if (onlyRev && onlyFwd) {\n return undefined // Don't run anything when both directions are disabled\n } else if (onlyRev) {\n return divide(plug(), pipeline, true) // Plug going forward -- unless flipped\n } else if (onlyFwd) {\n return divide(pipeline, plug(), true) // Plug going in reverse -- unless flipped\n } else {\n return pipe(pipeline) // Run in both directions\n }\n}\n\nconst createSetPipeline = (options: Options) =>\n function createSetPipeline([prop, pipeline]: [string, TransformDefinition]):\n | Operation\n | undefined {\n // Adjust sub map object\n if (isTransformObject(pipeline)) {\n pipeline = [\n rev(mergeTargetAndValueOperation), // This will make sure the result of this pipeline is merged with the target in reverse\n {\n ...pipeline,\n $iterate: pipeline.$iterate || isArr(prop),\n },\n ]\n }\n\n // Handle slashed props\n const unslashedProp = removeSlash(prop)\n const isSlashed = prop !== unslashedProp // If these are different, we have removed a slash\n const onlyFwd = isPathWithModify(unslashedProp)\n const onlyRev = isSlashed || isPathWithModify(pipeline)\n\n // Prepare the operations and return as an operation\n const operations = [defToOperation(pipeline, options), set(unslashedProp)] // `pipeline` should not be flattened out with the `set`, to avoid destroying iteration logic\n return createDirectionalOperation(operations, onlyFwd, onlyRev)\n }\n\nconst runOperations =\n (stateMappers: NextStateMapper[], options: Options) =>\n async (state: State) => {\n if (isNonvalueState(state, options.nonvalues)) {\n return state\n } else {\n const run = runOperationWithOriginalValue(state)\n let nextState: State = state\n for (const stateMapper of stateMappers) {\n nextState = await run(nextState, stateMapper(noopNext)) // We call `noopNext` here to avoid running recursive pipelines more times than the data dictates\n }\n return nextState\n }\n }\n\nconst setStateProps = (state: State, noDefaults?: boolean, flip?: boolean) => ({\n ...state,\n noDefaults: noDefaults || state.noDefaults || false,\n flip: flip || state.flip || false,\n target: undefined,\n})\n\nconst fixModifyPath = (def: TransformObject) =>\n def.$modify === true ? { ...def, $modify: '.' } : def\n\nconst createStateMappers = (def: TransformObject, options: Options) =>\n Object.entries(def)\n .filter(isRegularProp)\n .sort(sortProps)\n .map(createSetPipeline(options))\n .filter(isNotNullOrUndefined)\n .map((fn) => fn(options))\n\n// Prepare one operation that will run all the prop pipelines\nfunction prepareOperation(def: TransformObject): Operation {\n return (options) => {\n // Prepare one state mapper for each prop\n const nextStateMappers = createStateMappers(fixModifyPath(def), options)\n\n // When there's no props on the transform object, return an operation that\n // simply sets value to an empty object\n if (nextStateMappers.length === 0) {\n return (next) => async (state) => setStateValue(await next(state), {}) // TODO: Not sure if we need to call `next()` here\n }\n\n // Prepare operations runner\n const run = runOperations(nextStateMappers, options)\n const runWithIterateWhenNeeded =\n def.$iterate === true ? iterate(() => () => run)(options)(noopNext) : run\n\n return (next) => {\n return async function doMutate(state) {\n const nextState = await next(state)\n\n // Don't touch state if its value is a nonvalue\n if (isNonvalueState(nextState, options.nonvalues)) {\n return nextState\n }\n\n // Don't pass on iteration to props\n const propsState = stopIteration(\n setStateProps(nextState, def.$noDefaults, def.$flip)\n )\n\n // Run the props operations\n const thisState = await runWithIterateWhenNeeded(propsState)\n\n // Set the value, but keep the target\n return setValueFromState(nextState, thisState)\n }\n }\n }\n}\n\n/**\n * Maps to an object by running the object values as pipelines and setting the\n * resulting values with the keys as paths – going forward. Will work in reverse\n * too, as each prop and pipeline are merged into one pipeline, with the key\n * path in a `set` operation at the end. So when running in reverse, the `set`\n * will `get` and vice versa.\n *\n * Supports $modify paths, $iterate, $noDefaults, $flip, and $direction.\n */\nexport default function props(def: TransformObject): Operation {\n const operation = prepareOperation(def)\n return wrapInDirectional(operation, def.$direction) // Wrap operation in a directional operation, if a $direction is specified\n}\n","import type {\n Operation,\n DataMapperWithOptions,\n AsyncDataMapperWithOptions,\n} from '../types.js'\nimport {\n getStateValue,\n setStateValue,\n revFromState,\n} from '../utils/stateHelpers.js'\n\nexport default function transform(\n fn: DataMapperWithOptions | AsyncDataMapperWithOptions,\n revFn?: DataMapperWithOptions | AsyncDataMapperWithOptions\n): Operation {\n return (options) => {\n if (typeof fn !== 'function') {\n throw new Error(\n 'Transform operation was called without a valid transformer function'\n )\n }\n const fwdPipeline = fn(options)\n const revPipeline =\n typeof revFn === 'function' ? revFn(options) : fwdPipeline\n\n return (next) => async (state) => {\n const nextState = await next(state)\n const fn = revFromState(nextState) ? revPipeline : fwdPipeline\n const value = await fn(getStateValue(nextState), nextState)\n return setStateValue(nextState, value)\n }\n }\n}\n","import type {\n Operation,\n DataMapperWithOptions,\n AsyncDataMapperWithOptions,\n DataMapperWithState,\n AsyncDataMapperWithState,\n State,\n} from '../types.js'\nimport { getStateValue, setStateValue } from '../utils/stateHelpers.js'\n\n// Filters an array with the provided filter function, or returns the single\n// value if it passes the filter.\nasync function filterValue(\n values: unknown,\n filterFn: DataMapperWithState | AsyncDataMapperWithState,\n state: State,\n) {\n if (Array.isArray(values)) {\n const results = []\n for (const value of values) {\n if (await filterFn(value, state)) {\n results.push(value)\n }\n }\n return results\n } else {\n const result = await filterFn(values, state)\n return result ? values : undefined\n }\n}\n\n/**\n * Given a filter function, returns an operation that will filter arrays or\n * single values with that filter function.\n */\nexport default function filter(\n fn: DataMapperWithOptions | AsyncDataMapperWithOptions,\n): Operation {\n return (options) => (next) => {\n if (typeof fn !== 'function') {\n return async (state) => await next(state)\n }\n const fnWithOptions = fn(options)\n\n return async (state) => {\n const nextState = await next(state)\n return setStateValue(\n nextState,\n await filterValue(getStateValue(nextState), fnWithOptions, nextState),\n )\n }\n }\n}\n","import type { DataMapper, TransformDefinition, Operation } from '../types.js'\nimport {\n getStateValue,\n setStateValue,\n goForward,\n} from '../utils/stateHelpers.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { noopNext } from '../utils/stateHelpers.js'\n\nfunction runCondition(conditionDef: DataMapper): Operation {\n return () => (next) => async (state) => {\n const nextState = await next(state)\n return setStateValue(\n nextState,\n await conditionDef(getStateValue(nextState), nextState)\n )\n }\n}\n\nexport default function (\n conditionDef?: DataMapper | TransformDefinition,\n trueDef?: TransformDefinition,\n falseDef?: TransformDefinition\n): Operation {\n return (options) => {\n const falseFn = defToOperation(falseDef, options)\n if (!conditionDef) {\n return falseFn(options)\n }\n const conditionFn: Operation =\n typeof conditionDef === 'function'\n ? runCondition(conditionDef as DataMapper) // We know to expect a datamapper here\n : defToOperation(conditionDef, options)\n const trueFn = defToOperation(trueDef, options)\n\n return (next) => {\n const runCondition = conditionFn(options)(noopNext)\n const runTrue = trueFn(options)(noopNext)\n const runFalse = falseFn(options)(noopNext)\n\n return async (state) => {\n const nextState = await next(state)\n const bool = getStateValue(await runCondition(goForward(nextState)))\n return bool ? await runTrue(nextState) : await runFalse(nextState)\n }\n }\n }\n}\n","import type {\n Options,\n Operation,\n State,\n TransformDefinition,\n} from '../types.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { noopNext } from '../utils/stateHelpers.js'\n\n// TODO: use a Map instead of an object, and prepare the pipelines when the\n// options is hande to MapTransform instead of here.\n\nconst getPipeline = (pipelineId: string | symbol, { pipelines }: Options) =>\n (typeof pipelineId === 'string' || typeof pipelineId === 'symbol') &&\n pipelines\n ? pipelines[pipelineId] // eslint-disable-line security/detect-object-injection\n : undefined\n\nfunction setPipeline(\n id: string | symbol,\n operation: Operation,\n options: Options\n) {\n if (options.pipelines) {\n options.pipelines[id] = operation // eslint-disable-line security/detect-object-injection\n }\n}\n\nconst removeFlip = ({ flip, ...state }: State) => state\n\n// If this is not an operation (function), we convert it to an operation and\n// then set it back on `pipelines`. This is done to allow for recursive\n// pipelines and works because as soon as it is set as an operation, it won't be\n// touched again until it is used in the \"next\" phase.\nfunction prepareAndSetPipeline(\n pipelineId: string | symbol,\n pipeline: TransformDefinition,\n options: Options\n) {\n if (typeof pipeline !== 'function' && pipeline) {\n setPipeline(pipelineId, () => () => noopNext, options) // Set an empty operation to tell any `apply()` calls further down, that we are taking care of this pipeline\n const operation = defToOperation(pipeline, options)(options)\n setPipeline(pipelineId, () => operation, options) // Set the actual operation\n }\n}\n\nexport default function apply(pipelineId: string | symbol): Operation {\n return (options) => {\n const pipeline = getPipeline(pipelineId, options)\n if (!pipeline) {\n const message = pipelineId\n ? `Failed to apply pipeline '${String(pipelineId)}'. Unknown pipeline`\n : 'Failed to apply pipeline. No id provided'\n throw new Error(message)\n }\n\n prepareAndSetPipeline(pipelineId, pipeline, options)\n\n return (next) => {\n // Fetch the prepared operation, and start the \"next\" phase.\n const operation = getPipeline(pipelineId, options)\n const fn =\n typeof operation === 'function'\n ? operation(options)(noopNext)\n : undefined\n if (fn) {\n // Set the next-ed operation back, so this won't be done in every\n // location we use the pipeline. It will still apply options and next to\n // the operation we set here, but it won't actually do anything.\n setPipeline(pipelineId, () => () => fn, options)\n }\n\n return async (state) => {\n const nextState = await next(state)\n return fn ? fn(removeFlip(nextState)) : nextState\n }\n }\n }\n}\n","import pipe from './pipe.js'\nimport {\n setValueFromState,\n popContext,\n isNonvalueState,\n setStateValue,\n markAsUntouched,\n isUntouched,\n} from '../utils/stateHelpers.js'\nimport { defToOperations } from '../utils/definitionHelpers.js'\nimport { noopNext } from '../utils/stateHelpers.js'\nimport type {\n Operation,\n State,\n StateMapper,\n TransformDefinition,\n} from '../types.js'\n\n// We run an array of operations through a pipe here when necessary, and specify\n// that the pipe should return context\nconst pipeIfArray = (def: Operation | Operation[]) =>\n Array.isArray(def) ? pipe(def, true) : def\n\n// Run the pipeline and return the value. If the state value has not been\n// changed by the alt pipeline, we set it to undefined to correct the case where\n// the alt pipeline is wrapped in `fwd` or `rev`, as these will simply return\n// the state untouched when we move in the wrong direction. We don't want the\n// untouched pipeline here, as a skipped pipeline should be treated as if it\n// returned a nonvalue.\n//\n// Note that we're just checking that the values are the exact same here, which\n// may not be work in all cases. E.g. the pipeline `'.'` would be considered\n// untouched, even though it is not wrapped in a directional, but again, what\n// would be the use of having a pipeline that just returns the state untouched\n// in an alt operation?\nasync function runAltPipeline(pipeline: StateMapper, state: State) {\n const afterState = await pipeline(markAsUntouched(state))\n return isUntouched(afterState)\n ? setStateValue(afterState, undefined)\n : afterState\n}\n\n// Prepare the pipeline and return an operation that will run it if the state\n// value is a nonvalue or if this is the first of several alt pipelines.\n//\n// The way we push and pop context might seem a bit strange, but the logic is\n// that if the first alt pipeline returns a nonvalue, the pushed context makes\n// the original value available to the following pipelines. Also, if all return\n// nonvalues, it is correct that we have moved one level down and need to go up\n// again to get the value from before the alt operation.\nfunction createOneAltPipeline(\n def: TransformDefinition,\n index: number,\n hasOnlyOneAlt: boolean\n): Operation {\n return (options) => {\n const pipeline = pipeIfArray(defToOperations(def, options))(options)(\n noopNext\n )\n const isFirst = !hasOnlyOneAlt && index === 0\n const { nonvalues } = options\n\n return (next: StateMapper) => async (state: State) => {\n const nextState = await next(state)\n if (!isFirst && !isNonvalueState(nextState, nonvalues)) {\n // We already have a value, so we don't need to run the alt operation\n return nextState\n }\n\n // For all alts after the first, we remove the context pushed from the\n // first and provides it as the value.\n const beforeState = isFirst ? nextState : popContext(nextState)\n\n // Run operation and set value if it is not a nonvalue\n const afterState = await runAltPipeline(pipeline, beforeState)\n return isNonvalueState(afterState, nonvalues)\n ? setValueFromState(nextState, afterState, isFirst) // We got a non-value, so set it on the original state\n : afterState // We got a value, so return it\n }\n }\n}\n\n/**\n * All alt operations are returned as individual operations, but the first one\n * is run in isolation (if it returns undefined, it will not polute the context)\n * and the rest are run only if the state value is not set. The exception is\n * when there is only one alt operation, in which case it is run as if it was\n * not the first (the \"first\" is the value already in the pipeline).\n */\nexport default function alt(...defs: TransformDefinition[]): Operation[] {\n const hasOnlyOneAlt = defs.length === 1\n return defs.map((def, index) =>\n createOneAltPipeline(def, index, hasOnlyOneAlt)\n )\n}\n","import deepmerge from 'deepmerge'\nimport type { Operation, State, TransformDefinition } from '../types.js'\nimport {\n getStateValue,\n setStateValue,\n isNonvalueState,\n} from '../utils/stateHelpers.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { isObject } from '../utils/is.js'\n\nexport function mergeExisting<T, U>(\n target: T[],\n source: U[],\n): U | (U | T | (U & T))[] {\n if (Array.isArray(target)) {\n const arr = source.slice()\n target.forEach((value, index) => {\n // eslint-disable-next-line security/detect-object-injection\n arr[index] = deepmerge<U, T>(source[index], value, {\n arrayMerge: mergeExisting,\n })\n })\n return arr\n }\n return target\n}\n\nexport function mergeStates(state: State, thisState: State) {\n const target = getStateValue(state)\n const source = getStateValue(thisState)\n\n const value = !isObject(source)\n ? target\n : !isObject(target)\n ? source\n : deepmerge(target, source, { arrayMerge: mergeExisting })\n\n return setStateValue(state, value)\n}\n\nexport default function merge(...defs: TransformDefinition[]): Operation {\n return (options) => (next) => {\n if (defs.length === 0) {\n return async (state) => setStateValue(await next(state), undefined)\n }\n const pipelines = defs.map((def) =>\n defToOperation(def, options)(options)(next),\n )\n\n return async function (state) {\n const nextState = await next(state)\n if (isNonvalueState(nextState, options.nonvalues)) {\n return setStateValue(nextState, undefined)\n } else {\n const states = []\n for (const pipeline of pipelines) {\n states.push(await pipeline(nextState))\n }\n return states.reduce(mergeStates)\n }\n }\n }\n}\n","import { mergeStates } from './merge.js'\nimport {\n setStateValue,\n getStateValue,\n revFromState,\n flipState,\n} from '../utils/stateHelpers.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { noopNext } from '../utils/stateHelpers.js'\nimport type {\n Operation,\n TransformDefinition,\n State,\n StateMapper,\n} from '../types.js'\n\nconst merge = <T, U>(left: T[], right: U | U[]) =>\n Array.isArray(right) ? [...left, ...right] : [...left, right]\n\n// Extract arrays from several pipelines and merge them into one array, then\n// filter away `undefined`.\nasync function getAndMergeArrays(state: State, fns: StateMapper[]) {\n let nextValue: unknown[] = []\n for (const fn of fns) {\n const value = getStateValue(await fn(state))\n nextValue = merge(nextValue, value)\n }\n return setStateValue(\n state,\n nextValue.filter((val) => val !== undefined)\n )\n}\n\n// Set the first pipeline to the entire array, and the following pipelines to\n// empty arrays.\nasync function setArrayOnFirstOperation(state: State, fns: StateMapper[]) {\n let valueState = await fns[0](state) // We know there is at least one function\n for (const fn of fns.slice(1)) {\n const thisState = await fn(setStateValue(state, []))\n valueState = mergeStates(valueState, thisState)\n }\n return valueState\n}\n\nfunction concatPipelines(\n defs: TransformDefinition[],\n flip: boolean\n): Operation {\n return (options) => {\n const fns = defs.map((def) =>\n defToOperation(def, options)(options)(noopNext)\n )\n\n if (fns.length === 0) {\n // Always return an empty array (or empty object in rev) when there are no\n // pipelines.\n return (next) => async (state) =>\n setStateValue(await next(state), revFromState(state, flip) ? {} : [])\n }\n\n return (next) =>\n async function doConcat(state) {\n const nextState = flipState(await next(state), flip)\n return revFromState(nextState)\n ? setArrayOnFirstOperation(nextState, fns)\n : getAndMergeArrays(nextState, fns)\n }\n }\n}\n\n/**\n * Extracts arrays from several pipelines and merges them into one array. If\n * a pipeline does not return an array, it will be wrapped in one. `undefined`\n * will be stripped away.\n *\n * In reverse, the first pipeline will be given the entire array, and the\n * following pipelines will be given an empty array. This will not lead to the\n * original object, if we ran this forward and then in reverse, but there is no\n * way of knowing how to split up an array into the \"original\" arrays.\n */\nexport function concat(...defs: TransformDefinition[]): Operation {\n return concatPipelines(defs, false)\n}\n\n// `concatRev` is the exact oposite of `concat`, and will concat in reverse.\nexport function concatRev(...defs: TransformDefinition[]): Operation {\n return concatPipelines(defs, true)\n}\n","import mapAny from 'map-any-cjs/async.js'\nimport { pathGetter } from '../operations/getSet.js'\nimport type { Operation, State, Path, TransformerProps } from '../types.js'\nimport {\n getStateValue,\n setStateValue,\n goForward,\n revFromState,\n} from '../utils/stateHelpers.js'\nimport { defToOperation } from '../utils/definitionHelpers.js'\nimport { noopNext } from '../utils/stateHelpers.js'\nimport { isObject } from '../utils/is.js'\n\nexport interface Props extends TransformerProps {\n arrayPath: Path\n propPath: Path\n matchSeveral?: boolean\n flip?: boolean\n}\n\ninterface GetPropFn {\n (val: unknown, state: State): unknown\n}\n\ninterface MatchFn {\n (value: unknown, state: State, arr: unknown[], getProp: GetPropFn): unknown\n}\n\nconst FLATTEN_DEPTH = 1\n\nconst flattenIfArray = (data: unknown) =>\n Array.isArray(data) ? data.flat(FLATTEN_DEPTH) : data\n\n// Find all matches in array. To support async, we first map over the\n// array and get the value to compare against,