dynamite
Version:
promise-based DynamoDB client
215 lines (179 loc) • 5.37 kB
JavaScript
var util = require('util')
var Q = require('kew')
var errors = require('./errors')
var DynamoResponse = require('./DynamoResponse')
/**
* @constructor
* @param {Object} options
*/
function Builder(options) {
this._options = options || {}
this._retryHandler = this._options.retryHandler
}
Builder.prototype.setHashKey = function (keyName, keyVal) {
this._hashKey = {
name: keyName,
val: keyVal
}
return this
}
Builder.prototype.setRangeKey = function (keyName, keyVal) {
this._rangeKey = {
name: keyName,
val: keyVal
}
return this
}
Builder.prototype.getRetryHandler = function () {
return this._retryHandler
}
Builder.prototype.setRetryHandler = function (retryHandler) {
this._retryHandler = retryHandler
return this
}
Builder.prototype.getOptions = function () {
return this._options
}
Builder.prototype.setDatabase = function (db) {
this._db = db
return this
}
Builder.prototype.setConsistent = function (isConsistent) {
this._isConsistent = isConsistent
return this
}
Builder.prototype.consistentRead = function () {
return this.setConsistent(true)
}
Builder.prototype.getPrefix = function () {
return this._tablePrefix
}
Builder.prototype.setPrefix = function (prefix) {
this._tablePrefix = prefix
return this
}
Builder.prototype.setTable = function (table) {
this._table = table
return this
}
Builder.prototype.setLimit = function (limit) {
this._limit = limit
return this
}
Builder.prototype.scanForward = function () {
this._shouldScanForward = true
return this
}
Builder.prototype.scanBackward = function () {
this._shouldScanForward = false
return this
}
Builder.prototype.getCount = function () {
this._isCount = true
return this
}
Builder.prototype.withFilter = function (filter) {
if (!this._filters) this._filters = []
if (filter) this._filters.push(filter)
return this
}
Builder.prototype.withCondition = function (condition) {
if (!this._conditions) this._conditions = []
if (condition) this._conditions.push(condition)
return this
}
Builder.prototype.selectAttributes = function (attributes) {
if (!attributes) return this
if (!Array.isArray(attributes)) attributes = Array.prototype.slice.call(arguments, 0)
this._attributes = attributes
return this
}
/** @this {*} */
Builder.prototype.emptyResults = function (e) {
if (e.message === 'Requested resource not found') return {results:[]}
throw e
}
/** @this {*} */
Builder.prototype.emptyResult = function (e) {
if (e.message === 'Requested resource not found') return {results:null}
throw e
}
Builder.prototype.request = function (method, data) {
if (this._options.logQueries) {
this.logQuery(method, data)
}
var defer = Q.defer()
if (!this._db.isFakeDynamo) {
delete data._requestBuilder
}
var req = this._db[method](data, defer.makeNodeResolver())
var startedAt = Date.now()
var retryHandler = this.getRetryHandler()
var table = this._table
var processingStartedAt, byteLength, requestLatencyMs
// FakeDynamo doesn't return a request object
if (req && req.on) {
req.on('httpDone', function (res) {
var now = Date.now()
processingStartedAt = now
requestLatencyMs = now - startedAt
if (res && res.httpResponse) {
byteLength = res.httpResponse.headers && res.httpResponse.headers['content-length']
}
})
if (retryHandler) {
req.on('retry', function (res) {
retryHandler(method, table, res)
})
}
}
return defer.then(function (output) {
output.ByteLength = byteLength
output.ProcessingStartedAt = processingStartedAt
output.RequestLatencyMs = requestLatencyMs
return output
})
}
Builder.prototype.logQuery = function (method, data) {
var cyanBold, cyan, reset
cyanBold = '\u001b[1;36m'
cyan = '\u001b[0;36m'
reset = '\u001b[0m'
console.info(cyanBold + method + cyan)
console.info(util.inspect(data, {depth: null}))
console.info(reset)
}
Builder.prototype.prepareOutput = function (output) {
return new DynamoResponse(this._tablePrefix, output, null)
}
Builder.prototype.convertErrors = function (context, err) {
// Errors in Dynamo response are JSON objects like this:
// {
// "message":"Attribute found when none expected.",
// "code":"ConditionalCheckFailedException",
// "name":"ConditionalCheckFailedException",
// "statusCode":400,
// "retryable":false
// }
//
// More at http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html
//
// To be more reliable, we check both err.name and err.code.
// Dynamo doc specifies the value of "code", so the error object
// should have "code" assigned. The node.js SDK assigns "code"
// to "name". "name" is the standard attribute of javascript Error
// object, so we double-check it.
var data = context.data
var isWrite = !!context.isWrite
switch (err.code || err.name) {
case 'ConditionalCheckFailedException':
throw new errors.ConditionalError(data, err.message, err.requestId)
case 'ProvisionedThroughputExceededException':
throw new errors.ProvisioningError(data, err.message, isWrite, err.requestId)
case 'ValidationException':
throw new errors.ValidationError(data, err.message, isWrite, err.requestId)
default:
throw err
}
}
module.exports = Builder