dist/jsonpatcherproxy.js
/**
   * A callback to be used as the proxy set trap callback.
   * It updates parenthood map if needed, proxifies nested newly-added objects, calls default callback with the changes occurred.
   * @param {JSONPatcherProxy} instance JSONPatcherProxy instance
   * @param {Object} tree the affected object
   * @param {String} key the effect property's name
   * @param {Any} newValue the value being set
   */
A callback to be used as the proxy set trap callback.
It updates parenthood map if needed, proxifies nested newly-added objects, calls default callback with the changes occurred.
@param {JSONPatcherProxy} instance JSONPatcherProxy instance
@param {Object} tree the affected object
@param {String} key the effect property's name
@param {Any} newValue the value being set
A callback to be used as the proxy set trap callback . It updates parenthood map if needed proxifies nested newly - added objects calls default callback with the changes occurred . @param { JSONPatcherProxy } instance JSONPatcherProxy instance @param { Object } tree the affected object @param { String } key the effect property ' s name @param { Any } newValue the value being set
function trapForSet(instance, tree, key, newValue) {
    const pathToKey = getPathToTree(instance, tree) + '/' + escapePathComponent(key);
    const subtreeMetadata = instance._treeMetadataMap.get(newValue);
    if (instance._treeMetadataMap.has(newValue)) {
      instance._parenthoodMap.set(subtreeMetadata.originalObject, { parent: tree, key });
    }
    if (subtreeMetadata && !instance._isProxifyingTreeNow) {
      subtreeMetadata.inherited = true;
    }
    let warnedAboutNonIntegrerArrayProp = false;
    const isTreeAnArray = Array.isArray(tree);
    const isNonSerializableArrayProperty = isTreeAnArray && !Number.isInteger(+key.toString());
    if (
      newValue &&
      typeof newValue == 'object' &&
      !instance._treeMetadataMap.has(newValue)
    ) {
      if (isNonSerializableArrayProperty) {
        console.warn(`JSONPatcherProxy noticed a non-integer property ('${key}') was set for an array. This interception will not emit a patch. The value is an object, but it was not proxified, because it would not be addressable in JSON-Pointer`);
        warnedAboutNonIntegrerArrayProp = true;
      }
      else {
        instance._parenthoodMap.set(newValue, { parent: tree, key });
        newValue = instance._proxifyTreeRecursively(tree, newValue, key);
      }
    }
    const valueBeforeReflection = tree[key];
    const wasKeyInTreeBeforeReflection = tree.hasOwnProperty(key);
    if (isTreeAnArray && !isNonSerializableArrayProperty) {
      const index = parseInt(key, 10);
      if (index > tree.length) {
        trapForSet(instance, tree, (index - 1) + '', undefined);
      }
    }
    const reflectionResult = Reflect.set(tree, key, newValue);
    const operation = {
      op: 'remove',
      path: pathToKey
    };
    if (typeof newValue == 'undefined') {
      if (!isTreeAnArray && !wasKeyInTreeBeforeReflection) {
        return reflectionResult;
      } else {
        if (wasKeyInTreeBeforeReflection && !isSignificantChange(valueBeforeReflection, newValue, isTreeAnArray)) {
          return reflectionResult; 
        }
        if (isTreeAnArray) {
          operation.value = null;
          if (wasKeyInTreeBeforeReflection) {
            operation.op = 'replace';
          }
          else {
            operation.op = 'add';
          }
        }
        const oldSubtreeMetadata = instance._treeMetadataMap.get(valueBeforeReflection);
        if (oldSubtreeMetadata) {
          instance._parenthoodMap.delete(valueBeforeReflection);
          instance._disableTrapsForTreeMetadata(oldSubtreeMetadata);
          instance._treeMetadataMap.delete(oldSubtreeMetadata);
        }
      }
    } else {
      if (isNonSerializableArrayProperty) {
        if(key != 'length' && !warnedAboutNonIntegrerArrayProp) {
          console.warn(`JSONPatcherProxy noticed a non-integer property ('${key}') was set for an array. This interception will not emit a patch`);
        }
        return reflectionResult;
      }
      operation.op = 'add';
      if (wasKeyInTreeBeforeReflection) {
        if (typeof valueBeforeReflection !== 'undefined' || isTreeAnArray) {
          if (!isSignificantChange(valueBeforeReflection, newValue, isTreeAnArray)) {
            return reflectionResult; 
          }
          operation.op = 'replace'; 
        }
      }
      operation.value = newValue;
    }
    instance._defaultCallback(operation);
    return reflectionResult;
  }
