UNPKG

map-transform-cjs

Version:
1 lines 93.2 kB
{"version":3,"sources":["../../src/utils/definitionHelpers.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/directionals.ts","../../src/operations/transform.ts","../../src/transformers/flatten.ts","../../src/operations/pipe.ts","../../src/operations/plug.ts","../../src/operations/props.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"],"sourcesContent":["import { getStateValue, setStateValue } from './stateHelpers.js'\nimport modifyOperationObject from './modifyOperationObject.js'\nimport { noopNext } from '../utils/stateHelpers.js'\nimport { isObject } from './is.js'\nimport { get } from '../operations/getSet.js'\nimport props from '../operations/props.js'\nimport iterate from '../operations/iterate.js'\nimport transform from '../operations/transform.js'\nimport filter from '../operations/filter.js'\nimport ifelse from '../operations/ifelse.js'\nimport apply from '../operations/apply.js'\nimport alt from '../operations/alt.js'\nimport { fwd, rev } from '../operations/directionals.js'\nimport { concat, concatRev } from '../operations/concat.js'\nimport { lookup, lookdown, Props as LookupProps } from '../operations/lookup.js'\nimport pipe from '../operations/pipe.js'\nimport { unescapeValue } from './escape.js'\nimport { ensureArray } from './array.js'\nimport type {\n Operation,\n TransformDefinition,\n TransformObject,\n Path,\n Pipeline,\n OperationObject,\n TransformOperation,\n FilterOperation,\n IfOperation,\n ApplyOperation,\n AltOperation,\n ConcatOperation,\n ConcatRevOperation,\n LookupOperation,\n LookdownOperation,\n Options,\n DataMapperWithState,\n DataMapperWithOptions,\n AsyncDataMapperWithOptions,\n State,\n StateMapper,\n AsyncDataMapperWithState,\n} from '../types.js'\n\nconst passStateThroughNext = (next: StateMapper) => async (state: State) =>\n next(state)\n\nconst nonOperatorKeys = [\n '$iterate',\n '$modify',\n '$noDefaults',\n '$flip',\n '$direction',\n]\n\nconst isOperatorKey = (key: string) =>\n key[0] === '$' && !nonOperatorKeys.includes(key)\n\nconst isOperationObject = (def: unknown): def is OperationObject =>\n isObject(def) && Object.keys(def).filter(isOperatorKey).length > 0\n\nexport const isOperationType = <T extends OperationObject>(\n def: TransformObject | OperationObject,\n prop: string,\n): def is T => (def as object).hasOwnProperty(prop)\n\nconst pipeIfArray = (\n operations: Operation | Operation[] | Pipeline,\n): Operation => (Array.isArray(operations) ? pipe(operations) : operations)\n\nexport const isPath = (def: unknown): def is Path => typeof def === 'string'\nexport const isTransformObject = (def: unknown): def is TransformObject =>\n isObject(def) && !isOperationObject(def)\nexport const isPipeline = (def: unknown): def is Pipeline => Array.isArray(def)\nexport const isOperation = (def: unknown): def is Operation =>\n typeof def === 'function'\nexport const isTransformDefinition = (\n def: unknown,\n): def is TransformDefinition =>\n isPath(def) || isObject(def) || isPipeline(def) || isOperation(def)\n\nconst wrapInNoDefaults =\n (fn: Operation): Operation =>\n (options) =>\n (next) => {\n const stateMapper = fn(options)(next)\n return async (state: State) => {\n const stateWithNoDefaults = { ...state, noDefaults: true }\n return stateMapper(stateWithNoDefaults)\n }\n }\n\nfunction wrapFromDefinition(\n ops: Operation | Operation[],\n def: OperationObject,\n): Operation {\n const opsWithNoDefaults =\n def.$noDefaults === true ? wrapInNoDefaults(pipeIfArray(ops)) : ops\n const fn =\n def.$iterate === true ? iterate(opsWithNoDefaults) : opsWithNoDefaults\n return (options) => {\n const dir = def.$direction\n if (typeof dir === 'string') {\n if (dir === 'rev' || dir === options.revAlias) {\n return rev(fn)(options)\n } else if (dir === 'fwd' || dir === options.fwdAlias) {\n return fwd(fn)(options)\n }\n }\n return Array.isArray(fn) ? pipe(fn, true)(options) : fn(options)\n }\n}\n\nconst humanizeOperatorName = (operatorProp: string) =>\n `${operatorProp[1].toUpperCase()}${operatorProp.slice(2)}`\n\nconst createOperation =\n <U extends OperationObject>(\n operationFn: (\n fn: DataMapperWithOptions | AsyncDataMapperWithOptions,\n ) => Operation,\n fnProp: string,\n def: U,\n ): Operation =>\n (options) => {\n const { [fnProp]: fnId, ...props } = def\n let transformFn\n if (typeof fnId === 'function') {\n transformFn = fnId as DataMapperWithOptions | AsyncDataMapperWithOptions\n } else {\n if (typeof fnId !== 'string' && typeof fnId !== 'symbol') {\n throw new Error(\n `${humanizeOperatorName(\n fnProp,\n )} operator was given no transformer id or an invalid transformer id`,\n )\n }\n\n // eslint-disable-next-line security/detect-object-injection\n const fn = options.transformers && options.transformers[fnId]\n if (typeof fn !== 'function') {\n throw new Error(\n `${humanizeOperatorName(\n fnProp,\n )} operator was given the unknown transformer id '${String(fnId)}'`,\n )\n }\n transformFn = fn(props)\n }\n\n return typeof transformFn === 'function'\n ? wrapFromDefinition(operationFn(transformFn), def)(options)\n : passStateThroughNext\n }\n\nconst createTransformOperation = (def: TransformOperation): Operation =>\n createOperation(transform, '$transform', def)\n\nconst createFilterOperation = (def: FilterOperation): Operation =>\n createOperation(filter, '$filter', def)\n\nconst setNoneValuesOnOptions = (options: Options, nonvalues?: unknown[]) =>\n Array.isArray(nonvalues)\n ? { ...options, nonvalues: nonvalues.map(unescapeValue) }\n : options\n\nconst createAltOperation =\n (\n operationFn: (...defs: TransformDefinition[]) => Operation[],\n def: AltOperation,\n ): Operation | Operation[] =>\n (options) => {\n const { $alt: defs, $undefined: nonvalues } = def\n return Array.isArray(defs)\n ? wrapFromDefinition(\n operationFn(...defs),\n def,\n )(setNoneValuesOnOptions(options, nonvalues))\n : passStateThroughNext\n }\n\nconst createIfOperation =\n (def: IfOperation): Operation =>\n (options) => {\n const {\n $if: conditionPipeline,\n then: thenPipeline,\n else: elsePipeline,\n } = def\n return wrapFromDefinition(\n ifelse(conditionPipeline, thenPipeline, elsePipeline),\n def,\n )(options)\n }\n\nfunction createApplyOperation(\n operationFn: (pipelineId: string | symbol) => Operation,\n def: ApplyOperation,\n) {\n const pipelineId = def.$apply\n return wrapFromDefinition(operationFn(pipelineId), def)\n}\n\nfunction createConcatOperation(\n operationFn: (...fn: TransformDefinition[]) => Operation,\n pipeline: TransformDefinition[],\n) {\n const pipelines = ensureArray(pipeline)\n return operationFn(...pipelines)\n}\n\nfunction createLookupOperation(\n operationFn: (props: LookupProps) => Operation,\n def: LookupOperation | LookdownOperation,\n arrayPath: string,\n) {\n const { path: propPath, ...props } = def\n return wrapFromDefinition(operationFn({ ...props, arrayPath, propPath }), def)\n}\n\nfunction operationFromObject(\n defRaw: OperationObject | TransformObject,\n options: Options,\n): Operation | Operation[] {\n const def = modifyOperationObject(defRaw, options.modifyOperationObject)\n\n if (isOperationObject(def)) {\n if (isOperationType<TransformOperation>(def, '$transform')) {\n return createTransformOperation(def)\n } else if (isOperationType<FilterOperation>(def, '$filter')) {\n return createFilterOperation(def)\n } else if (isOperationType<IfOperation>(def, '$if')) {\n return createIfOperation(def)\n } else if (isOperationType<ApplyOperation>(def, '$apply')) {\n return createApplyOperation(apply, def)\n } else if (isOperationType<AltOperation>(def, '$alt')) {\n return createAltOperation(alt, def)\n } else if (isOperationType<ConcatOperation>(def, '$concat')) {\n return createConcatOperation(concat, def.$concat)\n } else if (isOperationType<ConcatRevOperation>(def, '$concatRev')) {\n return createConcatOperation(concatRev, def.$concatRev)\n } else if (isOperationType<LookupOperation>(def, '$lookup')) {\n return createLookupOperation(lookup, def, def.$lookup)\n } else if (isOperationType<LookdownOperation>(def, '$lookdown')) {\n return createLookupOperation(lookdown, def, def.$lookdown)\n } else {\n // Not a known operation\n return () => () => async (value) => value\n }\n } else {\n return props(def as TransformObject)\n }\n}\n\nexport const defToOperations = (\n def: TransformDefinition | undefined,\n options: Options,\n): Operation[] | Operation =>\n isPipeline(def)\n ? def.flatMap((def) => defToOperations(def, options))\n : isObject(def)\n ? operationFromObject(def, options)\n : isPath(def)\n ? get(def)\n : isOperation(def)\n ? def\n : () => () => async (value) => value\n\nexport function defToOperation(\n def: TransformDefinition | undefined,\n options: Options,\n): Operation {\n const operations = isPipeline(def) ? def : defToOperations(def, options)\n return pipeIfArray(operations)\n}\n\nexport function operationToDataMapper(\n operation: Operation,\n options: Options,\n): DataMapperWithState | AsyncDataMapperWithState {\n const fn = operation(options)(noopNext)\n return async (value, state) =>\n getStateValue(await fn(setStateValue(state, value)))\n}\n\nexport function defToDataMapper(\n def?: TransformDefinition,\n options: Options = {},\n): DataMapperWithState | AsyncDataMapperWithState {\n return operationToDataMapper(defToOperation(def, options), options)\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 { defToOperation } from '../utils/definitionHelpers.js'\nimport type { TransformDefinition, Operation, Options } from '../types.js'\nimport { revFromState } from '../utils/stateHelpers.js'\n\nconst applyInDirection =\n (def: TransformDefinition, shouldRunRev: boolean): Operation =>\n (options: Options) =>\n (next) => {\n const fn = defToOperation(def, options)(options)(next)\n return async function applyFwdOrRev(state) {\n return !!state.rev === shouldRunRev ? await fn(state) : await next(state)\n }\n }\n\nexport function fwd(def: TransformDefinition): Operation {\n return applyInDirection(def, false)\n}\n\nexport function rev(def: TransformDefinition): Operation {\n return applyInDirection(def, true)\n}\n\nexport function divide(\n fwdDef: TransformDefinition,\n revDef: TransformDefinition,\n honorFlip = false\n): Operation {\n return (options) => (next) => {\n const fwdFn = defToOperation(fwdDef, options)(options)(next)\n const revFn = defToOperation(revDef, options)(options)(next)\n return async function applyDivide(state) {\n const isRev = honorFlip ? revFromState(state) : !!state.rev\n return isRev ? await revFn(state) : await fwdFn(state)\n }\n }\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 { Transformer, TransformerProps } from '../types.js'\n\nexport interface Props extends TransformerProps {\n depth?: number\n}\n\nconst transformer: Transformer<Props> = function flatten({ depth = 1 }) {\n return () => (data) => Array.isArray(data) ? data.flat(depth) : data\n}\n\nexport default transformer\n","import { divide, fwd } from './directionals.js'\nimport iterate from './iterate.js'\nimport transform from './transform.js'\nimport flatten from '../transformers/flatten.js'\nimport { defToOperations } from '../utils/definitionHelpers.js'\nimport { setValueFromState, revFromState } from '../utils/stateHelpers.js'\nimport type { Pipeline, Operation, StateMapper } from '../types.js'\n\ninterface Fn {\n (next: StateMapper): StateMapper\n}\n\nfunction createRun(fns: Fn[], next: StateMapper) {\n let fn = next\n for (const f of fns) {\n fn = f(fn)\n }\n return fn\n}\n\n// Run through a pipeline def and split up any paths with open array brackets\n// in the middle. The path after the brackets will be iterated along with the\n// rest of the pipeline, and then flattened – in the forward direction. Nothing\n// will happen in reverse.\nfunction splitArrayPaths(defs: Pipeline) {\n const pipeline: Pipeline = []\n\n for (const [index, step] of defs.entries()) {\n if (typeof step === 'string' && step.includes('[].')) {\n const pos = step.indexOf('[].')\n pipeline.push(step.slice(0, pos + 2))\n pipeline.push(\n divide(iterate([step.slice(pos + 3), ...defs.slice(index + 1)]), [\n step.slice(pos + 3),\n ...defs.slice(index + 1),\n ])\n )\n pipeline.push(fwd(transform(flatten({ depth: 1 }))))\n break\n } else {\n pipeline.push(step)\n }\n }\n\n return pipeline\n}\n\nexport default function pipe(\n defs?: Pipeline,\n doReturnContext = false\n): Operation {\n return (options) => {\n if (!Array.isArray(defs) || defs.length === 0) {\n return () => async (state) => state\n }\n\n const fns = splitArrayPaths(defs)\n .flat()\n .flatMap((def) => defToOperations(def, options))\n .map((fn) => fn(options))\n\n return (next) => {\n const runFwd = createRun(fns, next)\n const runRev = createRun(Array.from(fns).reverse(), next) // Reverse the order of the operations in rev\n\n return async function doPipe(state) {\n const thisState = revFromState(state)\n ? await runRev(state)\n : await runFwd(state)\n return doReturnContext ? thisState : setValueFromState(state, thisState)\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 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