dynamodb-toolbox

📁 alfgoto/ddb-toolbox-skill 📅 Jan 28, 2026
10
总安装量
5
周安装量
#29532
全站排名
安装命令
npx skills add https://github.com/alfgoto/ddb-toolbox-skill --skill dynamodb-toolbox

Agent 安装分布

opencode 5
gemini-cli 5
antigravity 5
github-copilot 5
windsurf 5

Skill 文档

DynamoDB-Toolbox v2

Type-safe query builder for DynamoDB with schema validation and the .build() pattern.

Installation

npm install dynamodb-toolbox @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

TypeScript 5+ with "strict": true in tsconfig.json is recommended.

Core Concepts

Import Pattern

Import directly from the main package:

import {
  Table,
  Entity,
  item, string, number, boolean, binary, list, map, set, record, anyOf, any,
  GetItemCommand,
  PutItemCommand,
  UpdateItemCommand,
  DeleteItemCommand,
  QueryCommand,
  ScanCommand
} from 'dynamodb-toolbox'

Table Definition

import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
import { Table } from 'dynamodb-toolbox'

const client = new DynamoDBClient({})
const documentClient = DynamoDBDocumentClient.from(client)

const MyTable = new Table({
  documentClient,
  name: 'my-table',
  partitionKey: { name: 'PK', type: 'string' },
  sortKey: { name: 'SK', type: 'string' },  // optional
  indexes: {  // optional
    GSI1: {
      type: 'global',
      partitionKey: { name: 'GSI1PK', type: 'string' },
      sortKey: { name: 'GSI1SK', type: 'string' }
    },
    LSI1: {
      type: 'local',
      sortKey: { name: 'LSI1SK', type: 'number' }
    }
  },
  entityAttributeSavedAs: '_et'  // optional, defaults to 'entity'
})

Entity Definition

import { Entity, item, string, number, boolean, list, map } from 'dynamodb-toolbox'

const UserEntity = new Entity({
  name: 'User',
  table: MyTable,
  schema: item({
    // Key attributes
    PK: string().key().savedAs('PK'),
    SK: string().key().savedAs('SK'),

    // Regular attributes
    userId: string(),
    email: string(),
    name: string().optional(),
    age: number().optional(),
    isActive: boolean().default(true),
    tags: list(string()).optional(),
    profile: map({
      bio: string().optional(),
      avatar: string().optional()
    }).optional()
  }),
  computeKey: ({ userId }) => ({
    PK: `USER#${userId}`,
    SK: `PROFILE`
  }),
  timestamps: true  // adds created/modified timestamps
})

Schema Types

Primitives

string()              // String
number()              // Number (use .big() for BigInt)
boolean()             // Boolean
binary()              // Binary (Uint8Array)
any()                 // Any type (no validation)

Collections

list(string())                    // List of strings
set(number())                     // Number set (also string/binary sets)
map({ name: string() })           // Map with known keys
record(string(), number())        // Map with dynamic keys

Union Types

anyOf(
  map({ type: string().const('dog'), breed: string() }),
  map({ type: string().const('cat'), lives: number() })
)

Attribute Modifiers

string()
  .required()           // Required (default: 'atLeastOnce')
  .optional()           // Same as .required('never')
  .hidden()             // Omit from formatted output
  .key()                // Mark as primary key attribute
  .savedAs('attr_name') // Rename in DynamoDB
  .enum('a', 'b', 'c')  // Restrict to specific values
  .default('value')     // Default value
  .default(() => uuid()) // Default with getter
  .transform(...)       // Transform during parse/format
  .validate(v => v.length > 0) // Custom validation
  .link<Schema>(({ name }) => name.toUpperCase()) // Derive from other attrs

Commands (The .build() Pattern)

GetItemCommand

import { GetItemCommand } from 'dynamodb-toolbox'

const { Item } = await UserEntity.build(GetItemCommand)
  .key({ userId: '123' })
  .options({
    consistent: true,           // Strongly consistent read
    attributes: ['email', 'name'] // Project specific attributes
  })
  .send()

PutItemCommand

import { PutItemCommand } from 'dynamodb-toolbox'

const { Attributes } = await UserEntity.build(PutItemCommand)
  .item({
    userId: '123',
    email: 'user@example.com',
    name: 'John'
  })
  .options({
    returnValues: 'ALL_OLD',
    condition: { attr: 'userId', exists: false } // Only if not exists
  })
  .send()

UpdateItemCommand

import { UpdateItemCommand, $add, $remove, $append, $set } from 'dynamodb-toolbox'

// Basic update
await UserEntity.build(UpdateItemCommand)
  .item({
    userId: '123',
    name: 'Jane',
    age: 30
  })
  .send()

// Extended syntax
await UserEntity.build(UpdateItemCommand)
  .item({
    userId: '123',
    age: $add(1),                    // Increment
    oldField: $remove(),             // Remove attribute
    tags: $append(['new-tag']),      // Append to list
    profile: $set({ bio: 'Hello' })  // Replace entire nested object
  })
  .options({ returnValues: 'ALL_NEW' })
  .send()

Update Operations:

  • $add(n) – Add to number or add elements to set
  • $remove() – Remove attribute
  • $set(value) – Override entire value (for deep attributes)
  • $append(items) – Append to list
  • $prepend(items) – Prepend to list
  • $get(path) – Reference another attribute’s value

