diff --git a/actions/transactWriteItems.js b/actions/transactWriteItems.js new file mode 100644 index 0000000..06b27da --- /dev/null +++ b/actions/transactWriteItems.js @@ -0,0 +1,89 @@ +var async = require('async'), + putItem = require('./putItem'), + deleteItem = require('./deleteItem'), + updateItem = require('./updateItem'), + db = require('../db') + +module.exports = function transactWriteItem(store, data, cb) { + var actions = [] + + async.series([ + async.eachSeries.bind(async, data.TransactItems, addActions), + async.series.bind(async, actions), + ], function(err, responses) { + if (err) { + if (err.body && (/Missing the key/.test(err.body.message) || /Type mismatch for key/.test(err.body.message))) + err.body.message = 'The provided key element does not match the schema' + return cb(err) + } + var res = {UnprocessedItems: {}}, tableUnits = {} + + if (~['TOTAL', 'INDEXES'].indexOf(data.ReturnConsumedCapacity)) { + responses[1].forEach(function(action) { + var table = action.ConsumedCapacity.TableName + if (!tableUnits[table]) tableUnits[table] = 0 + tableUnits[table] += action.ConsumedCapacity.CapacityUnits + }) + res.ConsumedCapacity = Object.keys(tableUnits).map(function(table) { + return { + CapacityUnits: tableUnits[table], + TableName: table, + Table: data.ReturnConsumedCapacity == 'INDEXES' ? {CapacityUnits: tableUnits[table]} : undefined, + } + }) + } + + cb(null, res) + }) + + function addActions(transactItem, cb) { + var options = {} + var tableName + + if (data.ReturnConsumedCapacity) options.ReturnConsumedCapacity = data.ReturnConsumedCapacity + + if (transactItem.Put) { + tableName = transactItem.Put.TableName; + options = {TableName: tableName} + + store.getTable(tableName, function(err, table) { + if (err) return cb(err) + if ((err = db.validateItem(transactItem.Put.Item, table)) != null) return cb(err) + + options.Item = transactItem.Put.Item + actions.push(putItem.bind(null, store, options)) + db.createKey(options.Item, table) + + cb() + }) + } else if (transactItem.Delete) { + tableName = transactItem.Delete.TableName; + options = {TableName: tableName} + + store.getTable(tableName, function(err, table) { + if (err) return cb(err) + if ((err = db.validateKey(transactItem.Delete.Key, table) != null)) return cb(err) + + options.Key = transactItem.Delete.Key + actions.push(deleteItem.bind(null, store, options)) + + db.createKey(options.Key, table) + + cb() + }) + } else if (transactItem.Update) { + + store.getTable(tableName, function(err, table) { + if (err) return cb(err) + if ((err = db.validateKey(transactItem.Update.Key, table) != null)) return cb(err) + + options.Key = transactItem.Update.Key + actions.push(updateItem.bind(null, store, options)) + + db.createKey(options.Key, table) + + cb() + }) + } + } +} diff --git a/index.js b/index.js index 1c169ca..6a7385d 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ var MAX_REQUEST_BYTES = 16 * 1024 * 1024 var validApis = ['DynamoDB_20111205', 'DynamoDB_20120810'], validOperations = ['BatchGetItem', 'BatchWriteItem', 'CreateTable', 'DeleteItem', 'DeleteTable', 'DescribeTable', 'DescribeTimeToLive', 'GetItem', 'ListTables', 'PutItem', 'Query', 'Scan', 'TagResource', - 'UntagResource', 'ListTagsOfResource', 'UpdateItem', 'UpdateTable'], + 'UntagResource', 'ListTagsOfResource', 'UpdateItem', 'UpdateTable', 'TransactWriteItems'], actions = {}, actionValidations = {} diff --git a/validations/transactWriteItems.js b/validations/transactWriteItems.js new file mode 100644 index 0000000..6126ab9 --- /dev/null +++ b/validations/transactWriteItems.js @@ -0,0 +1,138 @@ +var validations = require('./index'), + db = require('../db') + +exports.types = { + ReturnConsumedCapacity: { + type: 'String', + enum: ['INDEXES', 'TOTAL', 'NONE'], + }, + ReturnItemCollectionMetrics: { + type: 'String', + enum: ['SIZE', 'NONE'], + }, + ClientRequestToken: { + type: 'String' + }, + TransactItems: { + type: 'List', + notNull: true, + lengthGreaterThanOrEqual: 1, + children: { + type: 'ValueStruct', + children: { + Put: { + type: 'FieldStruct', + children: { + TableName: { + type: 'String', + }, + ExpressionAttributeValues: { + type: 'Map', + children: 'AttrStruct', + }, + ExpressionAttributeNames: { + type: 'Map', + children: 'String', + }, + ReturnValuesOnConditionCheckFailure: { + type: 'String', + enum: ['ALL_OLD', 'NONE'], + }, + ConditionExpression: { + type: 'String', + }, + Item: { + type: 'Map', + notNull: true, + children: 'AttrStruct', + }, + }, + }, + Update: { + type: 'FieldStruct', + children: { + TableName: { + type: 'String', + }, + ExpressionAttributeValues: { + type: 'Map', + children: 'AttrStruct', + }, + ExpressionAttributeNames: { + type: 'Map', + children: 'String', + }, + ReturnValuesOnConditionCheckFailure: { + type: 'String', + enum: ['ALL_OLD', 'NONE'], + }, + ConditionExpression: { + type: 'String', + }, + Key: { + type: 'Map', + notNull: true, + children: 'AttrStruct', + }, + }, + }, + Delete: { + type: 'FieldStruct', + children: { + TableName: { + type: 'String', + }, + ExpressionAttributeValues: { + type: 'Map', + children: 'AttrStruct', + }, + ExpressionAttributeNames: { + type: 'Map', + children: 'String', + }, + ReturnValuesOnConditionCheckFailure: { + type: 'String', + enum: ['ALL_OLD', 'NONE'], + }, + ConditionExpression: { + type: 'String', + }, + Key: { + type: 'Map', + notNull: true, + children: 'AttrStruct', + }, + }, + }, + ConditionCheck: { + type: 'FieldStruct', + children: { + TableName: { + type: 'String', + }, + ExpressionAttributeValues: { + type: 'Map', + children: 'AttrStruct', + }, + ExpressionAttributeNames: { + type: 'Map', + children: 'String', + }, + ReturnValuesOnConditionCheckFailure: { + type: 'String', + enum: ['ALL_OLD', 'NONE'], + }, + ConditionExpression: { + type: 'String', + }, + Key: { + type: 'Map', + notNull: true, + children: 'AttrStruct', + }, + }, + }, + }, + }, + }, +}