mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774e5d585c | ||
|
|
ede41af674 | ||
|
|
cb0c55c6f6 |
File diff suppressed because one or more lines are too long
@@ -16,6 +16,7 @@ import {
|
||||
ConfluenceIcon,
|
||||
DiscordIcon,
|
||||
DocumentIcon,
|
||||
DynamoDBIcon,
|
||||
ElevenLabsIcon,
|
||||
ExaAIIcon,
|
||||
EyeIcon,
|
||||
@@ -64,6 +65,7 @@ import {
|
||||
PosthogIcon,
|
||||
PylonIcon,
|
||||
QdrantIcon,
|
||||
RDSIcon,
|
||||
RedditIcon,
|
||||
ResendIcon,
|
||||
S3Icon,
|
||||
@@ -134,6 +136,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
s3: S3Icon,
|
||||
resend: ResendIcon,
|
||||
reddit: RedditIcon,
|
||||
rds: RDSIcon,
|
||||
qdrant: QdrantIcon,
|
||||
pylon: PylonIcon,
|
||||
posthog: PosthogIcon,
|
||||
@@ -182,6 +185,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
file: DocumentIcon,
|
||||
exa: ExaAIIcon,
|
||||
elevenlabs: ElevenLabsIcon,
|
||||
dynamodb: DynamoDBIcon,
|
||||
discord: DiscordIcon,
|
||||
confluence: ConfluenceIcon,
|
||||
clay: ClayIcon,
|
||||
|
||||
193
apps/docs/content/docs/en/tools/dynamodb.mdx
Normal file
193
apps/docs/content/docs/en/tools/dynamodb.mdx
Normal file
@@ -0,0 +1,193 @@
|
||||
---
|
||||
title: Amazon DynamoDB
|
||||
description: Connect to Amazon DynamoDB
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="dynamodb"
|
||||
color="linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Amazon DynamoDB](https://aws.amazon.com/dynamodb/) is a fully managed NoSQL database service offered by AWS that provides fast and predictable performance with seamless scalability. DynamoDB lets you store and retrieve any amount of data and serves any level of request traffic, without the need for you to manage hardware or infrastructure.
|
||||
|
||||
With DynamoDB, you can:
|
||||
|
||||
- **Get items**: Look up items in your tables using primary keys
|
||||
- **Put items**: Add or replace items in your tables
|
||||
- **Query items**: Retrieve multiple items using queries across indexes
|
||||
- **Scan tables**: Read all or part of the data in a table
|
||||
- **Update items**: Modify specific attributes of existing items
|
||||
- **Delete items**: Remove records from your tables
|
||||
|
||||
In Sim, the DynamoDB integration enables your agents to securely access and manipulate DynamoDB tables using AWS credentials. Supported operations include:
|
||||
|
||||
- **Get**: Retrieve an item by its key
|
||||
- **Put**: Insert or overwrite items
|
||||
- **Query**: Run queries using key conditions and filters
|
||||
- **Scan**: Read multiple items by scanning the table or index
|
||||
- **Update**: Change specific attributes of one or more items
|
||||
- **Delete**: Remove an item from a table
|
||||
|
||||
This integration empowers Sim agents to automate data management tasks within your DynamoDB tables programmatically, so you can build workflows that manage, modify, and retrieve scalable NoSQL data without manual effort or server management.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Amazon DynamoDB into workflows. Supports Get, Put, Query, Scan, Update, and Delete operations on DynamoDB tables.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `dynamodb_get`
|
||||
|
||||
Get an item from a DynamoDB table by primary key
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `tableName` | string | Yes | DynamoDB table name |
|
||||
| `key` | object | Yes | Primary key of the item to retrieve |
|
||||
| `consistentRead` | boolean | No | Use strongly consistent read |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `item` | object | Retrieved item |
|
||||
|
||||
### `dynamodb_put`
|
||||
|
||||
Put an item into a DynamoDB table
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `tableName` | string | Yes | DynamoDB table name |
|
||||
| `item` | object | Yes | Item to put into the table |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `item` | object | Created item |
|
||||
|
||||
### `dynamodb_query`
|
||||
|
||||
Query items from a DynamoDB table using key conditions
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `tableName` | string | Yes | DynamoDB table name |
|
||||
| `keyConditionExpression` | string | Yes | Key condition expression \(e.g., "pk = :pk"\) |
|
||||
| `filterExpression` | string | No | Filter expression for results |
|
||||
| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words |
|
||||
| `expressionAttributeValues` | object | No | Expression attribute values |
|
||||
| `indexName` | string | No | Secondary index name to query |
|
||||
| `limit` | number | No | Maximum number of items to return |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `items` | array | Array of items returned |
|
||||
| `count` | number | Number of items returned |
|
||||
|
||||
### `dynamodb_scan`
|
||||
|
||||
Scan all items in a DynamoDB table
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `tableName` | string | Yes | DynamoDB table name |
|
||||
| `filterExpression` | string | No | Filter expression for results |
|
||||
| `projectionExpression` | string | No | Attributes to retrieve |
|
||||
| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words |
|
||||
| `expressionAttributeValues` | object | No | Expression attribute values |
|
||||
| `limit` | number | No | Maximum number of items to return |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `items` | array | Array of items returned |
|
||||
| `count` | number | Number of items returned |
|
||||
|
||||
### `dynamodb_update`
|
||||
|
||||
Update an item in a DynamoDB table
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `tableName` | string | Yes | DynamoDB table name |
|
||||
| `key` | object | Yes | Primary key of the item to update |
|
||||
| `updateExpression` | string | Yes | Update expression \(e.g., "SET #name = :name"\) |
|
||||
| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words |
|
||||
| `expressionAttributeValues` | object | No | Expression attribute values |
|
||||
| `conditionExpression` | string | No | Condition that must be met for the update to succeed |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `item` | object | Updated item |
|
||||
|
||||
### `dynamodb_delete`
|
||||
|
||||
Delete an item from a DynamoDB table
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `tableName` | string | Yes | DynamoDB table name |
|
||||
| `key` | object | Yes | Primary key of the item to delete |
|
||||
| `conditionExpression` | string | No | Condition that must be met for the delete to succeed |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `dynamodb`
|
||||
@@ -11,6 +11,7 @@
|
||||
"clay",
|
||||
"confluence",
|
||||
"discord",
|
||||
"dynamodb",
|
||||
"elevenlabs",
|
||||
"exa",
|
||||
"file",
|
||||
@@ -59,6 +60,7 @@
|
||||
"posthog",
|
||||
"pylon",
|
||||
"qdrant",
|
||||
"rds",
|
||||
"reddit",
|
||||
"resend",
|
||||
"s3",
|
||||
|
||||
173
apps/docs/content/docs/en/tools/rds.mdx
Normal file
173
apps/docs/content/docs/en/tools/rds.mdx
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
title: Amazon RDS
|
||||
description: Connect to Amazon RDS via Data API
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="rds"
|
||||
color="linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Amazon RDS Aurora Serverless](https://aws.amazon.com/rds/aurora/serverless/) is a fully managed relational database that automatically starts up, shuts down, and scales capacity based on your application's needs. It allows you to run SQL databases in the cloud without managing database servers.
|
||||
|
||||
With RDS Aurora Serverless, you can:
|
||||
|
||||
- **Query data**: Run flexible SQL queries across your tables
|
||||
- **Insert new records**: Add data to your database automatically
|
||||
- **Update existing records**: Modify data in your tables using custom filters
|
||||
- **Delete records**: Remove unwanted data using precise criteria
|
||||
- **Execute raw SQL**: Run any valid SQL command supported by Aurora
|
||||
|
||||
In Sim, the RDS integration enables your agents to work with Amazon Aurora Serverless databases securely and programmatically. Supported operations include:
|
||||
|
||||
- **Query**: Run SELECT and other SQL queries to fetch rows from your database
|
||||
- **Insert**: Insert new records into tables with structured data
|
||||
- **Update**: Change data in rows that match your specified conditions
|
||||
- **Delete**: Remove records from a table by custom filters or criteria
|
||||
- **Execute**: Run raw SQL for advanced scenarios
|
||||
|
||||
This integration allows your agents to automate a wide range of database operations without manual intervention. By connecting Sim with Amazon RDS, you can build agents that manage, update, and retrieve relational data within your workflows—all without handling database infrastructure or connections.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Amazon RDS Aurora Serverless into the workflow using the Data API. Can query, insert, update, delete, and execute raw SQL without managing database connections.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `rds_query`
|
||||
|
||||
Execute a SELECT query on Amazon RDS using the Data API
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `resourceArn` | string | Yes | ARN of the Aurora DB cluster |
|
||||
| `secretArn` | string | Yes | ARN of the Secrets Manager secret containing DB credentials |
|
||||
| `database` | string | No | Database name \(optional\) |
|
||||
| `query` | string | Yes | SQL SELECT query to execute |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `rows` | array | Array of rows returned from the query |
|
||||
| `rowCount` | number | Number of rows returned |
|
||||
|
||||
### `rds_insert`
|
||||
|
||||
Insert data into an Amazon RDS table using the Data API
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `resourceArn` | string | Yes | ARN of the Aurora DB cluster |
|
||||
| `secretArn` | string | Yes | ARN of the Secrets Manager secret containing DB credentials |
|
||||
| `database` | string | No | Database name \(optional\) |
|
||||
| `table` | string | Yes | Table name to insert into |
|
||||
| `data` | object | Yes | Data to insert as key-value pairs |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `rows` | array | Array of inserted rows |
|
||||
| `rowCount` | number | Number of rows inserted |
|
||||
|
||||
### `rds_update`
|
||||
|
||||
Update data in an Amazon RDS table using the Data API
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `resourceArn` | string | Yes | ARN of the Aurora DB cluster |
|
||||
| `secretArn` | string | Yes | ARN of the Secrets Manager secret containing DB credentials |
|
||||
| `database` | string | No | Database name \(optional\) |
|
||||
| `table` | string | Yes | Table name to update |
|
||||
| `data` | object | Yes | Data to update as key-value pairs |
|
||||
| `conditions` | object | Yes | Conditions for the update \(e.g., \{"id": 1\}\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `rows` | array | Array of updated rows |
|
||||
| `rowCount` | number | Number of rows updated |
|
||||
|
||||
### `rds_delete`
|
||||
|
||||
Delete data from an Amazon RDS table using the Data API
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `resourceArn` | string | Yes | ARN of the Aurora DB cluster |
|
||||
| `secretArn` | string | Yes | ARN of the Secrets Manager secret containing DB credentials |
|
||||
| `database` | string | No | Database name \(optional\) |
|
||||
| `table` | string | Yes | Table name to delete from |
|
||||
| `conditions` | object | Yes | Conditions for the delete \(e.g., \{"id": 1\}\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `rows` | array | Array of deleted rows |
|
||||
| `rowCount` | number | Number of rows deleted |
|
||||
|
||||
### `rds_execute`
|
||||
|
||||
Execute raw SQL on Amazon RDS using the Data API
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `region` | string | Yes | AWS region \(e.g., us-east-1\) |
|
||||
| `accessKeyId` | string | Yes | AWS access key ID |
|
||||
| `secretAccessKey` | string | Yes | AWS secret access key |
|
||||
| `resourceArn` | string | Yes | ARN of the Aurora DB cluster |
|
||||
| `secretArn` | string | Yes | ARN of the Secrets Manager secret containing DB credentials |
|
||||
| `database` | string | No | Database name \(optional\) |
|
||||
| `query` | string | Yes | Raw SQL query to execute |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | string | Operation status message |
|
||||
| `rows` | array | Array of rows returned or affected |
|
||||
| `rowCount` | number | Number of rows affected |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `rds`
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="s3"
|
||||
color="#E0E0E0"
|
||||
color="linear-gradient(45deg, #1B660F 0%, #6CAE3E 100%)"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import type { SVGProps } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { StatusDotIcon } from '@/components/icons'
|
||||
import type { StatusType } from '@/app/api/status/types'
|
||||
import { useStatus } from '@/hooks/queries/status'
|
||||
|
||||
interface StatusDotIconProps extends SVGProps<SVGSVGElement> {
|
||||
status: 'operational' | 'degraded' | 'outage' | 'maintenance' | 'loading' | 'error'
|
||||
}
|
||||
|
||||
export function StatusDotIcon({ status, className, ...props }: StatusDotIconProps) {
|
||||
const colors = {
|
||||
operational: '#10B981',
|
||||
degraded: '#F59E0B',
|
||||
outage: '#EF4444',
|
||||
maintenance: '#3B82F6',
|
||||
loading: '#9CA3AF',
|
||||
error: '#9CA3AF',
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width={6}
|
||||
height={6}
|
||||
viewBox='0 0 6 6'
|
||||
fill='none'
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
<circle cx={3} cy={3} r={3} fill={colors[status]} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<StatusType, string> = {
|
||||
operational: 'text-[#10B981] hover:text-[#059669]',
|
||||
degraded: 'text-[#F59E0B] hover:text-[#D97706]',
|
||||
|
||||
41
apps/sim/app/api/tools/dynamodb/delete/route.ts
Normal file
41
apps/sim/app/api/tools/dynamodb/delete/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createDynamoDBClient, deleteItem } from '@/app/api/tools/dynamodb/utils'
|
||||
|
||||
const DeleteSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
key: z.record(z.unknown()).refine((val) => Object.keys(val).length > 0, {
|
||||
message: 'Key is required',
|
||||
}),
|
||||
conditionExpression: z.string().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = DeleteSchema.parse(body)
|
||||
|
||||
const client = createDynamoDBClient({
|
||||
region: validatedData.region,
|
||||
accessKeyId: validatedData.accessKeyId,
|
||||
secretAccessKey: validatedData.secretAccessKey,
|
||||
})
|
||||
|
||||
await deleteItem(
|
||||
client,
|
||||
validatedData.tableName,
|
||||
validatedData.key,
|
||||
validatedData.conditionExpression
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: 'Item deleted successfully',
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'DynamoDB delete failed'
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 })
|
||||
}
|
||||
}
|
||||
48
apps/sim/app/api/tools/dynamodb/get/route.ts
Normal file
48
apps/sim/app/api/tools/dynamodb/get/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createDynamoDBClient, getItem } from '@/app/api/tools/dynamodb/utils'
|
||||
|
||||
const GetSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
key: z.record(z.unknown()).refine((val) => Object.keys(val).length > 0, {
|
||||
message: 'Key is required',
|
||||
}),
|
||||
consistentRead: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.optional()
|
||||
.transform((val) => {
|
||||
if (val === true || val === 'true') return true
|
||||
return undefined
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = GetSchema.parse(body)
|
||||
|
||||
const client = createDynamoDBClient({
|
||||
region: validatedData.region,
|
||||
accessKeyId: validatedData.accessKeyId,
|
||||
secretAccessKey: validatedData.secretAccessKey,
|
||||
})
|
||||
|
||||
const result = await getItem(
|
||||
client,
|
||||
validatedData.tableName,
|
||||
validatedData.key,
|
||||
validatedData.consistentRead
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: result.item ? 'Item retrieved successfully' : 'Item not found',
|
||||
item: result.item,
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'DynamoDB get failed'
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 })
|
||||
}
|
||||
}
|
||||
36
apps/sim/app/api/tools/dynamodb/put/route.ts
Normal file
36
apps/sim/app/api/tools/dynamodb/put/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createDynamoDBClient, putItem } from '@/app/api/tools/dynamodb/utils'
|
||||
|
||||
const PutSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
item: z.record(z.unknown()).refine((val) => Object.keys(val).length > 0, {
|
||||
message: 'Item is required',
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = PutSchema.parse(body)
|
||||
|
||||
const client = createDynamoDBClient({
|
||||
region: validatedData.region,
|
||||
accessKeyId: validatedData.accessKeyId,
|
||||
secretAccessKey: validatedData.secretAccessKey,
|
||||
})
|
||||
|
||||
await putItem(client, validatedData.tableName, validatedData.item)
|
||||
|
||||
return NextResponse.json({
|
||||
message: 'Item created successfully',
|
||||
item: validatedData.item,
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'DynamoDB put failed'
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 })
|
||||
}
|
||||
}
|
||||
51
apps/sim/app/api/tools/dynamodb/query/route.ts
Normal file
51
apps/sim/app/api/tools/dynamodb/query/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createDynamoDBClient, queryItems } from '@/app/api/tools/dynamodb/utils'
|
||||
|
||||
const QuerySchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
keyConditionExpression: z.string().min(1, 'Key condition expression is required'),
|
||||
filterExpression: z.string().optional(),
|
||||
expressionAttributeNames: z.record(z.string()).optional(),
|
||||
expressionAttributeValues: z.record(z.unknown()).optional(),
|
||||
indexName: z.string().optional(),
|
||||
limit: z.number().positive().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = QuerySchema.parse(body)
|
||||
|
||||
const client = createDynamoDBClient({
|
||||
region: validatedData.region,
|
||||
accessKeyId: validatedData.accessKeyId,
|
||||
secretAccessKey: validatedData.secretAccessKey,
|
||||
})
|
||||
|
||||
const result = await queryItems(
|
||||
client,
|
||||
validatedData.tableName,
|
||||
validatedData.keyConditionExpression,
|
||||
{
|
||||
filterExpression: validatedData.filterExpression,
|
||||
expressionAttributeNames: validatedData.expressionAttributeNames,
|
||||
expressionAttributeValues: validatedData.expressionAttributeValues,
|
||||
indexName: validatedData.indexName,
|
||||
limit: validatedData.limit,
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Query returned ${result.count} items`,
|
||||
items: result.items,
|
||||
count: result.count,
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'DynamoDB query failed'
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 })
|
||||
}
|
||||
}
|
||||
45
apps/sim/app/api/tools/dynamodb/scan/route.ts
Normal file
45
apps/sim/app/api/tools/dynamodb/scan/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createDynamoDBClient, scanItems } from '@/app/api/tools/dynamodb/utils'
|
||||
|
||||
const ScanSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
filterExpression: z.string().optional(),
|
||||
projectionExpression: z.string().optional(),
|
||||
expressionAttributeNames: z.record(z.string()).optional(),
|
||||
expressionAttributeValues: z.record(z.unknown()).optional(),
|
||||
limit: z.number().positive().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = ScanSchema.parse(body)
|
||||
|
||||
const client = createDynamoDBClient({
|
||||
region: validatedData.region,
|
||||
accessKeyId: validatedData.accessKeyId,
|
||||
secretAccessKey: validatedData.secretAccessKey,
|
||||
})
|
||||
|
||||
const result = await scanItems(client, validatedData.tableName, {
|
||||
filterExpression: validatedData.filterExpression,
|
||||
projectionExpression: validatedData.projectionExpression,
|
||||
expressionAttributeNames: validatedData.expressionAttributeNames,
|
||||
expressionAttributeValues: validatedData.expressionAttributeValues,
|
||||
limit: validatedData.limit,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Scan returned ${result.count} items`,
|
||||
items: result.items,
|
||||
count: result.count,
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'DynamoDB scan failed'
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 })
|
||||
}
|
||||
}
|
||||
50
apps/sim/app/api/tools/dynamodb/update/route.ts
Normal file
50
apps/sim/app/api/tools/dynamodb/update/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createDynamoDBClient, updateItem } from '@/app/api/tools/dynamodb/utils'
|
||||
|
||||
const UpdateSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
key: z.record(z.unknown()).refine((val) => Object.keys(val).length > 0, {
|
||||
message: 'Key is required',
|
||||
}),
|
||||
updateExpression: z.string().min(1, 'Update expression is required'),
|
||||
expressionAttributeNames: z.record(z.string()).optional(),
|
||||
expressionAttributeValues: z.record(z.unknown()).optional(),
|
||||
conditionExpression: z.string().optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const validatedData = UpdateSchema.parse(body)
|
||||
|
||||
const client = createDynamoDBClient({
|
||||
region: validatedData.region,
|
||||
accessKeyId: validatedData.accessKeyId,
|
||||
secretAccessKey: validatedData.secretAccessKey,
|
||||
})
|
||||
|
||||
const result = await updateItem(
|
||||
client,
|
||||
validatedData.tableName,
|
||||
validatedData.key,
|
||||
validatedData.updateExpression,
|
||||
{
|
||||
expressionAttributeNames: validatedData.expressionAttributeNames,
|
||||
expressionAttributeValues: validatedData.expressionAttributeValues,
|
||||
conditionExpression: validatedData.conditionExpression,
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
message: 'Item updated successfully',
|
||||
item: result.attributes,
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'DynamoDB update failed'
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 })
|
||||
}
|
||||
}
|
||||
174
apps/sim/app/api/tools/dynamodb/utils.ts
Normal file
174
apps/sim/app/api/tools/dynamodb/utils.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
|
||||
import {
|
||||
DeleteCommand,
|
||||
DynamoDBDocumentClient,
|
||||
GetCommand,
|
||||
PutCommand,
|
||||
QueryCommand,
|
||||
ScanCommand,
|
||||
UpdateCommand,
|
||||
} from '@aws-sdk/lib-dynamodb'
|
||||
import type { DynamoDBConnectionConfig } from '@/tools/dynamodb/types'
|
||||
|
||||
export function createDynamoDBClient(config: DynamoDBConnectionConfig): DynamoDBDocumentClient {
|
||||
const client = new DynamoDBClient({
|
||||
region: config.region,
|
||||
credentials: {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
})
|
||||
|
||||
return DynamoDBDocumentClient.from(client, {
|
||||
marshallOptions: {
|
||||
removeUndefinedValues: true,
|
||||
convertEmptyValues: false,
|
||||
},
|
||||
unmarshallOptions: {
|
||||
wrapNumbers: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function getItem(
|
||||
client: DynamoDBDocumentClient,
|
||||
tableName: string,
|
||||
key: Record<string, unknown>,
|
||||
consistentRead?: boolean
|
||||
): Promise<{ item: Record<string, unknown> | null }> {
|
||||
const command = new GetCommand({
|
||||
TableName: tableName,
|
||||
Key: key,
|
||||
ConsistentRead: consistentRead,
|
||||
})
|
||||
|
||||
const response = await client.send(command)
|
||||
return {
|
||||
item: (response.Item as Record<string, unknown>) || null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function putItem(
|
||||
client: DynamoDBDocumentClient,
|
||||
tableName: string,
|
||||
item: Record<string, unknown>
|
||||
): Promise<{ success: boolean }> {
|
||||
const command = new PutCommand({
|
||||
TableName: tableName,
|
||||
Item: item,
|
||||
})
|
||||
|
||||
await client.send(command)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function queryItems(
|
||||
client: DynamoDBDocumentClient,
|
||||
tableName: string,
|
||||
keyConditionExpression: string,
|
||||
options?: {
|
||||
filterExpression?: string
|
||||
expressionAttributeNames?: Record<string, string>
|
||||
expressionAttributeValues?: Record<string, unknown>
|
||||
indexName?: string
|
||||
limit?: number
|
||||
}
|
||||
): Promise<{ items: Record<string, unknown>[]; count: number }> {
|
||||
const command = new QueryCommand({
|
||||
TableName: tableName,
|
||||
KeyConditionExpression: keyConditionExpression,
|
||||
...(options?.filterExpression && { FilterExpression: options.filterExpression }),
|
||||
...(options?.expressionAttributeNames && {
|
||||
ExpressionAttributeNames: options.expressionAttributeNames,
|
||||
}),
|
||||
...(options?.expressionAttributeValues && {
|
||||
ExpressionAttributeValues: options.expressionAttributeValues,
|
||||
}),
|
||||
...(options?.indexName && { IndexName: options.indexName }),
|
||||
...(options?.limit && { Limit: options.limit }),
|
||||
})
|
||||
|
||||
const response = await client.send(command)
|
||||
return {
|
||||
items: (response.Items as Record<string, unknown>[]) || [],
|
||||
count: response.Count || 0,
|
||||
}
|
||||
}
|
||||
|
||||
export async function scanItems(
|
||||
client: DynamoDBDocumentClient,
|
||||
tableName: string,
|
||||
options?: {
|
||||
filterExpression?: string
|
||||
projectionExpression?: string
|
||||
expressionAttributeNames?: Record<string, string>
|
||||
expressionAttributeValues?: Record<string, unknown>
|
||||
limit?: number
|
||||
}
|
||||
): Promise<{ items: Record<string, unknown>[]; count: number }> {
|
||||
const command = new ScanCommand({
|
||||
TableName: tableName,
|
||||
...(options?.filterExpression && { FilterExpression: options.filterExpression }),
|
||||
...(options?.projectionExpression && { ProjectionExpression: options.projectionExpression }),
|
||||
...(options?.expressionAttributeNames && {
|
||||
ExpressionAttributeNames: options.expressionAttributeNames,
|
||||
}),
|
||||
...(options?.expressionAttributeValues && {
|
||||
ExpressionAttributeValues: options.expressionAttributeValues,
|
||||
}),
|
||||
...(options?.limit && { Limit: options.limit }),
|
||||
})
|
||||
|
||||
const response = await client.send(command)
|
||||
return {
|
||||
items: (response.Items as Record<string, unknown>[]) || [],
|
||||
count: response.Count || 0,
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateItem(
|
||||
client: DynamoDBDocumentClient,
|
||||
tableName: string,
|
||||
key: Record<string, unknown>,
|
||||
updateExpression: string,
|
||||
options?: {
|
||||
expressionAttributeNames?: Record<string, string>
|
||||
expressionAttributeValues?: Record<string, unknown>
|
||||
conditionExpression?: string
|
||||
}
|
||||
): Promise<{ attributes: Record<string, unknown> | null }> {
|
||||
const command = new UpdateCommand({
|
||||
TableName: tableName,
|
||||
Key: key,
|
||||
UpdateExpression: updateExpression,
|
||||
...(options?.expressionAttributeNames && {
|
||||
ExpressionAttributeNames: options.expressionAttributeNames,
|
||||
}),
|
||||
...(options?.expressionAttributeValues && {
|
||||
ExpressionAttributeValues: options.expressionAttributeValues,
|
||||
}),
|
||||
...(options?.conditionExpression && { ConditionExpression: options.conditionExpression }),
|
||||
ReturnValues: 'ALL_NEW',
|
||||
})
|
||||
|
||||
const response = await client.send(command)
|
||||
return {
|
||||
attributes: (response.Attributes as Record<string, unknown>) || null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteItem(
|
||||
client: DynamoDBDocumentClient,
|
||||
tableName: string,
|
||||
key: Record<string, unknown>,
|
||||
conditionExpression?: string
|
||||
): Promise<{ success: boolean }> {
|
||||
const command = new DeleteCommand({
|
||||
TableName: tableName,
|
||||
Key: key,
|
||||
...(conditionExpression && { ConditionExpression: conditionExpression }),
|
||||
})
|
||||
|
||||
await client.send(command)
|
||||
return { success: true }
|
||||
}
|
||||
74
apps/sim/app/api/tools/rds/delete/route.ts
Normal file
74
apps/sim/app/api/tools/rds/delete/route.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createRdsClient, executeDelete } from '@/app/api/tools/rds/utils'
|
||||
|
||||
const logger = createLogger('RDSDeleteAPI')
|
||||
|
||||
const DeleteSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
resourceArn: z.string().min(1, 'Resource ARN is required'),
|
||||
secretArn: z.string().min(1, 'Secret ARN is required'),
|
||||
database: z.string().optional(),
|
||||
table: z.string().min(1, 'Table name is required'),
|
||||
conditions: z.record(z.unknown()).refine((obj) => Object.keys(obj).length > 0, {
|
||||
message: 'At least one condition is required',
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = DeleteSchema.parse(body)
|
||||
|
||||
logger.info(`[${requestId}] Deleting from RDS table ${params.table} in ${params.database}`)
|
||||
|
||||
const client = createRdsClient({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
database: params.database,
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await executeDelete(
|
||||
client,
|
||||
params.resourceArn,
|
||||
params.secretArn,
|
||||
params.database,
|
||||
params.table,
|
||||
params.conditions
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Delete executed successfully, affected ${result.rowCount} rows`)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Delete executed successfully. ${result.rowCount} row(s) deleted.`,
|
||||
rows: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
})
|
||||
} finally {
|
||||
client.destroy()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] RDS delete failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `RDS delete failed: ${errorMessage}` }, { status: 500 })
|
||||
}
|
||||
}
|
||||
70
apps/sim/app/api/tools/rds/execute/route.ts
Normal file
70
apps/sim/app/api/tools/rds/execute/route.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createRdsClient, executeStatement } from '@/app/api/tools/rds/utils'
|
||||
|
||||
const logger = createLogger('RDSExecuteAPI')
|
||||
|
||||
const ExecuteSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
resourceArn: z.string().min(1, 'Resource ARN is required'),
|
||||
secretArn: z.string().min(1, 'Secret ARN is required'),
|
||||
database: z.string().optional(),
|
||||
query: z.string().min(1, 'Query is required'),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = ExecuteSchema.parse(body)
|
||||
|
||||
logger.info(`[${requestId}] Executing raw SQL on RDS database ${params.database}`)
|
||||
|
||||
const client = createRdsClient({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
database: params.database,
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await executeStatement(
|
||||
client,
|
||||
params.resourceArn,
|
||||
params.secretArn,
|
||||
params.database,
|
||||
params.query
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Execute completed successfully, affected ${result.rowCount} rows`)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Query executed successfully. ${result.rowCount} row(s) affected.`,
|
||||
rows: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
})
|
||||
} finally {
|
||||
client.destroy()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] RDS execute failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `RDS execute failed: ${errorMessage}` }, { status: 500 })
|
||||
}
|
||||
}
|
||||
74
apps/sim/app/api/tools/rds/insert/route.ts
Normal file
74
apps/sim/app/api/tools/rds/insert/route.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createRdsClient, executeInsert } from '@/app/api/tools/rds/utils'
|
||||
|
||||
const logger = createLogger('RDSInsertAPI')
|
||||
|
||||
const InsertSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
resourceArn: z.string().min(1, 'Resource ARN is required'),
|
||||
secretArn: z.string().min(1, 'Secret ARN is required'),
|
||||
database: z.string().optional(),
|
||||
table: z.string().min(1, 'Table name is required'),
|
||||
data: z.record(z.unknown()).refine((obj) => Object.keys(obj).length > 0, {
|
||||
message: 'Data object must have at least one field',
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = InsertSchema.parse(body)
|
||||
|
||||
logger.info(`[${requestId}] Inserting into RDS table ${params.table} in ${params.database}`)
|
||||
|
||||
const client = createRdsClient({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
database: params.database,
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await executeInsert(
|
||||
client,
|
||||
params.resourceArn,
|
||||
params.secretArn,
|
||||
params.database,
|
||||
params.table,
|
||||
params.data
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Insert executed successfully, affected ${result.rowCount} rows`)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Insert executed successfully. ${result.rowCount} row(s) inserted.`,
|
||||
rows: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
})
|
||||
} finally {
|
||||
client.destroy()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] RDS insert failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `RDS insert failed: ${errorMessage}` }, { status: 500 })
|
||||
}
|
||||
}
|
||||
77
apps/sim/app/api/tools/rds/query/route.ts
Normal file
77
apps/sim/app/api/tools/rds/query/route.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createRdsClient, executeStatement, validateQuery } from '@/app/api/tools/rds/utils'
|
||||
|
||||
const logger = createLogger('RDSQueryAPI')
|
||||
|
||||
const QuerySchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
resourceArn: z.string().min(1, 'Resource ARN is required'),
|
||||
secretArn: z.string().min(1, 'Secret ARN is required'),
|
||||
database: z.string().optional(),
|
||||
query: z.string().min(1, 'Query is required'),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = QuerySchema.parse(body)
|
||||
|
||||
logger.info(`[${requestId}] Executing RDS query on ${params.database}`)
|
||||
|
||||
// Validate the query
|
||||
const validation = validateQuery(params.query)
|
||||
if (!validation.isValid) {
|
||||
logger.warn(`[${requestId}] Query validation failed: ${validation.error}`)
|
||||
return NextResponse.json({ error: validation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const client = createRdsClient({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
database: params.database,
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await executeStatement(
|
||||
client,
|
||||
params.resourceArn,
|
||||
params.secretArn,
|
||||
params.database,
|
||||
params.query
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Query executed successfully, returned ${result.rowCount} rows`)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Query executed successfully. ${result.rowCount} row(s) returned.`,
|
||||
rows: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
})
|
||||
} finally {
|
||||
client.destroy()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] RDS query failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `RDS query failed: ${errorMessage}` }, { status: 500 })
|
||||
}
|
||||
}
|
||||
78
apps/sim/app/api/tools/rds/update/route.ts
Normal file
78
apps/sim/app/api/tools/rds/update/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createRdsClient, executeUpdate } from '@/app/api/tools/rds/utils'
|
||||
|
||||
const logger = createLogger('RDSUpdateAPI')
|
||||
|
||||
const UpdateSchema = z.object({
|
||||
region: z.string().min(1, 'AWS region is required'),
|
||||
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
|
||||
resourceArn: z.string().min(1, 'Resource ARN is required'),
|
||||
secretArn: z.string().min(1, 'Secret ARN is required'),
|
||||
database: z.string().optional(),
|
||||
table: z.string().min(1, 'Table name is required'),
|
||||
data: z.record(z.unknown()).refine((obj) => Object.keys(obj).length > 0, {
|
||||
message: 'Data object must have at least one field',
|
||||
}),
|
||||
conditions: z.record(z.unknown()).refine((obj) => Object.keys(obj).length > 0, {
|
||||
message: 'At least one condition is required',
|
||||
}),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const params = UpdateSchema.parse(body)
|
||||
|
||||
logger.info(`[${requestId}] Updating RDS table ${params.table} in ${params.database}`)
|
||||
|
||||
const client = createRdsClient({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
database: params.database,
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await executeUpdate(
|
||||
client,
|
||||
params.resourceArn,
|
||||
params.secretArn,
|
||||
params.database,
|
||||
params.table,
|
||||
params.data,
|
||||
params.conditions
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Update executed successfully, affected ${result.rowCount} rows`)
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Update executed successfully. ${result.rowCount} row(s) updated.`,
|
||||
rows: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
})
|
||||
} finally {
|
||||
client.destroy()
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request data', details: error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
logger.error(`[${requestId}] RDS update failed:`, error)
|
||||
|
||||
return NextResponse.json({ error: `RDS update failed: ${errorMessage}` }, { status: 500 })
|
||||
}
|
||||
}
|
||||
266
apps/sim/app/api/tools/rds/utils.ts
Normal file
266
apps/sim/app/api/tools/rds/utils.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import {
|
||||
ExecuteStatementCommand,
|
||||
type ExecuteStatementCommandOutput,
|
||||
type Field,
|
||||
RDSDataClient,
|
||||
type SqlParameter,
|
||||
} from '@aws-sdk/client-rds-data'
|
||||
import type { RdsConnectionConfig } from '@/tools/rds/types'
|
||||
|
||||
export function createRdsClient(config: RdsConnectionConfig): RDSDataClient {
|
||||
return new RDSDataClient({
|
||||
region: config.region,
|
||||
credentials: {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function executeStatement(
|
||||
client: RDSDataClient,
|
||||
resourceArn: string,
|
||||
secretArn: string,
|
||||
database: string | undefined,
|
||||
sql: string,
|
||||
parameters?: SqlParameter[]
|
||||
): Promise<{ rows: Record<string, unknown>[]; rowCount: number }> {
|
||||
const command = new ExecuteStatementCommand({
|
||||
resourceArn,
|
||||
secretArn,
|
||||
...(database && { database }),
|
||||
sql,
|
||||
...(parameters && parameters.length > 0 && { parameters }),
|
||||
includeResultMetadata: true,
|
||||
})
|
||||
|
||||
const response = await client.send(command)
|
||||
const rows = parseRdsResponse(response)
|
||||
|
||||
return {
|
||||
rows,
|
||||
rowCount: response.numberOfRecordsUpdated ?? rows.length,
|
||||
}
|
||||
}
|
||||
|
||||
function parseRdsResponse(response: ExecuteStatementCommandOutput): Record<string, unknown>[] {
|
||||
if (!response.records || !response.columnMetadata) {
|
||||
return []
|
||||
}
|
||||
|
||||
const columnNames = response.columnMetadata.map((col) => col.name || col.label || 'unknown')
|
||||
|
||||
return response.records.map((record) => {
|
||||
const row: Record<string, unknown> = {}
|
||||
record.forEach((field, index) => {
|
||||
const columnName = columnNames[index] || `column_${index}`
|
||||
row[columnName] = parseFieldValue(field)
|
||||
})
|
||||
return row
|
||||
})
|
||||
}
|
||||
|
||||
function parseFieldValue(field: Field): unknown {
|
||||
if (field.isNull) return null
|
||||
if (field.stringValue !== undefined) return field.stringValue
|
||||
if (field.longValue !== undefined) return field.longValue
|
||||
if (field.doubleValue !== undefined) return field.doubleValue
|
||||
if (field.booleanValue !== undefined) return field.booleanValue
|
||||
if (field.blobValue !== undefined) return Buffer.from(field.blobValue).toString('base64')
|
||||
if (field.arrayValue !== undefined) {
|
||||
const arr = field.arrayValue
|
||||
if (arr.stringValues) return arr.stringValues
|
||||
if (arr.longValues) return arr.longValues
|
||||
if (arr.doubleValues) return arr.doubleValues
|
||||
if (arr.booleanValues) return arr.booleanValues
|
||||
if (arr.arrayValues) return arr.arrayValues.map((f) => parseFieldValue({ arrayValue: f }))
|
||||
return []
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function validateQuery(query: string): { isValid: boolean; error?: string } {
|
||||
const trimmedQuery = query.trim().toLowerCase()
|
||||
|
||||
const dangerousPatterns = [
|
||||
/drop\s+database/i,
|
||||
/drop\s+schema/i,
|
||||
/drop\s+user/i,
|
||||
/create\s+user/i,
|
||||
/create\s+role/i,
|
||||
/grant\s+/i,
|
||||
/revoke\s+/i,
|
||||
/alter\s+user/i,
|
||||
/alter\s+role/i,
|
||||
/set\s+role/i,
|
||||
/reset\s+role/i,
|
||||
]
|
||||
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(query)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Query contains potentially dangerous operation: ${pattern.source}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allowedStatements = /^(select|insert|update|delete|with|explain|show)\s+/i
|
||||
if (!allowedStatements.test(trimmedQuery)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: 'Only SELECT, INSERT, UPDATE, DELETE, WITH, EXPLAIN, and SHOW statements are allowed',
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true }
|
||||
}
|
||||
|
||||
export function sanitizeIdentifier(identifier: string): string {
|
||||
if (identifier.includes('.')) {
|
||||
const parts = identifier.split('.')
|
||||
return parts.map((part) => sanitizeSingleIdentifier(part)).join('.')
|
||||
}
|
||||
|
||||
return sanitizeSingleIdentifier(identifier)
|
||||
}
|
||||
|
||||
function sanitizeSingleIdentifier(identifier: string): string {
|
||||
const cleaned = identifier.replace(/`/g, '').replace(/"/g, '')
|
||||
|
||||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(cleaned)) {
|
||||
throw new Error(
|
||||
`Invalid identifier: ${identifier}. Identifiers must start with a letter or underscore and contain only letters, numbers, and underscores.`
|
||||
)
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JS value to an RDS Data API SqlParameter value
|
||||
*/
|
||||
function toSqlParameterValue(value: unknown): SqlParameter['value'] {
|
||||
if (value === null || value === undefined) {
|
||||
return { isNull: true }
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return { booleanValue: value }
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
if (Number.isInteger(value)) {
|
||||
return { longValue: value }
|
||||
}
|
||||
return { doubleValue: value }
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return { stringValue: value }
|
||||
}
|
||||
if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
|
||||
return { blobValue: value }
|
||||
}
|
||||
// Objects/arrays as JSON strings
|
||||
return { stringValue: JSON.stringify(value) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Build parameterized INSERT query
|
||||
*/
|
||||
export async function executeInsert(
|
||||
client: RDSDataClient,
|
||||
resourceArn: string,
|
||||
secretArn: string,
|
||||
database: string | undefined,
|
||||
table: string,
|
||||
data: Record<string, unknown>
|
||||
): Promise<{ rows: Record<string, unknown>[]; rowCount: number }> {
|
||||
const sanitizedTable = sanitizeIdentifier(table)
|
||||
const columns = Object.keys(data)
|
||||
const sanitizedColumns = columns.map((col) => sanitizeIdentifier(col))
|
||||
|
||||
const placeholders = columns.map((col) => `:${col}`)
|
||||
const parameters: SqlParameter[] = columns.map((col) => ({
|
||||
name: col,
|
||||
value: toSqlParameterValue(data[col]),
|
||||
}))
|
||||
|
||||
const sql = `INSERT INTO ${sanitizedTable} (${sanitizedColumns.join(', ')}) VALUES (${placeholders.join(', ')})`
|
||||
|
||||
return executeStatement(client, resourceArn, secretArn, database, sql, parameters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build parameterized UPDATE query with conditions
|
||||
*/
|
||||
export async function executeUpdate(
|
||||
client: RDSDataClient,
|
||||
resourceArn: string,
|
||||
secretArn: string,
|
||||
database: string | undefined,
|
||||
table: string,
|
||||
data: Record<string, unknown>,
|
||||
conditions: Record<string, unknown>
|
||||
): Promise<{ rows: Record<string, unknown>[]; rowCount: number }> {
|
||||
const sanitizedTable = sanitizeIdentifier(table)
|
||||
|
||||
// Build SET clause with parameters
|
||||
const dataColumns = Object.keys(data)
|
||||
const setClause = dataColumns.map((col) => `${sanitizeIdentifier(col)} = :set_${col}`).join(', ')
|
||||
|
||||
// Build WHERE clause with parameters
|
||||
const conditionColumns = Object.keys(conditions)
|
||||
if (conditionColumns.length === 0) {
|
||||
throw new Error('At least one condition is required for UPDATE operations')
|
||||
}
|
||||
const whereClause = conditionColumns
|
||||
.map((col) => `${sanitizeIdentifier(col)} = :where_${col}`)
|
||||
.join(' AND ')
|
||||
|
||||
// Build parameters array (prefixed to avoid name collisions)
|
||||
const parameters: SqlParameter[] = [
|
||||
...dataColumns.map((col) => ({
|
||||
name: `set_${col}`,
|
||||
value: toSqlParameterValue(data[col]),
|
||||
})),
|
||||
...conditionColumns.map((col) => ({
|
||||
name: `where_${col}`,
|
||||
value: toSqlParameterValue(conditions[col]),
|
||||
})),
|
||||
]
|
||||
|
||||
const sql = `UPDATE ${sanitizedTable} SET ${setClause} WHERE ${whereClause}`
|
||||
|
||||
return executeStatement(client, resourceArn, secretArn, database, sql, parameters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build parameterized DELETE query with conditions
|
||||
*/
|
||||
export async function executeDelete(
|
||||
client: RDSDataClient,
|
||||
resourceArn: string,
|
||||
secretArn: string,
|
||||
database: string | undefined,
|
||||
table: string,
|
||||
conditions: Record<string, unknown>
|
||||
): Promise<{ rows: Record<string, unknown>[]; rowCount: number }> {
|
||||
const sanitizedTable = sanitizeIdentifier(table)
|
||||
|
||||
// Build WHERE clause with parameters
|
||||
const conditionColumns = Object.keys(conditions)
|
||||
if (conditionColumns.length === 0) {
|
||||
throw new Error('At least one condition is required for DELETE operations')
|
||||
}
|
||||
const whereClause = conditionColumns
|
||||
.map((col) => `${sanitizeIdentifier(col)} = :${col}`)
|
||||
.join(' AND ')
|
||||
|
||||
const parameters: SqlParameter[] = conditionColumns.map((col) => ({
|
||||
name: col,
|
||||
value: toSqlParameterValue(conditions[col]),
|
||||
}))
|
||||
|
||||
const sql = `DELETE FROM ${sanitizedTable} WHERE ${whereClause}`
|
||||
|
||||
return executeStatement(client, resourceArn, secretArn, database, sql, parameters)
|
||||
}
|
||||
@@ -232,7 +232,7 @@ function TemplateCardInner({
|
||||
key={index}
|
||||
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-[4px]'
|
||||
style={{
|
||||
backgroundColor: blockConfig.bgColor || 'gray',
|
||||
background: blockConfig.bgColor || 'gray',
|
||||
marginLeft: index > 0 ? '-4px' : '0',
|
||||
}}
|
||||
>
|
||||
@@ -257,7 +257,7 @@ function TemplateCardInner({
|
||||
key={index}
|
||||
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-[4px]'
|
||||
style={{
|
||||
backgroundColor: blockConfig.bgColor || 'gray',
|
||||
background: blockConfig.bgColor || 'gray',
|
||||
marginLeft: index > 0 ? '-4px' : '0',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -196,7 +196,7 @@ export const NoteBlock = memo(function NoteBlock({ id, data }: NodeProps<NoteBlo
|
||||
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
|
||||
<div
|
||||
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
|
||||
style={{ backgroundColor: isEnabled ? config.bgColor : 'gray' }}
|
||||
style={{ background: isEnabled ? config.bgColor : 'gray' }}
|
||||
>
|
||||
<config.icon className='h-[16px] w-[16px] text-white' />
|
||||
</div>
|
||||
|
||||
@@ -176,7 +176,7 @@ export function MentionMenu({
|
||||
icon: (
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: blk.bgColor || '#6B7280' }}
|
||||
style={{ background: blk.bgColor || '#6B7280' }}
|
||||
>
|
||||
{Icon && <Icon className='!h-[10px] !w-[10px] text-white' />}
|
||||
</div>
|
||||
@@ -198,7 +198,7 @@ export function MentionMenu({
|
||||
icon: (
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: blk.bgColor || '#6B7280' }}
|
||||
style={{ background: blk.bgColor || '#6B7280' }}
|
||||
>
|
||||
{Icon && <Icon className='!h-[10px] !w-[10px] text-white' />}
|
||||
</div>
|
||||
@@ -474,7 +474,7 @@ export function MentionMenu({
|
||||
>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: blk.bgColor || '#6B7280' }}
|
||||
style={{ background: blk.bgColor || '#6B7280' }}
|
||||
>
|
||||
{Icon && <Icon className='!h-[10px] !w-[10px] text-white' />}
|
||||
</div>
|
||||
@@ -503,7 +503,7 @@ export function MentionMenu({
|
||||
>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: blk.bgColor || '#6B7280' }}
|
||||
style={{ background: blk.bgColor || '#6B7280' }}
|
||||
>
|
||||
{Icon && <Icon className='!h-[10px] !w-[10px] text-white' />}
|
||||
</div>
|
||||
@@ -680,7 +680,7 @@ export function MentionMenu({
|
||||
<PopoverItem key={blk.id} onClick={() => insertBlockMention(blk)}>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: blk.bgColor || '#6B7280' }}
|
||||
style={{ background: blk.bgColor || '#6B7280' }}
|
||||
>
|
||||
{Icon && <Icon className='!h-[10px] !w-[10px] text-white' />}
|
||||
</div>
|
||||
@@ -709,7 +709,7 @@ export function MentionMenu({
|
||||
<PopoverItem key={blk.id} onClick={() => insertWorkflowBlockMention(blk)}>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: blk.bgColor || '#6B7280' }}
|
||||
style={{ background: blk.bgColor || '#6B7280' }}
|
||||
>
|
||||
{Icon && <Icon className='!h-[10px] !w-[10px] text-white' />}
|
||||
</div>
|
||||
|
||||
@@ -130,7 +130,7 @@ function ConnectionItem({
|
||||
>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: bgColor }}
|
||||
style={{ background: bgColor }}
|
||||
>
|
||||
{Icon && (
|
||||
<Icon
|
||||
|
||||
@@ -84,7 +84,7 @@ export function McpToolsList({
|
||||
>
|
||||
<div
|
||||
className='flex h-[15px] w-[15px] flex-shrink-0 items-center justify-center rounded'
|
||||
style={{ backgroundColor: mcpTool.bgColor }}
|
||||
style={{ background: mcpTool.bgColor }}
|
||||
>
|
||||
<IconComponent icon={mcpTool.icon} className='h-[11px] w-[11px] text-white' />
|
||||
</div>
|
||||
|
||||
@@ -1728,7 +1728,7 @@ export function ToolInput({
|
||||
>
|
||||
<div
|
||||
className='flex h-[15px] w-[15px] flex-shrink-0 items-center justify-center rounded'
|
||||
style={{ backgroundColor: block.bgColor }}
|
||||
style={{ background: block.bgColor }}
|
||||
>
|
||||
<IconComponent
|
||||
icon={block.icon}
|
||||
@@ -2257,7 +2257,7 @@ export function ToolInput({
|
||||
>
|
||||
<div
|
||||
className='flex h-[15px] w-[15px] flex-shrink-0 items-center justify-center rounded'
|
||||
style={{ backgroundColor: block.bgColor }}
|
||||
style={{ background: block.bgColor }}
|
||||
>
|
||||
<IconComponent
|
||||
icon={block.icon}
|
||||
|
||||
@@ -346,10 +346,11 @@ function SubBlockComponent({
|
||||
| undefined
|
||||
|
||||
// Use dependsOn gating to compute final disabled state
|
||||
// Only pass previewContextValues when in preview mode to avoid format mismatches
|
||||
const { finalDisabled: gatedDisabled } = useDependsOnGate(blockId, config, {
|
||||
disabled,
|
||||
isPreview,
|
||||
previewContextValues: subBlockValues,
|
||||
previewContextValues: isPreview ? subBlockValues : undefined,
|
||||
})
|
||||
|
||||
const isDisabled = gatedDisabled
|
||||
@@ -611,7 +612,7 @@ function SubBlockComponent({
|
||||
disabled={isDisabled}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
previewContextValues={subBlockValues}
|
||||
previewContextValues={isPreview ? subBlockValues : undefined}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ export function Editor() {
|
||||
{(blockConfig || isSubflow) && (
|
||||
<div
|
||||
className='flex h-[18px] w-[18px] items-center justify-center rounded-[4px]'
|
||||
style={{ backgroundColor: isSubflow ? subflowBgColor : blockConfig?.bgColor }}
|
||||
style={{ background: isSubflow ? subflowBgColor : blockConfig?.bgColor }}
|
||||
>
|
||||
<IconComponent
|
||||
icon={isSubflow ? subflowIcon : blockConfig?.icon}
|
||||
@@ -353,7 +353,6 @@ export function Editor() {
|
||||
blockId={currentBlockId}
|
||||
config={subBlock}
|
||||
isPreview={false}
|
||||
subBlockValues={subBlockState}
|
||||
disabled={!userPermissions.canEdit}
|
||||
fieldDiffStatus={undefined}
|
||||
allowExpandInPreview={false}
|
||||
|
||||
@@ -570,7 +570,7 @@ export const Toolbar = forwardRef<ToolbarRef, ToolbarProps>(function Toolbar(
|
||||
>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: trigger.bgColor }}
|
||||
style={{ background: trigger.bgColor }}
|
||||
>
|
||||
{Icon && (
|
||||
<Icon
|
||||
@@ -659,7 +659,7 @@ export const Toolbar = forwardRef<ToolbarRef, ToolbarProps>(function Toolbar(
|
||||
>
|
||||
<div
|
||||
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
|
||||
style={{ backgroundColor: block.bgColor }}
|
||||
style={{ background: block.bgColor }}
|
||||
>
|
||||
{Icon && (
|
||||
<Icon
|
||||
|
||||
@@ -848,7 +848,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
|
||||
<div
|
||||
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
|
||||
style={{
|
||||
backgroundColor: isEnabled ? config.bgColor : 'gray',
|
||||
background: isEnabled ? config.bgColor : 'gray',
|
||||
}}
|
||||
>
|
||||
<config.icon className='h-[16px] w-[16px] text-white' />
|
||||
|
||||
372
apps/sim/blocks/blocks/dynamodb.ts
Normal file
372
apps/sim/blocks/blocks/dynamodb.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { DynamoDBIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { DynamoDBResponse } from '@/tools/dynamodb/types'
|
||||
|
||||
export const DynamoDBBlock: BlockConfig<DynamoDBResponse> = {
|
||||
type: 'dynamodb',
|
||||
name: 'Amazon DynamoDB',
|
||||
description: 'Connect to Amazon DynamoDB',
|
||||
longDescription:
|
||||
'Integrate Amazon DynamoDB into workflows. Supports Get, Put, Query, Scan, Update, and Delete operations on DynamoDB tables.',
|
||||
docsLink: 'https://docs.sim.ai/tools/dynamodb',
|
||||
category: 'tools',
|
||||
bgColor: 'linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)',
|
||||
icon: DynamoDBIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Get Item', id: 'get' },
|
||||
{ label: 'Put Item', id: 'put' },
|
||||
{ label: 'Query', id: 'query' },
|
||||
{ label: 'Scan', id: 'scan' },
|
||||
{ label: 'Update Item', id: 'update' },
|
||||
{ label: 'Delete Item', id: 'delete' },
|
||||
],
|
||||
value: () => 'get',
|
||||
},
|
||||
{
|
||||
id: 'region',
|
||||
title: 'AWS Region',
|
||||
type: 'short-input',
|
||||
placeholder: 'us-east-1',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'accessKeyId',
|
||||
title: 'AWS Access Key ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'AKIA...',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'secretAccessKey',
|
||||
title: 'AWS Secret Access Key',
|
||||
type: 'short-input',
|
||||
placeholder: 'Your secret access key',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'tableName',
|
||||
title: 'Table Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'my-table',
|
||||
required: true,
|
||||
},
|
||||
// Key field for get, update, delete operations
|
||||
{
|
||||
id: 'key',
|
||||
title: 'Key (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "pk": "user#123"\n}',
|
||||
condition: { field: 'operation', value: 'get' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'key',
|
||||
title: 'Key (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "pk": "user#123"\n}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'key',
|
||||
title: 'Key (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "pk": "user#123"\n}',
|
||||
condition: { field: 'operation', value: 'delete' },
|
||||
required: true,
|
||||
},
|
||||
// Consistent read for get
|
||||
{
|
||||
id: 'consistentRead',
|
||||
title: 'Consistent Read',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Eventually Consistent', id: 'false' },
|
||||
{ label: 'Strongly Consistent', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'get' },
|
||||
},
|
||||
// Item for put operation
|
||||
{
|
||||
id: 'item',
|
||||
title: 'Item (JSON)',
|
||||
type: 'code',
|
||||
placeholder:
|
||||
'{\n "pk": "user#123",\n "name": "John Doe",\n "email": "john@example.com"\n}',
|
||||
condition: { field: 'operation', value: 'put' },
|
||||
required: true,
|
||||
},
|
||||
// Key condition expression for query
|
||||
{
|
||||
id: 'keyConditionExpression',
|
||||
title: 'Key Condition Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'pk = :pk',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
required: true,
|
||||
},
|
||||
// Update expression for update operation
|
||||
{
|
||||
id: 'updateExpression',
|
||||
title: 'Update Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'SET #name = :name',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
},
|
||||
// Filter expression for query and scan
|
||||
{
|
||||
id: 'filterExpression',
|
||||
title: 'Filter Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'attribute_exists(email)',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
{
|
||||
id: 'filterExpression',
|
||||
title: 'Filter Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'attribute_exists(email)',
|
||||
condition: { field: 'operation', value: 'scan' },
|
||||
},
|
||||
// Projection expression for scan
|
||||
{
|
||||
id: 'projectionExpression',
|
||||
title: 'Projection Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'pk, #name, email',
|
||||
condition: { field: 'operation', value: 'scan' },
|
||||
},
|
||||
// Expression attribute names for query, scan, update
|
||||
{
|
||||
id: 'expressionAttributeNames',
|
||||
title: 'Expression Attribute Names (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "#name": "name"\n}',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
{
|
||||
id: 'expressionAttributeNames',
|
||||
title: 'Expression Attribute Names (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "#name": "name"\n}',
|
||||
condition: { field: 'operation', value: 'scan' },
|
||||
},
|
||||
{
|
||||
id: 'expressionAttributeNames',
|
||||
title: 'Expression Attribute Names (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "#name": "name"\n}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
},
|
||||
// Expression attribute values for query, scan, update
|
||||
{
|
||||
id: 'expressionAttributeValues',
|
||||
title: 'Expression Attribute Values (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n ":pk": "user#123",\n ":name": "Jane"\n}',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
{
|
||||
id: 'expressionAttributeValues',
|
||||
title: 'Expression Attribute Values (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n ":status": "active"\n}',
|
||||
condition: { field: 'operation', value: 'scan' },
|
||||
},
|
||||
{
|
||||
id: 'expressionAttributeValues',
|
||||
title: 'Expression Attribute Values (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n ":name": "Jane Doe"\n}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
},
|
||||
// Index name for query
|
||||
{
|
||||
id: 'indexName',
|
||||
title: 'Index Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'GSI1',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
// Limit for query and scan
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
},
|
||||
{
|
||||
id: 'limit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'scan' },
|
||||
},
|
||||
// Condition expression for update and delete
|
||||
{
|
||||
id: 'conditionExpression',
|
||||
title: 'Condition Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'attribute_exists(pk)',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
},
|
||||
{
|
||||
id: 'conditionExpression',
|
||||
title: 'Condition Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'attribute_exists(pk)',
|
||||
condition: { field: 'operation', value: 'delete' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'dynamodb_get',
|
||||
'dynamodb_put',
|
||||
'dynamodb_query',
|
||||
'dynamodb_scan',
|
||||
'dynamodb_update',
|
||||
'dynamodb_delete',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'get':
|
||||
return 'dynamodb_get'
|
||||
case 'put':
|
||||
return 'dynamodb_put'
|
||||
case 'query':
|
||||
return 'dynamodb_query'
|
||||
case 'scan':
|
||||
return 'dynamodb_scan'
|
||||
case 'update':
|
||||
return 'dynamodb_update'
|
||||
case 'delete':
|
||||
return 'dynamodb_delete'
|
||||
default:
|
||||
throw new Error(`Invalid DynamoDB operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const {
|
||||
operation,
|
||||
key,
|
||||
item,
|
||||
expressionAttributeNames,
|
||||
expressionAttributeValues,
|
||||
consistentRead,
|
||||
limit,
|
||||
...rest
|
||||
} = params
|
||||
|
||||
// Parse JSON fields
|
||||
const parseJson = (value: unknown, fieldName: string) => {
|
||||
if (!value) return undefined
|
||||
if (typeof value === 'object') return value
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (parseError) {
|
||||
const errorMsg =
|
||||
parseError instanceof Error ? parseError.message : 'Unknown JSON error'
|
||||
throw new Error(`Invalid JSON in ${fieldName}: ${errorMsg}`)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const parsedKey = parseJson(key, 'key')
|
||||
const parsedItem = parseJson(item, 'item')
|
||||
const parsedExpressionAttributeNames = parseJson(
|
||||
expressionAttributeNames,
|
||||
'expressionAttributeNames'
|
||||
)
|
||||
const parsedExpressionAttributeValues = parseJson(
|
||||
expressionAttributeValues,
|
||||
'expressionAttributeValues'
|
||||
)
|
||||
|
||||
// Build connection config
|
||||
const connectionConfig = {
|
||||
region: rest.region,
|
||||
accessKeyId: rest.accessKeyId,
|
||||
secretAccessKey: rest.secretAccessKey,
|
||||
}
|
||||
|
||||
// Build params object
|
||||
const result: Record<string, unknown> = {
|
||||
...connectionConfig,
|
||||
tableName: rest.tableName,
|
||||
}
|
||||
|
||||
if (parsedKey !== undefined) result.key = parsedKey
|
||||
if (parsedItem !== undefined) result.item = parsedItem
|
||||
if (rest.keyConditionExpression) result.keyConditionExpression = rest.keyConditionExpression
|
||||
if (rest.updateExpression) result.updateExpression = rest.updateExpression
|
||||
if (rest.filterExpression) result.filterExpression = rest.filterExpression
|
||||
if (rest.projectionExpression) result.projectionExpression = rest.projectionExpression
|
||||
if (parsedExpressionAttributeNames !== undefined) {
|
||||
result.expressionAttributeNames = parsedExpressionAttributeNames
|
||||
}
|
||||
if (parsedExpressionAttributeValues !== undefined) {
|
||||
result.expressionAttributeValues = parsedExpressionAttributeValues
|
||||
}
|
||||
if (rest.indexName) result.indexName = rest.indexName
|
||||
if (limit) result.limit = Number.parseInt(String(limit), 10)
|
||||
if (rest.conditionExpression) result.conditionExpression = rest.conditionExpression
|
||||
// Handle consistentRead - dropdown sends 'true'/'false' strings or boolean
|
||||
if (consistentRead === 'true' || consistentRead === true) {
|
||||
result.consistentRead = true
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'DynamoDB operation to perform' },
|
||||
region: { type: 'string', description: 'AWS region' },
|
||||
accessKeyId: { type: 'string', description: 'AWS access key ID' },
|
||||
secretAccessKey: { type: 'string', description: 'AWS secret access key' },
|
||||
tableName: { type: 'string', description: 'DynamoDB table name' },
|
||||
key: { type: 'json', description: 'Primary key for get/update/delete operations' },
|
||||
item: { type: 'json', description: 'Item to put into the table' },
|
||||
keyConditionExpression: { type: 'string', description: 'Key condition for query operations' },
|
||||
updateExpression: { type: 'string', description: 'Update expression for update operations' },
|
||||
filterExpression: { type: 'string', description: 'Filter expression for query/scan' },
|
||||
projectionExpression: { type: 'string', description: 'Attributes to retrieve in scan' },
|
||||
expressionAttributeNames: { type: 'json', description: 'Attribute name mappings' },
|
||||
expressionAttributeValues: { type: 'json', description: 'Expression attribute values' },
|
||||
indexName: { type: 'string', description: 'Secondary index name for query' },
|
||||
limit: { type: 'number', description: 'Maximum items to return' },
|
||||
conditionExpression: { type: 'string', description: 'Condition for update/delete' },
|
||||
consistentRead: { type: 'string', description: 'Use strongly consistent read' },
|
||||
},
|
||||
outputs: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Success or error message describing the operation outcome',
|
||||
},
|
||||
item: {
|
||||
type: 'json',
|
||||
description: 'Single item returned from get or update operation',
|
||||
},
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of items returned from query or scan',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Number of items returned',
|
||||
},
|
||||
},
|
||||
}
|
||||
326
apps/sim/blocks/blocks/rds.ts
Normal file
326
apps/sim/blocks/blocks/rds.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
import { RDSIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { RdsResponse } from '@/tools/rds/types'
|
||||
|
||||
export const RDSBlock: BlockConfig<RdsResponse> = {
|
||||
type: 'rds',
|
||||
name: 'Amazon RDS',
|
||||
description: 'Connect to Amazon RDS via Data API',
|
||||
longDescription:
|
||||
'Integrate Amazon RDS Aurora Serverless into the workflow using the Data API. Can query, insert, update, delete, and execute raw SQL without managing database connections.',
|
||||
docsLink: 'https://docs.sim.ai/tools/rds',
|
||||
category: 'tools',
|
||||
bgColor: 'linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)',
|
||||
icon: RDSIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Query (SELECT)', id: 'query' },
|
||||
{ label: 'Insert Data', id: 'insert' },
|
||||
{ label: 'Update Data', id: 'update' },
|
||||
{ label: 'Delete Data', id: 'delete' },
|
||||
{ label: 'Execute Raw SQL', id: 'execute' },
|
||||
],
|
||||
value: () => 'query',
|
||||
},
|
||||
{
|
||||
id: 'region',
|
||||
title: 'AWS Region',
|
||||
type: 'short-input',
|
||||
placeholder: 'us-east-1',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'accessKeyId',
|
||||
title: 'AWS Access Key ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'AKIA...',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'secretAccessKey',
|
||||
title: 'AWS Secret Access Key',
|
||||
type: 'short-input',
|
||||
placeholder: 'Your secret access key',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'resourceArn',
|
||||
title: 'Resource ARN',
|
||||
type: 'short-input',
|
||||
placeholder: 'arn:aws:rds:us-east-1:123456789:cluster:my-cluster',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'secretArn',
|
||||
title: 'Secret ARN',
|
||||
type: 'short-input',
|
||||
placeholder: 'arn:aws:secretsmanager:us-east-1:123456789:secret:my-secret',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
title: 'Database Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'your_database',
|
||||
required: false,
|
||||
},
|
||||
// Table field for insert/update/delete operations
|
||||
{
|
||||
id: 'table',
|
||||
title: 'Table Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'users',
|
||||
condition: { field: 'operation', value: 'insert' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'table',
|
||||
title: 'Table Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'users',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'table',
|
||||
title: 'Table Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'users',
|
||||
condition: { field: 'operation', value: 'delete' },
|
||||
required: true,
|
||||
},
|
||||
// SQL Query field
|
||||
{
|
||||
id: 'query',
|
||||
title: 'SQL Query',
|
||||
type: 'code',
|
||||
placeholder: 'SELECT * FROM users WHERE active = true',
|
||||
condition: { field: 'operation', value: 'query' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert SQL database developer. Write SQL queries based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the SQL query. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw SQL query.
|
||||
|
||||
### QUERY GUIDELINES
|
||||
1. **Syntax**: Use standard SQL syntax compatible with both MySQL and PostgreSQL
|
||||
2. **Performance**: Write efficient queries with proper indexing considerations
|
||||
3. **Security**: Use parameterized queries when applicable
|
||||
4. **Readability**: Format queries with proper indentation and spacing
|
||||
5. **Best Practices**: Follow standard SQL naming conventions
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Simple Select**: "Get all active users"
|
||||
→ SELECT id, name, email, created_at
|
||||
FROM users
|
||||
WHERE active = true
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
**Complex Join**: "Get users with their order counts and total spent"
|
||||
→ SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.email,
|
||||
COUNT(o.id) as order_count,
|
||||
COALESCE(SUM(o.total), 0) as total_spent
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.user_id
|
||||
WHERE u.active = true
|
||||
GROUP BY u.id, u.name, u.email
|
||||
HAVING COUNT(o.id) > 0
|
||||
ORDER BY total_spent DESC;
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the SQL query - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe the SQL query you need...',
|
||||
generationType: 'sql-query',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'SQL Query',
|
||||
type: 'code',
|
||||
placeholder: 'SELECT * FROM table_name',
|
||||
condition: { field: 'operation', value: 'execute' },
|
||||
required: true,
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert SQL database developer. Write SQL queries based on the user's request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the SQL query. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw SQL query.
|
||||
|
||||
### QUERY GUIDELINES
|
||||
1. **Syntax**: Use standard SQL syntax compatible with both MySQL and PostgreSQL
|
||||
2. **Performance**: Write efficient queries with proper indexing considerations
|
||||
3. **Security**: Use parameterized queries when applicable
|
||||
4. **Readability**: Format queries with proper indentation and spacing
|
||||
5. **Best Practices**: Follow standard SQL naming conventions
|
||||
|
||||
### EXAMPLES
|
||||
|
||||
**Simple Select**: "Get all active users"
|
||||
→ SELECT id, name, email, created_at
|
||||
FROM users
|
||||
WHERE active = true
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
**Create Table**: "Create a users table"
|
||||
→ CREATE TABLE users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the SQL query - no explanations, no markdown, no extra text.`,
|
||||
placeholder: 'Describe the SQL query you need...',
|
||||
generationType: 'sql-query',
|
||||
},
|
||||
},
|
||||
// Data for insert operations
|
||||
{
|
||||
id: 'data',
|
||||
title: 'Data (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "name": "John Doe",\n "email": "john@example.com",\n "active": true\n}',
|
||||
condition: { field: 'operation', value: 'insert' },
|
||||
required: true,
|
||||
},
|
||||
// Set clause for updates
|
||||
{
|
||||
id: 'data',
|
||||
title: 'Update Data (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "name": "Jane Doe",\n "email": "jane@example.com"\n}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
},
|
||||
// Conditions for update/delete (parameterized for SQL injection prevention)
|
||||
{
|
||||
id: 'conditions',
|
||||
title: 'Conditions (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "id": 1\n}',
|
||||
condition: { field: 'operation', value: 'update' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'conditions',
|
||||
title: 'Conditions (JSON)',
|
||||
type: 'code',
|
||||
placeholder: '{\n "id": 1\n}',
|
||||
condition: { field: 'operation', value: 'delete' },
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: ['rds_query', 'rds_insert', 'rds_update', 'rds_delete', 'rds_execute'],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'query':
|
||||
return 'rds_query'
|
||||
case 'insert':
|
||||
return 'rds_insert'
|
||||
case 'update':
|
||||
return 'rds_update'
|
||||
case 'delete':
|
||||
return 'rds_delete'
|
||||
case 'execute':
|
||||
return 'rds_execute'
|
||||
default:
|
||||
throw new Error(`Invalid RDS operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, data, conditions, ...rest } = params
|
||||
|
||||
// Parse JSON fields
|
||||
const parseJson = (value: unknown, fieldName: string) => {
|
||||
if (!value) return undefined
|
||||
if (typeof value === 'object') return value
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (parseError) {
|
||||
const errorMsg =
|
||||
parseError instanceof Error ? parseError.message : 'Unknown JSON error'
|
||||
throw new Error(`Invalid JSON in ${fieldName}: ${errorMsg}`)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const parsedData = parseJson(data, 'data')
|
||||
const parsedConditions = parseJson(conditions, 'conditions')
|
||||
|
||||
// Build connection config
|
||||
const connectionConfig = {
|
||||
region: rest.region,
|
||||
accessKeyId: rest.accessKeyId,
|
||||
secretAccessKey: rest.secretAccessKey,
|
||||
resourceArn: rest.resourceArn,
|
||||
secretArn: rest.secretArn,
|
||||
database: rest.database,
|
||||
}
|
||||
|
||||
// Build params object
|
||||
const result: Record<string, unknown> = { ...connectionConfig }
|
||||
|
||||
if (rest.table) result.table = rest.table
|
||||
if (rest.query) result.query = rest.query
|
||||
if (parsedConditions !== undefined) result.conditions = parsedConditions
|
||||
if (parsedData !== undefined) result.data = parsedData
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Database operation to perform' },
|
||||
region: { type: 'string', description: 'AWS region' },
|
||||
accessKeyId: { type: 'string', description: 'AWS access key ID' },
|
||||
secretAccessKey: { type: 'string', description: 'AWS secret access key' },
|
||||
resourceArn: { type: 'string', description: 'Aurora DB cluster ARN' },
|
||||
secretArn: { type: 'string', description: 'Secrets Manager secret ARN' },
|
||||
database: { type: 'string', description: 'Database name' },
|
||||
table: { type: 'string', description: 'Table name' },
|
||||
query: { type: 'string', description: 'SQL query to execute' },
|
||||
data: { type: 'json', description: 'Data for insert/update operations' },
|
||||
conditions: { type: 'json', description: 'Conditions for update/delete (e.g., {"id": 1})' },
|
||||
},
|
||||
outputs: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Success or error message describing the operation outcome',
|
||||
},
|
||||
rows: {
|
||||
type: 'array',
|
||||
description: 'Array of rows returned from the query',
|
||||
},
|
||||
rowCount: {
|
||||
type: 'number',
|
||||
description: 'Number of rows affected by the operation',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const S3Block: BlockConfig<S3Response> = {
|
||||
'Integrate S3 into the workflow. Upload files, download objects, list bucket contents, delete objects, and copy objects between buckets. Requires AWS access key and secret access key.',
|
||||
docsLink: 'https://docs.sim.ai/tools/s3',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
bgColor: 'linear-gradient(45deg, #1B660F 0%, #6CAE3E 100%)',
|
||||
icon: S3Icon,
|
||||
subBlocks: [
|
||||
// Operation selector
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ClayBlock } from '@/blocks/blocks/clay'
|
||||
import { ConditionBlock } from '@/blocks/blocks/condition'
|
||||
import { ConfluenceBlock } from '@/blocks/blocks/confluence'
|
||||
import { DiscordBlock } from '@/blocks/blocks/discord'
|
||||
import { DynamoDBBlock } from '@/blocks/blocks/dynamodb'
|
||||
import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs'
|
||||
import { EvaluatorBlock } from '@/blocks/blocks/evaluator'
|
||||
import { ExaBlock } from '@/blocks/blocks/exa'
|
||||
@@ -70,6 +71,7 @@ import { PostgreSQLBlock } from '@/blocks/blocks/postgresql'
|
||||
import { PostHogBlock } from '@/blocks/blocks/posthog'
|
||||
import { PylonBlock } from '@/blocks/blocks/pylon'
|
||||
import { QdrantBlock } from '@/blocks/blocks/qdrant'
|
||||
import { RDSBlock } from '@/blocks/blocks/rds'
|
||||
import { RedditBlock } from '@/blocks/blocks/reddit'
|
||||
import { ResendBlock } from '@/blocks/blocks/resend'
|
||||
import { ResponseBlock } from '@/blocks/blocks/response'
|
||||
@@ -189,6 +191,8 @@ export const registry: Record<string, BlockConfig> = {
|
||||
posthog: PostHogBlock,
|
||||
pylon: PylonBlock,
|
||||
qdrant: QdrantBlock,
|
||||
rds: RDSBlock,
|
||||
dynamodb: DynamoDBBlock,
|
||||
reddit: RedditBlock,
|
||||
resend: ResendBlock,
|
||||
response: ResponseBlock,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@aws-sdk/client-dynamodb": "3.940.0",
|
||||
"@aws-sdk/client-rds-data": "3.940.0",
|
||||
"@aws-sdk/client-s3": "^3.779.0",
|
||||
"@aws-sdk/lib-dynamodb": "3.940.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.779.0",
|
||||
"@azure/communication-email": "1.0.0",
|
||||
"@azure/storage-blob": "12.27.0",
|
||||
|
||||
84
apps/sim/tools/dynamodb/delete.ts
Normal file
84
apps/sim/tools/dynamodb/delete.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { DynamoDBDeleteParams, DynamoDBDeleteResponse } from '@/tools/dynamodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteTool: ToolConfig<DynamoDBDeleteParams, DynamoDBDeleteResponse> = {
|
||||
id: 'dynamodb_delete',
|
||||
name: 'DynamoDB Delete',
|
||||
description: 'Delete an item from a DynamoDB table',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DynamoDB table name',
|
||||
},
|
||||
key: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Primary key of the item to delete',
|
||||
},
|
||||
conditionExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Condition that must be met for the delete to succeed',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/dynamodb/delete',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
tableName: params.tableName,
|
||||
key: params.key,
|
||||
...(params.conditionExpression && { conditionExpression: params.conditionExpression }),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'DynamoDB delete failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Item deleted successfully',
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
},
|
||||
}
|
||||
86
apps/sim/tools/dynamodb/get.ts
Normal file
86
apps/sim/tools/dynamodb/get.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { DynamoDBGetParams, DynamoDBGetResponse } from '@/tools/dynamodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getTool: ToolConfig<DynamoDBGetParams, DynamoDBGetResponse> = {
|
||||
id: 'dynamodb_get',
|
||||
name: 'DynamoDB Get',
|
||||
description: 'Get an item from a DynamoDB table by primary key',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DynamoDB table name',
|
||||
},
|
||||
key: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Primary key of the item to retrieve',
|
||||
},
|
||||
consistentRead: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Use strongly consistent read',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/dynamodb/get',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
tableName: params.tableName,
|
||||
key: params.key,
|
||||
...(params.consistentRead !== undefined && { consistentRead: params.consistentRead }),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'DynamoDB get failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Item retrieved successfully',
|
||||
item: data.item,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
item: { type: 'object', description: 'Retrieved item' },
|
||||
},
|
||||
}
|
||||
8
apps/sim/tools/dynamodb/index.ts
Normal file
8
apps/sim/tools/dynamodb/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { deleteTool } from './delete'
|
||||
import { getTool } from './get'
|
||||
import { putTool } from './put'
|
||||
import { queryTool } from './query'
|
||||
import { scanTool } from './scan'
|
||||
import { updateTool } from './update'
|
||||
|
||||
export { deleteTool, getTool, putTool, queryTool, scanTool, updateTool }
|
||||
79
apps/sim/tools/dynamodb/put.ts
Normal file
79
apps/sim/tools/dynamodb/put.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { DynamoDBPutParams, DynamoDBPutResponse } from '@/tools/dynamodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const putTool: ToolConfig<DynamoDBPutParams, DynamoDBPutResponse> = {
|
||||
id: 'dynamodb_put',
|
||||
name: 'DynamoDB Put',
|
||||
description: 'Put an item into a DynamoDB table',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DynamoDB table name',
|
||||
},
|
||||
item: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Item to put into the table',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/dynamodb/put',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
tableName: params.tableName,
|
||||
item: params.item,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'DynamoDB put failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Item created successfully',
|
||||
item: data.item,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
item: { type: 'object', description: 'Created item' },
|
||||
},
|
||||
}
|
||||
120
apps/sim/tools/dynamodb/query.ts
Normal file
120
apps/sim/tools/dynamodb/query.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import type { DynamoDBQueryParams, DynamoDBQueryResponse } from '@/tools/dynamodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const queryTool: ToolConfig<DynamoDBQueryParams, DynamoDBQueryResponse> = {
|
||||
id: 'dynamodb_query',
|
||||
name: 'DynamoDB Query',
|
||||
description: 'Query items from a DynamoDB table using key conditions',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DynamoDB table name',
|
||||
},
|
||||
keyConditionExpression: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Key condition expression (e.g., "pk = :pk")',
|
||||
},
|
||||
filterExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter expression for results',
|
||||
},
|
||||
expressionAttributeNames: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attribute name mappings for reserved words',
|
||||
},
|
||||
expressionAttributeValues: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Expression attribute values',
|
||||
},
|
||||
indexName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Secondary index name to query',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of items to return',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/dynamodb/query',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
tableName: params.tableName,
|
||||
keyConditionExpression: params.keyConditionExpression,
|
||||
...(params.filterExpression && { filterExpression: params.filterExpression }),
|
||||
...(params.expressionAttributeNames && {
|
||||
expressionAttributeNames: params.expressionAttributeNames,
|
||||
}),
|
||||
...(params.expressionAttributeValues && {
|
||||
expressionAttributeValues: params.expressionAttributeValues,
|
||||
}),
|
||||
...(params.indexName && { indexName: params.indexName }),
|
||||
...(params.limit && { limit: params.limit }),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'DynamoDB query failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Query executed successfully',
|
||||
items: data.items || [],
|
||||
count: data.count || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
items: { type: 'array', description: 'Array of items returned' },
|
||||
count: { type: 'number', description: 'Number of items returned' },
|
||||
},
|
||||
}
|
||||
113
apps/sim/tools/dynamodb/scan.ts
Normal file
113
apps/sim/tools/dynamodb/scan.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type { DynamoDBScanParams, DynamoDBScanResponse } from '@/tools/dynamodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const scanTool: ToolConfig<DynamoDBScanParams, DynamoDBScanResponse> = {
|
||||
id: 'dynamodb_scan',
|
||||
name: 'DynamoDB Scan',
|
||||
description: 'Scan all items in a DynamoDB table',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DynamoDB table name',
|
||||
},
|
||||
filterExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter expression for results',
|
||||
},
|
||||
projectionExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attributes to retrieve',
|
||||
},
|
||||
expressionAttributeNames: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attribute name mappings for reserved words',
|
||||
},
|
||||
expressionAttributeValues: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Expression attribute values',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of items to return',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/dynamodb/scan',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
tableName: params.tableName,
|
||||
...(params.filterExpression && { filterExpression: params.filterExpression }),
|
||||
...(params.projectionExpression && { projectionExpression: params.projectionExpression }),
|
||||
...(params.expressionAttributeNames && {
|
||||
expressionAttributeNames: params.expressionAttributeNames,
|
||||
}),
|
||||
...(params.expressionAttributeValues && {
|
||||
expressionAttributeValues: params.expressionAttributeValues,
|
||||
}),
|
||||
...(params.limit && { limit: params.limit }),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'DynamoDB scan failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Scan executed successfully',
|
||||
items: data.items || [],
|
||||
count: data.count || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
items: { type: 'array', description: 'Array of items returned' },
|
||||
count: { type: 'number', description: 'Number of items returned' },
|
||||
},
|
||||
}
|
||||
70
apps/sim/tools/dynamodb/types.ts
Normal file
70
apps/sim/tools/dynamodb/types.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface DynamoDBConnectionConfig {
|
||||
region: string
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
}
|
||||
|
||||
export interface DynamoDBGetParams extends DynamoDBConnectionConfig {
|
||||
tableName: string
|
||||
key: Record<string, unknown>
|
||||
consistentRead?: boolean
|
||||
}
|
||||
|
||||
export interface DynamoDBPutParams extends DynamoDBConnectionConfig {
|
||||
tableName: string
|
||||
item: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface DynamoDBQueryParams extends DynamoDBConnectionConfig {
|
||||
tableName: string
|
||||
keyConditionExpression: string
|
||||
filterExpression?: string
|
||||
expressionAttributeNames?: Record<string, string>
|
||||
expressionAttributeValues?: Record<string, unknown>
|
||||
indexName?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface DynamoDBScanParams extends DynamoDBConnectionConfig {
|
||||
tableName: string
|
||||
filterExpression?: string
|
||||
projectionExpression?: string
|
||||
expressionAttributeNames?: Record<string, string>
|
||||
expressionAttributeValues?: Record<string, unknown>
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface DynamoDBUpdateParams extends DynamoDBConnectionConfig {
|
||||
tableName: string
|
||||
key: Record<string, unknown>
|
||||
updateExpression: string
|
||||
expressionAttributeNames?: Record<string, string>
|
||||
expressionAttributeValues?: Record<string, unknown>
|
||||
conditionExpression?: string
|
||||
}
|
||||
|
||||
export interface DynamoDBDeleteParams extends DynamoDBConnectionConfig {
|
||||
tableName: string
|
||||
key: Record<string, unknown>
|
||||
conditionExpression?: string
|
||||
}
|
||||
|
||||
export interface DynamoDBBaseResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
item?: Record<string, unknown>
|
||||
items?: Record<string, unknown>[]
|
||||
count?: number
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface DynamoDBGetResponse extends DynamoDBBaseResponse {}
|
||||
export interface DynamoDBPutResponse extends DynamoDBBaseResponse {}
|
||||
export interface DynamoDBQueryResponse extends DynamoDBBaseResponse {}
|
||||
export interface DynamoDBScanResponse extends DynamoDBBaseResponse {}
|
||||
export interface DynamoDBUpdateResponse extends DynamoDBBaseResponse {}
|
||||
export interface DynamoDBDeleteResponse extends DynamoDBBaseResponse {}
|
||||
export interface DynamoDBResponse extends DynamoDBBaseResponse {}
|
||||
111
apps/sim/tools/dynamodb/update.ts
Normal file
111
apps/sim/tools/dynamodb/update.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { DynamoDBUpdateParams, DynamoDBUpdateResponse } from '@/tools/dynamodb/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateTool: ToolConfig<DynamoDBUpdateParams, DynamoDBUpdateResponse> = {
|
||||
id: 'dynamodb_update',
|
||||
name: 'DynamoDB Update',
|
||||
description: 'Update an item in a DynamoDB table',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DynamoDB table name',
|
||||
},
|
||||
key: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Primary key of the item to update',
|
||||
},
|
||||
updateExpression: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Update expression (e.g., "SET #name = :name")',
|
||||
},
|
||||
expressionAttributeNames: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attribute name mappings for reserved words',
|
||||
},
|
||||
expressionAttributeValues: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Expression attribute values',
|
||||
},
|
||||
conditionExpression: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Condition that must be met for the update to succeed',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/dynamodb/update',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
tableName: params.tableName,
|
||||
key: params.key,
|
||||
updateExpression: params.updateExpression,
|
||||
...(params.expressionAttributeNames && {
|
||||
expressionAttributeNames: params.expressionAttributeNames,
|
||||
}),
|
||||
...(params.expressionAttributeValues && {
|
||||
expressionAttributeValues: params.expressionAttributeValues,
|
||||
}),
|
||||
...(params.conditionExpression && { conditionExpression: params.conditionExpression }),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'DynamoDB update failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Item updated successfully',
|
||||
item: data.item,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
item: { type: 'object', description: 'Updated item' },
|
||||
},
|
||||
}
|
||||
102
apps/sim/tools/rds/delete.ts
Normal file
102
apps/sim/tools/rds/delete.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { RdsDeleteParams, RdsDeleteResponse } from '@/tools/rds/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteTool: ToolConfig<RdsDeleteParams, RdsDeleteResponse> = {
|
||||
id: 'rds_delete',
|
||||
name: 'RDS Delete',
|
||||
description: 'Delete data from an Amazon RDS table using the Data API',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
resourceArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Aurora DB cluster',
|
||||
},
|
||||
secretArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Secrets Manager secret containing DB credentials',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name (optional)',
|
||||
},
|
||||
table: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name to delete from',
|
||||
},
|
||||
conditions: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Conditions for the delete (e.g., {"id": 1})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/rds/delete',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
...(params.database && { database: params.database }),
|
||||
table: params.table,
|
||||
conditions: params.conditions,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'RDS delete failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Delete executed successfully',
|
||||
rows: data.rows || [],
|
||||
rowCount: data.rowCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
rows: { type: 'array', description: 'Array of deleted rows' },
|
||||
rowCount: { type: 'number', description: 'Number of rows deleted' },
|
||||
},
|
||||
}
|
||||
95
apps/sim/tools/rds/execute.ts
Normal file
95
apps/sim/tools/rds/execute.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { RdsExecuteParams, RdsExecuteResponse } from '@/tools/rds/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const executeTool: ToolConfig<RdsExecuteParams, RdsExecuteResponse> = {
|
||||
id: 'rds_execute',
|
||||
name: 'RDS Execute',
|
||||
description: 'Execute raw SQL on Amazon RDS using the Data API',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
resourceArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Aurora DB cluster',
|
||||
},
|
||||
secretArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Secrets Manager secret containing DB credentials',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name (optional)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Raw SQL query to execute',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/rds/execute',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
...(params.database && { database: params.database }),
|
||||
query: params.query,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'RDS execute failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Query executed successfully',
|
||||
rows: data.rows || [],
|
||||
rowCount: data.rowCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
rows: { type: 'array', description: 'Array of rows returned or affected' },
|
||||
rowCount: { type: 'number', description: 'Number of rows affected' },
|
||||
},
|
||||
}
|
||||
7
apps/sim/tools/rds/index.ts
Normal file
7
apps/sim/tools/rds/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { deleteTool } from './delete'
|
||||
import { executeTool } from './execute'
|
||||
import { insertTool } from './insert'
|
||||
import { queryTool } from './query'
|
||||
import { updateTool } from './update'
|
||||
|
||||
export { deleteTool, executeTool, insertTool, queryTool, updateTool }
|
||||
102
apps/sim/tools/rds/insert.ts
Normal file
102
apps/sim/tools/rds/insert.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { RdsInsertParams, RdsInsertResponse } from '@/tools/rds/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const insertTool: ToolConfig<RdsInsertParams, RdsInsertResponse> = {
|
||||
id: 'rds_insert',
|
||||
name: 'RDS Insert',
|
||||
description: 'Insert data into an Amazon RDS table using the Data API',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
resourceArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Aurora DB cluster',
|
||||
},
|
||||
secretArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Secrets Manager secret containing DB credentials',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name (optional)',
|
||||
},
|
||||
table: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name to insert into',
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Data to insert as key-value pairs',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/rds/insert',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
...(params.database && { database: params.database }),
|
||||
table: params.table,
|
||||
data: params.data,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'RDS insert failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Insert executed successfully',
|
||||
rows: data.rows || [],
|
||||
rowCount: data.rowCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
rows: { type: 'array', description: 'Array of inserted rows' },
|
||||
rowCount: { type: 'number', description: 'Number of rows inserted' },
|
||||
},
|
||||
}
|
||||
95
apps/sim/tools/rds/query.ts
Normal file
95
apps/sim/tools/rds/query.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { RdsQueryParams, RdsQueryResponse } from '@/tools/rds/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const queryTool: ToolConfig<RdsQueryParams, RdsQueryResponse> = {
|
||||
id: 'rds_query',
|
||||
name: 'RDS Query',
|
||||
description: 'Execute a SELECT query on Amazon RDS using the Data API',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
resourceArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Aurora DB cluster',
|
||||
},
|
||||
secretArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Secrets Manager secret containing DB credentials',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name (optional)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'SQL SELECT query to execute',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/rds/query',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
...(params.database && { database: params.database }),
|
||||
query: params.query,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'RDS query failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Query executed successfully',
|
||||
rows: data.rows || [],
|
||||
rowCount: data.rowCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
rows: { type: 'array', description: 'Array of rows returned from the query' },
|
||||
rowCount: { type: 'number', description: 'Number of rows returned' },
|
||||
},
|
||||
}
|
||||
50
apps/sim/tools/rds/types.ts
Normal file
50
apps/sim/tools/rds/types.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface RdsConnectionConfig {
|
||||
region: string
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
resourceArn: string
|
||||
secretArn: string
|
||||
database?: string
|
||||
}
|
||||
|
||||
export interface RdsQueryParams extends RdsConnectionConfig {
|
||||
query: string
|
||||
}
|
||||
|
||||
export interface RdsInsertParams extends RdsConnectionConfig {
|
||||
table: string
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface RdsUpdateParams extends RdsConnectionConfig {
|
||||
table: string
|
||||
data: Record<string, unknown>
|
||||
conditions: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface RdsDeleteParams extends RdsConnectionConfig {
|
||||
table: string
|
||||
conditions: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface RdsExecuteParams extends RdsConnectionConfig {
|
||||
query: string
|
||||
}
|
||||
|
||||
export interface RdsBaseResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
rows: unknown[]
|
||||
rowCount: number
|
||||
}
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface RdsQueryResponse extends RdsBaseResponse {}
|
||||
export interface RdsInsertResponse extends RdsBaseResponse {}
|
||||
export interface RdsUpdateResponse extends RdsBaseResponse {}
|
||||
export interface RdsDeleteResponse extends RdsBaseResponse {}
|
||||
export interface RdsExecuteResponse extends RdsBaseResponse {}
|
||||
export interface RdsResponse extends RdsBaseResponse {}
|
||||
109
apps/sim/tools/rds/update.ts
Normal file
109
apps/sim/tools/rds/update.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { RdsUpdateParams, RdsUpdateResponse } from '@/tools/rds/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateTool: ToolConfig<RdsUpdateParams, RdsUpdateResponse> = {
|
||||
id: 'rds_update',
|
||||
name: 'RDS Update',
|
||||
description: 'Update data in an Amazon RDS table using the Data API',
|
||||
version: '1.0',
|
||||
|
||||
params: {
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS region (e.g., us-east-1)',
|
||||
},
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS access key ID',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'AWS secret access key',
|
||||
},
|
||||
resourceArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Aurora DB cluster',
|
||||
},
|
||||
secretArn: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ARN of the Secrets Manager secret containing DB credentials',
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Database name (optional)',
|
||||
},
|
||||
table: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name to update',
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Data to update as key-value pairs',
|
||||
},
|
||||
conditions: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Conditions for the update (e.g., {"id": 1})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/rds/update',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
resourceArn: params.resourceArn,
|
||||
secretArn: params.secretArn,
|
||||
...(params.database && { database: params.database }),
|
||||
table: params.table,
|
||||
data: params.data,
|
||||
conditions: params.conditions,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'RDS update failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message || 'Update executed successfully',
|
||||
rows: data.rows || [],
|
||||
rowCount: data.rowCount || 0,
|
||||
},
|
||||
error: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: { type: 'string', description: 'Operation status message' },
|
||||
rows: { type: 'array', description: 'Array of updated rows' },
|
||||
rowCount: { type: 'number', description: 'Number of rows updated' },
|
||||
},
|
||||
}
|
||||
@@ -108,6 +108,14 @@ import {
|
||||
discordUpdateMemberTool,
|
||||
discordUpdateRoleTool,
|
||||
} from '@/tools/discord'
|
||||
import {
|
||||
deleteTool as dynamodbDeleteTool,
|
||||
getTool as dynamodbGetTool,
|
||||
putTool as dynamodbPutTool,
|
||||
queryTool as dynamodbQueryTool,
|
||||
scanTool as dynamodbScanTool,
|
||||
updateTool as dynamodbUpdateTool,
|
||||
} from '@/tools/dynamodb'
|
||||
import { elevenLabsTtsTool } from '@/tools/elevenlabs'
|
||||
import {
|
||||
exaAnswerTool,
|
||||
@@ -725,6 +733,13 @@ import {
|
||||
pylonUpdateUserTool,
|
||||
} from '@/tools/pylon'
|
||||
import { qdrantFetchTool, qdrantSearchTool, qdrantUpsertTool } from '@/tools/qdrant'
|
||||
import {
|
||||
deleteTool as rdsDeleteTool,
|
||||
executeTool as rdsExecuteTool,
|
||||
insertTool as rdsInsertTool,
|
||||
queryTool as rdsQueryTool,
|
||||
updateTool as rdsUpdateTool,
|
||||
} from '@/tools/rds'
|
||||
import {
|
||||
redditDeleteTool,
|
||||
redditEditTool,
|
||||
@@ -1227,6 +1242,17 @@ export const tools: Record<string, ToolConfig> = {
|
||||
postgresql_update: postgresUpdateTool,
|
||||
postgresql_delete: postgresDeleteTool,
|
||||
postgresql_execute: postgresExecuteTool,
|
||||
rds_query: rdsQueryTool,
|
||||
rds_insert: rdsInsertTool,
|
||||
rds_update: rdsUpdateTool,
|
||||
rds_delete: rdsDeleteTool,
|
||||
rds_execute: rdsExecuteTool,
|
||||
dynamodb_get: dynamodbGetTool,
|
||||
dynamodb_put: dynamodbPutTool,
|
||||
dynamodb_query: dynamodbQueryTool,
|
||||
dynamodb_scan: dynamodbScanTool,
|
||||
dynamodb_update: dynamodbUpdateTool,
|
||||
dynamodb_delete: dynamodbDeleteTool,
|
||||
mongodb_query: mongodbQueryTool,
|
||||
mongodb_insert: mongodbInsertTool,
|
||||
mongodb_update: mongodbUpdateTool,
|
||||
|
||||
Reference in New Issue
Block a user