mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
added new diffing method to module manager
Signed-off-by: dswbx <dennis.senn@gmx.ch>
This commit is contained in:
181
app/src/core/object/diff.ts
Normal file
181
app/src/core/object/diff.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
enum Change {
|
||||
Add = "a",
|
||||
Remove = "r",
|
||||
Edit = "e"
|
||||
}
|
||||
|
||||
type Object = object;
|
||||
type Primitive = string | number | boolean | null | object | any[] | undefined;
|
||||
|
||||
interface DiffEntry {
|
||||
t: Change | string;
|
||||
p: (string | number)[];
|
||||
o: Primitive;
|
||||
n: Primitive;
|
||||
}
|
||||
|
||||
function isObject(value: any): value is Object {
|
||||
return value !== null && value.constructor.name === "Object";
|
||||
}
|
||||
function isPrimitive(value: any): value is Primitive {
|
||||
try {
|
||||
return (
|
||||
value === null ||
|
||||
value === undefined ||
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean" ||
|
||||
Array.isArray(value) ||
|
||||
isObject(value)
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function diff(oldObj: Object, newObj: Object): DiffEntry[] {
|
||||
const diffs: DiffEntry[] = [];
|
||||
|
||||
function recurse(oldValue: Primitive, newValue: Primitive, path: (string | number)[]) {
|
||||
if (!isPrimitive(oldValue) || !isPrimitive(newValue)) {
|
||||
throw new Error("Diff: Only primitive types are supported");
|
||||
}
|
||||
|
||||
if (oldValue === newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof oldValue !== typeof newValue) {
|
||||
diffs.push({
|
||||
t: Change.Edit,
|
||||
p: path,
|
||||
o: oldValue,
|
||||
n: newValue
|
||||
});
|
||||
} else if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
||||
const maxLength = Math.max(oldValue.length, newValue.length);
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
if (i >= oldValue.length) {
|
||||
diffs.push({
|
||||
t: Change.Add,
|
||||
p: [...path, i],
|
||||
o: undefined,
|
||||
n: newValue[i]
|
||||
});
|
||||
} else if (i >= newValue.length) {
|
||||
diffs.push({
|
||||
t: Change.Remove,
|
||||
p: [...path, i],
|
||||
o: oldValue[i],
|
||||
n: undefined
|
||||
});
|
||||
} else {
|
||||
recurse(oldValue[i], newValue[i], [...path, i]);
|
||||
}
|
||||
}
|
||||
} else if (isObject(oldValue) && isObject(newValue)) {
|
||||
const oKeys = Object.keys(oldValue);
|
||||
const nKeys = Object.keys(newValue);
|
||||
const allKeys = new Set([...oKeys, ...nKeys]);
|
||||
for (const key of allKeys) {
|
||||
if (!(key in oldValue)) {
|
||||
diffs.push({
|
||||
t: Change.Add,
|
||||
p: [...path, key],
|
||||
o: undefined,
|
||||
n: newValue[key]
|
||||
});
|
||||
} else if (!(key in newValue)) {
|
||||
diffs.push({
|
||||
t: Change.Remove,
|
||||
p: [...path, key],
|
||||
o: oldValue[key],
|
||||
n: undefined
|
||||
});
|
||||
} else {
|
||||
recurse(oldValue[key], newValue[key], [...path, key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
diffs.push({
|
||||
t: Change.Edit,
|
||||
p: path,
|
||||
o: oldValue,
|
||||
n: newValue
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
recurse(oldObj, newObj, []);
|
||||
return diffs;
|
||||
}
|
||||
|
||||
function apply(obj: Object, diffs: DiffEntry[]): any {
|
||||
const clonedObj = clone(obj);
|
||||
|
||||
for (const diff of diffs) {
|
||||
applyChange(clonedObj, diff);
|
||||
}
|
||||
|
||||
return clonedObj;
|
||||
}
|
||||
|
||||
function revert(obj: Object, diffs: DiffEntry[]): any {
|
||||
const clonedObj = clone(obj);
|
||||
const reversedDiffs = diffs.slice().reverse();
|
||||
|
||||
for (const diff of reversedDiffs) {
|
||||
revertChange(clonedObj, diff);
|
||||
}
|
||||
|
||||
return clonedObj;
|
||||
}
|
||||
|
||||
function applyChange(obj: Object, diff: DiffEntry) {
|
||||
const { p: path, t: type, n: newValue } = diff;
|
||||
const parent = getParent(obj, path.slice(0, -1));
|
||||
const key = path[path.length - 1]!;
|
||||
|
||||
if (type === Change.Add || type === Change.Edit) {
|
||||
parent[key] = newValue;
|
||||
} else if (type === Change.Remove) {
|
||||
if (Array.isArray(parent)) {
|
||||
parent.splice(key as number, 1);
|
||||
} else {
|
||||
delete parent[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function revertChange(obj: Object, diff: DiffEntry) {
|
||||
const { p: path, t: type, o: oldValue } = diff;
|
||||
const parent = getParent(obj, path.slice(0, -1));
|
||||
const key = path[path.length - 1]!;
|
||||
|
||||
if (type === Change.Add) {
|
||||
if (Array.isArray(parent)) {
|
||||
parent.splice(key as number, 1);
|
||||
} else {
|
||||
delete parent[key];
|
||||
}
|
||||
} else if (type === Change.Remove || type === Change.Edit) {
|
||||
parent[key] = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
function getParent(obj: Object, path: (string | number)[]): any {
|
||||
let current = obj;
|
||||
for (const key of path) {
|
||||
if (current[key] === undefined) {
|
||||
current[key] = typeof key === "number" ? [] : {};
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function clone<In extends Object>(obj: In): In {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export { diff, apply, revert, clone };
|
||||
Reference in New Issue
Block a user