A modern, decorator-first ORM for DynamoDB with TypeScript support Full-featured | Type-safe | Relationships | Auto table creation | Transactions
import { Dynamite, Table, PrimaryKey, Default, CreatedAt, UpdatedAt, CreationOptional } from "@arcaelas/dynamite";
// Define your model
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
declare name: string;
declare email: string;
@Default(() => "customer")
declare role: CreationOptional<string>;
@CreatedAt()
declare created_at: CreationOptional<string>;
@UpdatedAt()
declare updated_at: CreationOptional<string>;
}
// Connect to DynamoDB
const dynamite = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000", // DynamoDB Local
credentials: { accessKeyId: "test", secretAccessKey: "test" },
tables: [User]
});
await dynamite.connect();
// Use it!
const user = await User.create({ name: "John Doe", email: "john@example.com" });
console.log(user.id); // "a1b2c3d4-..."
console.log(user.role); // "customer"
console.log(user.created_at); // "2025-01-15T10:30:00.000Z"npm install @arcaelas/dynamite| Decorator | Description |
|---|---|
@PrimaryKey() |
Primary key (partition key) |
@Index() |
Partition key for GSI |
@IndexSort() |
Sort key |
| Decorator | Description |
|---|---|
@Default(value | fn) |
Default value (static or dynamic) |
@Mutate(fn) |
Transform value before save |
@Validate(fn) |
Validate value before save |
@Serialize(fromDB, toDB) |
Bidirectional transformation |
@NotNull() |
Required field validation |
@Name("custom") |
Custom column/table name |
@Column() |
Column configuration |
| Decorator | Description |
|---|---|
@CreatedAt() |
Auto-set on creation |
@UpdatedAt() |
Auto-set on every update |
@DeleteAt() |
Soft delete timestamp |
| Decorator | Description |
|---|---|
@HasMany(() => Model, foreignKey, localKey?) |
One-to-many |
@HasOne(() => Model, foreignKey, localKey?) |
One-to-one |
@BelongsTo(() => Model, localKey, foreignKey?) |
Many-to-one |
@ManyToMany(() => Model, pivotTable, foreignKey, relatedKey, localKey?, relatedPK?) |
Many-to-many |
import {
CreationOptional, // Optional during create(), required after
NonAttribute, // Excluded from database (computed/relations)
InferAttributes, // Extract DB attributes from model
InferRelations, // Extract relations from model
CreateInput, // Input type for create()
UpdateInput, // Input type for update()
WhereOptions, // Query options type
QueryOperator // Available operators
} from "@arcaelas/dynamite";Use for fields that are optional during creation but exist after:
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>; // Optional in create()
declare name: string; // Required in create()
@CreatedAt()
declare created_at: CreationOptional<string>; // Auto-generated
}Use for computed properties and relations (not stored in DB):
class User extends Table<User> {
declare first_name: string;
declare last_name: string;
// Computed property - not stored
declare full_name: NonAttribute<string>;
// Relations - loaded via include
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<Order[]>;
}// Get all
const users = await User.where({});
// Filter by field
const admins = await User.where({ role: "admin" });
const user = await User.where("email", "john@example.com");
// First/Last
const first = await User.first({ active: true });
const last = await User.last({});// Comparison
await User.where("age", ">=", 18);
await User.where("age", "<", 65);
await User.where("status", "!=", "banned");
// Array membership
await User.where("role", "in", ["admin", "moderator"]);
// String contains
await User.where("email", "$include", "gmail");Available operators: =, !=, <>, <, <=, >, >=, in, $include (aliases: $eq, $ne, $lt, $lte, $gt, $gte, $in, include)
const users = await User.where({}, {
limit: 10,
skip: 20,
order: "DESC",
attributes: ["id", "name", "email"],
include: {
orders: {
where: { status: "completed" },
limit: 5
}
}
});class User extends Table<User> {
@PrimaryKey()
declare id: string;
// HasMany(model, foreignKey, localKey = 'id')
@HasMany(() => Order, "user_id", "id")
declare orders: NonAttribute<Order[]>;
// HasOne(model, foreignKey, localKey = 'id')
@HasOne(() => Profile, "user_id", "id")
declare profile: NonAttribute<Profile | null>;
// ManyToMany(model, pivotTable, foreignKey, relatedKey, localKey = 'id', relatedPK = 'id')
@ManyToMany(() => Role, "user_roles", "user_id", "role_id")
declare roles: NonAttribute<Role[]>;
}
class Order extends Table<Order> {
@PrimaryKey()
declare id: string;
declare user_id: string;
// BelongsTo(model, localKey, foreignKey = 'id')
@BelongsTo(() => User, "user_id", "id")
declare user: NonAttribute<User | null>;
}const users = await User.where({}, {
include: {
orders: { where: { status: "completed" } },
profile: {},
roles: {}
}
});const user = await User.first({ id: "user-1" });
// Attach relation
await user.attach(Role, "role-123");
// Detach relation
await user.detach(Role, "role-123");
// Sync relations (replace all)
await user.sync(Role, ["role-1", "role-2", "role-3"]);const user = await User.create({
name: "John Doe",
email: "john@example.com"
});const users = await User.where({ active: true });
const user = await User.first({ id: "user-123" });// Static update (bulk)
await User.update({ role: "premium" }, { id: "user-123" });
// Instance update
user.name = "Jane Doe";
await user.save();
// Or
await user.update({ name: "Jane Doe" });// Static delete (bulk)
await User.delete({ status: "inactive" });
// Instance delete (soft delete if @DeleteAt present)
await user.destroy();
// Force hard delete
await user.forceDestroy();class Post extends Table<Post> {
@PrimaryKey()
declare id: string;
declare title: string;
@DeleteAt()
declare deleted_at: CreationOptional<string | null>;
}
// Soft delete
await post.destroy(); // Sets deleted_at timestamp
// Query including soft-deleted
const all = await Post.withTrashed({});
// Query only soft-deleted
const trashed = await Post.onlyTrashed({});
// Force hard delete
await post.forceDestroy();await dynamite.tx(async (tx) => {
const user = await User.create({ name: "John" }, tx);
await Order.create({ user_id: user.id, total: 100 }, tx);
// If any operation fails, all are rolled back
});const dynamite = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: { accessKeyId: "test", secretAccessKey: "test" },
tables: [User, Order, Product]
});
await dynamite.connect();const dynamite = new Dynamite({
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
tables: [User, Order, Product]
});
await dynamite.connect();docker run -d -p 8000:8000 amazon/dynamodb-local// CRUD
static create(data, tx?): Promise<T>
static update(data, filters, tx?): Promise<number>
static delete(filters, tx?): Promise<number>
// Query
static where(filters, options?): Promise<T[]>
static where(field, value): Promise<T[]>
static where(field, operator, value): Promise<T[]>
static first(filters?, options?): Promise<T | undefined>
static last(filters?, options?): Promise<T | undefined>
// Soft deletes
static withTrashed(filters?, options?): Promise<T[]>
static onlyTrashed(filters?, options?): Promise<T[]>// CRUD
save(): Promise<boolean>
update(data): Promise<boolean>
destroy(): Promise<null>
forceDestroy(): Promise<null>
// ManyToMany
attach(Model, id, pivotData?): Promise<void>
detach(Model, id): Promise<void>
sync(Model, ids): Promise<void>
// Serialization
toJSON(): Record<string, unknown>For complete documentation, examples, and guides:
MIT License - see LICENSE file for details.
Made with care by Miguel Alejandro - Arcaelas Insiders
