@budibase/server
Version:
Budibase Web Server
138 lines (115 loc) • 3.62 kB
text/typescript
import {
Tool,
RestQueryToolSource as RestQueryToolSourceType,
Query,
} from "@budibase/types"
import { ToolSource } from "./ToolSource"
import { context } from "@budibase/backend-core"
import { z } from "zod"
import { newTool } from ".."
import * as queryController from "../../../api/controllers/query"
import { buildCtx } from "../../../automations/steps/utils"
export class RestQueryToolSource extends ToolSource {
private queries: Query[] = []
private loaded = false
getType(): string {
return "REST_QUERY"
}
getName(): string {
return "REST Queries"
}
private async loadQueries(): Promise<void> {
if (this.loaded) {
return
}
const toolSourceConfig = this.toolSource as RestQueryToolSourceType
const { queryIds } = toolSourceConfig
if (!queryIds || queryIds.length === 0) {
this.loaded = true
return
}
const db = context.getWorkspaceDB()
const queries: Query[] = []
for (const queryId of queryIds) {
try {
const query = await db.tryGet<Query>(queryId)
if (query) {
queries.push(query)
}
} catch (err) {
console.warn(`Failed to load query ${queryId}:`, err)
}
}
this.queries = queries
this.loaded = true
}
private sanitiseToolName(name: string): string {
if (name.length > 64) {
return name.substring(0, 64) + "..."
}
return name.replace(/[^a-zA-Z0-9_-]/g, "_")
}
private buildParametersSchema(query: Query): z.ZodObject<any> {
const schemaFields: Record<string, z.ZodTypeAny> = {}
for (const param of query.parameters || []) {
schemaFields[param.name] = z
.string()
.optional()
.describe(`Parameter: ${param.name}`)
}
return z.object(schemaFields)
}
private createQueryTool(query: Query): Tool {
const toolName = this.sanitiseToolName(query.name)
const parametersSchema = this.buildParametersSchema(query)
const description = query.restTemplateMetadata?.description
? `${query.name}: ${query.restTemplateMetadata.description}`
: `Execute REST query: ${query.name}`
return newTool({
name: toolName,
description,
parameters: parametersSchema,
handler: async (params: Record<string, any>) => {
const workspaceId = context.getWorkspaceId()
if (!workspaceId) {
return { error: "No app context available" }
}
const ctx: any = buildCtx(workspaceId, null, {
body: {
parameters: params,
},
params: {
queryId: query._id,
},
})
try {
await queryController.executeV2AsAutomation(ctx)
const { data, ...rest } = ctx.body
return { success: true, data, info: rest }
} catch (err: any) {
return {
success: false,
error: err.message || "Query execution failed",
}
}
},
})
}
getTools(): Tool[] {
// Note: This is synchronous but we need async loading
// The tools are loaded lazily when first accessed
// For now, return empty if not loaded - the async version should be used
if (!this.loaded) {
return []
}
return this.queries.map(query => this.createQueryTool(query))
}
async getToolsAsync(): Promise<Tool[]> {
await this.loadQueries()
return this.queries.map(query => this.createQueryTool(query))
}
validate(): boolean {
const config = this.toolSource as RestQueryToolSourceType
return !!config.datasourceId && Array.isArray(config.queryIds)
}
}