documentdb-utils
Version:
Drop-in replacement+extensions for Azure's DocumentDB node.js client with auto-retry on 429 errors plus a lot more
425 lines (396 loc) • 15.9 kB
JavaScript
// Generated by CoffeeScript 1.9.2
(function() {
var DocumentClient, MAC_SIGNATURE_STRING, RETRY_ERROR_CODES, WrappedClient, WrappedQueryIterator, _, async, convertParametersArrayToSQLFromMongo, delay, isRetryError, reduceResults, sqlFromMongo, wrapArrayMultiMethod, wrapCallbackMethod, wrapExecuteStoredProcedure, wrapMultiMethod, wrapQueryIteratorMethod, wrapQueryIteratorMethodForArray, wrapSimpleMethod, wrapToArray, wrapToCreateArrayAsyncJSIterator, wrapToCreateAsyncJSIterator,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
slice = [].slice;
_ = require('lodash');
async = require('async');
DocumentClient = require('documentdb').DocumentClient;
sqlFromMongo = require('sql-from-mongo').sqlFromMongo;
delay = function(ms, func) {
return setTimeout(func, ms);
};
RETRY_ERROR_CODES = [429, 449, 503];
MAC_SIGNATURE_STRING = "The MAC signature found";
isRetryError = function(err) {
var ref;
return (ref = err.code, indexOf.call(RETRY_ERROR_CODES, ref) >= 0) || (err.code === 401 && err.body.indexOf(MAC_SIGNATURE_STRING) >= 0);
};
WrappedQueryIterator = (function() {
function _Class(_iterator1, retriesAllowed) {
var _method, methodName, ref;
this._iterator = _iterator1;
ref = this._iterator;
for (methodName in ref) {
_method = ref[methodName];
if (methodName === 'executeNext' || methodName === 'forEach' || methodName === 'nextItem') {
this[methodName] = wrapCallbackMethod(this._iterator, _method, retriesAllowed);
} else if (methodName === 'toArray') {
this[methodName] = wrapToArray(this);
} else {
this[methodName] = wrapSimpleMethod(this._iterator, _method);
}
}
}
return _Class;
})();
convertParametersArrayToSQLFromMongo = function(parameters) {
var collectionName, fields, i, index, len, mongoObject, parameter;
for (index = i = 0, len = parameters.length; i < len; index = ++i) {
parameter = parameters[index];
if (_.isString(parameter) && _.startsWith(parameter, "SELECT")) {
return parameters;
} else if (_.isPlainObject(parameter) && _.isString(parameter.query) && (parameter.parameters != null)) {
return parameters;
} else if (_.isPlainObject(parameter) && ((parameter.mongoObject != null) || (parameter.query != null))) {
mongoObject = parameter.mongoObject, collectionName = parameter.collectionName, fields = parameter.fields;
if ((parameter.query != null) && (mongoObject == null)) {
mongoObject = parameter.query;
}
if (collectionName == null) {
collectionName = 'c';
}
if (fields == null) {
fields = '*';
}
parameters[index] = sqlFromMongo(mongoObject, collectionName, fields);
return parameters;
}
}
return parameters;
};
wrapQueryIteratorMethod = function(_client, _method, retriesAllowed) {
var f;
f = function() {
var _iterator, parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
parameters = convertParametersArrayToSQLFromMongo(parameters);
_iterator = _method.call.apply(_method, [_client].concat(slice.call(parameters)));
return new WrappedQueryIterator(_iterator, retriesAllowed);
};
return f;
};
wrapQueryIteratorMethodForArray = function(_client, _method, retriesAllowed) {
var f;
f = function() {
var _iterator, callback, iterator, parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
parameters = convertParametersArrayToSQLFromMongo(parameters);
callback = parameters.pop();
_iterator = _method.call.apply(_method, [_client].concat(slice.call(parameters)));
iterator = new WrappedQueryIterator(_iterator, retriesAllowed);
return iterator.toArray(callback);
};
return f;
};
wrapToArray = function(iterator) {
var f;
f = function(callback) {
var all, innerF, stats;
all = [];
stats = {
roundTripCount: 0,
retries: 0,
requestUnitCharges: 0,
totalDelay: 0,
totalTime: 0
};
innerF = function() {
return iterator.executeNext(function(err, response, headers, roundTripCount, retries, totalDelay, totalTime, requestUnitCharges) {
stats.roundTripCount++;
stats.retries += retries;
stats.requestUnitCharges += requestUnitCharges;
stats.totalDelay += totalDelay;
stats.totalTime += totalTime;
if (err != null) {
return callback(err, all, stats);
} else {
all = all.concat(response);
if (iterator.hasMoreResults()) {
return innerF();
} else {
return callback(err, all, stats);
}
}
});
};
return innerF();
};
return f;
};
wrapSimpleMethod = function(that, _method) {
var f;
f = function() {
var parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
return _method.call.apply(_method, [that].concat(slice.call(parameters)));
};
return f;
};
wrapToCreateAsyncJSIterator = function(that, _method) {
var f;
f = function(item, callback) {
return _method.call.apply(_method, [that].concat(slice.call(item), [function(err, response, headers, roundTripCount, retries, totalDelay, totalTime, requestUnitCharges) {
return callback(err, {
response: response,
headers: headers,
roundTripCount: roundTripCount,
retries: retries,
totalDelay: totalDelay,
totalTime: totalTime,
requestUnitCharges: requestUnitCharges
});
}]));
};
return f;
};
wrapToCreateArrayAsyncJSIterator = function(that, _method) {
var f;
f = function(item, callback) {
return _method.call.apply(_method, [that].concat(slice.call(item), [function(err, all, stats) {
return callback(err, {
all: all,
stats: stats
});
}]));
};
return f;
};
reduceResults = function(memo, result) {
var key, value;
if (memo == null) {
memo = {};
}
for (key in result) {
value = result[key];
if (_.isArray(value)) {
if (memo[key] != null) {
memo[key] = memo[key].concat(value);
} else {
memo[key] = value;
}
} else if (_.isNumber(value)) {
if (memo[key] != null) {
memo[key] += value;
} else {
memo[key] = value;
}
} else if (_.isPlainObject(value)) {
if (memo[key] != null) {
memo[key].push(value);
} else {
memo[key] = [value];
}
}
}
return memo;
};
wrapMultiMethod = function(that, asyncJSIterator) {
var f;
f = function() {
var callback, items, link, linkArray, parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
callback = parameters.pop();
linkArray = parameters.shift();
if (!_.isArray(linkArray)) {
linkArray = [linkArray];
}
items = (function() {
var i, len, results1;
results1 = [];
for (i = 0, len = linkArray.length; i < len; i++) {
link = linkArray[i];
results1.push([link].concat(parameters));
}
return results1;
})();
return async.map(items, asyncJSIterator, function(err, results) {
var accumulatedResults, i, len, result;
accumulatedResults = {};
for (i = 0, len = results.length; i < len; i++) {
result = results[i];
accumulatedResults = reduceResults(accumulatedResults, result);
}
return callback(err, accumulatedResults);
});
};
return f;
};
wrapArrayMultiMethod = function(that, asyncJSIterator) {
var f;
f = function() {
var callback, items, link, linkArray, parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
callback = parameters.pop();
linkArray = parameters.shift();
if (!_.isArray(linkArray)) {
linkArray = [linkArray];
}
items = (function() {
var i, len, results1;
results1 = [];
for (i = 0, len = linkArray.length; i < len; i++) {
link = linkArray[i];
results1.push([link].concat(parameters));
}
return results1;
})();
return async.map(items, asyncJSIterator, function(err, results) {
var accumulatedStats, all, i, len, result;
all = [];
accumulatedStats = {};
for (i = 0, len = results.length; i < len; i++) {
result = results[i];
all = all.concat(result.all);
accumulatedStats = reduceResults(accumulatedStats, result.stats);
}
return callback(err, {
all: all,
stats: accumulatedStats
});
});
};
return f;
};
wrapCallbackMethod = function(that, _method, retriesAllowed) {
var f;
f = function() {
var callback, innerF, parameters, requestUnitCharges, retries, roundTripCount, startTime, totalDelay;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
startTime = new Date();
roundTripCount = 0;
retries = 0;
totalDelay = 0;
requestUnitCharges = 0;
callback = parameters.pop();
innerF = function() {
var parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
return _method.call.apply(_method, [that].concat(slice.call(parameters), [function(err, response, headers) {
var retryAfter;
roundTripCount++;
if (err != null) {
if (isRetryError(err) && retries <= retriesAllowed) {
retryAfter = headers['x-ms-retry-after-ms'] || 0;
retryAfter = Number(retryAfter);
retries++;
totalDelay += retryAfter;
requestUnitCharges += Number(headers['x-ms-request-charge']) || 0;
delay(retryAfter, function() {
return innerF.apply(null, parameters);
});
} else {
return callback(err, response, headers, roundTripCount, retries, totalDelay, new Date() - startTime, requestUnitCharges);
}
} else {
return callback(err, response, headers, roundTripCount, retries, totalDelay, new Date() - startTime, requestUnitCharges);
}
}]));
};
return innerF.apply(null, parameters);
};
return f;
};
wrapExecuteStoredProcedure = function(that, _method, retriesAllowed) {
var f;
f = function() {
var callback, innerF, parameters, requestUnitCharges, retries, roundTripCount, startTime, totalDelay;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
startTime = new Date();
roundTripCount = 0;
retries = 0;
totalDelay = 0;
requestUnitCharges = 0;
callback = parameters.pop();
innerF = function() {
var parameters;
parameters = 1 <= arguments.length ? slice.call(arguments, 0) : [];
return _method.call.apply(_method, [that].concat(slice.call(parameters), [function(err, response, headers) {
var retryAfter;
roundTripCount++;
if (err != null) {
if (isRetryError(err) && retries <= retriesAllowed) {
retryAfter = headers['x-ms-retry-after-ms'] || 0;
retryAfter = Number(retryAfter);
retries++;
totalDelay += retryAfter;
requestUnitCharges += Number(headers['x-ms-request-charge']) || 0;
delay(retryAfter, function() {
return innerF.apply(null, parameters);
});
} else {
return callback(err, response, headers, roundTripCount, retries, totalDelay, new Date() - startTime, requestUnitCharges);
}
} else {
if (response.continuation != null) {
parameters[1] = response;
return innerF.apply(null, parameters);
} else {
return callback(err, response, headers, roundTripCount, retries, totalDelay, new Date() - startTime, requestUnitCharges);
}
}
}]));
};
return innerF.apply(null, parameters);
};
return f;
};
module.exports = WrappedClient = (function() {
function WrappedClient(urlConnection, auth, connectionPolicy, consistencyLevel) {
var _method, asyncJSMethod, firstParameterIsLink, hasArrayVersion, indexOfLeftParen, indexOfRightParen, lastParameterIsCallback, masterKey, methodName, methodNameToWrap, methodSpec, multiMethod, parameterList, parameterListString, ref, ref1, ref2;
this.urlConnection = urlConnection;
this.auth = auth;
this.connectionPolicy = connectionPolicy;
this.consistencyLevel = consistencyLevel;
if ((((ref = this.urlConnection) != null ? ref.readDocuments : void 0) != null) && (((ref1 = this.urlConnection) != null ? ref1.queryDocuments : void 0) != null)) {
this._client = this.urlConnection;
} else {
this.urlConnection = this.urlConnection || process.env.DOCUMENT_DB_URL;
masterKey = process.env.DOCUMENT_DB_KEY;
this.auth = this.auth || {
masterKey: masterKey
};
this._client = new DocumentClient(this.urlConnection, this.auth, this.connectionPolicy, this.consistencyLevel);
}
this.retriesAllowed = 3;
ref2 = this._client;
for (methodName in ref2) {
_method = ref2[methodName];
if (typeof _method !== 'function') {
continue;
}
hasArrayVersion = _.startsWith(methodName, 'query') || _.startsWith(methodName, 'read') && _.endsWith(methodName, 's');
methodSpec = _method.toString().split('\n')[0];
indexOfLeftParen = methodSpec.indexOf('(');
indexOfRightParen = methodSpec.indexOf(')');
parameterListString = methodSpec.substr(indexOfLeftParen + 1, indexOfRightParen - indexOfLeftParen - 1);
parameterList = parameterListString.split(', ');
firstParameterIsLink = _.endsWith(parameterList[0], 'Link');
lastParameterIsCallback = parameterList[parameterList.length - 1] === 'callback';
if (methodName === 'executeStoredProcedure') {
this[methodName] = wrapExecuteStoredProcedure(this._client, _method, this.retriesAllowed);
} else if (lastParameterIsCallback) {
this[methodName] = wrapCallbackMethod(this._client, _method, this.retriesAllowed);
} else if (hasArrayVersion) {
this[methodName] = wrapQueryIteratorMethod(this._client, _method, this.retriesAllowed);
this[methodName + 'Array'] = wrapQueryIteratorMethodForArray(this._client, _method, this.retriesAllowed);
} else {
}
if (firstParameterIsLink) {
if (hasArrayVersion) {
methodNameToWrap = methodName + 'Array';
asyncJSMethod = wrapToCreateArrayAsyncJSIterator(this, this[methodNameToWrap]);
multiMethod = wrapArrayMultiMethod(this, asyncJSMethod);
} else {
methodNameToWrap = methodName;
asyncJSMethod = wrapToCreateAsyncJSIterator(this, this[methodNameToWrap]);
multiMethod = wrapMultiMethod(this, asyncJSMethod);
}
this[methodNameToWrap + 'AsyncJSIterator'] = asyncJSMethod;
this[methodNameToWrap + 'Multi'] = multiMethod;
}
}
}
return WrappedClient;
})();
}).call(this);