import AWS from 'aws-sdk';
import { getScanParams, getUpdateParams } from './dynamo-util.js';
const docClient = new AWS.DynamoDB.DocumentClient({
  region: process.env.AWS_REGION,
});
const defaultTable = process.env.TABLE_NAME;
// *=========== GET =============*
export async function getItem(id, tableName) {
  return await docClient
    .get({ TableName: tableName || defaultTable, Key: { id } })
    .promise();
}
// *=========== ADD =============*
export function addItem(Item, tableName) {
  docClient.put({ TableName: tableName || defaultTable, Item }, function (err, data) {
    if (err) console.error('DynamoDB ERROR:', err);
    else console.log('Successfully added item ' + Item.id);
  });
}
// *=========== UPDATE =============*
const updateParams = (id, obj, tableName) => {
  if (!id || !Object.keys(obj).length) {
    return undefined;
  }
  let updateExpression = 'set';
  let ExpressionAttributeNames = {};
  let ExpressionAttributeValues = {};
  for (const property in obj) {
    updateExpression += ` #${property} = :${property} ,`;
    ExpressionAttributeNames['#' + property] = property;
    ExpressionAttributeValues[':' + property] = obj[property];
  }
  updateExpression = updateExpression.slice(0, -1);
  return {
    TableName: tableName || defaultTable,
    Key: { id },
    UpdateExpression: updateExpression,
    ExpressionAttributeNames: ExpressionAttributeNames,
    ExpressionAttributeValues: ExpressionAttributeValues,
  };
};
export const updateItem = (id, obj, tableName) => {
  const params = updateParams(id, obj, tableName);
  docClient.update(params, function (err, data) {
    if (err) console.error(err);
    else console.log(`Updated ${id}`);
  });
};
export const updateItemAsync = async (id, obj, tableName) => {
  const params = updateParams(id, obj, tableName);
  return await docClient.update(params).promise();
};
// *=========== SCAN =============*
export async function getAllItemsAsync(params) {
  let scanResults = [];
  let res;
  do {
    res = await docClient.scan(params).promise();
    if (res.Items) res.Items.forEach((item) => scanResults.push(item));
    params.ExclusiveStartKey = res.LastEvaluatedKey;
  } while (typeof res.LastEvaluatedKey != 'undefined');
  return scanResults;
}
// This readability is embarrassing
function getScanParams(start, end, otherFilter, tableName) {
  let FilterExpression = '';
  let values = {};
  if (!!start) {
    FilterExpression += 'created > :start';
    values = { ':start': start };
  }
  if (!!start && !!end) FilterExpression += ' and ';
  if (!!end) {
    FilterExpression += 'created < :end';
    values = { ...values, ':end': end };
  }
  FilterExpression += !!!otherFilter
    ? ''
    : start || end
    ? ' and ' + otherFilter
    : otherFilter;
  const expressions =
    start || end || otherFilter
      ? {
          FilterExpression,
          ExpressionAttributeValues: start || end ? values : undefined,
        }
      : {};
  return { TableName: tableName || defaultTable, ...expressions };
}
export async function scanTableAsync(start, end, otherFilter, tableName) {
  const params = getScanParams(start, end, otherFilter, tableName);
  return await getAllItems(params);
}
export function scanTable(FilterExpression, tableName, callback) {
  var params = { TableName: tableName || defaultTable, FilterExpression };
  docClient.scan(params, function (err, data) {
    if (err) console.error('DynamoDB ERROR:', err);
    else {
      callback(data.Items);
    }
  });
}
// *=========== REMOVE FIELDS =============*
export function removeFields(id, fieldNamesArray, tableName, callback) {
  var params = {
    TableName: tableName || defaultTable,
    Key: { id },
    UpdateExpression: `REMOVE ${fieldNamesArray.join(', ')}`,
    ReturnValues: 'UPDATED_NEW',
  };
  docClient.update(params, function (err, data) {
    if (err) console.error('DynamoDB ERROR:', err);
    else callback(data.Attributes);
  });
}
js
dynamodb-util.js
export const getUpdateParams = (id, obj, tableName) => {
  if (!id || !Object.keys(obj).length) {
    return undefined;
  }
  let updateExpression = 'set';
  let ExpressionAttributeNames = {};
  let ExpressionAttributeValues = {};
  for (const property in obj) {
    updateExpression += ` #${property} = :${property} ,`;
    ExpressionAttributeNames['#' + property] = property;
    ExpressionAttributeValues[':' + property] = obj[property];
  }
  updateExpression = updateExpression.slice(0, -1);
  return {
    TableName: tableName || defaultTable,
    Key: { id },
    UpdateExpression: updateExpression,
    ExpressionAttributeNames: ExpressionAttributeNames,
    ExpressionAttributeValues: ExpressionAttributeValues,
  };
};
export const getScanParams = (start, end, otherFilter, tableName) => {
  let FilterExpression = '';
  let values = {};
  if (!!start) {
    FilterExpression += 'created > :start';
    values = { ':start': start };
  }
  if (!!start && !!end) FilterExpression += ' and ';
  if (!!end) {
    FilterExpression += 'created < :end';
    values = { ...values, ':end': end };
  }
  FilterExpression += !!!otherFilter
    ? ''
    : start || end
    ? ' and ' + otherFilter
    : otherFilter;
  const expressions =
    start || end || otherFilter
      ? {
          FilterExpression,
          ExpressionAttributeValues: start || end ? values : undefined,
        }
      : {};
  return { TableName: tableName || defaultTable, ...expressions };
};
js
Usage
Here's an example with scan and update. This will update the last week of data where
fieldToUpdate is null:
const aWeekAgo = new Date(Date.now().valueOf() - 7 * 24 * 60 * 60 * 1000).toISOString();
const items = await scanTableAsync(
  eightDaysAgo,
  undefined,
  `attribute_not_exists(fieldToUpdate)`
);
for (let i = 0; i < items.length; i++) {
  const item = items[i];
  console.log('UPDATING', item.id);
  updateItem(item.id, { fieldToUpdate: 'new value' });
}
js
Note: The code above should only be used for one-off operations, like infrequent cron-jobs or if there was a gap in your data for some reason. Scan operations are expensive and should not be used regularly, instead use indexes/keys for querying a NoSQL database.