# 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'; ```