function trapForSet ( instance , tree , key , newValue ) { const pathToKey = getPathToTree ( instance , tree ) + '/' + escapePathComponent ( key ) ; const subtreeMetadata = instance . _treeMetadataMap . get ( newValue ) ; if ( instance . _treeMetadataMap . has ( newValue ) ) { instance . _parenthoodMap . set ( subtreeMetadata . originalObject , { parent : tree , key } ) ; } if ( subtreeMetadata && ! instance . _isProxifyingTreeNow ) { subtreeMetadata . inherited = true ; } let warnedAboutNonIntegrerArrayProp = false ; const isTreeAnArray = Array . isArray ( tree ) ; const isNonSerializableArrayProperty = isTreeAnArray && ! Number . isInteger ( + key . toString ( ) ) ; if ( newValue && typeof newValue == 'object' && ! instance . _treeMetadataMap . has ( newValue ) ) { if ( isNonSerializableArrayProperty ) { console . warn ( ` ${ key } ` ) ; warnedAboutNonIntegrerArrayProp = true ; } else { instance . _parenthoodMap . set ( newValue , { parent : tree , key } ) ; newValue = instance . _proxifyTreeRecursively ( tree , newValue , key ) ; } } const valueBeforeReflection = tree [ key ] ; const wasKeyInTreeBeforeReflection = tree . hasOwnProperty ( key ) ; if ( isTreeAnArray && ! isNonSerializableArrayProperty ) { const index = parseInt ( key , 10 ) ; if ( index > tree . length ) { trapForSet ( instance , tree , ( index - 1 ) + '' , undefined ) ; } } const reflectionResult = Reflect . set ( tree , key , newValue ) ; const operation = { op : 'remove' , path : pathToKey } ; if ( typeof newValue == 'undefined' ) { if ( ! isTreeAnArray && ! wasKeyInTreeBeforeReflection ) { return reflectionResult ; } else { if ( wasKeyInTreeBeforeReflection && ! isSignificantChange ( valueBeforeReflection , newValue , isTreeAnArray ) ) { return reflectionResult ; } if ( isTreeAnArray ) { operation . value = null ; if ( wasKeyInTreeBeforeReflection ) { operation . op = 'replace' ; } else { operation . op = 'add' ; } } const oldSubtreeMetadata = instance . _treeMetadataMap . get ( valueBeforeReflection ) ; if ( oldSubtreeMetadata ) { instance . _parenthoodMap . delete ( valueBeforeReflection ) ; instance . _disableTrapsForTreeMetadata ( oldSubtreeMetadata ) ; instance . _treeMetadataMap . delete ( oldSubtreeMetadata ) ; } } } else { if ( isNonSerializableArrayProperty ) { if ( key != 'length' && ! warnedAboutNonIntegrerArrayProp ) { console . warn ( ` ${ key } ` ) ; } return reflectionResult ; } operation . op = 'add' ; if ( wasKeyInTreeBeforeReflection ) { if ( typeof valueBeforeReflection !== 'undefined' || isTreeAnArray ) { if ( ! isSignificantChange ( valueBeforeReflection , newValue , isTreeAnArray ) ) { return reflectionResult ; } operation . op = 'replace' ; } } operation . value = newValue ; } instance . _defaultCallback ( operation ) ; return reflectionResult ; }
A callback to be used as the proxy set trap callback.
A callback to be used as the proxy set trap callback .
/*
        mark already proxified values as inherited.
        rationale: proxy.arr.shift()
        will emit
        {op: replace, path: '/arr/1', value: arr_2}
        {op: remove, path: '/arr/2'}

        by default, the second operation would revoke the proxy, and this renders arr revoked.
        That's why we need to remember the proxies that are inherited.
      */ /*
    Why do we need to check instance._isProxifyingTreeNow?

    We need to make sure we mark revocables as inherited ONLY when we're observing,
    because throughout the first proxification, a sub-object is proxified and then assigned to
    its parent object. This assignment of a pre-proxified object can fool us into thinking
    that it's a proxified object moved around, while in fact it's the first assignment ever.

    Checking _isProxifyingTreeNow ensures this is not happening in the first proxification,
    but in fact is is a proxified object moved around the tree
    */ // if the new value is an object, make sure to watch it // This happens in Vue 1-2 (should not happen in Vue 3). See: https://github.com/vuejs/vue/issues/427, https://github.com/vuejs/vue/issues/9259 // let's start with this operation, and may or may not update it later // force call trapForSet for implicit undefined elements of the array added by the JS engine // because JSON-Patch spec prohibits adding an index that is higher than array.length // applying De Morgan's laws would be a tad faster, but less readable // `undefined` is being set to an already undefined value, keep silent // Value wasn't actually changed with respect to its JSON projection // when array element is set to `undefined`, should generate replace to `null` //TODO there is no test for this! /* array props (as opposed to indices) don't emit any patches, to avoid needless `length` patches */ // Value wasn't actually changed with respect to its JSON projection // setting `undefined` array elements is a `replace` op