_objectUpdate(diff, before, after, local, changes) {
    if (!diff.objectId) return
    if (this.observers[diff.objectId]) {
      for (let callback of this.observers[diff.objectId]) {
        callback(diff, before, after, local, changes)
      }
    }

    if (diff.type === 'map' && diff.props) {
      for (const propName of Object.keys(diff.props)) {
        for (const opId of Object.keys(diff.props[propName])) {
          this._objectUpdate(diff.props[propName][opId],
                             before && before[CONFLICTS] && before[CONFLICTS][propName] && before[CONFLICTS][propName][opId],
                             after && after[CONFLICTS] && after[CONFLICTS][propName] && after[CONFLICTS][propName][opId],
                             local, changes)
        }
      }

    } else if (diff.type === 'table' && diff.props) {
      for (const rowId of Object.keys(diff.props)) {
        for (const opId of Object.keys(diff.props[rowId])) {
          this._objectUpdate(diff.props[rowId][opId],
                             before && before.byId(rowId),
                             after && after.byId(rowId),
                             local, changes)
        }
      }

    } else if (diff.type === 'list' && diff.edits) {
      let offset = 0
      for (const edit of diff.edits) {
        if (edit.action === 'insert') {
          offset -= 1
          this._objectUpdate(edit.value, undefined,
                             after && after[CONFLICTS] && after[CONFLICTS][edit.index] && after[CONFLICTS][edit.index][edit.elemId],
                             local, changes)
        } else if (edit.action === 'multi-insert') {
          offset -= edit.values.length
        } else if (edit.action === 'update') {
          this._objectUpdate(edit.value,
                             before && before[CONFLICTS] && before[CONFLICTS][edit.index + offset] &&
                               before[CONFLICTS][edit.index + offset][edit.opId],
                             after && after[CONFLICTS] && after[CONFLICTS][edit.index] && after[CONFLICTS][edit.index][edit.opId],
                             local, changes)
        } else if (edit.action === 'remove') {
          offset += edit.count
        }
      }

    } else if (diff.type === 'text' && diff.edits) {
      let offset = 0
      for (const edit of diff.edits) {
        if (edit.action === 'insert') {
          offset -= 1
          this._objectUpdate(edit.value, undefined, after && after.get(edit.index), local, changes)
        } else if (edit.action === 'multi-insert') {
          offset -= edit.values.length
        } else if (edit.action === 'update') {
          this._objectUpdate(edit.value,
                             before && before.get(edit.index + offset),
                             after && after.get(edit.index),
                             local, changes)
        } else if (edit.action === 'remove') {
          offset += edit.count
        }
      }
    }
  }
Recursively walks a patch and calls the callbacks for all objects that
appear in the patch.