UNPKG

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
// 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);