sveltekit-superforms
Version:
Making SvelteKit forms a pleasure to use!
143 lines (142 loc) • 5.04 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
function setPath(parent, key, value) {
parent[key] = value;
return 'skip';
}
function isInvalidPath(originalPath, pathData) {
return (pathData.value !== undefined &&
typeof pathData.value !== 'object' &&
pathData.path.length < originalPath.length);
}
export function pathExists(obj, path, options = {}) {
if (!options.modifier) {
options.modifier = (pathData) => (isInvalidPath(path, pathData) ? undefined : pathData.value);
}
const exists = traversePath(obj, path, options.modifier);
if (!exists)
return undefined;
if (options.value === undefined)
return exists;
return options.value(exists.value) ? exists : undefined;
}
export function traversePath(obj, realPath, modifier) {
if (!realPath.length)
return undefined;
const path = [realPath[0]];
let parent = obj;
while (parent && path.length < realPath.length) {
const key = path[path.length - 1];
const value = modifier
? modifier({
parent,
key: String(key),
value: parent[key],
path: path.map((p) => String(p)),
isLeaf: false,
set: (v) => setPath(parent, key, v)
})
: parent[key];
if (value === undefined)
return undefined;
else
parent = value;
path.push(realPath[path.length]);
}
if (!parent)
return undefined;
const key = realPath[realPath.length - 1];
return {
parent,
key: String(key),
value: parent[key],
path: realPath.map((p) => String(p)),
isLeaf: true,
set: (v) => setPath(parent, key, v)
};
}
export function traversePaths(parent, modifier, path = []) {
for (const key in parent) {
const value = parent[key];
const isLeaf = value === null || typeof value !== 'object';
const pathData = {
parent,
key,
value,
path: path.concat([key]), // path.map(String).concat([key])
isLeaf,
set: (v) => setPath(parent, key, v)
};
const status = modifier(pathData);
if (status === 'abort')
return status;
else if (status === 'skip')
continue;
else if (!isLeaf) {
const status = traversePaths(value, modifier, pathData.path);
if (status === 'abort')
return status;
}
}
}
// Thanks to https://stackoverflow.com/a/31129384/70894
function eqSet(xs, ys) {
return xs === ys || (xs.size === ys.size && [...xs].every((x) => ys.has(x)));
}
/**
* Compare two objects and return the differences as paths.
*/
export function comparePaths(newObj, oldObj) {
const diffPaths = new Map();
function builtInDiff(one, other) {
if (one instanceof Date && other instanceof Date && one.getTime() !== other.getTime())
return true;
if (one instanceof Set && other instanceof Set && !eqSet(one, other))
return true;
if (one instanceof File && other instanceof File && one !== other)
return true;
return false;
}
function isBuiltin(data) {
return data instanceof Date || data instanceof Set || data instanceof File;
}
function checkPath(data, compareTo) {
const otherData = compareTo ? traversePath(compareTo, data.path) : undefined;
//console.log('Compare', data.path, data.value, 'to', otherData?.path, otherData?.value);
function addDiff() {
//console.log('Diff', data.path);
diffPaths.set(data.path.join(' '), data.path);
return 'skip';
}
if (isBuiltin(data.value)) {
if (!isBuiltin(otherData?.value) || builtInDiff(data.value, otherData.value)) {
return addDiff();
}
}
if (data.isLeaf) {
if (!otherData || data.value !== otherData.value) {
addDiff();
}
}
}
traversePaths(newObj, (data) => checkPath(data, oldObj));
traversePaths(oldObj, (data) => checkPath(data, newObj));
// Need to sort the list so the shortest paths comes first
const output = Array.from(diffPaths.values());
output.sort((a, b) => a.length - b.length);
return output;
}
export function setPaths(obj, paths, value) {
const isFunction = typeof value === 'function';
for (const path of paths) {
const leaf = traversePath(obj, path, ({ parent, key, value }) => {
if (value === undefined || typeof value !== 'object') {
// If a previous check tainted the node, but the search goes deeper,
// so it needs to be replaced with a (parent) node
parent[key] = {};
}
return parent[key];
});
if (leaf)
leaf.parent[leaf.key] = isFunction ? value(path, leaf) : value;
}
}