Files
DiceCloud/app/imports/migrations/server/transformFields.js

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;
}