lib/storage/dataset-clients.js
function doUpdateDatasetClientWithRecords(datasetClientId, fields, records, callback) {
  debug('doUpdateDatasetClientWithRecords datasetClientId = %s :: records.length = %d', datasetClientId, records.length);
  async.waterfall([
    function readDatasetClient(next) {
      doReadDatasetClient(datasetClientId, function(err, datasetClientJson){
        if (!datasetClientJson) {
          return next(new Error('can not find datasetClient with id ' + datasetClientId));
        } else {
          return next(err, datasetClientJson);
        }
      });
    },
    function createIndex(datasetClientJson, next) {
      ensureIndexesForDataset(datasetClientJson.datasetId);
      return next(null, datasetClientJson);
    },
    function listRecords(datasetClientJson, next) {
      //we don't need the data field here as it will not be used
      listDatasetClientRecords(datasetClientJson, {uid: 1, hash: 1}, function(err, localRecords) {
        return next(err, datasetClientJson, localRecords);
      });
    },
    //to reduce the number of db operations, we try to compute only the records that need to be changed here.
    function diff(datasetClientJson, localRecords, next) {
      var recordsDiff = diffRecords(syncUtil.convertToObject(localRecords), syncUtil.convertToObject(records));
      return next(null, datasetClientJson, recordsDiff);
    },
    function updateRecords(datasetClientJson, recordsDiff, next) {
      var datasetId = datasetClientJson.datasetId;
      upsertOrDeleteDatasetRecords(datasetId, datasetClientId, recordsDiff, function(err) {
        return next(err, datasetClientJson);
      });
    },
    //make sure we only update the dataset client at the end of the operation. Will should cause the globalHash value to be changed for the datasetClient, which should trigger clients to sync again.
    function updateDatasetClient(datasetClientJson, next) {
      doUpdateDatasetClient(datasetClientId, fields, false, next);
    },
    function invalidateCache(updatedDatasetClient, next) {
      var cacheKey = buildDatasetClientRecordsCachkey(datasetClientId);
      cacheClient.del(cacheKey);
      return next(null, updatedDatasetClient);
    }
  ], function(err, updatedDatasetClient) {
    if (err) {
      debugError('Failed to doUpdateDatasetClientWithRecords due to error %s :: datasetClientId = %s :: fields = %j', err, datasetClientId, fields);
    }
    return callback(err, updatedDatasetClient);
  });
}
function doUpdateDatasetClientWithRecords(datasetClientId, fields, records, callback) {
  debug('doUpdateDatasetClientWithRecords datasetClientId = %s :: records.length = %d', datasetClientId, records.length);
  async.waterfall([
    function readDatasetClient(next) {
      doReadDatasetClient(datasetClientId, function(err, datasetClientJson){
        if (!datasetClientJson) {
          return next(new Error('can not find datasetClient with id ' + datasetClientId));
        } else {
          return next(err, datasetClientJson);
        }
      });
    },
    function createIndex(datasetClientJson, next) {
      ensureIndexesForDataset(datasetClientJson.datasetId);
      return next(null, datasetClientJson);
    },
    function listRecords(datasetClientJson, next) {
      //we don't need the data field here as it will not be used
      listDatasetClientRecords(datasetClientJson, {uid: 1, hash: 1}, function(err, localRecords) {
        return next(err, datasetClientJson, localRecords);
      });
    },
    //to reduce the number of db operations, we try to compute only the records that need to be changed here.
    function diff(datasetClientJson, localRecords, next) {
      var recordsDiff = diffRecords(syncUtil.convertToObject(localRecords), syncUtil.convertToObject(records));
      return next(null, datasetClientJson, recordsDiff);
    },
    function updateRecords(datasetClientJson, recordsDiff, next) {
      var datasetId = datasetClientJson.datasetId;
      upsertOrDeleteDatasetRecords(datasetId, datasetClientId, recordsDiff, function(err) {
        return next(err, datasetClientJson);
      });
    },
    //make sure we only update the dataset client at the end of the operation. Will should cause the globalHash value to be changed for the datasetClient, which should trigger clients to sync again.
    function updateDatasetClient(datasetClientJson, next) {
      doUpdateDatasetClient(datasetClientId, fields, false, next);
    },
    function invalidateCache(updatedDatasetClient, next) {
      var cacheKey = buildDatasetClientRecordsCachkey(datasetClientId);
      cacheClient.del(cacheKey);
      return next(null, updatedDatasetClient);
    }
  ], function(err, updatedDatasetClient) {
    if (err) {
      debugError('Failed to doUpdateDatasetClientWithRecords due to error %s :: datasetClientId = %s :: fields = %j', err, datasetClientId, fields);
    }
    return callback(err, updatedDatasetClient);
  });
}
Save datasetClient json object to the `fhsync-datasetClients` collection, as well as the associated records for the datasetClient to the `fhsync-<datasetId>-records` collection
@param {String} datasetClientId the id of the datasetClient
@param {storage~DatasetClient} fields the fields that need to be updated for the given datasetClient
@param {storage~DatasetClientRecord[]} records the array of records that needs to be saved to the `fhsync-<datasetId>-records` collection
@param {Function} callback