improvement(supabase): add supabase upsert tool, insert/replace on PK conflict (#1038)

This commit is contained in:
Waleed Latif
2025-08-19 21:21:09 -07:00
committed by Siddharth Ganesan
parent 79e932fed9
commit bbb5e53e43
6 changed files with 153 additions and 0 deletions
+20
View File
@@ -185,6 +185,26 @@ Delete rows from a Supabase table based on filter criteria
| `message` | string | Operation status message |
| `results` | array | Array of deleted records |
### `supabase_upsert`
Insert or update data in a Supabase table (upsert operation)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to upsert data into |
| `data` | any | Yes | The data to upsert \(insert or update\) |
| `apiKey` | string | Yes | Your Supabase service role secret key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `results` | array | Array of upserted records |
## Notes
+13
View File
@@ -27,6 +27,7 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
{ label: 'Create a Row', id: 'insert' },
{ label: 'Update a Row', id: 'update' },
{ label: 'Delete a Row', id: 'delete' },
{ label: 'Upsert a Row', id: 'upsert' },
],
value: () => 'query',
},
@@ -75,6 +76,15 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
condition: { field: 'operation', value: 'update' },
required: true,
},
{
id: 'data',
title: 'Data',
type: 'code',
layout: 'full',
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'upsert' },
required: true,
},
// Filter for get_row, update, delete operations (required)
{
id: 'filter',
@@ -138,6 +148,7 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
'supabase_get_row',
'supabase_update',
'supabase_delete',
'supabase_upsert',
],
config: {
tool: (params) => {
@@ -152,6 +163,8 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
return 'supabase_update'
case 'delete':
return 'supabase_delete'
case 'upsert':
return 'supabase_upsert'
default:
throw new Error(`Invalid Supabase operation: ${params.operation}`)
}
+2
View File
@@ -128,6 +128,7 @@ import {
supabaseInsertTool,
supabaseQueryTool,
supabaseUpdateTool,
supabaseUpsertTool,
} from '@/tools/supabase'
import { tavilyExtractTool, tavilySearchTool } from '@/tools/tavily'
import { telegramMessageTool } from '@/tools/telegram'
@@ -190,6 +191,7 @@ export const tools: Record<string, ToolConfig> = {
supabase_get_row: supabaseGetRowTool,
supabase_update: supabaseUpdateTool,
supabase_delete: supabaseDeleteTool,
supabase_upsert: supabaseUpsertTool,
typeform_responses: typeformResponsesTool,
typeform_files: typeformFilesTool,
typeform_insights: typeformInsightsTool,
+2
View File
@@ -3,9 +3,11 @@ import { getRowTool } from '@/tools/supabase/get_row'
import { insertTool } from '@/tools/supabase/insert'
import { queryTool } from '@/tools/supabase/query'
import { updateTool } from '@/tools/supabase/update'
import { upsertTool } from '@/tools/supabase/upsert'
export const supabaseQueryTool = queryTool
export const supabaseInsertTool = insertTool
export const supabaseGetRowTool = getRowTool
export const supabaseUpdateTool = updateTool
export const supabaseDeleteTool = deleteTool
export const supabaseUpsertTool = upsertTool
+9
View File
@@ -38,6 +38,13 @@ export interface SupabaseDeleteParams {
filter: string
}
export interface SupabaseUpsertParams {
apiKey: string
projectId: string
table: string
data: any
}
export interface SupabaseBaseResponse extends ToolResponse {
output: {
message: string
@@ -56,4 +63,6 @@ export interface SupabaseUpdateResponse extends SupabaseBaseResponse {}
export interface SupabaseDeleteResponse extends SupabaseBaseResponse {}
export interface SupabaseUpsertResponse extends SupabaseBaseResponse {}
export interface SupabaseResponse extends SupabaseBaseResponse {}
+107
View File
@@ -0,0 +1,107 @@
import type { SupabaseUpsertParams, SupabaseUpsertResponse } from '@/tools/supabase/types'
import type { ToolConfig } from '@/tools/types'
export const upsertTool: ToolConfig<SupabaseUpsertParams, SupabaseUpsertResponse> = {
id: 'supabase_upsert',
name: 'Supabase Upsert',
description: 'Insert or update data in a Supabase table (upsert operation)',
version: '1.0',
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
},
table: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The name of the Supabase table to upsert data into',
},
data: {
type: 'any',
required: true,
visibility: 'user-or-llm',
description: 'The data to upsert (insert or update)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Your Supabase service role secret key',
},
},
request: {
url: (params) => `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`,
method: 'POST',
headers: (params) => ({
apikey: params.apiKey,
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Prefer: 'return=representation,resolution=merge-duplicates',
}),
body: (params) => {
// Prepare the data - if it's an object but not an array, wrap it in an array
const dataToSend =
typeof params.data === 'object' && !Array.isArray(params.data) ? [params.data] : params.data
return dataToSend
},
},
transformResponse: async (response: Response) => {
const text = await response.text()
if (!text || text.trim() === '') {
return {
success: true,
output: {
message: 'Successfully upserted data into Supabase (no data returned)',
results: [],
},
error: undefined,
}
}
let data
try {
data = JSON.parse(text)
} catch (parseError) {
throw new Error(`Failed to parse Supabase response: ${parseError}`)
}
// Check if results array is empty and provide better feedback
const resultsArray = Array.isArray(data) ? data : [data]
const isEmpty = resultsArray.length === 0 || (resultsArray.length === 1 && !resultsArray[0])
if (isEmpty) {
return {
success: false,
output: {
message: 'No data was upserted into Supabase',
results: data,
},
error:
'No data was upserted into Supabase. This usually indicates invalid data format or schema mismatch. Please check that your JSON is valid and matches your table schema.',
}
}
const upsertedCount = resultsArray.length
return {
success: true,
output: {
message: `Successfully upserted ${upsertedCount} row${upsertedCount === 1 ? '' : 's'} into Supabase`,
results: data,
},
error: undefined,
}
},
outputs: {
message: { type: 'string', description: 'Operation status message' },
results: { type: 'array', description: 'Array of upserted records' },
},
}