118 lines
3.2 KiB
JavaScript
118 lines
3.2 KiB
JavaScript
import { get, set, unset, forEachRight, cloneDeep } from 'lodash';
|
|
|
|
export default function transformFields(src, transformList, reversed = false){
|
|
if (!transformList) return src;
|
|
// don't bash the old document during the transforms
|
|
let doc = cloneDeep(src);
|
|
for(let originalTransform of transformList){
|
|
let transform;
|
|
// Swap to and from when reversing
|
|
if (reversed){
|
|
transform = {
|
|
to: originalTransform.from,
|
|
from: originalTransform.to,
|
|
up: originalTransform.down,
|
|
}
|
|
} else {
|
|
transform = {...originalTransform};
|
|
}
|
|
transformField(src, doc, transform, reversed);
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
export function transformField(src, doc, transform, reversed){
|
|
if (transform.from?.includes('$.')){
|
|
transformArrayField(src, doc, transform, reversed);
|
|
} else {
|
|
transformSingleField(src, doc, transform);
|
|
}
|
|
}
|
|
|
|
function transformSingleField(src, doc, {from, to, up}){
|
|
// Get the value in the `from` path and delete it
|
|
let value = undefined;
|
|
if (from){
|
|
value = get(src, from);
|
|
unset(doc, from);
|
|
}
|
|
|
|
// apply the transform function
|
|
if (up){
|
|
value = up(value, src, doc);
|
|
}
|
|
|
|
// Store the value in the `to` path or unset it if undefined
|
|
if (to){
|
|
if (value === undefined){
|
|
unset(doc, to);
|
|
} else {
|
|
set(doc, to, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* from: 'from.$.here', to: 'to.$.here'
|
|
* where from and to are an [array, of, objects] that each need to be modified
|
|
* documents at 'from.x.here' will map to 'to.x.here'
|
|
* Attempts to support 'from.$.here.$.nested'
|
|
* by mapping 'from.x.here.y.nest.z.deep' to 'to.y.nest.z.lessDeep'
|
|
* from depth must be >= to depth
|
|
*/
|
|
function transformArrayField(src, doc, {from, to, up}, reversed){
|
|
const fromSplit = from.split('.$');
|
|
const toSplit = to.split('.$');
|
|
|
|
if (toSplit.length > fromSplit.length){
|
|
throw 'Can\'t transform array fields where "to" is deeper than "from"'
|
|
}
|
|
|
|
// Stack based depth first traversal of arrays
|
|
const stack = [{
|
|
array: get(src, fromSplit[0]),
|
|
paths: fromSplit.slice(1),
|
|
currentPath: fromSplit[0],
|
|
indices: [],
|
|
}];
|
|
while(stack.length){
|
|
const state = stack.pop();
|
|
// Iterate forwads or backwads defpending on our migration direction
|
|
if (reversed){
|
|
forEachRight(state.array, iterate(stack, state, src, doc, toSplit, up));
|
|
} else {
|
|
state.array?.forEach(iterate(stack, state, src, doc, toSplit, up));
|
|
}
|
|
}
|
|
}
|
|
|
|
function iterate(stack, state, src, doc, toSplit, up){return function(key, index){
|
|
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
|
if (state.paths.length == 1){
|
|
transformSingleField(src, doc, {
|
|
from: currentPath,
|
|
to: buildToPath(toSplit, [...state.indices, index]),
|
|
up
|
|
});
|
|
} else {
|
|
stack.push({
|
|
array: get(src, currentPath),
|
|
paths: state.paths.slice(1),
|
|
currentPath,
|
|
indices: [...state.indices, index],
|
|
});
|
|
}
|
|
}}
|
|
|
|
function buildToPath(toSplit, indices){
|
|
let toPath = '';
|
|
let offset = indices.length - toSplit.length + 1;
|
|
toSplit.forEach((path, i) => {
|
|
toPath += `${path}`;
|
|
if (i < toSplit.length - 1){
|
|
toPath += `[${indices[i + offset]}]`
|
|
}
|
|
});
|
|
return toPath;
|
|
}
|