# DynamoDB Client

Execute operations against Amazon DynamoDB with full type safety and runtime validation. All parameters are passed directly to the AWS SDK as JSON.

## Methods

| Method                                                          | Description                  |
| --------------------------------------------------------------- | ---------------------------- |
| `query(statement, schema, params?, metadata?)`                  | Execute a PartiQL statement  |
| `getItem(table, key, schema, metadata?)`                        | Get an item by key           |
| `putItem(table, item, metadata?)`                               | Insert or replace an item    |
| `updateItem(table, key, expr, values, names?, metadata?)`       | Update an existing item      |
| `deleteItem(table, key, metadata?)`                             | Delete an item by key        |
| `scan(table, schema, filter?, values?, names?, metadata?)`      | Scan a table                 |
| `scan(table, schema, options, metadata?)`                       | Scan with pagination options |
| `queryTable(table, keyExpr, values, schema, names?, metadata?)` | Query by key condition       |
| `batchWriteItem(requestItems, metadata?)`                       | Batch write multiple items   |
| `listTables(schema, metadata?)`                                 | List all tables              |
| `describeTable(table, schema, metadata?)`                       | Describe a table's structure |
| `deleteTable(table, metadata?)`                                 | Delete a table               |

## DynamoDB AttributeValue Format

All values use DynamoDB's native type descriptors:

```typescript
{
  S: "hello";
} // String
{
  N: "42";
} // Number (always a string)
{
  BOOL: true;
} // Boolean
{
  NULL: true;
} // Null
{
  L: [{ S: "a" }];
} // List
{
  M: {
    k: {
      S: "v";
    }
  }
} // Map
```

## Usage

### PartiQL Query

```typescript
import { api, z, dynamodb } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_DYNAMODB = "a1b2c3d4-5678-90ab-cdef-dynamodb0001";

const UserSchema = z.array(
  z.object({
    id: z.string(),
    name: z.string(),
    email: z.string(),
  }),
);

export default api({
  name: "DynamoDBExample",
  integrations: {
    dynamodb: dynamodb(PROD_DYNAMODB),
  },
  input: z.object({
    status: z.string(),
  }),
  output: z.object({
    users: UserSchema,
  }),
  async run(ctx, { status }) {
    const users = await ctx.integrations.dynamodb.query(
      "SELECT * FROM users WHERE status = ?",
      UserSchema,
      [{ S: status }],
    );

    return { users };
  },
});
```

### Get Item by Key

```typescript
const user = await ctx.integrations.dynamodb.getItem(
  "users",
  { id: { S: "user-123" } },
  z.object({ id: z.string(), name: z.string(), email: z.string() }),
);
```

### Put Item

```typescript
await ctx.integrations.dynamodb.putItem("users", {
  id: { S: "user-456" },
  name: { S: "John Doe" },
  email: { S: "john@example.com" },
  age: { N: "30" },
});
```

### Update Item

```typescript
await ctx.integrations.dynamodb.updateItem(
  "users",
  { id: { S: "user-123" } },
  "SET #n = :name, email = :email",
  {
    ":name": { S: "Jane Doe" },
    ":email": { S: "jane@example.com" },
  },
  { "#n": "name" }, // name is a reserved word in DynamoDB
);
```

### Delete Item

```typescript
await ctx.integrations.dynamodb.deleteItem("users", { id: { S: "user-123" } });
```

### Scan Table

```typescript
// DynamoDB Scan returns raw AttributeValue maps.
// import type { DynamoDBAttributeValue } from "@superblocksteam/sdk-api";
const AttributeValueSchema = z.custom<DynamoDBAttributeValue>();
const ItemSchema = z.record(AttributeValueSchema);
const ScanPageSchema = z.object({
  Items: z.array(ItemSchema).optional(),
  LastEvaluatedKey: z.record(AttributeValueSchema).optional(),
});

// Paginate past DynamoDB's 1 MB per-Scan limit. Bound the number of
// requests so throttling or an unexpectedly large table cannot consume
// the entire API execution timeout.
const MAX_PAGES = 100;
let exclusiveStartKey: Record<string, DynamoDBAttributeValue> | undefined;
const allItems: Array<Record<string, DynamoDBAttributeValue>> = [];
for (let pageNumber = 0; pageNumber < MAX_PAGES; pageNumber += 1) {
  const page = await ctx.integrations.dynamodb.scan("users", ScanPageSchema, {
    exclusiveStartKey,
    filterExpression: "status = :s",
    expressionAttributeValues: { ":s": { S: "active" } },
  });
  allItems.push(...(page.Items ?? []));
  exclusiveStartKey = page.LastEvaluatedKey;
  if (!exclusiveStartKey) {
    break;
  }
  if (pageNumber === MAX_PAGES - 1) {
    throw new Error(`Scan exceeded the ${MAX_PAGES}-page safety limit`);
  }
}
```

Choose a page cap appropriate for the API timeout and provisioned read
capacity. For very large tables, process and persist pages incrementally.

### Query Table by Key Condition

```typescript
const userOrders = await ctx.integrations.dynamodb.queryTable(
  "orders",
  "userId = :uid",
  { ":uid": { S: "user-123" } },
  z.array(z.object({ orderId: z.string(), total: z.number() })),
);
```

### Batch Write Items

```typescript
await ctx.integrations.dynamodb.batchWriteItem({
  users: [
    { PutRequest: { Item: { id: { S: "user-1" }, name: { S: "Alice" } } } },
    { PutRequest: { Item: { id: { S: "user-2" }, name: { S: "Bob" } } } },
    { DeleteRequest: { Key: { id: { S: "user-3" } } } },
  ],
});
```

### List Tables

```typescript
const tables = await ctx.integrations.dynamodb.listTables(
  z.object({ TableNames: z.array(z.string()) }),
);
```

### Describe Table

```typescript
const description = await ctx.integrations.dynamodb.describeTable(
  "users",
  z.object({
    Table: z.object({
      TableName: z.string(),
      KeySchema: z.array(
        z.object({
          AttributeName: z.string(),
          KeyType: z.string(),
        }),
      ),
      ItemCount: z.number(),
    }),
  }),
);
```

### Delete Table

```typescript
await ctx.integrations.dynamodb.deleteTable("temp-table");
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### Use Parameterized PartiQL Queries

Always use `?` placeholders with typed parameters:

```typescript
// CORRECT - parameterized with AttributeValue types
await ctx.integrations.dynamodb.query(
  "SELECT * FROM users WHERE id = ?",
  schema,
  [{ S: userId }],
);

// AVOID - string interpolation (potential injection)
await ctx.integrations.dynamodb.query(
  `SELECT * FROM users WHERE id = '${userId}'`,
  schema,
);
```

### Numbers Are Always Strings in DynamoDB

DynamoDB represents numbers as strings in the wire format:

```typescript
// CORRECT
{
  age: {
    N: "30";
  }
}

// WRONG - N must be a string
{
  age: {
    N: 30;
  }
}
```

## Error Handling

```typescript
import {
  RestApiValidationError,
  IntegrationError,
} from "@superblocksteam/sdk-api";

try {
  const users = await ctx.integrations.dynamodb.query(
    "SELECT * FROM users WHERE status = ?",
    UserSchema,
    [{ S: "active" }],
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  } else if (error instanceof IntegrationError) {
    console.error("DynamoDB error:", error.message);
  }
}
```

## API Reference

- [PartiQL for DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html)
- [DynamoDB Developer Guide](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/)
- [AttributeValue Reference](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html)
