diff --git a/apps/docs/content/docs/blocks/meta.json b/apps/docs/content/docs/blocks/meta.json index 374ed282c1..d2e8f50c01 100644 --- a/apps/docs/content/docs/blocks/meta.json +++ b/apps/docs/content/docs/blocks/meta.json @@ -4,12 +4,13 @@ "agent", "api", "condition", - "function", "evaluator", - "router", - "response", - "workflow", + "function", "loop", - "parallel" + "parallel", + "response", + "router", + "webhook_trigger", + "workflow" ] } diff --git a/apps/docs/content/docs/blocks/webhook_trigger.mdx b/apps/docs/content/docs/blocks/webhook_trigger.mdx new file mode 100644 index 0000000000..08f9922e51 --- /dev/null +++ b/apps/docs/content/docs/blocks/webhook_trigger.mdx @@ -0,0 +1,113 @@ +--- +title: Webhook Trigger +description: Trigger workflow execution from external webhooks +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Step, Steps } from 'fumadocs-ui/components/steps' +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' +import { Card, Cards } from 'fumadocs-ui/components/card' +import { ThemeImage } from '@/components/ui/theme-image' + +The Webhook Trigger block allows external services to trigger your workflow execution through HTTP webhooks. Unlike starter blocks, webhook triggers are pure input sources that start workflows without requiring manual intervention. + + + + + Webhook triggers cannot receive incoming connections and do not expose webhook data to the workflow. They serve as pure execution triggers. + + +## Overview + +The Webhook Trigger block enables you to: + + + + Receive external triggers: Accept HTTP requests from external services + + + Support multiple providers: Handle webhooks from Slack, Gmail, GitHub, and more + + + Start workflows automatically: Execute workflows without manual intervention + + + Provide secure endpoints: Generate unique webhook URLs for each trigger + + + +## How It Works + +The Webhook Trigger block operates as a pure input source: + +1. **Generate Endpoint** - Creates a unique webhook URL when configured +2. **Receive Request** - Accepts HTTP POST requests from external services +3. **Trigger Execution** - Starts the workflow when a valid request is received + +## Configuration Options + +### Webhook Provider + +Choose from supported service providers: + + + + Receive events from Slack apps and bots + + + Handle email-based triggers and notifications + + + Respond to database changes + + + Process bot messages and updates + + + Handle messaging events + + + Process repository events and pull requests + + + Respond to Discord server events + + + Handle payment and subscription events + + + +### Generic Webhooks + +For custom integrations or services not listed above, use the **Generic** provider. This option accepts HTTP POST requests from any client and provides flexible authentication options: + +- **Optional Authentication** - Configure Bearer token or custom header authentication +- **IP Restrictions** - Limit access to specific IP addresses +- **Request Deduplication** - Automatic duplicate request detection using content hashing +- **Flexible Headers** - Support for custom authentication header names + +The Generic provider is ideal for internal services, custom applications, or third-party tools that need to trigger workflows via standard HTTP requests. + +### Webhook Configuration + +Configure provider-specific settings: + +- **Webhook URL** - Automatically generated unique endpoint +- **Provider Settings** - Authentication and validation options +- **Security** - Built-in rate limiting and provider-specific authentication + +## Best Practices + +- **Use unique webhook URLs** for each integration to maintain security +- **Configure proper authentication** when supported by the provider +- **Keep workflows independent** of webhook payload structure +- **Test webhook endpoints** before deploying to production +- **Monitor webhook delivery** through provider dashboards + + diff --git a/apps/docs/public/static/dark/webhooktrigger-dark.png b/apps/docs/public/static/dark/webhooktrigger-dark.png new file mode 100644 index 0000000000..a8cceb5f3c Binary files /dev/null and b/apps/docs/public/static/dark/webhooktrigger-dark.png differ diff --git a/apps/docs/public/static/light/webhooktrigger-light.png b/apps/docs/public/static/light/webhooktrigger-light.png new file mode 100644 index 0000000000..edd269a5ec Binary files /dev/null and b/apps/docs/public/static/light/webhooktrigger-light.png differ diff --git a/apps/sim/app/api/schedules/[id]/route.ts b/apps/sim/app/api/schedules/[id]/route.ts index a8f1ff83ef..04a39a8663 100644 --- a/apps/sim/app/api/schedules/[id]/route.ts +++ b/apps/sim/app/api/schedules/[id]/route.ts @@ -141,6 +141,29 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ }) } + if (action === 'disable' || (body.status && body.status === 'disabled')) { + if (schedule.status === 'disabled') { + return NextResponse.json({ message: 'Schedule is already disabled' }, { status: 200 }) + } + + const now = new Date() + + await db + .update(workflowSchedule) + .set({ + status: 'disabled', + updatedAt: now, + nextRunAt: null, // Clear next run time when disabled + }) + .where(eq(workflowSchedule.id, scheduleId)) + + logger.info(`[${requestId}] Disabled schedule: ${scheduleId}`) + + return NextResponse.json({ + message: 'Schedule disabled successfully', + }) + } + logger.warn(`[${requestId}] Unsupported update action for schedule: ${scheduleId}`) return NextResponse.json({ error: 'Unsupported update action' }, { status: 400 }) } catch (error) { diff --git a/apps/sim/app/api/schedules/execute/route.ts b/apps/sim/app/api/schedules/execute/route.ts index 5729b225e4..65845a5e2c 100644 --- a/apps/sim/app/api/schedules/execute/route.ts +++ b/apps/sim/app/api/schedules/execute/route.ts @@ -46,10 +46,13 @@ function calculateNextRunTime( schedule: typeof workflowSchedule.$inferSelect, blocks: Record ): Date { - const starterBlock = Object.values(blocks).find((block) => block.type === 'starter') - if (!starterBlock) throw new Error('No starter block found') - const scheduleType = getSubBlockValue(starterBlock, 'scheduleType') - const scheduleValues = getScheduleTimeValues(starterBlock) + // Look for either starter block or schedule trigger block + const scheduleBlock = Object.values(blocks).find( + (block) => block.type === 'starter' || block.type === 'schedule' + ) + if (!scheduleBlock) throw new Error('No starter or schedule block found') + const scheduleType = getSubBlockValue(scheduleBlock, 'scheduleType') + const scheduleValues = getScheduleTimeValues(scheduleBlock) if (schedule.cronExpression) { const cron = new Cron(schedule.cronExpression) @@ -401,7 +404,10 @@ export async function GET() { // Set up enhanced logging on the executor loggingSession.setupExecutor(executor) - const result = await executor.execute(schedule.workflowId) + const result = await executor.execute( + schedule.workflowId, + schedule.blockId || undefined + ) const executionResult = 'stream' in result && 'execution' in result ? result.execution : result diff --git a/apps/sim/app/api/schedules/route.ts b/apps/sim/app/api/schedules/route.ts index 6c4e4d0128..c6746a0ff0 100644 --- a/apps/sim/app/api/schedules/route.ts +++ b/apps/sim/app/api/schedules/route.ts @@ -1,5 +1,5 @@ import crypto from 'crypto' -import { eq } from 'drizzle-orm' +import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -19,6 +19,7 @@ const logger = createLogger('ScheduledAPI') const ScheduleRequestSchema = z.object({ workflowId: z.string(), + blockId: z.string().optional(), state: z.object({ blocks: z.record(z.any()), edges: z.array(z.any()), @@ -66,6 +67,7 @@ export async function GET(req: NextRequest) { const requestId = crypto.randomUUID().slice(0, 8) const url = new URL(req.url) const workflowId = url.searchParams.get('workflowId') + const blockId = url.searchParams.get('blockId') const mode = url.searchParams.get('mode') if (mode && mode !== 'schedule') { @@ -92,10 +94,16 @@ export async function GET(req: NextRequest) { recentRequests.set(workflowId, now) } + // Build query conditions + const conditions = [eq(workflowSchedule.workflowId, workflowId)] + if (blockId) { + conditions.push(eq(workflowSchedule.blockId, blockId)) + } + const schedule = await db .select() .from(workflowSchedule) - .where(eq(workflowSchedule.workflowId, workflowId)) + .where(conditions.length > 1 ? and(...conditions) : conditions[0]) .limit(1) const headers = new Headers() @@ -138,36 +146,81 @@ export async function POST(req: NextRequest) { } const body = await req.json() - const { workflowId, state } = ScheduleRequestSchema.parse(body) + const { workflowId, blockId, state } = ScheduleRequestSchema.parse(body) logger.info(`[${requestId}] Processing schedule update for workflow ${workflowId}`) - const starterBlock = Object.values(state.blocks).find( - (block: any) => block.type === 'starter' - ) as BlockState | undefined - - if (!starterBlock) { - logger.warn(`[${requestId}] No starter block found in workflow ${workflowId}`) - return NextResponse.json({ error: 'No starter block found in workflow' }, { status: 400 }) + // Find the target block - prioritize the specific blockId if provided + let targetBlock: BlockState | undefined + if (blockId) { + // If blockId is provided, find that specific block + targetBlock = Object.values(state.blocks).find((block: any) => block.id === blockId) as + | BlockState + | undefined + } else { + // Fallback: find either starter block or schedule trigger block + targetBlock = Object.values(state.blocks).find( + (block: any) => block.type === 'starter' || block.type === 'schedule' + ) as BlockState | undefined } - const startWorkflow = getSubBlockValue(starterBlock, 'startWorkflow') - const scheduleType = getSubBlockValue(starterBlock, 'scheduleType') + if (!targetBlock) { + logger.warn(`[${requestId}] No starter or schedule block found in workflow ${workflowId}`) + return NextResponse.json( + { error: 'No starter or schedule block found in workflow' }, + { status: 400 } + ) + } - const scheduleValues = getScheduleTimeValues(starterBlock) + const startWorkflow = getSubBlockValue(targetBlock, 'startWorkflow') + const scheduleType = getSubBlockValue(targetBlock, 'scheduleType') - const hasScheduleConfig = hasValidScheduleConfig(scheduleType, scheduleValues, starterBlock) + const scheduleValues = getScheduleTimeValues(targetBlock) - if (startWorkflow !== 'schedule' && !hasScheduleConfig) { + const hasScheduleConfig = hasValidScheduleConfig(scheduleType, scheduleValues, targetBlock) + + // For schedule trigger blocks, we always have valid configuration + // For starter blocks, check if schedule is selected and has valid config + const isScheduleBlock = targetBlock.type === 'schedule' + const hasValidConfig = isScheduleBlock || (startWorkflow === 'schedule' && hasScheduleConfig) + + // Debug logging to understand why validation fails + logger.info(`[${requestId}] Schedule validation debug:`, { + workflowId, + blockId, + blockType: targetBlock.type, + isScheduleBlock, + startWorkflow, + scheduleType, + hasScheduleConfig, + hasValidConfig, + scheduleValues: { + minutesInterval: scheduleValues.minutesInterval, + dailyTime: scheduleValues.dailyTime, + cronExpression: scheduleValues.cronExpression, + }, + }) + + if (!hasValidConfig) { logger.info( `[${requestId}] Removing schedule for workflow ${workflowId} - no valid configuration found` ) - await db.delete(workflowSchedule).where(eq(workflowSchedule.workflowId, workflowId)) + // Build delete conditions + const deleteConditions = [eq(workflowSchedule.workflowId, workflowId)] + if (blockId) { + deleteConditions.push(eq(workflowSchedule.blockId, blockId)) + } + + await db + .delete(workflowSchedule) + .where(deleteConditions.length > 1 ? and(...deleteConditions) : deleteConditions[0]) return NextResponse.json({ message: 'Schedule removed' }) } - if (startWorkflow !== 'schedule') { + if (isScheduleBlock) { + logger.info(`[${requestId}] Processing schedule trigger block for workflow ${workflowId}`) + } else if (startWorkflow !== 'schedule') { logger.info( `[${requestId}] Setting workflow to scheduled mode based on schedule configuration` ) @@ -177,12 +230,12 @@ export async function POST(req: NextRequest) { let cronExpression: string | null = null let nextRunAt: Date | undefined - const timezone = getSubBlockValue(starterBlock, 'timezone') || 'UTC' + const timezone = getSubBlockValue(targetBlock, 'timezone') || 'UTC' try { const defaultScheduleType = scheduleType || 'daily' - const scheduleStartAt = getSubBlockValue(starterBlock, 'scheduleStartAt') - const scheduleTime = getSubBlockValue(starterBlock, 'scheduleTime') + const scheduleStartAt = getSubBlockValue(targetBlock, 'scheduleStartAt') + const scheduleTime = getSubBlockValue(targetBlock, 'scheduleTime') logger.debug(`[${requestId}] Schedule configuration:`, { type: defaultScheduleType, @@ -218,6 +271,7 @@ export async function POST(req: NextRequest) { const values = { id: crypto.randomUUID(), workflowId, + blockId, cronExpression, triggerType: 'schedule', createdAt: new Date(), @@ -229,6 +283,7 @@ export async function POST(req: NextRequest) { } const setValues = { + blockId, cronExpression, updatedAt: new Date(), nextRunAt, @@ -241,7 +296,7 @@ export async function POST(req: NextRequest) { .insert(workflowSchedule) .values(values) .onConflictDoUpdate({ - target: [workflowSchedule.workflowId], + target: [workflowSchedule.workflowId, workflowSchedule.blockId], set: setValues, }) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 3f86741d52..fabf9ed3fe 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -26,15 +26,30 @@ export async function GET(request: NextRequest) { // Get query parameters const { searchParams } = new URL(request.url) const workflowId = searchParams.get('workflowId') + const blockId = searchParams.get('blockId') + + if (workflowId && !blockId) { + // For now, allow the call but return empty results to avoid breaking the UI + return NextResponse.json({ webhooks: [] }, { status: 200 }) + } logger.debug(`[${requestId}] Fetching webhooks for user ${session.user.id}`, { filteredByWorkflow: !!workflowId, + filteredByBlock: !!blockId, }) // Create where condition - const whereCondition = workflowId - ? and(eq(workflow.userId, session.user.id), eq(webhook.workflowId, workflowId)) - : eq(workflow.userId, session.user.id) + const conditions = [eq(workflow.userId, session.user.id)] + + if (workflowId) { + conditions.push(eq(webhook.workflowId, workflowId)) + } + + if (blockId) { + conditions.push(eq(webhook.blockId, blockId)) + } + + const whereCondition = conditions.length > 1 ? and(...conditions) : conditions[0] const webhooks = await db .select({ @@ -68,7 +83,7 @@ export async function POST(request: NextRequest) { try { const body = await request.json() - const { workflowId, path, provider, providerConfig } = body + const { workflowId, path, provider, providerConfig, blockId } = body // Validate input if (!workflowId || !path) { @@ -115,6 +130,7 @@ export async function POST(request: NextRequest) { const updatedResult = await db .update(webhook) .set({ + blockId, provider, providerConfig, isActive: true, @@ -132,6 +148,7 @@ export async function POST(request: NextRequest) { .values({ id: webhookId, workflowId, + blockId, path, provider, providerConfig, diff --git a/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts b/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts index 4803912dc9..c66e9930a3 100644 --- a/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts +++ b/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts @@ -64,7 +64,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ isDeployed: workflowData.isDeployed, deployedAt: workflowData.deployedAt, deploymentStatuses: deployedState.deploymentStatuses || {}, - hasActiveSchedule: deployedState.hasActiveSchedule || false, hasActiveWebhook: deployedState.hasActiveWebhook || false, }) diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index 08d87b6356..d7534f41f3 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -119,7 +119,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ finalWorkflowData.state = { // Default values for expected properties deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, // Preserve any existing state properties ...existingState, diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index b66959219b..8a62c00b61 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -103,7 +103,6 @@ const WorkflowStateSchema = z.object({ isDeployed: z.boolean().optional(), deployedAt: z.date().optional(), deploymentStatuses: z.record(DeploymentStatusSchema).optional(), - hasActiveSchedule: z.boolean().optional(), hasActiveWebhook: z.boolean().optional(), }) @@ -180,7 +179,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ isDeployed: state.isDeployed || false, deployedAt: state.deployedAt, deploymentStatuses: state.deploymentStatuses || {}, - hasActiveSchedule: state.hasActiveSchedule || false, hasActiveWebhook: state.hasActiveWebhook || false, } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx index 8be4f04c64..b23445907a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx @@ -1,18 +1,19 @@ -import { useEffect, useMemo, useState } from 'react' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' +import { useEffect, useMemo, useRef, useState } from 'react' +import { Check, ChevronDown } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { cn } from '@/lib/utils' import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler' import { useSubBlockValue } from '../hooks/use-sub-block-value' interface DropdownProps { options: - | Array - | (() => Array) + | Array< + string | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> } + > + | (() => Array< + string | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> } + >) defaultValue?: string blockId: string subBlockId: string @@ -20,6 +21,7 @@ interface DropdownProps { isPreview?: boolean previewValue?: string | null disabled?: boolean + placeholder?: string } export function Dropdown({ @@ -31,9 +33,15 @@ export function Dropdown({ isPreview = false, previewValue, disabled, + placeholder = 'Select an option...', }: DropdownProps) { const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) const [storeInitialized, setStoreInitialized] = useState(false) + const [open, setOpen] = useState(false) + const [highlightedIndex, setHighlightedIndex] = useState(-1) + + const inputRef = useRef(null) + const dropdownRef = useRef(null) // For response dataMode conversion - get builderData and data sub-blocks const [builderData] = useSubBlockValue(blockId, 'builderData') @@ -47,11 +55,19 @@ export function Dropdown({ return typeof options === 'function' ? options() : options }, [options]) - const getOptionValue = (option: string | { label: string; id: string }) => { + const getOptionValue = ( + option: + | string + | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> } + ) => { return typeof option === 'string' ? option : option.id } - const getOptionLabel = (option: string | { label: string; id: string }) => { + const getOptionLabel = ( + option: + | string + | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> } + ) => { return typeof option === 'string' ? option : option.label } @@ -85,67 +101,234 @@ export function Dropdown({ } }, [storeInitialized, value, defaultOptionValue, setStoreValue]) - // Calculate the effective value to use in the dropdown - const effectiveValue = useMemo(() => { - // If we have a value from the store, use that - if (value !== null && value !== undefined) { - return value + // Event handlers + const handleSelect = (selectedValue: string) => { + if (!isPreview && !disabled) { + // Handle conversion when switching from Builder to Editor mode in response blocks + if ( + subBlockId === 'dataMode' && + storeValue === 'structured' && + selectedValue === 'json' && + builderData && + Array.isArray(builderData) && + builderData.length > 0 + ) { + // Convert builderData to JSON string for editor mode + const jsonString = ResponseBlockHandler.convertBuilderDataToJsonString(builderData) + setData(jsonString) + } + + setStoreValue(selectedValue) + } + setOpen(false) + setHighlightedIndex(-1) + inputRef.current?.blur() + } + + const handleDropdownClick = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + if (!disabled) { + setOpen(!open) + if (!open) { + inputRef.current?.focus() + } + } + } + + const handleFocus = () => { + setOpen(true) + setHighlightedIndex(-1) + } + + const handleBlur = () => { + // Delay closing to allow dropdown selection + setTimeout(() => { + const activeElement = document.activeElement + if (!activeElement || !activeElement.closest('.absolute.top-full')) { + setOpen(false) + setHighlightedIndex(-1) + } + }, 150) + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + setOpen(false) + setHighlightedIndex(-1) + return } - // Only return defaultOptionValue if store is initialized - if (storeInitialized) { - return defaultOptionValue + if (e.key === 'ArrowDown') { + e.preventDefault() + if (!open) { + setOpen(true) + setHighlightedIndex(0) + } else { + setHighlightedIndex((prev) => (prev < evaluatedOptions.length - 1 ? prev + 1 : 0)) + } } - // While store is loading, don't use any value - return undefined - }, [value, defaultOptionValue, storeInitialized]) + if (e.key === 'ArrowUp') { + e.preventDefault() + if (open) { + setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : evaluatedOptions.length - 1)) + } + } - // Handle the case where evaluatedOptions changes and the current selection is no longer valid - const isValueInOptions = useMemo(() => { - if (!effectiveValue || evaluatedOptions.length === 0) return false - return evaluatedOptions.some((opt) => getOptionValue(opt) === effectiveValue) - }, [effectiveValue, evaluatedOptions, getOptionValue]) + if (e.key === 'Enter' && open && highlightedIndex >= 0) { + e.preventDefault() + const selectedOption = evaluatedOptions[highlightedIndex] + if (selectedOption) { + handleSelect(getOptionValue(selectedOption)) + } + } + } + // Effects + useEffect(() => { + setHighlightedIndex((prev) => { + if (prev >= 0 && prev < evaluatedOptions.length) { + return prev + } + return -1 + }) + }, [evaluatedOptions]) + + // Scroll highlighted option into view + useEffect(() => { + if (highlightedIndex >= 0 && dropdownRef.current) { + const highlightedElement = dropdownRef.current.querySelector( + `[data-option-index="${highlightedIndex}"]` + ) + if (highlightedElement) { + highlightedElement.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }) + } + } + }, [highlightedIndex]) + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as Element + if ( + inputRef.current && + !inputRef.current.contains(target) && + !target.closest('.absolute.top-full') + ) { + setOpen(false) + setHighlightedIndex(-1) + } + } + + if (open) { + document.addEventListener('mousedown', handleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + } + }, [open]) + + // Display value + const displayValue = value?.toString() ?? '' + const selectedOption = evaluatedOptions.find((opt) => getOptionValue(opt) === value) + const selectedLabel = selectedOption ? getOptionLabel(selectedOption) : displayValue + const SelectedIcon = + selectedOption && typeof selectedOption === 'object' && 'icon' in selectedOption + ? (selectedOption.icon as React.ComponentType<{ className?: string }>) + : null + + // Render component return ( - + {/* Icon overlay */} + {SelectedIcon && ( +
+ +
+ )} + {/* Chevron button */} + + - setStoreValue(newValue) - } - }} - disabled={isPreview || disabled} - > - - - - - {evaluatedOptions.map((option) => ( - - {getOptionLabel(option)} - - ))} - - + {/* Dropdown */} + {open && ( +
+
+
+ {evaluatedOptions.length === 0 ? ( +
+ No options available. +
+ ) : ( + evaluatedOptions.map((option, index) => { + const optionValue = getOptionValue(option) + const optionLabel = getOptionLabel(option) + const OptionIcon = + typeof option === 'object' && 'icon' in option + ? (option.icon as React.ComponentType<{ className?: string }>) + : null + const isSelected = value === optionValue + const isHighlighted = index === highlightedIndex + + return ( +
handleSelect(optionValue)} + onMouseDown={(e) => { + e.preventDefault() + handleSelect(optionValue) + }} + onMouseEnter={() => setHighlightedIndex(index)} + className={cn( + 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground', + isHighlighted && 'bg-accent text-accent-foreground' + )} + > + {OptionIcon && } + {optionLabel} + {isSelected && } +
+ ) + }) + )} +
+
+
+ )} + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx index c0f0a43c96..be4cd80dba 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react' -import { format } from 'date-fns' import { Trash2, X } from 'lucide-react' import { Alert, AlertDescription } from '@/components/ui/alert' import { @@ -13,10 +12,8 @@ import { AlertDialogTitle, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' -import { Calendar as CalendarComponent } from '@/components/ui/calendar' import { DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Select, SelectContent, @@ -54,8 +51,6 @@ export function ScheduleModal({ }: ScheduleModalProps) { // States for schedule configuration const [scheduleType, setScheduleType] = useSubBlockValue(blockId, 'scheduleType') - const [scheduleStartAt, setScheduleStartAt] = useSubBlockValue(blockId, 'scheduleStartAt') - const [scheduleTime, setScheduleTime] = useSubBlockValue(blockId, 'scheduleTime') const [minutesInterval, setMinutesInterval] = useSubBlockValue(blockId, 'minutesInterval') const [hourlyMinute, setHourlyMinute] = useSubBlockValue(blockId, 'hourlyMinute') const [dailyTime, setDailyTime] = useSubBlockValue(blockId, 'dailyTime') @@ -86,8 +81,6 @@ export function ScheduleModal({ // Capture all current values when modal opens const currentValues = { scheduleType: scheduleType || 'daily', - scheduleStartAt: scheduleStartAt || '', - scheduleTime: scheduleTime || '', minutesInterval: minutesInterval || '', hourlyMinute: hourlyMinute || '', dailyTime: dailyTime || '', @@ -111,8 +104,6 @@ export function ScheduleModal({ const currentValues = { scheduleType: scheduleType || 'daily', - scheduleStartAt: scheduleStartAt || '', - scheduleTime: scheduleTime || '', minutesInterval: minutesInterval || '', hourlyMinute: hourlyMinute || '', dailyTime: dailyTime || '', @@ -160,8 +151,6 @@ export function ScheduleModal({ isOpen, scheduleId, scheduleType, - scheduleStartAt, - scheduleTime, minutesInterval, hourlyMinute, dailyTime, @@ -188,8 +177,6 @@ export function ScheduleModal({ // Revert form values to initial values if (hasChanges) { setScheduleType(initialValues.scheduleType) - setScheduleStartAt(initialValues.scheduleStartAt) - setScheduleTime(initialValues.scheduleTime) setMinutesInterval(initialValues.minutesInterval) setHourlyMinute(initialValues.hourlyMinute) setDailyTime(initialValues.dailyTime) @@ -279,8 +266,6 @@ export function ScheduleModal({ // Update initial values to match current state const updatedValues = { scheduleType: scheduleType || 'daily', - scheduleStartAt: scheduleStartAt || '', - scheduleTime: scheduleTime || '', minutesInterval: minutesInterval || '', hourlyMinute: hourlyMinute || '', dailyTime: dailyTime || '', @@ -329,15 +314,6 @@ export function ScheduleModal({ setShowDeleteConfirm(true) } - // Helper to format a date for display - const formatDate = (date: string) => { - try { - return date ? format(new Date(date), 'PPP') : 'Select date' - } catch (_e) { - return 'Select date' - } - } - return ( <> @@ -359,46 +335,6 @@ export function ScheduleModal({ )}
- {/* Common date and time fields */} -
-
- - - - - - - setScheduleStartAt(date ? date.toISOString() : '')} - initialFocus - /> - - -
- -
- - -
-
- {/* Frequency selector */}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx index 65870360d8..33d9b1c192 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx @@ -6,7 +6,7 @@ import { Dialog } from '@/components/ui/dialog' import { createLogger } from '@/lib/logs/console-logger' import { parseCronToHumanReadable } from '@/lib/schedules/utils' import { formatDateTime } from '@/lib/utils' -import { getWorkflowWithValues } from '@/stores/workflows' +import { getBlockWithValues, getWorkflowWithValues } from '@/stores/workflows' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' @@ -49,7 +49,6 @@ export function ScheduleConfig({ const workflowId = params.workflowId as string // Get workflow state from store - const setScheduleStatus = useWorkflowStore((state) => state.setScheduleStatus) // Get the schedule type from the block state const [scheduleType] = useSubBlockValue(blockId, 'scheduleType') @@ -58,12 +57,25 @@ export function ScheduleConfig({ // and expose the setter so we can update it const [_startWorkflow, setStartWorkflow] = useSubBlockValue(blockId, 'startWorkflow') + // Determine if this is a schedule trigger block vs starter block + const blockWithValues = getBlockWithValues(blockId) + const isScheduleTriggerBlock = blockWithValues?.type === 'schedule' + // Function to check if schedule exists in the database const checkSchedule = async () => { setIsLoading(true) try { // Check if there's a schedule for this workflow, passing the mode parameter - const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, { + // For schedule trigger blocks, include blockId to get the specific schedule + const url = new URL('/api/schedules', window.location.origin) + url.searchParams.set('workflowId', workflowId) + url.searchParams.set('mode', 'schedule') + + if (isScheduleTriggerBlock) { + url.searchParams.set('blockId', blockId) + } + + const response = await fetch(url.toString(), { // Add cache: 'no-store' to prevent caching of this request cache: 'no-store', headers: { @@ -82,16 +94,15 @@ export function ScheduleConfig({ setCronExpression(data.schedule.cronExpression) setTimezone(data.schedule.timezone || 'UTC') - // Set active schedule flag to true since we found an active schedule - setScheduleStatus(true) + // Note: We no longer set global schedule status from individual components + // The global schedule status should be managed by a higher-level component } else { setScheduleId(null) setNextRunAt(null) setLastRanAt(null) setCronExpression(null) - // Set active schedule flag to false since no schedule was found - setScheduleStatus(false) + // Note: We no longer set global schedule status from individual components } } } catch (error) { @@ -104,9 +115,8 @@ export function ScheduleConfig({ // Check for schedule on mount and when relevant dependencies change useEffect(() => { - // Only check for schedules when workflowId changes or modal opens - // Avoid checking on every scheduleType change to prevent excessive API calls - if (workflowId && (isModalOpen || refreshCounter > 0)) { + // Check for schedules when workflowId changes, modal opens, or on initial mount + if (workflowId) { checkSchedule() } @@ -160,23 +170,33 @@ export function ScheduleConfig({ setError(null) try { - // 1. First, update the startWorkflow value in SubBlock store to 'schedule' - setStartWorkflow('schedule') + // For starter blocks, update the startWorkflow value to 'schedule' + // For schedule trigger blocks, skip this step as startWorkflow is not needed + if (!isScheduleTriggerBlock) { + // 1. First, update the startWorkflow value in SubBlock store to 'schedule' + setStartWorkflow('schedule') + + // 2. Directly access and modify the SubBlock store to guarantee the value is set + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) { + setError('No active workflow found') + return false + } + + // Update the SubBlock store directly to ensure the value is set correctly + const subBlockStore = useSubBlockStore.getState() + subBlockStore.setValue(blockId, 'startWorkflow', 'schedule') + + // Give React time to process the state update + await new Promise((resolve) => setTimeout(resolve, 200)) + } - // 2. Directly access and modify the SubBlock store to guarantee the value is set const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId if (!activeWorkflowId) { setError('No active workflow found') return false } - // Update the SubBlock store directly to ensure the value is set correctly - const subBlockStore = useSubBlockStore.getState() - subBlockStore.setValue(blockId, 'startWorkflow', 'schedule') - - // Give React time to process the state update - await new Promise((resolve) => setTimeout(resolve, 200)) - // 3. Get the fully merged current state with updated values // This ensures we send the complete, correct workflow state to the backend const currentWorkflowWithValues = getWorkflowWithValues(activeWorkflowId) @@ -188,15 +208,24 @@ export function ScheduleConfig({ // 4. Make a direct API call instead of relying on sync // This gives us more control and better error handling logger.debug('Making direct API call to save schedule with complete state') + + // Prepare the request body + const requestBody: any = { + workflowId, + state: currentWorkflowWithValues.state, + } + + // For schedule trigger blocks, include the blockId + if (isScheduleTriggerBlock) { + requestBody.blockId = blockId + } + const response = await fetch('/api/schedules', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - workflowId, - state: currentWorkflowWithValues.state, - }), + body: JSON.stringify(requestBody), }) // Parse the response @@ -230,7 +259,7 @@ export function ScheduleConfig({ } // 6. Update the schedule status and trigger a workflow update - setScheduleStatus(true) + // Note: Global schedule status is managed at a higher level // 7. Tell the workflow store that the state has been saved const workflowStore = useWorkflowStore.getState() @@ -262,25 +291,29 @@ export function ScheduleConfig({ setIsDeleting(true) try { - // 1. First update the workflow state to disable scheduling - setStartWorkflow('manual') + // For starter blocks, update the startWorkflow value to 'manual' + // For schedule trigger blocks, skip this step as startWorkflow is not relevant + if (!isScheduleTriggerBlock) { + // 1. First update the workflow state to disable scheduling + setStartWorkflow('manual') - // 2. Directly update the SubBlock store to ensure the value is set - const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId - if (!activeWorkflowId) { - setError('No active workflow found') - return false + // 2. Directly update the SubBlock store to ensure the value is set + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) { + setError('No active workflow found') + return false + } + + // Update the store directly + const subBlockStore = useSubBlockStore.getState() + subBlockStore.setValue(blockId, 'startWorkflow', 'manual') + + // 3. Update the workflow store + const workflowStore = useWorkflowStore.getState() + workflowStore.triggerUpdate() + workflowStore.updateLastSaved() } - // Update the store directly - const subBlockStore = useSubBlockStore.getState() - subBlockStore.setValue(blockId, 'startWorkflow', 'manual') - - // 3. Update the workflow store - const workflowStore = useWorkflowStore.getState() - workflowStore.triggerUpdate() - workflowStore.updateLastSaved() - // 4. Make the DELETE API call to remove the schedule const response = await fetch(`/api/schedules/${scheduleId}`, { method: 'DELETE', @@ -299,7 +332,7 @@ export function ScheduleConfig({ setCronExpression(null) // 6. Update schedule status and refresh UI - setScheduleStatus(false) + // Note: Global schedule status is managed at a higher level setRefreshCounter((prev) => prev + 1) return true diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx index 825db4c761..2de92c4a9b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx @@ -14,7 +14,6 @@ import { import { Button } from '@/components/ui/button' import { createLogger } from '@/lib/logs/console-logger' import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { useSubBlockValue } from '../../hooks/use-sub-block-value' import { ToolCredentialSelector } from '../tool-input/components/tool-credential-selector' import { WebhookModal } from './components/webhook-modal' @@ -314,8 +313,7 @@ export function WebhookConfig({ const [isLoading, setIsLoading] = useState(false) const [gmailCredentialId, setGmailCredentialId] = useState('') - // Get workflow store function to update webhook status - const setWebhookStatus = useWorkflowStore((state) => state.setWebhookStatus) + // No need to manage webhook status separately - it's determined by having provider + path // Get the webhook provider from the block state const [storeWebhookProvider, setWebhookProvider] = useSubBlockValue(blockId, 'webhookProvider') @@ -323,6 +321,9 @@ export function WebhookConfig({ // Store the webhook path const [storeWebhookPath, setWebhookPath] = useSubBlockValue(blockId, 'webhookPath') + // Don't auto-generate webhook paths - only create them when user actually configures a webhook + // This prevents the "Active Webhook" badge from showing on unconfigured blocks + // Store provider-specific configuration const [storeProviderConfig, setProviderConfig] = useSubBlockValue(blockId, 'providerConfig') @@ -331,16 +332,132 @@ export function WebhookConfig({ const webhookPath = propValue?.webhookPath ?? storeWebhookPath const providerConfig = propValue?.providerConfig ?? storeProviderConfig - // Reset provider config when provider changes + // Store the actual provider from the database + const [actualProvider, setActualProvider] = useState(null) + + // Track the previous provider to detect changes + const [previousProvider, setPreviousProvider] = useState(null) + + // Handle provider changes - clear webhook data when switching providers + useEffect(() => { + // Skip on initial load or if no provider is set + if (!webhookProvider || !previousProvider) { + setPreviousProvider(webhookProvider) + return + } + + // If the provider has changed, clear all webhook-related data + if (webhookProvider !== previousProvider) { + // IMPORTANT: Store the current webhook ID BEFORE clearing it + const currentWebhookId = webhookId + + logger.info('Webhook provider changed, clearing webhook data', { + from: previousProvider, + to: webhookProvider, + blockId, + webhookId: currentWebhookId, + }) + + // If there's an existing webhook, delete it from the database + const deleteExistingWebhook = async () => { + if (currentWebhookId && !isPreview) { + try { + logger.info('Deleting existing webhook due to provider change', { + webhookId: currentWebhookId, + oldProvider: previousProvider, + newProvider: webhookProvider, + }) + + const response = await fetch(`/api/webhooks/${currentWebhookId}`, { + method: 'DELETE', + }) + + if (!response.ok) { + const errorData = await response.json() + logger.error('Failed to delete existing webhook', { + webhookId: currentWebhookId, + error: errorData.error, + }) + } else { + logger.info('Successfully deleted existing webhook', { webhookId: currentWebhookId }) + + const store = useSubBlockStore.getState() + const workflowValues = store.workflowValues[workflowId] || {} + const blockValues = { ...workflowValues[blockId] } + + // Clear webhook-related fields + blockValues.webhookPath = undefined + blockValues.providerConfig = undefined + + // Update the store with the cleaned block values + useSubBlockStore.setState({ + workflowValues: { + ...workflowValues, + [workflowId]: { + ...workflowValues, + [blockId]: blockValues, + }, + }, + }) + + logger.info('Cleared webhook data from store after successful deletion', { blockId }) + } + } catch (error: any) { + logger.error('Error deleting existing webhook', { + webhookId: currentWebhookId, + error: error.message, + }) + } + } + } + + // Clear webhook fields FIRST to make badge disappear immediately + // Then delete from database to prevent the webhook check useEffect from restoring the path + + // IMPORTANT: Clear webhook connection data FIRST + // This prevents the webhook check useEffect from finding and restoring the webhook + setWebhookId(null) + setActualProvider(null) + + // Clear provider config + setProviderConfig({}) + + // Clear component state + setError(null) + setGmailCredentialId('') + + // Note: Store will be cleared AFTER successful database deletion + // This ensures store and database stay perfectly in sync + + // Update previous provider to the new provider + setPreviousProvider(webhookProvider) + + // Delete existing webhook AFTER clearing the path to prevent race condition + // The webhook check useEffect won't restore the path if we clear it first + // Execute deletion asynchronously but don't block the UI + + ;(async () => { + await deleteExistingWebhook() + })() + } + }, [webhookProvider, previousProvider, blockId, webhookId, isPreview]) + + // Reset provider config when provider changes (legacy effect - keeping for safety) useEffect(() => { if (webhookProvider) { // Reset the provider config when the provider changes setProviderConfig({}) - } - }, [webhookProvider, setProviderConfig]) - // Store the actual provider from the database - const [actualProvider, setActualProvider] = useState(null) + // Clear webhook ID and actual provider when switching providers + // This ensures the webhook status is properly reset + if (webhookProvider !== actualProvider) { + setWebhookId(null) + setActualProvider(null) + } + + // Provider config is reset - webhook status will be determined by provider + path existence + } + }, [webhookProvider, webhookId, actualProvider]) // Check if webhook exists in the database useEffect(() => { @@ -353,18 +470,17 @@ export function WebhookConfig({ const checkWebhook = async () => { setIsLoading(true) try { - // Check if there's a webhook for this workflow - const response = await fetch(`/api/webhooks?workflowId=${workflowId}`) + // Check if there's a webhook for this specific block + // Always include blockId - every webhook should be associated with a specific block + const response = await fetch(`/api/webhooks?workflowId=${workflowId}&blockId=${blockId}`) if (response.ok) { const data = await response.json() if (data.webhooks && data.webhooks.length > 0) { const webhook = data.webhooks[0].webhook setWebhookId(webhook.id) - // Update the provider in the block state if it's different - if (webhook.provider && webhook.provider !== webhookProvider) { - setWebhookProvider(webhook.provider) - } + // Don't automatically update the provider - let user control it + // The user should be able to change providers even when a webhook exists // Store the actual provider from the database setActualProvider(webhook.provider) @@ -374,14 +490,22 @@ export function WebhookConfig({ setWebhookPath(webhook.path) } - // Set active webhook flag to true since we found an active webhook - setWebhookStatus(true) + // Webhook found - status will be determined by provider + path existence } else { setWebhookId(null) setActualProvider(null) - // Set active webhook flag to false since no webhook was found - setWebhookStatus(false) + // IMPORTANT: Clear stale webhook data from store when no webhook found in database + // This ensures the reactive badge status updates correctly on page refresh + if (webhookPath) { + setWebhookPath('') + logger.info('Cleared stale webhook path on page refresh - no webhook in database', { + blockId, + clearedPath: webhookPath, + }) + } + + // No webhook found - reactive blockWebhookStatus will now be false } } } catch (error) { @@ -392,15 +516,7 @@ export function WebhookConfig({ } checkWebhook() - }, [ - webhookPath, - webhookProvider, - workflowId, - setWebhookPath, - setWebhookProvider, - setWebhookStatus, - isPreview, - ]) + }, [workflowId, blockId, isPreview]) // Removed webhookPath dependency to prevent race condition with provider changes const handleOpenModal = () => { if (isPreview || disabled) return @@ -443,6 +559,7 @@ export function WebhookConfig({ }, body: JSON.stringify({ workflowId, + blockId, path, provider: webhookProvider || 'generic', providerConfig: finalConfig, @@ -459,13 +576,20 @@ export function WebhookConfig({ } const data = await response.json() - setWebhookId(data.webhook.id) + const savedWebhookId = data.webhook.id + setWebhookId(savedWebhookId) + + logger.info('Webhook saved successfully', { + webhookId: savedWebhookId, + provider: webhookProvider, + path, + blockId, + }) // Update the actual provider after saving setActualProvider(webhookProvider || 'generic') - // Set active webhook flag to true after successfully saving - setWebhookStatus(true) + // Webhook saved successfully - status will be determined by provider + path existence return true } catch (error: any) { @@ -504,7 +628,7 @@ export function WebhookConfig({ // Remove webhook-related fields blockValues.webhookProvider = undefined blockValues.providerConfig = undefined - blockValues.webhookPath = '' + blockValues.webhookPath = undefined // Update the store with the cleaned block values store.setValue(blockId, 'startWorkflow', 'manual') @@ -522,8 +646,7 @@ export function WebhookConfig({ setWebhookId(null) setActualProvider(null) - // Set active webhook flag to false - setWebhookStatus(false) + // Webhook deleted - status will be determined by provider + path existence handleCloseModal() return true diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 2a3cd42cb5..c6d9890e6d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { parseCronToHumanReadable } from '@/lib/schedules/utils' -import { cn, formatDateTime, validateName } from '@/lib/utils' +import { cn, validateName } from '@/lib/utils' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider' import type { BlockConfig, SubBlockConfig } from '@/blocks/types' import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' import { useExecutionStore } from '@/stores/execution/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { ActionBar } from './components/action-bar/action-bar' @@ -67,7 +68,17 @@ export function WorkflowBlock({ id, data }: NodeProps) { ) const isWide = useWorkflowStore((state) => state.blocks[id]?.isWide ?? false) const blockHeight = useWorkflowStore((state) => state.blocks[id]?.height ?? 0) - const hasActiveWebhook = useWorkflowStore((state) => state.hasActiveWebhook ?? false) + // Get per-block webhook status by checking if webhook is configured + const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId) + + const hasWebhookProvider = useSubBlockStore( + (state) => state.workflowValues[activeWorkflowId || '']?.[id]?.webhookProvider + ) + const hasWebhookPath = useSubBlockStore( + (state) => state.workflowValues[activeWorkflowId || '']?.[id]?.webhookPath + ) + const blockWebhookStatus = !!(hasWebhookProvider && hasWebhookPath) + const blockAdvancedMode = useWorkflowStore((state) => state.blocks[id]?.advancedMode ?? false) // Collaborative workflow actions @@ -89,6 +100,11 @@ export function WorkflowBlock({ id, data }: NodeProps) { const params = useParams() const currentWorkflowId = params.workflowId as string + // Check if this is a starter block or trigger block + const isStarterBlock = type === 'starter' + const isTriggerBlock = config.category === 'triggers' + const isWebhookTriggerBlock = type === 'webhook' + const reactivateSchedule = async (scheduleId: string) => { try { const response = await fetch(`/api/schedules/${scheduleId}`, { @@ -112,13 +128,42 @@ export function WorkflowBlock({ id, data }: NodeProps) { } } + const disableSchedule = async (scheduleId: string) => { + try { + const response = await fetch(`/api/schedules/${scheduleId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action: 'disable' }), + }) + + if (response.ok) { + // Refresh schedule info to show updated status + if (currentWorkflowId) { + fetchScheduleInfo(currentWorkflowId) + } + } else { + console.error('Failed to disable schedule') + } + } catch (error) { + console.error('Error disabling schedule:', error) + } + } + const fetchScheduleInfo = async (workflowId: string) => { if (!workflowId) return try { setIsLoadingScheduleInfo(true) - const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, { + // For schedule trigger blocks, always include the blockId parameter + const url = new URL('/api/schedules', window.location.origin) + url.searchParams.set('workflowId', workflowId) + url.searchParams.set('mode', 'schedule') + url.searchParams.set('blockId', id) // Always include blockId for schedule blocks + + const response = await fetch(url.toString(), { cache: 'no-store', headers: { 'Cache-Control': 'no-cache', @@ -185,48 +230,25 @@ export function WorkflowBlock({ id, data }: NodeProps) { } useEffect(() => { - if (type === 'starter' && currentWorkflowId) { + if (type === 'schedule' && currentWorkflowId) { fetchScheduleInfo(currentWorkflowId) } else { setScheduleInfo(null) - setIsLoadingScheduleInfo(false) // Reset loading state when not a starter block + setIsLoadingScheduleInfo(false) // Reset loading state when not a schedule block } // Cleanup function to reset loading state when component unmounts or workflow changes return () => { setIsLoadingScheduleInfo(false) } - }, [type, currentWorkflowId]) + }, [isStarterBlock, isTriggerBlock, type, currentWorkflowId, lastUpdate]) // Get webhook information for the tooltip useEffect(() => { - if (type === 'starter' && hasActiveWebhook) { - const fetchWebhookInfo = async () => { - try { - const workflowId = useWorkflowRegistry.getState().activeWorkflowId - if (!workflowId) return - - const response = await fetch(`/api/webhooks?workflowId=${workflowId}`) - if (response.ok) { - const data = await response.json() - if (data.webhooks?.[0]?.webhook) { - const webhook = data.webhooks[0].webhook - setWebhookInfo({ - webhookPath: webhook.path || '', - provider: webhook.provider || 'generic', - }) - } - } - } catch (error) { - console.error('Error fetching webhook info:', error) - } - } - - fetchWebhookInfo() - } else if (!hasActiveWebhook) { + if (!blockWebhookStatus) { setWebhookInfo(null) } - }, [type, hasActiveWebhook]) + }, [blockWebhookStatus]) // Update node internals when handles change useEffect(() => { @@ -404,9 +426,8 @@ export function WorkflowBlock({ id, data }: NodeProps) { } } - // Check if this is a starter block and has active schedule or webhook - const isStarterBlock = type === 'starter' - const showWebhookIndicator = isStarterBlock && hasActiveWebhook + // Check webhook indicator + const showWebhookIndicator = (isStarterBlock || isWebhookTriggerBlock) && blockWebhookStatus const getProviderName = (providerId: string): string => { const providers: Record = { @@ -422,7 +443,8 @@ export function WorkflowBlock({ id, data }: NodeProps) { return providers[providerId] || 'Webhook' } - const shouldShowScheduleBadge = isStarterBlock && !isLoadingScheduleInfo && scheduleInfo !== null + const shouldShowScheduleBadge = + type === 'schedule' && !isLoadingScheduleInfo && scheduleInfo !== null const userPermissions = useUserPermissionsContext() return ( @@ -447,15 +469,18 @@ export function WorkflowBlock({ id, data }: NodeProps) { )} - + {/* Connection Blocks - Don't show for trigger blocks or starter blocks */} + {config.category !== 'triggers' && type !== 'starter' && ( + + )} - {/* Input Handle - Don't show for starter blocks */} - {type !== 'starter' && ( + {/* Input Handle - Don't show for trigger blocks or starter blocks */} + {config.category !== 'triggers' && type !== 'starter' && ( ) { reactivateSchedule(scheduleInfo.id!) + scheduleInfo?.id + ? scheduleInfo.isDisabled + ? () => reactivateSchedule(scheduleInfo.id!) + : () => disableSchedule(scheduleInfo.id!) : undefined } > @@ -570,32 +597,12 @@ export function WorkflowBlock({ id, data }: NodeProps) { - {scheduleInfo ? ( - <> -

{scheduleInfo.scheduleTiming}

- {scheduleInfo.isDisabled && ( -

- This schedule is currently disabled due to consecutive failures. Click the - badge to reactivate it. -

- )} - {scheduleInfo.nextRunAt && !scheduleInfo.isDisabled && ( -

- Next run:{' '} - {formatDateTime(new Date(scheduleInfo.nextRunAt), scheduleInfo.timezone)} -

- )} - {scheduleInfo.lastRanAt && ( -

- Last run:{' '} - {formatDateTime(new Date(scheduleInfo.lastRanAt), scheduleInfo.timezone)} -

- )} - - ) : ( -

- This workflow is running on a schedule. + {scheduleInfo?.isDisabled ? ( +

+ This schedule is currently disabled. Click the badge to reactivate it.

+ ) : ( +

Click the badge to disable this schedule.

)}
@@ -825,8 +832,8 @@ export function WorkflowBlock({ id, data }: NodeProps) { isValidConnection={(connection) => connection.target !== id} /> - {/* Error Handle - Don't show for starter blocks */} - {type !== 'starter' && ( + {/* Error Handle - Don't show for trigger blocks or starter blocks */} + {config.category !== 'triggers' && type !== 'starter' && ( => { // Use the mergeSubblockState utility to get all block states const mergedStates = mergeSubblockState(blocks) - const currentBlockStates = Object.entries(mergedStates).reduce( + + // Filter out trigger blocks for manual execution + const filteredStates = Object.entries(mergedStates).reduce( + (acc, [id, block]) => { + const blockConfig = getBlock(block.type) + const isTriggerBlock = blockConfig?.category === 'triggers' + + // Skip trigger blocks during manual execution + if (!isTriggerBlock) { + acc[id] = block + } + return acc + }, + {} as typeof mergedStates + ) + + const currentBlockStates = Object.entries(filteredStates).reduce( (acc, [id, block]) => { acc[id] = Object.entries(block.subBlocks).reduce( (subAcc, [key, subBlock]) => { @@ -453,8 +470,23 @@ export function useWorkflowExecution() { {} as Record ) - // Create serialized workflow - const workflow = new Serializer().serializeWorkflow(mergedStates, edges, loops, parallels) + // Filter edges to exclude connections to/from trigger blocks + const triggerBlockIds = Object.keys(mergedStates).filter((id) => { + const blockConfig = getBlock(mergedStates[id].type) + return blockConfig?.category === 'triggers' + }) + + const filteredEdges = edges.filter( + (edge) => !triggerBlockIds.includes(edge.source) && !triggerBlockIds.includes(edge.target) + ) + + // Create serialized workflow with filtered blocks and edges + const workflow = new Serializer().serializeWorkflow( + filteredStates, + filteredEdges, + loops, + parallels + ) // Determine if this is a chat execution const isChatExecution = diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts index 73cec39ce1..23faa5206e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts @@ -1,4 +1,5 @@ import { createLogger } from '@/lib/logs/console-logger' +import { getBlock } from '@/blocks' const logger = createLogger('WorkflowUtils') @@ -549,14 +550,25 @@ export const analyzeWorkflowGraph = ( const outDegreeValue = (adjacencyList.get(blockId) || []).length const block = blocks[blockId] - if (inDegreeValue === 0 && outDegreeValue === 0 && block.type !== 'starter') { + const blockConfig = getBlock(block.type) + const isTriggerBlock = blockConfig?.category === 'triggers' + + if ( + inDegreeValue === 0 && + outDegreeValue === 0 && + block.type !== 'starter' && + !isTriggerBlock + ) { orphanedBlocks.add(blockId) } }) const queue: string[] = [] inDegree.forEach((degree, blockId) => { - if (degree === 0 || blocks[blockId].type === 'starter') { + const blockConfig = getBlock(blocks[blockId].type) + const isTriggerBlock = blockConfig?.category === 'triggers' + + if (degree === 0 || blocks[blockId].type === 'starter' || isTriggerBlock) { queue.push(blockId) blockLayers.set(blockId, 0) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 0b1e45a4e0..c3d84431aa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -1080,6 +1080,16 @@ const WorkflowContent = React.memo(() => { if (!sourceNode || !targetNode) return + // Prevent incoming connections to trigger blocks (webhook, schedule, etc.) + if (targetNode.data?.config?.category === 'triggers') { + return + } + + // Prevent incoming connections to starter blocks (still keep separate for backward compatibility) + if (targetNode.data?.type === 'starter') { + return + } + // Get parent information (handle container start node case) const sourceParentId = sourceNode.parentId || diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx index d545d38067..420a71e103 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx @@ -143,7 +143,10 @@ export function SearchModal({ const allBlocks = getAllBlocks() return allBlocks .filter( - (block) => block.type !== 'starter' && !block.hideFromToolbar && block.category === 'blocks' + (block) => + block.type !== 'starter' && + !block.hideFromToolbar && + (block.category === 'blocks' || block.category === 'triggers') ) .map( (block): BlockItem => ({ @@ -222,7 +225,7 @@ export function SearchModal({ name: block.name, icon: block.icon, href: block.docsLink, - type: block.category === 'blocks' ? 'block' : 'tool', + type: block.category === 'blocks' || block.category === 'triggers' ? 'block' : 'tool', }) } }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx index e1b7965fb0..678d3c7b4b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx @@ -25,7 +25,7 @@ interface BlockItem { export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }: ToolbarProps) { const [searchQuery, setSearchQuery] = useState('') - const { regularBlocks, specialBlocks, tools } = useMemo(() => { + const { regularBlocks, specialBlocks, tools, triggers } = useMemo(() => { const allBlocks = getAllBlocks() // Filter blocks based on search query @@ -39,9 +39,10 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }: ) }) - // Separate regular blocks (category: 'blocks') and tools (category: 'tools') + // Separate blocks by category: 'blocks', 'tools', and 'triggers' const regularBlockConfigs = filteredBlocks.filter((block) => block.category === 'blocks') const toolConfigs = filteredBlocks.filter((block) => block.category === 'tools') + const triggerConfigs = filteredBlocks.filter((block) => block.category === 'triggers') // Create regular block items and sort alphabetically const regularBlockItems: BlockItem[] = regularBlockConfigs @@ -75,6 +76,16 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }: // Sort special blocks alphabetically specialBlockItems.sort((a, b) => a.name.localeCompare(b.name)) + // Create trigger block items and sort alphabetically + const triggerBlockItems: BlockItem[] = triggerConfigs + .map((block) => ({ + name: block.name, + type: block.type, + config: block, + isCustom: false, + })) + .sort((a, b) => a.name.localeCompare(b.name)) + // Sort tools alphabetically toolConfigs.sort((a, b) => a.name.localeCompare(b.name)) @@ -82,6 +93,7 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }: regularBlocks: regularBlockItems, specialBlocks: specialBlockItems, tools: toolConfigs, + triggers: triggerBlockItems, } }, [searchQuery]) @@ -127,6 +139,15 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }: return null })} + {/* Triggers Section */} + {triggers.map((trigger) => ( + + ))} + {/* Tools Section */} {tools.map((tool) => ( diff --git a/apps/sim/blocks/blocks/schedule.ts b/apps/sim/blocks/blocks/schedule.ts new file mode 100644 index 0000000000..fe1af83378 --- /dev/null +++ b/apps/sim/blocks/blocks/schedule.ts @@ -0,0 +1,116 @@ +import { ScheduleIcon } from '@/components/icons' +import type { BlockConfig } from '../types' + +export const ScheduleBlock: BlockConfig = { + type: 'schedule', + name: 'Schedule', + description: 'Trigger workflow execution on a schedule', + longDescription: + 'Configure automated workflow execution with flexible timing options. Set up recurring workflows that run at specific intervals or times.', + category: 'triggers', + bgColor: '#7B68EE', + icon: ScheduleIcon, + + subBlocks: [ + // Schedule configuration status display + { + id: 'scheduleConfig', + title: 'Schedule Status', + type: 'schedule-config', + layout: 'full', + }, + // Hidden fields for schedule configuration (used by the modal only) + { + id: 'scheduleType', + title: 'Frequency', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Every X Minutes', id: 'minutes' }, + { label: 'Hourly', id: 'hourly' }, + { label: 'Daily', id: 'daily' }, + { label: 'Weekly', id: 'weekly' }, + { label: 'Monthly', id: 'monthly' }, + { label: 'Custom Cron', id: 'custom' }, + ], + value: () => 'daily', + hidden: true, + }, + { + id: 'minutesInterval', + type: 'short-input', + hidden: true, + }, + { + id: 'hourlyMinute', + type: 'short-input', + hidden: true, + }, + { + id: 'dailyTime', + type: 'short-input', + hidden: true, + }, + { + id: 'weeklyDay', + type: 'dropdown', + hidden: true, + options: [ + { label: 'Monday', id: 'MON' }, + { label: 'Tuesday', id: 'TUE' }, + { label: 'Wednesday', id: 'WED' }, + { label: 'Thursday', id: 'THU' }, + { label: 'Friday', id: 'FRI' }, + { label: 'Saturday', id: 'SAT' }, + { label: 'Sunday', id: 'SUN' }, + ], + value: () => 'MON', + }, + { + id: 'weeklyDayTime', + type: 'short-input', + hidden: true, + }, + { + id: 'monthlyDay', + type: 'short-input', + hidden: true, + }, + { + id: 'monthlyTime', + type: 'short-input', + hidden: true, + }, + { + id: 'cronExpression', + type: 'short-input', + hidden: true, + }, + { + id: 'timezone', + type: 'dropdown', + hidden: true, + options: [ + { label: 'UTC', id: 'UTC' }, + { label: 'US Eastern (UTC-4)', id: 'America/New_York' }, + { label: 'US Central (UTC-5)', id: 'America/Chicago' }, + { label: 'US Mountain (UTC-6)', id: 'America/Denver' }, + { label: 'US Pacific (UTC-7)', id: 'America/Los_Angeles' }, + { label: 'London (UTC+1)', id: 'Europe/London' }, + { label: 'Paris (UTC+2)', id: 'Europe/Paris' }, + { label: 'Singapore (UTC+8)', id: 'Asia/Singapore' }, + { label: 'Tokyo (UTC+9)', id: 'Asia/Tokyo' }, + { label: 'Sydney (UTC+10)', id: 'Australia/Sydney' }, + ], + value: () => 'UTC', + }, + ], + + tools: { + access: [], // No external tools needed + }, + + inputs: {}, // No inputs - schedule triggers initiate workflows + + outputs: {}, // No outputs - schedule triggers initiate workflow execution +} diff --git a/apps/sim/blocks/blocks/starter.ts b/apps/sim/blocks/blocks/starter.ts index 0af83aefee..412b670698 100644 --- a/apps/sim/blocks/blocks/starter.ts +++ b/apps/sim/blocks/blocks/starter.ts @@ -5,8 +5,7 @@ export const StarterBlock: BlockConfig = { type: 'starter', name: 'Starter', description: 'Start workflow', - longDescription: - 'Initiate your workflow manually, on a schedule, or via webhook triggers. Configure flexible execution patterns with customizable timing options and webhook security.', + longDescription: 'Initiate your workflow manually with optional structured input for API calls.', category: 'blocks', bgColor: '#2FB3FF', icon: StartIcon, @@ -19,8 +18,7 @@ export const StarterBlock: BlockConfig = { layout: 'full', options: [ { label: 'Run manually', id: 'manual' }, - { label: 'On webhook call', id: 'webhook' }, - { label: 'On schedule', id: 'schedule' }, + { label: 'Chat', id: 'chat' }, ], value: () => 'manual', }, @@ -33,148 +31,6 @@ export const StarterBlock: BlockConfig = { mode: 'advanced', condition: { field: 'startWorkflow', value: 'manual' }, }, - // Webhook configuration - { - id: 'webhookProvider', - title: 'Webhook Provider', - type: 'dropdown', - layout: 'full', - options: [ - { label: 'Slack', id: 'slack' }, - { label: 'Gmail', id: 'gmail' }, - { label: 'Airtable', id: 'airtable' }, - { label: 'Telegram', id: 'telegram' }, - { label: 'Generic', id: 'generic' }, - // { label: 'WhatsApp', id: 'whatsapp' }, - // { label: 'GitHub', id: 'github' }, - // { label: 'Discord', id: 'discord' }, - // { label: 'Stripe', id: 'stripe' }, - ], - value: () => 'generic', - condition: { field: 'startWorkflow', value: 'webhook' }, - }, - { - id: 'webhookConfig', - title: 'Webhook Configuration', - type: 'webhook-config', - layout: 'full', - condition: { field: 'startWorkflow', value: 'webhook' }, - }, - // Schedule configuration status display - { - id: 'scheduleConfig', - title: 'Schedule Status', - type: 'schedule-config', - layout: 'full', - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - // Hidden fields for schedule configuration (used by the modal only) - { - id: 'scheduleType', - title: 'Frequency', - type: 'dropdown', - layout: 'full', - options: [ - { label: 'Every X Minutes', id: 'minutes' }, - { label: 'Hourly', id: 'hourly' }, - { label: 'Daily', id: 'daily' }, - { label: 'Weekly', id: 'weekly' }, - { label: 'Monthly', id: 'monthly' }, - { label: 'Custom Cron', id: 'custom' }, - ], - value: () => 'daily', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'scheduleStartAt', - type: 'date-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'scheduleTime', - type: 'time-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'minutesInterval', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'hourlyMinute', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'dailyTime', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'weeklyDay', - type: 'dropdown', - hidden: true, - options: [ - { label: 'Monday', id: 'MON' }, - { label: 'Tuesday', id: 'TUE' }, - { label: 'Wednesday', id: 'WED' }, - { label: 'Thursday', id: 'THU' }, - { label: 'Friday', id: 'FRI' }, - { label: 'Saturday', id: 'SAT' }, - { label: 'Sunday', id: 'SUN' }, - ], - value: () => 'MON', - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'weeklyDayTime', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'monthlyDay', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'monthlyTime', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'cronExpression', - type: 'short-input', - hidden: true, - condition: { field: 'startWorkflow', value: 'schedule' }, - }, - { - id: 'timezone', - type: 'dropdown', - hidden: true, - options: [ - { label: 'UTC', id: 'UTC' }, - { label: 'US Eastern (UTC-4)', id: 'America/New_York' }, - { label: 'US Central (UTC-5)', id: 'America/Chicago' }, - { label: 'US Mountain (UTC-6)', id: 'America/Denver' }, - { label: 'US Pacific (UTC-7)', id: 'America/Los_Angeles' }, - { label: 'London (UTC+1)', id: 'Europe/London' }, - { label: 'Paris (UTC+2)', id: 'Europe/Paris' }, - { label: 'Singapore (UTC+8)', id: 'Asia/Singapore' }, - { label: 'Tokyo (UTC+9)', id: 'Asia/Tokyo' }, - { label: 'Sydney (UTC+10)', id: 'Australia/Sydney' }, - ], - value: () => 'UTC', - condition: { field: 'startWorkflow', value: 'schedule' }, - }, ], tools: { access: [], diff --git a/apps/sim/blocks/blocks/webhook.ts b/apps/sim/blocks/blocks/webhook.ts new file mode 100644 index 0000000000..2f361e4f5a --- /dev/null +++ b/apps/sim/blocks/blocks/webhook.ts @@ -0,0 +1,92 @@ +import { + AirtableIcon, + DiscordIcon, + GithubIcon, + GmailIcon, + SignalIcon, + SlackIcon, + StripeIcon, + TelegramIcon, + WebhookIcon, + WhatsAppIcon, +} from '@/components/icons' +import type { BlockConfig } from '../types' + +const getWebhookProviderIcon = (provider: string) => { + const iconMap: Record> = { + slack: SlackIcon, + gmail: GmailIcon, + airtable: AirtableIcon, + telegram: TelegramIcon, + generic: SignalIcon, + whatsapp: WhatsAppIcon, + github: GithubIcon, + discord: DiscordIcon, + stripe: StripeIcon, + } + + return iconMap[provider.toLowerCase()] +} + +export const WebhookBlock: BlockConfig = { + type: 'webhook', + name: 'Webhook', + description: 'Trigger workflow execution from external webhooks', + category: 'triggers', + icon: WebhookIcon, + bgColor: '#10B981', // Green color for triggers + + subBlocks: [ + { + id: 'webhookProvider', + title: 'Webhook Provider', + type: 'dropdown', + layout: 'full', + options: [ + 'slack', + 'gmail', + 'airtable', + 'telegram', + 'generic', + 'whatsapp', + 'github', + 'discord', + 'stripe', + ].map((provider) => { + const providerLabels = { + slack: 'Slack', + gmail: 'Gmail', + airtable: 'Airtable', + telegram: 'Telegram', + generic: 'Generic', + whatsapp: 'WhatsApp', + github: 'GitHub', + discord: 'Discord', + stripe: 'Stripe', + } + + const icon = getWebhookProviderIcon(provider) + return { + label: providerLabels[provider as keyof typeof providerLabels], + id: provider, + ...(icon && { icon }), + } + }), + value: () => 'generic', + }, + { + id: 'webhookConfig', + title: 'Webhook Configuration', + type: 'webhook-config', + layout: 'full', + }, + ], + + tools: { + access: [], // No external tools needed + }, + + inputs: {}, // No inputs - webhook triggers are pure input sources + + outputs: {}, // No outputs - webhook data is injected directly into workflow context +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 467071b1be..eabee94198 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -45,6 +45,7 @@ import { RedditBlock } from '@/blocks/blocks/reddit' import { ResponseBlock } from '@/blocks/blocks/response' import { RouterBlock } from '@/blocks/blocks/router' import { S3Block } from '@/blocks/blocks/s3' +import { ScheduleBlock } from '@/blocks/blocks/schedule' import { SerperBlock } from '@/blocks/blocks/serper' import { SlackBlock } from '@/blocks/blocks/slack' import { StagehandBlock } from '@/blocks/blocks/stagehand' @@ -59,6 +60,7 @@ import { TwilioSMSBlock } from '@/blocks/blocks/twilio' import { TypeformBlock } from '@/blocks/blocks/typeform' import { VisionBlock } from '@/blocks/blocks/vision' import { WealthboxBlock } from '@/blocks/blocks/wealthbox' +import { WebhookBlock } from '@/blocks/blocks/webhook' import { WhatsAppBlock } from '@/blocks/blocks/whatsapp' import { WorkflowBlock } from '@/blocks/blocks/workflow' import { XBlock } from '@/blocks/blocks/x' @@ -108,6 +110,7 @@ export const registry: Record = { reddit: RedditBlock, response: ResponseBlock, router: RouterBlock, + schedule: ScheduleBlock, s3: S3Block, serper: SerperBlock, stagehand: StagehandBlock, @@ -123,6 +126,7 @@ export const registry: Record = { typeform: TypeformBlock, vision: VisionBlock, wealthbox: WealthboxBlock, + webhook: WebhookBlock, whatsapp: WhatsAppBlock, workflow: WorkflowBlock, x: XBlock, @@ -132,7 +136,7 @@ export const registry: Record = { // Helper functions to access the registry export const getBlock = (type: string): BlockConfig | undefined => registry[type] -export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] => +export const getBlocksByCategory = (category: 'blocks' | 'tools' | 'triggers'): BlockConfig[] => Object.values(registry).filter((block) => block.category === category) export const getAllBlockTypes = (): string[] => Object.keys(registry) diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index d68c2158ff..bcc7a75dd5 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -7,7 +7,7 @@ export type ParamType = 'string' | 'number' | 'boolean' | 'json' export type PrimitiveValueType = 'string' | 'number' | 'boolean' | 'json' | 'any' // Block classification -export type BlockCategory = 'blocks' | 'tools' +export type BlockCategory = 'blocks' | 'tools' | 'triggers' // SubBlock types export type SubBlockType = diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 36eed84bdc..df94a90ca3 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2988,3 +2988,41 @@ export function WealthboxIcon(props: SVGProps) { ) } + +export function WebhookIcon(props: SVGProps) { + return ( + + + + + ) +} + +export function ScheduleIcon(props: SVGProps) { + return ( + + + + + + + ) +} diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx index 40c9bb66a7..df2f613df8 100644 --- a/apps/sim/components/ui/tag-dropdown.tsx +++ b/apps/sim/components/ui/tag-dropdown.tsx @@ -184,19 +184,33 @@ export const TagDropdown: React.FC = ({ } else if (Object.keys(blockConfig.outputs).length === 0) { // Handle blocks with no outputs (like starter) - check for custom input fields if (sourceBlock.type === 'starter') { - // Check for custom input format fields - const inputFormatValue = useSubBlockStore + // Check what start workflow mode is selected + const startWorkflowValue = useSubBlockStore .getState() - .getValue(activeSourceBlockId, 'inputFormat') + .getValue(activeSourceBlockId, 'startWorkflow') - if (inputFormatValue && Array.isArray(inputFormatValue) && inputFormatValue.length > 0) { - // Use custom input fields if they exist - blockTags = inputFormatValue - .filter((field: any) => field.name && field.name.trim() !== '') - .map((field: any) => `${normalizedBlockName}.${field.name}`) + if (startWorkflowValue === 'chat') { + // For chat mode, provide input and conversationId + blockTags = [`${normalizedBlockName}.input`, `${normalizedBlockName}.conversationId`] } else { - // Fallback to just the block name - blockTags = [normalizedBlockName] + // Check for custom input format fields (for manual mode) + const inputFormatValue = useSubBlockStore + .getState() + .getValue(activeSourceBlockId, 'inputFormat') + + if ( + inputFormatValue && + Array.isArray(inputFormatValue) && + inputFormatValue.length > 0 + ) { + // Use custom input fields if they exist + blockTags = inputFormatValue + .filter((field: any) => field.name && field.name.trim() !== '') + .map((field: any) => `${normalizedBlockName}.${field.name}`) + } else { + // Fallback to just the block name + blockTags = [normalizedBlockName] + } } } else { // Other blocks with no outputs - show as just @@ -429,19 +443,33 @@ export const TagDropdown: React.FC = ({ } else if (Object.keys(blockConfig.outputs).length === 0) { // Handle blocks with no outputs (like starter) - check for custom input fields if (accessibleBlock.type === 'starter') { - // Check for custom input format fields - const inputFormatValue = useSubBlockStore + // Check what start workflow mode is selected + const startWorkflowValue = useSubBlockStore .getState() - .getValue(accessibleBlockId, 'inputFormat') + .getValue(accessibleBlockId, 'startWorkflow') - if (inputFormatValue && Array.isArray(inputFormatValue) && inputFormatValue.length > 0) { - // Use custom input fields if they exist - blockTags = inputFormatValue - .filter((field: any) => field.name && field.name.trim() !== '') - .map((field: any) => `${normalizedBlockName}.${field.name}`) + if (startWorkflowValue === 'chat') { + // For chat mode, provide input and conversationId + blockTags = [`${normalizedBlockName}.input`, `${normalizedBlockName}.conversationId`] } else { - // Fallback to just the block name - blockTags = [normalizedBlockName] + // Check for custom input format fields (for manual mode) + const inputFormatValue = useSubBlockStore + .getState() + .getValue(accessibleBlockId, 'inputFormat') + + if ( + inputFormatValue && + Array.isArray(inputFormatValue) && + inputFormatValue.length > 0 + ) { + // Use custom input fields if they exist + blockTags = inputFormatValue + .filter((field: any) => field.name && field.name.trim() !== '') + .map((field: any) => `${normalizedBlockName}.${field.name}`) + } else { + // Fallback to just the block name + blockTags = [normalizedBlockName] + } } } else { // Other blocks with no outputs - show as just diff --git a/apps/sim/contexts/socket-context.tsx b/apps/sim/contexts/socket-context.tsx index 90f28477c2..f0f58dc546 100644 --- a/apps/sim/contexts/socket-context.tsx +++ b/apps/sim/contexts/socket-context.tsx @@ -357,7 +357,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) { }) socketInstance.on('workflow-state', (state) => { - logger.info('Received workflow state from server:', state) + // logger.info('Received workflow state from server:', state) // This will be used to sync initial state when joining a workflow }) diff --git a/apps/sim/db/migrations/0057_charming_star_brand.sql b/apps/sim/db/migrations/0057_charming_star_brand.sql new file mode 100644 index 0000000000..4825c79305 --- /dev/null +++ b/apps/sim/db/migrations/0057_charming_star_brand.sql @@ -0,0 +1,6 @@ +ALTER TABLE "workflow_schedule" DROP CONSTRAINT "workflow_schedule_workflow_id_unique";--> statement-breakpoint +ALTER TABLE "webhook" ADD COLUMN "block_id" text;--> statement-breakpoint +ALTER TABLE "workflow_schedule" ADD COLUMN "block_id" text;--> statement-breakpoint +ALTER TABLE "webhook" ADD CONSTRAINT "webhook_block_id_workflow_blocks_id_fk" FOREIGN KEY ("block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workflow_schedule" ADD CONSTRAINT "workflow_schedule_block_id_workflow_blocks_id_fk" FOREIGN KEY ("block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "workflow_schedule_workflow_block_unique" ON "workflow_schedule" USING btree ("workflow_id","block_id"); \ No newline at end of file diff --git a/apps/sim/db/migrations/meta/0057_snapshot.json b/apps/sim/db/migrations/meta/0057_snapshot.json new file mode 100644 index 0000000000..d9a6cebabe --- /dev/null +++ b/apps/sim/db/migrations/meta/0057_snapshot.json @@ -0,0 +1,5655 @@ +{ + "id": "629121b5-cdc9-4e2f-b671-7e3b11af7af1", + "prevId": "3bb59215-ddd2-4c4a-82e7-4fc9e998ca08", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subdomain": { + "name": "subdomain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subdomain_idx": { + "name": "subdomain_idx", + "columns": [ + { + "expression": "subdomain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_uploaded_at_idx": { + "name": "doc_kb_uploaded_at_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 100, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.marketplace": { + "name": "marketplace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "marketplace_workflow_id_workflow_id_fk": { + "name": "marketplace_workflow_id_workflow_id_fk", + "tableFrom": "marketplace", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "marketplace_author_id_user_id_fk": { + "name": "marketplace_author_id_user_id_fk", + "tableFrom": "marketplace", + "tableTo": "user", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_idx": { + "name": "memory_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_key_idx": { + "name": "memory_workflow_key_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workflow_id_workflow_id_fk": { + "name": "memory_workflow_id_workflow_id_fk", + "tableFrom": "memory", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_fill_env_vars": { + "name": "auto_fill_env_vars", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_pan": { + "name": "auto_pan", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "console_expanded_by_default": { + "name": "console_expanded_by_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_notified_user": { + "name": "telemetry_notified_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "general": { + "name": "general", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR (metadata IS NOT NULL AND (metadata->>'perSeatAllowance' IS NOT NULL OR metadata->>'totalAllowance' IS NOT NULL))" + } + }, + "isRLSEnabled": false + }, + "public.template_stars": { + "name": "template_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_stars_user_id_idx": { + "name": "template_stars_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_id_idx": { + "name": "template_stars_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_idx": { + "name": "template_stars_user_template_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_user_idx": { + "name": "template_stars_template_user_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_starred_at_idx": { + "name": "template_stars_starred_at_idx", + "columns": [ + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_starred_at_idx": { + "name": "template_stars_template_starred_at_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_unique": { + "name": "template_stars_user_template_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_stars_user_id_user_id_fk": { + "name": "template_stars_user_id_user_id_fk", + "tableFrom": "template_stars", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "template_stars_template_id_templates_id_fk": { + "name": "template_stars_template_id_templates_id_fk", + "tableFrom": "template_stars", + "tableTo": "templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'FileText'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "templates_workflow_id_idx": { + "name": "templates_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_user_id_idx": { + "name": "templates_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_idx": { + "name": "templates_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_views_idx": { + "name": "templates_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_stars_idx": { + "name": "templates_stars_idx", + "columns": [ + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_views_idx": { + "name": "templates_category_views_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_stars_idx": { + "name": "templates_category_stars_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_user_category_idx": { + "name": "templates_user_category_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_created_at_idx": { + "name": "templates_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_updated_at_idx": { + "name": "templates_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workflow_id_workflow_id_fk": { + "name": "templates_workflow_id_workflow_id_fk", + "tableFrom": "templates", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "templates_user_id_user_id_fk": { + "name": "templates_user_id_user_id_fk", + "tableFrom": "templates", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_rate_limits": { + "name": "user_rate_limits", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "sync_api_requests": { + "name": "sync_api_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "async_api_requests": { + "name": "async_api_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "window_start": { + "name": "window_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_rate_limited": { + "name": "is_rate_limited", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "rate_limit_reset_at": { + "name": "rate_limit_reset_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_rate_limits_user_id_user_id_fk": { + "name": "user_rate_limits_user_id_user_id_fk", + "tableFrom": "user_rate_limits", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'5'" + }, + "usage_limit_set_by": { + "name": "usage_limit_set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_idx": { + "name": "path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_block_id_workflow_blocks_id_fk": { + "name": "webhook_block_id_workflow_blocks_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_blocks", + "columnsFrom": ["block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_state": { + "name": "deployed_state", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "collaborators": { + "name": "collaborators", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "marketplace_data": { + "name": "marketplace_data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "extent": { + "name": "extent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_parent_id_idx": { + "name": "workflow_blocks_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_workflow_parent_idx": { + "name": "workflow_blocks_workflow_parent_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_workflow_type_idx": { + "name": "workflow_blocks_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_source_block_idx": { + "name": "workflow_edges_source_block_idx", + "columns": [ + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_target_block_idx": { + "name": "workflow_edges_target_block_idx", + "columns": [ + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_blocks": { + "name": "workflow_execution_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_name": { + "name": "block_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_type": { + "name": "block_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack_trace": { + "name": "error_stack_trace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_data": { + "name": "input_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output_data": { + "name": "output_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_input": { + "name": "cost_input", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "cost_output": { + "name": "cost_output", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "tokens_prompt": { + "name": "tokens_prompt", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_completion": { + "name": "tokens_completion", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_total": { + "name": "tokens_total", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_blocks_execution_id_idx": { + "name": "execution_blocks_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_workflow_id_idx": { + "name": "execution_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_block_id_idx": { + "name": "execution_blocks_block_id_idx", + "columns": [ + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_status_idx": { + "name": "execution_blocks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_duration_idx": { + "name": "execution_blocks_duration_idx", + "columns": [ + { + "expression": "duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_cost_idx": { + "name": "execution_blocks_cost_idx", + "columns": [ + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_workflow_execution_idx": { + "name": "execution_blocks_workflow_execution_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_execution_status_idx": { + "name": "execution_blocks_execution_status_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_blocks_started_at_idx": { + "name": "execution_blocks_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_execution_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "block_count": { + "name": "block_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "success_count": { + "name": "success_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "total_input_cost": { + "name": "total_input_cost", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "total_output_cost": { + "name": "total_output_cost", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_idx": { + "name": "workflow_execution_logs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_cost_idx": { + "name": "workflow_execution_logs_cost_idx", + "columns": [ + { + "expression": "total_cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_duration_idx": { + "name": "workflow_execution_logs_duration_idx", + "columns": [ + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_logs": { + "name": "workflow_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_logs_workflow_id_workflow_id_fk": { + "name": "workflow_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_unique": { + "name": "workflow_schedule_workflow_block_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_block_id_workflow_blocks_id_fk": { + "name": "workflow_schedule_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_blocks", + "columnsFrom": ["block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_invitation": { + "name": "workspace_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invitation_workspace_id_workspace_id_fk": { + "name": "workspace_invitation_workspace_id_workspace_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invitation_inviter_id_user_id_fk": { + "name": "workspace_invitation_inviter_id_user_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invitation_token_unique": { + "name": "workspace_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/sim/db/migrations/meta/_journal.json b/apps/sim/db/migrations/meta/_journal.json index 50ef4abc63..d7979a916f 100644 --- a/apps/sim/db/migrations/meta/_journal.json +++ b/apps/sim/db/migrations/meta/_journal.json @@ -393,6 +393,13 @@ "when": 1752789061522, "tag": "0056_adorable_franklin_richards", "breakpoints": true + }, + { + "idx": 57, + "version": "7", + "when": 1752980338632, + "tag": "0057_charming_star_brand", + "breakpoints": true } ] } diff --git a/apps/sim/db/schema.ts b/apps/sim/db/schema.ts index 5cc468a472..46e28de8cc 100644 --- a/apps/sim/db/schema.ts +++ b/apps/sim/db/schema.ts @@ -410,23 +410,34 @@ export const settings = pgTable('settings', { updatedAt: timestamp('updated_at').notNull().defaultNow(), }) -export const workflowSchedule = pgTable('workflow_schedule', { - id: text('id').primaryKey(), - workflowId: text('workflow_id') - .notNull() - .references(() => workflow.id, { onDelete: 'cascade' }) - .unique(), - cronExpression: text('cron_expression'), - nextRunAt: timestamp('next_run_at'), - lastRanAt: timestamp('last_ran_at'), - triggerType: text('trigger_type').notNull(), // "manual", "webhook", "schedule" - timezone: text('timezone').notNull().default('UTC'), - failedCount: integer('failed_count').notNull().default(0), // Track consecutive failures - status: text('status').notNull().default('active'), // 'active' or 'disabled' - lastFailedAt: timestamp('last_failed_at'), // When the schedule last failed - createdAt: timestamp('created_at').notNull().defaultNow(), - updatedAt: timestamp('updated_at').notNull().defaultNow(), -}) +export const workflowSchedule = pgTable( + 'workflow_schedule', + { + id: text('id').primaryKey(), + workflowId: text('workflow_id') + .notNull() + .references(() => workflow.id, { onDelete: 'cascade' }), + blockId: text('block_id').references(() => workflowBlocks.id, { onDelete: 'cascade' }), + cronExpression: text('cron_expression'), + nextRunAt: timestamp('next_run_at'), + lastRanAt: timestamp('last_ran_at'), + triggerType: text('trigger_type').notNull(), // "manual", "webhook", "schedule" + timezone: text('timezone').notNull().default('UTC'), + failedCount: integer('failed_count').notNull().default(0), // Track consecutive failures + status: text('status').notNull().default('active'), // 'active' or 'disabled' + lastFailedAt: timestamp('last_failed_at'), // When the schedule last failed + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => { + return { + workflowBlockUnique: uniqueIndex('workflow_schedule_workflow_block_unique').on( + table.workflowId, + table.blockId + ), + } + } +) export const webhook = pgTable( 'webhook', @@ -435,6 +446,7 @@ export const webhook = pgTable( workflowId: text('workflow_id') .notNull() .references(() => workflow.id, { onDelete: 'cascade' }), + blockId: text('block_id').references(() => workflowBlocks.id, { onDelete: 'cascade' }), // ID of the webhook trigger block (nullable for legacy starter block webhooks) path: text('path').notNull(), provider: text('provider'), // e.g., "whatsapp", "github", etc. providerConfig: json('provider_config'), // Store provider-specific configuration diff --git a/apps/sim/executor/consts.ts b/apps/sim/executor/consts.ts index b5ebea7154..6304ee848a 100644 --- a/apps/sim/executor/consts.ts +++ b/apps/sim/executor/consts.ts @@ -14,6 +14,8 @@ export enum BlockType { RESPONSE = 'response', WORKFLOW = 'workflow', STARTER = 'starter', + SCHEDULE = 'schedule', + WEBHOOK_TRIGGER = 'webhook_trigger', } /** diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index 22508b7697..0703ff3c87 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -167,9 +167,13 @@ export class Executor { * Executes the workflow and returns the result. * * @param workflowId - Unique identifier for the workflow execution + * @param startBlockId - Optional block ID to start execution from (for webhook or schedule triggers) * @returns Execution result containing output, logs, and metadata, or a stream, or combined execution and stream */ - async execute(workflowId: string): Promise { + async execute( + workflowId: string, + startBlockId?: string + ): Promise { const { setIsExecuting, setIsDebugging, setPendingBlocks, reset } = useExecutionStore.getState() const startTime = new Date() let finalOutput: NormalizedBlockOutput = {} @@ -182,9 +186,9 @@ export class Executor { startTime: startTime.toISOString(), }) - this.validateWorkflow() + this.validateWorkflow(startBlockId) - const context = this.createExecutionContext(workflowId, startTime) + const context = this.createExecutionContext(workflowId, startTime, startBlockId) try { setIsExecuting(true) @@ -543,30 +547,46 @@ export class Executor { /** * Validates that the workflow meets requirements for execution. - * Checks for starter block, connections, and loop configurations. + * Checks for starter block, webhook trigger block, or schedule trigger block, connections, and loop configurations. * + * @param startBlockId - Optional specific block to start from * @throws Error if workflow validation fails */ - private validateWorkflow(): void { - const starterBlock = this.actualWorkflow.blocks.find( - (block) => block.metadata?.id === BlockType.STARTER - ) - if (!starterBlock || !starterBlock.enabled) { - throw new Error('Workflow must have an enabled starter block') - } + private validateWorkflow(startBlockId?: string): void { + let validationBlock: SerializedBlock | undefined - const incomingToStarter = this.actualWorkflow.connections.filter( - (conn) => conn.target === starterBlock.id - ) - if (incomingToStarter.length > 0) { - throw new Error('Starter block cannot have incoming connections') - } + if (startBlockId) { + // If starting from a specific block (webhook trigger or schedule trigger), validate that block exists + const startBlock = this.actualWorkflow.blocks.find((block) => block.id === startBlockId) + if (!startBlock || !startBlock.enabled) { + throw new Error(`Start block ${startBlockId} not found or disabled`) + } + validationBlock = startBlock + // Trigger blocks (webhook and schedule) can have incoming connections, so no need to check that + } else { + // Default validation for starter block + const starterBlock = this.actualWorkflow.blocks.find( + (block) => block.metadata?.id === BlockType.STARTER + ) + if (!starterBlock || !starterBlock.enabled) { + throw new Error('Workflow must have an enabled starter block') + } + validationBlock = starterBlock - const outgoingFromStarter = this.actualWorkflow.connections.filter( - (conn) => conn.source === starterBlock.id - ) - if (outgoingFromStarter.length === 0) { - throw new Error('Starter block must have at least one outgoing connection') + const incomingToStarter = this.actualWorkflow.connections.filter( + (conn) => conn.target === starterBlock.id + ) + if (incomingToStarter.length > 0) { + throw new Error('Starter block cannot have incoming connections') + } + + // Only check outgoing connections for starter blocks, not trigger blocks + const outgoingFromStarter = this.actualWorkflow.connections.filter( + (conn) => conn.source === starterBlock.id + ) + if (outgoingFromStarter.length === 0) { + throw new Error('Starter block must have at least one outgoing connection') + } } const blockIds = new Set(this.actualWorkflow.blocks.map((block) => block.id)) @@ -603,13 +623,18 @@ export class Executor { /** * Creates the initial execution context with predefined states. - * Sets up the starter block and its connections in the active execution path. + * Sets up the starter block, webhook trigger block, or schedule trigger block and its connections in the active execution path. * * @param workflowId - Unique identifier for the workflow execution * @param startTime - Execution start time + * @param startBlockId - Optional specific block to start from * @returns Initialized execution context */ - private createExecutionContext(workflowId: string, startTime: Date): ExecutionContext { + private createExecutionContext( + workflowId: string, + startTime: Date, + startBlockId?: string + ): ExecutionContext { const context: ExecutionContext = { workflowId, blockStates: new Map(), @@ -652,13 +677,22 @@ export class Executor { } } - const starterBlock = this.actualWorkflow.blocks.find( - (block) => block.metadata?.id === BlockType.STARTER - ) - if (starterBlock) { - // Initialize the starter block with the workflow input + // Determine which block to initialize as the starting point + let initBlock: SerializedBlock | undefined + if (startBlockId) { + // Starting from a specific block (webhook trigger or schedule trigger) + initBlock = this.actualWorkflow.blocks.find((block) => block.id === startBlockId) + } else { + // Default to starter block + initBlock = this.actualWorkflow.blocks.find( + (block) => block.metadata?.id === BlockType.STARTER + ) + } + + if (initBlock) { + // Initialize the starting block with the workflow input try { - const blockParams = starterBlock.config.params + const blockParams = initBlock.config.params const inputFormat = blockParams?.inputFormat // If input format is defined, structure the input according to the schema @@ -718,17 +752,17 @@ export class Executor { // Use the structured input if we processed fields, otherwise use raw input const finalInput = hasProcessedFields ? structuredInput : rawInputData - // Initialize the starter block with structured input (flattened) - const starterOutput = { + // Initialize the starting block with structured input (flattened) + const blockOutput = { input: finalInput, conversationId: this.workflowInput?.conversationId, // Add conversationId to root ...finalInput, // Add input fields directly at top level } - logger.info(`[Executor] Starter output:`, JSON.stringify(starterOutput, null, 2)) + logger.info(`[Executor] Starting block output:`, JSON.stringify(blockOutput, null, 2)) - context.blockStates.set(starterBlock.id, { - output: starterOutput, + context.blockStates.set(initBlock.id, { + output: blockOutput, executed: true, executionTime: 0, }) @@ -746,7 +780,7 @@ export class Executor { conversationId: this.workflowInput.conversationId, } - context.blockStates.set(starterBlock.id, { + context.blockStates.set(initBlock.id, { output: starterOutput, executed: true, executionTime: 0, @@ -755,7 +789,7 @@ export class Executor { // API workflow: spread the raw data directly (no wrapping) const starterOutput = { ...this.workflowInput } - context.blockStates.set(starterBlock.id, { + context.blockStates.set(initBlock.id, { output: starterOutput, executed: true, executionTime: 0, @@ -767,7 +801,7 @@ export class Executor { input: this.workflowInput, } - context.blockStates.set(starterBlock.id, { + context.blockStates.set(initBlock.id, { output: starterOutput, executed: true, executionTime: 0, @@ -778,7 +812,7 @@ export class Executor { logger.warn('Error processing starter block input format:', e) // Error handler fallback - use appropriate structure - let starterOutput: any + let blockOutput: any if (this.workflowInput && typeof this.workflowInput === 'object') { // Check if this is a chat workflow input (has both input and conversationId) if ( @@ -786,40 +820,43 @@ export class Executor { Object.hasOwn(this.workflowInput, 'conversationId') ) { // Chat workflow: extract input and conversationId to root level - starterOutput = { + blockOutput = { input: this.workflowInput.input, conversationId: this.workflowInput.conversationId, } } else { // API workflow: spread the raw data directly (no wrapping) - starterOutput = { ...this.workflowInput } + blockOutput = { ...this.workflowInput } } } else { // Primitive input - starterOutput = { + blockOutput = { input: this.workflowInput, } } - logger.info('[Executor] Fallback starter output:', JSON.stringify(starterOutput, null, 2)) + logger.info( + '[Executor] Fallback starting block output:', + JSON.stringify(blockOutput, null, 2) + ) - context.blockStates.set(starterBlock.id, { - output: starterOutput, + context.blockStates.set(initBlock.id, { + output: blockOutput, executed: true, executionTime: 0, }) } - // Ensure the starter block is in the active execution path - context.activeExecutionPath.add(starterBlock.id) - // Mark the starter block as executed - context.executedBlocks.add(starterBlock.id) + // Ensure the starting block is in the active execution path + context.activeExecutionPath.add(initBlock.id) + // Mark the starting block as executed + context.executedBlocks.add(initBlock.id) - // Add all blocks connected to the starter to the active execution path - const connectedToStarter = this.actualWorkflow.connections - .filter((conn) => conn.source === starterBlock.id) + // Add all blocks connected to the starting block to the active execution path + const connectedToStartBlock = this.actualWorkflow.connections + .filter((conn) => conn.source === initBlock.id) .map((conn) => conn.target) - connectedToStarter.forEach((blockId) => { + connectedToStartBlock.forEach((blockId) => { context.activeExecutionPath.add(blockId) }) } diff --git a/apps/sim/hooks/use-collaborative-workflow.ts b/apps/sim/hooks/use-collaborative-workflow.ts index b303109ec8..cd7eb6f374 100644 --- a/apps/sim/hooks/use-collaborative-workflow.ts +++ b/apps/sim/hooks/use-collaborative-workflow.ts @@ -289,7 +289,6 @@ export function useCollaborativeWorkflow() { isDeployed: workflowData.state.isDeployed || false, deployedAt: workflowData.state.deployedAt, lastSaved: workflowData.state.lastSaved || Date.now(), - hasActiveSchedule: workflowData.state.hasActiveSchedule || false, hasActiveWebhook: workflowData.state.hasActiveWebhook || false, deploymentStatuses: workflowData.state.deploymentStatuses || {}, }) diff --git a/apps/sim/lib/webhooks/utils.ts b/apps/sim/lib/webhooks/utils.ts index 0edb2bf6ac..0aee84245d 100644 --- a/apps/sim/lib/webhooks/utils.ts +++ b/apps/sim/lib/webhooks/utils.ts @@ -423,7 +423,8 @@ export async function executeWorkflowFromPayload( foundWorkflow: any, input: any, executionId: string, - requestId: string + requestId: string, + startBlockId?: string | null ): Promise { // Add log at the beginning of this function for clarity logger.info(`[${requestId}] Preparing to execute workflow`, { @@ -668,7 +669,7 @@ export async function executeWorkflowFromPayload( ) // This is THE critical line where the workflow actually executes - const result = await executor.execute(foundWorkflow.id) + const result = await executor.execute(foundWorkflow.id, startBlockId || undefined) // Check if we got a StreamingExecution result (with stream + execution properties) // For webhook executions, we only care about the ExecutionResult part, not the stream @@ -1275,7 +1276,7 @@ export async function fetchAndProcessAirtablePayloads( } ) - await executeWorkflowFromPayload(workflowData, input, requestId, requestId) + await executeWorkflowFromPayload(workflowData, input, requestId, requestId, null) // COMPLETION LOG - This will only appear if execution succeeds logger.info(`[${requestId}] CRITICAL_TRACE: Workflow execution completed successfully`, { @@ -1372,7 +1373,13 @@ export async function processWebhook( `[${requestId}] Executing workflow ${foundWorkflow.id} for webhook ${foundWebhook.id} (Execution: ${executionId})` ) - await executeWorkflowFromPayload(foundWorkflow, input, executionId, requestId) + await executeWorkflowFromPayload( + foundWorkflow, + input, + executionId, + requestId, + foundWebhook.blockId + ) // Since executeWorkflowFromPayload handles logging and errors internally, // we just need to return a standard success response for synchronous webhooks. diff --git a/apps/sim/lib/workflows/db-helpers.test.ts b/apps/sim/lib/workflows/db-helpers.test.ts index 66035a06b7..c82858a1b4 100644 --- a/apps/sim/lib/workflows/db-helpers.test.ts +++ b/apps/sim/lib/workflows/db-helpers.test.ts @@ -207,7 +207,6 @@ const mockWorkflowState: WorkflowState = { lastSaved: Date.now(), isDeployed: false, deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, } @@ -463,7 +462,6 @@ describe('Database Helpers', () => { lastSaved: Date.now(), isDeployed: false, deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, } @@ -643,7 +641,6 @@ describe('Database Helpers', () => { lastSaved: Date.now(), isDeployed: false, deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, } @@ -731,7 +728,6 @@ describe('Database Helpers', () => { lastSaved: Date.now(), isDeployed: false, deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, } diff --git a/apps/sim/lib/workflows/db-helpers.ts b/apps/sim/lib/workflows/db-helpers.ts index 89672ef22e..3545c4c1c2 100644 --- a/apps/sim/lib/workflows/db-helpers.ts +++ b/apps/sim/lib/workflows/db-helpers.ts @@ -211,7 +211,6 @@ export async function saveWorkflowToNormalizedTables( isDeployed: state.isDeployed, deployedAt: state.deployedAt, deploymentStatuses: state.deploymentStatuses, - hasActiveSchedule: state.hasActiveSchedule, hasActiveWebhook: state.hasActiveWebhook, } @@ -264,7 +263,6 @@ export async function migrateWorkflowToNormalizedTables( isDeployed: jsonState.isDeployed, deployedAt: jsonState.deployedAt, deploymentStatuses: jsonState.deploymentStatuses || {}, - hasActiveSchedule: jsonState.hasActiveSchedule, hasActiveWebhook: jsonState.hasActiveWebhook, } diff --git a/apps/sim/socket-server/database/operations.ts b/apps/sim/socket-server/database/operations.ts index 7c1c296ee1..434e6829c7 100644 --- a/apps/sim/socket-server/database/operations.ts +++ b/apps/sim/socket-server/database/operations.ts @@ -130,7 +130,6 @@ export async function getWorkflowState(workflowId: string) { const finalState = { // Default values for expected properties deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, // Preserve any existing state properties ...existingState, diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts index d077538e6f..c49741f7ce 100644 --- a/apps/sim/stores/workflows/registry/store.ts +++ b/apps/sim/stores/workflows/registry/store.ts @@ -188,7 +188,6 @@ function resetWorkflowStores() { isDeployed: false, deployedAt: undefined, deploymentStatuses: {}, // Reset deployment statuses map - hasActiveSchedule: false, history: { past: [], present: { @@ -441,7 +440,6 @@ export const useWorkflowRegistry = create()( lastSaved: Date.now(), marketplaceData: workflowData.marketplaceData || null, deploymentStatuses: {}, - hasActiveSchedule: false, history: { past: [], present: { @@ -490,7 +488,6 @@ export const useWorkflowRegistry = create()( isDeployed: false, deployedAt: undefined, deploymentStatuses: {}, - hasActiveSchedule: false, history: { past: [], present: { @@ -1253,7 +1250,6 @@ export const useWorkflowRegistry = create()( parallels: {}, isDeployed: false, deployedAt: undefined, - hasActiveSchedule: false, history: { past: [], present: { diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index b1308552de..3f624f0032 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -23,7 +23,6 @@ const initialState = { // New field for per-workflow deployment tracking deploymentStatuses: {}, needsRedeployment: false, - hasActiveSchedule: false, hasActiveWebhook: false, history: { past: [], @@ -436,7 +435,6 @@ export const useWorkflowStore = create()( lastSaved: Date.now(), isDeployed: false, isPublished: false, - hasActiveSchedule: false, hasActiveWebhook: false, } set(newState) @@ -799,23 +797,9 @@ export const useWorkflowStore = create()( })) }, - setScheduleStatus: (hasActiveSchedule: boolean) => { - // Only update if the status has changed to avoid unnecessary rerenders - if (get().hasActiveSchedule !== hasActiveSchedule) { - set({ hasActiveSchedule }) - get().updateLastSaved() - // Note: Socket.IO handles real-time sync automatically - } - }, - setWebhookStatus: (hasActiveWebhook: boolean) => { // Only update if the status has changed to avoid unnecessary rerenders if (get().hasActiveWebhook !== hasActiveWebhook) { - // If the workflow has an active schedule, disable it - if (get().hasActiveSchedule) { - get().setScheduleStatus(false) - } - set({ hasActiveWebhook }) get().updateLastSaved() // Note: Socket.IO handles real-time sync automatically diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index 65ef362451..05b31bfbc2 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -140,7 +140,6 @@ export interface WorkflowState { // New field for per-workflow deployment status deploymentStatuses?: Record needsRedeployment?: boolean - hasActiveSchedule?: boolean hasActiveWebhook?: boolean } @@ -189,7 +188,6 @@ export interface WorkflowActions { generateLoopBlocks: () => Record generateParallelBlocks: () => Record setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void - setScheduleStatus: (hasActiveSchedule: boolean) => void setWebhookStatus: (hasActiveWebhook: boolean) => void revertToDeployedState: (deployedState: WorkflowState) => void toggleBlockAdvancedMode: (id: string) => void diff --git a/apps/sim/stores/workflows/yaml/importer.ts b/apps/sim/stores/workflows/yaml/importer.ts index 6ef4d3b84e..d1187d7a94 100644 --- a/apps/sim/stores/workflows/yaml/importer.ts +++ b/apps/sim/stores/workflows/yaml/importer.ts @@ -696,7 +696,6 @@ export async function importWorkflowFromYaml( isDeployed: false, deployedAt: undefined, deploymentStatuses: {}, - hasActiveSchedule: false, hasActiveWebhook: false, }