DeleteItemCommand

import { DeleteItemCommand } from 'dynamodb-toolbox'

const { Attributes } = await UserEntity.build(DeleteItemCommand)
  .key({ userId: '123' })
  .options({
    returnValues: 'ALL_OLD',
    condition: { attr: 'isActive', eq: false }
  })
  .send()

QueryCommand (Table-level)

import { QueryCommand } from 'dynamodb-toolbox'

const { Items, LastEvaluatedKey } = await MyTable.build(QueryCommand)
  .query({
    partition: 'USER#123',
    range: { gte: 'ORDER#' }  // Range condition: eq, lt, lte, gt, gte, between, beginsWith
  })
  .entities(UserEntity, OrderEntity)  // Filter/format by entities
  .options({
    index: 'GSI1',           // Use secondary index
    consistent: true,
    reverse: true,           // Reverse order
    limit: 10,
    filters: { attr: 'status', eq: 'active' }
  })
  .send()

// Pagination
let lastKey
do {
  const result = await MyTable.build(QueryCommand)
    .query({ partition: 'USER#123' })
    .options({ exclusiveStartKey: lastKey })
    .send()
  // Process result.Items
  lastKey = result.LastEvaluatedKey
} while (lastKey)

ScanCommand (Table-level)

import { ScanCommand } from 'dynamodb-toolbox'

const { Items } = await MyTable.build(ScanCommand)
  .entities(UserEntity)
  .options({
    limit: 100,
    filters: { attr: 'isActive', eq: true }
  })
  .send()

// Parallel scan
const segment = 0
const totalSegments = 4
await MyTable.build(ScanCommand)
  .options({ segment, totalSegments })
  .send()

Batch Operations

BatchGet

import { BatchGetRequest, BatchGetCommand, executeBatchGet } from 'dynamodb-toolbox'

const requests = [
  UserEntity.build(BatchGetRequest).key({ userId: '1' }),
  UserEntity.build(BatchGetRequest).key({ userId: '2' })
]

const command = MyTable.build(BatchGetCommand).requests(...requests)
const { Responses } = await executeBatchGet(command)

BatchWrite

import { BatchPutRequest, BatchDeleteRequest, BatchWriteCommand, executeBatchWrite } from 'dynamodb-toolbox'

const requests = [
  UserEntity.build(BatchPutRequest).item({ userId: '1', email: 'a@b.com' }),
  UserEntity.build(BatchDeleteRequest).key({ userId: '2' })
]

const command = MyTable.build(BatchWriteCommand).requests(...requests)
await executeBatchWrite(command)

Transactions

TransactGet

import { GetTransaction, executeTransactGet } from 'dynamodb-toolbox'

const transactions = [
  UserEntity.build(GetTransaction).key({ userId: '1' }),
  OrderEntity.build(GetTransaction).key({ orderId: '100' })
]

const { Responses } = await executeTransactGet(...transactions)

TransactWrite

import { PutTransaction, UpdateTransaction, DeleteTransaction, ConditionCheck, executeTransactWrite } from 'dynamodb-toolbox'

await executeTransactWrite(
  UserEntity.build(PutTransaction).item({ userId: '1', email: 'new@email.com' }),
  OrderEntity.build(UpdateTransaction).item({ orderId: '100', status: 'shipped' }),
  UserEntity.build(ConditionCheck)
    .key({ userId: '2' })
    .options({ condition: { attr: 'balance', gte: 100 } })
)

Conditions

Use in .options({ condition: ... }) for conditional writes:

// Simple conditions
{ attr: 'status', eq: 'active' }
{ attr: 'age', gt: 18 }
{ attr: 'email', exists: true }
{ attr: 'name', beginsWith: 'John' }
{ attr: 'tags', contains: 'premium' }
{ attr: 'score', between: [10, 100] }

// Logical operators
{ and: [{ attr: 'a', eq: 1 }, { attr: 'b', eq: 2 }] }
{ or: [{ attr: 'status', eq: 'a' }, { attr: 'status', eq: 'b' }] }
{ not: { attr: 'deleted', eq: true } }

Common Patterns

Single-Table Design

// Base entity with shared key structure
const baseSchema = {
  PK: string().key(),
  SK: string().key()
}

const UserEntity = new Entity({
  name: 'User',
  table: MyTable,
  schema: item({
    ...baseSchema,
    userId: string(),
    email: string()
  }),
  computeKey: ({ userId }) => ({ PK: `USER#${userId}`, SK: 'PROFILE' })
})

const OrderEntity = new Entity({
  name: 'Order',
  table: MyTable,
  schema: item({
    ...baseSchema,
    userId: string(),
    orderId: string(),
    total: number()
  }),
  computeKey: ({ userId, orderId }) => ({ PK: `USER#${userId}`, SK: `ORDER#${orderId}` })
})

Access Patterns

import { EntityAccessPattern, item, string } from 'dynamodb-toolbox'

const getUserOrders = UserEntity.build(EntityAccessPattern)
  .schema(item({ userId: string() }))
  .pattern(({ userId }) => ({
    partition: `USER#${userId}`,
    range: { beginsWith: 'ORDER#' }
  }))

const { Items } = await getUserOrders.query({ userId: '123' }).send()

For detailed API reference, see references/api.md.