Table API Reference¶
Descripción General¶
La clase Table es la clase base para todos los modelos en Dynamite ORM. Proporciona una API completa y tipada para realizar operaciones CRUD, consultas avanzadas, gestión de relaciones y manipulación de datos en DynamoDB.
Características Principales: - Tipado estricto con TypeScript - Operaciones CRUD completas - Sistema de consultas flexible con múltiples operadores - Soporte para relaciones HasMany y BelongsTo - Gestión automática de timestamps (createdAt/updatedAt) - Validaciones y mutaciones integradas - Paginación y ordenamiento - Selección de atributos específicos - Inclusión de relaciones anidadas
Importación¶
Definición de Modelo¶
import { Table, Name, PrimaryKey, NotNull, Default, CreatedAt, UpdatedAt } from '@arcaelas/dynamite';
@Name("users")
class User extends Table<User> {
@PrimaryKey()
declare id: string;
@NotNull()
declare email: string;
@NotNull()
declare name: string;
@Default(() => 25)
declare age: number;
@CreatedAt()
declare createdAt: string;
@UpdatedAt()
declare updatedAt: string;
}
Constructor¶
constructor(data: InferAttributes<T>)¶
Crea una nueva instancia del modelo con los datos proporcionados.
Parámetros: - data - Objeto con los atributos del modelo (excluye relaciones y métodos)
Características: - Aplica valores por defecto definidos con @Default() - Inicializa propiedades declaradas en el modelo - No persiste automáticamente en la base de datos (usar save() para persistir)
Ejemplo:
const user = new User({
id: "user-123",
email: "john@example.com",
name: "John Doe",
age: 30
});
// Para persistir en la base de datos
await user.save();
Métodos de Instancia¶
save(): Promise<this>¶
Guarda o actualiza el registro actual en la base de datos.
Comportamiento: - Si el registro no tiene id (o es null/undefined), crea un nuevo registro - Si el registro tiene id, actualiza el registro existente - Actualiza automáticamente el campo updatedAt si está definido - Establece createdAt solo en registros nuevos
Retorna: La instancia actual actualizada
Ejemplo:
// Crear nuevo registro
const user = new User({
email: "jane@example.com",
name: "Jane Smith"
});
await user.save(); // createdAt y updatedAt se establecen automáticamente
// Actualizar registro existente
user.name = "Jane Doe";
await user.save(); // Solo updatedAt se actualiza
update(patch: Partial<InferAttributes<T>>): Promise<this>¶
Actualiza parcialmente el registro con los campos proporcionados.
Parámetros: - patch - Objeto con los campos a actualizar
Retorna: La instancia actual actualizada
Ejemplo:
const user = await User.first({ id: "user-123" });
await user.update({
name: "John Updated",
age: 31
});
console.log(user.name); // "John Updated"
console.log(user.age); // 31
destroy(): Promise<null>¶
Elimina el registro actual de la base de datos.
Requisitos: - La instancia debe tener un id válido
Retorna: null
Errores: - Lanza error si la instancia no tiene id
Ejemplo:
const user = await User.first({ id: "user-123" });
await user.destroy(); // Elimina el registro de la base de datos
toJSON(): Record<string, any>¶
Serializa la instancia a un objeto JSON plano.
Características: - Incluye todas las columnas definidas con decoradores - Excluye relaciones (HasMany, BelongsTo) - Activa getters virtuales definidos en el modelo - Incluye propiedades enumerables ad-hoc
Retorna: Objeto plano con los datos del modelo
Ejemplo:
const user = await User.first({ id: "user-123" });
const json = user.toJSON();
console.log(json);
// {
// id: "user-123",
// email: "john@example.com",
// name: "John Doe",
// age: 30,
// createdAt: "2025-01-15T10:30:00.000Z",
// updatedAt: "2025-01-15T10:30:00.000Z"
// }
Métodos Estáticos¶
create<M>(data: InferAttributes<M>): Promise<M>¶
Crea y persiste un nuevo registro en la base de datos.
Parámetros: - data - Objeto con los atributos del nuevo registro
Características: - Crea una nueva instancia - Establece automáticamente createdAt y updatedAt - Aplica valores por defecto, validaciones y mutaciones - Persiste inmediatamente en DynamoDB
Retorna: Nueva instancia del modelo persistida
Ejemplo:
const user = await User.create({
id: "user-456",
email: "alice@example.com",
name: "Alice Wonder",
age: 28
});
console.log(user.id); // "user-456"
console.log(user.createdAt); // "2025-01-15T10:30:00.000Z"
Con validaciones y mutaciones:
@Name("users")
class User extends Table<User> {
@Mutate(v => v.toLowerCase().trim())
@Validate(v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? true : "Email inválido")
declare email: string;
}
// El email se convertirá a minúsculas y validará el formato
const user = await User.create({
id: "user-789",
email: " BOB@EXAMPLE.COM ", // Se convierte a "bob@example.com"
name: "Bob"
});
update<M>(updates: Partial<InferAttributes<M>>, filters: Partial<InferAttributes<M>>): Promise<number>¶
Actualiza múltiples registros que coincidan con los filtros.
Parámetros: - updates - Objeto con los campos a actualizar (campos undefined se ignoran) - filters - Objeto con los criterios de selección
Características: - Actualiza todos los registros que coincidan con los filtros - Actualiza automáticamente el campo updatedAt - Los campos con valor undefined se ignoran - Operación atómica por cada registro
Retorna: Número de registros actualizados
Ejemplo:
// Actualizar múltiples usuarios
const count = await User.update(
{ active: false, role: "suspended" },
{ status: "banned" }
);
console.log(`${count} usuarios suspendidos`);
// Actualizar un usuario específico
await User.update(
{ balance: 100.0 },
{ id: "user-123" }
);
Actualización condicional:
// Desactivar usuarios inactivos
const inactiveCount = await User.update(
{ active: false },
{ lastLoginDate: "2024-01-01" } // Filtro personalizado
);
delete<M>(filters: Partial<InferAttributes<M>>): Promise<number>¶
Elimina registros que coincidan con los filtros.
Parámetros: - filters - Objeto con los criterios de selección
Características: - Elimina todos los registros que coincidan con los filtros - Operación permanente (no hay soft delete por defecto) - Retorna el número de registros eliminados
Retorna: Número de registros eliminados
Ejemplo:
// Eliminar un usuario específico
const count = await User.delete({ id: "user-123" });
console.log(`${count} usuario(s) eliminado(s)`);
// Eliminar múltiples usuarios
await User.delete({ status: "inactive", verified: false });
// Eliminar todos los registros (usar con precaución)
await User.delete({});
Método where() - Consultas Avanzadas¶
El método where() es el método más versátil para consultar datos, con múltiples sobrecargas y opciones avanzadas.
Sobrecarga 1: where(field, value): Promise<M[]>¶
Busca registros donde un campo es igual a un valor (o múltiples valores).
Parámetros: - field - Nombre del campo - value - Valor o array de valores (array se convierte en operador IN)
Ejemplo:
// Igualdad simple
const admins = await User.where("role", "admin");
// IN implícito con array
const users = await User.where("role", ["admin", "employee"]);
// Equivalente a: role IN ("admin", "employee")
Sobrecarga 2: where(field, operator, value): Promise<M[]>¶
Busca registros usando un operador específico.
Parámetros: - field - Nombre del campo - operator - Operador de comparación (ver tabla de operadores) - value - Valor o array de valores (según el operador)
Operadores Soportados:
| Operador | Descripción | Ejemplo |
|---|---|---|
"=" | Igual a | where("age", "=", 25) |
"!=" | Diferente de | where("status", "!=", "banned") |
"<" | Menor que | where("age", "<", 30) |
"<=" | Menor o igual que | where("price", "<=", 100) |
">" | Mayor que | where("balance", ">", 1000) |
">=" | Mayor o igual que | where("rating", ">=", 4) |
"in" | Incluido en array | where("status", "in", ["active", "pending"]) |
"not-in" | No incluido en array | where("role", "not-in", ["banned"]) |
"contains" | Contiene substring | where("name", "contains", "John") |
"begins-with" | Comienza con | where("email", "begins-with", "admin@") |
Ejemplos:
// Comparación numérica
const youngUsers = await User.where("age", "<", 30);
const richUsers = await User.where("balance", ">", 1000);
// Comparación de strings
const notBanned = await User.where("status", "!=", "banned");
// Operadores de array
const staff = await User.where("role", "in", ["admin", "employee"]);
const customers = await User.where("role", "not-in", ["admin", "employee"]);
// Operadores de texto
const johns = await User.where("name", "contains", "John");
const admins = await User.where("email", "begins-with", "admin@");
Sobrecarga 3: where(filters): Promise<M[]>¶
Busca registros que coincidan con múltiples campos (operador AND implícito).
Parámetros: - filters - Objeto con pares campo-valor
Ejemplo:
// Múltiples condiciones (AND)
const activeAdmins = await User.where({
role: "admin",
active: true,
verified: true
});
// Equivalente a: WHERE role = "admin" AND active = true AND verified = true
Sobrecarga 4: where(filters, options): Promise<M[]>¶
Busca registros con opciones avanzadas de paginación, ordenamiento, selección de atributos e inclusión de relaciones.
Parámetros: - filters - Objeto con pares campo-valor - options - Objeto con opciones avanzadas
Opciones Disponibles:
interface WhereQueryOptions<T> {
order?: "ASC" | "DESC"; // Ordenamiento
skip?: number; // Número de registros a saltar (offset)
limit?: number; // Número máximo de registros a retornar
attributes?: string[]; // Campos específicos a seleccionar
include?: { // Relaciones a incluir
[relation: string]: IncludeRelationOptions | true;
};
}
interface IncludeRelationOptions {
where?: Record<string, any>; // Filtros para la relación
attributes?: string[]; // Campos específicos de la relación
order?: "ASC" | "DESC"; // Ordenamiento de la relación
skip?: number; // Offset de la relación
limit?: number; // Límite de la relación
include?: Record<string, IncludeRelationOptions | true>; // Relaciones anidadas
}
Ejemplos Completos:
// Paginación y ordenamiento
const users = await User.where({}, {
limit: 10,
skip: 20, // Página 3 (20 registros saltados)
order: "DESC"
});
// Selección de atributos específicos
const usernames = await User.where({}, {
attributes: ["id", "name", "email"]
});
// Solo retorna los campos solicitados
console.log(usernames[0].id); // "user-123"
console.log(usernames[0].name); // "John Doe"
console.log(usernames[0].age); // undefined (no solicitado)
// Inclusión de relaciones simples
const usersWithOrders = await User.where({}, {
include: {
orders: true // Incluir todas las órdenes
}
});
console.log(usersWithOrders[0].orders); // Array de órdenes
// Inclusión de relaciones con filtros
const usersWithCompletedOrders = await User.where({}, {
include: {
orders: {
where: { status: "completed" },
limit: 5,
order: "DESC"
}
}
});
// Relaciones anidadas
const ordersWithDetails = await Order.where({}, {
include: {
user: true, // Incluir usuario
items: { // Incluir items de orden
include: {
product: { // Incluir producto de cada item
include: {
category: true // Incluir categoría de cada producto
}
}
}
}
}
});
// Combinación completa
const result = await User.where(
{ active: true },
{
attributes: ["id", "name", "email"],
limit: 20,
skip: 0,
order: "ASC",
include: {
orders: {
where: { status: "delivered" },
attributes: ["id", "total", "status"],
limit: 10,
include: {
items: {
include: {
product: true
}
}
}
},
reviews: {
where: { rating: 5 },
limit: 5
}
}
}
);
first<M>(...args): Promise<M | undefined>¶
Obtiene el primer registro que coincida con los criterios.
Sobrecargas: - first(field, value): Promise<M | undefined> - first(field, operator, value): Promise<M | undefined> - first(filters): Promise<M | undefined>
Características: - Internamente llama a where() con los mismos argumentos - Retorna solo el primer resultado - Retorna undefined si no se encuentra ningún registro
Ejemplo:
// Buscar por campo único
const user = await User.first("id", "user-123");
if (user) {
console.log(user.name);
}
// Buscar con operador
const admin = await User.first("role", "=", "admin");
// Buscar con múltiples condiciones
const activeAdmin = await User.first({
role: "admin",
active: true
});
// Verificación de existencia
const exists = (await User.first({ email: "test@example.com" })) !== undefined;
last<M>(...args): Promise<M | undefined>¶
Obtiene el último registro que coincida con los criterios.
Sobrecargas: - last(field, value): Promise<M | undefined> - last(field, operator, value): Promise<M | undefined> - last(filters): Promise<M | undefined>
Características: - Similar a first() pero retorna el último resultado - Útil para obtener el registro más reciente - Internamente usa ordenamiento descendente
Ejemplo:
// Obtener el último usuario creado
const latestUser = await User.last({});
// Obtener la última orden de un usuario
const lastOrder = await Order.last({ user_id: "user-123" });
// Con operador
const lastHighRating = await Review.last("rating", ">=", 4);
if (lastOrder) {
console.log(`Última orden: ${lastOrder.id}`);
console.log(`Total: $${lastOrder.total}`);
}
Ejemplos de Uso Avanzado¶
Consultas Complejas con Múltiples Condiciones¶
// Buscar usuarios activos con balance alto
const premiumUsers = await User.where("balance", ">", 1000);
const activePremium = premiumUsers.filter(u => u.active === true);
// Alternativa: usar where anidado
const activeUsers = await User.where({ active: true });
const activePremiumAlt = activeUsers.filter(u => (u.balance as number) > 1000);
Paginación Eficiente¶
async function getPaginatedUsers(page: number, pageSize: number) {
const skip = (page - 1) * pageSize;
const users = await User.where({}, {
limit: pageSize,
skip: skip,
order: "ASC"
});
return {
page,
pageSize,
data: users,
hasMore: users.length === pageSize
};
}
// Uso
const page1 = await getPaginatedUsers(1, 10);
const page2 = await getPaginatedUsers(2, 10);
Búsqueda de Texto¶
// Buscar por nombre que contenga un texto
const johns = await User.where("name", "contains", "John");
// Buscar por email que comience con un prefijo
const adminEmails = await User.where("email", "begins-with", "admin@");
// Combinar con otros filtros
const activeJohns = johns.filter(u => u.active === true);
Trabajar con Relaciones¶
// HasMany: Un usuario tiene muchas órdenes
@Name("users")
class User extends Table<User> {
@HasMany(() => Order, "user_id")
declare orders: HasMany<Order>;
}
@Name("orders")
class Order extends Table<Order> {
@BelongsTo(() => User, "user_id")
declare user: BelongsTo<User>;
}
// Obtener usuario con sus órdenes
const user = await User.first({ id: "user-123" });
const userWithOrders = await User.where(
{ id: "user-123" },
{ include: { orders: true } }
);
console.log(userWithOrders[0].orders); // Array de Order
// Obtener orden con su usuario
const orderWithUser = await Order.where(
{ id: "order-456" },
{ include: { user: true } }
);
console.log(orderWithUser[0].user.name); // Nombre del usuario
Relaciones Anidadas Profundas¶
// Estructura: User -> Orders -> OrderItems -> Products -> Categories
const completeUserData = await User.where(
{ id: "user-123" },
{
include: {
orders: {
include: {
items: {
include: {
product: {
include: {
category: true
}
}
}
}
}
}
}
}
);
// Acceder a datos anidados
const user = completeUserData[0];
const firstOrder = user.orders[0];
const firstItem = firstOrder.items[0];
const product = firstItem.product;
const category = product.category;
console.log(`Categoría: ${category.name}`);
Operaciones en Batch¶
// Crear múltiples registros
const users = await Promise.all([
User.create({ email: "user1@example.com", name: "User 1" }),
User.create({ email: "user2@example.com", name: "User 2" }),
User.create({ email: "user3@example.com", name: "User 3" })
]);
// Actualizar múltiples registros
await User.update(
{ verified: true },
{ registrationDate: "2025-01-01" }
);
// Eliminar múltiples registros
await User.delete({ status: "inactive" });
Validación y Manejo de Errores¶
try {
const user = await User.create({
email: "invalid-email", // Email inválido
name: "Test User"
});
} catch (error) {
console.error(`Validación fallida: ${error.message}`);
// "Validación fallida: Email inválido"
}
// Verificar existencia antes de actualizar
const user = await User.first({ id: "user-123" });
if (user) {
await user.update({ name: "New Name" });
} else {
console.log("Usuario no encontrado");
}
Selección Parcial de Campos¶
// Solo obtener campos específicos (reduce transferencia de datos)
const lightUsers = await User.where({}, {
attributes: ["id", "name", "email"]
});
// Los campos no solicitados serán undefined
console.log(lightUsers[0].id); // "user-123"
console.log(lightUsers[0].name); // "John Doe"
console.log(lightUsers[0].age); // undefined
console.log(lightUsers[0].balance); // undefined
Inferencia de Tipos¶
Dynamite ORM proporciona inferencia de tipos completa con TypeScript.
InferAttributes¶
Extrae solo los atributos (excluye métodos y relaciones).
import type { InferAttributes } from '@arcaelas/dynamite';
type UserAttributes = InferAttributes<User>;
// {
// id: string;
// email: string;
// name: string;
// age: number;
// createdAt: string;
// updatedAt: string;
// }
// Uso en funciones
function createUser(data: InferAttributes<User>) {
return User.create(data);
}
CreationOptional¶
Marca campos que son opcionales durante la creación (tienen valores por defecto).
import { CreationOptional } from '@arcaelas/dynamite';
@Name("products")
class Product extends Table<Product> {
@PrimaryKey()
declare id: string;
@NotNull()
declare name: string;
@Default(() => 0)
declare stock: CreationOptional<number>; // Opcional en create()
@Default(() => true)
declare active: CreationOptional<boolean>;
}
// TypeScript permite omitir campos CreationOptional
await Product.create({
id: "prod-123",
name: "Product Name"
// stock y active son opcionales
});
Tipos de Relaciones¶
import { HasMany, BelongsTo } from '@arcaelas/dynamite';
@Name("users")
class User extends Table<User> {
@HasMany(() => Order, "user_id")
declare orders: HasMany<Order>; // Array de Order
@HasMany(() => Review, "user_id")
declare reviews: HasMany<Review>;
}
@Name("orders")
class Order extends Table<Order> {
@BelongsTo(() => User, "user_id")
declare user: BelongsTo<User>; // User o null
}
Manejo de Errores¶
Errores Comunes¶
// 1. Validación fallida
try {
await User.create({
email: "invalid",
name: "Test"
});
} catch (error) {
// ValidationError: Email inválido
}
// 2. Campo requerido faltante
try {
await User.create({
name: "Test"
// email es @NotNull y falta
});
} catch (error) {
// ValidationError: email es requerido
}
// 3. Intentar destruir sin id
const user = new User({ email: "test@example.com", name: "Test" });
try {
await user.destroy();
} catch (error) {
// Error: destroy() requiere que la instancia tenga un id
}
// 4. Operador inválido
try {
await User.where("age", "===", 25); // Operador inválido
} catch (error) {
// Error: Operador inválido: ===
}
Buenas Prácticas de Manejo de Errores¶
async function safeCreateUser(data: InferAttributes<User>) {
try {
const user = await User.create(data);
return { success: true, data: user };
} catch (error) {
console.error("Error creating user:", error);
return { success: false, error: error.message };
}
}
// Uso
const result = await safeCreateUser({
email: "test@example.com",
name: "Test User"
});
if (result.success) {
console.log(`Usuario creado: ${result.data.id}`);
} else {
console.log(`Error: ${result.error}`);
}
Rendimiento y Optimización¶
1. Selección de Atributos Específicos¶
Reduce la transferencia de datos seleccionando solo los campos necesarios:
// ❌ Mal: Obtiene todos los campos (incluye campos grandes innecesarios)
const users = await User.where({});
// ✅ Bien: Solo campos necesarios
const users = await User.where({}, {
attributes: ["id", "name", "email"]
});
2. Paginación Efectiva¶
// ✅ Usar limit para evitar cargar demasiados registros
const users = await User.where({}, {
limit: 20,
skip: (page - 1) * 20
});
3. Inclusión Selectiva de Relaciones¶
// ❌ Mal: Incluir todas las relaciones siempre
const users = await User.where({}, {
include: {
orders: true,
reviews: true,
notifications: true
}
});
// ✅ Bien: Solo incluir relaciones necesarias con límites
const users = await User.where({}, {
include: {
orders: {
limit: 5,
order: "DESC"
}
}
});
4. Operaciones en Batch¶
// ✅ Crear múltiples registros en paralelo
await Promise.all([
User.create({ email: "user1@example.com", name: "User 1" }),
User.create({ email: "user2@example.com", name: "User 2" }),
User.create({ email: "user3@example.com", name: "User 3" })
]);
Restricciones y Limitaciones¶
1. Consultas AND vs OR¶
where()con múltiples campos usa operadorANDimplícito- No hay soporte nativo para operador
ORen un solowhere() - Solución: Realizar múltiples consultas y combinar resultados
// Solo soporta AND
const result = await User.where({
role: "admin",
active: true // AND active = true
});
// Para OR, hacer múltiples consultas
const admins = await User.where({ role: "admin" });
const employees = await User.where({ role: "employee" });
const staff = [...admins, ...employees];
2. Profundidad de Relaciones¶
- Las relaciones anidadas pueden aumentar el tiempo de consulta exponencialmente
- Recomendación: Limitar a 3-4 niveles de profundidad
- Usar
limiten relaciones anidadas
3. Scan vs Query¶
where()internamente usaScanCommandde DynamoDB- Los scans son más lentos que queries pero más flexibles
- Para mejor rendimiento, considerar índices en DynamoDB
Migración y Compatibilidad¶
Desde otros ORMs¶
Sequelize:
// Sequelize
const users = await User.findAll({ where: { role: "admin" } });
// Dynamite
const users = await User.where({ role: "admin" });
TypeORM:
// TypeORM
const users = await userRepository.find({ where: { role: "admin" } });
// Dynamite
const users = await User.where({ role: "admin" });
Changelog de Versiones¶
v1.0.0¶
- ✅ Implementación completa de métodos CRUD
- ✅ Soporte para relaciones HasMany y BelongsTo
- ✅ Sistema de validaciones y mutaciones
- ✅ Paginación y ordenamiento
- ✅ Selección de atributos específicos
- ✅ Inclusión de relaciones anidadas
- ✅ Timestamps automáticos (createdAt/updatedAt)
Archivo Fuente¶
Ubicación: /tmp/dynamite/src/core/table.ts Líneas de código: 636 líneas Última actualización: 2025-07-30
Soporte y Contribución¶
- Documentación completa: https://github.com/arcaelas/dynamite
- Reportar bugs: https://github.com/arcaelas/dynamite/issues
- Discusiones: https://github.com/arcaelas/dynamite/discussions
Nota: Este documento fue generado a partir del código fuente real en /tmp/dynamite/src/core/table.ts. Para cualquier discrepancia, consulta el código fuente como fuente de verdad.