@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
159 lines • 4.45 kB
JavaScript
/**
* @module runtime/diff-engine
* @description Computes differences between snapshots
*/
/**
* Compute difference between two snapshots
*/
export function computeDiff(from, to) {
const operations = [];
// Compare state objects
compareObjects(from.state, to.state, 'state', operations);
return {
fromId: `${from.requestId}_${from.timestamp}`,
toId: `${to.requestId}_${to.timestamp}`,
operations,
timestamp: Date.now(),
};
}
/**
* Apply diff operations to a snapshot
*/
export function applyDiff(snapshot, diff) {
const result = { ...snapshot, state: { ...snapshot.state } };
for (const op of diff.operations) {
const path = op.path.split('.').slice(1); // Remove 'state' prefix
if (op.op === 'add' || op.op === 'replace') {
setPath(result.state, path, op.newValue);
}
else if (op.op === 'remove') {
deletePath(result.state, path);
}
}
return result;
}
/**
* Compare two objects and generate diff operations
*/
function compareObjects(from, to, path, operations) {
const fromKeys = new Set(Object.keys(from));
const toKeys = new Set(Object.keys(to));
// Check for removed keys
for (const key of fromKeys) {
if (!toKeys.has(key)) {
operations.push({
op: 'remove',
path: `${path}.${key}`,
oldValue: from[key],
});
}
}
// Check for added or modified keys
for (const key of toKeys) {
const fromValue = from[key];
const toValue = to[key];
const keyPath = `${path}.${key}`;
if (!fromKeys.has(key)) {
// Added
operations.push({
op: 'add',
path: keyPath,
newValue: toValue,
});
}
else if (!deepEqual(fromValue, toValue)) {
// Modified
if (isObject(fromValue) && isObject(toValue)) {
compareObjects(fromValue, toValue, keyPath, operations);
}
else if (Array.isArray(fromValue) && Array.isArray(toValue)) {
compareArrays(fromValue, toValue, keyPath, operations);
}
else {
operations.push({
op: 'replace',
path: keyPath,
oldValue: fromValue,
newValue: toValue,
});
}
}
}
}
/**
* Compare two arrays and generate diff operations
*/
function compareArrays(from, to, path, operations) {
if (from.length !== to.length || !deepEqual(from, to)) {
operations.push({
op: 'replace',
path,
oldValue: from,
newValue: to,
});
}
}
/**
* Deep equality check
*/
function deepEqual(a, b) {
if (a === b)
return true;
if (a == null || b == null)
return false;
if (typeof a !== typeof b)
return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length)
return false;
return a.every((val, i) => deepEqual(val, b[i]));
}
if (isObject(a) && isObject(b)) {
const aObj = a;
const bObj = b;
const aKeys = Object.keys(aObj);
const bKeys = Object.keys(bObj);
if (aKeys.length !== bKeys.length)
return false;
return aKeys.every(key => deepEqual(aObj[key], bObj[key]));
}
return false;
}
/**
* Check if value is a plain object
*/
function isObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/**
* Set value at path in object
*/
function setPath(obj, path, value) {
if (path.length === 0)
return;
let current = obj;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
if (!isObject(current[key])) {
current[key] = {};
}
current = current[key];
}
current[path[path.length - 1]] = value;
}
/**
* Delete value at path in object
*/
function deletePath(obj, path) {
if (path.length === 0)
return;
let current = obj;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
if (!isObject(current[key]))
return;
current = current[key];
}
delete current[path[path.length - 1]];
}
//# sourceMappingURL=diff-engine.js.map