# @npmtapi/tapi-lib-dynamo-db

> A library to manage DYnamoDB

Latest version **2.10.0** (published 2026-09-24) · ISC license · 0 weekly downloads

## Install

```sh
npm install @npmtapi/tapi-lib-dynamo-db
pnpm add @npmtapi/tapi-lib-dynamo-db
yarn add @npmtapi/tapi-lib-dynamo-db
bun add @npmtapi/tapi-lib-dynamo-db
```

## Health

**Score 60/100 (C)** — status: active.

Positive: esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types.

## Facts

| | |
|---|---|
| Version | 2.10.0 |
| Published | 2026-09-24 |
| First published | 2023-04-25 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | ESM + CommonJS |
| Node | >=18.0.0 |
| Dependencies | 1 |
| Unpacked size | 102.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Elias Cáceres - TAPI |
| Maintainers | tapnpm |
| Keywords | dynamodb |

## Links

- npm: https://www.npmjs.com/package/@npmtapi/tapi-lib-dynamo-db
- Repository: https://bitbucket.org/tapila/tapi-lib-dynamo-db
- Homepage: https://bitbucket.org/tapila/tapi-lib-dynamo-db#readme
- Issues: https://bitbucket.org/tapila/tapi-lib-dynamo-db/issues
- npm.io page: https://npm.io/package/@npmtapi/tapi-lib-dynamo-db

## Dependencies (1)

- [@npmtapi/tap-lib-logger](https://npm.io/package/@npmtapi/tap-lib-logger.md) ^2.3.0

## Recent versions

- 2.10.0 (latest) — 2026-09-24
- 2.9.0 — 2025-12-15
- 2.8.0 — 2025-06-22
- 2.7.0 — 2025-02-17
- 2.6.1 — 2024-07-30
- 2.6.0 — 2024-04-19
- 2.5.2 — 2024-04-19
- 2.5.1 — 2024-01-22
- 2.5.0 — 2023-11-21
- 2.4.0 — 2023-10-26
- 2.3.0 — 2023-10-14
- 2.2.0 — 2023-10-12
- 2.1.0 — 2023-10-09
- 2.0.1 — 2023-08-25
- 2.0.0 — 2023-04-25

## README

# 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

```bash
npm install @tapila/tapi-lib-dynamo-db
```

## ⚙️ Configuración

### Configuración básica con AWS

```javascript
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

```javascript
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

```javascript
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

```javascript
// 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

```javascript
// 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

```javascript
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

```javascript
// 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

```javascript
// 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)

```javascript
// 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

```javascript
// 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

```javascript
// 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

```javascript
// 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

```javascript
// 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

```javascript
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

```javascript
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

```javascript
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

```bash
# .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

```yaml
# 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

```javascript
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

```javascript
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
```javascript
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:

- 🐛 [Reportar un bug](https://github.com/tapila/tapi-lib-dynamo-db/issues)
- 💡 [Solicitar una feature](https://github.com/tapila/tapi-lib-dynamo-db/issues)
- 📖 [Ver la documentación completa](https://tapila.github.io/tapi-lib-dynamo-db)

---
_Source: https://npm.io/package/@npmtapi/tapi-lib-dynamo-db · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
