npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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 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: '[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

  1. Fork el repositorio
  2. Crea una rama para tu feature (git checkout -b feature/amazing-feature)
  3. Commit tus cambios (git commit -m 'Add some amazing feature')
  4. Push a la rama (git push origin feature/amazing-feature)
  5. 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: