@npmtapi/tapi-lib-dynamo-db
v2.8.0
Published
A library to manage DYnamoDB
Downloads
541
Readme
TAPI DynamoDB Library 2.8.0
Una librería Node.js moderna para interactuar con Amazon DynamoDB de manera sencilla y eficiente, diseñada para usar ES Modules (ESM).
📋 Características
- ✅ Compatible con ES Modules (ESM)
- ✅ Soporte completo para operaciones CRUD
- ✅ Operaciones de consulta y escaneo avanzadas
- ✅ Operaciones en lote (batch operations)
- ✅ Soporte para índices globales y locales (GSI/LSI)
- ✅ Manejo automático de paginación
- ✅ Creación condicional de elementos
- ✅ TypeScript friendly con JSDoc completo
🚀 Instalación
npm install @tapila/tapi-lib-dynamo-db⚙️ Configuración
Configuración básica con AWS
import { dynamoConnection, DynamoModel } from '@tapila/tapi-lib-dynamo-db';
// Configurar el cliente de DynamoDB usando la función de conexión de la librería
const region = 'us-east-1';
const client = dynamoConnection(region);
// Crear una instancia del modelo
const userModel = new DynamoModel('users-table', client);Configuración para DynamoDB Local
import { dynamoConnection, DynamoModel } from '@tapila/tapi-lib-dynamo-db';
// Configuración para DynamoDB Local
const region = 'us-east-1';
const dynamoEndpoint = 'http://localhost:8000';
const client = dynamoConnection(region, dynamoEndpoint);
// Crear una instancia del modelo
const userModel = new DynamoModel('users-table', client);Configuración avanzada con variables de entorno
import { dynamoConnection, DynamoModel } from '@tapila/tapi-lib-dynamo-db';
// Usar variables de entorno para configuración flexible
const region = process.env.AWS_REGION || 'us-east-1';
const dynamoEndpoint = process.env.DYNAMO_ENDPOINT; // undefined para AWS, URL para local
const client = dynamoConnection(region, dynamoEndpoint);
const userModel = new DynamoModel(process.env.TABLE_NAME || 'users-table', client);📖 Uso y Ejemplos
🔍 findOne - Buscar un elemento por clave primaria
// Buscar por clave simple
const user = await userModel.findOne({
key: { id: 'user-123' }
});
// Buscar por clave compuesta (partition key + sort key)
const userProfile = await userModel.findOne({
key: {
PK: 'user-123',
SK: 'profile'
}
});
console.log(user); // { id: 'user-123', name: 'John Doe', email: '[email protected]' } o null➕ create - Crear un nuevo elemento
// Crear un nuevo usuario
const newUser = await userModel.create({
id: 'user-123',
name: 'John Doe',
email: '[email protected]',
createdAt: new Date().toISOString(),
status: 'active'
});
// Crear con opciones personalizadas
const newUserWithOptions = await userModel.create({
id: 'user-124',
name: 'Jane Smith',
email: '[email protected]',
tempField: undefined // Se eliminará automáticamente
}, {
removeUndefinedValues: true
});
console.log(newUser); // null si se creó exitosamente, o el elemento anterior si ya existía✨ createIfNotExists - Crear solo si no existe
try {
const result = await userModel.createIfNotExists({
id: 'user-125',
name: 'Bob Johnson',
email: '[email protected]',
uniqueCode: 'ABC123'
}, 'id'); // Verificar que 'id' no exista
console.log('Usuario creado exitosamente');
} catch (error) {
if (error.name === 'ConditionalCheckFailedException') {
console.log('El usuario ya existe');
}
}🔄 update - Actualizar un elemento existente
// Actualizar campos específicos
const updatedUser = await userModel.update(
{
name: 'John Updated',
status: 'inactive',
lastUpdated: new Date().toISOString()
}, // Campos a actualizar
{ id: 'user-123' } // Clave para identificar el elemento
);
console.log(updatedUser); // Elemento con todos los nuevos valores🗑️ delete - Eliminar un elemento
// Eliminar por clave simple
const deleteResult = await userModel.delete({
Key: { id: 'user-123' }
});
// Eliminar por clave compuesta
const deleteProfileResult = await userModel.delete({
Key: {
PK: 'user-123',
SK: 'profile'
}
});
console.log(deleteResult); // Respuesta de DynamoDB🔍 findAll - Escanear todos los elementos (con filtros opcionales)
// Obtener todos los elementos
const allUsers = await userModel.findAll({});
// Escanear con filtro
const activeUsers = await userModel.findAll({
filterExpression: '#status = :status',
expressionAttributeNames: {
'#status': 'status'
},
expressionAttributeValues: {
':status': 'active'
}
});
// Escanear con límite
const limitedUsers = await userModel.findAll({
limit: 10,
filterExpression: 'attribute_exists(email)'
});
console.log(activeUsers); // { items: [...] }🎯 queryAll - Consultar por clave de partición
// Consulta básica por partition key
const userSessions = await userModel.queryAll({
keyConditionExpression: 'PK = :pk',
expressionAttributeValues: {
':pk': 'user-123'
}
});
// Consulta con rango de sort key
const recentSessions = await userModel.queryAll({
keyConditionExpression: 'PK = :pk AND SK BETWEEN :start AND :end',
expressionAttributeValues: {
':pk': 'user-123',
':start': '2024-01-01',
':end': '2024-12-31'
}
});
// Consulta con ordenamiento descendente y límite
const latestMessages = await userModel.queryAll({
keyConditionExpression: 'chatId = :chatId',
expressionAttributeValues: {
':chatId': 'chat-123'
},
scanIndexForward: false, // Orden descendente
limit: 50
});
// Consulta en un índice secundario global (GSI)
const usersByEmail = await userModel.queryAll({
keyConditionExpression: 'email = :email',
expressionAttributeValues: {
':email': '[email protected]'
},
indexKey: 'email-index'
});
console.log(userSessions); // { items: [...] }🏆 queryLast - Obtener el elemento más reciente
// Obtener el mensaje más reciente de un chat
const lastMessage = await userModel.queryLast({
keyConditionExpression: 'chatId = :chatId',
expressionAttributeValues: {
':chatId': 'chat-123'
}
});
// Obtener la sesión más reciente de un usuario
const lastSession = await userModel.queryLast({
keyConditionExpression: 'userId = :userId',
expressionAttributeValues: {
':userId': 'user-123'
},
indexKey: 'user-sessions-index'
});
console.log(lastMessage); // Objeto del elemento más reciente o null🥇 queryFirst - Obtener el elemento más antiguo
// Obtener el primer mensaje de un chat
const firstMessage = await userModel.queryFirst({
keyConditionExpression: 'chatId = :chatId',
expressionAttributeValues: {
':chatId': 'chat-123'
}
});
// Obtener la primera sesión de un usuario
const firstSession = await userModel.queryFirst({
keyConditionExpression: 'userId = :userId',
expressionAttributeValues: {
':userId': 'user-123'
}
});
console.log(firstMessage); // Objeto del elemento más antiguo o null📦 bulkCreate - Crear múltiples elementos en lote
// Crear múltiples usuarios
const usersToCreate = [
{ id: 'user-200', name: 'Alice', email: '[email protected]' },
{ id: 'user-201', name: 'Bob', email: '[email protected]' },
{ id: 'user-202', name: 'Charlie', email: '[email protected]' },
// ... hasta 25 elementos por lote
];
const results = await userModel.bulkCreate({
rows: usersToCreate,
chunkSize: 25 // Opcional, por defecto es 25
});
// Procesar resultados
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Lote ${index + 1}: ${result.value} elementos procesados`);
} else {
console.error(`Lote ${index + 1} falló:`, result.reason);
}
});🏗️ Ejemplos Avanzados
Manejo de errores y reintentos
import { BulkUnprocessedItems } from '@tapila/tapi-lib-dynamo-db/exceptions';
try {
const results = await userModel.bulkCreate({
rows: largeDataSet,
chunkSize: 25
});
} catch (error) {
if (error instanceof BulkUnprocessedItems) {
console.log(`Elementos procesados: ${error.processedItemsCount}`);
console.log('Elementos no procesados:', error.items);
// Implementar lógica de reintento
}
}Consultas complejas con múltiples filtros
const complexQuery = await userModel.findAll({
filterExpression: '#status = :status AND #createdAt BETWEEN :startDate AND :endDate AND contains(#tags, :tag)',
expressionAttributeNames: {
'#status': 'status',
'#createdAt': 'createdAt',
'#tags': 'tags'
},
expressionAttributeValues: {
':status': 'active',
':startDate': '2024-01-01',
':endDate': '2024-12-31',
':tag': 'premium'
},
limit: 100
});Paginación manual
let lastEvaluatedKey = null;
const allResults = [];
do {
const response = await userModel.queryAll({
keyConditionExpression: 'PK = :pk',
expressionAttributeValues: { ':pk': 'user-data' },
limit: 50,
exclusiveStartKey: lastEvaluatedKey
});
allResults.push(...response.items);
lastEvaluatedKey = response.lastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Total elementos obtenidos: ${allResults.length}`);🔧 Configuración del entorno
Variables de entorno recomendadas
# .env
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
DYNAMODB_ENDPOINT=http://localhost:8000 # Solo para desarrollo localDocker Compose para DynamoDB Local
# docker-compose.yml
version: '3.8'
services:
dynamodb-local:
command: "-jar DynamoDBLocal.jar -sharedDb -optimizeDbBeforeStartup -dbPath ./data"
image: "amazon/dynamodb-local:latest"
container_name: dynamodb-local
ports:
- "8000:8000"
volumes:
- "./docker/dynamodb:/home/dynamodblocal/data"
working_dir: /home/dynamodblocal🧪 Testing
Ejemplo de test unitario
import { describe, it, expect, beforeEach } from 'vitest';
import { dynamoConnection, DynamoModel } from '../lib/index.mjs';
import { mockClient } from './mocks/aws.mjs';
describe('DynamoModel', () => {
let model;
beforeEach(() => {
// Para tests unitarios, se puede usar un mock client
model = new DynamoModel('test-table', mockClient);
});
it('should create a user successfully', async () => {
const userData = {
id: 'test-user',
name: 'Test User',
email: '[email protected]'
};
const result = await model.create(userData);
expect(result).toBeNull(); // Nuevo elemento creado
});
it('should find a user by id', async () => {
const user = await model.findOne({
key: { id: 'test-user' }
});
expect(user).toEqual({
id: 'test-user',
name: 'Test User',
email: '[email protected]'
});
});
});Ejemplo de test de integración
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { dynamoConnection, DynamoModel } from '../lib/index.mjs';
import { createTable, deleteTable } from '../testHelpers/manageTables.mjs';
describe('DynamoModel Integration Tests', () => {
let model;
const tableName = 'test-table';
beforeEach(async () => {
// Crear tabla para testing
await createTable({ tableName });
// Configurar cliente para DynamoDB Local
const client = dynamoConnection('us-east-1', 'http://localhost:8000');
model = new DynamoModel(tableName, client);
});
afterEach(async () => {
// Limpiar tabla después de cada test
await deleteTable(tableName);
});
it('should perform full CRUD operations', async () => {
const userData = {
id: 'test-user',
name: 'Test User',
email: '[email protected]'
};
// Create
await model.create(userData);
// Read
const user = await model.findOne({ key: { id: 'test-user' } });
expect(user).toEqual(userData);
// Update
const updatedUser = await model.update(
{ name: 'Updated User' },
{ id: 'test-user' }
);
expect(updatedUser.name).toBe('Updated User');
// Delete
await model.delete({ Key: { id: 'test-user' } });
const deletedUser = await model.findOne({ key: { id: 'test-user' } });
expect(deletedUser).toBeNull();
});
});📚 API Reference
Constructor
new DynamoModel(tableName, dynamoDBClient)Métodos disponibles
| Método | Descripción | Retorna |
|--------|-------------|---------|
| findOne(params) | Busca un elemento por clave primaria | Promise<Object\|null> |
| create(params, options?) | Crea un nuevo elemento | Promise<Object\|null> |
| createIfNotExists(params, key, options?) | Crea solo si no existe | Promise<Object\|null> |
| update(setParams, whereParams) | Actualiza un elemento existente | Promise<Object\|null> |
| delete(params) | Elimina un elemento | Promise<Object> |
| findAll(params) | Escanea la tabla con filtros opcionales | Promise<{items: Array}> |
| queryAll(params) | Consulta por clave de partición | Promise<{items: Array}> |
| queryLast(params) | Obtiene el elemento más reciente | Promise<Object\|null> |
| queryFirst(params) | Obtiene el elemento más antiguo | Promise<Object\|null> |
| bulkCreate(params) | Crea múltiples elementos en lote | Promise<Array> |
🤝 Contribución
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add some amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
📄 Licencia
Este proyecto está bajo la licencia MIT. Ver el archivo LICENSE para más detalles.
📞 Soporte
Si encuentras algún problema o tienes preguntas:
