# Dynamo Document Builder — Full Context
> TypeScript library for DynamoDB single-table design with type-safe operations and automatic Zod schema validation. Version 0.4.0.
This file contains all Dynamo Document Builder documentation concatenated for agents that prefer a single context load. For the link-indexed entry point see https://dynamodocumentbuilder.com/llms.txt.
# Core Concepts
> Tables and Entities are the two foundational primitives. A Table wraps a DynamoDB table; an Entity defines a typed, schema-validated data model that belongs to that table. Commands are dispatched through entities (or the table for multi-entity operations).
## Installation
```bash
npm i dynamo-document-builder zod @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
```
## Tables
A `DynamoTable` represents one DynamoDB table. Construct it once (outside Lambda handlers) and share it across all entities that belong to that table.
```typescript
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { DynamoTable } from 'dynamo-document-builder';
const client = DynamoDBDocumentClient.from(new DynamoDBClient());
const myTable = new DynamoTable({
tableName: 'MyTable',
documentClient: client,
keyNames: {
partitionKey: 'PK',
sortKey: 'SK',
},
});
```
**`keyNames` options**:
- `partitionKey`: Partition key attribute name (default: `'PK'`)
- `sortKey`: Sort key attribute name (default: `'SK'`, set to `null` for simple key tables)
- `globalSecondaryIndexes`: Map of GSI name → `{ partitionKey, sortKey? }`
- `localSecondaryIndexes`: Map of LSI name → `{ sortKey }`
## Entities
A `DynamoEntity` defines a data model, its Zod schema, and optional computed key functions. The schema drives both runtime validation and TypeScript type inference.
```typescript
import { DynamoEntity, key, type Entity } from 'dynamo-document-builder';
import { z } from 'zod';
const todoEntity = new DynamoEntity({
table: myTable,
schema: z.object({
todoId: z.string(),
userId: z.string(),
title: z.string(),
isComplete: z.boolean().default(false),
createdAt: z.iso.datetime(),
}),
partitionKey: todo => key('USER', todo.userId, 'TODO', todo.todoId),
sortKey: todo => key('CREATED_AT', todo.createdAt),
});
type Todo = Entity;
```
**Entity configuration**:
- `table`: Parent `DynamoTable` (required)
- `schema`: Zod object schema (required)
- `partitionKey`: `(item) => string` — computes the PK from item data (optional)
- `sortKey`: `(item) => string` — computes the SK from item data (optional)
- `globalSecondaryIndexes`: Map of GSI name → `{ partitionKey, sortKey? }` builder functions
- `localSecondaryIndexes`: Map of LSI name → `{ sortKey }` builder function
**Key behaviours**:
- `key('PREFIX', value, ...)` joins parts with `'#'` — e.g. `key('USER', '123')` → `'USER#123'`
- PK/SK attributes are automatically added on write and stripped on read (schema validation removes them)
- Schema validation runs on all read results by default; disable per-command with `skipValidation: true`
## Sparse Indexes
Return `undefined` from a GSI/LSI key builder to exclude an item from that index. Use `indexKey()` which automatically returns `undefined` if any input value is undefined.
```typescript
import { indexKey } from 'dynamo-document-builder';
const articleEntity = new DynamoEntity({
table: myTable,
schema: z.object({
id: z.string(),
title: z.string(),
publishedAt: z.string().optional(),
}),
partitionKey: item => key('ARTICLE', item.id),
sortKey: () => 'METADATA',
globalSecondaryIndexes: {
PublishedIndex: {
partitionKey: item => indexKey('PUBLISHED_AT', item.publishedAt),
sortKey: item => indexKey('ARTICLE', item.id),
},
},
});
```
If the GSI partition key builder returns `undefined`, the sort key builder is not called.
## Dispatching Commands
```typescript
// Single-entity command
const { item } = await todoEntity.send(new Get({ key: { userId: '1', todoId: '2' } }));
// Multi-entity command — bind each command to its entity first, then send via the table
const result = await myTable.send(
new TableBatchGet({
gets: [
todoEntity.prepare(new BatchGet({ keys: [{ userId: '1', todoId: '2' }] })),
userEntity.prepare(new BatchGet({ keys: [{ userId: '1' }] })),
],
}),
);
```
`entity.prepare(command)` binds a command to an entity without executing it, producing an input accepted by table-level commands (`TableBatchGet`, `TableBatchWrite`, `TableTransactGet`, `TableTransactWrite`).
## Type Utilities
```typescript
import { type Entity, type EncodedEntity } from 'dynamo-document-builder';
// Domain type — what your application works with
type Todo = Entity;
// Pre-codec type — what is actually stored in DynamoDB (relevant when using Zod codecs)
type EncodedTodo = EncodedEntity;
```
# Read Commands
> All read commands are dispatched via `entity.send(command)`. Results are validated against the entity's Zod schema by default.
## Get
Retrieve a single item by primary key. Returns `{ item: T | undefined }`.
```typescript
import { Get, ProjectedGet } from 'dynamo-document-builder';
const { item } = await todoEntity.send(new Get({
key: { userId: '123', todoId: '456' },
consistent: true, // optional: strongly consistent read
}));
// Projected get — returns a subset of attributes with a narrowed type
const { item } = await todoEntity.send(new ProjectedGet({
key: { userId: '123', todoId: '456' },
projection: ['title', 'isComplete'],
projectionSchema: z.object({
title: z.string(),
isComplete: z.boolean(),
}),
}));
```
**Options**: `key` (required), `consistent`, `skipValidation`, `returnConsumedCapacity`
**`ProjectedGet` additional options**: `projection` (required), `projectionSchema` (required)
## Query
Retrieve multiple items matching a partition key, with optional sort key conditions and filters. Returns `{ items: T[], count: number, lastEvaluatedKey }`.
```typescript
import { Query, ProjectedQuery } from 'dynamo-document-builder';
import { beginsWith, greaterThan, and } from 'dynamo-document-builder';
// Query by entity partition key
const { items, count, lastEvaluatedKey } = await todoEntity.send(new Query({
key: { userId: '123' },
sortKeyCondition: { SK: beginsWith('TODO#') },
filter: and(
{ isComplete: false },
{ createdAt: greaterThan('2024-01-01') },
),
limit: 10,
reverseIndexScan: false,
consistent: false,
}));
// Query a GSI
const { items } = await todoEntity.send(new Query({
index: { StatusIndex: { status: 'in-progress' } },
sortKeyCondition: { GSI1SK: beginsWith('2024') },
}));
```
**Options**: `key` or `index` (one required), `sortKeyCondition`, `filter`, `limit`, `pageSize`, `consistent`, `reverseIndexScan`, `exclusiveStartKey`, `validationConcurrency`, `skipValidation`, `returnConsumedCapacity`
### Pagination
Use `entity.paginate(query)` to iterate pages without manually tracking `lastEvaluatedKey`.
```typescript
for await (const page of todoEntity.paginate(new Query({
key: { userId: '123' },
pageSize: 50,
}))) {
console.log(`Page: ${page.count} items`);
processItems(page.items);
}
```
`pageSize` controls the DynamoDB `Limit` per request. `limit` caps total items returned across all pages.
## Scan
Read all items in a table or index. **Expensive** — prefer `Query` when possible. Filters do not reduce read capacity consumption.
```typescript
import { Scan, ProjectedScan } from 'dynamo-document-builder';
const { items, scannedCount } = await todoEntity.send(new Scan({
filter: { isComplete: false },
limit: 100,
indexName: 'StatusIndex', // optional: scan a GSI or LSI
}));
// Parallel scan — split the table into segments processed concurrently
async function parallelScan(totalSegments: number) {
const segments = Array.from({ length: totalSegments }, (_, i) =>
todoEntity.paginate(new Scan({ segment: i, totalSegments }))
);
// Process each segment's async iterator concurrently
}
```
**Options**: `indexName`, `filter`, `limit`, `segment`, `totalSegments`, `consistent`, `pageSize`, `exclusiveStartKey`, `skipValidation`, `returnConsumedCapacity`
## BatchGet
Retrieve up to 100 items by primary key in one DynamoDB request. Unprocessed keys should be retried with exponential backoff.
```typescript
import { BatchGet, BatchProjectedGet } from 'dynamo-document-builder';
const { items, unprocessedKeys } = await todoEntity.send(new BatchGet({
keys: [
{ userId: '123', todoId: '456' },
{ userId: '789', todoId: '101' },
],
consistent: true,
}));
// Projected batch get
const { items } = await todoEntity.send(new BatchProjectedGet({
keys: [{ userId: '123', todoId: '456' }],
projection: ['title', 'isComplete'],
projectionSchema: z.object({ title: z.string(), isComplete: z.boolean() }),
}));
if (unprocessedKeys?.length) {
// Retry unprocessedKeys with exponential backoff
}
```
**`BatchGet` options**: `keys` (required), `consistent`, `skipValidation`, `returnConsumedCapacity`
**`BatchProjectedGet` options**: `keys` (required), `projection` (required), `projectionSchema` (required), `consistent`, `skipValidation`, `returnConsumedCapacity`
## TransactGet
Strongly consistent, all-or-nothing read of multiple items. Returns `{ items: (T | undefined)[] }` in the same order as the input keys.
```typescript
import { TransactGet } from 'dynamo-document-builder';
const { items } = await todoEntity.send(new TransactGet({
keys: [
{ userId: '123', todoId: '456' },
{ userId: '789', todoId: '101' },
],
}));
// items[0] is undefined if the first key was not found
```
**Options**: `keys` (required), `skipValidation`, `returnConsumedCapacity`
# Write Commands
> All write commands are dispatched via `entity.send(command)`. Input items are validated against the entity's Zod schema before being written.
## Put
Create or replace an item.
```typescript
import { Put, ConditionalPut } from 'dynamo-document-builder';
await todoEntity.send(new Put({
item: {
userId: '123',
todoId: '456',
title: 'Take out trash',
isComplete: false,
createdAt: new Date().toISOString(),
},
returnValues: 'ALL_OLD', // optional: return the previous item if it existed
}));
// Conditional put — only write if condition passes
await todoEntity.send(new ConditionalPut({
item: { /* ... */ },
condition: { isComplete: false },
returnValuesOnConditionCheckFailure: 'ALL_OLD',
}));
```
**`Put` options**: `item` (required), `returnValues`, `returnItemCollectionMetrics`, `skipValidation`, `returnConsumedCapacity`
**`ConditionalPut` additional options**: `condition` (required), `returnValuesOnConditionCheckFailure`
## Update
Modify specific attributes of an existing item without replacing it.
```typescript
import { Update, ConditionalUpdate } from 'dynamo-document-builder';
import {
ref, remove, add, subtract,
append, prepend, addToSet, removeFromSet,
} from 'dynamo-document-builder';
await todoEntity.send(new Update({
key: { userId: '123', todoId: '456' },
updates: {
title: 'New title',
'nested.attribute': 'value',
backup: ref('title'),
valueWithDefault: ref('optional', 'fallback'),
obsolete: remove(),
counter: add(5),
score: subtract(2),
tags: append(['newTag']),
priorities: prepend(['urgent']),
categories: addToSet(['category1', 'category2']),
oldTags: removeFromSet(['deprecated']),
},
returnValues: 'ALL_NEW',
}));
// Conditional update
await todoEntity.send(new ConditionalUpdate({
key: { userId: '123', todoId: '456' },
updates: { isComplete: true },
condition: { isComplete: false },
}));
```
### Update Operations Reference
| Operation | Description |
|---|---|
| `value` | Set attribute to a literal value |
| `ref(path, default?)` | Set to the current value of another attribute |
| `remove()` | Delete the attribute |
| `add(n)` | Increment a number by `n` |
| `subtract(n)` | Decrement a number by `n` |
| `append(items)` | Append elements to a list |
| `prepend(items)` | Prepend elements to a list |
| `addToSet(values)` | Add values to a DynamoDB set |
| `removeFromSet(values)` | Remove values from a DynamoDB set |
**`Update` options**: `key` (required), `updates` (required), `returnValues`, `skipValidation`, `returnConsumedCapacity`
**`ConditionalUpdate` additional options**: `condition` (required), `returnValuesOnConditionCheckFailure`
## Delete
Remove an item by primary key.
```typescript
import { Delete, ConditionalDelete } from 'dynamo-document-builder';
await todoEntity.send(new Delete({
key: { userId: '123', todoId: '456' },
returnValues: 'ALL_OLD',
}));
// Conditional delete
await todoEntity.send(new ConditionalDelete({
key: { userId: '123', todoId: '456' },
condition: { isComplete: true },
}));
```
**`Delete` options**: `key` (required), `returnValues`, `returnItemCollectionMetrics`, `skipValidation`, `returnConsumedCapacity`
**`ConditionalDelete` additional options**: `condition` (required), `returnValuesOnConditionCheckFailure`
## BatchWrite
Put and/or delete multiple items in one DynamoDB request. At least one of `items` or `deletes` is required.
```typescript
import { BatchWrite } from 'dynamo-document-builder';
const { unprocessedPuts, unprocessedDeletes } = await todoEntity.send(new BatchWrite({
items: [
{ userId: '123', todoId: '456', title: 'Task 1', isComplete: false, createdAt: '...' },
],
deletes: [{ userId: '111', todoId: '222' }],
}));
```
**Options**: `items`, `deletes` (at least one required), `returnItemCollectionMetrics`, `skipValidation`, `returnConsumedCapacity`
## TransactWrite
Atomic, all-or-nothing multi-item write. Supports up to 100 operations.
```typescript
import { TransactWrite, ConditionCheck, Put, Update, Delete } from 'dynamo-document-builder';
await todoEntity.send(new TransactWrite({
writes: [
new Put({ item: { /* ... */ } }),
new Update({ key: { /* ... */ }, updates: { isComplete: true } }),
new Delete({ key: { /* ... */ } }),
new ConditionCheck({
key: { userId: '123', todoId: '456' },
condition: { isComplete: false },
}),
],
idempotencyToken: 'unique-idempotency-token',
}));
```
**Supported write types**: `Put`, `ConditionalPut`, `Update`, `ConditionalUpdate`, `Delete`, `ConditionalDelete`, `ConditionCheck`
**Options**: `writes` (required), `idempotencyToken`, `returnItemCollectionMetrics`, `skipValidation`, `returnConsumedCapacity`
## Error Handling
Conditional commands throw `ConditionalCheckFailedException` when the condition is not satisfied.
```typescript
try {
await todoEntity.send(new ConditionalPut({ item: newItem, condition: { status: 'draft' } }));
} catch (error) {
if (error.name === 'ConditionalCheckFailedException') {
// Handle condition failure
}
}
```
# Multi-Entity Commands
> Table-level commands operate across multiple entity types in a single DynamoDB request. They are dispatched via `table.send()`. Use `entity.prepare(command)` to bind a command to an entity before passing it to a table command.
All entities involved in a multi-entity command must belong to the same table. A `DocumentBuilderError` is thrown at runtime if they do not.
## entity.prepare()
`entity.prepare(command)` binds a single command or array of commands to an entity without executing them, producing a typed group accepted by table-level commands.
```typescript
// Single command
todoEntity.prepare(new BatchGet({ keys: [...] }))
// Array of commands (used with TableTransactWrite)
todoEntity.prepare([
new Put({ item: { /* ... */ } }),
new Delete({ key: { /* ... */ } }),
])
```
## TableBatchGet
Batch get across multiple entity types. Results are typed tuples in the same order as the input groups.
```typescript
import { TableBatchGet, BatchGet } from 'dynamo-document-builder';
const { items, unprocessedKeys } = await myTable.send(
new TableBatchGet({
consistent: true,
gets: [
userEntity.prepare(new BatchGet({ keys: [{ userId: '1' }] })),
orderEntity.prepare(new BatchGet({ keys: [{ orderId: 'o1' }, { orderId: 'o2' }] })),
],
}),
);
const [users, orders] = items;
```
**Consistency semantics**:
- `consistent: true` on the command → forces `ConsistentRead: true` for all groups
- `consistent: false` on the command → forces `ConsistentRead: false` even if a group sets `consistent: true`
- Not set on the command → per-group logic; if any group has `consistent: true`, the entire request uses it
DynamoDB does not guarantee order in batch get responses. Document Builder matches items back to their group by primary key.
**Options**: `gets` (required), `consistent`, `skipValidation`, `returnConsumedCapacity`
## TableBatchWrite
Batch write (puts and deletes) across multiple entity types.
```typescript
import { TableBatchWrite, BatchWrite } from 'dynamo-document-builder';
const { unprocessedPuts, unprocessedDeletes } = await myTable.send(
new TableBatchWrite({
writes: [
userEntity.prepare(new BatchWrite({
items: [{ userId: '1', name: 'Alice', /* ... */ }],
deletes: [{ userId: '2' }],
})),
orderEntity.prepare(new BatchWrite({
items: [{ orderId: 'o1', status: 'pending', total: 99 }],
})),
],
}),
);
const [userUnprocessedPuts, orderUnprocessedPuts] = unprocessedPuts;
const [userUnprocessedDeletes, orderUnprocessedDeletes] = unprocessedDeletes;
```
**Options**: `writes` (required), `returnItemCollectionMetrics`, `skipValidation`, `returnConsumedCapacity`
## TableTransactGet
Strongly consistent, all-or-nothing read across multiple entity types.
```typescript
import { TableTransactGet, TransactGet } from 'dynamo-document-builder';
const { items } = await myTable.send(
new TableTransactGet({
gets: [
userEntity.prepare(new TransactGet({ keys: [{ userId: '1' }, { userId: '2' }] })),
orderEntity.prepare(new TransactGet({ keys: [{ orderId: 'o1' }] })),
],
}),
);
const [users, orders] = items;
// users: (User | undefined)[]
// orders: (Order | undefined)[]
```
**Options**: `gets` (required), `skipValidation`, `returnConsumedCapacity`
## TableTransactWrite
Atomic, all-or-nothing write across multiple entity types.
```typescript
import { TableTransactWrite, Put, Update, Delete } from 'dynamo-document-builder';
await myTable.send(
new TableTransactWrite({
transactions: [
userEntity.prepare([
new Put({ item: { userId: '1', name: 'Alice' } }),
new Delete({ key: { userId: '2' } }),
]),
orderEntity.prepare([
new Update({ key: { orderId: 'o1' }, updates: { status: 'shipped' } }),
]),
],
idempotencyToken: 'unique-token',
}),
);
```
**Options**: `transactions` (required), `idempotencyToken`, `returnItemCollectionMetrics`, `skipValidation`, `returnConsumedCapacity`
## Tree-Shakable Imports
```typescript
import { TableBatchGet } from 'dynamo-document-builder/commands/table-batch-get';
import { TableBatchWrite } from 'dynamo-document-builder/commands/table-batch-write';
import { TableTransactGet } from 'dynamo-document-builder/commands/table-transact-get';
import { TableTransactWrite } from 'dynamo-document-builder/commands/table-transact-write';
```
# Conditions API
> An expressive API for building DynamoDB condition expressions. Used in conditional writes (`condition:`), query/scan filters (`filter:`), and sort key conditions (`sortKeyCondition:`). All operators are importable from `'dynamo-document-builder'`.
Conditions are plain objects mapping attribute paths to operator values. A direct value implies `equals`.
```typescript
{ status: 'active' } // same as { status: equals('active') }
{ status: equals('active') }
```
## Comparison Operators
```typescript
import {
equals, notEquals,
greaterThan, greaterThanOrEqual,
lessThan, lessThanOrEqual,
between, isIn,
} from 'dynamo-document-builder';
{ status: equals('active') }
{ status: notEquals('cancelled') }
{ age: greaterThan(18) }
{ age: greaterThanOrEqual(21) }
{ age: lessThan(65) }
{ age: lessThanOrEqual(100) }
{ price: between(10, 100) } // inclusive on both ends
{ status: isIn('pending', 'processing', 'shipped') }
```
## Logical Operators
```typescript
import { and, or, not } from 'dynamo-document-builder';
and({ total: greaterThan(50) }, { total: lessThan(200) })
// AND — implicit via array
[{ total: greaterThan(50) }, { total: lessThan(200) }]
or({ status: 'pending' }, { status: 'processing' })
not({ status: 'cancelled' })
// Nested combinations
and(
{ status: 'active' },
not({ role: 'admin' }),
or({ tier: 'pro' }, { tier: 'enterprise' }),
)
```
## String Operators
```typescript
import { beginsWith, contains } from 'dynamo-document-builder';
{ SK: beginsWith('USER#') }
{ title: contains('DynamoDB') }
{ tags: contains('urgent') } // works on lists and sets too
```
`beginsWith` is the only operator valid in `sortKeyCondition`.
## Attribute Checks
```typescript
import { exists, notExists, size, typeIs } from 'dynamo-document-builder';
{ lastLogin: exists() }
{ middleName: notExists() }
{ username: size(5) } // exactly 5 characters/elements
{ tags: size(greaterThan(2)) } // size with nested comparison
{ age: typeIs('N') }
{ isActive: typeIs('BOOL') }
```
### DynamoDB Type Descriptors
| Descriptor | Type |
|---|---|
| `S` | String |
| `N` | Number |
| `B` | Binary |
| `BOOL` | Boolean |
| `NULL` | Null |
| `M` | Map |
| `L` | List |
| `SS` | String Set |
| `NS` | Number Set |
| `BS` | Binary Set |
# Advanced Features
## Zod Codecs
Document Builder supports Zod 4.1+ codecs for custom serialization/deserialization. Codecs let you work with richer types in application code (e.g. `Date`) while storing a simpler representation in DynamoDB (e.g. ISO string).
```typescript
import { z } from 'zod';
const stringToDate = z.codec(
z.iso.datetime(), // stored type
z.date(), // domain type
{
encode: (date: Date) => date.toISOString(),
decode: (iso: string) => new Date(iso),
},
);
const eventEntity = new DynamoEntity({
table: myTable,
schema: z.object({
id: z.string(),
name: z.string(),
eventDate: stringToDate,
}),
partitionKey: event => key('EVENT', event.id),
sortKey: () => 'METADATA',
});
await eventEntity.send(new Put({
item: { id: '1', name: 'Conference', eventDate: new Date(2025, 0, 15) },
}));
const { item } = await eventEntity.send(new Get({ key: { id: '1' } }));
// item.eventDate is a Date instance
```
**Warning**: `skipValidation: true` bypasses both schema validation and codec encoding/decoding. Use `EncodedEntity` when you need the raw stored types.
## Low-Level Parsers
```typescript
import { parseCondition } from 'dynamo-document-builder/conditions/condition-parser';
import { parseUpdate } from 'dynamo-document-builder/updates/update-parser';
import { parseProjection } from 'dynamo-document-builder/projections/projection-parser';
import { AttributeExpressionMap } from 'dynamo-document-builder/attributes/attribute-map';
const { conditionExpression, attributeExpressionMap } = parseCondition({
age: greaterThan(21),
status: 'active',
});
const { updateExpression } = parseUpdate({ counter: add(1) });
const { projectionExpression } = parseProjection(['name', 'age', 'address.city']);
// Share a map across parsers to avoid token collisions
const map = new AttributeExpressionMap();
const { conditionExpression } = parseCondition(condition, map);
const { updateExpression } = parseUpdate(updates, map);
const { ExpressionAttributeNames, ExpressionAttributeValues } =
map.toDynamoAttributeExpression();
```
## AttributeExpressionMap
```typescript
import { AttributeExpressionMap } from 'dynamo-document-builder/attributes/attribute-map';
const map = new AttributeExpressionMap();
const [nameToken, valueToken] = map.add('status', 'active');
// '#status', ':v1'
map.addName('age'); // '#age'
map.addValue(30); // ':v2'
map.hasName('status');
map.hasValue(30);
map.getPlaceholderFromName('status'); // '#status'
map.getPlaceholderFromValue('active'); // ':v1'
map.getNameFromPlaceholder('#status'); // 'status'
map.getValueFromPlaceholder(':v1'); // 'active'
map.toDynamoAttributeNames(); // { '#status': 'status' }
map.toDynamoAttributeValues(); // { ':v1': 'active' }
map.toDynamoAttributeExpression(); // both combined
```
## Tree-Shakable Imports
```typescript
import { DynamoTable } from 'dynamo-document-builder/core/table';
import { DynamoEntity } from 'dynamo-document-builder/core/entity';
import { Get } from 'dynamo-document-builder/commands/get';
import { ProjectedGet } from 'dynamo-document-builder/commands/projected-get';
import { Query } from 'dynamo-document-builder/commands/query';
import { ProjectedQuery } from 'dynamo-document-builder/commands/projected-query';
import { Scan } from 'dynamo-document-builder/commands/scan';
import { ProjectedScan } from 'dynamo-document-builder/commands/projected-scan';
import { BatchGet } from 'dynamo-document-builder/commands/batch-get';
import { BatchProjectedGet } from 'dynamo-document-builder/commands/batch-projected-get';
import { TransactGet } from 'dynamo-document-builder/commands/transact-get';
import { Put } from 'dynamo-document-builder/commands/put';
import { ConditionalPut } from 'dynamo-document-builder/commands/conditional-put';
import { Update } from 'dynamo-document-builder/commands/update';
import { ConditionalUpdate } from 'dynamo-document-builder/commands/conditional-update';
import { Delete } from 'dynamo-document-builder/commands/delete';
import { ConditionalDelete } from 'dynamo-document-builder/commands/conditional-delete';
import { BatchWrite } from 'dynamo-document-builder/commands/batch-write';
import { TransactWrite } from 'dynamo-document-builder/commands/transact-write';
import { TableBatchGet } from 'dynamo-document-builder/commands/table-batch-get';
import { TableBatchWrite } from 'dynamo-document-builder/commands/table-batch-write';
import { TableTransactGet } from 'dynamo-document-builder/commands/table-transact-get';
import { TableTransactWrite } from 'dynamo-document-builder/commands/table-transact-write';
import { equals } from 'dynamo-document-builder/conditions/equals';
import { notEquals } from 'dynamo-document-builder/conditions/not-equals';
import { greaterThan } from 'dynamo-document-builder/conditions/greater-than';
import { greaterThanOrEqual } from 'dynamo-document-builder/conditions/greater-than-or-equal';
import { lessThan } from 'dynamo-document-builder/conditions/less-than';
import { lessThanOrEqual } from 'dynamo-document-builder/conditions/less-than-or-equal';
import { between } from 'dynamo-document-builder/conditions/between';
import { isIn } from 'dynamo-document-builder/conditions/is-in';
import { and } from 'dynamo-document-builder/conditions/and';
import { or } from 'dynamo-document-builder/conditions/or';
import { not } from 'dynamo-document-builder/conditions/not';
import { beginsWith } from 'dynamo-document-builder/conditions/begins-with';
import { contains } from 'dynamo-document-builder/conditions/contains';
import { exists } from 'dynamo-document-builder/conditions/exists';
import { notExists } from 'dynamo-document-builder/conditions/not-exists';
import { size } from 'dynamo-document-builder/conditions/size';
import { typeIs } from 'dynamo-document-builder/conditions/type-is';
import { ref } from 'dynamo-document-builder/updates/ref';
import { remove } from 'dynamo-document-builder/updates/remove';
import { add } from 'dynamo-document-builder/updates/add';
import { subtract } from 'dynamo-document-builder/updates/subtract';
import { append } from 'dynamo-document-builder/updates/append';
import { prepend } from 'dynamo-document-builder/updates/prepend';
import { addToSet } from 'dynamo-document-builder/updates/add-to-set';
import { removeFromSet } from 'dynamo-document-builder/updates/remove-from-set';
```
# Patterns, Best Practices & Troubleshooting
## Performance Best Practices
1. **Query over Scan** — Queries target a specific partition; scans read the entire table. Filters on scans do not reduce capacity consumption.
2. **Limit consistent reads** — Strongly consistent reads cost 2× read capacity. Use `consistent: true` only when stale data is unacceptable.
3. **Use projection expressions** — `ProjectedGet`, `ProjectedQuery`, `ProjectedScan`, and `BatchProjectedGet` reduce data transfer.
4. **Paginate large result sets** — Use `entity.paginate(query)` with a `pageSize`.
5. **Parallel scans** — Split large full-table scans into `totalSegments` processed concurrently.
6. **Batch operations** — Use `BatchGet` / `BatchWrite` instead of looping single-item commands.
7. **Retry unprocessed items** — Retry with exponential backoff and jitter.
## Design Patterns
### Single Table Design
```
PK: USER# SK: PROFILE
PK: USER# SK: TODO#
PK: ORDER# SK: METADATA
```
### User with Email Lookup Index
```typescript
const userEntity = new DynamoEntity({
table: myTable,
schema: z.object({
userId: z.string(),
email: z.string().email(),
name: z.string(),
role: z.enum(['user', 'admin']),
createdAt: z.iso.datetime(),
}),
partitionKey: user => key('USER', user.userId),
sortKey: () => 'PROFILE',
globalSecondaryIndexes: {
EmailIndex: {
partitionKey: user => key('EMAIL', user.email),
sortKey: user => key('USER', user.userId),
},
},
});
```
### Hierarchical Data
```typescript
const commentEntity = new DynamoEntity({
table: myTable,
schema: z.object({
postId: z.string(),
commentId: z.string(),
userId: z.string(),
content: z.string(),
createdAt: z.iso.datetime(),
}),
partitionKey: comment => key('POST', comment.postId),
sortKey: comment => key('COMMENT', comment.createdAt, comment.commentId),
});
const { items } = await commentEntity.send(new Query({
key: { postId: '123' },
sortKeyCondition: { SK: beginsWith('COMMENT#') },
reverseIndexScan: true,
}));
```
### Audit Trail
```typescript
const auditEntity = new DynamoEntity({
table: myTable,
schema: z.object({
entityId: z.string(),
action: z.enum(['create', 'update', 'delete']),
userId: z.string(),
timestamp: z.iso.datetime(),
changes: z.record(z.any()),
}),
partitionKey: audit => key('ENTITY', audit.entityId),
sortKey: audit => key('AUDIT', audit.timestamp),
});
```
### Lambda Best Practices
```typescript
// Outside handler — constructed once per Lambda instance
const table = new DynamoTable({
tableName: process.env.TABLE_NAME!,
documentClient: DynamoDBDocumentClient.from(new DynamoDBClient()),
});
const todoEntity = new DynamoEntity({ table, schema: todoSchema, /* ... */ });
export const handler = async (event) => {
const { item } = await todoEntity.send(new Get({ key: { /* ... */ } }));
};
```
## Troubleshooting
**Keys not being written**: Verify `partitionKey`/`sortKey` are defined and return non-empty strings. Use `key()` for consistent `#`-separated formatting.
**Validation errors on read**: Confirm schema matches stored attribute names and types. Use `skipValidation: true` temporarily to inspect raw results (not for production).
**TypeScript type errors**: Use `Entity` for domain type inference. Use `EncodedEntity` for raw stored types.
**Performance issues**: Replace `Scan` with `Query` by adding a GSI. Add projection expressions. Enable pagination.
## Quick Reference: All Imports
```typescript
import { DynamoTable, DynamoEntity, key, indexKey, type Entity, type EncodedEntity } from 'dynamo-document-builder';
import { Get, ProjectedGet, Query, ProjectedQuery, Scan, ProjectedScan } from 'dynamo-document-builder';
import { BatchGet, BatchProjectedGet, TransactGet } from 'dynamo-document-builder';
import { Put, ConditionalPut, Update, ConditionalUpdate } from 'dynamo-document-builder';
import { Delete, ConditionalDelete, BatchWrite, TransactWrite, ConditionCheck } from 'dynamo-document-builder';
import { TableBatchGet, TableBatchWrite, TableTransactGet, TableTransactWrite } from 'dynamo-document-builder';
import {
equals, notEquals, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual,
between, isIn, and, or, not, beginsWith, contains, exists, notExists, size, typeIs,
} from 'dynamo-document-builder';
import { ref, remove, add, subtract, append, prepend, addToSet, removeFromSet } from 'dynamo-document-builder';
```