betterddb
Version:
A definition-based DynamoDB wrapper library that provides a schema-driven and fully typesafe DAL.
545 lines (422 loc) • 11.8 kB
Markdown
# BetterDDB API Reference
This document provides a comprehensive reference for the BetterDDB library, explaining its classes, methods, and usage patterns.
## Table of Contents
- [Initialization](#initialization)
- [Schema Definition](#schema-definition)
- [CRUD Operations](#crud-operations)
- [Create](#create)
- [Read](#read)
- [Update](#update)
- [Delete](#delete)
- [Query Operations](#query-operations)
- [Basic Queries](#basic-queries)
- [Using Indexes](#using-indexes)
- [Filtering](#filtering)
- [Scan Operations](#scan-operations)
- [Batch Operations](#batch-operations)
- [Transaction Operations](#transaction-operations)
- [Advanced Features](#advanced-features)
- [Automatic Timestamps](#automatic-timestamps)
- [Versioning](#versioning)
## Initialization
### BetterDDB Constructor
The main entry point for interacting with DynamoDB.
```typescript
constructor(options: BetterDDBOptions<T>)
```
#### Parameters
```typescript
export interface BetterDDBOptions<T> {
schema: z.AnyZodObject;
tableName: string;
entityType?: string;
keys: KeysConfig<T>;
client: DynamoDBDocumentClient;
counter?: boolean;
timestamps?: boolean;
}
```
- **schema**: Zod schema for validation
- **tableName**: Your DynamoDB table name
- **entityType**: Optional type discriminator for multi-entity tables
- **keys**: Configuration for primary key, sort key, and GSIs
- **client**: AWS SDK v3 DynamoDB document client
- **counter**: Whether to use a counter field for optimistic locking
- **timestamps**: Whether to automatically manage `createdAt` and `updatedAt` timestamps
#### Example
```typescript
import { BetterDDB } from "betterddb";
import { z } from "zod";
import { DynamoDBDocumentClient, DynamoDB } from "@aws-sdk/lib-dynamodb";
// Define schema
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
createdAt: z.string().optional(),
updatedAt: z.string().optional(),
});
// Init client
const client = DynamoDBDocumentClient.from(
new DynamoDB({
region: "us-east-1",
}),
);
// Initialize BetterDDB
const userDdb = new BetterDDB({
schema: UserSchema,
tableName: "Users",
keys: {
primary: {
name: "pk",
definition: { build: (raw) => `USER#${raw.id}` },
},
sort: {
name: "sk",
definition: { build: (raw) => `PROFILE` },
},
gsis: {
EmailIndex: {
name: "EmailIndex",
primary: {
name: "gsi1pk",
definition: "email",
},
},
},
},
client,
timestamps: true,
});
```
## Schema Definition
BetterDDB uses [Zod](https://github.com/colinhacks/zod) for schema validation and type inference. Your schema defines both runtime validation rules and TypeScript types.
### Basic Schema
```typescript
const Schema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
age: z.number().optional(),
});
type Entity = z.infer<typeof Schema>; // TypeScript type inference
```
### Schema with Computed Fields
To allow for computed fields (like keys), use `.passthrough()`:
```typescript
const Schema = z
.object({
id: z.string(),
name: z.string(),
})
.passthrough();
```
## CRUD Operations
### Create
Creates a new item in the DynamoDB table with automatic validation and key computation.
```typescript
create(item: T): CreateBuilder<T>
```
#### CreateBuilder Methods
- **execute()**: Performs the create operation and returns the created item
- **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
#### Example
```typescript
// Simple create
const user = await userDdb
.create({
id: "123",
name: "Alice",
email: "alice@example.com",
})
.execute();
// Create with condition
const user = await userDdb
.create({
id: "123",
name: "Alice",
email: "alice@example.com",
})
.withCondition("attribute_not_exists(#pk)", { "#pk": "pk" })
.execute();
```
### Read
Retrieves items from the DynamoDB table.
```typescript
get(key: Partial<T>): GetBuilder<T>
```
#### GetBuilder Methods
- **execute()**: Performs the get operation and returns the item
- **withProjection(attributes: string[])**: Specifies which attributes to return
- **withConsistentRead(consistent: boolean)**: Uses consistent read
#### Example
```typescript
// Simple get
const user = await userDdb.get({ id: "123" }).execute();
// Get with projection
const userNameOnly = await userDdb
.get({ id: "123" })
.withProjection(["name", "email"])
.execute();
```
### Batch Get
Retrieves multiple items in a single operation.
```typescript
batchGet(keys: Partial<T>[]): BatchGetBuilder<T>
```
#### Example
```typescript
const users = await userDdb.batchGet([{ id: "123" }, { id: "456" }]).execute();
```
### Update
Updates an existing item with automatic validation.
```typescript
update(key: Partial<T>, expectedVersion?: number): UpdateBuilder<T>
```
#### UpdateBuilder Methods
- **set(attributes: Partial<T>)**: Sets attributes to specific values
- **remove(attributes: string[])**: Removes attributes
- **add(attributes: Record<string, number>)**: Adds numeric values to attributes
- **delete(attributes: Record<string, any[]>)**: Removes elements from sets
- **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
- **execute()**: Performs the update operation and returns the updated item
- **toTransactUpdate()**: Converts the update to a transaction operation
#### Example
```typescript
// Simple update
const user = await userDdb
.update({ id: "123" })
.set({ name: "Alice Smith" })
.execute();
// Complex update
const user = await userDdb
.update({ id: "123" }, 1) // Version check
.set({ name: "Alice Smith" })
.remove(["oldField"])
.add({ age: 1 })
.withCondition("attribute_exists(#id)", { "#id": "id" })
.execute();
```
### Delete
Deletes an item from the DynamoDB table.
```typescript
delete(key: Partial<T>): DeleteBuilder<T>
```
#### DeleteBuilder Methods
- **execute()**: Performs the delete operation
- **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
- **toTransactDelete()**: Converts the delete to a transaction operation
#### Example
```typescript
// Simple delete
await userDdb.delete({ id: "123" }).execute();
// Delete with condition
await userDdb
.delete({ id: "123" })
.withCondition("#status = :status", {
"#status": "status",
":status": "inactive",
})
.execute();
```
## Query Operations
Performs a DynamoDB query operation with a fluent interface.
```typescript
query(keyCondition: Partial<T>): QueryBuilder<T>
```
### QueryBuilder Methods
- **execute()**: Performs the query operation and returns the results
- **where(operator: string, value: any)**: Adds a condition on the sort key
- **filter(attribute: keyof T, operator: string, value: any)**: Adds a filter condition
- **usingIndex(indexName: string)**: Specifies a GSI to query
- **limitResults(limit: number)**: Limits the number of results
- **scanDescending()**: Changes the scan direction to descending
- **startFrom(lastEvaluatedKey: Record<string, any>)**: Enables pagination
### Basic Queries
```typescript
// Query by primary key
const results = await userDdb.query({ id: "123" }).execute();
// Query with sort key condition
const results = await userDdb
.query({ id: "123" })
.where("begins_with", { email: "a" })
.execute();
```
### Using Indexes
```typescript
// Query using GSI
const results = await userDdb
.query({ email: "alice@example.com" })
.usingIndex("EmailIndex")
.execute();
```
### Filtering
```typescript
// Query with filter
const results = await userDdb
.query({ id: "123" })
.filter("age", ">", 21)
.limitResults(10)
.execute();
// Multiple filters
const results = await userDdb
.query({ id: "123" })
.filter("age", ">", 21)
.filter("name", "begins_with", "A")
.execute();
```
### Pagination
```typescript
const firstPage = await userDdb.query({ id: "123" }).limitResults(10).execute();
if (firstPage.lastEvaluatedKey) {
const secondPage = await userDdb
.query({ id: "123" })
.limitResults(10)
.startFrom(firstPage.lastEvaluatedKey)
.execute();
}
```
## Scan Operations
Performs a DynamoDB scan operation.
```typescript
scan(): ScanBuilder<T>
```
### ScanBuilder Methods
- **execute()**: Performs the scan operation and returns the results
- **filter(attribute: keyof T, operator: string, value: any)**: Adds a filter condition
- **limitResults(limit: number)**: Limits the number of results
- **startFrom(lastEvaluatedKey: Record<string, any>)**: Enables pagination
- **usingIndex(indexName: string)**: Specifies a GSI to scan
### Example
```typescript
// Simple scan
const results = await userDdb.scan().execute();
// Scan with filter
const results = await userDdb
.scan()
.filter("status", "==", "active")
.limitResults(100)
.execute();
```
## Batch Operations
Performs multiple write operations in a single request.
```typescript
batchWrite(operations: {
puts?: T[];
deletes?: Partial<T>[]
}): Promise<void>
```
### Example
```typescript
await userDdb.batchWrite({
puts: [
{ id: "123", name: "Alice", email: "alice@example.com" },
{ id: "456", name: "Bob", email: "bob@example.com" },
],
deletes: [{ id: "789" }],
});
```
## Transaction Operations
Performs multiple operations atomically.
```typescript
transactWrite(transactItems: any[]): Promise<void>
```
### Example
```typescript
// Build transaction items
const createItem = userDdb
.create({
id: "123",
name: "Alice",
email: "alice@example.com",
})
.toTransactPut();
const updateItem = userDdb
.update({ id: "456" })
.set({ name: "Bob Smith" })
.toTransactUpdate();
const deleteItem = userDdb.delete({ id: "789" }).toTransactDelete();
// Execute transaction
await userDdb.transactWrite([createItem, updateItem, deleteItem]);
```
## Advanced Features
### Automatic Timestamps
When enabled, BetterDDB automatically manages:
- **createdAt**: Set on item creation
- **updatedAt**: Set on item creation and updated on every update
Enable with:
```typescript
const ddb = new BetterDDB({
// ...other options
timestamps: true,
});
```
### Versioning
Enables optimistic locking with version numbers.
```typescript
// Update with version checking
const user = await userDdb
.update({ id: "123" }, 1) // Expected version
.set({ name: "Alice Smith" })
.execute();
```
This will only update if the current version is 1, then increment it to 2.
### Counter
Enables automatic counter incrementing for items.
```typescript
const ddb = new BetterDDB({
// ...other options
counter: true,
});
```
## Key Management
### Key Definitions
```typescript
export interface KeysConfig<T> {
primary: PrimaryKeyConfig<T>;
sort?: SortKeyConfig<T>;
gsis?: Record<string, GSIConfig<T>>;
}
export interface PrimaryKeyConfig<T> {
name: string;
definition: KeyDefinition<T>;
}
export interface SortKeyConfig<T> {
name: string;
definition: KeyDefinition<T>;
}
export interface GSIConfig<T> {
name: string;
primary: PrimaryKeyConfig<T>;
sort?: SortKeyConfig<T>;
}
export type KeyDefinition<T> =
| keyof T
| {
build: (rawKey: Partial<T>) => string;
};
```
### Using Raw Attribute Names
```typescript
const ddb = new BetterDDB<User>({
keys: {
primary: {
name: "pk",
definition: "id", // Use the raw 'id' attribute
},
},
});
```
### Using Computed Keys
```typescript
const ddb = new BetterDDB<User>({
keys: {
primary: {
name: "pk",
definition: {
build: (raw) => `USER#${raw.id}`,
},
},
},
});
```