UNPKG

differify-js

Version:

Differify allows you to get the diff between two entities (objects diff, arrays diff, date diff, functions diff, number diff, etc) very easily, quickly and in a friendly way.

219 lines 8.64 kB
/*! * Copyright(c) 2020 Fabian Roberto Orue <fabianorue@gmail.com> * BSD Licensed */ import DIFF_MODES from './enums/modes'; import { isArray } from './utils/validations'; import { buildDiff } from './property-diff-model'; import Configuration from './config-builder'; import PROPERTY_STATUS from './enums/property-status'; import { valueRefEqualityComparator } from './comparators'; import comparatorSelector from './comparator-selector'; const INVALID_VAL = Symbol('invalid'); function diff(comparatorSelector, a, b) { // here, we avoid comparing by reference because of the nested objects can be changed const aType = typeof a; const bType = typeof b; if (aType !== bType) { return buildDiff(a, b, PROPERTY_STATUS.MODIFIED, 1); } const comparator = comparatorSelector.getComparatorByType(aType); return comparator ? comparator(a, b) : valueRefEqualityComparator(a, b); } /** * It returns a normalized output based on the type of the * input when the output is invalid. * @param inputData * @param outputData * @returns */ function normalizeInvalidOutputFormat(inputData, outputData) { return outputData === INVALID_VAL ? Array.isArray(inputData) ? [] : {} : outputData; } const applyChanges = (next, selector) => { if (isArray(next)) { const list = []; let curr; for (let i = 0; i < next.length; i++) { curr = selector(next[i]); if (curr !== INVALID_VAL) { list.push(curr); } } return list.length === 0 ? INVALID_VAL : list; } if (typeof next === 'object') { const o = {}; let curr; let atLeastOneProp = false; /* eslint-disable no-debugger,guard-for-in */ for (const i in next) { if (Object.prototype.hasOwnProperty.call(next, i)) { curr = selector(next[i]); if (curr !== INVALID_VAL) { o[i] = curr; atLeastOneProp = true; } } } /* eslint-enable no-alert,guard-for-in */ return atLeastOneProp ? o : INVALID_VAL; } return selector(next); }; const rightChangeSelector = (curr) => { if (curr._) { return applyChanges(curr._, rightChangeSelector); } return curr.status === PROPERTY_STATUS.DELETED ? curr.original : curr.current; }; const leftChangeSelector = (curr) => { if (curr._) { return applyChanges(curr._, leftChangeSelector); } return curr.status === PROPERTY_STATUS.ADDED ? curr.current : curr.original; }; const diffChangeSelectorCreator = (selector) => { const diffChangeSelector = (curr) => { if (curr._ && curr.changes > 0) { return applyChanges(curr._, diffChangeSelector); } return curr.status === PROPERTY_STATUS.EQUAL ? INVALID_VAL : selector(curr); }; return diffChangeSelector; }; const statusSelectorCreator = (status) => { const property = status === PROPERTY_STATUS.DELETED ? 'original' : 'current'; const check = status === PROPERTY_STATUS.EQUAL; const statusChangeSelector = (curr) => { if (curr._ && (check || curr.changes > 0)) { return applyChanges(curr._, statusChangeSelector); } return curr.status === status ? curr[property] : INVALID_VAL; }; return statusChangeSelector; }; const statusSelectorCreatorExtendedInformation = (status) => { const check = status === PROPERTY_STATUS.EQUAL; const statusChangeSelector = (curr) => { if (curr._ && (check || curr.changes > 0)) { return applyChanges(curr._, statusChangeSelector); } return curr.status === status ? { current: curr.current, original: curr.original } : INVALID_VAL; }; return statusChangeSelector; }; const getValidStatus = (status) => { if (typeof status === 'string') { const s = status.trim().toUpperCase(); return Object.keys(PROPERTY_STATUS).find((prop) => s === prop) !== undefined ? s : null; } return null; }; const isValidPropertyDescriptor = (prop) => prop && 'original' in prop && 'current' in prop && 'status' in prop; class Differify { constructor(config) { this.compSelector = comparatorSelector(); this.config = null; /** * It sets the configuration options that will be applied when compare() method is called. * @param _config */ this.setConfig = (_config) => { this.config = new Configuration(_config); this.compSelector.configure(this.config); }; /** * It returns a copy of the current configuration object. * @returns {config} */ this.getConfig = () => { return { compareArraysInOrder: this.config.compareArraysInOrder, mode: Object.assign({}, this.config.mode), }; }; /** * It returns the difference between two entities. * @param a * @param b * @returns {multiPropDiff} */ this.compare = (a, b) => { return diff(this.compSelector, a, b); }; /** * It will apply the changes (merge both entities) and will keep the modified values * @param {multiPropDiff} diffResult | it is the Object returned by the compare() method call. * @param {boolean} diffOnly | It returns just the difference (only the !== EQUAL properties) [default: false]. * @returns {Object|Array} */ this.applyLeftChanges = (diffResult, diffOnly = false) => { if (diffResult && diffResult._) { return normalizeInvalidOutputFormat(diffResult._, applyChanges(diffResult._, diffOnly ? diffChangeSelectorCreator(leftChangeSelector) : leftChangeSelector)); } if (isValidPropertyDescriptor(diffResult)) { return diffResult.original; } return null; }; /** * It will apply the changes (merge both entities) and will keep the modified values * @param {multiPropDiff} diffResult | it is the Object returned by the compare() method call. * @param {boolean} diffOnly | It returns just the difference (only the !== EQUAL properties) * @returns {Object} */ this.applyRightChanges = (diffResult, diffOnly = false) => { if (diffResult && diffResult._) { return normalizeInvalidOutputFormat(diffResult._, applyChanges(diffResult._, diffOnly ? diffChangeSelectorCreator(rightChangeSelector) : rightChangeSelector)); } if (isValidPropertyDescriptor(diffResult)) { return diffResult.current; } return null; }; /** * It will return the changes that match with the specified status (second parameter). * @param {multiPropDiff} diffResult | It is the Object returned by the compare() method call. * @param {boolean} status | one of the following (ADDED || MODIFIED || DELETED || EQUAL). * @returns {Object|Array} | depending on if the input is an Object or an Array. */ this.filterDiffByStatus = (diffResult, status = PROPERTY_STATUS.MODIFIED, extendedInformation = false) => { const propStatus = getValidStatus(status); if (propStatus && diffResult) { if (diffResult._) { return normalizeInvalidOutputFormat(diffResult._, applyChanges(diffResult._, extendedInformation ? statusSelectorCreatorExtendedInformation(status) : statusSelectorCreator(status))); } if (isValidPropertyDescriptor(diffResult) && diffResult.status === propStatus) { const selector = extendedInformation ? statusSelectorCreatorExtendedInformation(status) : statusSelectorCreator(status); return selector(diffResult); } } return null; }; this.config = new Configuration(config); this.compSelector.configure(this.config); } } Differify.DIFF_MODES = DIFF_MODES; Differify.PROPERTY_STATUS = PROPERTY_STATUS; export default Differify; export { DIFF_MODES, PROPERTY_STATUS }; //# sourceMappingURL=differify.js.map