2.10.0 • Published 2h ago
@npmtapi/tapi-lib-dynamo-db
Licence
ISC
Version
2.10.0
Deps
1
Size
102 kB
Vulns
0
Weekly
0
TAPI DynamoDB Library
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: