UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

194 lines 7.58 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createChangeLog = exports.getChangeLog = exports.ChangeLog = exports.CHANGELOG_METADATA_KEY = void 0; const DataSerializerUtils_1 = require("../DataSerializerUtils"); exports.CHANGELOG_METADATA_KEY = Symbol('__changelog__'); /** * Change log for tracking changes on an object */ class ChangeLog { constructor() { /** * Changes */ this.changes = []; } /** * Reset the log with the assumption that the object has been saved */ reset() { this.changes = []; } /** * Add a change to the log * @param property {string} Property key * @param oldValue {any} Old value * @param newValue {any} New value */ addChange(property, oldValue, newValue) { if (oldValue === newValue) { return; } this.changes.push({ property, oldValue, newValue, date: new Date(), }); } /** * Get the latest changes * @returns {Change[]} Latest changes */ getLatestChanges() { // Get the changes per property const changesPerProperty = {}; this.changes.forEach((change) => { if (!changesPerProperty[change.property]) { changesPerProperty[change.property] = []; } changesPerProperty[change.property].push(change); }); // Sort the changes by date Object.keys(changesPerProperty).forEach((property) => { changesPerProperty[property].sort((a, b) => a.date.getTime() - b.date.getTime()); }); // Filter out changes that end with the same value as the initial state const unchangedProperties = []; Object.keys(changesPerProperty).forEach((property) => { const lastIndex = changesPerProperty[property].length - 1; if (changesPerProperty[property][0].oldValue === changesPerProperty[property][lastIndex].newValue) { unchangedProperties.push(property); } }); // Remove the unchanged properties Object.keys(changesPerProperty).forEach((property) => { changesPerProperty[property] = changesPerProperty[property].filter(() => !unchangedProperties.includes(property)); }); // Aggregate all changes of each properties const changes = Object.keys(changesPerProperty) .map((property) => { const lastIndex = changesPerProperty[property].length - 1; const firstChange = changesPerProperty[property][0]; const lastChange = changesPerProperty[property][lastIndex]; if (lastChange) { return { property, oldValue: firstChange.oldValue, newValue: lastChange.newValue, date: lastChange.date, }; } return undefined; }) .filter((p) => p !== undefined); return changes; } /** * Get the deleted properties * @returns {string[]} Deleted properties */ getDeletedProperties() { return this.getLatestChanges() .filter((change) => change.newValue === undefined) .map((change) => change.property); } /** * Get the added properties * @returns {string[]} Added properties */ getAddedProperties() { return this.getLatestChanges() .filter((change) => change.oldValue === undefined) .map((change) => change.property); } } exports.ChangeLog = ChangeLog; /** * Get the change log of an object * @param target Target object */ function getChangeLog(target) { return target[exports.CHANGELOG_METADATA_KEY]; } exports.getChangeLog = getChangeLog; const IGNORED_TYPES = [Uint8Array, Date]; /** * Create a change log for an object * @param target Target object */ function createChangeLog(target) { target[exports.CHANGELOG_METADATA_KEY] = new ChangeLog(); // Wrap all data members with a changelog to track deep changes const metadata = DataSerializerUtils_1.DataSerializerUtils.getOwnMetadata(target.constructor); if (metadata) { const watchedProperties = []; metadata.dataMembers.forEach((member) => { watchedProperties.push(member.key); if (target[member.key]) { if (Array.isArray(target[member.key])) { target[member.key].forEach((element) => { if (element instanceof Object) { element = createChangeLog(element); } }); // Wrap the array in a proxy to track changes target[member.key] = new Proxy(target[member.key], { set: (arr, index, value) => { const oldArray = [...arr]; arr[index] = value; const newArray = [...arr]; target[exports.CHANGELOG_METADATA_KEY].addChange(member.key, oldArray, newArray); return true; }, }); } else if (target[member.key] instanceof Map || target[member.key] instanceof Set) { target[member.key].forEach((element) => { if (element instanceof Object) { element = createChangeLog(element); } }); // The map itself should also be watched target[member.key] = createChangeLog(target[member.key]); } else if (target[member.key] instanceof Object) { // Only wrap objects that are not ignored if (!IGNORED_TYPES.includes(target[member.key].constructor)) { target[member.key] = createChangeLog(target[member.key]); } } } }); // Wrap the target in a proxy to track changes const proxy = new Proxy(target, { set: (obj, prop, value) => { if (watchedProperties.includes(prop.toString())) { if (obj[prop] !== value) { obj[exports.CHANGELOG_METADATA_KEY].addChange(prop.toString(), obj[prop], value); } obj[prop] = value; } else { // Get the current state of watched properties const currentState = {}; watchedProperties.forEach((watchedProperty) => { currentState[watchedProperty] = obj[watchedProperty]; }); obj[prop] = value; // Determine if a setter modified another variable watchedProperties.forEach((watchedProperty) => { if (currentState[watchedProperty] !== obj[watchedProperty]) { obj[exports.CHANGELOG_METADATA_KEY].addChange(watchedProperty, currentState[watchedProperty], obj[watchedProperty]); } }); } return true; }, }); return proxy; } return target; } exports.createChangeLog = createChangeLog; //# sourceMappingURL=ChangeLog.js.map