@arcaelas/dynamite
v1.0.29
Published
<p align="center"> <img src="docs/assets/cover.png" alt="Dynamite ORM - Arcaelas Insiders for DynamoDB" width="100%"> </p>
Readme
@arcaelas/dynamite
A modern, decorator-first ORM for DynamoDB with TypeScript support Full-featured | Type-safe | Relationships | Auto table creation | Transactions
Quick Start
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: "[email protected]" });
console.log(user.id); // "a1b2c3d4-..."
console.log(user.role); // "customer"
console.log(user.created_at); // "2025-01-15T10:30:00.000Z"Installation
npm install @arcaelas/dynamiteDecorators
Index Decorators
| Decorator | Description |
|-----------|-------------|
| @PrimaryKey() | Primary key (partition key) |
| @Index() | Partition key for GSI |
| @IndexSort() | Sort key |
Data Decorators
| 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 |
Timestamp Decorators
| Decorator | Description |
|-----------|-------------|
| @CreatedAt() | Auto-set on creation |
| @UpdatedAt() | Auto-set on every update |
| @DeleteAt() | Soft delete timestamp |
Relationship Decorators
| Decorator | Description |
|-----------|-------------|
| @HasMany(() => Model, "foreign_key") | One-to-many |
| @HasOne(() => Model, "foreign_key") | One-to-one |
| @BelongsTo(() => Model, "local_key") | Many-to-one |
| @ManyToMany(() => Model, config) | Many-to-many with pivot table |
TypeScript Types
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";CreationOptional
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
}NonAttribute
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[]>;
}Query Operations
Basic Queries
// Get all
const users = await User.where({});
// Filter by field
const admins = await User.where({ role: "admin" });
const user = await User.where("email", "[email protected]");
// First/Last
const first = await User.first({ active: true });
const last = await User.last({});Query Operators
// 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
Query Options
const users = await User.where({}, {
limit: 10,
skip: 20,
order: "DESC",
attributes: ["id", "name", "email"],
include: {
orders: {
where: { status: "completed" },
limit: 5
}
}
});Relationships
Defining Relations
class User extends Table<User> {
@PrimaryKey()
declare id: string;
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<Order[]>;
@HasOne(() => Profile, "user_id")
declare profile: NonAttribute<Profile | null>;
@ManyToMany(() => Role, {
pivotTable: "user_roles",
foreignKey: "user_id",
relatedKey: "role_id"
})
declare roles: NonAttribute<Role[]>;
}
class Order extends Table<Order> {
@PrimaryKey()
declare id: string;
declare user_id: string;
@BelongsTo(() => User, "user_id")
declare user: NonAttribute<User | null>;
}Loading Relations
const users = await User.where({}, {
include: {
orders: { where: { status: "completed" } },
profile: {},
roles: {}
}
});ManyToMany Operations
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"]);CRUD Operations
Create
const user = await User.create({
name: "John Doe",
email: "[email protected]"
});Read
const users = await User.where({ active: true });
const user = await User.first({ id: "user-123" });Update
// 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" });Delete
// Static delete (bulk)
await User.delete({ status: "inactive" });
// Instance delete (soft delete if @DeleteAt present)
await user.destroy();
// Force hard delete
await user.forceDestroy();Soft Deletes
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();Transactions
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
});Configuration
DynamoDB Local
const dynamite = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: { accessKeyId: "test", secretAccessKey: "test" },
tables: [User, Order, Product]
});
await dynamite.connect();AWS DynamoDB
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 Setup
docker run -d -p 8000:8000 amazon/dynamodb-localAPI Reference
Static Methods
// 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[]>Instance Methods
// 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>Documentation
For complete documentation, examples, and guides:
License
MIT License - see LICENSE file for details.
Made with care by Miguel Alejandro - Arcaelas Insiders
