map-transform
Version:
Map and transform objects with mapping definitions
165 lines • 7.25 kB
JavaScript
import { getStateValue, setStateValue } from './stateHelpers.js';
import modifyOperationObject from './modifyOperationObject.js';
import { noopNext } from '../utils/stateHelpers.js';
import { isObject } from './is.js';
import { get } from '../operations/getSet.js';
import props from '../operations/props.js';
import iterate from '../operations/iterate.js';
import transform from '../operations/transform.js';
import filter from '../operations/filter.js';
import ifelse from '../operations/ifelse.js';
import apply from '../operations/apply.js';
import alt from '../operations/alt.js';
import { fwd, rev } from '../operations/directionals.js';
import { concat, concatRev } from '../operations/concat.js';
import { lookup, lookdown } from '../operations/lookup.js';
import pipe from '../operations/pipe.js';
import { unescapeValue } from './escape.js';
import { ensureArray } from './array.js';
const passStateThroughNext = (next) => async (state) => next(state);
const nonOperatorKeys = [
'$iterate',
'$modify',
'$noDefaults',
'$flip',
'$direction',
];
const isOperatorKey = (key) => key[0] === '$' && !nonOperatorKeys.includes(key);
const isOperationObject = (def) => isObject(def) && Object.keys(def).filter(isOperatorKey).length > 0;
export const isOperationType = (def, prop) => def.hasOwnProperty(prop);
const pipeIfArray = (operations) => (Array.isArray(operations) ? pipe(operations) : operations);
export const isPath = (def) => typeof def === 'string';
export const isTransformObject = (def) => isObject(def) && !isOperationObject(def);
export const isPipeline = (def) => Array.isArray(def);
export const isOperation = (def) => typeof def === 'function';
export const isTransformDefinition = (def) => isPath(def) || isObject(def) || isPipeline(def) || isOperation(def);
const wrapInNoDefaults = (fn) => (options) => (next) => {
const stateMapper = fn(options)(next);
return async (state) => {
const stateWithNoDefaults = { ...state, noDefaults: true };
return stateMapper(stateWithNoDefaults);
};
};
function wrapFromDefinition(ops, def) {
const opsWithNoDefaults = def.$noDefaults === true ? wrapInNoDefaults(pipeIfArray(ops)) : ops;
const fn = def.$iterate === true ? iterate(opsWithNoDefaults) : opsWithNoDefaults;
return (options) => {
const dir = def.$direction;
if (typeof dir === 'string') {
if (dir === 'rev' || dir === options.revAlias) {
return rev(fn)(options);
}
else if (dir === 'fwd' || dir === options.fwdAlias) {
return fwd(fn)(options);
}
}
return Array.isArray(fn) ? pipe(fn, true)(options) : fn(options);
};
}
const humanizeOperatorName = (operatorProp) => `${operatorProp[1].toUpperCase()}${operatorProp.slice(2)}`;
const createOperation = (operationFn, fnProp, def) => (options) => {
const { [fnProp]: fnId, ...props } = def;
let transformFn;
if (typeof fnId === 'function') {
transformFn = fnId;
}
else {
if (typeof fnId !== 'string' && typeof fnId !== 'symbol') {
throw new Error(`${humanizeOperatorName(fnProp)} operator was given no transformer id or an invalid transformer id`);
}
const fn = options.transformers && options.transformers[fnId];
if (typeof fn !== 'function') {
throw new Error(`${humanizeOperatorName(fnProp)} operator was given the unknown transformer id '${String(fnId)}'`);
}
transformFn = fn(props);
}
return typeof transformFn === 'function'
? wrapFromDefinition(operationFn(transformFn), def)(options)
: passStateThroughNext;
};
const createTransformOperation = (def) => createOperation(transform, '$transform', def);
const createFilterOperation = (def) => createOperation(filter, '$filter', def);
const setNoneValuesOnOptions = (options, nonvalues) => Array.isArray(nonvalues)
? { ...options, nonvalues: nonvalues.map(unescapeValue) }
: options;
const createAltOperation = (operationFn, def) => (options) => {
const { $alt: defs, $undefined: nonvalues } = def;
return Array.isArray(defs)
? wrapFromDefinition(operationFn(...defs), def)(setNoneValuesOnOptions(options, nonvalues))
: passStateThroughNext;
};
const createIfOperation = (def) => (options) => {
const { $if: conditionPipeline, then: thenPipeline, else: elsePipeline, } = def;
return wrapFromDefinition(ifelse(conditionPipeline, thenPipeline, elsePipeline), def)(options);
};
function createApplyOperation(operationFn, def) {
const pipelineId = def.$apply;
return wrapFromDefinition(operationFn(pipelineId), def);
}
function createConcatOperation(operationFn, pipeline) {
const pipelines = ensureArray(pipeline);
return operationFn(...pipelines);
}
function createLookupOperation(operationFn, def, arrayPath) {
const { path: propPath, ...props } = def;
return wrapFromDefinition(operationFn({ ...props, arrayPath, propPath }), def);
}
function operationFromObject(defRaw, options) {
const def = modifyOperationObject(defRaw, options.modifyOperationObject);
if (isOperationObject(def)) {
if (isOperationType(def, '$transform')) {
return createTransformOperation(def);
}
else if (isOperationType(def, '$filter')) {
return createFilterOperation(def);
}
else if (isOperationType(def, '$if')) {
return createIfOperation(def);
}
else if (isOperationType(def, '$apply')) {
return createApplyOperation(apply, def);
}
else if (isOperationType(def, '$alt')) {
return createAltOperation(alt, def);
}
else if (isOperationType(def, '$concat')) {
return createConcatOperation(concat, def.$concat);
}
else if (isOperationType(def, '$concatRev')) {
return createConcatOperation(concatRev, def.$concatRev);
}
else if (isOperationType(def, '$lookup')) {
return createLookupOperation(lookup, def, def.$lookup);
}
else if (isOperationType(def, '$lookdown')) {
return createLookupOperation(lookdown, def, def.$lookdown);
}
else {
return () => () => async (value) => value;
}
}
else {
return props(def);
}
}
export const defToOperations = (def, options) => isPipeline(def)
? def.flatMap((def) => defToOperations(def, options))
: isObject(def)
? operationFromObject(def, options)
: isPath(def)
? get(def)
: isOperation(def)
? def
: () => () => async (value) => value;
export function defToOperation(def, options) {
const operations = isPipeline(def) ? def : defToOperations(def, options);
return pipeIfArray(operations);
}
export function operationToDataMapper(operation, options) {
const fn = operation(options)(noopNext);
return async (value, state) => getStateValue(await fn(setStateValue(state, value)));
}
export function defToDataMapper(def, options = {}) {
return operationToDataMapper(defToOperation(def, options), options);
}
//# sourceMappingURL=definitionHelpers.js.map