UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

186 lines (162 loc) 4.1 kB
/** * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact schukai GmbH. * * SPDX-License-Identifier: AGPL-3.0 */ import { isArray, isObject } from "../types/is.mjs"; import { typeOf } from "../types/typeof.mjs"; export { diff }; /** * With the diff function you can perform the change of one object to another. The result shows the changes of the second object to the first object. * * The operator `add` means that something has been added to the second object. `delete` means that something has been deleted from the second object compared to the first object. * * @externalExample ../../example/data/diff.mjs * @param {*} first * @param {*} second * @return {array} * @license AGPLv3 * @since 1.6.0 * @copyright schukai GmbH */ function diff(first, second) { return doDiff(first, second); } /** * @private * @param a * @param b * @param type * @return {Set<string>|Set<number>} */ function getKeys(a, b, type) { if (isArray(type)) { const keys = a.length > b.length ? new Array(a.length) : new Array(b.length); keys.fill(0); return new Set(keys.map((_, i) => i)); } return new Set(Object.keys(a).concat(Object.keys(b))); } /** * @private * @param a * @param b * @param path * @param diff * @return {array} */ function doDiff(a, b, path, diff) { const typeA = typeOf(a); const typeB = typeOf(b); const currPath = path || []; const currDiff = diff || []; if (typeA === typeB && (typeA === "object" || typeA === "array")) { getKeys(a, b, typeA).forEach((v) => { if (!Object.prototype.hasOwnProperty.call(a, v)) { currDiff.push(buildResult(a[v], b[v], "add", currPath.concat(v))); } else if (!Object.prototype.hasOwnProperty.call(b, v)) { currDiff.push(buildResult(a[v], b[v], "delete", currPath.concat(v))); } else { doDiff(a[v], b[v], currPath.concat(v), currDiff); } }); } else { const o = getOperator(a, b, typeA, typeB); if (o !== undefined) { currDiff.push(buildResult(a, b, o, path)); } } return currDiff; } /** * * @param {*} a * @param {*} b * @param {string} operator * @param {array} path * @return {{path: array, operator: string}} * @private */ function buildResult(a, b, operator, path) { const result = { operator, path, }; if (operator !== "add") { result.first = { value: a, type: typeof a, }; if (isObject(a)) { const name = Object.getPrototypeOf(a)?.constructor?.name; if (name !== undefined) { result.first.instance = name; } } } if (operator === "add" || operator === "update") { result.second = { value: b, type: typeof b, }; if (isObject(b)) { const name = Object.getPrototypeOf(b)?.constructor?.name; if (name !== undefined) { result.second.instance = name; } } } return result; } /** * @private * @param {*} a * @param {*} b * @return {boolean} */ function isNotEqual(a, b) { if (typeof a !== typeof b) { return true; } if (a instanceof Date && b instanceof Date) { return a.getTime() !== b.getTime(); } return a !== b; } /** * @private * @param {*} a * @param {*} b * @return {string|undefined} */ function getOperator(a, b) { /** * @type {string|undefined} */ let operator; /** * @type {string} */ const typeA = typeof a; /** * @type {string} */ const typeB = typeof b; if (typeA === "undefined" && typeB !== "undefined") { operator = "add"; } else if (typeA !== "undefined" && typeB === "undefined") { operator = "delete"; } else if (isNotEqual(a, b)) { operator = "update"; } return operator; }