Package Exports
- @npmtapi/tapi-lib-dynamo-db
- @npmtapi/tapi-lib-dynamo-db/lib/index.mjs
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@npmtapi/tapi-lib-dynamo-db) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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: 'john@example.com' } o null
➕ create - Crear un nuevo elemento
// Crear un nuevo usuario
const newUser = await userModel.create({
id: 'user-123',
name: 'John Doe',
email: 'john@example.com',
createdAt: new Date().toISOString(),
status: 'active'
});
// Crear con opciones personalizadas
const newUserWithOptions = await userModel.create({
id: 'user-124',
name: 'Jane Smith',
email: 'jane@example.com',
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: 'bob@example.com',
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': 'john@example.com'
},
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: 'alice@example.com' },
{ id: 'user-201', name: 'Bob', email: 'bob@example.com' },
{ id: 'user-202', name: 'Charlie', email: 'charlie@example.com' },
// ... 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 local
Docker 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: 'test@example.com'
};
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: 'test@example.com'
});
});
});
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: 'test@example.com'
};
// 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: