diff --git a/apps/sim/app/api/resume/poll/route.ts b/apps/sim/app/api/resume/poll/route.ts index ad07ea009b5..86edf2f218c 100644 --- a/apps/sim/app/api/resume/poll/route.ts +++ b/apps/sim/app/api/resume/poll/route.ts @@ -139,13 +139,21 @@ async function dispatchRow(row: DueRow, now: Date): Promise { }) if (enqueueResult.status === 'starting') { - PauseResumeManager.startResumeExecution({ + // Route through `executeResumeJob` (not `PauseResumeManager.startResumeExecution` + // directly) so cell-context restoration + cascade-loop continuation + // fires. This is the same primitive the trigger.dev `resumeExecutionTask` + // wraps — calling it directly handles both trigger.dev-disabled local + // dev and trigger.dev-enabled prod identically. + const { executeResumeJob } = await import('@/background/resume-execution') + void executeResumeJob({ resumeEntryId: enqueueResult.resumeEntryId, resumeExecutionId: enqueueResult.resumeExecutionId, - pausedExecution: enqueueResult.pausedExecution, + pausedExecutionId: enqueueResult.pausedExecution.id, contextId: enqueueResult.contextId, resumeInput: enqueueResult.resumeInput, userId: enqueueResult.userId, + workflowId: row.workflowId, + parentExecutionId: row.executionId, }).catch((error) => { logger.error('Background time-pause resume failed', { executionId: row.executionId, diff --git a/apps/sim/app/api/table/[tableId]/columns/run/route.ts b/apps/sim/app/api/table/[tableId]/columns/run/route.ts index eddfc416e0a..2b96981d115 100644 --- a/apps/sim/app/api/table/[tableId]/columns/run/route.ts +++ b/apps/sim/app/api/table/[tableId]/columns/run/route.ts @@ -1,5 +1,4 @@ import { createLogger } from '@sim/logger' -import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { runColumnContract } from '@/lib/api/contracts/tables' import { parseRequest } from '@/lib/api/server' @@ -30,21 +29,16 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro const access = await checkAccess(tableId, auth.userId, 'write') if (!access.ok) return accessError(access, requestId, tableId) - // Dispatch in the background — large fan-outs (thousands of rows) issue - // sequential trigger.dev calls and would otherwise hold the HTTP response - // open for minutes, blocking the AI/copilot tool span and the UI mutation. - void runWorkflowColumn({ + const { dispatchId } = await runWorkflowColumn({ tableId, workspaceId, groupIds, mode: runMode, rowIds, requestId, - }).catch((err) => { - logger.error(`[${requestId}] run-column dispatch failed:`, toError(err).message) }) - return NextResponse.json({ success: true, data: { triggered: null } }) + return NextResponse.json({ success: true, data: { dispatchId } }) } catch (error) { if (error instanceof Error && error.message === 'Invalid workspace ID') { return NextResponse.json({ error: 'Invalid workspace ID' }, { status: 400 }) diff --git a/apps/sim/app/api/table/[tableId]/dispatches/route.ts b/apps/sim/app/api/table/[tableId]/dispatches/route.ts new file mode 100644 index 00000000000..a5e442f05dd --- /dev/null +++ b/apps/sim/app/api/table/[tableId]/dispatches/route.ts @@ -0,0 +1,65 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { type ActiveDispatch, listActiveDispatchesContract } from '@/lib/api/contracts/tables' +import { parseRequest } from '@/lib/api/server' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { countRunningCells, listActiveDispatches } from '@/lib/table/dispatcher' +import { accessError, checkAccess } from '@/app/api/table/utils' + +const logger = createLogger('TableDispatchesAPI') + +interface RouteParams { + params: Promise<{ tableId: string }> +} + +/** + * GET /api/table/[tableId]/dispatches + * + * Returns active (`pending` / `dispatching`) dispatches for the table. Drives + * the client's "about to run" overlay so refresh during a long Run-all keeps + * the queued indicators on rows the dispatcher hasn't reached yet. + */ +export const GET = withRouteHandler(async (request: NextRequest, { params }: RouteParams) => { + const requestId = generateRequestId() + + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) + } + + const parsed = await parseRequest(listActiveDispatchesContract, request, { params }) + if (!parsed.success) return parsed.response + const { tableId } = parsed.data.params + + const result = await checkAccess(tableId, authResult.userId, 'read') + if (!result.ok) return accessError(result, requestId, tableId) + + const [rows, running] = await Promise.all([ + listActiveDispatches(tableId), + countRunningCells(tableId), + ]) + const dispatches: ActiveDispatch[] = rows.map((r) => ({ + id: r.id, + status: r.status as 'pending' | 'dispatching', + mode: r.mode, + isManualRun: r.isManualRun, + cursor: r.cursor, + scope: r.scope, + })) + + return NextResponse.json({ + success: true, + data: { + dispatches, + runningCellCount: running.total, + runningByRowId: running.byRowId, + }, + }) + } catch (error) { + logger.error(`[${requestId}] list-dispatches failed:`, error) + return NextResponse.json({ error: 'Failed to list active dispatches' }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts b/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts index 9a4a988bc25..fe7452230ee 100644 --- a/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts @@ -136,6 +136,11 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: RowR // Only `null` when a `cancellationGuard` is supplied and the SQL guard // rejects the write — this route doesn't pass one, so reaching null is a bug. if (!updatedRow) throw new Error('updateRow returned null without a cancellationGuard') + // Auto-dispatch for user edits is handled inside `updateRow` (mode: 'new'). + // Firing a second mode: 'incomplete' dispatch here would race with the + // `mode: 'new'` one AND bulk-clear sibling-group outputs (the incomplete + // bulk-clear wipes ALL targeted columns when any one column on the row + // is empty). return NextResponse.json({ success: true, diff --git a/apps/sim/app/api/table/[tableId]/rows/route.ts b/apps/sim/app/api/table/[tableId]/rows/route.ts index 9b0076a127d..98cae6137f9 100644 --- a/apps/sim/app/api/table/[tableId]/rows/route.ts +++ b/apps/sim/app/api/table/[tableId]/rows/route.ts @@ -1,8 +1,8 @@ import { db } from '@sim/db' -import { userTableRows } from '@sim/db/schema' +import { tableRowExecutions, userTableRows } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { and, eq, sql } from 'drizzle-orm' +import { and, eq, inArray, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { type BatchInsertTableRowsBodyInput, @@ -17,7 +17,14 @@ import { isZodError, validationErrorResponse } from '@/lib/api/server/validation import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import type { Filter, RowData, Sort, TableSchema } from '@/lib/table' +import type { + Filter, + RowData, + RowExecutionMetadata, + RowExecutions, + Sort, + TableSchema, +} from '@/lib/table' import { batchInsertRows, batchUpdateRows, @@ -277,7 +284,6 @@ export const GET = withRouteHandler( .select({ id: userTableRows.id, data: userTableRows.data, - executions: userTableRows.executions, position: userTableRows.position, createdAt: userTableRows.createdAt, updatedAt: userTableRows.updatedAt, @@ -308,6 +314,41 @@ export const GET = withRouteHandler( const rows = await query.limit(validated.limit).offset(validated.offset) + // Sidecar: fetch per-(row, group) execution state and group into a map + // so the response preserves the legacy `row.executions[groupId]` wire + // shape. One indexed-IN scan against table_row_executions. + const executionsByRow = new Map() + if (rows.length > 0) { + const execRows = await db + .select() + .from(tableRowExecutions) + .where( + inArray( + tableRowExecutions.rowId, + rows.map((r) => r.id) + ) + ) + for (const e of execRows) { + const existing = executionsByRow.get(e.rowId) ?? {} + const meta: RowExecutionMetadata = { + status: e.status as RowExecutionMetadata['status'], + executionId: e.executionId ?? null, + jobId: e.jobId ?? null, + workflowId: e.workflowId, + error: e.error ?? null, + ...(e.runningBlockIds && e.runningBlockIds.length > 0 + ? { runningBlockIds: e.runningBlockIds } + : {}), + ...(e.blockErrors && Object.keys(e.blockErrors as Record).length > 0 + ? { blockErrors: e.blockErrors as Record } + : {}), + ...(e.cancelledAt ? { cancelledAt: e.cancelledAt.toISOString() } : {}), + } + existing[e.groupId] = meta + executionsByRow.set(e.rowId, existing) + } + } + logger.info( `[${requestId}] Queried ${rows.length} rows from table ${tableId} (total: ${totalCount ?? 'n/a'})` ) @@ -318,7 +359,7 @@ export const GET = withRouteHandler( rows: rows.map((r) => ({ id: r.id, data: r.data, - executions: r.executions ?? {}, + executions: executionsByRow.get(r.id) ?? {}, position: r.position, createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt), diff --git a/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts b/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts index 810bb0dfc65..96e2cf323c0 100644 --- a/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts +++ b/apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts @@ -144,6 +144,9 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: RowR if (!updatedRow) { return NextResponse.json({ error: 'Row not found' }, { status: 404 }) } + // Auto-dispatch for user edits is handled inside `updateRow` (mode: 'new'). + // Firing a second mode: 'incomplete' dispatch here would race with it AND + // bulk-clear sibling-group outputs. return NextResponse.json({ success: true, diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-render.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-render.tsx index c2c78971d90..6d63bca978b 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-render.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/cell-render.tsx @@ -1,6 +1,7 @@ 'use client' import type React from 'react' +import { useEffect, useRef, useState } from 'react' import { parse } from 'tldts' import { Badge, Checkbox, Tooltip } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' @@ -60,6 +61,14 @@ export function resolveCellRender({ if (!isNull) return { kind: 'value', text: stringifyValue(value) } if (inFlight && !(groupHasBlockErrors && !blockRunning)) { + // A `pending` cell whose jobId starts with `paused-` is mid-pause + // (workflow yielded for human-in-the-loop). Render as Pending rather + // than Queued so the user can tell it's not just waiting to start. + const isPaused = + exec?.status === 'pending' && + typeof exec.jobId === 'string' && + exec.jobId.startsWith('paused-') + if (isPaused) return { kind: 'pending-upstream' } if (exec?.status === 'queued' || exec?.status === 'pending') return { kind: 'queued' } return { kind: 'pending-upstream' } } @@ -119,6 +128,9 @@ interface CellRenderProps { } export function CellRender({ kind, isEditing }: CellRenderProps): React.ReactElement | null { + const valueText = kind.kind === 'value' ? kind.text : null + const revealedValueText = useTypewriter(valueText) + switch (kind.kind) { case 'value': return ( @@ -128,7 +140,7 @@ export function CellRender({ kind, isEditing }: CellRenderProps): React.ReactEle isEditing && 'invisible' )} > - {kind.text} + {revealedValueText ?? kind.text} ) @@ -275,3 +287,45 @@ function Wrap({ isEditing, children }: { isEditing: boolean; children: React.Rea if (!isEditing) return <>{children} return
{children}
} + +const TYPEWRITER_MS_PER_CHAR = 15 + +/** + * Reveals `text` character-by-character whenever it changes after the first + * render. Initial render (page hydration or virtualization remount) shows the + * value statically — animation fires only for subsequent updates, which in + * practice means SSE-driven workflow completions arriving via + * `useTableEventStream → applyCell()`. + */ +function useTypewriter(text: string | null): string | null { + const [revealed, setRevealed] = useState(text) + const isFirstRunRef = useRef(true) + const prevTextRef = useRef(text) + + useEffect(() => { + if (isFirstRunRef.current) { + isFirstRunRef.current = false + prevTextRef.current = text + setRevealed(text) + return + } + if (prevTextRef.current === text) return + prevTextRef.current = text + + if (text === null || text.length === 0) { + setRevealed(text) + return + } + + setRevealed('') + let i = 0 + const id = window.setInterval(() => { + i++ + setRevealed(text.slice(0, i)) + if (i >= text.length) window.clearInterval(id) + }, TYPEWRITER_MS_PER_CHAR) + return () => window.clearInterval(id) + }, [text]) + + return revealed +} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/data-row.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/data-row.tsx index b340fb95d2a..aed56ac8de4 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/data-row.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/data-row.tsx @@ -3,6 +3,7 @@ import React from 'react' import { Button, Checkbox } from '@/components/emcn' import { PlayOutline, Square } from '@/components/emcn/icons' +import type { ActiveDispatch } from '@/lib/api/contracts/tables' import { cn } from '@/lib/core/utils/cn' import { handleKeyboardActivation } from '@/lib/core/utils/keyboard' import type { TableRow as TableRowType, WorkflowGroup } from '@/lib/table' @@ -17,7 +18,7 @@ import { SELECTION_TINT_BG, } from './constants' import type { DisplayColumn } from './types' -import { type NormalizedSelection, readExecution } from './utils' +import { type NormalizedSelection, resolveCellExec } from './utils' export interface DataRowProps { row: TableRowType @@ -50,6 +51,12 @@ export interface DataRowProps { * for empty workflow-output cells whose group has unmet dependencies. */ workflowGroups: WorkflowGroup[] + /** + * Active dispatches on the table — rows in scope ahead of the dispatcher's + * cursor render as `Queued` until the dispatcher pre-stamps them. Preserves + * queued indicators across page refresh during long Run-all dispatches. + */ + activeDispatches: ActiveDispatch[] | undefined } function cellRangeRowChanged( @@ -105,7 +112,8 @@ function dataRowPropsAreEqual(prev: DataRowProps, next: DataRowProps): boolean { prev.numDivWidth !== next.numDivWidth || prev.onStopRow !== next.onStopRow || prev.onRunRow !== next.onRunRow || - prev.workflowGroups !== next.workflowGroups + prev.workflowGroups !== next.workflowGroups || + prev.activeDispatches !== next.activeDispatches ) { return false } @@ -148,6 +156,7 @@ export const DataRow = React.memo(function DataRow({ onStopRow, onRunRow, workflowGroups, + activeDispatches, }: DataRowProps) { const sel = normalizedSelection /** @@ -178,8 +187,8 @@ export const DataRow = React.memo(function DataRow({
0 ? `Stop ${runningCount} running` : 'Run row'} title={runningCount > 0 ? `Stop ${runningCount} running` : 'Run row'} - // mr-px keeps the hover bg off the cell's right border — without - // it the rounded-rect background paints over the divider line - // while the button is hovered. - className='mr-px size-[20px] shrink-0 p-0 text-[var(--text-primary)] hover-hover:bg-[var(--surface-2)]' + className='size-[20px] shrink-0 p-0 text-[var(--text-primary)] hover-hover:bg-[var(--surface-2)]' onClick={() => { if (runningCount > 0) { onStopRow(row.id) @@ -309,7 +315,13 @@ export const DataRow = React.memo(function DataRow({ ? pendingCellValue[column.name] : row.data[column.name] } - exec={readExecution(row, column.workflowGroupId)} + exec={resolveCellExec( + row, + column.workflowGroupId + ? workflowGroups.find((g) => g.id === column.workflowGroupId) + : undefined, + activeDispatches + )} column={column} isEditing={isEditing} initialCharacter={isEditing ? initialCharacter : undefined} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu.tsx index e3c437f2588..06a004843e2 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/column-header-menu.tsx @@ -89,12 +89,12 @@ export const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({ ? workflows?.find((w) => w.id === ownGroup.workflowId) : undefined // Workflow-output column with siblings → "Hide column" (non-destructive, - // re-addable from sidebar). Last output of a group → "Delete workflow" + // re-addable from sidebar). Last output of a group → "Delete column" // (removes the entire group). Plain column → undefined (default "Delete column"). const deleteLabel = ownGroup ? ownGroup.outputs.length > 1 ? 'Hide column' - : 'Delete workflow' + : 'Delete column' : undefined useEffect(() => { if (isRenaming && renameInputRef.current) { diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.tsx index 70f2cc9ac6a..23fa84f2227 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/headers/workflow-group-meta-cell.tsx @@ -35,9 +35,9 @@ interface ColumnOptionsMenuProps { position: { x: number; y: number } column: DisplayColumn /** Override for the destructive item's label. Defaults to "Delete column" - * (or "Delete workflow" when `onDeleteGroup` is set). Use "Hide column" - * when the destructive action is non-lossy (workflow-output column where - * removing it leaves the group with siblings). */ + * for both plain columns and workflow groups. Use "Hide column" when the + * destructive action is non-lossy (workflow-output column where removing + * it leaves the group with siblings). */ deleteLabel?: string onOpenConfig: (columnName: string) => void onInsertLeft: (columnName: string) => void @@ -156,7 +156,7 @@ export function ColumnOptionsMenu({ onSelect={() => (onDeleteGroup ? onDeleteGroup() : onDeleteColumn(column.name))} > {deleteLabel === 'Hide column' ? : } - {deleteLabel ?? (onDeleteGroup ? 'Delete workflow' : 'Delete column')} + {deleteLabel ?? 'Delete column'} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx index 2640e0a1453..698c6b31e4d 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx @@ -12,7 +12,6 @@ import { cn } from '@/lib/core/utils/cn' import { captureEvent } from '@/lib/posthog/client' import type { ColumnDefinition, TableRow as TableRowType } from '@/lib/table' import { TABLE_LIMITS } from '@/lib/table/constants' -import { isExecInFlight } from '@/lib/table/deps' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { useAddTableColumn, @@ -21,6 +20,7 @@ import { useCreateTableRow, useDeleteColumn, useDeleteWorkflowGroup, + useTableRunState, useUpdateColumn, useUpdateTableMetadata, useUpdateTableRow, @@ -73,6 +73,8 @@ import { const logger = createLogger('TableView') +const EMPTY_RUNNING_BY_ROW: Readonly> = Object.freeze({}) + const COL_WIDTH_MIN = 80 const COL_WIDTH_AUTO_FIT_MAX = 1000 const SKELETON_COL_COUNT = 4 @@ -300,6 +302,11 @@ export function TableGrid({ ensureAllRowsLoaded, } = useTable({ workspaceId, tableId, queryOptions }) + const { data: tableRunState } = useTableRunState(tableId) + const activeDispatches = tableRunState?.dispatches + const totalRunning = tableRunState?.runningCellCount ?? 0 + const runningByRowId = tableRunState?.runningByRowId ?? EMPTY_RUNNING_BY_ROW + const fetchNextPageRef = useRef(fetchNextPage) fetchNextPageRef.current = fetchNextPage const hasNextPageRef = useRef(hasNextPage) @@ -652,12 +659,20 @@ export function TableGrid({ if (_col && _gid) { const _exec = contextMenu.row.executions?.[_gid] contextMenuIsWorkflowColumn = true - // Only `completed` / `error` / `running` cells are guaranteed to have a - // server-side execution log. `queued` / `pending` haven't started yet; - // `cancelled` may have been cancelled before the worker ever picked the - // job up, so its executionId can't be relied on either. + // Cells with a server-side execution log: `completed` / `error` / + // `running`, plus HITL-paused runs (status `pending` with a `paused-` + // jobId — has a real executionId + viewable trace). `queued` / plain + // `pending` haven't started yet; `cancelled` may have been cancelled + // before the worker ever picked the job up. + const _isPaused = + _exec?.status === 'pending' && + typeof _exec?.jobId === 'string' && + _exec.jobId.startsWith('paused-') contextMenuHasStartedRun = - _exec?.status === 'completed' || _exec?.status === 'error' || _exec?.status === 'running' + _exec?.status === 'completed' || + _exec?.status === 'error' || + _exec?.status === 'running' || + _isPaused contextMenuExecutionId = _exec?.executionId ?? null } } @@ -2690,22 +2705,11 @@ export function TableGrid({ return ids.length > 0 ? ids : null }, [rowSelection, rows]) - const { runningByRowId, totalRunning } = useMemo(() => { - const byRow = new Map() - let total = 0 - for (const row of rows) { - let count = 0 - const executions = row.executions ?? {} - for (const gid in executions) { - if (isExecInFlight(executions[gid])) count++ - } - if (count > 0) { - byRow.set(row.id, count) - total += count - } - } - return { runningByRowId: byRow, totalRunning: total } - }, [rows]) + // `runningByRowId` + `totalRunning` come from `useTableRunState` above — + // backend-bootstrapped via `countRunningCells` and kept live by + // `applyCell`'s SSE-driven delta. Counts only cells whose worker has + // actually claimed the cell (`status === 'running'`), ignoring optimistic + // queued/pending stamps. // Context-menu wrappers: act on `contextMenuRowIds`, then close the menu. // Mirror the action bar's Play / Refresh split: Play fills empty/failed, @@ -2726,7 +2730,7 @@ export function TableGrid({ // Total running/queued cells across the rows the context menu is acting on; // drives the "Stop N running workflows" item, shown only when > 0. const runningInContextSelection = contextMenuRowIds.reduce( - (total, rowId) => total + (runningByRowId.get(rowId) ?? 0), + (total, rowId) => total + (runningByRowId[rowId] ?? 0), 0 ) @@ -2757,7 +2761,7 @@ export function TableGrid({ return [] }, [rowSelection, normalizedSelection, rows]) const runningInActionBarSelection = actionBarRowIds.reduce( - (total, rowId) => total + (runningByRowId.get(rowId) ?? 0), + (total, rowId) => total + (runningByRowId[rowId] ?? 0), 0 ) @@ -2785,11 +2789,17 @@ export function TableGrid({ } const exec = row.executions?.[groupId] const status = exec?.status + // A `pending` execution with a `paused-` jobId is a HITL-paused run — + // it has a real executionId and a viewable trace, same as + // running/completed/error. + const isPaused = + status === 'pending' && typeof exec?.jobId === 'string' && exec.jobId.startsWith('paused-') return { rowId: row.id, groupId, executionId: exec?.executionId ?? null, - canViewExecution: status === 'completed' || status === 'error' || status === 'running', + canViewExecution: + status === 'completed' || status === 'error' || status === 'running' || isPaused, } }, [normalizedSelection, rows, displayColumns]) @@ -3149,12 +3159,13 @@ export function TableGrid({ onCellMouseEnter={handleCellMouseEnter} isRowChecked={rowSelectionIncludes(rowSelection, row.id)} onRowToggle={handleRowToggle} - runningCount={runningByRowId.get(row.id) ?? 0} + runningCount={runningByRowId[row.id] ?? 0} hasWorkflowColumns={hasWorkflowColumns} numDivWidth={numDivWidth} onStopRow={onStopRow} onRunRow={onRunRow} workflowGroups={tableWorkflowGroups} + activeDispatches={activeDispatches} /> ))} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/utils.ts b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/utils.ts index a0efce3b877..50f7aae85af 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/utils.ts @@ -1,3 +1,4 @@ +import type { ActiveDispatch } from '@/lib/api/contracts/tables' import type { ColumnDefinition, RowExecutionMetadata, @@ -5,6 +6,7 @@ import type { TableRow as TableRowType, WorkflowGroup, } from '@/lib/table' +import { areGroupDepsSatisfied, areOutputsFilled } from '@/lib/table/deps' import type { DeletedRowSnapshot } from '@/stores/table/types' import type { DisplayColumn } from './types' @@ -166,6 +168,41 @@ export function readExecution( return row?.executions?.[groupId] } +/** + * Resolves a cell's execution state with the "about to run" overlay applied: + * for cells in an active dispatch's scope ahead of its cursor whose deps are + * already satisfied, returns a synthetic `pending` exec so the renderer + * shows `Queued`. Cells with a real DB exec always win — the overlay only + * fills the gap between dispatch start and the dispatcher's per-row pending + * stamp. Cells with unmet deps still render as `Waiting` (the renderer + * computes that from `waitingOnLabels`). + */ +export function resolveCellExec( + row: TableRowType, + group: WorkflowGroup | undefined, + activeDispatches: ActiveDispatch[] | undefined +): RowExecutionMetadata | undefined { + if (!group) return undefined + const real = row.executions?.[group.id] + if (real) return real + if (!activeDispatches || activeDispatches.length === 0) return undefined + if (areOutputsFilled(group, row)) return undefined + if (!areGroupDepsSatisfied(group, row)) return undefined + for (const d of activeDispatches) { + if (!d.scope.groupIds.includes(group.id)) continue + if (d.scope.rowIds && !d.scope.rowIds.includes(row.id)) continue + if (row.position <= d.cursor) continue + return { + status: 'pending', + executionId: null, + jobId: null, + workflowId: group.workflowId, + error: null, + } + } + return undefined +} + export interface ExecStatusMix { hasIncompleteOrFailed: boolean hasCompleted: boolean diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/run-settings-section.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/run-settings-section.tsx index 320eda4bd1f..ad8e460585a 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/run-settings-section.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/run-settings-section.tsx @@ -10,15 +10,22 @@ interface RunSettingsSectionProps { /** Column names this group waits on. */ deps: string[] onChangeDeps: (next: string[]) => void + /** Inline validation error rendered under the picker. */ + error?: string | null } /** * "Run after" picker: which upstream columns must be filled before this group * fires. Workflow output columns count the same as plain columns — once a - * column is non-empty, the dep is satisfied. Empty selection = the group fires - * on any row change. + * column is non-empty, the dep is satisfied. At least one dep is required + * when auto-run is on. */ -export function RunSettingsSection({ depOptions, deps, onChangeDeps }: RunSettingsSectionProps) { +export function RunSettingsSection({ + depOptions, + deps, + onChangeDeps, + error, +}: RunSettingsSectionProps) { const options = depOptions.map((c) => ({ label: c.name, value: c.name })) return ( @@ -38,11 +45,12 @@ export function RunSettingsSection({ depOptions, deps, onChangeDeps }: RunSettin multiSelectValues={deps} onMultiSelectChange={onChangeDeps} overlayContent={ - - {deps.length === 0 ? 'Any row change' : `${deps.length} selected`} + + {deps.length === 0 ? 'Select at least one column' : `${deps.length} selected`} } /> + {error &&

{error}

}
) } diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx index fda9f87a13f..b339d3397ef 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx @@ -241,31 +241,42 @@ function WorkflowSidebarBody({ ? (allColumns.find((c) => c.name === config.columnName) ?? null) : null - // Anchor column for "left of current" filtering. For create + edit-group we - // treat the anchor as missing (group config sits at the right edge of the - // group); for edit-output the anchor is the column being edited. - const anchorColumnName = config.mode === 'edit-output' ? config.columnName : null + // Anchor index for "left of current" filtering. + // - edit-output: the column being edited. + // - edit-group: the leftmost column belonging to this group (deps must be + // reachable from the group's first output column). + // - create: no anchor; new column sits at the right edge, so every + // existing column qualifies. + const anchorIdx = (() => { + if (config.mode === 'edit-output') { + const idx = allColumns.findIndex((c) => c.name === config.columnName) + return idx === -1 ? allColumns.length : idx + } + if (config.mode === 'edit-group' && existingGroup) { + let leftmost = Number.POSITIVE_INFINITY + for (let i = 0; i < allColumns.length; i++) { + if (allColumns[i].workflowGroupId === existingGroup.id && i < leftmost) leftmost = i + } + return Number.isFinite(leftmost) ? leftmost : allColumns.length + } + return allColumns.length + })() /** * Columns "left of current" — these are the only valid trigger dependencies. - * For create + edit-group, every existing column qualifies. For edit-output, - * only columns physically before the anchor. */ - const otherColumns = (() => { - if (anchorColumnName === null) return allColumns - const idx = allColumns.findIndex((c) => c.name === anchorColumnName) - if (idx === -1) return allColumns.filter((c) => c.name !== anchorColumnName) - return allColumns.slice(0, idx) - })() + const otherColumns = anchorIdx >= allColumns.length ? allColumns : allColumns.slice(0, anchorIdx) + + // Used by the "missing workflow input" suggestion below — for edit-output + // we exclude the column being edited (you can't suggest it as its own + // input). + const anchorColumnName = config.mode === 'edit-output' ? config.columnName : null // Every left-of-current column is a valid dep — workflow output columns // included. Exclude this group's own outputs (you can't depend on yourself). const ownOutputNames = new Set(existingGroup?.outputs.map((o) => o.columnName) ?? []) const depOptions = otherColumns.filter((c) => !ownOutputNames.has(c.name)) - // Default deps for a brand-new group: tick every left-of-current column. - const defaultDeps = depOptions.map((c) => c.name) - const [selectedWorkflowId, setSelectedWorkflowId] = useState( () => existingGroup?.workflowId ?? '' ) @@ -276,9 +287,10 @@ function WorkflowSidebarBody({ const [autoRun, setAutoRun] = useState(() => existingGroup ? existingGroup.autoRun !== false : false ) - const [deps, setDeps] = useState( - () => existingGroup?.dependencies?.columns ?? defaultDeps - ) + // Deps default to none selected. With auto-run on, at least one is required + // (enforced via `depsValid` below); a legacy group with empty deps will + // surface the error on first open until the user picks at least one column. + const [deps, setDeps] = useState(() => existingGroup?.dependencies?.columns ?? []) // `selectedOutputs` is encoded `${blockId}::${path}`. Seeded once `blockOutputGroups` // resolves (we may not have the workflow blocks loaded at first render); see the // post-load reconciliation below. @@ -542,6 +554,7 @@ function WorkflowSidebarBody({ if (!selectedWorkflowId) missing.push('a workflow') if (selectedWorkflowId && selectedOutputs.length === 0) missing.push('at least one output') if (isEditOutputMode && !trimmedName) missing.push('a column name') + if (autoRun && deps.length === 0) missing.push('at least one Run after column') if (missing.length > 0) { setShowValidation(true) return @@ -664,8 +677,15 @@ function WorkflowSidebarBody({ } } + // Auto-run requires ≥1 dependency column — without one, the dispatcher's + // eligibility predicate would never fire the workflow. Block Save and + // surface an inline error so the user picks a column. + const depsValid = !autoRun || deps.length > 0 const saveDisabled = - addWorkflowGroup.isPending || updateWorkflowGroup.isPending || updateColumn.isPending + addWorkflowGroup.isPending || + updateWorkflowGroup.isPending || + updateColumn.isPending || + !depsValid const titleByMode = { create: 'Add workflow', 'edit-group': 'Configure workflow', @@ -875,7 +895,12 @@ function WorkflowSidebarBody({ {autoRun && ( <> - + )} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts index e5a0e5e807f..7c00d06e338 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts @@ -3,9 +3,11 @@ import { useEffect } from 'react' import { createLogger } from '@sim/logger' import { useQueryClient } from '@tanstack/react-query' +import type { ActiveDispatch } from '@/lib/api/contracts/tables' import type { RowData, RowExecutionMetadata, RowExecutions } from '@/lib/table' +import { isExecInFlight } from '@/lib/table/deps' import type { TableEvent, TableEventEntry } from '@/lib/table/events' -import { snapshotAndMutateRows, tableKeys } from '@/hooks/queries/tables' +import { snapshotAndMutateRows, type TableRunState, tableKeys } from '@/hooks/queries/tables' const logger = createLogger('useTableEventStream') @@ -71,6 +73,28 @@ export function useTableEventStream({ let lastEventId = loadPointer(tableId) let reconnectAttempt = 0 + const updateRunStateCounters = ( + rowId: string, + wasInFlight: boolean, + isInFlight: boolean + ): void => { + if (wasInFlight === isInFlight) return + const delta = isInFlight ? 1 : -1 + queryClient.setQueryData(tableKeys.activeDispatches(tableId), (prev) => { + if (!prev) return prev + const prevForRow = prev.runningByRowId[rowId] ?? 0 + const nextForRow = Math.max(0, prevForRow + delta) + const nextByRow = { ...prev.runningByRowId } + if (nextForRow === 0) delete nextByRow[rowId] + else nextByRow[rowId] = nextForRow + return { + ...prev, + runningCellCount: Math.max(0, prev.runningCellCount + delta), + runningByRowId: nextByRow, + } + }) + } + const applyCell = (event: Extract): void => { const { rowId, @@ -83,12 +107,18 @@ export function useTableEventStream({ runningBlockIds, blockErrors, } = event + let wasInFlight: boolean | null = null void snapshotAndMutateRows( queryClient, tableId, (row) => { if (row.id !== rowId) return null const prevExec = row.executions?.[groupId] + // In-flight = queued | running | pending. Server's countRunningCells + // counts all three (the gutter Run/Stop button reads this map and + // needs Stop visible during queued too, else clicking Play would + // re-enqueue a cell that's already queued). + if (wasInFlight === null) wasInFlight = isExecInFlight(prevExec) const nextExec: RowExecutionMetadata = { status, executionId: executionId ?? null, @@ -108,11 +138,69 @@ export function useTableEventStream({ }, { cancelInFlight: false } ) + if (wasInFlight === null) { + // Row outside the loaded page slice — can't compute the delta locally. + // Refetch the run-state snapshot from the server. Cheap and rare. + void queryClient.invalidateQueries({ + queryKey: tableKeys.activeDispatches(tableId), + }) + } else { + updateRunStateCounters( + rowId, + wasInFlight, + isExecInFlight({ status } as RowExecutionMetadata) + ) + } + } + + const applyDispatch = (event: Extract): void => { + const { dispatchId, status, scope, cursor, mode, isManualRun } = event + queryClient.setQueryData(tableKeys.activeDispatches(tableId), (prev) => { + // SSE may arrive before the initial fetch lands. Seed an empty + // run-state so the dispatch isn't dropped; counters are reconciled + // by the subsequent fetch / per-cell SSE events. + const base: TableRunState = prev ?? { + dispatches: [], + runningCellCount: 0, + runningByRowId: {}, + } + const list = base.dispatches + // Terminal states drop the dispatch from the overlay; client renders + // the row's authoritative DB exec state from here. + if (status === 'complete' || status === 'cancelled') { + const filtered = list.filter((d) => d.id !== dispatchId) + return filtered.length === list.length ? base : { ...base, dispatches: filtered } + } + if (scope === undefined || cursor === undefined || mode === undefined) { + // Defensive: a legacy emit without the new fields can't drive the + // overlay. Leave existing cache alone. + return base + } + const idx = list.findIndex((d) => d.id === dispatchId) + const existing = idx === -1 ? undefined : list[idx] + // Prefer the event payload (current truth from server); fall back to + // the cached entry's value if this is a legacy emit without the + // field, and finally to `false` if we have nothing. + const resolvedManualRun = isManualRun ?? existing?.isManualRun ?? false + const next: ActiveDispatch = { + id: dispatchId, + status, + mode, + isManualRun: resolvedManualRun, + cursor, + scope, + } + if (idx === -1) return { ...base, dispatches: [...list, next] } + const merged = list.slice() + merged[idx] = next + return { ...base, dispatches: merged } + }) } const handlePrune = (payload: PrunedEvent): void => { logger.info('Table event buffer pruned — full refetch', { tableId, ...payload }) void queryClient.invalidateQueries({ queryKey: tableKeys.rowsRoot(tableId) }) + void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) }) lastEventId = typeof payload.earliestEventId === 'number' ? payload.earliestEventId : 0 savePointer(tableId, lastEventId) // Close proactively so the server's close doesn't fire onerror and route @@ -152,11 +240,11 @@ export function useTableEventStream({ eventSource.onmessage = (msg: MessageEvent) => { try { const entry = JSON.parse(msg.data) as TableEventEntry - if (entry.event?.kind !== 'cell') return if (entry.eventId <= lastEventId) return lastEventId = entry.eventId savePointer(tableId, lastEventId) - applyCell(entry.event) + if (entry.event?.kind === 'cell') applyCell(entry.event) + else if (entry.event?.kind === 'dispatch') applyDispatch(entry.event) } catch (err) { logger.warn('Failed to parse table event', { tableId, err }) } diff --git a/apps/sim/background/resume-execution.ts b/apps/sim/background/resume-execution.ts index f7bd79d2a37..fa1ed60f595 100644 --- a/apps/sim/background/resume-execution.ts +++ b/apps/sim/background/resume-execution.ts @@ -1,6 +1,8 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { task } from '@trigger.dev/sdk' +import { withCascadeLock } from '@/lib/table/cascade-lock' import type { RowData, RowExecutionMetadata } from '@/lib/table/types' import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager' @@ -38,149 +40,65 @@ export async function executeResumeJob(payload: ResumeExecutionPayload) { // context so post-resume block outputs land on the same row + group as // the original cell task. Without this, blocks that run after the human // approves write nothing back to the table — the row silently truncates - // at the pause boundary. The original `parentExecutionId` is preserved - // on the cell's `executions[gid]` so it stays one logical execution - // across the pause/resume boundary. + // at the pause boundary. const { findCellContextByExecutionId } = await import('@/lib/table/workflow-columns') const cellContext = await findCellContextByExecutionId(parentExecutionId) - let cellOnBlockComplete: ((blockId: string, output: unknown) => Promise) | undefined - let writeCellTerminal: - | ((status: 'completed' | 'error' | 'paused', error: string | null) => Promise) - | undefined + const writers = cellContext + ? await buildResumeCellWriters(cellContext, parentExecutionId) + : null - if (cellContext) { - const { getTableById } = await import('@/lib/table/service') - const { writeWorkflowGroupState, buildOutputsByBlockId } = await import( - '@/lib/table/cell-write' - ) - const { pluckByPath } = await import('@/lib/table/pluck') - - const table = await getTableById(cellContext.tableId) - const group = table?.schema.workflowGroups?.find((g) => g.id === cellContext.groupId) - if (group) { - const outputsByBlockId = buildOutputsByBlockId(group) - const accumulatedData: RowData = {} - const blockErrors: Record = {} - const writeCtx = { - tableId: cellContext.tableId, - rowId: cellContext.rowId, - workspaceId: cellContext.workspaceId, - groupId: cellContext.groupId, - executionId: parentExecutionId, - requestId: `wfgrp-resume-${parentExecutionId}`, - } - let writeChain: Promise = Promise.resolve() - let terminalWritten = false - - cellOnBlockComplete = async (blockId, output) => { - const outputs = outputsByBlockId.get(blockId) - if (!outputs) return - const blockResult = - output && typeof output === 'object' && 'output' in (output as object) - ? (output as { output: unknown }).output - : output - const errorMessage = - blockResult && - typeof blockResult === 'object' && - typeof (blockResult as { error?: unknown }).error === 'string' - ? (blockResult as { error: string }).error - : null - if (errorMessage) { - blockErrors[blockId] = errorMessage - } else { - for (const out of outputs) { - const plucked = pluckByPath(blockResult, out.path) - if (plucked === undefined) continue - accumulatedData[out.columnName] = plucked as RowData[string] - } - } - const dataSnapshot: RowData = { ...accumulatedData } - const blockErrorsSnapshot = { ...blockErrors } - writeChain = writeChain - .then(async () => { - if (terminalWritten) return - const partial: RowExecutionMetadata = { - status: 'running', - executionId: parentExecutionId, - jobId: null, - workflowId: cellContext.workflowId, - error: null, - blockErrors: blockErrorsSnapshot, - } - await writeWorkflowGroupState(writeCtx, { - executionState: partial, - dataPatch: dataSnapshot, - }) - }) - .catch((err) => { - logger.warn( - `Resume per-block partial write failed (table=${cellContext.tableId} row=${cellContext.rowId} group=${cellContext.groupId}):`, - err - ) - }) - } - - writeCellTerminal = async (status, error) => { - terminalWritten = true - await writeChain.catch(() => {}) - // Paused → keep `pending` + sentinel jobId so eligibility predicates - // continue treating the row as in-flight while we wait on another - // pause. Mirrors the initial cell-task pause branch. - const terminal: RowExecutionMetadata = - status === 'paused' - ? { - status: 'pending', - executionId: parentExecutionId, - jobId: `paused-${parentExecutionId}`, - workflowId: cellContext.workflowId, - error: null, - blockErrors, - } - : { - status, - executionId: parentExecutionId, - jobId: null, - workflowId: cellContext.workflowId, - error, - runningBlockIds: [], - blockErrors, - } - await writeWorkflowGroupState(writeCtx, { - executionState: terminal, - dataPatch: accumulatedData, - }) - } - } else { - logger.warn( - 'Cell context found but table or group missing — falling back to plain resume', - { - parentExecutionId, - tableId: cellContext.tableId, - groupId: cellContext.groupId, - } - ) + // No cell context → plain resume, no lock, no cascade continuation. + if (!cellContext || !writers) { + const result = await PauseResumeManager.startResumeExecution({ + resumeEntryId: payload.resumeEntryId, + resumeExecutionId: payload.resumeExecutionId, + pausedExecution, + contextId: payload.contextId, + resumeInput: payload.resumeInput, + userId: payload.userId, + }) + logger.info('Background resume execution completed', { + resumeExecutionId, + workflowId, + success: result.success, + status: result.status, + }) + return { + success: result.success, + workflowId, + executionId: resumeExecutionId, + parentExecutionId, + status: result.status, + output: result.output, + executedAt: new Date().toISOString(), } } - const result = await PauseResumeManager.startResumeExecution({ - resumeEntryId: payload.resumeEntryId, - resumeExecutionId: payload.resumeExecutionId, - pausedExecution, - contextId: payload.contextId, - resumeInput: payload.resumeInput, - userId: payload.userId, - ...(cellOnBlockComplete ? { onBlockComplete: cellOnBlockComplete } : {}), - }) - - if (writeCellTerminal) { - if (result.status === 'paused') { - await writeCellTerminal('paused', null) - } else if (result.success) { - await writeCellTerminal('completed', null) - } else { - await writeCellTerminal('error', result.error ?? 'Workflow execution failed') + // Cell-context path: hold the row's cascade lock for the resume + any + // downstream cascade continuation. On lock contention, fall through to + // resume-only (the lock holder will pick up the resumed group's + // completion on its next eligibility scan). + const outcome = await withCascadeLock( + cellContext.tableId, + cellContext.rowId, + parentExecutionId, + async () => { + const result = await runResumeAndCellTerminal(payload, pausedExecution, writers) + if (result.status === 'paused') return result + await continueCascadeAfterResume(cellContext) + return result } + ) + + let result + if (outcome.status === 'contended') { + logger.info( + `Resume cascade lock held — writing resumed group only (table=${cellContext.tableId} row=${cellContext.rowId} executionId=${parentExecutionId})` + ) + result = await runResumeAndCellTerminal(payload, pausedExecution, writers) + } else { + result = outcome.result } logger.info('Background resume execution completed', { @@ -209,6 +127,192 @@ export async function executeResumeJob(payload: ResumeExecutionPayload) { } } +type CellWriters = { + cellOnBlockComplete: (blockId: string, output: unknown) => Promise + writeCellTerminal: ( + status: 'completed' | 'error' | 'paused', + error: string | null + ) => Promise +} + +async function buildResumeCellWriters( + cellContext: { + tableId: string + rowId: string + workspaceId: string + groupId: string + workflowId: string + }, + parentExecutionId: string +): Promise { + const { getTableById } = await import('@/lib/table/service') + const { writeWorkflowGroupState, buildOutputsByBlockId } = await import('@/lib/table/cell-write') + const { pluckByPath } = await import('@/lib/table/pluck') + + const table = await getTableById(cellContext.tableId) + const group = table?.schema.workflowGroups?.find((g) => g.id === cellContext.groupId) + if (!group) { + logger.warn('Cell context found but table or group missing — falling back to plain resume', { + parentExecutionId, + tableId: cellContext.tableId, + groupId: cellContext.groupId, + }) + return null + } + + const outputsByBlockId = buildOutputsByBlockId(group) + const accumulatedData: RowData = {} + const blockErrors: Record = {} + const writeCtx = { + tableId: cellContext.tableId, + rowId: cellContext.rowId, + workspaceId: cellContext.workspaceId, + groupId: cellContext.groupId, + executionId: parentExecutionId, + requestId: `wfgrp-resume-${parentExecutionId}`, + } + let writeChain: Promise = Promise.resolve() + let terminalWritten = false + + const cellOnBlockComplete = async (blockId: string, output: unknown) => { + const outputs = outputsByBlockId.get(blockId) + if (!outputs) return + const blockResult = + output && typeof output === 'object' && 'output' in (output as object) + ? (output as { output: unknown }).output + : output + const errorMessage = + blockResult && + typeof blockResult === 'object' && + typeof (blockResult as { error?: unknown }).error === 'string' + ? (blockResult as { error: string }).error + : null + if (errorMessage) { + blockErrors[blockId] = errorMessage + } else { + for (const out of outputs) { + const plucked = pluckByPath(blockResult, out.path) + if (plucked === undefined) continue + accumulatedData[out.columnName] = plucked as RowData[string] + } + } + const dataSnapshot: RowData = { ...accumulatedData } + const blockErrorsSnapshot = { ...blockErrors } + writeChain = writeChain + .then(async () => { + if (terminalWritten) return + const partial: RowExecutionMetadata = { + status: 'running', + executionId: parentExecutionId, + jobId: null, + workflowId: cellContext.workflowId, + error: null, + blockErrors: blockErrorsSnapshot, + } + await writeWorkflowGroupState(writeCtx, { + executionState: partial, + dataPatch: dataSnapshot, + }) + }) + .catch((err) => { + logger.warn( + `Resume per-block partial write failed (table=${cellContext.tableId} row=${cellContext.rowId} group=${cellContext.groupId}):`, + err + ) + }) + } + + const writeCellTerminal = async ( + status: 'completed' | 'error' | 'paused', + error: string | null + ) => { + terminalWritten = true + await writeChain.catch(() => {}) + // Paused → keep `pending` + sentinel jobId so eligibility predicates + // continue treating the row as in-flight while we wait on another + // pause. Mirrors the initial cell-task pause branch. + const terminal: RowExecutionMetadata = + status === 'paused' + ? { + status: 'pending', + executionId: parentExecutionId, + jobId: `paused-${parentExecutionId}`, + workflowId: cellContext.workflowId, + error: null, + blockErrors, + } + : { + status, + executionId: parentExecutionId, + jobId: null, + workflowId: cellContext.workflowId, + error, + runningBlockIds: [], + blockErrors, + } + await writeWorkflowGroupState(writeCtx, { + executionState: terminal, + dataPatch: accumulatedData, + }) + } + + return { cellOnBlockComplete, writeCellTerminal } +} + +async function runResumeAndCellTerminal( + payload: ResumeExecutionPayload, + pausedExecution: Awaited>, + writers: CellWriters +): Promise>> { + if (!pausedExecution) throw new Error('Paused execution missing — already nulled by caller') + const result = await PauseResumeManager.startResumeExecution({ + resumeEntryId: payload.resumeEntryId, + resumeExecutionId: payload.resumeExecutionId, + pausedExecution, + contextId: payload.contextId, + resumeInput: payload.resumeInput, + userId: payload.userId, + onBlockComplete: writers.cellOnBlockComplete, + }) + + if (result.status === 'paused') { + await writers.writeCellTerminal('paused', null) + } else if (result.success) { + await writers.writeCellTerminal('completed', null) + } else { + await writers.writeCellTerminal('error', result.error ?? 'Workflow execution failed') + } + + return result +} + +async function continueCascadeAfterResume(cellContext: { + tableId: string + rowId: string + workspaceId: string + groupId: string +}): Promise { + const { getTableById, getRowById } = await import('@/lib/table/service') + const { pickNextEligibleGroupForRow } = await import('@/lib/table/workflow-columns') + const { runRowCascadeLoop } = await import('@/background/workflow-column-execution') + + const freshTable = await getTableById(cellContext.tableId) + if (!freshTable) return + const freshRow = await getRowById(cellContext.tableId, cellContext.rowId, cellContext.workspaceId) + if (!freshRow) return + const next = pickNextEligibleGroupForRow(freshTable, freshRow, cellContext.groupId) + if (!next) return + await runRowCascadeLoop({ + tableId: cellContext.tableId, + tableName: freshTable.name, + rowId: cellContext.rowId, + workspaceId: cellContext.workspaceId, + groupId: next.id, + workflowId: next.workflowId, + executionId: generateId(), + }) +} + export const resumeExecutionTask = task({ id: 'resume-execution', machine: 'medium-1x', diff --git a/apps/sim/background/table-run-dispatcher.ts b/apps/sim/background/table-run-dispatcher.ts new file mode 100644 index 00000000000..441348b8acb --- /dev/null +++ b/apps/sim/background/table-run-dispatcher.ts @@ -0,0 +1,37 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { task } from '@trigger.dev/sdk' +import { runDispatcherToCompletion } from '@/lib/table/dispatcher' + +const logger = createLogger('TableRunDispatcherTask') + +export interface TableRunDispatcherPayload { + dispatchId: string +} + +/** + * Trigger.dev wrapper around `dispatcherStep`. One task run holds the + * dispatcher loop for the dispatch's entire lifetime — each iteration + * processes a window of cells via `batchTriggerAndWait`, which checkpoints + * the parent via CRIU during the wait so we don't pay compute while cells + * execute. The cursor is persisted in DB; if this run crashes, trigger.dev + * retries and the next attempt resumes from the persisted cursor. + */ +export const tableRunDispatcherTask = task({ + id: 'table-run-dispatcher', + machine: 'small-1x', + retry: { maxAttempts: 3 }, + queue: { + name: 'table-run-dispatcher', + concurrencyLimit: 8, + }, + run: async (payload: TableRunDispatcherPayload) => { + const { dispatchId } = payload + try { + await runDispatcherToCompletion(dispatchId) + } catch (err) { + logger.error(`[${dispatchId}] dispatcher loop failed`, { error: toError(err).message }) + throw err + } + }, +}) diff --git a/apps/sim/background/workflow-column-execution.ts b/apps/sim/background/workflow-column-execution.ts index 7c4b977d537..9c62ee21cae 100644 --- a/apps/sim/background/workflow-column-execution.ts +++ b/apps/sim/background/workflow-column-execution.ts @@ -2,38 +2,108 @@ import { db } from '@sim/db' import { workflow as workflowTable } from '@sim/db/schema' import { createLogger, runWithRequestContext } from '@sim/logger' import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' import { task } from '@trigger.dev/sdk' import { eq } from 'drizzle-orm' -import type { RowData, RowExecutionMetadata } from '@/lib/table/types' +import { withCascadeLock } from '@/lib/table/cascade-lock' +import type { + RowData, + RowExecutionMetadata, + TableDefinition, + WorkflowGroup, +} from '@/lib/table/types' +import type { WorkflowGroupCellPayload } from '@/lib/table/workflow-columns' -const logger = createLogger('TriggerWorkflowGroupCell') +export type { WorkflowGroupCellPayload } -export type WorkflowGroupCellPayload = { - tableId: string - tableName: string - rowId: string - groupId: string - workflowId: string - workspaceId: string - /** Sim-side correlation id used as `wfgrp-${executionId}` in logs/requestId. */ - executionId: string -} +const logger = createLogger('TriggerWorkflowGroupCell') -/** - * Background workflow-group cell execution. Runs in a trigger.dev worker; - * writes plain primitives into `row.data[output.columnName]` as picked - * blocks complete, and execution state into `row.executions[groupId]`. - * Cancellation is authoritative via `cancelWorkflowGroupRuns`. - */ +/** Cell-task entrypoint. Holds a per-row cascade lock so only one worker + * advances a given row at a time; bails on contention. The held lock heart- + * beats every 10s so a crashed pod releases within ~30s. */ export async function executeWorkflowGroupCellJob( payload: WorkflowGroupCellPayload, signal?: AbortSignal ) { + const { tableId, rowId, executionId } = payload + const outcome = await withCascadeLock(tableId, rowId, executionId, () => + runRowCascadeLoop(payload, signal) + ) + if (outcome.status === 'contended') { + logger.info( + `Cascade lock held — bailing (table=${tableId} row=${rowId} executionId=${executionId})` + ) + } +} + +/** Re-fetches the table schema each iteration so groups added DURING the + * cascade become visible to the eligibility check. The resume worker must + * already hold the row's cascade lock before calling. */ +export async function runRowCascadeLoop( + payload: WorkflowGroupCellPayload, + signal?: AbortSignal +): Promise { + const { tableId, rowId, workspaceId } = payload + const { getTableById, getRowById } = await import('@/lib/table/service') + const { pickNextEligibleGroupForRow } = await import('@/lib/table/workflow-columns') + + let currentGroupId = payload.groupId + let currentWorkflowId = payload.workflowId + // Fresh executionId per iteration: SQL guard rejects writes whose id ≠ + // row.executions[gid].executionId, so we need a new claim per group. + let currentExecutionId = payload.executionId + + while (true) { + if (signal?.aborted) break + + const freshTable = await getTableById(tableId) + if (!freshTable) { + logger.warn(`Table ${tableId} vanished mid-cascade`) + break + } + const currentGroup = freshTable.schema.workflowGroups?.find((g) => g.id === currentGroupId) + if (!currentGroup) { + logger.warn(`Group ${currentGroupId} no longer exists on table ${tableId}`) + break + } + + const result = await runWorkflowAndWriteTerminal( + { + ...payload, + groupId: currentGroupId, + workflowId: currentWorkflowId, + executionId: currentExecutionId, + }, + signal, + freshTable, + currentGroup + ) + + if (result === 'paused') break + + const freshRow = await getRowById(tableId, rowId, workspaceId) + if (!freshRow) break + const next = pickNextEligibleGroupForRow(freshTable, freshRow, currentGroupId) + if (!next) break + currentGroupId = next.id + currentWorkflowId = next.workflowId + currentExecutionId = generateId() + } +} + +/** Returns `'paused'` to signal the cascade loop must exit (resume worker + * takes over). `'completed' | 'error'` keep the loop running. */ +async function runWorkflowAndWriteTerminal( + payload: WorkflowGroupCellPayload, + signal: AbortSignal | undefined, + table: TableDefinition, + group: WorkflowGroup +): Promise<'completed' | 'error' | 'paused'> { const { tableId, tableName, rowId, groupId, workflowId, workspaceId, executionId } = payload const requestId = `wfgrp-${executionId}` return runWithRequestContext({ requestId }, async () => { - const { getTableById, getRowById, updateRow } = await import('@/lib/table/service') + const { getRowById } = await import('@/lib/table/service') const { executeWorkflow } = await import('@/lib/workflows/executor/execute-workflow') const { loadWorkflowFromNormalizedTables } = await import('@/lib/workflows/persistence/utils') const { writeWorkflowGroupState, markWorkflowGroupPickedUp, buildOutputsByBlockId } = @@ -44,36 +114,11 @@ export async function executeWorkflowGroupCellJob( const writeState = (executionState: RowExecutionMetadata, dataPatch?: RowData) => writeWorkflowGroupState(cellCtx, { executionState, dataPatch }) - // Hoisted out of the try so the catch block can drain pending writes and - // surface partial errors in the terminal-error state. const blockErrors: Record = {} let writeChain: Promise = Promise.resolve() - // Set right before the terminal `completed`/`error` write fires. The - // executor fires `onBlockComplete` callbacks fire-and-forget, so some can - // still be in the microtask queue after `executeWorkflow` resolves; they - // would otherwise enqueue a `running` partial-write that lands after the - // terminal state and clobber it. Once this flag is set, `schedulePartialWrite` - // becomes a no-op. let terminalWritten = false try { - const table = await getTableById(tableId) - if (!table) { - logger.warn(`Table ${tableId} vanished before execution`) - return - } - const group = (table.schema.workflowGroups ?? []).find((g) => g.id === groupId) - if (!group) { - await writeState({ - status: 'error', - executionId, - jobId: null, - workflowId, - error: `Workflow group ${groupId} no longer exists on this table`, - }) - return - } - const [workflowRecord] = await db .select() .from(workflowTable) @@ -88,7 +133,7 @@ export async function executeWorkflowGroupCellJob( workflowId, error: 'Workflow not found', }) - return + return 'error' } const normalizedData = await loadWorkflowFromNormalizedTables(workflowId) @@ -103,24 +148,22 @@ export async function executeWorkflowGroupCellJob( workflowId, error: 'Workflow is missing a Start trigger', }) - return + return 'error' } const row = await getRowById(tableId, rowId, workspaceId) if (!row) { logger.warn(`Row ${rowId} vanished before execution`) - return + return 'error' } - // Flip `queued` → `running` to signal the worker has actually started. - // Bail out if the cancel-sticky guard rejects the write (a stop click - // landed between enqueue and pickup). - const queuedExec = row.executions?.[groupId] as RowExecutionMetadata | undefined + // SQL guard rejects if a stop click stamped `cancelled` between enqueue + // and pickup. const pickedUp = await markWorkflowGroupPickedUp(cellCtx, { workflowId, - jobId: queuedExec?.jobId ?? null, + jobId: null, }) - if (pickedUp === 'skipped') return + if (pickedUp === 'skipped') return 'error' // Output columns produced by THIS group are skipped on input — they're // populated by the run we're starting. Other group's outputs ARE @@ -137,8 +180,6 @@ export async function executeWorkflowGroupCellJob( .filter((c) => !ownOutputColumns.has(c.name)) .map((c) => c.name) - // Spread row columns as top-level inputs so Start block fields resolve - // directly by column name; reserved metadata keys win on collision. const input = { ...inputRow, row: inputRow, @@ -156,11 +197,9 @@ export async function executeWorkflowGroupCellJob( const { pluckByPath } = await import('@/lib/table/pluck') const outputsByBlockId = buildOutputsByBlockId(group) - // Local accumulators for the run. const accumulatedData: RowData = {} const runningBlockIds = new Set() - /** Snapshot the current state and append a partial write to the chain. */ const schedulePartialWrite = () => { if (terminalWritten) return const dataSnapshot: RowData = { ...accumulatedData } @@ -169,18 +208,12 @@ export async function executeWorkflowGroupCellJob( writeChain = writeChain .then(async () => { if (signal?.aborted) return - // Re-check inside the chain — a write enqueued before the - // terminal flag flipped should still bail if the chain runs - // after the terminal write. if (terminalWritten) return await writeState( { status: 'running', executionId, - // Stamp the jobId from the current row state — the scheduler - // wrote it before this task started, and we don't want to lose - // it on partial writes. Re-read defensively. - jobId: await readJobId(), + jobId: null, workflowId, error: null, runningBlockIds: runningSnapshot, @@ -197,12 +230,6 @@ export async function executeWorkflowGroupCellJob( }) } - const readJobId = async (): Promise => { - const r = await getRowById(tableId, rowId, workspaceId) - const exec = r?.executions?.[groupId] as RowExecutionMetadata | undefined - return exec?.jobId ?? null - } - const onBlockStart = async (blockId: string): Promise => { if (!outputsByBlockId.has(blockId)) return runningBlockIds.add(blockId) @@ -213,7 +240,6 @@ export async function executeWorkflowGroupCellJob( const outputs = outputsByBlockId.get(blockId) if (!outputs) return - // executor hands us `{ input?, output: NormalizedBlockOutput, executionTime, ... }` const blockResult = output && typeof output === 'object' && 'output' in (output as object) ? (output as { output: unknown }).output @@ -231,9 +257,6 @@ export async function executeWorkflowGroupCellJob( } else { for (const out of outputs) { const plucked = pluckByPath(blockResult, out.path) - // Skip when pluck misses — assigning `undefined` would drop the - // key on JSON serialization, clearing any prior value already - // landed for this column. if (plucked === undefined) continue accumulatedData[out.columnName] = plucked as RowData[string] } @@ -257,10 +280,6 @@ export async function executeWorkflowGroupCellJob( executionMode: 'sync', workflowTriggerType: 'table', triggerBlockId: startBlock.id, - // Always run the live workflow state — table cells track the - // current editor state rather than the most recent deploy, so - // every save lands in the next row run without forcing the user - // to re-deploy. useDraftState: true, abortSignal: signal, onBlockStart, @@ -269,20 +288,10 @@ export async function executeWorkflowGroupCellJob( executionId ) - // Drain queued partial writes before the terminal write so a late - // `running` partial doesn't clobber it. Setting `terminalWritten` - // before draining means any onBlockComplete callbacks still in the - // microtask queue (the executor fires them fire-and-forget) become - // no-ops the moment they try to enqueue. terminalWritten = true await writeChain.catch(() => {}) if (result.status === 'paused') { - // HITL pause: keep the row in `pending` so the renderer surfaces it - // the same way logs do, but stamp a sentinel jobId so the scheduler's - // eligibility predicate keeps treating the row as in-flight (no - // re-enqueue while we wait on a human). Resume worker rewrites this - // back to `completed`/`error` once the pause clears. await writeState( { status: 'pending', @@ -304,7 +313,7 @@ export async function executeWorkflowGroupCellJob( workflowId, workspaceId, }) - return + return 'paused' } await writeState( @@ -319,16 +328,13 @@ export async function executeWorkflowGroupCellJob( }, accumulatedData ) + return result.success ? 'completed' : 'error' } catch (err) { const message = toError(err).message logger.error( `Workflow group cell execution failed (table=${tableId} row=${rowId} group=${groupId})`, { error: message, executionId } ) - // Drain queued partial writes before the terminal error write so a late - // `running` partial doesn't clobber it — same reason as the success - // path above. Reset `runningBlockIds`/`blockErrors` explicitly so the - // renderer sees a clean terminal state (otherwise stale spinners stay). terminalWritten = true await writeChain.catch(() => {}) try { @@ -344,6 +350,7 @@ export async function executeWorkflowGroupCellJob( } catch (writeErr) { logger.error('Also failed to write error state', { error: toError(writeErr).message }) } + return 'error' } }) } diff --git a/apps/sim/hooks/queries/tables.ts b/apps/sim/hooks/queries/tables.ts index 9d1f6993cdb..90ba8777a7b 100644 --- a/apps/sim/hooks/queries/tables.ts +++ b/apps/sim/hooks/queries/tables.ts @@ -19,6 +19,7 @@ import { isValidationError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import type { ContractJsonResponse } from '@/lib/api/contracts' import { + type ActiveDispatch, type AddWorkflowGroupBodyInput, addTableColumnContract, addWorkflowGroupContract, @@ -38,6 +39,7 @@ import { deleteWorkflowGroupContract, getTableContract, type InsertTableRowBodyInput, + listActiveDispatchesContract, listTableRowsContract, listTablesContract, type RunMode, @@ -69,7 +71,12 @@ import type { WorkflowGroupDependencies, WorkflowGroupOutput, } from '@/lib/table' -import { areOutputsFilled, optimisticallyScheduleNewlyEligibleGroups } from '@/lib/table/deps' +import { + areGroupDepsSatisfied, + areOutputsFilled, + isExecInFlight, + optimisticallyScheduleNewlyEligibleGroups, +} from '@/lib/table/deps' const logger = createLogger('TableQueries') @@ -86,6 +93,8 @@ export const tableKeys = { infiniteRows: (tableId: string, paramsKey: string) => [...tableKeys.rowsRoot(tableId), 'infinite', paramsKey] as const, rowWrites: (tableId: string) => [...tableKeys.rowsRoot(tableId), 'write'] as const, + activeDispatches: (tableId: string) => + [...tableKeys.detail(tableId), 'active-dispatches'] as const, } type TableRowsParams = Omit & @@ -153,10 +162,6 @@ async function fetchTableRows({ return { rows, totalCount } } -function invalidateRowData(queryClient: ReturnType, tableId: string) { - queryClient.invalidateQueries({ queryKey: tableKeys.rowsRoot(tableId) }) -} - function invalidateRowCount(queryClient: ReturnType, tableId: string) { queryClient.invalidateQueries({ queryKey: tableKeys.rowsRoot(tableId) }) queryClient.invalidateQueries({ queryKey: tableKeys.detail(tableId) }) @@ -202,6 +207,40 @@ export function useTable(workspaceId: string | undefined, tableId: string | unde }) } +export interface TableRunState { + dispatches: ActiveDispatch[] + runningCellCount: number + runningByRowId: Record +} + +async function fetchTableRunState(tableId: string, signal?: AbortSignal): Promise { + const response = await requestJson(listActiveDispatchesContract, { + params: { tableId }, + signal, + }) + return { + dispatches: response.data.dispatches, + runningCellCount: response.data.runningCellCount, + runningByRowId: response.data.runningByRowId, + } +} + +/** + * Aggregate live state for a table: active dispatches (drives the "about to + * run" overlay), the running-cell count (top-right counter), and per-row + * running counts (per-row badge). Bootstrap snapshot fetched once on mount; + * SSE `kind: 'cell'` and `kind: 'dispatch'` events incrementally update the + * same cache. + */ +export function useTableRunState(tableId: string | undefined) { + return useQuery({ + queryKey: tableKeys.activeDispatches(tableId ?? ''), + queryFn: ({ signal }) => fetchTableRunState(tableId as string, signal), + enabled: Boolean(tableId), + staleTime: 30 * 1000, + }) +} + interface InfiniteTableRowsParams { workspaceId: string tableId: string @@ -409,7 +448,11 @@ export function useCreateTableRow({ workspaceId, tableId }: RowMutationContext) const row = response.data.row if (!row) return - reconcileCreatedRow(queryClient, tableId, row) + const groups = + queryClient.getQueryData(tableKeys.detail(tableId))?.schema + .workflowGroups ?? [] + const stamped = withOptimisticAutoFireExec(groups, row) + reconcileCreatedRow(queryClient, tableId, stamped) }, onError: (error) => { if (isValidationError(error)) return @@ -421,6 +464,19 @@ export function useCreateTableRow({ workspaceId, tableId }: RowMutationContext) }) } +/** + * Pre-stamp `pending` for any auto-fire-eligible workflow groups on a row that + * was just inserted server-side. Mirrors the server's `mode: 'new'` dispatch: + * the server will fire these groups in the background; the optimistic stamp + * shows the user a `queued` badge immediately rather than waiting ~1s for the + * first SSE event. + */ +function withOptimisticAutoFireExec(groups: WorkflowGroup[], row: TableRow): TableRow { + const nextExecutions = optimisticallyScheduleNewlyEligibleGroups(groups, row, {}) + if (!nextExecutions) return row + return { ...row, executions: nextExecutions } +} + /** * Apply a row-level transformation to all cached infinite row queries for this * table. Used for cell edits where positions don't change. @@ -602,11 +658,6 @@ export function useUpdateTableRow({ workspaceId, tableId }: RowMutationContext) if (isValidationError(error)) return toast.error(error.message, { duration: 5000 }) }, - onSettled: () => { - if (queryClient.isMutating({ mutationKey: tableKeys.rowWrites(tableId) }) === 1) { - invalidateRowData(queryClient, tableId) - } - }, }) } @@ -666,11 +717,6 @@ export function useBatchUpdateTableRows({ workspaceId, tableId }: RowMutationCon if (isValidationError(error)) return toast.error(error.message, { duration: 5000 }) }, - onSettled: () => { - if (queryClient.isMutating({ mutationKey: tableKeys.rowWrites(tableId) }) === 1) { - invalidateRowData(queryClient, tableId) - } - }, }) } @@ -869,13 +915,19 @@ export function useCancelTableRuns({ workspaceId, tableId }: RowMutationContext) const nextExecutions: RowExecutions = { ...executions } for (const gid in executions) { const exec = executions[gid] - if (!isOptimisticInFlight(exec)) continue - // Preserve blockErrors so cells that already errored keep their - // Error rendering after the stop — only cells without a value or - // error should flip to "Cancelled". + if (!isExecInFlight(exec)) continue + if (exec.executionId == null) { + // Optimistic-only or dispatcher-pre-stamp pending — server has not + // claimed the cell yet, so no SSE will arrive to reconcile a + // `cancelled` stamp. Strip the entry instead and let the renderer + // fall through to the cell's prior state (value / empty / etc.). + delete nextExecutions[gid] + rowTouched = true + continue + } nextExecutions[gid] = { status: 'cancelled', - executionId: exec.executionId ?? null, + executionId: exec.executionId, jobId: null, workflowId: exec.workflowId, error: 'Cancelled', @@ -892,6 +944,11 @@ export function useCancelTableRuns({ workspaceId, tableId }: RowMutationContext) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: tableKeys.rowsRoot(tableId) }) + // Refetch the run-state snapshot — server re-derives runningCellCount + + // runningByRowId from the freshly-updated sidecar via countRunningCells. + // Without this, the counter and row gutter button stay stale until the + // user refetches manually. + queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) }) }, }) } @@ -1251,14 +1308,6 @@ function buildPendingExec( } } -/** Broader sibling of `isExecInFlight` from `lib/table/deps`: treats any - * `pending` (with or without a jobId) as in-flight. The optimistic-patch - * context uses this to avoid re-marking a cell we just flipped optimistically. - * The eligibility predicate uses the stricter version. */ -function isOptimisticInFlight(exec: RowExecutionMetadata | undefined): boolean { - return exec?.status === 'running' || exec?.status === 'queued' || exec?.status === 'pending' -} - /** * The single canonical run mutation. Every UI gesture (single cell, per-row * Play, action-bar Play/Refresh, column-header menu) maps to a `groupIds` + @@ -1292,32 +1341,47 @@ export function useRunColumn({ workspaceId, tableId }: RowMutationContext) { const executions = r.executions ?? {} let changed = false const next: RowExecutions = { ...executions } + const nextData = { ...r.data } for (const groupId of targetGroupIds) { const exec = executions[groupId] as RowExecutionMetadata | undefined - if (isOptimisticInFlight(exec)) continue + if (isExecInFlight(exec)) continue + const group = groupsById.get(groupId) + // Mirror server eligibility: rows with unmet deps are skipped by the + // dispatcher regardless of mode. Stamping pending here would leave + // the cell flashing Queued indefinitely (no SSE event will arrive). + if (group && !areGroupDepsSatisfied(group, r)) continue // Mirror server eligibility for `mode: 'incomplete'`: skip cells whose // outputs are filled, regardless of exec status. A cancelled/error // cell with a leftover value from a prior run was rendering as filled // but flipping to "queued" optimistically here even though the server // would skip it. - if (runMode === 'incomplete') { - const group = groupsById.get(groupId) - if (group && areOutputsFilled(group, r)) continue - } + if (runMode === 'incomplete' && group && areOutputsFilled(group, r)) continue next[groupId] = buildPendingExec(exec) + // Mirror the server-side bulk clear: wipe output values so the cell + // doesn't render the stale completed value behind a pending badge. + // Without this the cell-render path's "value wins" branch keeps + // showing the previous run's output and the Queued/Running pill + // never appears. + if (group) { + for (const o of group.outputs) { + if (o.columnName in nextData) nextData[o.columnName] = null + } + } changed = true } if (!changed) return null - return { ...r, executions: next } + return { ...r, data: nextData, executions: next } }) return { snapshots } }, onError: (_err, _variables, context) => { if (context?.snapshots) restoreCachedWorkflowCells(queryClient, context.snapshots) }, - // No reconciliation here — useTableEventStream is the source of truth for - // post-mutation cache state, and a refetch would race its incremental - // patches. + onSuccess: () => { + // Seed the active-dispatch overlay immediately (insertDispatch ran + // server-side before responding); rows cache stays owned by SSE. + void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) }) + }, }) } diff --git a/apps/sim/lib/api/contracts/tables.ts b/apps/sim/lib/api/contracts/tables.ts index db7ecbb57ad..18f23ae67e3 100644 --- a/apps/sim/lib/api/contracts/tables.ts +++ b/apps/sim/lib/api/contracts/tables.ts @@ -898,11 +898,13 @@ export const runColumnContract = defineRouteContract({ response: { mode: 'json', /** - * `triggered` is `null` when the dispatcher runs in the background — the - * actual count is only known after a fan-out that may be tens of thousands - * of rows, and we don't hold the HTTP response open for that long. + * `dispatchId` is the id of the `table_run_dispatches` row created for + * this run. The dispatcher task picks it up and crawls the table row by + * row; clients receive cell + dispatch events via SSE. Null when + * trigger.dev is disabled — in that mode cells run inline in-process and + * no dispatch row is created. */ - schema: successResponseSchema(z.object({ triggered: z.number().nullable() })), + schema: successResponseSchema(z.object({ dispatchId: z.string().min(1).nullable() })), }, }) @@ -915,6 +917,47 @@ export type RunColumnBodyInput = z.input * builds a run-column payload. Single source of truth for the literal pair. */ export type RunMode = NonNullable +/** + * Active dispatch overlay: rows in the scope ahead of `cursor` render as + * `pending` on refresh, so a long Run-all doesn't lose its queued indicators. + * Returned by `GET /api/table/[tableId]/dispatches`; mirrored client-side via + * `kind: 'dispatch'` SSE events. + */ +export const activeDispatchSchema = z.object({ + id: z.string(), + status: z.enum(['pending', 'dispatching']), + mode: z.enum(['all', 'incomplete', 'new']), + isManualRun: z.boolean(), + cursor: z.number().int(), + scope: z.object({ + groupIds: z.array(z.string()), + rowIds: z.array(z.string()).optional(), + }), +}) + +export const listActiveDispatchesContract = defineRouteContract({ + method: 'GET', + path: '/api/table/[tableId]/dispatches', + params: tableIdParamsSchema, + response: { + mode: 'json', + schema: successResponseSchema( + z.object({ + dispatches: z.array(activeDispatchSchema), + /** Total cells across the table whose `status === 'running'`. The + * client maintains this incrementally via cell SSE events; this + * field is the bootstrap snapshot on mount. */ + runningCellCount: z.number().int().nonnegative(), + /** Map rowId → number of running cells on that row. Drives the + * per-row badge next to the Stop button. */ + runningByRowId: z.record(z.string(), z.number().int().positive()), + }) + ), + }, +}) + +export type ActiveDispatch = z.output + export const tableEventStreamQuerySchema = z.object({ from: z.preprocess((value) => { if (typeof value !== 'string') return 0 diff --git a/apps/sim/lib/copilot/tools/server/table/user-table.ts b/apps/sim/lib/copilot/tools/server/table/user-table.ts index e6464e87afd..8090f40c5dd 100644 --- a/apps/sim/lib/copilot/tools/server/table/user-table.ts +++ b/apps/sim/lib/copilot/tools/server/table/user-table.ts @@ -525,6 +525,11 @@ export const userTableServerTool: BaseServerTool // doesn't, so the guard never trips here. Defensive narrowing. return { success: false, message: 'Row update was skipped' } } + // Auto-dispatch for user edits is handled inside `updateRow` + // (mode: 'new' for newly-cleared groups + cancel+rerun for in-flight + // downstream groups). Firing a second mode: 'incomplete' dispatch + // here would race with the internal one AND bulk-clear sibling-group + // outputs (mode: 'incomplete' wipes terminal-state cells in scope). return { success: true, @@ -1415,24 +1420,19 @@ export const userTableServerTool: BaseServerTool } const requestId = generateId().slice(0, 8) assertNotAborted() - // Dispatch in the background — large fan-outs (thousands of rows) - // issue sequential trigger.dev calls and would otherwise hold the - // tool span open for minutes, blocking the chat connection. - void runWorkflowColumn({ + const { dispatchId } = await runWorkflowColumn({ tableId: args.tableId, workspaceId, groupIds, mode: runMode, rowIds, requestId, - }).catch((err) => { - logger.error(`[${requestId}] run_column dispatch failed`, err) }) const scopeLabel = rowIds ? `${rowIds.length} row(s) by id` : runMode return { success: true, message: `Started running ${groupIds.length} column(s) (${scopeLabel}). Cells will populate as workflows complete.`, - data: { triggered: null }, + data: { dispatchId }, } } diff --git a/apps/sim/lib/core/async-jobs/backends/database.ts b/apps/sim/lib/core/async-jobs/backends/database.ts index afaa44a3641..aeedfbd3878 100644 --- a/apps/sim/lib/core/async-jobs/backends/database.ts +++ b/apps/sim/lib/core/async-jobs/backends/database.ts @@ -1,7 +1,7 @@ import { asyncJobs, db } from '@sim/db' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' +import { generateShortId } from '@sim/utils/id' import { eq, sql } from 'drizzle-orm' import { type EnqueueOptions, @@ -37,6 +37,14 @@ function rowToJob(row: AsyncJobRow): Job { const inlineAbortControllers = new Map() +/** + * Per-cancel-key abort controllers for the `batchEnqueueAndWait` direct-call + * path. Distinct from `inlineAbortControllers` (which keys by jobId) — this + * map keys by the domain `cancelKey` callers pass in, since the await-blocking + * path skips `async_jobs` entirely and has no jobId to cancel by. + */ +const inlineCancelKeyControllers = new Map() + interface Semaphore { available: number waiters: Array<() => void> @@ -73,7 +81,7 @@ export class DatabaseJobQueue implements JobQueueBackend { payload: TPayload, options?: EnqueueOptions ): Promise { - const jobId = options?.jobId ?? `run_${generateId().replace(/-/g, '').slice(0, 20)}` + const jobId = options?.jobId ?? `run_${generateShortId(20)}` const now = new Date() await db @@ -112,7 +120,7 @@ export class DatabaseJobQueue implements JobQueueBackend { if (items.length === 0) return [] const now = new Date() const rows = items.map(({ payload, options }) => ({ - id: `run_${generateId().replace(/-/g, '').slice(0, 20)}`, + id: `run_${generateShortId(20)}`, type, payload: payload as Record, status: JOB_STATUS.PENDING, @@ -144,6 +152,44 @@ export class DatabaseJobQueue implements JobQueueBackend { return rows.map((r) => r.id) } + /** Skips `async_jobs` entirely — ids are returned empty since callers can't + * look up rows that don't exist. Cancel goes through `cancelByKey`. */ + async batchEnqueueAndWait( + type: JobType, + items: Array<{ payload: TPayload; options?: EnqueueOptions }> + ): Promise { + if (items.length === 0) return [] + const tracked: Array<{ key: string; controller: AbortController }> = [] + const runs = items.map((item) => { + const runner = item.options?.runner + if (!runner) return Promise.resolve() + const controller = new AbortController() + const cancelKey = item.options?.cancelKey + if (cancelKey) { + inlineCancelKeyControllers.set(cancelKey, controller) + tracked.push({ key: cancelKey, controller }) + } + return runner(item.payload, controller.signal).catch((err) => { + logger.error(`[${type}] Inline run failed`, { + cancelKey, + error: toError(err).message, + }) + }) + }) + try { + await Promise.all(runs) + } finally { + // Compare-and-delete guards against a re-enqueue under the same key + // racing with our cleanup. + for (const t of tracked) { + if (inlineCancelKeyControllers.get(t.key) === t.controller) { + inlineCancelKeyControllers.delete(t.key) + } + } + } + return items.map(() => '') + } + async getJob(jobId: string): Promise { const [row] = await db.select().from(asyncJobs).where(eq(asyncJobs.id, jobId)).limit(1) @@ -224,6 +270,14 @@ export class DatabaseJobQueue implements JobQueueBackend { logger.debug('Marked job as cancelled (DB queue)', { jobId, abortedInline: aborted }) } + cancelByKey(cancelKey: string): boolean { + const controller = inlineCancelKeyControllers.get(cancelKey) + if (!controller) return false + controller.abort('Cancelled') + inlineCancelKeyControllers.delete(cancelKey) + return true + } + /** * Fire-and-forget IIFE that owns the lifecycle for an inline job: registers * the abort controller (so `cancelJob` can interrupt mid-flight), acquires diff --git a/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts b/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts index 97a7428b310..ef0fcaed65f 100644 --- a/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts +++ b/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { taskContext } from '@trigger.dev/core/v3' import { runs, type TriggerOptions, tasks } from '@trigger.dev/sdk' import { type EnqueueOptions, @@ -103,6 +104,50 @@ export class TriggerDevJobQueue implements JobQueueBackend { return ids } + async batchEnqueueAndWait( + type: JobType, + items: Array<{ payload: TPayload; options?: EnqueueOptions }> + ): Promise { + if (items.length === 0) return [] + // The SDK's checkpoint-and-resume requires task runtime context. The only + // caller (`dispatcherStep` invoked by `tableRunDispatcherTask.run`) is + // always inside a task; check defensively so misuse fails at the boundary + // instead of as a confusing SDK internal error. + if (!taskContext.isInsideTask) { + throw new Error( + 'batchEnqueueAndWait requires trigger.dev task runtime context — call from within a registered task' + ) + } + + const taskId = JOB_TYPE_TO_TASK_ID[type] + if (!taskId) throw new Error(`Unknown job type: ${type}`) + + const batchItems = items.map(({ payload, options }) => { + const enrichedPayload = + options?.metadata && typeof payload === 'object' && payload !== null + ? { ...payload, ...options.metadata } + : payload + const tags = buildTags(options) + const batchItem: { + payload: unknown + options?: { concurrencyKey?: string; tags?: string[] } + } = { payload: enrichedPayload } + const batchOpts: { concurrencyKey?: string; tags?: string[] } = {} + if (options?.concurrencyKey) batchOpts.concurrencyKey = options.concurrencyKey + if (tags.length > 0) batchOpts.tags = tags + if (Object.keys(batchOpts).length > 0) batchItem.options = batchOpts + return batchItem + }) + + const result = await tasks.batchTriggerAndWait(taskId, batchItems) + logger.debug('batchTriggerAndWait completed', { + type, + taskId, + runCount: result.runs.length, + }) + return result.runs.map((r) => r.id) + } + async getJob(jobId: string): Promise { try { const run = await runs.retrieve(jobId) @@ -168,6 +213,13 @@ export class TriggerDevJobQueue implements JobQueueBackend { throw error } } + + cancelByKey(_cancelKey: string): boolean { + // No in-process AbortControllers to abort — trigger.dev runs are cancelled + // by jobId or via tag sweep (see `cancelCellRunsByTags`). Callers that + // need both surfaces should fan out themselves. + return false + } } /** diff --git a/apps/sim/lib/core/async-jobs/config.ts b/apps/sim/lib/core/async-jobs/config.ts index b28f5453ea4..5d32dc6fcd8 100644 --- a/apps/sim/lib/core/async-jobs/config.ts +++ b/apps/sim/lib/core/async-jobs/config.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { taskContext } from '@trigger.dev/core/v3' import type { AsyncBackendType, JobQueueBackend } from '@/lib/core/async-jobs/types' import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' @@ -10,10 +11,15 @@ let cachedInlineBackend: JobQueueBackend | null = null /** * Determines which async backend to use based on environment configuration. - * Follows the fallback chain: trigger.dev → database + * Falls back to the database backend when trigger.dev isn't enabled — except + * when this process IS a trigger.dev worker (`taskContext.isInsideTask`), in + * which case the SDK runtime is available regardless of env vars and we + * always want to enqueue back through trigger.dev. Without this carve-out, a + * worker pod missing `TRIGGER_DEV_ENABLED=true` silently routes cell jobs to + * the database backend that nothing's draining. */ export function getAsyncBackendType(): AsyncBackendType { - if (isTriggerDevEnabled) { + if (isTriggerDevEnabled || taskContext.isInsideTask) { return 'trigger-dev' } diff --git a/apps/sim/lib/core/async-jobs/types.ts b/apps/sim/lib/core/async-jobs/types.ts index e1d2c411313..3acb9927c25 100644 --- a/apps/sim/lib/core/async-jobs/types.ts +++ b/apps/sim/lib/core/async-jobs/types.ts @@ -96,6 +96,14 @@ export interface EnqueueOptions { * payload and an `AbortSignal` driven by `cancelJob`. */ runner?: (payload: TPayload, signal: AbortSignal) => Promise + /** + * Stable identity for cancellation lookups on the database backend's + * `batchEnqueueAndWait` path (which skips `async_jobs` entirely, so there + * is no jobId to cancel by). Lets callers map a domain identity (e.g. + * `tableId:rowId:groupId`) to the in-flight `AbortController`. Ignored + * by trigger.dev — runs there are cancelled by tag or jobId. + */ + cancelKey?: string } /** @@ -118,6 +126,28 @@ export interface JobQueueBackend { items: Array<{ payload: TPayload; options?: EnqueueOptions }> ): Promise + /** + * Enqueue a batch and block until every job has reached a terminal state + * (completed, failed, or cancelled). The caller — typically a dispatcher + * walking work in windows — uses this to gate window N+1 on window N's + * completion. + * + * Backend implementations: + * - Trigger.dev: wraps `tasks.batchTriggerAndWait`. MUST be called from + * inside a registered trigger.dev task (the SDK's checkpoint-and-resume + * requires task runtime context). Backends guard with + * `taskContext.isInsideTask` and throw a clear error otherwise. + * - Database (in-process): bypasses `async_jobs` entirely. Since the + * caller is awaiting in-process, the row would serve no live purpose + * (no cross-process recovery, no by-id lookup, no semaphore needed — + * window size IS the concurrency cap). Calls the runner directly via + * `Promise.all` and resolves on the runner's exit. + */ + batchEnqueueAndWait( + type: JobType, + items: Array<{ payload: TPayload; options?: EnqueueOptions }> + ): Promise + /** * Get a job by ID */ @@ -144,6 +174,15 @@ export interface JobQueueBackend { * should resolve quietly so callers can drive cancel from possibly-stale state. */ cancelJob(jobId: string): Promise + + /** + * Cancel an in-flight job by its `cancelKey` (the domain identity callers + * stamped on enqueue via `EnqueueOptions.cancelKey`). Used by + * `batchEnqueueAndWait` paths that skip per-job ids; the trigger.dev + * backend has no in-process AbortControllers to abort and returns `false`. + * Returns `true` if a matching controller was found and aborted. + */ + cancelByKey(cancelKey: string): boolean } export type AsyncBackendType = 'trigger-dev' | 'database' diff --git a/apps/sim/lib/table/cascade-lock.ts b/apps/sim/lib/table/cascade-lock.ts new file mode 100644 index 00000000000..cfdb4702c5f --- /dev/null +++ b/apps/sim/lib/table/cascade-lock.ts @@ -0,0 +1,61 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { acquireLock, extendLock, releaseLock } from '@/lib/core/config/redis' + +const logger = createLogger('TableCascadeLock') + +/** Lock TTL. Crashed pods release within this many seconds. */ +const LOCK_TTL_SECONDS = 30 +/** Heartbeat cadence. ~3x within TTL — tolerates two missed beats. */ +const HEARTBEAT_INTERVAL_MS = 10_000 + +/** Single source of truth for the cascade-lock key shape. The lock arbitrates + * ownership of a row's full workflow-group cascade — only the owner advances + * the row through its eligible groups. */ +export function cascadeLockKey(tableId: string, rowId: string): string { + return `table:cascade:${tableId}:${rowId}` +} + +/** + * Run `fn` while holding the row's cascade lock, with a heartbeat extending + * the TTL every 10s so a crashed pod releases the lock in ≤30s. `ownerId` + * must be unique per holder (typically the cell-task's `executionId`) so + * `releaseLock` does compare-and-delete and can't accidentally drop another + * owner's lock. + * + * Returns `'acquired'` after `fn` resolves, or `'contended'` if another + * task already holds the lock — `fn` is NOT invoked in that case. The + * caller decides what to do on contention (cell-task bails; resume worker + * still writes the resumed-group's terminal state but skips the cascade). + * + * NOTE: when Redis is unavailable, `acquireLock` returns `true` as a + * single-replica fallback — concurrent cell-tasks would all "acquire" and + * run in parallel. The cell-write SQL guard mitigates double-writes but + * doesn't prevent duplicate workflow executions. + */ +export async function withCascadeLock( + tableId: string, + rowId: string, + ownerId: string, + fn: () => Promise +): Promise<{ status: 'acquired'; result: T } | { status: 'contended' }> { + const key = cascadeLockKey(tableId, rowId) + const acquired = await acquireLock(key, ownerId, LOCK_TTL_SECONDS) + if (!acquired) return { status: 'contended' } + + const heartbeat = setInterval(() => { + extendLock(key, ownerId, LOCK_TTL_SECONDS).catch((err) => { + logger.warn(`Heartbeat refresh failed for ${key}`, { error: toError(err).message }) + }) + }, HEARTBEAT_INTERVAL_MS) + + try { + const result = await fn() + return { status: 'acquired', result } + } finally { + clearInterval(heartbeat) + await releaseLock(key, ownerId).catch((err) => { + logger.warn(`Lock release failed for ${key}`, { error: toError(err).message }) + }) + } +} diff --git a/apps/sim/lib/table/cell-write.ts b/apps/sim/lib/table/cell-write.ts index 5e179319f9e..c2655a1e745 100644 --- a/apps/sim/lib/table/cell-write.ts +++ b/apps/sim/lib/table/cell-write.ts @@ -161,7 +161,9 @@ export async function markWorkflowGroupPickedUp( /** Builds the canonical `cancelled` execution state used by every cancel path. * Preserves `blockErrors` from the prior state so errored cells keep * rendering Error after a stop click — only cells that hadn't yet produced - * a value or an error should flip to "Cancelled". */ + * a value or an error should flip to "Cancelled". `cancelledAt` is the + * tombstone the dispatcher reads to skip re-runs of cells the user killed + * mid-cascade. */ export function buildCancelledExecution( prev: Pick ): RowExecutionMetadata { @@ -171,6 +173,7 @@ export function buildCancelledExecution( jobId: null, workflowId: prev.workflowId, error: 'Cancelled', + cancelledAt: new Date().toISOString(), ...(prev.blockErrors ? { blockErrors: prev.blockErrors } : {}), } } diff --git a/apps/sim/lib/table/deps.ts b/apps/sim/lib/table/deps.ts index d9b33f59dbb..9cc19293a3f 100644 --- a/apps/sim/lib/table/deps.ts +++ b/apps/sim/lib/table/deps.ts @@ -10,18 +10,18 @@ import type { RowData, RowExecutionMetadata, RowExecutions, TableRow, WorkflowGr const logger = createLogger('OptimisticCascade') /** - * True when the cell has a worker actively reserved — `queued` / `running`, - * or `pending` after the scheduler stamped a jobId. Single source of truth - * for the "is this exec in flight" classification across the eligibility - * predicate, optimistic patches, status counters, and renderer. `pending` - * without a jobId is the optimistic-flag-only state, not in-flight. + * True when the cell is `pending` / `queued` / `running`. Single source of + * truth for the "is this exec in flight" classification across the + * eligibility predicate, optimistic patches, status counters, and renderer. + * `pending` counts even without a jobId so the row-gutter Stop button is + * available the moment the user clicks Play — the cancel path writes + * `cancelled` authoritatively whether or not a real trigger.dev run exists + * yet, which is correct: cancel means "don't run this." */ export function isExecInFlight(exec: RowExecutionMetadata | undefined): boolean { if (!exec) return false const s = exec.status - if (s === 'queued' || s === 'running') return true - if (s === 'pending' && exec.jobId) return true - return false + return s === 'queued' || s === 'running' || s === 'pending' } /** @@ -75,10 +75,10 @@ export function getUnmetGroupDeps(group: WorkflowGroup, row: TableRow): UnmetDep /** * Optimistic mirror of the server's row-update→scheduler cascade: for every * workflow group whose deps were unmet *before* the patch and are satisfied - * *after*, return a new `executions` map with that group flipped to - * `pending`. The cell renderer treats `pending` as "Queued", which is what - * the user expects to see immediately after they fill in the missing input — - * not a flash of dash before the server's pending write arrives. + * *after*, OR whose dep column was touched by the patch (the server will + * cancel+re-run via `deriveExecClearsForDataPatch` + the in-flight cancel + * orchestration), return a new `executions` map with that group flipped to + * `pending`. The cell renderer treats `pending` as "Queued". * * Returns `null` when nothing changed, so callers can short-circuit. */ @@ -93,6 +93,7 @@ export function optimisticallyScheduleNewlyEligibleGroups( ...beforeRow, data: { ...beforeRow.data, ...patch } as RowData, } + const patchedColumns = new Set(Object.keys(patch)) let next: RowExecutions | null = null let flipped = 0 @@ -108,10 +109,6 @@ export function optimisticallyScheduleNewlyEligibleGroups( } const exec = beforeRow.executions?.[group.id] - if (exec?.status === 'queued' || exec?.status === 'running') { - skipped++ - continue - } if (exec?.status === 'pending' && exec.jobId) { skipped++ continue @@ -121,7 +118,17 @@ export function optimisticallyScheduleNewlyEligibleGroups( const wasSatisfied = areGroupDepsSatisfied(group, beforeRow) const becameSatisfied = !wasSatisfied const isRetryable = exec?.status === 'cancelled' || exec?.status === 'error' - if (!becameSatisfied && !isStaleCompleted && !isRetryable && exec) { + // Dep-column touched: the server clears terminal entries + cancels in- + // flight downstream groups, so optimistically flip to `pending` + // regardless of current exec status (queued/running included — they're + // about to be cancelled and re-run). + const depTouched = (group.dependencies?.columns ?? []).some((d) => patchedColumns.has(d)) + + if (!depTouched && (exec?.status === 'queued' || exec?.status === 'running')) { + skipped++ + continue + } + if (!becameSatisfied && !isStaleCompleted && !isRetryable && !depTouched && exec) { skipped++ continue } diff --git a/apps/sim/lib/table/dispatcher.ts b/apps/sim/lib/table/dispatcher.ts new file mode 100644 index 00000000000..ab91ff67cb4 --- /dev/null +++ b/apps/sim/lib/table/dispatcher.ts @@ -0,0 +1,543 @@ +import { db } from '@sim/db' +import { tableRowExecutions, tableRunDispatches, userTableRows } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { and, asc, eq, gt, inArray, type SQL, sql } from 'drizzle-orm' +import { getJobQueue } from '@/lib/core/async-jobs/config' +import { writeWorkflowGroupState } from '@/lib/table/cell-write' +import { appendTableEvent } from '@/lib/table/events' +import type { RowExecutionMetadata, RowExecutions, TableRow } from '@/lib/table/types' +import { + buildEnqueueItems, + buildPendingRuns, + TABLE_CONCURRENCY_LIMIT, + toTableRow, + type WorkflowGroupCellPayload, +} from './workflow-columns' + +const logger = createLogger('TableRunDispatcher') + +/** Window size matches the cell-execution concurrency cap so one window + * saturates the pool before the next is loaded — yields a row-major + * scan-line crawl (rows 1-20 finish before 21-40 start). */ +const WINDOW_SIZE = TABLE_CONCURRENCY_LIMIT + +const ACTIVE_DISPATCH_STATUSES = ['pending', 'dispatching'] as const + +export type DispatchStatus = 'pending' | 'dispatching' | 'complete' | 'cancelled' +export type DispatchMode = 'all' | 'incomplete' | 'new' + +export interface DispatchScope { + groupIds: string[] + rowIds?: string[] +} + +export interface DispatchRow { + id: string + tableId: string + workspaceId: string + requestId: string + mode: DispatchMode + scope: DispatchScope + status: DispatchStatus + cursor: number + isManualRun: boolean + requestedAt: Date +} + +export type DispatcherStepResult = 'continue' | 'done' + +/** Eager bulk clear at click time so the user sees every targeted cell go + * blank/Pending instantly — without it, only the rows the dispatcher has + * reached visibly change, and the rest sit on stale data until the cursor + * walks to them. For `mode: 'incomplete'` we skip rows whose outputs are + * already filled, mirroring the eligibility predicate. */ +export async function bulkClearWorkflowGroupCells(input: { + tableId: string + groups: Array<{ id: string; outputs: Array<{ columnName: string }> }> + rowIds?: string[] + mode: DispatchMode +}): Promise { + const { tableId, groups, rowIds, mode } = input + if (groups.length === 0) return + // `'new'` mode targets only rows with no prior attempt — nothing to clear. + // Pre-existing outputs on any other row must not be wiped by an auto-fire. + if (mode === 'new') return + + const outputCols = Array.from(new Set(groups.flatMap((g) => g.outputs.map((o) => o.columnName)))) + const groupIds = groups.map((g) => g.id) + + // Step 1: clear the targeted output columns from `data` on every row in + // scope. Identical chain to the previous JSONB-only path. + let dataExpr: SQL = sql`coalesce(${userTableRows.data}, '{}'::jsonb)` + for (const col of outputCols) dataExpr = sql`(${dataExpr}) - ${col}::text` + + const filters: SQL[] = [eq(userTableRows.tableId, tableId)] + if (rowIds && rowIds.length > 0) { + filters.push(inArray(userTableRows.id, rowIds)) + } + if (mode === 'incomplete') { + // Skip rows where all output columns across all targeted groups already + // have a non-empty value — those are "completed-and-filled" and the + // eligibility predicate would skip them anyway. + const filledChecks = outputCols.map( + (col) => sql`coalesce(${userTableRows.data} ->> ${col}, '') != ''` + ) + const allFilled = filledChecks.reduce((acc, expr) => sql`${acc} AND ${expr}`) + filters.push(sql`NOT (${allFilled})`) + // Also skip rows where ANY targeted group has an in-flight exec — those + // belong to another dispatch and clobbering them would race. Encoded as + // a NOT EXISTS subquery against the sidecar's `(table_id, status)` + // partial index. + filters.push( + sql`NOT EXISTS ( + SELECT 1 FROM ${tableRowExecutions} re + WHERE re.row_id = ${userTableRows.id} + AND re.group_id = ANY(ARRAY[${sql.join( + groupIds.map((gid) => sql`${gid}`), + sql`, ` + )}]::text[]) + AND re.status IN ('queued', 'running', 'pending') + )` + ) + } + + await db.transaction(async (trx) => { + await trx + .update(userTableRows) + .set({ data: dataExpr, updatedAt: new Date() }) + .where(and(...filters)) + + // Step 2: delete the targeted groups' executions for the rows in scope. + // Reuse the same row-scope filter via a subquery. + const execFilters: SQL[] = [ + eq(tableRowExecutions.tableId, tableId), + inArray(tableRowExecutions.groupId, groupIds), + ] + if (rowIds && rowIds.length > 0) { + execFilters.push(inArray(tableRowExecutions.rowId, rowIds)) + } + if (mode === 'incomplete') { + // For `incomplete`, only delete entries that aren't already in-flight + // — terminal states (completed/error/cancelled) get wiped so the + // dispatcher re-enqueues; in-flight entries stay so we don't race + // with their worker. + execFilters.push(sql`${tableRowExecutions.status} NOT IN ('queued', 'running', 'pending')`) + } + await trx.delete(tableRowExecutions).where(and(...execFilters)) + }) +} + +export async function insertDispatch(input: { + tableId: string + workspaceId: string + requestId: string + mode: DispatchMode + scope: DispatchScope + isManualRun: boolean +}): Promise { + const id = `tdsp_${generateId().replace(/-/g, '')}` + await db.insert(tableRunDispatches).values({ + id, + tableId: input.tableId, + workspaceId: input.workspaceId, + requestId: input.requestId, + mode: input.mode, + scope: input.scope, + status: 'pending', + // -1 = "haven't started." First window's filter `position > -1` matches + // position 0; subsequent iterations advance to `lastPosition` which then + // correctly excludes already-processed rows. + cursor: -1, + isManualRun: input.isManualRun, + }) + return id +} + +/** Read every dispatch on a table whose status is still `pending` or + * `dispatching`. Drives the client-side "about to run" overlay: rows in an + * active dispatch's scope ahead of its cursor are rendered as queued even + * before the dispatcher has reached them, so refresh during a long Run-all + * doesn't lose the queued indicators. */ +/** Counts in-flight cells (queued / running / pending) across the entire + * table — the authoritative source for the "X running" badge and the per-row + * gutter Run/Stop button. All three statuses are user-cancellable, so the + * gutter must surface Stop whenever any of them are present (else clicking + * Play during the queued window would re-run an already-queued cell). + * Hits the `(table_id, status)` partial index on table_row_executions. */ +export async function countRunningCells( + tableId: string +): Promise<{ total: number; byRowId: Record }> { + const rows = await db + .select({ + rowId: tableRowExecutions.rowId, + runningCount: sql`count(*)::int`, + }) + .from(tableRowExecutions) + .where( + and( + eq(tableRowExecutions.tableId, tableId), + inArray(tableRowExecutions.status, ['queued', 'running', 'pending']) + ) + ) + .groupBy(tableRowExecutions.rowId) + let total = 0 + const byRowId: Record = {} + for (const r of rows) { + if (r.runningCount > 0) { + byRowId[r.rowId] = r.runningCount + total += r.runningCount + } + } + return { total, byRowId } +} + +export async function listActiveDispatches(tableId: string): Promise { + const rows = await db + .select() + .from(tableRunDispatches) + .where( + and( + eq(tableRunDispatches.tableId, tableId), + inArray(tableRunDispatches.status, [...ACTIVE_DISPATCH_STATUSES]) + ) + ) + return rows.map((row) => ({ + id: row.id, + tableId: row.tableId, + workspaceId: row.workspaceId, + requestId: row.requestId, + mode: row.mode as DispatchMode, + scope: row.scope as DispatchScope, + status: row.status as DispatchStatus, + cursor: row.cursor, + isManualRun: row.isManualRun, + requestedAt: row.requestedAt, + })) +} + +export async function readDispatch(dispatchId: string): Promise { + const [row] = await db + .select() + .from(tableRunDispatches) + .where(eq(tableRunDispatches.id, dispatchId)) + .limit(1) + if (!row) return null + return { + id: row.id, + tableId: row.tableId, + workspaceId: row.workspaceId, + requestId: row.requestId, + mode: row.mode as DispatchMode, + scope: row.scope as DispatchScope, + status: row.status as DispatchStatus, + cursor: row.cursor, + isManualRun: row.isManualRun, + requestedAt: row.requestedAt, + } +} + +/** Drive `dispatcherStep` to completion. Shared between the trigger.dev task + * wrapper (`tableRunDispatcherTask`) and the in-process inline path so both + * runtimes use identical loop semantics + error logging. */ +export async function runDispatcherToCompletion(dispatchId: string): Promise { + while ((await dispatcherStep(dispatchId)) === 'continue') {} +} + +/** Run one window of the dispatcher state machine. Caller re-invokes (via the + * trigger.dev task wrapper) until the returned status is `'done'`. */ +export async function dispatcherStep(dispatchId: string): Promise { + const dispatch = await readDispatch(dispatchId) + if (!dispatch) { + logger.warn(`[${dispatchId}] dispatch row missing — aborting`) + return 'done' + } + if (dispatch.status === 'cancelled' || dispatch.status === 'complete') return 'done' + + const { getTableById } = await import('./service') + const table = await getTableById(dispatch.tableId) + if (!table) { + logger.warn(`[${dispatchId}] table ${dispatch.tableId} missing — completing dispatch`) + await markDispatchComplete(dispatchId) + return 'done' + } + + const allGroups = table.schema.workflowGroups ?? [] + const targetGroups = allGroups.filter((g) => dispatch.scope.groupIds.includes(g.id)) + if (targetGroups.length === 0) { + await markDispatchComplete(dispatchId) + return 'done' + } + + // First iteration: just transition pending → dispatching. The bulk clear + // ran synchronously in `runWorkflowColumn` before this task fired, so the + // user already saw the column flip to empty/Pending before any cell + // started enqueueing. + if (dispatch.status === 'pending') { + await db + .update(tableRunDispatches) + .set({ status: 'dispatching' }) + .where(eq(tableRunDispatches.id, dispatchId)) + } + + const filters = [ + eq(userTableRows.tableId, dispatch.tableId), + gt(userTableRows.position, dispatch.cursor), + ] + if (dispatch.scope.rowIds && dispatch.scope.rowIds.length > 0) { + filters.push(inArray(userTableRows.id, dispatch.scope.rowIds)) + } + // `'new'` mode targets only rows whose targeted groups haven't been + // attempted. Exclude a row only when EVERY targeted group already has a + // sidecar entry — if any one is missing, the row still has work to do + // and per-group JS filtering in `classifyEligibility` handles the rest. + if (dispatch.mode === 'new' && dispatch.scope.groupIds.length > 0) { + const gids = dispatch.scope.groupIds + filters.push( + sql`NOT EXISTS ( + SELECT 1 FROM ${tableRowExecutions} re + WHERE re.row_id = ${userTableRows.id} + AND re.group_id = ANY(ARRAY[${sql.join( + gids.map((gid) => sql`${gid}`), + sql`, ` + )}]::text[]) + GROUP BY re.row_id + HAVING count(DISTINCT re.group_id) = ${gids.length} + )` + ) + } + + const chunk = await db + .select() + .from(userTableRows) + .where(and(...filters)) + .orderBy(asc(userTableRows.position)) + .limit(WINDOW_SIZE) + + if (chunk.length === 0) { + await markDispatchComplete(dispatchId) + await appendTableEvent({ + kind: 'dispatch', + tableId: dispatch.tableId, + dispatchId, + status: 'complete', + scope: dispatch.scope, + cursor: dispatch.cursor, + mode: dispatch.mode, + isManualRun: dispatch.isManualRun, + }) + return 'done' + } + + // Pre-fetch executions for the chunk so per-row eligibility doesn't fan + // out into one query per row. Returns `Map`. + const chunkRowIds = chunk.map((r) => r.id) + const execRows = await db + .select() + .from(tableRowExecutions) + .where(inArray(tableRowExecutions.rowId, chunkRowIds)) + const executionsByRow = new Map() + for (const r of execRows) { + const existing = executionsByRow.get(r.rowId) ?? {} + const meta: RowExecutionMetadata = { + status: r.status as RowExecutionMetadata['status'], + executionId: r.executionId ?? null, + jobId: r.jobId ?? null, + workflowId: r.workflowId, + error: r.error ?? null, + ...(r.runningBlockIds && r.runningBlockIds.length > 0 + ? { runningBlockIds: r.runningBlockIds } + : {}), + ...(r.blockErrors && Object.keys(r.blockErrors as Record).length > 0 + ? { blockErrors: r.blockErrors as Record } + : {}), + ...(r.cancelledAt ? { cancelledAt: r.cancelledAt.toISOString() } : {}), + } + existing[r.groupId] = meta + executionsByRow.set(r.rowId, existing) + } + + // Strip rows the user cancelled mid-cascade (post-dispatch tombstones) + // before running the shared eligibility filter — `buildPendingRuns` + // doesn't know about the per-dispatch cancel tombstone. + const tombstoneFiltered: TableRow[] = [] + for (const r of chunk) { + const tableRow = toTableRow(r, executionsByRow.get(r.id) ?? {}) + const tombstoned = dispatch.scope.groupIds.some((gid) => { + const exec = tableRow.executions?.[gid] + if (!exec?.cancelledAt) return false + const cancelledAtMs = Date.parse(exec.cancelledAt) + return Number.isFinite(cancelledAtMs) && cancelledAtMs > dispatch.requestedAt.getTime() + }) + if (!tombstoned) tombstoneFiltered.push(tableRow) + } + + const pendingRuns = buildPendingRuns(table, tombstoneFiltered, { + isManualRun: dispatch.isManualRun, + groupIds: dispatch.scope.groupIds, + mode: dispatch.mode, + }) + + // Cursor advances to the last position in this chunk regardless of + // eligibility — otherwise a window full of skipped cells loops forever. + const lastPosition = chunk[chunk.length - 1].position + + if (pendingRuns.length > 0) { + await stampQueuedForBatch(pendingRuns) + + // Backend-agnostic batch dispatch: trigger.dev wraps `batchTriggerAndWait` + // (CRIU-checkpointed wait); database backend calls the cell-task runner + // directly via Promise.all (skips async_jobs since we're awaiting in- + // process anyway). Either way the parent dispatcher blocks until every + // cell in the window terminates — bounds queue depth at WINDOW_SIZE. + const items = await buildEnqueueItems(pendingRuns) + const queue = await getJobQueue() + try { + await queue.batchEnqueueAndWait('workflow-group-cell', items) + } catch (err) { + logger.error(`[${dispatchId}] batch dispatch failed`, { + error: toError(err).message, + }) + // Cursor advances past this window, so flip the un-claimed pre-stamps to + // terminal `error` (+ SSE) — visible, not stuck pending, re-runnable. + const failedAt = new Date() + await Promise.allSettled( + pendingRuns.map(async (p) => { + const updated = await db + .update(tableRowExecutions) + .set({ status: 'error', error: 'Failed to enqueue run', updatedAt: failedAt }) + .where( + and( + eq(tableRowExecutions.rowId, p.rowId), + eq(tableRowExecutions.groupId, p.groupId), + eq(tableRowExecutions.status, 'pending'), + sql`${tableRowExecutions.executionId} IS NULL` + ) + ) + .returning({ rowId: tableRowExecutions.rowId }) + if (updated.length === 0) return + await appendTableEvent({ + kind: 'cell', + tableId: dispatch.tableId, + rowId: p.rowId, + groupId: p.groupId, + status: 'error', + executionId: null, + jobId: null, + error: 'Failed to enqueue run', + }) + }) + ) + } + } + + await Promise.all([ + advanceCursor(dispatchId, lastPosition), + appendTableEvent({ + kind: 'dispatch', + tableId: dispatch.tableId, + dispatchId, + status: 'dispatching', + scope: dispatch.scope, + cursor: lastPosition, + mode: dispatch.mode, + isManualRun: dispatch.isManualRun, + }), + ]) + + return 'continue' +} + +/** Pre-batch stamp: write each targeted cell as `pending` (no executionId) + * before firing the batch so the renderer shows the cell as in-flight + * immediately. The cell-task overwrites with `running` (and its own + * executionId) once it acquires the row's cascade lock — if another + * cell-task already holds the lock, this task bails and the pending stamp + * is later reconciled by whoever owns the cascade. */ +async function stampQueuedForBatch(pendingRuns: WorkflowGroupCellPayload[]): Promise { + await Promise.allSettled( + pendingRuns.map((runOpts) => + writeWorkflowGroupState(runOpts, { + executionState: { + status: 'pending', + executionId: null, + jobId: null, + workflowId: runOpts.workflowId, + error: null, + }, + }) + ) + ) +} + +async function advanceCursor(dispatchId: string, newCursor: number): Promise { + await db + .update(tableRunDispatches) + .set({ cursor: newCursor }) + .where(eq(tableRunDispatches.id, dispatchId)) +} + +async function markDispatchComplete(dispatchId: string): Promise { + await db + .update(tableRunDispatches) + .set({ status: 'complete', completedAt: new Date() }) + .where(eq(tableRunDispatches.id, dispatchId)) +} + +export async function markDispatchCancelled(dispatchId: string): Promise { + await db + .update(tableRunDispatches) + .set({ status: 'cancelled', cancelledAt: new Date() }) + .where( + and( + eq(tableRunDispatches.id, dispatchId), + inArray(tableRunDispatches.status, [...ACTIVE_DISPATCH_STATUSES]) + ) + ) +} + +/** Mark every active dispatch on this table as cancelled. Single atomic + * UPDATE so the dispatcher's next iteration observes the cancel. Returns the + * dispatches that were cancelled so the caller can emit per-dispatch SSE + * events — without those the client's overlay would hang on "queued" until + * the next refresh. */ +export async function markActiveDispatchesCancelled(tableId: string): Promise { + const cancelled = await db + .update(tableRunDispatches) + .set({ status: 'cancelled', cancelledAt: new Date() }) + .where( + and( + eq(tableRunDispatches.tableId, tableId), + inArray(tableRunDispatches.status, [...ACTIVE_DISPATCH_STATUSES]) + ) + ) + .returning() + const dispatches = cancelled.map((row) => ({ + id: row.id, + tableId: row.tableId, + workspaceId: row.workspaceId, + requestId: row.requestId, + mode: row.mode as DispatchMode, + scope: row.scope as DispatchScope, + status: 'cancelled' as DispatchStatus, + cursor: row.cursor, + isManualRun: row.isManualRun, + requestedAt: row.requestedAt, + })) + await Promise.all( + dispatches.map((d) => + appendTableEvent({ + kind: 'dispatch', + tableId: d.tableId, + dispatchId: d.id, + status: 'cancelled', + scope: d.scope, + cursor: d.cursor, + mode: d.mode, + isManualRun: d.isManualRun, + }) + ) + ) + return dispatches +} diff --git a/apps/sim/lib/table/events.ts b/apps/sim/lib/table/events.ts index 64d4fabc562..63c2dac92c7 100644 --- a/apps/sim/lib/table/events.ts +++ b/apps/sim/lib/table/events.ts @@ -68,30 +68,48 @@ function getMetaKey(tableId: string) { export type TableCellStatus = 'pending' | 'queued' | 'running' | 'completed' | 'cancelled' | 'error' -export interface TableEvent { - kind: 'cell' - tableId: string - rowId: string - groupId: string - status: TableCellStatus - executionId: string | null - jobId: string | null - error: string | null - /** - * Present when this transition wrote new output values; absent on - * pure-status transitions (queued, running, cancelled). The publisher - * already has these in hand from the same updateRow call that wrote DB. - */ - outputs?: Record - /** - * Block-level metadata the renderer reads to distinguish "running" (some - * block actively executing) from "pending-upstream" (run started but this - * column's block hasn't fired yet). The worker fills these on partial - * writes; without them the cell stays on the amber Pending pill. - */ - runningBlockIds?: string[] - blockErrors?: Record -} +export type TableDispatchStatus = 'pending' | 'dispatching' | 'complete' | 'cancelled' + +export type TableEvent = + | { + kind: 'cell' + tableId: string + rowId: string + groupId: string + status: TableCellStatus + executionId: string | null + jobId: string | null + error: string | null + /** + * Present when this transition wrote new output values; absent on + * pure-status transitions (queued, running, cancelled). The publisher + * already has these in hand from the same updateRow call that wrote DB. + */ + outputs?: Record + /** + * Block-level metadata the renderer reads to distinguish "running" (some + * block actively executing) from "pending-upstream" (run started but this + * column's block hasn't fired yet). The worker fills these on partial + * writes; without them the cell stays on the amber Pending pill. + */ + runningBlockIds?: string[] + blockErrors?: Record + } + | { + /** Dispatcher status signal emitted by `dispatcherStep` and the cancel + * path. Drives the client-side "about to run" overlay for rows the + * dispatcher hasn't reached yet. `scope` + `cursor` + `mode` + + * `isManualRun` are carried on every transition so the client can + * upsert without refetching the dispatches list. */ + kind: 'dispatch' + tableId: string + dispatchId: string + status: TableDispatchStatus + scope?: { groupIds: string[]; rowIds?: string[] } + cursor?: number + mode?: 'all' | 'incomplete' | 'new' + isManualRun?: boolean + } export interface TableEventEntry { eventId: number diff --git a/apps/sim/lib/table/service.ts b/apps/sim/lib/table/service.ts index 972bb551026..e4ffd8b09c3 100644 --- a/apps/sim/lib/table/service.ts +++ b/apps/sim/lib/table/service.ts @@ -8,7 +8,12 @@ */ import { db } from '@sim/db' -import { userTableDefinitions, userTableRows, workflowExecutionLogs } from '@sim/db/schema' +import { + tableRowExecutions, + userTableDefinitions, + userTableRows, + workflowExecutionLogs, +} from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getPostgresErrorCode } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' @@ -64,8 +69,8 @@ import { } from './validation' import { assertValidSchema, - scheduleRunsForRows, - scheduleRunsForTable, + cancelWorkflowGroupRuns, + runWorkflowColumn, stripGroupDeps, } from './workflow-columns' @@ -160,6 +165,36 @@ function scaledStatementTimeoutMs( * @param tableId - Table ID to fetch * @returns Table definition or null if not found */ +/** + * Returns `schema` with `columns` sorted by `metadata.columnOrder` (the user- + * editable visible order). Columns missing from `columnOrder` are appended at + * the end in their original (schema-creation) order — covers tables created + * before `columnOrder` existed and any drift from out-of-band column adds. + * + * This makes `schema.columns` the single source of truth for column order on + * the wire. The client doesn't have to join the two arrays itself — every + * consumer (grid, sidebar, copilot, mothership) gets the same ordered list. + */ +function applyColumnOrderToSchema( + schema: TableSchema, + metadata: TableMetadata | null +): TableSchema { + const order = metadata?.columnOrder + if (!order || order.length === 0) return schema + const byName = new Map() + for (const c of schema.columns) byName.set(c.name, c) + const ordered: TableSchema['columns'] = [] + for (const name of order) { + const c = byName.get(name) + if (c) { + ordered.push(c) + byName.delete(name) + } + } + for (const c of byName.values()) ordered.push(c) + return { ...schema, columns: ordered } +} + export async function getTableById( tableId: string, options?: { includeArchived?: boolean } @@ -191,12 +226,13 @@ export async function getTableById( if (results.length === 0) return null const table = results[0] + const metadata = (table.metadata as TableMetadata) ?? null return { id: table.id, name: table.name, description: table.description, - schema: table.schema as TableSchema, - metadata: (table.metadata as TableMetadata) ?? null, + schema: applyColumnOrderToSchema(table.schema as TableSchema, metadata), + metadata, rowCount: table.rowCount, maxRows: table.maxRows, workspaceId: table.workspaceId, @@ -262,20 +298,23 @@ export async function listTables( ) .orderBy(userTableDefinitions.createdAt) - return tables.map((t) => ({ - id: t.id, - name: t.name, - description: t.description, - schema: t.schema as TableSchema, - metadata: (t.metadata as TableMetadata) ?? null, - rowCount: t.rowCount, - maxRows: t.maxRows, - workspaceId: t.workspaceId, - createdBy: t.createdBy, - archivedAt: t.archivedAt, - createdAt: t.createdAt, - updatedAt: t.updatedAt, - })) + return tables.map((t) => { + const metadata = (t.metadata as TableMetadata) ?? null + return { + id: t.id, + name: t.name, + description: t.description, + schema: applyColumnOrderToSchema(t.schema as TableSchema, metadata), + metadata, + rowCount: t.rowCount, + maxRows: t.maxRows, + workspaceId: t.workspaceId, + createdBy: t.createdBy, + archivedAt: t.archivedAt, + createdAt: t.createdAt, + updatedAt: t.updatedAt, + } + }) } /** @@ -648,9 +687,49 @@ export async function updateTableMetadata( ): Promise { const merged: TableMetadata = { ...(existingMetadata ?? {}), ...metadata } + // When `columnOrder` is in the patch, scrub any workflow-group dependency + // that now sits to the right of (or at the same index as) its group's + // leftmost column. Without this, reordering a column could leave a group + // depending on a column it can no longer reach in the dag — the group + // would never fire. + const newOrder = metadata.columnOrder + let nextSchema: TableSchema | null = null + if (Array.isArray(newOrder) && newOrder.length > 0) { + const [tableRow] = await db + .select({ schema: userTableDefinitions.schema }) + .from(userTableDefinitions) + .where(eq(userTableDefinitions.id, tableId)) + .limit(1) + if (tableRow) { + const schema = tableRow.schema as TableSchema + const groups = schema.workflowGroups ?? [] + if (groups.length > 0) { + const positionOf = new Map() + newOrder.forEach((name, i) => positionOf.set(name, i)) + let mutated = false + const nextGroups = groups.map((group) => { + const ownCols = schema.columns.filter((c) => c.workflowGroupId === group.id) + let leftmost = Number.POSITIVE_INFINITY + for (const c of ownCols) { + const idx = positionOf.get(c.name) ?? Number.POSITIVE_INFINITY + if (idx < leftmost) leftmost = idx + } + if (!Number.isFinite(leftmost)) return group + const deps = group.dependencies?.columns ?? [] + const removed = new Set(deps.filter((dep) => (positionOf.get(dep) ?? -1) >= leftmost)) + if (removed.size === 0) return group + const stripped = stripGroupDeps(group, removed) + if (stripped !== group) mutated = true + return stripped + }) + if (mutated) nextSchema = { ...schema, workflowGroups: nextGroups } + } + } + } + await db .update(userTableDefinitions) - .set({ metadata: merged }) + .set(nextSchema ? { metadata: merged, schema: nextSchema } : { metadata: merged }) .where(eq(userTableDefinitions.id, tableId)) return merged @@ -915,7 +994,7 @@ export async function insertRow( const insertedRow: TableRow = { id: row.id, data: row.data as RowData, - executions: (row.executions as RowExecutions) ?? {}, + executions: {}, position: row.position, createdAt: row.createdAt, updatedAt: row.updatedAt, @@ -930,7 +1009,14 @@ export async function insertRow( table.schema, requestId ) - void scheduleRunsForRows(table, [insertedRow]) + void runWorkflowColumn({ + tableId: table.id, + workspaceId: table.workspaceId, + rowIds: [insertedRow.id], + mode: 'new', + isManualRun: false, + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (insertRow) failed:`, err)) return insertedRow } @@ -1041,14 +1127,25 @@ export async function batchInsertRowsWithTx( const result: TableRow[] = insertedRows.map((r) => ({ id: r.id, data: r.data as RowData, - executions: (r.executions as RowExecutions) ?? {}, + executions: {}, position: r.position, createdAt: r.createdAt, updatedAt: r.updatedAt, })) void fireTableTrigger(data.tableId, table.name, 'insert', result, null, table.schema, requestId) - void scheduleRunsForRows(table, result) + // Scope to the newly-inserted row ids so the dispatcher doesn't walk every + // row in the table. After the sidecar migration, all existing rows have + // zero entries → `mode:'new'`'s `NOT EXISTS` filter would otherwise include + // them, dispatching workflows on every row in a populated table. + void runWorkflowColumn({ + tableId: table.id, + workspaceId: table.workspaceId, + rowIds: result.map((r) => r.id), + mode: 'new', + isManualRun: false, + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (batchInsertRows) failed:`, err)) return result } @@ -1322,11 +1419,12 @@ export async function upsertRow( .where(eq(userTableRows.id, matchedRowId)) .returning() + const executions = await loadExecutionsForRow(trx, updatedRow.id) return { row: { id: updatedRow.id, data: updatedRow.data as RowData, - executions: (updatedRow.executions as RowExecutions) ?? {}, + executions, position: updatedRow.position, createdAt: updatedRow.createdAt, updatedAt: updatedRow.updatedAt, @@ -1354,7 +1452,7 @@ export async function upsertRow( row: { id: insertedRow.id, data: insertedRow.data as RowData, - executions: (insertedRow.executions as RowExecutions) ?? {}, + executions: {}, position: insertedRow.position, createdAt: insertedRow.createdAt, updatedAt: insertedRow.updatedAt, @@ -1389,7 +1487,14 @@ export async function upsertRow( requestId ) } - void scheduleRunsForRows(table, [result.row]) + void runWorkflowColumn({ + tableId: table.id, + workspaceId: table.workspaceId, + rowIds: [result.row.id], + mode: 'new', + isManualRun: false, + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (upsertRow) failed:`, err)) return result } @@ -1474,11 +1579,16 @@ export async function queryRows( `[${requestId}] Queried ${rows.length} rows from table ${tableId} (total: ${totalCount})` ) + const executionsByRow = await loadExecutionsByRow( + db, + rows.map((r) => r.id) + ) + return { rows: rows.map((r) => ({ id: r.id, data: r.data as RowData, - executions: (r.executions as RowExecutions) ?? {}, + executions: executionsByRow.get(r.id) ?? {}, position: r.position, createdAt: r.createdAt, updatedAt: r.updatedAt, @@ -1518,10 +1628,11 @@ export async function getRowById( if (results.length === 0) return null const row = results[0] + const executions = await loadExecutionsForRow(db, row.id) return { id: row.id, data: row.data as RowData, - executions: (row.executions as RowExecutions) ?? {}, + executions, position: row.position, createdAt: row.createdAt, updatedAt: row.updatedAt, @@ -1529,36 +1640,94 @@ export async function getRowById( } /** - * When a user edit clears a workflow output column to empty, also clear the - * exec record for that group. Without this, a `cancelled` (or `error`) exec - * sticks on the row even after the user wipes the output, blocking the - * auto-fire reactor (which respects terminal states). Treating the cleared - * cell as "user wants this re-armed" matches the rule that cells are the - * source of truth — we already do this for `completed` via - * `areOutputsFilled` in the eligibility predicate; this extends the same - * behavior to error/cancelled by making the data clear remove the exec. + * Derive automatic clears + cancellation candidates from a row's data patch. + * + * Walks `schema.workflowGroups` left-to-right with a propagating `dirtied` + * column set. For each group whose deps overlap the dirty set, decide to + * clear (terminal exec) or cancel+rerun (in-flight exec), then add the + * group's outputs to the dirty set so later groups in the chain see them + * as dirty too. This models transitive dep chains as a single forward pass — + * editing column A propagates through group 1 (deps on A) to group 2 (deps + * on group 1's output) without explicit DAG traversal. + * + * Returns: + * - `executionsPatch`: caller's patch + nulls for cleared groups (or + * undefined if nothing applied). + * - `inFlightDownstreamGroups`: groups whose dep was dirtied and that are + * currently in-flight. Cancel-and-restart is the caller's job. * - * Returns a merged `executionsPatch` (caller's patch + null for groups whose - * outputs were cleared), or the caller's patch unchanged if nothing applies. + * Assumption: `workflowGroups[]` is in topological order — a group's deps + * may only reference columns to its left (enforced by `workflow-sidebar`'s + * "Run after" picker + the reorder scrub via `stripGroupDeps`). Violating + * this would silently miss the propagation. */ function deriveExecClearsForDataPatch( dataPatch: RowData, schema: TableSchema, + existingExecutions: RowExecutions, callerPatch: Record | undefined -): Record | undefined { +): { + executionsPatch: Record | undefined + inFlightDownstreamGroups: string[] +} { + const dirtied = new Set(Object.keys(dataPatch)) const groupsToClear = new Set() + const inFlightDownstreamGroups: string[] = [] + + // Own-output clears: when the user wipes a workflow output column, drop + // that group's exec entry so the auto-fire reactor re-arms the cell. + // Also flags the cleared output column as dirty so transitive downstream + // groups see it. for (const [columnName, value] of Object.entries(dataPatch)) { const cleared = value === null || value === undefined || value === '' if (!cleared) continue const col = schema.columns.find((c) => c.name === columnName) if (col?.workflowGroupId) groupsToClear.add(col.workflowGroupId) } - if (groupsToClear.size === 0) return callerPatch + + // Left-to-right walk, propagating dirty columns forward. + const groups = schema.workflowGroups ?? [] + for (const group of groups) { + const deps = group.dependencies?.columns ?? [] + const depMatched = deps.some((d) => dirtied.has(d)) + if (!depMatched) continue + + const exec = existingExecutions[group.id] + if (exec) { + const status = exec.status + if (status === 'completed' || status === 'error' || status === 'cancelled') { + groupsToClear.add(group.id) + } else if (status === 'queued' || status === 'running' || status === 'pending') { + inFlightDownstreamGroups.push(group.id) + } + } else { + // No exec entry yet — `mode: 'new'` already covers this group. We + // still propagate the dirty signal forward so later groups in the + // chain see this group's outputs as dirty too. + groupsToClear.add(group.id) + } + + // Propagate: this group is about to be re-computed, so groups whose + // deps reference its output columns are also dirty. + for (const out of group.outputs) dirtied.add(out.columnName) + } + + if (groupsToClear.size === 0) { + return { executionsPatch: callerPatch, inFlightDownstreamGroups } + } const merged: Record = { ...(callerPatch ?? {}) } for (const gid of groupsToClear) { if (!(gid in merged)) merged[gid] = null } - return merged + return { executionsPatch: merged, inFlightDownstreamGroups } +} + +/** Internal: thrown inside `db.transaction` to roll back when the executions + * guard rejects a write. The outer `.catch` translates it into a `null` return. */ +class GuardRejected extends Error { + constructor() { + super('cell-write guard rejected') + } } /** Merges an `executionsPatch` into the row's existing executions blob. */ @@ -1579,50 +1748,172 @@ function applyExecutionsPatch( } /** - * Builds a SQL expression that applies the given `executionsPatch` to the - * row's `executions` jsonb in-place — set keys for non-null values, delete - * keys for `null` values. Returns null when the patch is empty/missing. + * Loads `tableRowExecutions` rows for the given row ids and groups them into + * a `Map` suitable for plugging into `TableRow.executions` + * everywhere callers used to read `userTableRows.executions` JSONB. + */ +async function loadExecutionsByRow( + trx: DbOrTx, + rowIds: Iterable +): Promise> { + const ids = Array.from(new Set(rowIds)) + const result = new Map() + if (ids.length === 0) return result + const rows = await trx + .select() + .from(tableRowExecutions) + .where(inArray(tableRowExecutions.rowId, ids)) + for (const r of rows) { + const existing = result.get(r.rowId) ?? {} + const meta: RowExecutionMetadata = { + status: r.status as RowExecutionMetadata['status'], + executionId: r.executionId ?? null, + jobId: r.jobId ?? null, + workflowId: r.workflowId, + error: r.error ?? null, + ...(r.runningBlockIds && r.runningBlockIds.length > 0 + ? { runningBlockIds: r.runningBlockIds } + : {}), + ...(r.blockErrors && Object.keys(r.blockErrors as Record).length > 0 + ? { blockErrors: r.blockErrors as Record } + : {}), + ...(r.cancelledAt ? { cancelledAt: r.cancelledAt.toISOString() } : {}), + } + existing[r.groupId] = meta + result.set(r.rowId, existing) + } + return result +} + +/** Convenience: load executions for one row, returning `{}` when missing. */ +async function loadExecutionsForRow(trx: DbOrTx, rowId: string): Promise { + const byRow = await loadExecutionsByRow(trx, [rowId]) + return byRow.get(rowId) ?? {} +} + +/** + * Writes a per-group execution patch for one row against the `tableRowExecutions` + * sidecar. Non-null values upsert into the table; nulls delete the entry. When + * `guard` is set, the upsert is gated to: + * - reject if a `cancelled` row for the same execution already exists, and + * - reject if the row exists but is owned by a different executionId + * (with carve-outs for missing rows and null executionIds — the dispatcher's + * pre-batch `pending` stamp leaves executionId unset so the first cell-task + * can claim). * - * Why server-side: read-modify-write on the entire jsonb blob races between - * concurrent writers (e.g., a column edit and a manual-retry stamp), so the - * last writer wins for keys it didn't touch and clobbers other writers' - * exec updates. Patching keys at the SQL level keeps each writer's changes - * atomic per-key. + * Returns `'guard-rejected'` when the guarded group's upsert affected 0 rows + * (callers signal failure to the cell-task path). Returns `'wrote'` otherwise. */ -function buildExecutionsSqlPatch( - patch: Record | undefined -): SQL | null { - if (!patch) return null +async function writeExecutionsPatch( + trx: DbOrTx, + tableId: string, + rowId: string, + patch: Record | undefined, + guard?: { groupId: string; executionId: string } +): Promise<'wrote' | 'guard-rejected'> { + if (!patch) return 'wrote' const entries = Object.entries(patch) - if (entries.length === 0) return null + if (entries.length === 0) return 'wrote' - let expr: SQL = sql`coalesce(${userTableRows.executions}, '{}'::jsonb)` for (const [gid, value] of entries) { if (value === null) { - expr = sql`(${expr}) - ${gid}::text` - } else { - expr = sql`(${expr}) || jsonb_build_object(${gid}::text, ${JSON.stringify(value)}::jsonb)` + await trx + .delete(tableRowExecutions) + .where(and(eq(tableRowExecutions.rowId, rowId), eq(tableRowExecutions.groupId, gid)) as SQL) + continue + } + const insertValues = { + tableId, + rowId, + groupId: gid, + status: value.status, + executionId: value.executionId, + jobId: value.jobId, + workflowId: value.workflowId, + error: value.error, + runningBlockIds: value.runningBlockIds ?? [], + blockErrors: value.blockErrors ?? {}, + cancelledAt: value.cancelledAt ? new Date(value.cancelledAt) : null, + updatedAt: new Date(), + } as const + + const isGuarded = guard && guard.groupId === gid + if (isGuarded) { + // Gate by guard semantics. The original JSONB guard had two AND'd + // clauses; we collapse them onto the upsert's WHERE so a non-matching + // existing row leaves the table untouched and we observe 0 affected. + const guardExecutionId = guard.executionId + const updated = await trx + .insert(tableRowExecutions) + .values(insertValues) + .onConflictDoUpdate({ + target: [tableRowExecutions.rowId, tableRowExecutions.groupId], + set: { + status: insertValues.status, + executionId: insertValues.executionId, + jobId: insertValues.jobId, + workflowId: insertValues.workflowId, + error: insertValues.error, + runningBlockIds: insertValues.runningBlockIds, + blockErrors: insertValues.blockErrors, + cancelledAt: insertValues.cancelledAt, + updatedAt: insertValues.updatedAt, + }, + where: and( + // Reject if this group already shows authoritative `cancelled` for + // the same executionId — a stop click wrote it first. + sql`NOT (${tableRowExecutions.status} = 'cancelled' AND ${tableRowExecutions.executionId} IS NOT DISTINCT FROM ${guardExecutionId})`, + // Stale-worker: the cell's active run has moved on. Carve-outs + // permit a fresh worker to take over when the row's executionId + // is unset (dispatcher's pre-batch `pending` stamp). + sql`(${tableRowExecutions.executionId} IS NULL OR ${tableRowExecutions.executionId} = ${guardExecutionId})` + ) as SQL, + }) + .returning({ rowId: tableRowExecutions.rowId }) + if (updated.length === 0) return 'guard-rejected' + continue } + + await trx + .insert(tableRowExecutions) + .values(insertValues) + .onConflictDoUpdate({ + target: [tableRowExecutions.rowId, tableRowExecutions.groupId], + set: { + status: insertValues.status, + executionId: insertValues.executionId, + jobId: insertValues.jobId, + workflowId: insertValues.workflowId, + error: insertValues.error, + runningBlockIds: insertValues.runningBlockIds, + blockErrors: insertValues.blockErrors, + cancelledAt: insertValues.cancelledAt, + updatedAt: insertValues.updatedAt, + }, + }) } - return expr + + return 'wrote' } /** - * Strips the given workflow group ids from every row's `executions` jsonb on - * a table — used by the column / group delete paths so stale running/queued - * exec records don't linger and inflate counters after the group is gone. - * The caller wraps in their own transaction. + * Strips the given workflow group ids from every row's executions on a table — + * used by the column / group delete paths so stale running/queued exec records + * don't linger and inflate counters after the group is gone. The caller wraps + * in their own transaction. */ async function stripGroupExecutions( - trx: Parameters[0]>[0], + trx: DbOrTx, tableId: string, groupIds: Iterable ): Promise { - for (const gid of groupIds) { - await trx.execute( - sql`UPDATE user_table_rows SET executions = executions - ${gid}::text WHERE table_id = ${tableId} AND executions ? ${gid}::text` + const ids = Array.from(new Set(groupIds)) + if (ids.length === 0) return + await trx + .delete(tableRowExecutions) + .where( + and(eq(tableRowExecutions.tableId, tableId), inArray(tableRowExecutions.groupId, ids)) as SQL ) - } } /** @@ -1650,13 +1941,16 @@ export async function updateRow( ...(existingRow.data as RowData), ...data.data, } - // Auto-clear exec records for workflow output columns the user just wiped, - // so the auto-fire reactor sees no exec and re-arms the cell. - const effectiveExecutionsPatch = deriveExecClearsForDataPatch( - data.data, - table.schema, - data.executionsPatch - ) + // Auto-clear exec records for workflow output columns the user just wiped + // AND for downstream groups whose deps just changed. Surfaces the in-flight + // downstream groups so the caller can cancel + re-run them. + const { executionsPatch: effectiveExecutionsPatch, inFlightDownstreamGroups } = + deriveExecClearsForDataPatch( + data.data, + table.schema, + existingRow.executions, + data.executionsPatch + ) const mergedExecutions = applyExecutionsPatch(existingRow.executions, effectiveExecutionsPatch) // Validate size @@ -1687,52 +1981,40 @@ export async function updateRow( const now = new Date() - // Cell-task partial writes pass `cancellationGuard` so the SQL update is a - // no-op when (a) a stop click already wrote `cancelled` for this run, or - // (b) a newer run has taken over the cell with a different executionId. The - // worker is "this run's writes only land if this run is still the active - // run on the cell." Authoritative cancel writes from `cancelWorkflowGroupRuns` - // skip the guard entirely (they don't pass `cancellationGuard`). - // - // SQL-level for atomicity: an in-process read + update would race a - // concurrent stop or rerun. The two clauses are joined by AND because - // either failing means the worker is no longer authoritative. + // Cell-task partial writes pass `cancellationGuard` so the upsert into + // `tableRowExecutions` is a no-op when (a) a stop click already wrote + // `cancelled` for this run, or (b) a newer run has taken over the cell + // with a different executionId. Authoritative cancel writes from + // `cancelWorkflowGroupRuns` skip the guard entirely. Data + executions + // commit in one transaction so a partial write can't leave the sidecar + // and the row out of sync. const guard = data.cancellationGuard - const whereClause = guard - ? and( - eq(userTableRows.id, data.rowId), - // Reject writes that would land on top of an already-`cancelled` state - // for this same run. Wrapped in IS DISTINCT FROM so a missing exec - // (NULL) cleanly evaluates as "different" rather than NULL-poisoning. - sql`(executions->${guard.groupId}->>'status' IS DISTINCT FROM 'cancelled' OR executions->${guard.groupId}->>'executionId' IS DISTINCT FROM ${guard.executionId})`, - // Reject writes from a stale worker — the cell's active run has moved - // on. `OR exec IS NULL` lets the worker land its first `running` - // stamp on a row that has no prior exec record (initial stamp from - // the scheduler may not have committed yet). - sql`(executions->${guard.groupId} IS NULL OR executions->${guard.groupId}->>'executionId' = ${guard.executionId})` + const guardRejected = await db + .transaction(async (trx) => { + await trx + .update(userTableRows) + .set({ data: mergedData, updatedAt: now }) + .where(eq(userTableRows.id, data.rowId)) + + const result = await writeExecutionsPatch( + trx, + data.tableId, + data.rowId, + effectiveExecutionsPatch, + guard ) - : eq(userTableRows.id, data.rowId) - - // Apply the executions patch at the SQL level — we never overwrite the full - // executions blob, only the keys the caller explicitly patched. Without - // this, concurrent updateRow calls (e.g., a column edit and a manual - // retry's stamp) would each compute `mergedExecutions` from their own - // in-memory snapshot and the last writer wins, clobbering the other's - // exec keys. The data field still does last-writer-wins because that's - // the user's edit, but exec records are independently keyed by groupId. - const executionsExpr = buildExecutionsSqlPatch(effectiveExecutionsPatch) - const updated = await db - .update(userTableRows) - .set({ - data: mergedData, - ...(executionsExpr ? { executions: executionsExpr } : {}), - updatedAt: now, + if (result === 'guard-rejected') { + // Roll back the data update too — the worker isn't authoritative. + throw new GuardRejected() + } + return false + }) + .catch((err) => { + if (err instanceof GuardRejected) return true + throw err }) - .where(whereClause) - .returning({ id: userTableRows.id }) - // Only meaningful when a guard is set — `null` signals "guard rejected". - if (guard && updated.length === 0) { + if (guardRejected) { return null } @@ -1757,9 +2039,54 @@ export async function updateRow( table.schema, requestId ) - // Awaited (not `void`) so cell tasks dispatch their cascade before the - // trigger.dev worker tears down on `run()` resolve. - if (!data.skipScheduler) await scheduleRunsForRows(table, [updatedRow]) + + // Auto-fire only on user-facing data edits. Internal callers that mutate + // executions (cell-task partial/terminal writes, cancel writes) always pass + // `executionsPatch` — re-dispatching from those would recursively spawn new + // dispatches for every running/terminal write, flooding the dispatcher with + // redundant pre-stamps that strand `pending` cells. + const isInternalExecWrite = data.executionsPatch && Object.keys(data.executionsPatch).length > 0 + if (isInternalExecWrite) { + return updatedRow + } + + // Two passes: + // 1. Cancel in-flight downstream groups whose dep just changed, then + // manually re-run them — the cancel writes `cancelled` per cell and + // `mode: 'incomplete' + isManualRun: true` wipes those entries and + // re-enqueues. + // 2. `mode: 'new'` for groups that just had their exec entries cleared + // (own-output wipe OR terminal downstream dep-changed) — the + // dispatcher's `jsonb_exists_all` SQL filter lets the row through + // because at least one targeted group's exec is now missing. + if (inFlightDownstreamGroups.length > 0) { + void (async () => { + try { + await cancelWorkflowGroupRuns(data.tableId, data.rowId, { + groupIds: inFlightDownstreamGroups, + }) + await runWorkflowColumn({ + tableId: data.tableId, + workspaceId: data.workspaceId, + mode: 'incomplete', + isManualRun: true, + rowIds: [data.rowId], + groupIds: inFlightDownstreamGroups, + requestId, + }) + } catch (err) { + logger.error(`[${requestId}] cancel+rerun for in-flight downstream groups failed:`, err) + } + })() + } + void runWorkflowColumn({ + tableId: data.tableId, + workspaceId: data.workspaceId, + rowIds: [data.rowId], + mode: 'new', + isManualRun: false, + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (updateRow) failed:`, err)) return updatedRow } @@ -1907,7 +2234,7 @@ export async function updateRowsByFilter( const updatedRows: TableRow[] = matchingRows.map((r) => ({ id: r.id, data: { ...(r.data as RowData), ...data.data }, - executions: ((r as { executions?: unknown }).executions as RowExecutions) ?? {}, + executions: {}, position: 0, createdAt: now, updatedAt: now, @@ -1921,7 +2248,14 @@ export async function updateRowsByFilter( table.schema, requestId ) - void scheduleRunsForRows(table, updatedRows) + void runWorkflowColumn({ + tableId: table.id, + workspaceId: table.workspaceId, + rowIds: updatedRows.map((r) => r.id), + mode: 'new', + isManualRun: false, + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (updateRowsByFilter) failed:`, err)) return { affectedCount: matchingRows.length, @@ -1947,7 +2281,6 @@ export async function batchUpdateRows( .select({ id: userTableRows.id, data: userTableRows.data, - executions: userTableRows.executions, }) .from(userTableRows) .where( @@ -1958,11 +2291,16 @@ export async function batchUpdateRows( ) ) + const executionsByRow = await loadExecutionsByRow( + db, + existingRows.map((r) => r.id) + ) + type ExistingRow = { data: RowData; executions: RowExecutions } const existingMap = new Map( existingRows.map((r) => [ r.id, - { data: r.data as RowData, executions: (r.executions as RowExecutions) ?? {} }, + { data: r.data as RowData, executions: executionsByRow.get(r.id) ?? {} }, ]) ) @@ -1976,17 +2314,22 @@ export async function batchUpdateRows( mergedData: RowData mergedExecutions: RowExecutions executionsPatch?: Record + inFlightDownstreamGroups: string[] }> = [] for (const update of data.updates) { const existing = existingMap.get(update.rowId)! const merged = { ...existing.data, ...update.data } // Auto-clear exec records for workflow output columns the user just - // wiped — same rationale as `updateRow`. - const effectiveExecutionsPatch = deriveExecClearsForDataPatch( - update.data, - table.schema, - update.executionsPatch - ) + // wiped AND downstream dep-changed terminal groups — same rationale as + // `updateRow`. Per-row in-flight downstream groups are surfaced so we + // can run the cancel+rerun orchestration after the batch commits. + const { executionsPatch: effectiveExecutionsPatch, inFlightDownstreamGroups } = + deriveExecClearsForDataPatch( + update.data, + table.schema, + existing.executions, + update.executionsPatch + ) const mergedExecutions = applyExecutionsPatch(existing.executions, effectiveExecutionsPatch) const sizeValidation = validateRowSize(merged) @@ -2004,6 +2347,7 @@ export async function batchUpdateRows( mergedData: merged, mergedExecutions, executionsPatch: effectiveExecutionsPatch, + inFlightDownstreamGroups, }) } @@ -2028,21 +2372,18 @@ export async function batchUpdateRows( await setTableTxTimeouts(trx, { statementMs: 60_000 }) for (let i = 0; i < mergedUpdates.length; i += TABLE_LIMITS.UPDATE_BATCH_SIZE) { const batch = mergedUpdates.slice(i, i + TABLE_LIMITS.UPDATE_BATCH_SIZE) - // Same as `updateRow`: patch executions at the SQL level when a patch - // is set, so concurrent writers don't clobber each other's keys via - // last-writer-wins on the full jsonb blob. - const updatePromises = batch.map(({ rowId, mergedData, executionsPatch }) => { - const executionsExpr = buildExecutionsSqlPatch(executionsPatch) - return trx + // Update row data in parallel; sidecar exec writes are sequential per + // row (each goes through writeExecutionsPatch's per-key upsert). + const dataPromises = batch.map(({ rowId, mergedData }) => + trx .update(userTableRows) - .set({ - data: mergedData, - ...(executionsExpr ? { executions: executionsExpr } : {}), - updatedAt: now, - }) + .set({ data: mergedData, updatedAt: now }) .where(eq(userTableRows.id, rowId)) - }) - await Promise.all(updatePromises) + ) + await Promise.all(dataPromises) + for (const { rowId, executionsPatch } of batch) { + await writeExecutionsPatch(trx, data.tableId, rowId, executionsPatch) + } } }) @@ -2070,7 +2411,47 @@ export async function batchUpdateRows( table.schema, requestId ) - if (!data.skipScheduler) void scheduleRunsForRows(table, updatedRowsForTrigger) + // Per-row cancel+rerun for in-flight downstream groups whose deps just + // changed — same orchestration as single-row `updateRow`. Without this, + // batch updates would leave running workflows reading stale dep values. + // Each row needs its own cancel + manual-incomplete dispatch because + // `cancelWorkflowGroupRuns`'s `groupIds` filter is per-row. + const rowsWithInFlightDownstream = mergedUpdates.filter( + (u) => u.inFlightDownstreamGroups.length > 0 + ) + if (rowsWithInFlightDownstream.length > 0) { + void (async () => { + try { + for (const { rowId, inFlightDownstreamGroups } of rowsWithInFlightDownstream) { + await cancelWorkflowGroupRuns(data.tableId, rowId, { + groupIds: inFlightDownstreamGroups, + }) + await runWorkflowColumn({ + tableId: data.tableId, + workspaceId: data.workspaceId, + mode: 'incomplete', + isManualRun: true, + rowIds: [rowId], + groupIds: inFlightDownstreamGroups, + requestId, + }) + } + } catch (err) { + logger.error( + `[${requestId}] cancel+rerun for in-flight downstream groups (batch) failed:`, + err + ) + } + })() + } + void runWorkflowColumn({ + tableId: table.id, + workspaceId: table.workspaceId, + rowIds: updatedRowsForTrigger.map((r) => r.id), + mode: 'new', + isManualRun: false, + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (batchUpdateRows) failed:`, err)) return { affectedCount: mergedUpdates.length, @@ -2788,18 +3169,20 @@ export async function addWorkflowGroup( updatedAt: now, } - // Schedule existing rows so already-filled deps trigger immediately. Skipped - // when the caller opted out (Mothership stages groups silently — `autoRun: - // false` — so the AI can compose multiple changes without firing rows mid-edit). - // Awaited (not `void`) so the response includes the queued exec state — the - // client's post-mutation refetch otherwise lands before the stamps commit - // and the rows query polling never starts. + // Auto-fire existing rows whose deps are already met for the new group. + // Fire-and-forget — the dispatcher bounds queue depth (window of 20) and + // walks the table in the background. HTTP returns instantly; cells fill + // in over the next minutes as the dispatcher walks. Mothership opts out + // by setting `autoRun: false`. if (data.autoRun !== false) { - try { - await scheduleRunsForTable(updatedTable) - } catch (err) { - logger.error(`[${requestId}] Failed to schedule runs after group add:`, err) - } + void runWorkflowColumn({ + tableId: updatedTable.id, + workspaceId: updatedTable.workspaceId, + mode: 'new', + isManualRun: false, + groupIds: [data.group.id], + requestId, + }).catch((err) => logger.error(`[${requestId}] auto-dispatch (addWorkflowGroup) failed:`, err)) } return updatedTable @@ -3085,16 +3468,20 @@ export async function updateWorkflowGroup( } } - // autoRun toggled false → true: fire deps-satisfied rows now. Mirrors the - // post-add scheduling path so re-enabling auto-fire doesn't require manual - // run clicks for rows that are already eligible. Awaited so the post- - // mutation refetch sees the queued exec stamps. + // autoRun toggled false → true: fire deps-satisfied rows now via the + // dispatcher. Mirrors the post-add path so re-enabling auto-fire doesn't + // require manual run clicks for rows that are already eligible. if (group.autoRun === false && data.autoRun === true) { - try { - await scheduleRunsForTable(updatedTable, { groupId: data.groupId }) - } catch (err) { - logger.error(`[${requestId}] Failed to schedule runs after autoRun toggled on:`, err) - } + void runWorkflowColumn({ + tableId: updatedTable.id, + workspaceId: updatedTable.workspaceId, + mode: 'new', + isManualRun: false, + groupIds: [data.groupId], + requestId, + }).catch((err) => + logger.error(`[${requestId}] auto-dispatch (updateWorkflowGroup autoRun=true) failed:`, err) + ) } return updatedTable @@ -3491,20 +3878,40 @@ async function backfillGroupOutputsFromLogs(opts: { const { pluckByPath } = await import('./pluck') - const rowRecords = await db - .select() - .from(userTableRows) - .where(eq(userTableRows.tableId, table.id)) + // Find rows whose group execution completed and grab their executionId + // directly from the sidecar — hits the (table_id, group_id) index, no + // table scan over rowdata. + const completedExecs = await db + .select({ + rowId: tableRowExecutions.rowId, + executionId: tableRowExecutions.executionId, + }) + .from(tableRowExecutions) + .where( + and( + eq(tableRowExecutions.tableId, table.id), + eq(tableRowExecutions.groupId, groupId), + eq(tableRowExecutions.status, 'completed') + ) + ) - // Collect unique executionIds across rows whose group execution completed. const executionIdsByRow = new Map() - for (const r of rowRecords) { - const exec = (r.executions as RowExecutions)?.[groupId] - if (!exec || exec.status !== 'completed' || !exec.executionId) continue - executionIdsByRow.set(r.id, exec.executionId) + for (const e of completedExecs) { + if (!e.executionId) continue + executionIdsByRow.set(e.rowId, e.executionId) } if (executionIdsByRow.size === 0) return + const rowRecords = await db + .select({ id: userTableRows.id, data: userTableRows.data }) + .from(userTableRows) + .where( + and( + eq(userTableRows.tableId, table.id), + inArray(userTableRows.id, Array.from(executionIdsByRow.keys())) + ) + ) + const executionIds = Array.from(new Set(executionIdsByRow.values())) const logs = await db .select({ @@ -3524,9 +3931,9 @@ async function backfillGroupOutputsFromLogs(opts: { const updates: Array<{ rowId: string; data: RowData }> = [] for (const r of rowRecords) { - const exec = (r.executions as RowExecutions)?.[groupId] - if (!exec?.executionId) continue - const log = logByExecutionId.get(exec.executionId) + const execId = executionIdsByRow.get(r.id) + if (!execId) continue + const log = logByExecutionId.get(execId) if (!log) continue const dataPatch: RowData = {} diff --git a/apps/sim/lib/table/types.ts b/apps/sim/lib/table/types.ts index 5d6b90d8413..4c92e65bfc0 100644 --- a/apps/sim/lib/table/types.ts +++ b/apps/sim/lib/table/types.ts @@ -70,9 +70,9 @@ export interface WorkflowGroup { } /** - * Per-row execution state for one workflow group, stored in - * `userTableRows.executions[groupId]`. Holds run metadata only — picked - * values land in `row.data` directly. + * Per-row execution state for one workflow group, persisted as a row in the + * `tableRowExecutions` sidecar keyed by `(rowId, groupId)`. Holds run + * metadata only — picked output values land in `row.data` directly. */ export interface RowExecutionMetadata { status: 'pending' | 'queued' | 'running' | 'completed' | 'error' | 'cancelled' @@ -94,6 +94,10 @@ export interface RowExecutionMetadata { * block should render `Error`, not every output column. */ blockErrors?: Record + /** ISO timestamp set when a cell is cancelled. The dispatcher skips + * re-runs whose `cancelledAt > dispatch.requestedAt` — a user cancel + * mid-dispatch must not be overridden by `isManualRun`. */ + cancelledAt?: string } /** Map of `WorkflowGroup.id` → execution state. Stored on every row. */ @@ -295,9 +299,10 @@ export interface UpdateRowData { data: RowData workspaceId: string /** - * Optional partial patch to merge into `userTableRows.executions`. Top-level - * keys are `WorkflowGroup.id`; pass `null` for a key to delete that group's - * execution state. Used by the cell task and cancel paths. + * Optional partial patch to apply to the row's `tableRowExecutions` + * entries. Top-level keys are `WorkflowGroup.id`; pass `null` for a key + * to delete that group's execution row. Used by the cell task and cancel + * paths. */ executionsPatch?: Record /** @@ -308,14 +313,6 @@ export interface UpdateRowData { * state. `updateRow` returns `null` when the guard rejects the write. */ cancellationGuard?: { groupId: string; executionId: string } - /** - * When true, the post-write `scheduleRunsForRows` call is skipped. Used by - * the cancel path (which is tearing rows down, not waking them up) and by - * the manual-run path (which fires its own `scheduleRunsForRows` with - * `isManualRun: true` and doesn't want a duplicate auto-fire pass on the - * cleared cells). Default false: every other write fires the reactor. - */ - skipScheduler?: boolean } export interface BulkUpdateData { @@ -334,8 +331,6 @@ export interface BatchUpdateByIdData { executionsPatch?: Record }> workspaceId: string - /** Same semantics as `UpdateRowData.skipScheduler`. */ - skipScheduler?: boolean } export interface BulkDeleteData { diff --git a/apps/sim/lib/table/workflow-columns.ts b/apps/sim/lib/table/workflow-columns.ts index da912e9d32e..686f97dbc6e 100644 --- a/apps/sim/lib/table/workflow-columns.ts +++ b/apps/sim/lib/table/workflow-columns.ts @@ -6,14 +6,14 @@ */ import { db } from '@sim/db' -import { pausedExecutions, userTableRows } from '@sim/db/schema' +import { pausedExecutions, tableRowExecutions, type userTableRows } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' -import { and, asc, eq, inArray, sql } from 'drizzle-orm' -import { getJobQueue } from '@/lib/core/async-jobs/config' +import { and, eq, inArray, sql } from 'drizzle-orm' import type { EnqueueOptions } from '@/lib/core/async-jobs/types' -import { buildCancelledExecution, writeWorkflowGroupState } from '@/lib/table/cell-write' +import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' +import { buildCancelledExecution } from '@/lib/table/cell-write' import type { RowData, RowExecutionMetadata, @@ -27,6 +27,7 @@ import type { const logger = createLogger('WorkflowGroupScheduler') import { areGroupDepsSatisfied, areOutputsFilled, isExecInFlight } from './deps' +import type { DispatchMode } from './dispatcher' export { getUnmetGroupDeps, @@ -54,14 +55,16 @@ export type EligibilityReason = | 'in-flight' | 'completed-on-auto' | 'error-on-auto' + | 'cancelled-on-auto' | 'completed-on-incomplete' + | 'has-prior-attempt' | 'manual-bypass' | 'deps-unmet' export function classifyEligibility( group: WorkflowGroup, row: TableRow, - opts?: { isManualRun?: boolean; mode?: 'all' | 'incomplete' } + opts?: { isManualRun?: boolean; mode?: DispatchMode } ): EligibilityReason { const isManualRun = opts?.isManualRun ?? false const mode = opts?.mode ?? 'all' @@ -69,14 +72,27 @@ export function classifyEligibility( if (group.autoRun === false && !isManualRun) return 'autoRun-off' const exec = row.executions?.[group.id] - if (isExecInFlight(exec)) return 'in-flight' + // Dispatcher pre-stamp orphans (`pending` + `executionId: null`) are + // placeholders left behind when a previous dispatcher loop wrote the stamp + // but no cell-task picked up (cascade-lock contention, trigger.dev queue + // failure, etc.). Treat them as claimable so a new dispatcher can re-enqueue + // — without this carve-out the row would render "Queued" forever. Matches + // the `pickNextEligibleGroupForRow` cascade-loop carve-out. + const isOrphanPreStamp = exec?.status === 'pending' && exec.executionId == null + if (!isOrphanPreStamp && isExecInFlight(exec)) return 'in-flight' const status = exec?.status + // `mode: 'new'` is the auto-fire scope: only rows that have never been + // attempted on this group run. Any pre-existing exec entry — completed, + // cancelled, or error — keeps the cell sticky until the user manually + // re-runs via "Run column" / "Run all rows" / "Run this row". + // Exception: orphan pre-stamps are claimable (handled above). + if (mode === 'new' && exec && !isOrphanPreStamp) return 'has-prior-attempt' + const completedAndFilled = status === 'completed' && areOutputsFilled(group, row) if (!isManualRun && completedAndFilled) return 'completed-on-auto' - // Auto-fire skips `error` to avoid infinite-retry loops on a deterministic - // failure. `cancelled` is left runnable — cancellation is user-initiated. if (!isManualRun && status === 'error') return 'error-on-auto' + if (!isManualRun && status === 'cancelled') return 'cancelled-on-auto' if (mode === 'incomplete' && completedAndFilled) return 'completed-on-incomplete' if (isManualRun && group.autoRun === false) return 'manual-bypass' @@ -92,6 +108,35 @@ export function isGroupEligible( return reason === 'eligible' || reason === 'manual-bypass' } +/** Walks a row's workflow groups (in `workflowGroups` order) and returns the + * first one whose deps are met and that isn't already in-flight under a + * different worker. Skips `excludeGroupId` (the group we just finished in + * the cascade loop, to prevent self-retrigger). The cascade-loop is allowed + * to claim past a dispatcher pre-stamp (`pending` with `executionId: null`) + * — that's a placeholder, not a real worker claim. */ +export function pickNextEligibleGroupForRow( + table: TableDefinition, + row: TableRow, + excludeGroupId?: string +): WorkflowGroup | null { + const groups = table.schema.workflowGroups ?? [] + for (const group of groups) { + if (group.id === excludeGroupId) continue + const exec = row.executions?.[group.id] + // Dispatcher pre-stamp (pending + executionId: null) is a placeholder; the + // cascade-loop is the right owner of the claim. Treat as "claimable" by + // pretending the exec doesn't exist for the eligibility check. + const effectiveRow = + exec?.status === 'pending' && exec.executionId == null + ? { ...row, executions: { ...row.executions, [group.id]: undefined } as RowExecutions } + : row + if (isGroupEligible(group, effectiveRow, { isManualRun: false, mode: 'incomplete' })) { + return group + } + } + return null +} + /** * Shared options for the three `scheduleRuns*` entry points. `isManualRun` * flips two gates in the eligibility predicate so a manual click can re-run @@ -101,177 +146,153 @@ export interface ScheduleOpts { groupId?: string groupIds?: string[] isManualRun?: boolean - mode?: 'all' | 'incomplete' + mode?: DispatchMode } -/** - * Re-evaluate eligibility on these specific rows and enqueue runnable cells. - * The hot path: every row write (insert / update / cascade) calls this with the - * just-written row(s). - */ -export async function scheduleRunsForRows( +/** Pure eligibility filter + payload building. Shared by the auto-fire path + * (`scheduleRunsForRows`) and the dispatcher's per-window batch path. */ +export function buildPendingRuns( table: TableDefinition, rows: TableRow[], opts?: ScheduleOpts -): Promise<{ triggered: number }> { - try { - const allGroups = table.schema.workflowGroups ?? [] - if (allGroups.length === 0) return { triggered: 0 } - if (rows.length === 0) return { triggered: 0 } - - const groupIdFilter = opts?.groupIds - ? new Set(opts.groupIds) - : opts?.groupId - ? new Set([opts.groupId]) - : null - const groups = groupIdFilter ? allGroups.filter((g) => groupIdFilter.has(g.id)) : allGroups - if (groups.length === 0) return { triggered: 0 } - - const orderedRows = rows.length <= 1 ? rows : [...rows].sort((a, b) => a.position - b.position) - - const pendingRuns: RunGroupCellOptions[] = [] - const reasonCounts: Partial> = {} - - for (const row of orderedRows) { - for (const group of groups) { - const reason = classifyEligibility(group, row, { - isManualRun: opts?.isManualRun, - mode: opts?.mode, - }) - reasonCounts[reason] = (reasonCounts[reason] ?? 0) + 1 - if (reason !== 'eligible' && reason !== 'manual-bypass') continue - pendingRuns.push({ - tableId: table.id, - tableName: table.name, - rowId: row.id, - groupId: group.id, - workflowId: group.workflowId, - workspaceId: table.workspaceId, - executionId: generateId(), - }) - } +): WorkflowGroupCellPayload[] { + const allGroups = table.schema.workflowGroups ?? [] + if (allGroups.length === 0) return [] + if (rows.length === 0) return [] + + const groupIdFilter = opts?.groupIds + ? new Set(opts.groupIds) + : opts?.groupId + ? new Set([opts.groupId]) + : null + const groups = groupIdFilter ? allGroups.filter((g) => groupIdFilter.has(g.id)) : allGroups + if (groups.length === 0) return [] + + const orderedRows = rows.length <= 1 ? rows : [...rows].sort((a, b) => a.position - b.position) + + const pendingRuns: WorkflowGroupCellPayload[] = [] + const reasonCounts: Partial> = {} + + for (const row of orderedRows) { + for (const group of groups) { + const reason = classifyEligibility(group, row, { + isManualRun: opts?.isManualRun, + mode: opts?.mode, + }) + reasonCounts[reason] = (reasonCounts[reason] ?? 0) + 1 + if (reason !== 'eligible' && reason !== 'manual-bypass') continue + pendingRuns.push({ + tableId: table.id, + tableName: table.name, + rowId: row.id, + groupId: group.id, + workflowId: group.workflowId, + workspaceId: table.workspaceId, + executionId: generateId(), + }) } + } - logger.debug( - `[Cascade] table=${table.id} rows=${rows.length} groups=${groups.length} manual=${opts?.isManualRun ?? false} mode=${opts?.mode ?? 'all'} reasons=${JSON.stringify(reasonCounts)}` - ) - - if (pendingRuns.length === 0) return { triggered: 0 } + logger.debug( + `[Cascade] table=${table.id} rows=${rows.length} groups=${groups.length} manual=${opts?.isManualRun ?? false} mode=${opts?.mode ?? 'all'} reasons=${JSON.stringify(reasonCounts)}` + ) - logger.info(`Scheduling ${pendingRuns.length} workflow group cell run(s) for table=${table.id}`) + return pendingRuns +} - const queue = await getJobQueue() - const { executeWorkflowGroupCellJob } = await import('@/background/workflow-column-execution') - const items = pendingRuns.map((runOpts) => ({ - payload: runOpts, - options: { - metadata: { +/** Build the per-cell `{payload, options}` items for `queue.batchEnqueue` / + * `queue.batchEnqueueAndWait`. Hydrates trigger.dev tags, concurrency keys, + * the inline runner, and the cancel key the inline backend uses to map a + * Stop click to the in-flight cell's AbortController. */ +export async function buildEnqueueItems( + pendingRuns: WorkflowGroupCellPayload[] +): Promise> { + const { executeWorkflowGroupCellJob } = await import('@/background/workflow-column-execution') + return pendingRuns.map((runOpts) => ({ + payload: runOpts, + options: { + metadata: { + workflowId: runOpts.workflowId, + workspaceId: runOpts.workspaceId, + correlation: { + executionId: runOpts.executionId, + requestId: `wfgrp-${runOpts.executionId}`, + source: 'workflow' as const, workflowId: runOpts.workflowId, - workspaceId: runOpts.workspaceId, - correlation: { - executionId: runOpts.executionId, - requestId: `wfgrp-${runOpts.executionId}`, - source: 'workflow' as const, - workflowId: runOpts.workflowId, - triggerType: 'table', - }, + triggerType: 'table', }, - concurrencyKey: runOpts.tableId, - concurrencyLimit: TABLE_CONCURRENCY_LIMIT, - tags: [`tableId:${runOpts.tableId}`, `rowId:${runOpts.rowId}`, `group:${runOpts.groupId}`], - runner: executeWorkflowGroupCellJob as EnqueueOptions['runner'], }, - })) - - let jobIds: string[] - try { - jobIds = await queue.batchEnqueue('workflow-group-cell', items) - } catch (err) { - logger.error(`Batch enqueue failed for table=${table.id}:`, err) - await Promise.allSettled( - pendingRuns.map((runOpts) => - writeWorkflowGroupState(runOpts, { - executionState: { - status: 'error', - executionId: runOpts.executionId, - jobId: null, - workflowId: runOpts.workflowId, - error: toError(err).message, - }, - }) - ) - ) - return { triggered: 0 } - } - - // Stamp `queued` in chunks of `TABLE_CONCURRENCY_LIMIT`. Within a chunk we - // parallelize the writes (no ordering constraint); across chunks we await - // serially so trigger.dev still picks rows up in submission order — the - // concurrency cap means at most one chunk is in flight per table anyway. - for (let i = 0; i < pendingRuns.length; i += TABLE_CONCURRENCY_LIMIT) { - const chunk = pendingRuns.slice(i, i + TABLE_CONCURRENCY_LIMIT) - const ids = jobIds.slice(i, i + TABLE_CONCURRENCY_LIMIT) - await Promise.all(chunk.map((run, j) => stampQueuedOrCancel(queue, run, ids[j]))) - } - return { triggered: pendingRuns.length } - } catch (err) { - logger.error('scheduleRunsForRows failed:', err) - return { triggered: 0 } - } + concurrencyKey: runOpts.tableId, + concurrencyLimit: TABLE_CONCURRENCY_LIMIT, + tags: cellTagsFor(runOpts), + runner: executeWorkflowGroupCellJob as EnqueueOptions['runner'], + cancelKey: cellCancelKey(runOpts.tableId, runOpts.rowId, runOpts.groupId), + }, + })) } -/** - * Re-evaluate eligibility on every row of the table. Used after schema changes - * (workflow group added, autoRun toggled on) where we don't have a list of - * just-written rows but need to fire any newly-eligible (row × group) pair. - */ -export async function scheduleRunsForTable( - table: TableDefinition, - opts?: ScheduleOpts -): Promise<{ triggered: number }> { - const rows = await fetchAllRows(table.id) - return scheduleRunsForRows(table, rows, opts) -} - -/** - * Re-evaluate eligibility on the rows with these ids. Sugar for callers that - * have row ids but not materialized rows. - */ -async function scheduleRunsForRowIds( - table: TableDefinition, - rowIds: string[], - opts?: ScheduleOpts -): Promise<{ triggered: number }> { - if (rowIds.length === 0) return { triggered: 0 } - const rows = await fetchRowsByIds(table.id, rowIds) - return scheduleRunsForRows(table, rows, opts) +/** Stable key for `cancelInlineRun` lookups. Stamped on every enqueue item by + * `buildEnqueueItems`; the cancel path computes the same key per cell. */ +export function cellCancelKey(tableId: string, rowId: string, groupId: string): string { + return `${tableId}:${rowId}:${groupId}` } -async function fetchAllRows(tableId: string): Promise { - const records = await db.select().from(userTableRows).where(eq(userTableRows.tableId, tableId)) - return records.map(toTableRow) +/** Trigger.dev tags stamped on every `workflow-group-cell` run so tag-based + * cancel (`runs.list({ tag })` + `runs.cancel(id)`) can target a specific + * cell or table without needing per-cell jobIds. */ +export function cellTagsFor(runOpts: WorkflowGroupCellPayload): string[] { + return [`tableId:${runOpts.tableId}`, `rowId:${runOpts.rowId}`, `group:${runOpts.groupId}`] } -async function fetchRowsByIds(tableId: string, rowIds: string[]): Promise { - const records = await db - .select() - .from(userTableRows) - .where(and(eq(userTableRows.tableId, tableId), inArray(userTableRows.id, rowIds))) - return records.map(toTableRow) +/** Cancel every active trigger.dev `workflow-group-cell` run whose tags + * match. Paginates `runs.list` and fires `runs.cancel` per match. Errors + * are logged and swallowed — the cell-write SQL guard already makes + * workers no-op on cancelled rows whether or not trigger.dev acked the + * cancel, so partial failure is safe. */ +export async function cancelCellRunsByTags(tags: string[]): Promise { + if (tags.length === 0) return + const { runs } = await import('@trigger.dev/sdk') + const cancellations: Array> = [] + try { + // Trigger.dev paginates with auto-iterating cursor — looping the page + // iterator is the documented usage pattern. + for await (const run of runs.list({ + tag: tags, + taskIdentifier: 'workflow-group-cell', + status: ['PENDING_VERSION', 'QUEUED', 'DEQUEUED', 'EXECUTING', 'WAITING', 'DELAYED'], + })) { + cancellations.push( + runs.cancel(run.id).catch((err) => { + logger.warn(`cancelCellRunsByTags: cancel ${run.id} failed`, { + error: toError(err).message, + }) + }) + ) + } + await Promise.allSettled(cancellations) + } catch (err) { + logger.warn(`cancelCellRunsByTags: list failed`, { + tags, + error: toError(err).message, + }) + } } -function toTableRow(r: typeof userTableRows.$inferSelect): TableRow { +export function toTableRow( + r: typeof userTableRows.$inferSelect, + executions: RowExecutions = {} +): TableRow { return { id: r.id, data: r.data as RowData, - executions: (r.executions as RowExecutions) ?? {}, + executions, position: r.position, createdAt: r.createdAt, updatedAt: r.updatedAt, } } -interface RunGroupCellOptions { +export interface WorkflowGroupCellPayload { tableId: string tableName: string rowId: string @@ -282,50 +303,25 @@ interface RunGroupCellOptions { } /** Per-table concurrency cap. Mirrors trigger.dev's `concurrencyLimit: 20`. */ -const TABLE_CONCURRENCY_LIMIT = 20 - -async function stampQueuedOrCancel( - queue: Awaited>, - opts: RunGroupCellOptions, - jobId: string -): Promise { - let stampResult: 'wrote' | 'skipped' = 'wrote' - try { - stampResult = await writeWorkflowGroupState(opts, { - executionState: { - status: 'queued', - executionId: opts.executionId, - jobId, - workflowId: opts.workflowId, - error: null, - }, - }) - } catch (err) { - logger.error( - `Failed to stamp queued state (table=${opts.tableId} row=${opts.rowId} group=${opts.groupId}):`, - err - ) - } - - if (stampResult === 'skipped') { - try { - await queue.cancelJob(jobId) - } catch (cancelErr) { - logger.error(`Failed to cancel orphaned workflow-group-cell job (jobId=${jobId}):`, cancelErr) - } - } -} +export const TABLE_CONCURRENCY_LIMIT = 20 /** * Cancels in-flight workflow-group runs for a table or single row. Writes * `cancelled` authoritatively for every `running` or `pending` group * execution — the client-side write is the source of truth, independent of * whether the trigger.dev cancel reaches the worker before its terminal - * write. + * write. Pass `groupIds` to restrict the cancel to a subset of groups on + * the row (used by `updateRow` to cancel only the downstream groups whose + * deps just changed). */ -export async function cancelWorkflowGroupRuns(tableId: string, rowId?: string): Promise { +export async function cancelWorkflowGroupRuns( + tableId: string, + rowId?: string, + options?: { groupIds?: string[] } +): Promise { const { getTableById, updateRow } = await import('@/lib/table/service') const { getJobQueue } = await import('@/lib/core/async-jobs/config') + const { listActiveDispatches, markActiveDispatchesCancelled } = await import('./dispatcher') const table = await getTableById(tableId) if (!table) { @@ -333,22 +329,66 @@ export async function cancelWorkflowGroupRuns(tableId: string, rowId?: string): return 0 } - const groups = table.schema.workflowGroups ?? [] - if (groups.length === 0) return 0 - const groupIds = new Set(groups.map((g) => g.id)) + // Per-row cancel leaves the dispatcher alone — other rows in the same + // dispatch keep running. Table-wide cancel must stop it, else the cursor + // marches on and re-enqueues fresh cells past what we just cancelled. + if (!rowId) { + await markActiveDispatchesCancelled(tableId) + } + + const allGroups = table.schema.workflowGroups ?? [] + if (allGroups.length === 0) return 0 + const groupIds = options?.groupIds + ? new Set(allGroups.filter((g) => options.groupIds?.includes(g.id)).map((g) => g.id)) + : new Set(allGroups.map((g) => g.id)) + if (groupIds.size === 0) return 0 + + // Per-row Stop on a row the dispatcher hasn't reached yet has no sidecar + // entry to cancel — the dispatcher would later walk to that row, see no + // exec, classify eligible, and re-fire. Pre-write `cancelled` tombstones + // for active-dispatch in-scope groups so the existing `cancelledAt > + // dispatch.requestedAt` filter in `dispatcherStep` catches them. Skip + // when there's no active dispatch (nothing to outrun). + let aheadOfCursorTombstones: Array<{ groupId: string; workflowId: string }> = [] + if (rowId) { + const activeDispatches = await listActiveDispatches(tableId) + const relevant = activeDispatches.filter((d) => { + if (d.scope.rowIds && !d.scope.rowIds.includes(rowId)) return false + return d.scope.groupIds.some((gid) => groupIds.has(gid)) + }) + if (relevant.length > 0) { + // Intersection of targeted groups with active-dispatch scopes — only + // these groups are at risk of being re-fired by an in-progress dispatch. + const atRisk = new Set() + for (const d of relevant) { + for (const gid of d.scope.groupIds) { + if (groupIds.has(gid)) atRisk.add(gid) + } + } + aheadOfCursorTombstones = Array.from(atRisk).map((gid) => ({ + groupId: gid, + workflowId: allGroups.find((g) => g.id === gid)?.workflowId ?? '', + })) + } + } // Always filter by tableId — for the per-row case this prevents a // cross-table rowId from doing a wasted DB round-trip and silently // under-counting in the response. For the table-wide case it's the // primary filter. - const rows = await db + const inFlightStatuses = ['running', 'queued', 'pending'] + const inFlightFilters = [ + eq(tableRowExecutions.tableId, tableId), + inArray(tableRowExecutions.status, inFlightStatuses), + inArray(tableRowExecutions.groupId, Array.from(groupIds)), + ] + if (rowId) { + inFlightFilters.push(eq(tableRowExecutions.rowId, rowId)) + } + const inFlightRows = await db .select() - .from(userTableRows) - .where( - rowId - ? and(eq(userTableRows.id, rowId), eq(userTableRows.tableId, tableId)) - : eq(userTableRows.tableId, tableId) - ) + .from(tableRowExecutions) + .where(and(...inFlightFilters)) const queue = await getJobQueue() @@ -358,45 +398,59 @@ export async function cancelWorkflowGroupRuns(tableId: string, rowId?: string): jobIds: string[] cancelledCount: number } - const mutations: RowMutation[] = [] - - for (const row of rows) { - const executions = (row.executions ?? {}) as RowExecutions - const executionsPatch: Record = {} - const jobIds: string[] = [] - let cancelledCount = 0 - for (const [gid, exec] of Object.entries(executions)) { - if (!groupIds.has(gid)) continue - // `pending` covers the post-reset, pre-dispatch window; `queued` covers - // the post-enqueue, pre-pickup window — a stop click in either state - // must still stick once the worker picks the row up. - if (exec.status !== 'running' && exec.status !== 'queued' && exec.status !== 'pending') - continue - if (exec.jobId) jobIds.push(exec.jobId) - executionsPatch[gid] = buildCancelledExecution(exec) - cancelledCount++ + const byRow = new Map() + + for (const r of inFlightRows) { + const prev: RowExecutionMetadata = { + status: r.status as RowExecutionMetadata['status'], + executionId: r.executionId ?? null, + jobId: r.jobId ?? null, + workflowId: r.workflowId, + error: r.error ?? null, + ...(r.blockErrors && Object.keys(r.blockErrors as Record).length > 0 + ? { blockErrors: r.blockErrors as Record } + : {}), } - if (cancelledCount > 0) { - mutations.push({ rowId: row.id, executionsPatch, jobIds, cancelledCount }) + const existing = byRow.get(r.rowId) ?? { + rowId: r.rowId, + executionsPatch: {}, + jobIds: [], + cancelledCount: 0, } + if (prev.jobId) existing.jobIds.push(prev.jobId) + existing.executionsPatch[r.groupId] = buildCancelledExecution(prev) + existing.cancelledCount++ + byRow.set(r.rowId, existing) } - // Cancel jobs and write rows in parallel — no ordering dependency, so - // serializing dozens-to-hundreds of rows per stop click is pure latency. - await Promise.allSettled( - mutations.flatMap((m) => + const mutations: RowMutation[] = Array.from(byRow.values()) + + // Abort in-flight cell runs. The interface method `cancelByKey` is a no-op + // on the trigger.dev backend (no in-process AbortControllers) and aborts + // the matching AbortController on the database backend. Trigger.dev's tag + // sweep covers the SaaS path; the cell-write SQL guard is the + // authoritative stop signal regardless of backend. + for (const m of mutations) { + for (const gid of Object.keys(m.executionsPatch)) { + queue.cancelByKey(cellCancelKey(tableId, m.rowId, gid)) + } + } + const tagSweepPromise = isTriggerDevEnabled + ? cancelCellRunsByTags(rowId ? [`rowId:${rowId}`] : [`tableId:${tableId}`]) + : Promise.resolve() + await Promise.allSettled([ + ...mutations.flatMap((m) => m.jobIds.map((jobId) => queue.cancelJob(jobId).catch((err) => { logger.error(`Failed to cancel job ${jobId} for ${tableId}/${m.rowId}:`, err) }) ) - ) - ) - // `skipScheduler: true` — we're tearing rows down, not waking them up. The - // auto-fire reactor would otherwise see independent (row, group) pairs whose - // deps are now satisfied (because the upstream group already wrote its - // output before the cancel) and re-enqueue them, which is exactly what the - // user clicked Stop to prevent. + ), + tagSweepPromise, + ]) + // `updateRow` no longer auto-fires the dispatcher post-write — the reactor + // was removed. Cancel-writes only touch executions[gid] state; no risk of + // re-enqueueing what we just cancelled. await Promise.allSettled( mutations.map((m) => updateRow( @@ -406,7 +460,6 @@ export async function cancelWorkflowGroupRuns(tableId: string, rowId?: string): data: {}, workspaceId: table.workspaceId, executionsPatch: m.executionsPatch, - skipScheduler: true, }, table, `wfgrp-cancel-${m.rowId}` @@ -416,6 +469,45 @@ export async function cancelWorkflowGroupRuns(tableId: string, rowId?: string): ) ) + // Tombstones for ahead-of-cursor groups. The in-flight cancel writes above + // already cover groups that have a sidecar entry; we only need fresh + // tombstones for groups that don't (the dispatcher hasn't reached them + // yet, so there's nothing to cancel — but without a tombstone the + // dispatcher would still re-fire when its cursor walks to this row). + if (rowId && aheadOfCursorTombstones.length > 0) { + const alreadyHandled = new Set(mutations.flatMap((m) => Object.keys(m.executionsPatch))) + const needsTombstone = aheadOfCursorTombstones.filter((t) => !alreadyHandled.has(t.groupId)) + if (needsTombstone.length > 0) { + const now = new Date() + await Promise.allSettled( + needsTombstone.map((t) => + db + .insert(tableRowExecutions) + .values({ + tableId, + rowId, + groupId: t.groupId, + status: 'cancelled', + executionId: null, + jobId: null, + workflowId: t.workflowId, + error: 'Cancelled', + runningBlockIds: [], + blockErrors: {}, + cancelledAt: now, + updatedAt: now, + }) + .onConflictDoNothing({ + target: [tableRowExecutions.rowId, tableRowExecutions.groupId], + }) + .catch((err) => { + logger.error(`Failed to write tombstone for ${tableId}/${rowId}/${t.groupId}:`, err) + }) + ) + ) + } + } + return mutations.reduce((sum, m) => sum + m.cancelledCount, 0) } @@ -429,96 +521,122 @@ export async function cancelWorkflowGroupRuns(tableId: string, rowId?: string): export async function runWorkflowColumn(opts: { tableId: string workspaceId: string - mode: 'all' | 'incomplete' + mode: DispatchMode requestId: string groupIds?: string[] rowIds?: string[] -}): Promise<{ triggered: number }> { + /** When false, eligibility honors `autoRun: false` and treats completed + * cells as terminal — appropriate for auto-fire after row writes or + * schema changes. Defaults to true (user-initiated "Run column"). */ + isManualRun?: boolean +}): Promise<{ dispatchId: string | null }> { const { tableId, workspaceId, mode, requestId, groupIds, rowIds } = opts - const { getTableById, batchUpdateRows } = await import('./service') + const isManualRun = opts.isManualRun ?? true + // Empty `rowIds` array means "scope explicitly empty" — auto-fire callers + // (CSV import on zero matches, etc.) end up here. Skip the dispatch entirely + // rather than walk the table with a no-match filter. + if (rowIds && rowIds.length === 0) return { dispatchId: null } + // Lazy imports: `./service` and `./dispatcher` both close cycles back to + // this module; `@trigger.dev/sdk` is heavy and only needed on this op. + const { getTableById } = await import('./service') const table = await getTableById(tableId) if (!table) throw new Error('Table not found') if (table.workspaceId !== workspaceId) throw new Error('Invalid workspace ID') const allGroups = table.schema.workflowGroups ?? [] const targetGroups = groupIds ? allGroups.filter((g) => groupIds.includes(g.id)) : allGroups - if (targetGroups.length === 0) return { triggered: 0 } - - logger.info( - `[Cascade] [${requestId}] manual run table=${tableId} groups=[${targetGroups.map((g) => g.id).join(',')}] rows=${rowIds ? `[${rowIds.join(',')}]` : 'all'} mode=${mode}` + // Tables with no workflow groups are the majority. Auto-fire callers from + // every row write would otherwise produce error-level log spam on every + // PATCH/insert. Manual run-column callers always pass `groupIds` so they + // can't reach here with an empty target. + if (targetGroups.length === 0) return { dispatchId: null } + const targetGroupIds = targetGroups.map((g) => g.id) + + const { bulkClearWorkflowGroupCells, insertDispatch, runDispatcherToCompletion } = await import( + './dispatcher' ) - const filters = [eq(userTableRows.tableId, tableId), eq(userTableRows.workspaceId, workspaceId)] - if (rowIds && rowIds.length > 0) { - filters.push(inArray(userTableRows.id, rowIds)) - } - const candidateRows = await db - .select({ - id: userTableRows.id, - position: userTableRows.position, - data: userTableRows.data, - executions: userTableRows.executions, - createdAt: userTableRows.createdAt, - updatedAt: userTableRows.updatedAt, - }) - .from(userTableRows) - .where(and(...filters)) - .orderBy(asc(userTableRows.position)) - - if (candidateRows.length === 0) return { triggered: 0 } - - // Per-row: collect eligible groups, build cleared data + executionsPatch. - type Update = { - rowId: string - data: RowData - executionsPatch: Record - } - const updates: Update[] = [] - const clearedRows: TableRow[] = [] - for (const r of candidateRows) { - const tableRow: TableRow = { - id: r.id, - data: r.data as RowData, - executions: (r.executions as RowExecutions) ?? {}, - position: r.position, - createdAt: r.createdAt, - updatedAt: r.updatedAt, - } - const eligibleGroups = targetGroups.filter((g) => - isGroupEligible(g, tableRow, { isManualRun: true, mode }) - ) - if (eligibleGroups.length === 0) continue - - const clearedData: RowData = {} - const executionsPatch: Record = {} - for (const g of eligibleGroups) { - for (const o of g.outputs) clearedData[o.columnName] = null - executionsPatch[g.id] = null + // For manual runs (Run all rows / Run column / Refresh-row / Refresh-cell), + // cancel any prior active dispatches AND in-flight cells in scope before + // clearing. Without this: + // - Two dispatcher loops would walk overlapping rows and burn duplicate work. + // - mode:'all' bulk-clear deletes in-flight sidecar rows without aborting + // workers — those would keep writing into the wiped state. + // Scope: table-wide cancel when rowIds is empty (also cancels active + // dispatches via markActiveDispatchesCancelled), per-row cancel otherwise + // (no dispatch cancel — other rows' dispatches keep running). Dep-edit + // cascade in `updateRow` already cancels its own scope before calling, + // so the duplicate work here is a cheap no-op for that caller. + // Auto-fire (`mode:'new'`) is harmless overlap-wise — the NOT EXISTS + // filter excludes already-attempted rows. + const cancelPriorRuns = isManualRun && (mode === 'all' || mode === 'incomplete') + if (cancelPriorRuns) { + if (!rowIds || rowIds.length === 0) { + await cancelWorkflowGroupRuns(tableId, undefined, { groupIds: targetGroupIds }) + } else { + // Per-row cancel — sequential so we don't fan out N parallel + // markActiveDispatchesCancelled calls (it's a no-op when rowId is set, + // but each call still touches the DB). + for (const rowId of rowIds) { + await cancelWorkflowGroupRuns(tableId, rowId, { groupIds: targetGroupIds }) + } } - updates.push({ rowId: r.id, data: clearedData, executionsPatch }) - - const remainingExec = { ...tableRow.executions } - for (const g of eligibleGroups) delete remainingExec[g.id] - clearedRows.push({ - ...tableRow, - data: { ...tableRow.data, ...clearedData }, - executions: remainingExec, - }) } - if (updates.length === 0) return { triggered: 0 } - - // `skipScheduler: true` because we fire `scheduleRunsForRows` ourselves - // below with `isManualRun: true`. Without the skip, batchUpdateRows runs the - // auto-fire reactor first and any autoRun=true sibling group whose deps are - // satisfied would race the manual call. - await batchUpdateRows({ tableId, updates, workspaceId, skipScheduler: true }, table, requestId) + // Wipe targeted output cols + executions[gid] before any cells fire so the + // user sees the column flip to empty/Pending instantly. + await bulkClearWorkflowGroupCells({ + tableId, + groups: targetGroups.map((g) => ({ id: g.id, outputs: g.outputs })), + rowIds, + mode, + }) - return scheduleRunsForRows(table, clearedRows, { - isManualRun: true, - groupIds: targetGroups.map((g) => g.id), + // Always insert a `table_run_dispatches` row. The dispatcher state machine + // is the single source of truth for cursor advancement, SSE emission, and + // cancel — backend (trigger.dev SaaS vs in-process) only affects how each + // window's cells get executed. + const dispatchId = await insertDispatch({ + tableId, + workspaceId, + requestId, mode, + scope: { + groupIds: targetGroupIds, + ...(rowIds && rowIds.length > 0 ? { rowIds } : {}), + }, + isManualRun, }) + + logger.info( + `[Cascade] [${requestId}] dispatch ${dispatchId} table=${tableId} groups=[${targetGroupIds.join(',')}] rows=${rowIds ? `[${rowIds.join(',')}]` : 'all'} mode=${mode}` + ) + + if (isTriggerDevEnabled) { + // Trigger.dev runs `tableRunDispatcherTask`, which loops `dispatcherStep` + // until done with CRIU-checkpointed waits between windows. + const [{ tableRunDispatcherTask }, { tasks }] = await Promise.all([ + import('@/background/table-run-dispatcher'), + import('@trigger.dev/sdk'), + ]) + await tasks.trigger( + 'table-run-dispatcher', + { dispatchId }, + { concurrencyKey: dispatchId } + ) + } else { + // Local / no-trigger.dev: drive the same loop in-process, fire-and-forget + // so the HTTP request returns instantly (mirrors the trigger.dev path's + // async fan-out). + void runDispatcherToCompletion(dispatchId).catch((err) => + logger.error(`[${requestId}] dispatcher loop failed`, { + dispatchId, + error: toError(err).message, + }) + ) + } + + return { dispatchId } } // ───────────────────────────── Validation ───────────────────────────── diff --git a/packages/db/migrations/0209_smiling_fixer.sql b/packages/db/migrations/0209_smiling_fixer.sql new file mode 100644 index 00000000000..109523397fb --- /dev/null +++ b/packages/db/migrations/0209_smiling_fixer.sql @@ -0,0 +1,41 @@ +CREATE TABLE "table_row_executions" ( + "table_id" text NOT NULL, + "row_id" text NOT NULL, + "group_id" text NOT NULL, + "status" text NOT NULL, + "execution_id" text, + "job_id" text, + "workflow_id" text NOT NULL, + "error" text, + "running_block_ids" text[] DEFAULT '{}'::text[] NOT NULL, + "block_errors" jsonb DEFAULT '{}'::jsonb NOT NULL, + "cancelled_at" timestamp, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "table_row_executions_row_id_group_id_pk" PRIMARY KEY("row_id","group_id") +); +--> statement-breakpoint +CREATE TABLE "table_run_dispatches" ( + "id" text PRIMARY KEY NOT NULL, + "table_id" text NOT NULL, + "workspace_id" text NOT NULL, + "request_id" text NOT NULL, + "mode" text NOT NULL, + "scope" jsonb NOT NULL, + "status" text DEFAULT 'pending' NOT NULL, + "cursor" integer DEFAULT 0 NOT NULL, + "is_manual_run" boolean DEFAULT true NOT NULL, + "requested_at" timestamp DEFAULT now() NOT NULL, + "completed_at" timestamp, + "cancelled_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "table_row_executions" ADD CONSTRAINT "table_row_executions_table_id_user_table_definitions_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."user_table_definitions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "table_row_executions" ADD CONSTRAINT "table_row_executions_row_id_user_table_rows_id_fk" FOREIGN KEY ("row_id") REFERENCES "public"."user_table_rows"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "table_run_dispatches" ADD CONSTRAINT "table_run_dispatches_table_id_user_table_definitions_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."user_table_definitions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "table_run_dispatches" ADD CONSTRAINT "table_run_dispatches_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "table_row_executions_table_status_idx" ON "table_row_executions" USING btree ("table_id","status") WHERE "table_row_executions"."status" IN ('queued', 'running', 'pending');--> statement-breakpoint +CREATE INDEX "table_row_executions_execution_id_idx" ON "table_row_executions" USING btree ("execution_id") WHERE "table_row_executions"."execution_id" IS NOT NULL;--> statement-breakpoint +CREATE INDEX "table_row_executions_table_group_idx" ON "table_row_executions" USING btree ("table_id","group_id");--> statement-breakpoint +CREATE INDEX "table_run_dispatches_active_idx" ON "table_run_dispatches" USING btree ("table_id","status");--> statement-breakpoint +CREATE INDEX "table_run_dispatches_watchdog_idx" ON "table_run_dispatches" USING btree ("status","requested_at");--> statement-breakpoint +ALTER TABLE "user_table_rows" DROP COLUMN "executions"; \ No newline at end of file diff --git a/packages/db/migrations/meta/0209_snapshot.json b/packages/db/migrations/meta/0209_snapshot.json new file mode 100644 index 00000000000..378fabd1b3a --- /dev/null +++ b/packages/db/migrations/meta/0209_snapshot.json @@ -0,0 +1,16431 @@ +{ + "id": "77be6741-7a6f-4e72-8be1-54fa58eee8e3", + "prevId": "67dde2dd-485d-4e3d-b2a5-a3caae8b26ae", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.a2a_agent": { + "name": "a2a_agent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "authentication": { + "name": "authentication", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "signatures": { + "name": "signatures", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "a2a_agent_workflow_id_idx": { + "name": "a2a_agent_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_created_by_idx": { + "name": "a2a_agent_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_workflow_unique": { + "name": "a2a_agent_workspace_workflow_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"a2a_agent\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_archived_at_idx": { + "name": "a2a_agent_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_archived_partial_idx": { + "name": "a2a_agent_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"a2a_agent\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_agent_workspace_id_workspace_id_fk": { + "name": "a2a_agent_workspace_id_workspace_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_workflow_id_workflow_id_fk": { + "name": "a2a_agent_workflow_id_workflow_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_created_by_user_id_fk": { + "name": "a2a_agent_created_by_user_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_push_notification_config": { + "name": "a2a_push_notification_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_schemes": { + "name": "auth_schemes", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "auth_credentials": { + "name": "auth_credentials", + "type": "text", + "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": { + "a2a_push_notification_config_task_unique": { + "name": "a2a_push_notification_config_task_unique", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_push_notification_config_task_id_a2a_task_id_fk": { + "name": "a2a_push_notification_config_task_id_a2a_task_id_fk", + "tableFrom": "a2a_push_notification_config", + "tableTo": "a2a_task", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_task": { + "name": "a2a_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "a2a_task_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'submitted'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "artifacts": { + "name": "artifacts", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "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()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "a2a_task_agent_id_idx": { + "name": "a2a_task_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_session_id_idx": { + "name": "a2a_task_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_status_idx": { + "name": "a2a_task_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_execution_id_idx": { + "name": "a2a_task_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_created_at_idx": { + "name": "a2a_task_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_task_agent_id_a2a_agent_id_fk": { + "name": "a2a_task_agent_id_a2a_agent_id_fk", + "tableFrom": "a2a_task", + "tableTo": "a2a_agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "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": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "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": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "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()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "identifier": { + "name": "identifier", + "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": "'[]'" + }, + "archived_at": { + "name": "archived_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": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "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_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_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": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_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": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "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'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "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_user_workspace_idx": { + "name": "copilot_chats_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": {} + }, + "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": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "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" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "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": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "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": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_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()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "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": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "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": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set": { + "name": "credential_set", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_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 + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "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": { + "credential_set_created_by_idx": { + "name": "credential_set_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_org_name_unique": { + "name": "credential_set_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_provider_id_idx": { + "name": "credential_set_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_organization_id_organization_id_fk": { + "name": "credential_set_organization_id_organization_id_fk", + "tableFrom": "credential_set", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_created_by_user_id_fk": { + "name": "credential_set_created_by_user_id_fk", + "tableFrom": "credential_set", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_invitation": { + "name": "credential_set_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "accepted_by_user_id": { + "name": "accepted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_invitation_set_id_idx": { + "name": "credential_set_invitation_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_token_idx": { + "name": "credential_set_invitation_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_status_idx": { + "name": "credential_set_invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_expires_at_idx": { + "name": "credential_set_invitation_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_invitation_credential_set_id_credential_set_id_fk": { + "name": "credential_set_invitation_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_invited_by_user_id_fk": { + "name": "credential_set_invitation_invited_by_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_accepted_by_user_id_user_id_fk": { + "name": "credential_set_invitation_accepted_by_user_id_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["accepted_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_set_invitation_token_unique": { + "name": "credential_set_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_member": { + "name": "credential_set_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "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": { + "credential_set_member_user_id_idx": { + "name": "credential_set_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_unique": { + "name": "credential_set_member_unique", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_status_idx": { + "name": "credential_set_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_member_credential_set_id_credential_set_id_fk": { + "name": "credential_set_member_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_user_id_user_id_fk": { + "name": "credential_set_member_user_id_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_invited_by_user_id_fk": { + "name": "credential_set_member_invited_by_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "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 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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 + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "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_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_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "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": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "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" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "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_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "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.form": { + "name": "form", + "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 + }, + "identifier": { + "name": "identifier", + "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": "'[]'" + }, + "show_branding": { + "name": "show_branding", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_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": { + "form_identifier_idx": { + "name": "form_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"form\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_workflow_id_idx": { + "name": "form_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_user_id_idx": { + "name": "form_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_archived_at_partial_idx": { + "name": "form_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"form\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_workflow_id_workflow_id_fk": { + "name": "form_workflow_id_workflow_id_fk", + "tableFrom": "form", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "form_user_id_user_id_fk": { + "name": "form_user_id_user_id_fk", + "tableFrom": "form", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "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": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "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": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "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 + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "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\": 1, \"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": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "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": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "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_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "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": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "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()" + }, + "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_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "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 + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "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": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "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 + } + }, + "indexes": { + "oauth_access_token_access_token_idx": { + "name": "oauth_access_token_access_token_idx", + "columns": [ + { + "expression": "access_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_token_idx": { + "name": "oauth_access_token_refresh_token_idx", + "columns": [ + { + "expression": "refresh_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_application_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_access_token_unique": { + "name": "oauth_access_token_access_token_unique", + "nullsNotDistinct": false, + "columns": ["access_token"] + }, + "oauth_access_token_refresh_token_unique": { + "name": "oauth_access_token_refresh_token_unique", + "nullsNotDistinct": false, + "columns": ["refresh_token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_application": { + "name": "oauth_application", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_urls": { + "name": "redirect_urls", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "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": { + "oauth_application_client_id_idx": { + "name": "oauth_application_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_application_user_id_user_id_fk": { + "name": "oauth_application_user_id_user_id_fk", + "tableFrom": "oauth_application", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_application_client_id_unique": { + "name": "oauth_application_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "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 + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_user_client_idx": { + "name": "oauth_consent_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_application_client_id_fk": { + "name": "oauth_consent_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_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 + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "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 + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_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 + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "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": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_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 + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "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()" + }, + "auto_add_new_members": { + "name": "auto_add_new_members", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_name_unique": { + "name": "permission_group_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_auto_add_unique": { + "name": "permission_group_workspace_auto_add_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "auto_add_new_members = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_id_workspace_id_fk", + "tableFrom": "permission_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_workspace_user_unique": { + "name": "permission_group_member_workspace_user_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_workspace_id_workspace_id_fk": { + "name": "permission_group_member_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_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 + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "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": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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" + } + }, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_creators": { + "name": "template_creators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "template_creator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_image_url": { + "name": "profile_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "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": { + "template_creators_reference_idx": { + "name": "template_creators_reference_idx", + "columns": [ + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_reference_id_idx": { + "name": "template_creators_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_created_by_idx": { + "name": "template_creators_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_creators_created_by_user_id_fk": { + "name": "template_creators_created_by_user_id_fk", + "tableFrom": "template_creators", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "template_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "required_credentials": { + "name": "required_credentials", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "og_image_url": { + "name": "og_image_url", + "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": { + "templates_status_idx": { + "name": "templates_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_creator_id_idx": { + "name": "templates_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "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_status_views_idx": { + "name": "templates_status_views_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_stars_idx": { + "name": "templates_status_stars_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "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": "set null", + "onUpdate": "no action" + }, + "templates_creator_id_template_creators_id_fk": { + "name": "templates_creator_id_template_creators_id_fk", + "tableFrom": "templates", + "tableTo": "template_creators", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "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_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_a2a_executions": { + "name": "total_a2a_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "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": false, + "default": "'5'" + }, + "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'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + } + }, + "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.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_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 + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "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()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_table_id_idx": { + "name": "user_table_rows_table_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_data_gin_idx": { + "name": "user_table_rows_data_gin_idx", + "columns": [ + { + "expression": "data", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id": { + "name": "idx_webhook_on_workflow_id_block_id", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_credential_set_id_idx": { + "name": "webhook_credential_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "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_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_credential_set_id_credential_set_id_fk": { + "name": "webhook_credential_set_id_credential_set_id_fk", + "tableFrom": "webhook", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_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": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "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": "'{}'" + }, + "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_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "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_checkpoints": { + "name": "workflow_checkpoints", + "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 + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "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()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_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": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "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_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_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": false + }, + "workspace_id": { + "name": "workspace_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 + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "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 + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_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_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": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "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": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_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" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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": false + }, + "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": "set null", + "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 + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "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": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "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_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_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_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "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": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "last_queued_at": { + "name": "last_queued_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 + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "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_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "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": { + "workspace_byok_provider_unique": { + "name": "workspace_byok_provider_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_byok_workspace_idx": { + "name": "workspace_byok_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "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": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_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 + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "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 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_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": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_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": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "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": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_delivery": { + "name": "workspace_notification_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "notification_delivery_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "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": { + "workspace_notification_delivery_subscription_id_idx": { + "name": "workspace_notification_delivery_subscription_id_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_execution_id_idx": { + "name": "workspace_notification_delivery_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_status_idx": { + "name": "workspace_notification_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_next_attempt_idx": { + "name": "workspace_notification_delivery_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk": { + "name": "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workspace_notification_subscription", + "columnsFrom": ["subscription_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_delivery_workflow_id_workflow_id_fk": { + "name": "workspace_notification_delivery_workflow_id_workflow_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_subscription": { + "name": "workspace_notification_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_ids": { + "name": "workflow_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "all_workflows": { + "name": "all_workflows", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "level_filter": { + "name": "level_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['info', 'error']::text[]" + }, + "trigger_filter": { + "name": "trigger_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['api', 'webhook', 'schedule', 'manual', 'chat']::text[]" + }, + "include_final_output": { + "name": "include_final_output", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_trace_spans": { + "name": "include_trace_spans", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_rate_limits": { + "name": "include_rate_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_usage_data": { + "name": "include_usage_data", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "webhook_config": { + "name": "webhook_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email_recipients": { + "name": "email_recipients", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "slack_config": { + "name": "slack_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "alert_config": { + "name": "alert_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_alert_at": { + "name": "last_alert_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "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": { + "workspace_notification_workspace_id_idx": { + "name": "workspace_notification_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_active_idx": { + "name": "workspace_notification_active_idx", + "columns": [ + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_type_idx": { + "name": "workspace_notification_type_idx", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_subscription_workspace_id_workspace_id_fk": { + "name": "workspace_notification_subscription_workspace_id_workspace_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_subscription_created_by_user_id_fk": { + "name": "workspace_notification_subscription_created_by_user_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.a2a_task_status": { + "name": "a2a_task_status", + "schema": "public", + "values": [ + "submitted", + "working", + "input-required", + "completed", + "failed", + "canceled", + "rejected", + "auth-required", + "unknown" + ] + }, + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_set_invitation_status": { + "name": "credential_set_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "expired", "cancelled"] + }, + "public.credential_set_member_status": { + "name": "credential_set_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": ["oauth", "env_workspace", "env_personal", "service_account"] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.notification_delivery_status": { + "name": "notification_delivery_status", + "schema": "public", + "values": ["pending", "in_progress", "success", "failed"] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": ["webhook", "email", "slack"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.template_creator_type": { + "name": "template_creator_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.template_status": { + "name": "template_status", + "schema": "public", + "values": ["pending", "approved", "rejected"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 36023183249..1dd9764d85e 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1457,6 +1457,13 @@ "when": 1778714378630, "tag": "0208_modern_power_man", "breakpoints": true + }, + { + "idx": 209, + "version": "7", + "when": 1779246572978, + "tag": "0209_smiling_fixer", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index ddeab5dd6cf..fe2913d3c89 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -13,6 +13,7 @@ import { jsonb, pgEnum, pgTable, + primaryKey, text, timestamp, uniqueIndex, @@ -2989,13 +2990,6 @@ export const userTableRows = pgTable( .notNull() .references(() => workspace.id, { onDelete: 'cascade' }), data: jsonb('data').notNull(), - /** - * Per-row workflow-group execution state, keyed by `WorkflowGroup.id`. - * Each entry holds status / executionId / jobId / blockErrors / - * runningBlockIds for one group's run on this row. Empty `{}` means no - * group has run for this row yet. - */ - executions: jsonb('executions').notNull().default({}), position: integer('position').notNull().default(0), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), @@ -3012,6 +3006,91 @@ export const userTableRows = pgTable( }) ) +/** + * Per-row workflow-group execution state. One row per (rowId, groupId) — the + * group's run metadata (status, executionId, jobId, blockErrors, etc.) for + * one row of one user-defined table. + * + * Lives in a sidecar table (not a JSONB column on `user_table_rows`) so the + * dispatcher and "X running" counter can hit `(table_id, status)` and + * `(table_id, group_id)` indexes directly instead of walking JSONB blobs, and + * so each cell-write rewrites only its own row instead of the whole + * executions object on the parent row tuple. + */ +export const tableRowExecutions = pgTable( + 'table_row_executions', + { + tableId: text('table_id') + .notNull() + .references(() => userTableDefinitions.id, { onDelete: 'cascade' }), + rowId: text('row_id') + .notNull() + .references(() => userTableRows.id, { onDelete: 'cascade' }), + groupId: text('group_id').notNull(), + status: text('status').notNull(), + executionId: text('execution_id'), + jobId: text('job_id'), + workflowId: text('workflow_id').notNull(), + error: text('error'), + runningBlockIds: text('running_block_ids').array().notNull().default(sql`'{}'::text[]`), + blockErrors: jsonb('block_errors').notNull().default({}), + cancelledAt: timestamp('cancelled_at'), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => ({ + pk: primaryKey({ columns: [table.rowId, table.groupId] }), + tableStatusInFlightIdx: index('table_row_executions_table_status_idx') + .on(table.tableId, table.status) + .where(sql`${table.status} IN ('queued', 'running', 'pending')`), + executionIdIdx: index('table_row_executions_execution_id_idx') + .on(table.executionId) + .where(sql`${table.executionId} IS NOT NULL`), + tableGroupIdx: index('table_row_executions_table_group_idx').on(table.tableId, table.groupId), + }) +) + +/** + * One row per "Run column / Run row / Run all rows" gesture on a user table. + * The dispatcher task walks the table in row-position windows, advancing + * `cursor` as it enqueues cells into trigger.dev. Cancel flips `status` to + * `'cancelled'` in one write; the dispatcher bails at the next iteration and + * a bulk-SQL cell-cancel sweep neuters anything still in trigger.dev's queue + * (workers no-op on pickup via the cancel-sticky guard). + */ +export const tableRunDispatches = pgTable( + 'table_run_dispatches', + { + id: text('id').primaryKey(), + tableId: text('table_id') + .notNull() + .references(() => userTableDefinitions.id, { onDelete: 'cascade' }), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + requestId: text('request_id').notNull(), + /** `'all'` re-runs completed cells; `'incomplete'` skips them. */ + mode: text('mode').notNull(), + /** `{ groupIds: string[], rowIds?: string[] }` — the run's scope. */ + scope: jsonb('scope').notNull(), + /** `pending` → `dispatching` → `complete` | `cancelled`. */ + status: text('status').notNull().default('pending'), + /** Highest `user_table_rows.position` we've already enqueued cells for. */ + cursor: integer('cursor').notNull().default(0), + /** When true, eligibility bypasses `autoRun: false` skip and treats + * terminal states as re-runnable. Auto-fire paths (row inserts, + * CSV import, addWorkflowGroup) set this to false so the dispatch + * honors the autoRun toggle. */ + isManualRun: boolean('is_manual_run').notNull().default(true), + requestedAt: timestamp('requested_at').notNull().defaultNow(), + completedAt: timestamp('completed_at'), + cancelledAt: timestamp('cancelled_at'), + }, + (table) => ({ + activeIdx: index('table_run_dispatches_active_idx').on(table.tableId, table.status), + watchdogIdx: index('table_run_dispatches_watchdog_idx').on(table.status, table.requestedAt), + }) +) + export const oauthApplication = pgTable( 'oauth_application', { diff --git a/packages/testing/src/mocks/database.mock.ts b/packages/testing/src/mocks/database.mock.ts index b73eb3566e7..98dda999745 100644 --- a/packages/testing/src/mocks/database.mock.ts +++ b/packages/testing/src/mocks/database.mock.ts @@ -115,7 +115,18 @@ const forClause = vi.fn(forBuilder) const onConflictDoUpdate = vi.fn(() => ({ returning }) as unknown as Promise) const onConflictDoNothing = vi.fn(() => ({ returning }) as unknown as Promise) -const whereBuilder = () => ({ limit, orderBy, returning, groupBy, for: forClause }) +const whereBuilder = () => { + // Some call sites (e.g. `db.select().from(t).where(eq(...))` with no + // limit/orderBy) await the where directly. Make the builder a thenable so + // those calls resolve to the default empty array. + const thenable: any = Promise.resolve([] as unknown[]) + thenable.limit = limit + thenable.orderBy = orderBy + thenable.returning = returning + thenable.groupBy = groupBy + thenable.for = forClause + return thenable +} const where = vi.fn(whereBuilder) const joinBuilder = (): { where: typeof where; innerJoin: any; leftJoin: any } => ({ diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index ba91d9d7746..66e14fabe0c 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1141,6 +1141,34 @@ export const schemaMock = { updatedAt: 'updatedAt', createdBy: 'createdBy', }, + tableRowExecutions: { + tableId: 'tableId', + rowId: 'rowId', + groupId: 'groupId', + status: 'status', + executionId: 'executionId', + jobId: 'jobId', + workflowId: 'workflowId', + error: 'error', + runningBlockIds: 'runningBlockIds', + blockErrors: 'blockErrors', + cancelledAt: 'cancelledAt', + updatedAt: 'updatedAt', + }, + tableRunDispatches: { + id: 'id', + tableId: 'tableId', + workspaceId: 'workspaceId', + requestId: 'requestId', + mode: 'mode', + scope: 'scope', + status: 'status', + cursor: 'cursor', + isManualRun: 'isManualRun', + requestedAt: 'requestedAt', + completedAt: 'completedAt', + cancelledAt: 'cancelledAt', + }, oauthApplication: { id: 'id', name: 'name', diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 8bd93e9545a..3f8b4f77c87 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 746, - zodRoutes: 746, + totalRoutes: 747, + zodRoutes: 747, nonZodRoutes: 0, } as const