'use strict'
const AWS = require('aws-sdk')
const db = new AWS.DynamoDB.DocumentClient({ region: process.env.AWS_REGION })
/**
* @namespace dynamodb
*/
const dynamodb = {}
/**
* Add a new item to a DynamoDB table
* @param {string} table - The name of the table to modify
* @param {object} args - The properties to apply to the new item
* @return {promise} - A promise that resolves on completion
*/
dynamodb.put = (table, args) => {
let item = {}
// Remove empty strings from query
Object.entries(args).forEach(([key, value]) => {
if (value !== '') {
item = {
...item,
[key]: value,
}
}
})
const params = {
TableName: table,
Item: item,
}
return new Promise((resolve, reject) => {
db.put(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Update an existing item in a DynamoDB table
* @param {string} table - The name of the table to modify
* @param {string} field - The name of the property to select the item by
* @param {object} args - The new properties to apply to the item
* @return {promise} - A promise that resolves on completion
*/
dynamodb.update = (table, field, args) => {
let expression = 'set'
let values = {}
let names = {}
Object.entries(args).forEach(([key, value]) => {
// Ignore the primary key
if (field === key) {
return
}
// Build expression string
expression += ` #${key} = :${key},`
// Build values object, removing empty strings
values = {
...values,
[`:${key}`]: value === '' ? null : value,
}
// Build name map
names = {
...names,
[`#${key}`]: key,
}
})
// Build parameter object, exluding empty values
let params = {
TableName: table,
Key: { [field]: args[field] },
ReturnValues: 'UPDATED_NEW',
}
if (Object.keys(names).length > 0) {
params = {
...params,
UpdateExpression: expression.replace(/,$/, ''),
ExpressionAttributeValues: values,
ExpressionAttributeNames: names,
}
}
return new Promise((resolve, reject) => {
db.update(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Select items from a DynamoDB table by matching field value
* @param {string} table - The name of the table to select from
* @param {string} field - The name of the field to select by
* @param {*} match - The field value to match against
* @return {promise} - A promise that resolves on completion
*/
dynamodb.query = (table, field, match) => {
const params = {
TableName: table,
IndexName: `${field}-index`,
KeyConditionExpression: `#${field} = :${field}`,
ExpressionAttributeNames: { [`#${field}`]: field },
ExpressionAttributeValues: { [`:${field}`]: match },
}
return new Promise((resolve, reject) => {
db.query(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Select items from a DynamoDB table by matching field and optional range values
* @param {string} table - The name of the table to select from
* @param {string} field - The name of the field to select by
* @param {*} match - The field value to match against
* @param {string} range - The name of the range field to select by
* @param {*} start - The range value to select items greater than
* @param {*} end - The range value to select items less than
* @return {promise} - A promise that resolves on completion
*/
dynamodb.rangeQuery = (table, field, match, range, start, end) => {
const params = {
TableName: table,
IndexName: `${field}-index`,
KeyConditions: {
[field]: {
ComparisonOperator: 'EQ',
AttributeValueList: [match],
},
},
}
if (range) {
if (start && end) {
params.KeyConditions[range] = {
ComparisonOperator: 'BETWEEN',
AttributeValueList: [start, end],
}
} else if (start) {
params.KeyConditions[range] = {
ComparisonOperator: ['GE'],
AttributeValueList: [start],
}
} else if (end) {
params.KeyConditions[range] = {
ComparisonOperator: ['LT'],
AttributeValueList: [end],
}
}
}
return new Promise((resolve, reject) => {
db.query(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Select an item from a DynamoDB table by matching index
* @param {string} table - The name of the table to select from
* @param {index} index - The name of the index to select by
* @param {*} match - The field value to match against
* @return {promise} - A promise that resolves on completion
*/
dynamodb.get = (table, index, match) => {
const params = {
TableName: table,
Key: { [index]: match },
}
return new Promise((resolve, reject) => {
db.get(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Get all values from a DynamoDB table
* @param {string} table - The name of the table to select from
* @return {promise} - A promise that resolves on completion
*/
dynamodb.scan = (table) => {
const params = {
TableName: table,
}
return new Promise((resolve, reject) => {
db.scan(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Remove an item from a DynamoDB table
* @param {string} table - The name of the table to remove from
* @param {object} keys - An object containing key value pairs to match by
* If a sort key is defined on the table, its value
* must be supplied in addition to the hash key
* @return {promise} - A promise that resolves on completion
*/
dynamodb.remove = (table, keys) => {
const params = {
TableName: table,
Key: keys,
}
return new Promise((resolve, reject) => {
db.delete(params, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
}
/**
* Perform an arbitrary database operation using the full API options
* @param {string} table - The name of the table operate on
* @param {string} name - The name of the operation to perform, eg query, scan etc
* @param {object} params - A params object as per the DocumentClient API
* The TableName parameter can be omitted
* @return {promise} - A promise that resolves on completion
*/
dynamodb.operation = (table, name, params) => (
new Promise((resolve, reject) => {
db[name]({ TableName: table, ...params }, (error, data) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
)
module.exports = dynamodb