@gp_jcisneros/aws-utils
Version:
AWS SDK utilities for GreenPay microservices
409 lines (362 loc) • 11.1 kB
JavaScript
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
QueryCommand,
ScanCommand,
DeleteCommand,
UpdateCommand,
BatchGetCommand,
} = require('@aws-sdk/lib-dynamodb');
const { AWSError, DatabaseError } = require('@gp_jcisneros/errors');
/**
* Utilities for AWS DynamoDB operations
*/
class DynamoUtils {
constructor(region = process.env.AWS_REGION || 'us-east-1') {
const client = new DynamoDBClient({ region });
this.docClient = DynamoDBDocumentClient.from(client);
}
/**
* Get an item from DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {Object} key - Primary key of the item
* @returns {Promise<Object|null>} - The found item or null
*/
async getItem(tableName, key) {
try {
const command = new GetCommand({
TableName: tableName,
Key: key,
});
const response = await this.docClient.send(command);
return response.Item || null;
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB get failed for ${tableName}: ${error.message}`,
'GET',
tableName
);
}
}
/**
* Put an item in DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {Object} item - Item to put
* @returns {Promise<Object>} - Put result
*/
async putItem(tableName, item) {
try {
const command = new PutCommand({
TableName: tableName,
Item: item,
});
await this.docClient.send(command);
return {
success: true,
item,
};
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB put failed for ${tableName}: ${error.message}`,
'INSERT',
tableName
);
}
}
/**
* Update an item in DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {Object} key - Primary key of the item
* @param {Object} updateExpression - Update expression
* @param {Object} expressionAttributeValues - Expression attribute values
* @param {Object} expressionAttributeNames - Expression attribute names
* @returns {Promise<Object>} - Update result
*/
async updateItem(
tableName,
key,
updateExpression,
expressionAttributeValues = {},
expressionAttributeNames = {}
) {
try {
const command = new UpdateCommand({
TableName: tableName,
Key: key,
UpdateExpression: updateExpression,
ExpressionAttributeValues: expressionAttributeValues,
ExpressionAttributeNames: expressionAttributeNames,
ReturnValues: 'ALL_NEW',
});
const response = await this.docClient.send(command);
return {
success: true,
item: response.Attributes,
};
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB update failed for ${tableName}: ${error.message}`,
'UPDATE',
tableName
);
}
}
/**
* Delete an item from DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {Object} key - Primary key of the item
* @returns {Promise<Object>} - Delete result
*/
async deleteItem(tableName, key) {
try {
const command = new DeleteCommand({
TableName: tableName,
Key: key,
});
await this.docClient.send(command);
return {
success: true,
};
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB delete failed for ${tableName}: ${error.message}`,
'DELETE',
tableName
);
}
}
/**
* Query items from DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {string} keyConditionExpression - Key condition expression
* @param {Object} expressionAttributeValues - Expression attribute values
* @param {Object|undefined} expressionAttributeNames - Expression attribute names (can be undefined)
* @param {string} indexName - Name of the index to query
* @param {number} limit - Limit the number of items to return
* @param {string} filterExpression - Filter expression for additional filtering
* @returns {Promise<Array>} - Query results
*/
async queryItems(
tableName,
keyConditionExpression,
expressionAttributeValues = {},
expressionAttributeNames = undefined,
indexName = null,
limit = 1000,
filterExpression = null
) {
const allItems = [];
let lastEvaluatedKey = undefined;
do {
const commandParams = {
TableName: tableName,
KeyConditionExpression: keyConditionExpression,
ExpressionAttributeValues: expressionAttributeValues,
Limit: limit,
};
// Solo agregar ExpressionAttributeNames si es un objeto no vacío
if (
expressionAttributeNames &&
typeof expressionAttributeNames === 'object' &&
Object.keys(expressionAttributeNames).length > 0
) {
commandParams.ExpressionAttributeNames = expressionAttributeNames;
}
// Solo agregar IndexName si se proporciona
if (indexName) {
commandParams.IndexName = indexName;
}
// Solo agregar FilterExpression si se proporciona
if (filterExpression) {
commandParams.FilterExpression = filterExpression;
}
if (lastEvaluatedKey) {
commandParams.ExclusiveStartKey = lastEvaluatedKey;
}
try {
const response = await this.docClient.send(
new QueryCommand(commandParams)
);
const items = response.Items || [];
allItems.push(...items);
lastEvaluatedKey = response.LastEvaluatedKey;
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB query failed for ${tableName}: ${error.message}`,
'QUERY',
tableName
);
}
} while (lastEvaluatedKey);
return allItems;
}
/**
* Scan items from DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {string} filterExpression - Filter expression
* @param {Object} expressionAttributeValues - Expression attribute values
* @param {Object} expressionAttributeNames - Expression attribute names
* @returns {Promise<Array>} - Scan results
*/
async scanItems(
tableName,
filterExpression = null,
expressionAttributeValues = {},
expressionAttributeNames = {}
) {
try {
const commandParams = {
TableName: tableName,
};
// Solo agregar FilterExpression si se proporciona
if (filterExpression) {
commandParams.FilterExpression = filterExpression;
}
// Solo agregar ExpressionAttributeValues si no está vacío
if (Object.keys(expressionAttributeValues).length > 0) {
commandParams.ExpressionAttributeValues = expressionAttributeValues;
}
// Solo agregar ExpressionAttributeNames si no está vacío
if (Object.keys(expressionAttributeNames).length > 0) {
commandParams.ExpressionAttributeNames = expressionAttributeNames;
}
const command = new ScanCommand(commandParams);
const response = await this.docClient.send(command);
return response.Items || [];
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB scan failed for ${tableName}: ${error.message}`,
'QUERY',
tableName
);
}
}
/**
* Batch get items from DynamoDB
* @param {string} tableName - Name of the DynamoDB table
* @param {Array} keys - Array of keys to get
* @returns {Promise<Array>} - Batch get results
*/
async batchGetItems(tableName, keys) {
try {
// Filtrar keys que no sean null o undefined
const validKeys = keys.filter((key) => key !== null && key !== undefined);
if (validKeys.length === 0) {
return [];
}
const command = new BatchGetCommand({
RequestItems: {
[tableName]: {
Keys: validKeys,
},
},
});
const response = await this.docClient.send(command);
return response.Responses?.[tableName] || [];
} catch (error) {
// Si ya es un error personalizado, lo relanzamos
if (error instanceof AWSError || error instanceof DatabaseError) {
throw error;
}
throw new DatabaseError(
`DynamoDB batch get failed for ${tableName}: ${error.message}`,
'QUERY',
tableName
);
}
}
// Static methods for convenience
static async getItem(tableName, key) {
const utils = new DynamoUtils();
return utils.getItem(tableName, key);
}
static async putItem(tableName, item) {
const utils = new DynamoUtils();
return utils.putItem(tableName, item);
}
static async updateItem(
tableName,
key,
updateExpression,
expressionAttributeValues = {},
expressionAttributeNames = {}
) {
const utils = new DynamoUtils();
return utils.updateItem(
tableName,
key,
updateExpression,
expressionAttributeValues,
expressionAttributeNames
);
}
static async deleteItem(tableName, key) {
const utils = new DynamoUtils();
return utils.deleteItem(tableName, key);
}
static async queryItems(
tableName,
keyConditionExpression,
expressionAttributeValues = {},
expressionAttributeNames = {},
indexName = null,
limit = 1000,
filterExpression = null
) {
const utils = new DynamoUtils();
return utils.queryItems(
tableName,
keyConditionExpression,
expressionAttributeValues,
expressionAttributeNames,
indexName,
limit,
filterExpression
);
}
static async scanItems(
tableName,
filterExpression = null,
expressionAttributeValues = {},
expressionAttributeNames = {}
) {
const utils = new DynamoUtils();
return utils.scanItems(
tableName,
filterExpression,
expressionAttributeValues,
expressionAttributeNames
);
}
static async batchGetItems(tableName, keys) {
const utils = new DynamoUtils();
return utils.batchGetItems(tableName, keys);
}
}
module.exports = { DynamoUtils };