diff --git a/apps/docs/content/docs/en/quick-reference/index.mdx b/apps/docs/content/docs/en/quick-reference/index.mdx index 2b1439a4c..1831918b8 100644 --- a/apps/docs/content/docs/en/quick-reference/index.mdx +++ b/apps/docs/content/docs/en/quick-reference/index.mdx @@ -180,6 +180,11 @@ A quick lookup for everyday actions in the Sim workflow editor. For keyboard sho Right-click → **Enable/Disable** + + Lock/Unlock a block + Hover block → Click lock icon (Admin only) + + Toggle handle orientation Right-click → **Toggle Handles** diff --git a/apps/docs/content/docs/en/tools/pulse.mdx b/apps/docs/content/docs/en/tools/pulse.mdx index 92d2319e0..a804d9952 100644 --- a/apps/docs/content/docs/en/tools/pulse.mdx +++ b/apps/docs/content/docs/en/tools/pulse.mdx @@ -11,7 +11,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" /> {/* MANUAL-CONTENT-START:intro */} -The [Pulse](https://www.pulseapi.com/) tool enables seamless extraction of text and structured content from a wide variety of documents—including PDFs, images, and Office files—using state-of-the-art OCR (Optical Character Recognition) powered by Pulse. Designed for automated agentic workflows, Pulse Parser makes it easy to unlock valuable information trapped in unstructured documents and integrate the extracted content directly into your workflow. +The [Pulse](https://www.runpulse.com) tool enables seamless extraction of text and structured content from a wide variety of documents—including PDFs, images, and Office files—using state-of-the-art OCR (Optical Character Recognition) powered by Pulse. Designed for automated agentic workflows, Pulse Parser makes it easy to unlock valuable information trapped in unstructured documents and integrate the extracted content directly into your workflow. With Pulse, you can: diff --git a/apps/docs/public/static/quick-reference/lock-block.png b/apps/docs/public/static/quick-reference/lock-block.png new file mode 100644 index 000000000..67a50e04e Binary files /dev/null and b/apps/docs/public/static/quick-reference/lock-block.png differ diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx index 3e0d78180..1678b8a41 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx @@ -1,5 +1,5 @@ import { memo, useCallback } from 'react' -import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react' +import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, Lock, LogOut, Unlock } from 'lucide-react' import { Button, Copy, PlayOutline, Tooltip, Trash2 } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' @@ -49,6 +49,7 @@ export const ActionBar = memo( collaborativeBatchRemoveBlocks, collaborativeBatchToggleBlockEnabled, collaborativeBatchToggleBlockHandles, + collaborativeBatchToggleLocked, } = useCollaborativeWorkflow() const { setPendingSelection } = useWorkflowRegistry() const { handleRunFromBlock } = useWorkflowExecution() @@ -84,16 +85,28 @@ export const ActionBar = memo( ) }, [blockId, addNotification, collaborativeBatchAddBlocks, setPendingSelection]) - const { isEnabled, horizontalHandles, parentId, parentType } = useWorkflowStore( + const { + isEnabled, + horizontalHandles, + parentId, + parentType, + isLocked, + isParentLocked, + isParentDisabled, + } = useWorkflowStore( useCallback( (state) => { const block = state.blocks[blockId] const parentId = block?.data?.parentId + const parentBlock = parentId ? state.blocks[parentId] : undefined return { isEnabled: block?.enabled ?? true, horizontalHandles: block?.horizontalHandles ?? false, parentId, - parentType: parentId ? state.blocks[parentId]?.type : undefined, + parentType: parentBlock?.type, + isLocked: block?.locked ?? false, + isParentLocked: parentBlock?.locked ?? false, + isParentDisabled: parentBlock ? !parentBlock.enabled : false, } }, [blockId] @@ -161,25 +174,27 @@ export const ActionBar = memo( {!isNoteBlock && !isInsideSubflow && ( - + + + {(() => { if (disabled) return getTooltipMessage('Run from block') if (isExecuting) return 'Execution in progress' - if (!dependenciesSatisfied) return 'Run upstream blocks first' + if (!dependenciesSatisfied) return 'Run previous blocks first' return 'Run from block' })()} @@ -193,18 +208,54 @@ export const ActionBar = memo( variant='ghost' onClick={(e) => { e.stopPropagation() - if (!disabled) { + // Can't enable if parent is disabled (must enable parent first) + const cantEnable = !isEnabled && isParentDisabled + if (!disabled && !isLocked && !isParentLocked && !cantEnable) { collaborativeBatchToggleBlockEnabled([blockId]) } }} className={ACTION_BUTTON_STYLES} - disabled={disabled} + disabled={ + disabled || isLocked || isParentLocked || (!isEnabled && isParentDisabled) + } > {isEnabled ? : } - {getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')} + {isLocked || isParentLocked + ? 'Block is locked' + : !isEnabled && isParentDisabled + ? 'Parent container is disabled' + : getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')} + + + )} + + {userPermissions.canAdmin && ( + + + + + + {isLocked && isParentLocked + ? 'Parent container is locked' + : isLocked + ? 'Unlock Block' + : 'Lock Block'} )} @@ -216,17 +267,21 @@ export const ActionBar = memo( variant='ghost' onClick={(e) => { e.stopPropagation() - if (!disabled) { + if (!disabled && !isLocked && !isParentLocked) { handleDuplicateBlock() } }} className={ACTION_BUTTON_STYLES} - disabled={disabled} + disabled={disabled || isLocked || isParentLocked} > - {getTooltipMessage('Duplicate Block')} + + {isLocked || isParentLocked + ? 'Block is locked' + : getTooltipMessage('Duplicate Block')} + )} @@ -237,12 +292,12 @@ export const ActionBar = memo( variant='ghost' onClick={(e) => { e.stopPropagation() - if (!disabled) { + if (!disabled && !isLocked && !isParentLocked) { collaborativeBatchToggleBlockHandles([blockId]) } }} className={ACTION_BUTTON_STYLES} - disabled={disabled} + disabled={disabled || isLocked || isParentLocked} > {horizontalHandles ? ( @@ -252,7 +307,9 @@ export const ActionBar = memo( - {getTooltipMessage(horizontalHandles ? 'Vertical Ports' : 'Horizontal Ports')} + {isLocked || isParentLocked + ? 'Block is locked' + : getTooltipMessage(horizontalHandles ? 'Vertical Ports' : 'Horizontal Ports')} )} @@ -264,19 +321,23 @@ export const ActionBar = memo( variant='ghost' onClick={(e) => { e.stopPropagation() - if (!disabled && userPermissions.canEdit) { + if (!disabled && userPermissions.canEdit && !isLocked && !isParentLocked) { window.dispatchEvent( new CustomEvent('remove-from-subflow', { detail: { blockIds: [blockId] } }) ) } }} className={ACTION_BUTTON_STYLES} - disabled={disabled || !userPermissions.canEdit} + disabled={disabled || !userPermissions.canEdit || isLocked || isParentLocked} > - {getTooltipMessage('Remove from Subflow')} + + {isLocked || isParentLocked + ? 'Block is locked' + : getTooltipMessage('Remove from Subflow')} + )} @@ -286,17 +347,19 @@ export const ActionBar = memo( variant='ghost' onClick={(e) => { e.stopPropagation() - if (!disabled) { + if (!disabled && !isLocked && !isParentLocked) { collaborativeBatchRemoveBlocks([blockId]) } }} className={ACTION_BUTTON_STYLES} - disabled={disabled} + disabled={disabled || isLocked || isParentLocked} > - {getTooltipMessage('Delete Block')} + + {isLocked || isParentLocked ? 'Block is locked' : getTooltipMessage('Delete Block')} + ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx index e02bf4ff5..79e8464bf 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx @@ -20,6 +20,9 @@ export interface BlockInfo { horizontalHandles: boolean parentId?: string parentType?: string + locked?: boolean + isParentLocked?: boolean + isParentDisabled?: boolean } /** @@ -46,10 +49,17 @@ export interface BlockMenuProps { showRemoveFromSubflow?: boolean /** Whether run from block is available (has snapshot, was executed, not inside subflow) */ canRunFromBlock?: boolean + /** Whether to disable edit actions (user can't edit OR blocks are locked) */ disableEdit?: boolean + /** Whether the user has edit permission (ignoring locked state) */ + userCanEdit?: boolean isExecuting?: boolean /** Whether the selected block is a trigger (has no incoming edges) */ isPositionalTrigger?: boolean + /** Callback to toggle locked state of selected blocks */ + onToggleLocked?: () => void + /** Whether the user has admin permissions */ + canAdmin?: boolean } /** @@ -78,13 +88,22 @@ export function BlockMenu({ showRemoveFromSubflow = false, canRunFromBlock = false, disableEdit = false, + userCanEdit = true, isExecuting = false, isPositionalTrigger = false, + onToggleLocked, + canAdmin = false, }: BlockMenuProps) { const isSingleBlock = selectedBlocks.length === 1 const allEnabled = selectedBlocks.every((b) => b.enabled) const allDisabled = selectedBlocks.every((b) => !b.enabled) + const allLocked = selectedBlocks.every((b) => b.locked) + const allUnlocked = selectedBlocks.every((b) => !b.locked) + // Can't unlock blocks that have locked parents + const hasBlockWithLockedParent = selectedBlocks.some((b) => b.locked && b.isParentLocked) + // Can't enable blocks that have disabled parents + const hasBlockWithDisabledParent = selectedBlocks.some((b) => !b.enabled && b.isParentDisabled) const hasSingletonBlock = selectedBlocks.some( (b) => @@ -108,6 +127,12 @@ export function BlockMenu({ return 'Toggle Enabled' } + const getToggleLockedLabel = () => { + if (allLocked) return 'Unlock' + if (allUnlocked) return 'Lock' + return 'Toggle Lock' + } + return ( { onPaste() onClose() @@ -164,13 +189,15 @@ export function BlockMenu({ {!allNoteBlocks && } {!allNoteBlocks && ( { - onToggleEnabled() - onClose() + if (!disableEdit && !hasBlockWithDisabledParent) { + onToggleEnabled() + onClose() + } }} > - {getToggleEnabledLabel()} + {hasBlockWithDisabledParent ? 'Parent is disabled' : getToggleEnabledLabel()} )} {!allNoteBlocks && !isSubflow && ( @@ -195,6 +222,19 @@ export function BlockMenu({ Remove from Subflow )} + {canAdmin && onToggleLocked && ( + { + if (!hasBlockWithLockedParent) { + onToggleLocked() + onClose() + } + }} + > + {hasBlockWithLockedParent ? 'Parent is locked' : getToggleLockedLabel()} + + )} {/* Single block actions */} {isSingleBlock && } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/canvas-menu/canvas-menu.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/canvas-menu/canvas-menu.tsx index d7c3e1a5d..e091849c8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/canvas-menu/canvas-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/canvas-menu/canvas-menu.tsx @@ -34,6 +34,8 @@ export interface CanvasMenuProps { canUndo?: boolean canRedo?: boolean isInvitationsDisabled?: boolean + /** Whether the workflow has locked blocks (disables auto-layout) */ + hasLockedBlocks?: boolean } /** @@ -60,6 +62,7 @@ export function CanvasMenu({ disableEdit = false, canUndo = false, canRedo = false, + hasLockedBlocks = false, }: CanvasMenuProps) { return ( { onAutoLayout() onClose() }} + title={hasLockedBlocks ? 'Unlock blocks to use auto-layout' : undefined} > Auto-layout ⇧L diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx index 07c8a9ce7..33bce6bb6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx @@ -9,7 +9,9 @@ import { ChevronUp, ExternalLink, Loader2, + Lock, Pencil, + Unlock, } from 'lucide-react' import { useParams } from 'next/navigation' import { useShallow } from 'zustand/react/shallow' @@ -46,6 +48,7 @@ import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' import { usePanelEditorStore } from '@/stores/panel' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' +import { useWorkflowStore } from '@/stores/workflows/workflow/store' /** Stable empty object to avoid creating new references */ const EMPTY_SUBBLOCK_VALUES = {} as Record @@ -110,6 +113,14 @@ export function Editor() { const userPermissions = useUserPermissionsContext() + // Check if block is locked (or inside a locked container) and compute edit permission + // Locked blocks cannot be edited by anyone (admins can only lock/unlock) + const blocks = useWorkflowStore((state) => state.blocks) + const parentId = currentBlock?.data?.parentId as string | undefined + const isParentLocked = parentId ? (blocks[parentId]?.locked ?? false) : false + const isLocked = (currentBlock?.locked ?? false) || isParentLocked + const canEditBlock = userPermissions.canEdit && !isLocked + const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId) const { advancedMode, triggerMode } = useEditorBlockProperties( @@ -147,9 +158,7 @@ export function Editor() { () => hasAdvancedValues(subBlocksForCanonical, blockSubBlockValues, canonicalIndex), [subBlocksForCanonical, blockSubBlockValues, canonicalIndex] ) - const displayAdvancedOptions = userPermissions.canEdit - ? advancedMode - : advancedMode || advancedValuesPresent + const displayAdvancedOptions = canEditBlock ? advancedMode : advancedMode || advancedValuesPresent const hasAdvancedOnlyFields = useMemo(() => { for (const subBlock of subBlocksForCanonical) { @@ -210,12 +219,13 @@ export function Editor() { collaborativeSetBlockCanonicalMode, collaborativeUpdateBlockName, collaborativeToggleBlockAdvancedMode, + collaborativeBatchToggleLocked, } = useCollaborativeWorkflow() const handleToggleAdvancedMode = useCallback(() => { - if (!currentBlockId || !userPermissions.canEdit) return + if (!currentBlockId || !canEditBlock) return collaborativeToggleBlockAdvancedMode(currentBlockId) - }, [currentBlockId, userPermissions.canEdit, collaborativeToggleBlockAdvancedMode]) + }, [currentBlockId, canEditBlock, collaborativeToggleBlockAdvancedMode]) const [isRenaming, setIsRenaming] = useState(false) const [editedName, setEditedName] = useState('') @@ -233,10 +243,10 @@ export function Editor() { * Handles starting the rename process. */ const handleStartRename = useCallback(() => { - if (!userPermissions.canEdit || !currentBlock) return + if (!canEditBlock || !currentBlock) return setEditedName(currentBlock.name || '') setIsRenaming(true) - }, [userPermissions.canEdit, currentBlock]) + }, [canEditBlock, currentBlock]) /** * Handles saving the renamed block. @@ -341,6 +351,36 @@ export function Editor() { )}
+ {/* Locked indicator - clickable to unlock if user has admin permissions, block is locked, and parent is not locked */} + {isLocked && currentBlock && ( + + + {userPermissions.canAdmin && currentBlock.locked && !isParentLocked ? ( + + ) : ( +
+ +
+ )} +
+ +

+ {isParentLocked + ? 'Parent container is locked' + : userPermissions.canAdmin && currentBlock.locked + ? 'Unlock block' + : 'Block is locked'} +

+
+
+ )} {/* Rename button */} {currentBlock && ( @@ -349,7 +389,7 @@ export function Editor() { variant='ghost' className='p-0' onClick={isRenaming ? handleSaveRename : handleStartRename} - disabled={!userPermissions.canEdit} + disabled={!canEditBlock} aria-label={isRenaming ? 'Save name' : 'Rename block'} > {isRenaming ? ( @@ -415,7 +455,7 @@ export function Editor() { incomingConnections={incomingConnections} handleConnectionsResizeMouseDown={handleConnectionsResizeMouseDown} toggleConnectionsCollapsed={toggleConnectionsCollapsed} - userCanEdit={userPermissions.canEdit} + userCanEdit={canEditBlock} isConnectionsAtMinHeight={isConnectionsAtMinHeight} /> ) : ( @@ -517,14 +557,14 @@ export function Editor() { config={subBlock} isPreview={false} subBlockValues={subBlockState} - disabled={!userPermissions.canEdit} + disabled={!canEditBlock} fieldDiffStatus={undefined} allowExpandInPreview={false} canonicalToggle={ isCanonicalSwap && canonicalMode && canonicalId ? { mode: canonicalMode, - disabled: !userPermissions.canEdit, + disabled: !canEditBlock, onToggle: () => { if (!currentBlockId) return const nextMode = @@ -548,7 +588,7 @@ export function Editor() { ) })} - {hasAdvancedOnlyFields && userPermissions.canEdit && ( + {hasAdvancedOnlyFields && canEditBlock && (
- {!isEnabled && disabled} +
+ {!isEnabled && disabled} + {isLocked && locked} +
{!isPreview && ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-block-state.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-block-state.ts index f14d4080c..658e0095e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-block-state.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-block-state.ts @@ -18,6 +18,8 @@ export interface UseBlockStateReturn { diffStatus: DiffStatus /** Whether this is a deleted block in diff mode */ isDeletedBlock: boolean + /** Whether the block is locked */ + isLocked: boolean } /** @@ -40,6 +42,11 @@ export function useBlockState( ? (data.blockState?.enabled ?? true) : (currentBlock?.enabled ?? true) + // Determine if block is locked + const isLocked = data.isPreview + ? (data.blockState?.locked ?? false) + : (currentBlock?.locked ?? false) + // Get diff status const diffStatus: DiffStatus = currentWorkflow.isDiffMode && currentBlock && hasDiffStatus(currentBlock) @@ -68,5 +75,6 @@ export function useBlockState( isActive, diffStatus, isDeletedBlock: isDeletedBlock ?? false, + isLocked, } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index b3ef43244..636fd559d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -672,6 +672,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ currentWorkflow, activeWorkflowId, isEnabled, + isLocked, handleClick, hasRing, ringStyles, @@ -1100,7 +1101,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ {name}
-
+
{isWorkflowSelector && childWorkflowId && typeof childIsDeployed === 'boolean' && @@ -1133,6 +1134,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ )} {!isEnabled && disabled} + {isLocked && locked} {type === 'schedule' && shouldShowScheduleBadge && scheduleInfo?.isDisabled && ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts index cd99dc9a8..e8982bb8d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-visual.ts @@ -47,6 +47,7 @@ export function useBlockVisual({ isActive: isExecuting, diffStatus, isDeletedBlock, + isLocked, } = useBlockState(blockId, currentWorkflow, data) const currentBlockId = usePanelEditorStore((state) => state.currentBlockId) @@ -103,6 +104,7 @@ export function useBlockVisual({ currentWorkflow, activeWorkflowId, isEnabled, + isLocked, handleClick, hasRing, ringStyles, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts index 9ecfe51f2..13a9968e7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts @@ -31,7 +31,8 @@ export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasCo nodes.map((n) => { const block = blocks[n.id] const parentId = block?.data?.parentId - const parentType = parentId ? blocks[parentId]?.type : undefined + const parentBlock = parentId ? blocks[parentId] : undefined + const parentType = parentBlock?.type return { id: n.id, type: block?.type || '', @@ -39,6 +40,9 @@ export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasCo horizontalHandles: block?.horizontalHandles ?? false, parentId, parentType, + locked: block?.locked ?? false, + isParentLocked: parentBlock?.locked ?? false, + isParentDisabled: parentBlock ? !parentBlock.enabled : false, } }), [blocks] diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts index 2be615c8d..5f494b29d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts @@ -52,6 +52,16 @@ export async function applyAutoLayoutAndUpdateStore( return { success: false, error: 'No blocks to layout' } } + // Check for locked blocks - auto-layout is disabled when blocks are locked + const hasLockedBlocks = Object.values(blocks).some((block) => block.locked) + if (hasLockedBlocks) { + logger.info('Auto layout skipped: workflow contains locked blocks', { workflowId }) + return { + success: false, + error: 'Auto-layout is disabled when blocks are locked. Unlock blocks to use auto-layout.', + } + } + // Merge with default options const layoutOptions = { spacing: { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils.ts new file mode 100644 index 000000000..eb76077fc --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils.ts @@ -0,0 +1,72 @@ +import type { BlockState } from '@/stores/workflows/workflow/types' + +/** + * Result of filtering protected blocks from a deletion operation + */ +export interface FilterProtectedBlocksResult { + /** Block IDs that can be deleted (not protected) */ + deletableIds: string[] + /** Block IDs that are protected and cannot be deleted */ + protectedIds: string[] + /** Whether all blocks are protected (deletion should be cancelled entirely) */ + allProtected: boolean +} + +/** + * Checks if a block is protected from editing/deletion. + * A block is protected if it is locked or if its parent container is locked. + * + * @param blockId - The ID of the block to check + * @param blocks - Record of all blocks in the workflow + * @returns True if the block is protected + */ +export function isBlockProtected(blockId: string, blocks: Record): boolean { + const block = blocks[blockId] + if (!block) return false + + // Block is locked directly + if (block.locked) return true + + // Block is inside a locked container + const parentId = block.data?.parentId + if (parentId && blocks[parentId]?.locked) return true + + return false +} + +/** + * Checks if an edge is protected from modification. + * An edge is protected if either its source or target block is protected. + * + * @param edge - The edge to check (must have source and target) + * @param blocks - Record of all blocks in the workflow + * @returns True if the edge is protected + */ +export function isEdgeProtected( + edge: { source: string; target: string }, + blocks: Record +): boolean { + return isBlockProtected(edge.source, blocks) || isBlockProtected(edge.target, blocks) +} + +/** + * Filters out protected blocks from a list of block IDs for deletion. + * Protected blocks are those that are locked or inside a locked container. + * + * @param blockIds - Array of block IDs to filter + * @param blocks - Record of all blocks in the workflow + * @returns Result containing deletable IDs, protected IDs, and whether all are protected + */ +export function filterProtectedBlocks( + blockIds: string[], + blocks: Record +): FilterProtectedBlocksResult { + const protectedIds = blockIds.filter((id) => isBlockProtected(id, blocks)) + const deletableIds = blockIds.filter((id) => !protectedIds.includes(id)) + + return { + deletableIds, + protectedIds, + allProtected: protectedIds.length === blockIds.length && blockIds.length > 0, + } +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/index.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/index.ts index d2845af28..88772d16f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/index.ts @@ -1,4 +1,5 @@ export * from './auto-layout-utils' +export * from './block-protection-utils' export * from './block-ring-utils' export * from './node-position-utils' export * from './workflow-canvas-helpers' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 2e305431a..bf637c03f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -55,7 +55,10 @@ import { clearDragHighlights, computeClampedPositionUpdates, estimateBlockDimensions, + filterProtectedBlocks, getClampedPositionForNode, + isBlockProtected, + isEdgeProtected, isInEditableElement, resolveParentChildSelectionConflicts, validateTriggerPaste, @@ -543,6 +546,7 @@ const WorkflowContent = React.memo(() => { collaborativeBatchRemoveBlocks, collaborativeBatchToggleBlockEnabled, collaborativeBatchToggleBlockHandles, + collaborativeBatchToggleLocked, undo, redo, } = useCollaborativeWorkflow() @@ -1069,8 +1073,27 @@ const WorkflowContent = React.memo(() => { const handleContextDelete = useCallback(() => { const blockIds = contextMenuBlocks.map((b) => b.id) - collaborativeBatchRemoveBlocks(blockIds) - }, [contextMenuBlocks, collaborativeBatchRemoveBlocks]) + const { deletableIds, protectedIds, allProtected } = filterProtectedBlocks(blockIds, blocks) + + if (protectedIds.length > 0) { + if (allProtected) { + addNotification({ + level: 'info', + message: 'Cannot delete locked blocks or blocks inside locked containers', + workflowId: activeWorkflowId || undefined, + }) + return + } + addNotification({ + level: 'info', + message: `Skipped ${protectedIds.length} protected block(s)`, + workflowId: activeWorkflowId || undefined, + }) + } + if (deletableIds.length > 0) { + collaborativeBatchRemoveBlocks(deletableIds) + } + }, [contextMenuBlocks, collaborativeBatchRemoveBlocks, addNotification, activeWorkflowId, blocks]) const handleContextToggleEnabled = useCallback(() => { const blockIds = contextMenuBlocks.map((block) => block.id) @@ -1082,6 +1105,11 @@ const WorkflowContent = React.memo(() => { collaborativeBatchToggleBlockHandles(blockIds) }, [contextMenuBlocks, collaborativeBatchToggleBlockHandles]) + const handleContextToggleLocked = useCallback(() => { + const blockIds = contextMenuBlocks.map((block) => block.id) + collaborativeBatchToggleLocked(blockIds) + }, [contextMenuBlocks, collaborativeBatchToggleLocked]) + const handleContextRemoveFromSubflow = useCallback(() => { const blocksToRemove = contextMenuBlocks.filter( (block) => block.parentId && (block.parentType === 'loop' || block.parentType === 'parallel') @@ -1145,7 +1173,7 @@ const WorkflowContent = React.memo(() => { block.parentId && (block.parentType === 'loop' || block.parentType === 'parallel') if (isInsideSubflow) return { canRun: false, reason: 'Cannot run from inside subflow' } - if (!dependenciesSatisfied) return { canRun: false, reason: 'Run upstream blocks first' } + if (!dependenciesSatisfied) return { canRun: false, reason: 'Run previous blocks first' } if (isNoteBlock) return { canRun: false, reason: undefined } if (isExecuting) return { canRun: false, reason: undefined } @@ -1951,7 +1979,6 @@ const WorkflowContent = React.memo(() => { const loadingWorkflowRef = useRef(null) const currentWorkflowExists = Boolean(workflows[workflowIdParam]) - /** Initializes workflow when it exists in registry and needs hydration. */ useEffect(() => { const currentId = workflowIdParam const currentWorkspaceHydration = hydration.workspaceId @@ -2128,6 +2155,7 @@ const WorkflowContent = React.memo(() => { parentId: block.data?.parentId, extent: block.data?.extent || undefined, dragHandle: '.workflow-drag-handle', + draggable: !isBlockProtected(block.id, blocks), data: { ...block.data, name: block.name, @@ -2163,6 +2191,7 @@ const WorkflowContent = React.memo(() => { position, parentId: block.data?.parentId, dragHandle, + draggable: !isBlockProtected(block.id, blocks), extent: (() => { // Clamp children to subflow body (exclude header) const parentId = block.data?.parentId as string | undefined @@ -2491,12 +2520,18 @@ const WorkflowContent = React.memo(() => { const edgeIdsToRemove = changes .filter((change: any) => change.type === 'remove') .map((change: any) => change.id) + .filter((edgeId: string) => { + // Prevent removing edges connected to protected blocks + const edge = edges.find((e) => e.id === edgeId) + if (!edge) return true + return !isEdgeProtected(edge, blocks) + }) if (edgeIdsToRemove.length > 0) { collaborativeBatchRemoveEdges(edgeIdsToRemove) } }, - [collaborativeBatchRemoveEdges] + [collaborativeBatchRemoveEdges, edges, blocks] ) /** @@ -2558,6 +2593,16 @@ const WorkflowContent = React.memo(() => { if (!sourceNode || !targetNode) return + // Prevent connections to/from protected blocks + if (isEdgeProtected(connection, blocks)) { + addNotification({ + level: 'info', + message: 'Cannot connect to locked blocks or blocks inside locked containers', + workflowId: activeWorkflowId || undefined, + }) + return + } + // Get parent information (handle container start node case) const sourceParentId = blocks[sourceNode.id]?.data?.parentId || @@ -2620,7 +2665,7 @@ const WorkflowContent = React.memo(() => { connectionCompletedRef.current = true } }, - [addEdge, getNodes, blocks] + [addEdge, getNodes, blocks, addNotification, activeWorkflowId] ) /** @@ -2715,6 +2760,9 @@ const WorkflowContent = React.memo(() => { // Only consider container nodes that aren't the dragged node if (n.type !== 'subflowNode' || n.id === node.id) return false + // Don't allow dropping into locked containers + if (blocks[n.id]?.locked) return false + // Get the container's absolute position const containerAbsolutePos = getNodeAbsolutePosition(n.id) @@ -2807,6 +2855,8 @@ const WorkflowContent = React.memo(() => { /** Captures initial parent ID and position when drag starts. */ const onNodeDragStart = useCallback( (_event: React.MouseEvent, node: any) => { + // Note: Protected blocks are already non-draggable via the `draggable` node property + // Store the original parent ID when starting to drag const currentParentId = blocks[node.id]?.data?.parentId || null setDragStartParentId(currentParentId) @@ -2835,7 +2885,7 @@ const WorkflowContent = React.memo(() => { } }) }, - [blocks, setDragStartPosition, getNodes, potentialParentId, setPotentialParentId] + [blocks, setDragStartPosition, getNodes, setPotentialParentId] ) /** Handles node drag stop to establish parent-child relationships. */ @@ -2897,6 +2947,18 @@ const WorkflowContent = React.memo(() => { // Don't process parent changes if the node hasn't actually changed parent or is being moved within same parent if (potentialParentId === dragStartParentId) return + // Prevent moving locked blocks out of locked containers + // Unlocked blocks (e.g., duplicates) can be moved out freely + if (dragStartParentId && blocks[dragStartParentId]?.locked && blocks[node.id]?.locked) { + addNotification({ + level: 'info', + message: 'Cannot move locked blocks out of locked containers', + workflowId: activeWorkflowId || undefined, + }) + setPotentialParentId(dragStartParentId) // Reset to original parent + return + } + // Check if this is a starter block - starter blocks should never be in containers const isStarterBlock = node.data?.type === 'starter' if (isStarterBlock) { @@ -3293,6 +3355,16 @@ const WorkflowContent = React.memo(() => { /** Stable delete handler to avoid creating new function references per edge. */ const handleEdgeDelete = useCallback( (edgeId: string) => { + // Prevent removing edges connected to protected blocks + const edge = edges.find((e) => e.id === edgeId) + if (edge && isEdgeProtected(edge, blocks)) { + addNotification({ + level: 'info', + message: 'Cannot remove connections from locked blocks', + workflowId: activeWorkflowId || undefined, + }) + return + } removeEdge(edgeId) // Remove this edge from selection (find by edge ID value) setSelectedEdges((prev) => { @@ -3305,7 +3377,7 @@ const WorkflowContent = React.memo(() => { return next }) }, - [removeEdge] + [removeEdge, edges, blocks, addNotification, activeWorkflowId] ) /** Transforms edges to include selection state and delete handlers. Memoized to prevent re-renders. */ @@ -3346,9 +3418,15 @@ const WorkflowContent = React.memo(() => { // Handle edge deletion first (edges take priority if selected) if (selectedEdges.size > 0) { - // Get all selected edge IDs and batch delete them - const edgeIds = Array.from(selectedEdges.values()) - collaborativeBatchRemoveEdges(edgeIds) + // Get all selected edge IDs and filter out edges connected to protected blocks + const edgeIds = Array.from(selectedEdges.values()).filter((edgeId) => { + const edge = edges.find((e) => e.id === edgeId) + if (!edge) return true + return !isEdgeProtected(edge, blocks) + }) + if (edgeIds.length > 0) { + collaborativeBatchRemoveEdges(edgeIds) + } setSelectedEdges(new Map()) return } @@ -3365,7 +3443,29 @@ const WorkflowContent = React.memo(() => { event.preventDefault() const selectedIds = selectedNodes.map((node) => node.id) - collaborativeBatchRemoveBlocks(selectedIds) + const { deletableIds, protectedIds, allProtected } = filterProtectedBlocks( + selectedIds, + blocks + ) + + if (protectedIds.length > 0) { + if (allProtected) { + addNotification({ + level: 'info', + message: 'Cannot delete locked blocks or blocks inside locked containers', + workflowId: activeWorkflowId || undefined, + }) + return + } + addNotification({ + level: 'info', + message: `Skipped ${protectedIds.length} protected block(s)`, + workflowId: activeWorkflowId || undefined, + }) + } + if (deletableIds.length > 0) { + collaborativeBatchRemoveBlocks(deletableIds) + } } window.addEventListener('keydown', handleKeyDown) @@ -3376,6 +3476,10 @@ const WorkflowContent = React.memo(() => { getNodes, collaborativeBatchRemoveBlocks, effectivePermissions.canEdit, + blocks, + edges, + addNotification, + activeWorkflowId, ]) return ( @@ -3496,12 +3600,18 @@ const WorkflowContent = React.memo(() => { (b) => b.parentId && (b.parentType === 'loop' || b.parentType === 'parallel') )} canRunFromBlock={runFromBlockState.canRun} - disableEdit={!effectivePermissions.canEdit} + disableEdit={ + !effectivePermissions.canEdit || + contextMenuBlocks.some((b) => b.locked || b.isParentLocked) + } + userCanEdit={effectivePermissions.canEdit} isExecuting={isExecuting} isPositionalTrigger={ contextMenuBlocks.length === 1 && edges.filter((e) => e.target === contextMenuBlocks[0]?.id).length === 0 } + onToggleLocked={handleContextToggleLocked} + canAdmin={effectivePermissions.canAdmin} /> { disableEdit={!effectivePermissions.canEdit} canUndo={canUndo} canRedo={canRedo} + hasLockedBlocks={Object.values(blocks).some((b) => b.locked)} /> )} diff --git a/apps/sim/components/emcn/components/code/code.tsx b/apps/sim/components/emcn/components/code/code.tsx index 58250adc1..e0a40846b 100644 --- a/apps/sim/components/emcn/components/code/code.tsx +++ b/apps/sim/components/emcn/components/code/code.tsx @@ -458,8 +458,8 @@ export function getCodeEditorProps(options?: { 'caret-[var(--text-primary)] dark:caret-white', // Font smoothing '[-webkit-font-smoothing:antialiased] [-moz-osx-font-smoothing:grayscale]', - // Disable interaction for streaming/preview - (isStreaming || isPreview) && 'pointer-events-none' + // Disable interaction for streaming/preview/disabled + (isStreaming || isPreview || disabled) && 'pointer-events-none' ), } } diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index 4d0c4d143..1c780ccb0 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -212,11 +212,11 @@ export class WorkflowBlockHandler implements BlockHandler { /** * Parses a potentially nested workflow error message to extract: * - The chain of workflow names - * - The actual root error message (preserving the block prefix for the failing block) + * - The actual root error message (preserving the block name prefix for the failing block) * * Handles formats like: * - "workflow-name" failed: error - * - [block_type] Block Name: "workflow-name" failed: error + * - Block Name: "workflow-name" failed: error * - Workflow chain: A → B | error */ private parseNestedWorkflowError(message: string): { chain: string[]; rootError: string } { @@ -234,8 +234,8 @@ export class WorkflowBlockHandler implements BlockHandler { // Extract workflow names from patterns like: // - "workflow-name" failed: - // - [block_type] Block Name: "workflow-name" failed: - const workflowPattern = /(?:\[[^\]]+\]\s*[^:]+:\s*)?"([^"]+)"\s*failed:\s*/g + // - Block Name: "workflow-name" failed: + const workflowPattern = /(?:\[[^\]]+\]\s*)?(?:[^:]+:\s*)?"([^"]+)"\s*failed:\s*/g let match: RegExpExecArray | null let lastIndex = 0 @@ -247,7 +247,7 @@ export class WorkflowBlockHandler implements BlockHandler { } // The root error is everything after the last match - // Keep the block prefix (e.g., [function] Function 1:) so we know which block failed + // Keep the block name prefix (e.g., Function 1:) so we know which block failed const rootError = lastIndex > 0 ? remaining.slice(lastIndex) : remaining return { chain, rootError: rootError.trim() || 'Unknown error' } diff --git a/apps/sim/executor/utils/errors.ts b/apps/sim/executor/utils/errors.ts index f92c9c1ff..17137730a 100644 --- a/apps/sim/executor/utils/errors.ts +++ b/apps/sim/executor/utils/errors.ts @@ -47,7 +47,7 @@ export function buildBlockExecutionError(details: BlockExecutionErrorDetails): E const blockName = details.block.metadata?.name || details.block.id const blockType = details.block.metadata?.id || 'unknown' - const error = new Error(`[${blockType}] ${blockName}: ${errorMessage}`) + const error = new Error(`${blockName}: ${errorMessage}`) Object.assign(error, { blockId: details.block.id, diff --git a/apps/sim/hooks/use-collaborative-workflow.ts b/apps/sim/hooks/use-collaborative-workflow.ts index caf0aad9f..01268a575 100644 --- a/apps/sim/hooks/use-collaborative-workflow.ts +++ b/apps/sim/hooks/use-collaborative-workflow.ts @@ -409,6 +409,20 @@ export function useCollaborativeWorkflow() { logger.info('Successfully applied batch-toggle-handles from remote user') break } + case BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED: { + const { blockIds } = payload + logger.info('Received batch-toggle-locked from remote user', { + userId, + count: (blockIds || []).length, + }) + + if (blockIds && blockIds.length > 0) { + useWorkflowStore.getState().batchToggleLocked(blockIds) + } + + logger.info('Successfully applied batch-toggle-locked from remote user') + break + } case BLOCKS_OPERATIONS.BATCH_UPDATE_PARENT: { const { updates } = payload logger.info('Received batch-update-parent from remote user', { @@ -730,6 +744,23 @@ export function useCollaborativeWorkflow() { const collaborativeUpdateBlockName = useCallback( (id: string, name: string): { success: boolean; error?: string } => { + const blocks = useWorkflowStore.getState().blocks + const block = blocks[id] + + if (block) { + const parentId = block.data?.parentId + const isParentLocked = parentId ? blocks[parentId]?.locked : false + if (block.locked || isParentLocked) { + logger.error('Cannot rename locked block') + useNotificationStore.getState().addNotification({ + level: 'info', + message: 'Cannot rename locked blocks', + workflowId: activeWorkflowId || undefined, + }) + return { success: false, error: 'Block is locked' } + } + } + const trimmedName = name.trim() const normalizedNewName = normalizeName(trimmedName) @@ -823,14 +854,27 @@ export function useCollaborativeWorkflow() { if (ids.length === 0) return + const currentBlocks = useWorkflowStore.getState().blocks const previousStates: Record = {} const validIds: string[] = [] + // For each ID, collect non-locked blocks and their children for undo/redo for (const id of ids) { - const block = useWorkflowStore.getState().blocks[id] - if (block) { - previousStates[id] = block.enabled - validIds.push(id) + const block = currentBlocks[id] + if (!block) continue + + // Skip locked blocks + if (block.locked) continue + validIds.push(id) + previousStates[id] = block.enabled + + // If it's a loop or parallel, also capture children's previous states for undo/redo + if (block.type === 'loop' || block.type === 'parallel') { + Object.entries(currentBlocks).forEach(([blockId, b]) => { + if (b.data?.parentId === id && !b.locked) { + previousStates[blockId] = b.enabled + } + }) } } @@ -992,12 +1036,23 @@ export function useCollaborativeWorkflow() { if (ids.length === 0) return + const blocks = useWorkflowStore.getState().blocks + + const isProtected = (blockId: string): boolean => { + const block = blocks[blockId] + if (!block) return false + if (block.locked) return true + const parentId = block.data?.parentId + if (parentId && blocks[parentId]?.locked) return true + return false + } + const previousStates: Record = {} const validIds: string[] = [] for (const id of ids) { - const block = useWorkflowStore.getState().blocks[id] - if (block) { + const block = blocks[id] + if (block && !isProtected(id)) { previousStates[id] = block.horizontalHandles ?? false validIds.push(id) } @@ -1025,6 +1080,56 @@ export function useCollaborativeWorkflow() { [isBaselineDiffView, addToQueue, activeWorkflowId, session?.user?.id, undoRedo] ) + const collaborativeBatchToggleLocked = useCallback( + (ids: string[]) => { + if (isBaselineDiffView) { + return + } + + if (ids.length === 0) return + + const currentBlocks = useWorkflowStore.getState().blocks + const previousStates: Record = {} + const validIds: string[] = [] + + for (const id of ids) { + const block = currentBlocks[id] + if (!block) continue + + validIds.push(id) + previousStates[id] = block.locked ?? false + + if (block.type === 'loop' || block.type === 'parallel') { + Object.entries(currentBlocks).forEach(([blockId, b]) => { + if (b.data?.parentId === id) { + previousStates[blockId] = b.locked ?? false + } + }) + } + } + + if (validIds.length === 0) return + + const operationId = crypto.randomUUID() + + addToQueue({ + id: operationId, + operation: { + operation: BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED, + target: OPERATION_TARGETS.BLOCKS, + payload: { blockIds: validIds, previousStates }, + }, + workflowId: activeWorkflowId || '', + userId: session?.user?.id || 'unknown', + }) + + useWorkflowStore.getState().batchToggleLocked(validIds) + + undoRedo.recordBatchToggleLocked(validIds, previousStates) + }, + [isBaselineDiffView, addToQueue, activeWorkflowId, session?.user?.id, undoRedo] + ) + const collaborativeBatchAddEdges = useCallback( (edges: Edge[], options?: { skipUndoRedo?: boolean }) => { if (isBaselineDiffView) { @@ -1038,7 +1143,6 @@ export function useCollaborativeWorkflow() { if (edges.length === 0) return false - // Filter out invalid edges (e.g., edges targeting trigger blocks) and duplicates const blocks = useWorkflowStore.getState().blocks const currentEdges = useWorkflowStore.getState().edges const validEdges = filterValidEdges(edges, blocks) @@ -1669,6 +1773,7 @@ export function useCollaborativeWorkflow() { collaborativeToggleBlockAdvancedMode, collaborativeSetBlockCanonicalMode, collaborativeBatchToggleBlockHandles, + collaborativeBatchToggleLocked, collaborativeBatchAddBlocks, collaborativeBatchRemoveBlocks, collaborativeBatchAddEdges, diff --git a/apps/sim/hooks/use-undo-redo.ts b/apps/sim/hooks/use-undo-redo.ts index 3ce564e06..252f0785a 100644 --- a/apps/sim/hooks/use-undo-redo.ts +++ b/apps/sim/hooks/use-undo-redo.ts @@ -20,6 +20,7 @@ import { type BatchRemoveEdgesOperation, type BatchToggleEnabledOperation, type BatchToggleHandlesOperation, + type BatchToggleLockedOperation, type BatchUpdateParentOperation, captureLatestEdges, captureLatestSubBlockValues, @@ -415,6 +416,36 @@ export function useUndoRedo() { [activeWorkflowId, userId] ) + const recordBatchToggleLocked = useCallback( + (blockIds: string[], previousStates: Record) => { + if (!activeWorkflowId || blockIds.length === 0) return + + const operation: BatchToggleLockedOperation = { + id: crypto.randomUUID(), + type: UNDO_REDO_OPERATIONS.BATCH_TOGGLE_LOCKED, + timestamp: Date.now(), + workflowId: activeWorkflowId, + userId, + data: { blockIds, previousStates }, + } + + const inverse: BatchToggleLockedOperation = { + id: crypto.randomUUID(), + type: UNDO_REDO_OPERATIONS.BATCH_TOGGLE_LOCKED, + timestamp: Date.now(), + workflowId: activeWorkflowId, + userId, + data: { blockIds, previousStates }, + } + + const entry = createOperationEntry(operation, inverse) + useUndoRedoStore.getState().push(activeWorkflowId, userId, entry) + + logger.debug('Recorded batch toggle locked', { blockIds, previousStates }) + }, + [activeWorkflowId, userId] + ) + const undo = useCallback(async () => { if (!activeWorkflowId) return @@ -777,7 +808,9 @@ export function useUndoRedo() { const toggleOp = entry.inverse as BatchToggleEnabledOperation const { blockIds, previousStates } = toggleOp.data - const validBlockIds = blockIds.filter((id) => useWorkflowStore.getState().blocks[id]) + // Restore all blocks in previousStates (includes children of containers) + const allBlockIds = Object.keys(previousStates) + const validBlockIds = allBlockIds.filter((id) => useWorkflowStore.getState().blocks[id]) if (validBlockIds.length === 0) { logger.debug('Undo batch-toggle-enabled skipped; no blocks exist') break @@ -788,14 +821,14 @@ export function useUndoRedo() { operation: { operation: BLOCKS_OPERATIONS.BATCH_TOGGLE_ENABLED, target: OPERATION_TARGETS.BLOCKS, - payload: { blockIds: validBlockIds, previousStates }, + payload: { blockIds, previousStates }, }, workflowId: activeWorkflowId, userId, }) // Use setBlockEnabled to directly restore to previous state - // This is more robust than conditional toggle in collaborative scenarios + // This restores all affected blocks including children of containers validBlockIds.forEach((blockId) => { useWorkflowStore.getState().setBlockEnabled(blockId, previousStates[blockId]) }) @@ -829,6 +862,36 @@ export function useUndoRedo() { }) break } + case UNDO_REDO_OPERATIONS.BATCH_TOGGLE_LOCKED: { + const toggleOp = entry.inverse as BatchToggleLockedOperation + const { blockIds, previousStates } = toggleOp.data + + // Restore all blocks in previousStates (includes children of containers) + const allBlockIds = Object.keys(previousStates) + const validBlockIds = allBlockIds.filter((id) => useWorkflowStore.getState().blocks[id]) + if (validBlockIds.length === 0) { + logger.debug('Undo batch-toggle-locked skipped; no blocks exist') + break + } + + addToQueue({ + id: opId, + operation: { + operation: BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED, + target: OPERATION_TARGETS.BLOCKS, + payload: { blockIds, previousStates }, + }, + workflowId: activeWorkflowId, + userId, + }) + + // Use setBlockLocked to directly restore to previous state + // This restores all affected blocks including children of containers + validBlockIds.forEach((blockId) => { + useWorkflowStore.getState().setBlockLocked(blockId, previousStates[blockId]) + }) + break + } case UNDO_REDO_OPERATIONS.APPLY_DIFF: { const applyDiffInverse = entry.inverse as any const { baselineSnapshot } = applyDiffInverse.data @@ -1365,7 +1428,9 @@ export function useUndoRedo() { const toggleOp = entry.operation as BatchToggleEnabledOperation const { blockIds, previousStates } = toggleOp.data - const validBlockIds = blockIds.filter((id) => useWorkflowStore.getState().blocks[id]) + // Process all blocks in previousStates (includes children of containers) + const allBlockIds = Object.keys(previousStates) + const validBlockIds = allBlockIds.filter((id) => useWorkflowStore.getState().blocks[id]) if (validBlockIds.length === 0) { logger.debug('Redo batch-toggle-enabled skipped; no blocks exist') break @@ -1376,16 +1441,18 @@ export function useUndoRedo() { operation: { operation: BLOCKS_OPERATIONS.BATCH_TOGGLE_ENABLED, target: OPERATION_TARGETS.BLOCKS, - payload: { blockIds: validBlockIds, previousStates }, + payload: { blockIds, previousStates }, }, workflowId: activeWorkflowId, userId, }) - // Use setBlockEnabled to directly set to toggled state - // Redo sets to !previousStates (the state after the original toggle) + // Compute target state the same way batchToggleEnabled does: + // use !firstBlock.enabled, where firstBlock is blockIds[0] + const firstBlockId = blockIds[0] + const targetEnabled = !previousStates[firstBlockId] validBlockIds.forEach((blockId) => { - useWorkflowStore.getState().setBlockEnabled(blockId, !previousStates[blockId]) + useWorkflowStore.getState().setBlockEnabled(blockId, targetEnabled) }) break } @@ -1417,6 +1484,38 @@ export function useUndoRedo() { }) break } + case UNDO_REDO_OPERATIONS.BATCH_TOGGLE_LOCKED: { + const toggleOp = entry.operation as BatchToggleLockedOperation + const { blockIds, previousStates } = toggleOp.data + + // Process all blocks in previousStates (includes children of containers) + const allBlockIds = Object.keys(previousStates) + const validBlockIds = allBlockIds.filter((id) => useWorkflowStore.getState().blocks[id]) + if (validBlockIds.length === 0) { + logger.debug('Redo batch-toggle-locked skipped; no blocks exist') + break + } + + addToQueue({ + id: opId, + operation: { + operation: BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED, + target: OPERATION_TARGETS.BLOCKS, + payload: { blockIds, previousStates }, + }, + workflowId: activeWorkflowId, + userId, + }) + + // Compute target state the same way batchToggleLocked does: + // use !firstBlock.locked, where firstBlock is blockIds[0] + const firstBlockId = blockIds[0] + const targetLocked = !previousStates[firstBlockId] + validBlockIds.forEach((blockId) => { + useWorkflowStore.getState().setBlockLocked(blockId, targetLocked) + }) + break + } case UNDO_REDO_OPERATIONS.APPLY_DIFF: { // Redo apply-diff means re-applying the proposed state with diff markers const applyDiffOp = entry.operation as any @@ -1738,6 +1837,7 @@ export function useUndoRedo() { recordBatchUpdateParent, recordBatchToggleEnabled, recordBatchToggleHandles, + recordBatchToggleLocked, recordApplyDiff, recordAcceptDiff, recordRejectDiff, diff --git a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts index f484ea5d8..66bb54ffa 100644 --- a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts +++ b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts @@ -54,6 +54,7 @@ type SkippedItemType = | 'block_not_found' | 'invalid_block_type' | 'block_not_allowed' + | 'block_locked' | 'tool_not_allowed' | 'invalid_edge_target' | 'invalid_edge_source' @@ -618,6 +619,7 @@ function createBlockFromParams( subBlocks: {}, outputs: outputs, data: parentId ? { parentId, extent: 'parent' as const } : {}, + locked: false, } // Add validated inputs as subBlocks @@ -1520,6 +1522,24 @@ function applyOperationsToWorkflowState( break } + // Check if block is locked or inside a locked container + const deleteBlock = modifiedState.blocks[block_id] + const deleteParentId = deleteBlock.data?.parentId as string | undefined + const deleteParentLocked = deleteParentId + ? modifiedState.blocks[deleteParentId]?.locked + : false + if (deleteBlock.locked || deleteParentLocked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'delete', + blockId: block_id, + reason: deleteParentLocked + ? `Block "${block_id}" is inside locked container "${deleteParentId}" and cannot be deleted` + : `Block "${block_id}" is locked and cannot be deleted`, + }) + break + } + // Find all child blocks to remove const blocksToRemove = new Set([block_id]) const findChildren = (parentId: string) => { @@ -1555,6 +1575,21 @@ function applyOperationsToWorkflowState( const block = modifiedState.blocks[block_id] + // Check if block is locked or inside a locked container + const editParentId = block.data?.parentId as string | undefined + const editParentLocked = editParentId ? modifiedState.blocks[editParentId]?.locked : false + if (block.locked || editParentLocked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'edit', + blockId: block_id, + reason: editParentLocked + ? `Block "${block_id}" is inside locked container "${editParentId}" and cannot be edited` + : `Block "${block_id}" is locked and cannot be edited`, + }) + break + } + // Ensure block has essential properties if (!block.type) { logger.warn(`Block ${block_id} missing type property, skipping edit`, { @@ -2122,6 +2157,19 @@ function applyOperationsToWorkflowState( // Handle nested nodes (for loops/parallels created from scratch) if (params.nestedNodes) { + // Defensive check: verify parent is not locked before adding children + // (Parent was just created with locked: false, but check for consistency) + const parentBlock = modifiedState.blocks[block_id] + if (parentBlock?.locked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'add_nested_nodes', + blockId: block_id, + reason: `Container "${block_id}" is locked - cannot add nested nodes`, + }) + break + } + Object.entries(params.nestedNodes).forEach(([childId, childBlock]: [string, any]) => { // Validate childId is a valid string if (!isValidKey(childId)) { @@ -2209,6 +2257,18 @@ function applyOperationsToWorkflowState( break } + // Check if subflow is locked + if (subflowBlock.locked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'insert_into_subflow', + blockId: block_id, + reason: `Subflow "${subflowId}" is locked - cannot insert block "${block_id}"`, + details: { subflowId }, + }) + break + } + if (subflowBlock.type !== 'loop' && subflowBlock.type !== 'parallel') { logger.error('Subflow block has invalid type', { subflowId, @@ -2247,6 +2307,17 @@ function applyOperationsToWorkflowState( break } + // Check if existing block is locked + if (existingBlock.locked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'insert_into_subflow', + blockId: block_id, + reason: `Block "${block_id}" is locked and cannot be moved into a subflow`, + }) + break + } + // Moving existing block into subflow - just update parent existingBlock.data = { ...existingBlock.data, @@ -2392,6 +2463,30 @@ function applyOperationsToWorkflowState( break } + // Check if block is locked + if (block.locked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'extract_from_subflow', + blockId: block_id, + reason: `Block "${block_id}" is locked and cannot be extracted from subflow`, + }) + break + } + + // Check if parent subflow is locked + const parentSubflow = modifiedState.blocks[subflowId] + if (parentSubflow?.locked) { + logSkippedItem(skippedItems, { + type: 'block_locked', + operationType: 'extract_from_subflow', + blockId: block_id, + reason: `Subflow "${subflowId}" is locked - cannot extract block "${block_id}"`, + details: { subflowId }, + }) + break + } + // Verify it's actually a child of this subflow if (block.data?.parentId !== subflowId) { logger.warn('Block is not a child of specified subflow', { diff --git a/apps/sim/lib/workflows/comparison/compare.test.ts b/apps/sim/lib/workflows/comparison/compare.test.ts index a2e98127b..be7b6e9c5 100644 --- a/apps/sim/lib/workflows/comparison/compare.test.ts +++ b/apps/sim/lib/workflows/comparison/compare.test.ts @@ -296,6 +296,26 @@ describe('hasWorkflowChanged', () => { }) expect(hasWorkflowChanged(state1, state2)).toBe(true) }) + + it.concurrent('should detect locked/unlocked changes', () => { + const state1 = createWorkflowState({ + blocks: { block1: createBlock('block1', { locked: false }) }, + }) + const state2 = createWorkflowState({ + blocks: { block1: createBlock('block1', { locked: true }) }, + }) + expect(hasWorkflowChanged(state1, state2)).toBe(true) + }) + + it.concurrent('should not detect changes when locked state is the same', () => { + const state1 = createWorkflowState({ + blocks: { block1: createBlock('block1', { locked: true }) }, + }) + const state2 = createWorkflowState({ + blocks: { block1: createBlock('block1', { locked: true }) }, + }) + expect(hasWorkflowChanged(state1, state2)).toBe(false) + }) }) describe('SubBlock Changes', () => { diff --git a/apps/sim/lib/workflows/comparison/compare.ts b/apps/sim/lib/workflows/comparison/compare.ts index da118fe1c..ce37dd86a 100644 --- a/apps/sim/lib/workflows/comparison/compare.ts +++ b/apps/sim/lib/workflows/comparison/compare.ts @@ -157,7 +157,7 @@ export function generateWorkflowDiffSummary( } // Check other block properties (boolean fields) // Use !! to normalize: null/undefined/false are all equivalent (falsy) - const blockFields = ['horizontalHandles', 'advancedMode', 'triggerMode'] as const + const blockFields = ['horizontalHandles', 'advancedMode', 'triggerMode', 'locked'] as const for (const field of blockFields) { if (!!currentBlock[field] !== !!previousBlock[field]) { changes.push({ diff --git a/apps/sim/lib/workflows/defaults.ts b/apps/sim/lib/workflows/defaults.ts index 590594aa5..d93dd0b13 100644 --- a/apps/sim/lib/workflows/defaults.ts +++ b/apps/sim/lib/workflows/defaults.ts @@ -100,6 +100,7 @@ function buildStartBlockState( triggerMode: false, height: 0, data: {}, + locked: false, } return { blockState, subBlockValues } diff --git a/apps/sim/lib/workflows/diff/diff-engine.test.ts b/apps/sim/lib/workflows/diff/diff-engine.test.ts new file mode 100644 index 000000000..aecbd801e --- /dev/null +++ b/apps/sim/lib/workflows/diff/diff-engine.test.ts @@ -0,0 +1,173 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/types' + +// Mock all external dependencies before imports +vi.mock('@sim/logger', () => ({ + createLogger: () => ({ + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }), +})) + +vi.mock('@/stores/workflows/workflow/store', () => ({ + useWorkflowStore: { + getState: () => ({ + getWorkflowState: () => ({ blocks: {}, edges: [], loops: {}, parallels: {} }), + }), + }, +})) + +vi.mock('@/stores/workflows/utils', () => ({ + mergeSubblockState: (blocks: Record) => blocks, +})) + +vi.mock('@/lib/workflows/sanitization/key-validation', () => ({ + isValidKey: (key: string) => key !== 'undefined' && key !== 'null' && key !== '', +})) + +vi.mock('@/lib/workflows/autolayout', () => ({ + transferBlockHeights: vi.fn(), + applyTargetedLayout: (blocks: Record) => blocks, + applyAutoLayout: () => ({ success: true, blocks: {} }), +})) + +vi.mock('@/lib/workflows/autolayout/constants', () => ({ + DEFAULT_HORIZONTAL_SPACING: 500, + DEFAULT_VERTICAL_SPACING: 400, + DEFAULT_LAYOUT_OPTIONS: {}, +})) + +vi.mock('@/stores/workflows/workflow/utils', () => ({ + generateLoopBlocks: () => ({}), + generateParallelBlocks: () => ({}), +})) + +import { WorkflowDiffEngine } from './diff-engine' + +function createMockBlock(overrides: Partial = {}): BlockState { + return { + id: 'block-1', + type: 'agent', + name: 'Test Block', + enabled: true, + position: { x: 0, y: 0 }, + subBlocks: {}, + outputs: {}, + ...overrides, + } as BlockState +} + +function createMockWorkflowState(blocks: Record): WorkflowState { + return { + blocks, + edges: [], + loops: {}, + parallels: {}, + } +} + +describe('WorkflowDiffEngine', () => { + let engine: WorkflowDiffEngine + + beforeEach(() => { + engine = new WorkflowDiffEngine() + vi.clearAllMocks() + }) + + describe('hasBlockChanged detection', () => { + describe('locked state changes', () => { + it.concurrent( + 'should detect when block locked state changes from false to true', + async () => { + const freshEngine = new WorkflowDiffEngine() + const baseline = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1', locked: false }), + }) + + const proposed = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1', locked: true }), + }) + + const result = await freshEngine.createDiffFromWorkflowState( + proposed, + undefined, + baseline + ) + + expect(result.success).toBe(true) + expect(result.diff?.diffAnalysis?.edited_blocks).toContain('block-1') + } + ) + + it.concurrent('should not detect change when locked state is the same', async () => { + const freshEngine = new WorkflowDiffEngine() + const baseline = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1', locked: true }), + }) + + const proposed = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1', locked: true }), + }) + + const result = await freshEngine.createDiffFromWorkflowState(proposed, undefined, baseline) + + expect(result.success).toBe(true) + expect(result.diff?.diffAnalysis?.edited_blocks).not.toContain('block-1') + }) + + it.concurrent('should detect change when locked goes from undefined to true', async () => { + const freshEngine = new WorkflowDiffEngine() + const baseline = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1' }), // locked undefined + }) + + const proposed = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1', locked: true }), + }) + + const result = await freshEngine.createDiffFromWorkflowState(proposed, undefined, baseline) + + expect(result.success).toBe(true) + // The hasBlockChanged function uses !!locked for comparison + // so undefined -> true should be detected as a change + expect(result.diff?.diffAnalysis?.edited_blocks).toContain('block-1') + }) + + it.concurrent('should not detect change when both locked states are falsy', async () => { + const freshEngine = new WorkflowDiffEngine() + const baseline = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1' }), // locked undefined + }) + + const proposed = createMockWorkflowState({ + 'block-1': createMockBlock({ id: 'block-1', locked: false }), // locked false + }) + + const result = await freshEngine.createDiffFromWorkflowState(proposed, undefined, baseline) + + expect(result.success).toBe(true) + // undefined and false should both be falsy, so !! comparison makes them equal + expect(result.diff?.diffAnalysis?.edited_blocks).not.toContain('block-1') + }) + }) + }) + + describe('diff lifecycle', () => { + it.concurrent('should start with no diff', () => { + const freshEngine = new WorkflowDiffEngine() + expect(freshEngine.hasDiff()).toBe(false) + expect(freshEngine.getCurrentDiff()).toBeUndefined() + }) + + it.concurrent('should clear diff', () => { + const freshEngine = new WorkflowDiffEngine() + freshEngine.clearDiff() + expect(freshEngine.hasDiff()).toBe(false) + }) + }) +}) diff --git a/apps/sim/lib/workflows/diff/diff-engine.ts b/apps/sim/lib/workflows/diff/diff-engine.ts index f22365d14..adb6eeae7 100644 --- a/apps/sim/lib/workflows/diff/diff-engine.ts +++ b/apps/sim/lib/workflows/diff/diff-engine.ts @@ -215,6 +215,7 @@ function hasBlockChanged(currentBlock: BlockState, proposedBlock: BlockState): b if (currentBlock.name !== proposedBlock.name) return true if (currentBlock.enabled !== proposedBlock.enabled) return true if (currentBlock.triggerMode !== proposedBlock.triggerMode) return true + if (!!currentBlock.locked !== !!proposedBlock.locked) return true // Compare subBlocks const currentSubKeys = Object.keys(currentBlock.subBlocks || {}) diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index d73df91cc..8e006e076 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -189,6 +189,7 @@ export async function duplicateWorkflow( parentId: newParentId, extent: newExtent, data: updatedData, + locked: false, // Duplicated blocks should always be unlocked createdAt: now, updatedAt: now, } diff --git a/apps/sim/lib/workflows/persistence/utils.ts b/apps/sim/lib/workflows/persistence/utils.ts index e38fb27ba..b747177e3 100644 --- a/apps/sim/lib/workflows/persistence/utils.ts +++ b/apps/sim/lib/workflows/persistence/utils.ts @@ -226,6 +226,7 @@ export async function loadWorkflowFromNormalizedTables( subBlocks: (block.subBlocks as BlockState['subBlocks']) || {}, outputs: (block.outputs as BlockState['outputs']) || {}, data: blockData, + locked: block.locked, } blocksMap[block.id] = assembled @@ -363,6 +364,7 @@ export async function saveWorkflowToNormalizedTables( data: block.data || {}, parentId: block.data?.parentId || null, extent: block.data?.extent || null, + locked: block.locked ?? false, })) await tx.insert(workflowBlocks).values(blockInserts) @@ -627,7 +629,8 @@ export function regenerateWorkflowStateIds(state: RegenerateStateInput): Regener // Regenerate blocks with updated references Object.entries(state.blocks || {}).forEach(([oldId, block]) => { const newId = blockIdMapping.get(oldId)! - const newBlock: BlockState = { ...block, id: newId } + // Duplicated blocks are always unlocked so users can edit them + const newBlock: BlockState = { ...block, id: newId, locked: false } // Update parentId reference if it exists if (newBlock.data?.parentId) { diff --git a/apps/sim/socket/constants.ts b/apps/sim/socket/constants.ts index fb2b1fc56..b3afff2e6 100644 --- a/apps/sim/socket/constants.ts +++ b/apps/sim/socket/constants.ts @@ -17,6 +17,7 @@ export const BLOCKS_OPERATIONS = { BATCH_TOGGLE_ENABLED: 'batch-toggle-enabled', BATCH_TOGGLE_HANDLES: 'batch-toggle-handles', BATCH_UPDATE_PARENT: 'batch-update-parent', + BATCH_TOGGLE_LOCKED: 'batch-toggle-locked', } as const export type BlocksOperation = (typeof BLOCKS_OPERATIONS)[keyof typeof BLOCKS_OPERATIONS] @@ -85,6 +86,7 @@ export const UNDO_REDO_OPERATIONS = { BATCH_UPDATE_PARENT: 'batch-update-parent', BATCH_TOGGLE_ENABLED: 'batch-toggle-enabled', BATCH_TOGGLE_HANDLES: 'batch-toggle-handles', + BATCH_TOGGLE_LOCKED: 'batch-toggle-locked', APPLY_DIFF: 'apply-diff', ACCEPT_DIFF: 'accept-diff', REJECT_DIFF: 'reject-diff', diff --git a/apps/sim/socket/database/operations.ts b/apps/sim/socket/database/operations.ts index 991eac1a0..d677466cb 100644 --- a/apps/sim/socket/database/operations.ts +++ b/apps/sim/socket/database/operations.ts @@ -263,18 +263,51 @@ async function handleBlockOperationTx( throw new Error('Missing required fields for update name operation') } - const updateResult = await tx + // Check if block is protected (locked or inside locked parent) + const blockToRename = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId))) + .limit(1) + + if (blockToRename.length === 0) { + throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`) + } + + const block = blockToRename[0] + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + + if (block.locked) { + logger.info(`Skipping rename of locked block ${payload.id}`) + break + } + + if (parentId) { + const parentBlock = await tx + .select({ locked: workflowBlocks.locked }) + .from(workflowBlocks) + .where(and(eq(workflowBlocks.id, parentId), eq(workflowBlocks.workflowId, workflowId))) + .limit(1) + + if (parentBlock.length > 0 && parentBlock[0].locked) { + logger.info(`Skipping rename of block ${payload.id} - parent ${parentId} is locked`) + break + } + } + + await tx .update(workflowBlocks) .set({ name: payload.name, updatedAt: new Date(), }) .where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId))) - .returning({ id: workflowBlocks.id }) - - if (updateResult.length === 0) { - throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`) - } logger.debug(`Updated block name: ${payload.id} -> "${payload.name}"`) break @@ -507,7 +540,37 @@ async function handleBlocksOperationTx( }) if (blocks && blocks.length > 0) { - const blockValues = blocks.map((block: Record) => { + // Fetch existing blocks to check for locked parents + const existingBlocks = await tx + .select({ id: workflowBlocks.id, locked: workflowBlocks.locked }) + .from(workflowBlocks) + .where(eq(workflowBlocks.workflowId, workflowId)) + + type ExistingBlockRecord = (typeof existingBlocks)[number] + const lockedParentIds = new Set( + existingBlocks + .filter((b: ExistingBlockRecord) => b.locked) + .map((b: ExistingBlockRecord) => b.id) + ) + + // Filter out blocks being added to locked parents + const allowedBlocks = (blocks as Array>).filter((block) => { + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && lockedParentIds.has(parentId)) { + logger.info(`Skipping block ${block.id} - parent ${parentId} is locked`) + return false + } + return true + }) + + if (allowedBlocks.length === 0) { + logger.info('All blocks filtered out due to locked parents, skipping add') + break + } + + const blockValues = allowedBlocks.map((block: Record) => { const blockId = block.id as string const mergedSubBlocks = mergeSubBlockValues( block.subBlocks as Record, @@ -529,6 +592,7 @@ async function handleBlocksOperationTx( advancedMode: (block.advancedMode as boolean) ?? false, triggerMode: (block.triggerMode as boolean) ?? false, height: (block.height as number) || 0, + locked: (block.locked as boolean) ?? false, } }) @@ -537,7 +601,7 @@ async function handleBlocksOperationTx( // Create subflow entries for loop/parallel blocks (skip if already in payload) const loopIds = new Set(loops ? Object.keys(loops) : []) const parallelIds = new Set(parallels ? Object.keys(parallels) : []) - for (const block of blocks) { + for (const block of allowedBlocks) { const blockId = block.id as string if (block.type === 'loop' && !loopIds.has(blockId)) { await tx.insert(workflowSubflows).values({ @@ -566,7 +630,7 @@ async function handleBlocksOperationTx( // Update parent subflow node lists const parentIds = new Set() - for (const block of blocks) { + for (const block of allowedBlocks) { const parentId = (block.data as Record)?.parentId as string | undefined if (parentId) { parentIds.add(parentId) @@ -624,44 +688,74 @@ async function handleBlocksOperationTx( logger.info(`Batch removing ${ids.length} blocks from workflow ${workflowId}`) + // Fetch all blocks to check lock status and filter out protected blocks + const allBlocks = await tx + .select({ + id: workflowBlocks.id, + type: workflowBlocks.type, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where(eq(workflowBlocks.workflowId, workflowId)) + + type BlockRecord = (typeof allBlocks)[number] + const blocksById: Record = Object.fromEntries( + allBlocks.map((b: BlockRecord) => [b.id, b]) + ) + + // Helper to check if a block is protected (locked or inside locked parent) + const isProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && blocksById[parentId]?.locked) return true + return false + } + + // Filter out protected blocks from deletion request + const deletableIds = ids.filter((id) => !isProtected(id)) + if (deletableIds.length === 0) { + logger.info('All requested blocks are protected, skipping deletion') + return + } + + if (deletableIds.length < ids.length) { + logger.info( + `Filtered out ${ids.length - deletableIds.length} protected blocks from deletion` + ) + } + // Collect all block IDs including children of subflows - const allBlocksToDelete = new Set(ids) + const allBlocksToDelete = new Set(deletableIds) - for (const id of ids) { - const blockToRemove = await tx - .select({ type: workflowBlocks.type }) - .from(workflowBlocks) - .where(and(eq(workflowBlocks.id, id), eq(workflowBlocks.workflowId, workflowId))) - .limit(1) - - if (blockToRemove.length > 0 && isSubflowBlockType(blockToRemove[0].type)) { - const childBlocks = await tx - .select({ id: workflowBlocks.id }) - .from(workflowBlocks) - .where( - and( - eq(workflowBlocks.workflowId, workflowId), - sql`${workflowBlocks.data}->>'parentId' = ${id}` - ) - ) - - childBlocks.forEach((child: { id: string }) => allBlocksToDelete.add(child.id)) + for (const id of deletableIds) { + const block = blocksById[id] + if (block && isSubflowBlockType(block.type)) { + // Include all children of the subflow (they should be deleted with parent) + for (const b of allBlocks) { + const parentId = (b.data as Record | null)?.parentId + if (parentId === id) { + allBlocksToDelete.add(b.id) + } + } } } const blockIdsArray = Array.from(allBlocksToDelete) - // Collect parent IDs BEFORE deleting blocks + // Collect parent IDs BEFORE deleting blocks (use blocksById, already fetched) const parentIds = new Set() - for (const id of ids) { - const parentInfo = await tx - .select({ parentId: sql`${workflowBlocks.data}->>'parentId'` }) - .from(workflowBlocks) - .where(and(eq(workflowBlocks.id, id), eq(workflowBlocks.workflowId, workflowId))) - .limit(1) - - if (parentInfo.length > 0 && parentInfo[0].parentId) { - parentIds.add(parentInfo[0].parentId) + for (const id of deletableIds) { + const block = blocksById[id] + const parentId = (block?.data as Record | null)?.parentId as + | string + | undefined + if (parentId) { + parentIds.add(parentId) } } @@ -741,22 +835,61 @@ async function handleBlocksOperationTx( `Batch toggling enabled state for ${blockIds.length} blocks in workflow ${workflowId}` ) - const blocks = await tx - .select({ id: workflowBlocks.id, enabled: workflowBlocks.enabled }) + // Get all blocks in workflow to find children and check locked state + const allBlocks = await tx + .select({ + id: workflowBlocks.id, + enabled: workflowBlocks.enabled, + locked: workflowBlocks.locked, + type: workflowBlocks.type, + data: workflowBlocks.data, + }) .from(workflowBlocks) - .where(and(eq(workflowBlocks.workflowId, workflowId), inArray(workflowBlocks.id, blockIds))) + .where(eq(workflowBlocks.workflowId, workflowId)) - for (const block of blocks) { + type BlockRecord = (typeof allBlocks)[number] + const blocksById: Record = Object.fromEntries( + allBlocks.map((b: BlockRecord) => [b.id, b]) + ) + const blocksToToggle = new Set() + + // Collect all blocks to toggle including children of containers + for (const id of blockIds) { + const block = blocksById[id] + if (!block || block.locked) continue + + blocksToToggle.add(id) + + // If it's a loop or parallel, also include all children + if (block.type === 'loop' || block.type === 'parallel') { + for (const b of allBlocks) { + const parentId = (b.data as Record | null)?.parentId + if (parentId === id && !b.locked) { + blocksToToggle.add(b.id) + } + } + } + } + + // Determine target enabled state based on first toggleable block + if (blocksToToggle.size === 0) break + const firstToggleableId = Array.from(blocksToToggle)[0] + const firstBlock = blocksById[firstToggleableId] + if (!firstBlock) break + const targetEnabled = !firstBlock.enabled + + // Update all affected blocks + for (const blockId of blocksToToggle) { await tx .update(workflowBlocks) .set({ - enabled: !block.enabled, + enabled: targetEnabled, updatedAt: new Date(), }) - .where(and(eq(workflowBlocks.id, block.id), eq(workflowBlocks.workflowId, workflowId))) + .where(and(eq(workflowBlocks.id, blockId), eq(workflowBlocks.workflowId, workflowId))) } - logger.debug(`Batch toggled enabled state for ${blocks.length} blocks`) + logger.debug(`Batch toggled enabled state for ${blocksToToggle.size} blocks`) break } @@ -768,22 +901,118 @@ async function handleBlocksOperationTx( logger.info(`Batch toggling handles for ${blockIds.length} blocks in workflow ${workflowId}`) - const blocks = await tx - .select({ id: workflowBlocks.id, horizontalHandles: workflowBlocks.horizontalHandles }) + // Fetch all blocks to check lock status and filter out protected blocks + const allBlocks = await tx + .select({ + id: workflowBlocks.id, + horizontalHandles: workflowBlocks.horizontalHandles, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) .from(workflowBlocks) - .where(and(eq(workflowBlocks.workflowId, workflowId), inArray(workflowBlocks.id, blockIds))) + .where(eq(workflowBlocks.workflowId, workflowId)) - for (const block of blocks) { + type HandleBlockRecord = (typeof allBlocks)[number] + const blocksById: Record = Object.fromEntries( + allBlocks.map((b: HandleBlockRecord) => [b.id, b]) + ) + + // Helper to check if a block is protected (locked or inside locked parent) + const isProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && blocksById[parentId]?.locked) return true + return false + } + + // Filter to only toggle handles on unprotected blocks + const blocksToToggle = blockIds.filter((id) => blocksById[id] && !isProtected(id)) + if (blocksToToggle.length === 0) { + logger.info('All requested blocks are protected, skipping handles toggle') + break + } + + for (const blockId of blocksToToggle) { + const block = blocksById[blockId] await tx .update(workflowBlocks) .set({ horizontalHandles: !block.horizontalHandles, updatedAt: new Date(), }) - .where(and(eq(workflowBlocks.id, block.id), eq(workflowBlocks.workflowId, workflowId))) + .where(and(eq(workflowBlocks.id, blockId), eq(workflowBlocks.workflowId, workflowId))) } - logger.debug(`Batch toggled handles for ${blocks.length} blocks`) + logger.debug(`Batch toggled handles for ${blocksToToggle.length} blocks`) + break + } + + case BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED: { + const { blockIds } = payload + if (!Array.isArray(blockIds) || blockIds.length === 0) { + return + } + + logger.info(`Batch toggling locked for ${blockIds.length} blocks in workflow ${workflowId}`) + + // Get all blocks in workflow to find children + const allBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + type: workflowBlocks.type, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where(eq(workflowBlocks.workflowId, workflowId)) + + type LockedBlockRecord = (typeof allBlocks)[number] + const blocksById: Record = Object.fromEntries( + allBlocks.map((b: LockedBlockRecord) => [b.id, b]) + ) + const blocksToToggle = new Set() + + // Collect all blocks to toggle including children of containers + for (const id of blockIds) { + const block = blocksById[id] + if (!block) continue + + blocksToToggle.add(id) + + // If it's a loop or parallel, also include all children + if (block.type === 'loop' || block.type === 'parallel') { + for (const b of allBlocks) { + const parentId = (b.data as Record | null)?.parentId + if (parentId === id) { + blocksToToggle.add(b.id) + } + } + } + } + + // Determine target locked state based on first toggleable block + if (blocksToToggle.size === 0) break + const firstToggleableId = Array.from(blocksToToggle)[0] + const firstBlock = blocksById[firstToggleableId] + if (!firstBlock) break + const targetLocked = !firstBlock.locked + + // Update all affected blocks + for (const blockId of blocksToToggle) { + await tx + .update(workflowBlocks) + .set({ + locked: targetLocked, + updatedAt: new Date(), + }) + .where(and(eq(workflowBlocks.id, blockId), eq(workflowBlocks.workflowId, workflowId))) + } + + logger.debug(`Batch toggled locked for ${blocksToToggle.size} blocks`) break } @@ -795,19 +1024,54 @@ async function handleBlocksOperationTx( logger.info(`Batch updating parent for ${updates.length} blocks in workflow ${workflowId}`) + // Fetch all blocks to check lock status + const allBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where(eq(workflowBlocks.workflowId, workflowId)) + + type ParentBlockRecord = (typeof allBlocks)[number] + const blocksById: Record = Object.fromEntries( + allBlocks.map((b: ParentBlockRecord) => [b.id, b]) + ) + + // Helper to check if a block is protected (locked or inside locked parent) + const isProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const currentParentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (currentParentId && blocksById[currentParentId]?.locked) return true + return false + } + for (const update of updates) { const { id, parentId, position } = update if (!id) continue + // Skip protected blocks (locked or inside locked container) + if (isProtected(id)) { + logger.info(`Skipping block ${id} parent update - block is protected`) + continue + } + + // Skip if trying to move into a locked container + if (parentId && blocksById[parentId]?.locked) { + logger.info(`Skipping block ${id} parent update - target parent ${parentId} is locked`) + continue + } + // Fetch current parent to update subflow node lists - const [existing] = await tx - .select({ - id: workflowBlocks.id, - parentId: sql`${workflowBlocks.data}->>'parentId'`, - }) - .from(workflowBlocks) - .where(and(eq(workflowBlocks.id, id), eq(workflowBlocks.workflowId, workflowId))) - .limit(1) + const existing = blocksById[id] + const existingParentId = (existing?.data as Record | null)?.parentId as + | string + | undefined if (!existing) { logger.warn(`Block ${id} not found for batch-update-parent`) @@ -852,8 +1116,8 @@ async function handleBlocksOperationTx( await updateSubflowNodeList(tx, workflowId, parentId) } // If the block had a previous parent, update that parent's node list as well - if (existing?.parentId && existing.parentId !== parentId) { - await updateSubflowNodeList(tx, workflowId, existing.parentId) + if (existingParentId && existingParentId !== parentId) { + await updateSubflowNodeList(tx, workflowId, existingParentId) } } @@ -869,11 +1133,75 @@ async function handleBlocksOperationTx( async function handleEdgeOperationTx(tx: any, workflowId: string, operation: string, payload: any) { switch (operation) { case EDGE_OPERATIONS.ADD: { - // Validate required fields if (!payload.id || !payload.source || !payload.target) { throw new Error('Missing required fields for add edge operation') } + const edgeBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, [payload.source, payload.target]) + ) + ) + + type EdgeBlockRecord = (typeof edgeBlocks)[number] + const blocksById: Record = Object.fromEntries( + edgeBlocks.map((b: EdgeBlockRecord) => [b.id, b]) + ) + + const parentIds = new Set() + for (const block of edgeBlocks) { + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && !blocksById[parentId]) { + parentIds.add(parentId) + } + } + + // Fetch parent blocks if needed + if (parentIds.size > 0) { + const parentBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, Array.from(parentIds)) + ) + ) + for (const b of parentBlocks) { + blocksById[b.id] = b + } + } + + const isBlockProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && blocksById[parentId]?.locked) return true + return false + } + + if (isBlockProtected(payload.source) || isBlockProtected(payload.target)) { + logger.info(`Skipping edge add - source or target block is protected`) + break + } + await tx.insert(workflowEdges).values({ id: payload.id, workflowId, @@ -892,15 +1220,94 @@ async function handleEdgeOperationTx(tx: any, workflowId: string, operation: str throw new Error('Missing edge ID for remove operation') } - const deleteResult = await tx - .delete(workflowEdges) + // Get the edge to check if connected blocks are protected + const [edgeToRemove] = await tx + .select({ + sourceBlockId: workflowEdges.sourceBlockId, + targetBlockId: workflowEdges.targetBlockId, + }) + .from(workflowEdges) .where(and(eq(workflowEdges.id, payload.id), eq(workflowEdges.workflowId, workflowId))) - .returning({ id: workflowEdges.id }) + .limit(1) - if (deleteResult.length === 0) { + if (!edgeToRemove) { throw new Error(`Edge ${payload.id} not found in workflow ${workflowId}`) } + // Check if source or target blocks are protected + const connectedBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, [edgeToRemove.sourceBlockId, edgeToRemove.targetBlockId]) + ) + ) + + type RemoveEdgeBlockRecord = (typeof connectedBlocks)[number] + const blocksById: Record = Object.fromEntries( + connectedBlocks.map((b: RemoveEdgeBlockRecord) => [b.id, b]) + ) + + // Collect parent IDs that need to be fetched + const parentIds = new Set() + for (const block of connectedBlocks) { + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && !blocksById[parentId]) { + parentIds.add(parentId) + } + } + + // Fetch parent blocks if needed + if (parentIds.size > 0) { + const parentBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, Array.from(parentIds)) + ) + ) + for (const b of parentBlocks) { + blocksById[b.id] = b + } + } + + const isBlockProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && blocksById[parentId]?.locked) return true + return false + } + + if ( + isBlockProtected(edgeToRemove.sourceBlockId) || + isBlockProtected(edgeToRemove.targetBlockId) + ) { + logger.info(`Skipping edge remove - source or target block is protected`) + break + } + + await tx + .delete(workflowEdges) + .where(and(eq(workflowEdges.id, payload.id), eq(workflowEdges.workflowId, workflowId))) + logger.debug(`Removed edge ${payload.id} from workflow ${workflowId}`) break } @@ -927,11 +1334,111 @@ async function handleEdgesOperationTx( logger.info(`Batch removing ${ids.length} edges from workflow ${workflowId}`) - await tx - .delete(workflowEdges) + // Get edges to check connected blocks + const edgesToRemove = await tx + .select({ + id: workflowEdges.id, + sourceBlockId: workflowEdges.sourceBlockId, + targetBlockId: workflowEdges.targetBlockId, + }) + .from(workflowEdges) .where(and(eq(workflowEdges.workflowId, workflowId), inArray(workflowEdges.id, ids))) - logger.debug(`Batch removed ${ids.length} edges from workflow ${workflowId}`) + if (edgesToRemove.length === 0) { + logger.debug('No edges found to remove') + return + } + + type EdgeToRemove = (typeof edgesToRemove)[number] + + // Get all connected block IDs + const connectedBlockIds = new Set() + edgesToRemove.forEach((e: EdgeToRemove) => { + connectedBlockIds.add(e.sourceBlockId) + connectedBlockIds.add(e.targetBlockId) + }) + + // Fetch blocks to check lock status + const connectedBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, Array.from(connectedBlockIds)) + ) + ) + + type EdgeBlockRecord = (typeof connectedBlocks)[number] + const blocksById: Record = Object.fromEntries( + connectedBlocks.map((b: EdgeBlockRecord) => [b.id, b]) + ) + + // Collect parent IDs that need to be fetched + const parentIds = new Set() + for (const block of connectedBlocks) { + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && !blocksById[parentId]) { + parentIds.add(parentId) + } + } + + // Fetch parent blocks if needed + if (parentIds.size > 0) { + const parentBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, Array.from(parentIds)) + ) + ) + for (const b of parentBlocks) { + blocksById[b.id] = b + } + } + + const isBlockProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && blocksById[parentId]?.locked) return true + return false + } + + const safeEdgeIds = edgesToRemove + .filter( + (e: EdgeToRemove) => + !isBlockProtected(e.sourceBlockId) && !isBlockProtected(e.targetBlockId) + ) + .map((e: EdgeToRemove) => e.id) + + if (safeEdgeIds.length === 0) { + logger.info('All edges are connected to protected blocks, skipping removal') + return + } + + await tx + .delete(workflowEdges) + .where( + and(eq(workflowEdges.workflowId, workflowId), inArray(workflowEdges.id, safeEdgeIds)) + ) + + logger.debug(`Batch removed ${safeEdgeIds.length} edges from workflow ${workflowId}`) break } @@ -944,7 +1451,86 @@ async function handleEdgesOperationTx( logger.info(`Batch adding ${edges.length} edges to workflow ${workflowId}`) - const edgeValues = edges.map((edge: Record) => ({ + // Get all connected block IDs to check lock status + const connectedBlockIds = new Set() + edges.forEach((e: Record) => { + connectedBlockIds.add(e.source as string) + connectedBlockIds.add(e.target as string) + }) + + // Fetch blocks to check lock status + const connectedBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, Array.from(connectedBlockIds)) + ) + ) + + type AddEdgeBlockRecord = (typeof connectedBlocks)[number] + const blocksById: Record = Object.fromEntries( + connectedBlocks.map((b: AddEdgeBlockRecord) => [b.id, b]) + ) + + // Collect parent IDs that need to be fetched + const parentIds = new Set() + for (const block of connectedBlocks) { + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && !blocksById[parentId]) { + parentIds.add(parentId) + } + } + + // Fetch parent blocks if needed + if (parentIds.size > 0) { + const parentBlocks = await tx + .select({ + id: workflowBlocks.id, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) + .from(workflowBlocks) + .where( + and( + eq(workflowBlocks.workflowId, workflowId), + inArray(workflowBlocks.id, Array.from(parentIds)) + ) + ) + for (const b of parentBlocks) { + blocksById[b.id] = b + } + } + + const isBlockProtected = (blockId: string): boolean => { + const block = blocksById[blockId] + if (!block) return false + if (block.locked) return true + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId && blocksById[parentId]?.locked) return true + return false + } + + // Filter edges - only add edges where neither block is protected + const safeEdges = (edges as Array>).filter( + (e) => !isBlockProtected(e.source as string) && !isBlockProtected(e.target as string) + ) + + if (safeEdges.length === 0) { + logger.info('All edges connect to protected blocks, skipping add') + return + } + + const edgeValues = safeEdges.map((edge: Record) => ({ id: edge.id as string, workflowId, sourceBlockId: edge.source as string, @@ -955,7 +1541,7 @@ async function handleEdgesOperationTx( await tx.insert(workflowEdges).values(edgeValues) - logger.debug(`Batch added ${edges.length} edges to workflow ${workflowId}`) + logger.debug(`Batch added ${safeEdges.length} edges to workflow ${workflowId}`) break } @@ -1198,6 +1784,7 @@ async function handleWorkflowOperationTx( advancedMode: block.advancedMode ?? false, triggerMode: block.triggerMode ?? false, height: block.height || 0, + locked: block.locked ?? false, })) await tx.insert(workflowBlocks).values(blockValues) diff --git a/apps/sim/socket/handlers/subblocks.ts b/apps/sim/socket/handlers/subblocks.ts index 23896fed3..df2cac862 100644 --- a/apps/sim/socket/handlers/subblocks.ts +++ b/apps/sim/socket/handlers/subblocks.ts @@ -180,7 +180,11 @@ async function flushSubblockUpdate( let updateSuccessful = false await db.transaction(async (tx) => { const [block] = await tx - .select({ subBlocks: workflowBlocks.subBlocks }) + .select({ + subBlocks: workflowBlocks.subBlocks, + locked: workflowBlocks.locked, + data: workflowBlocks.data, + }) .from(workflowBlocks) .where(and(eq(workflowBlocks.id, blockId), eq(workflowBlocks.workflowId, workflowId))) .limit(1) @@ -189,6 +193,29 @@ async function flushSubblockUpdate( return } + // Check if block is locked directly + if (block.locked) { + logger.info(`Skipping subblock update - block ${blockId} is locked`) + return + } + + // Check if block is inside a locked parent container + const parentId = (block.data as Record | null)?.parentId as + | string + | undefined + if (parentId) { + const [parentBlock] = await tx + .select({ locked: workflowBlocks.locked }) + .from(workflowBlocks) + .where(and(eq(workflowBlocks.id, parentId), eq(workflowBlocks.workflowId, workflowId))) + .limit(1) + + if (parentBlock?.locked) { + logger.info(`Skipping subblock update - parent ${parentId} is locked`) + return + } + } + const subBlocks = (block.subBlocks as any) || {} if (!subBlocks[subblockId]) { subBlocks[subblockId] = { id: subblockId, type: 'unknown', value } diff --git a/apps/sim/socket/middleware/permissions.test.ts b/apps/sim/socket/middleware/permissions.test.ts index a7029163e..784d4ea7f 100644 --- a/apps/sim/socket/middleware/permissions.test.ts +++ b/apps/sim/socket/middleware/permissions.test.ts @@ -214,6 +214,12 @@ describe('checkRolePermission', () => { readAllowed: false, }, { operation: 'toggle-handles', adminAllowed: true, writeAllowed: true, readAllowed: false }, + { + operation: 'batch-toggle-locked', + adminAllowed: true, + writeAllowed: false, // Admin-only operation + readAllowed: false, + }, { operation: 'batch-update-positions', adminAllowed: true, diff --git a/apps/sim/socket/middleware/permissions.ts b/apps/sim/socket/middleware/permissions.ts index f3c574d20..b160a061b 100644 --- a/apps/sim/socket/middleware/permissions.ts +++ b/apps/sim/socket/middleware/permissions.ts @@ -14,7 +14,10 @@ import { const logger = createLogger('SocketPermissions') -// All write operations (admin and write roles have same permissions) +// Admin-only operations (require admin role) +const ADMIN_ONLY_OPERATIONS: string[] = [BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED] + +// Write operations (admin and write roles both have these permissions) const WRITE_OPERATIONS: string[] = [ // Block operations BLOCK_OPERATIONS.UPDATE_POSITION, @@ -51,7 +54,7 @@ const READ_OPERATIONS: string[] = [ // Define operation permissions based on role const ROLE_PERMISSIONS: Record = { - admin: WRITE_OPERATIONS, + admin: [...ADMIN_ONLY_OPERATIONS, ...WRITE_OPERATIONS], write: WRITE_OPERATIONS, read: READ_OPERATIONS, } diff --git a/apps/sim/socket/validation/schemas.ts b/apps/sim/socket/validation/schemas.ts index c6d14d4ef..fc48500ab 100644 --- a/apps/sim/socket/validation/schemas.ts +++ b/apps/sim/socket/validation/schemas.ts @@ -208,6 +208,17 @@ export const BatchToggleHandlesSchema = z.object({ operationId: z.string().optional(), }) +export const BatchToggleLockedSchema = z.object({ + operation: z.literal(BLOCKS_OPERATIONS.BATCH_TOGGLE_LOCKED), + target: z.literal(OPERATION_TARGETS.BLOCKS), + payload: z.object({ + blockIds: z.array(z.string()), + previousStates: z.record(z.boolean()), + }), + timestamp: z.number(), + operationId: z.string().optional(), +}) + export const BatchUpdateParentSchema = z.object({ operation: z.literal(BLOCKS_OPERATIONS.BATCH_UPDATE_PARENT), target: z.literal(OPERATION_TARGETS.BLOCKS), @@ -231,6 +242,7 @@ export const WorkflowOperationSchema = z.union([ BatchRemoveBlocksSchema, BatchToggleEnabledSchema, BatchToggleHandlesSchema, + BatchToggleLockedSchema, BatchUpdateParentSchema, EdgeOperationSchema, BatchAddEdgesSchema, diff --git a/apps/sim/stores/undo-redo/types.ts b/apps/sim/stores/undo-redo/types.ts index f68aa66e6..8d5df192f 100644 --- a/apps/sim/stores/undo-redo/types.ts +++ b/apps/sim/stores/undo-redo/types.ts @@ -97,6 +97,14 @@ export interface BatchToggleHandlesOperation extends BaseOperation { } } +export interface BatchToggleLockedOperation extends BaseOperation { + type: typeof UNDO_REDO_OPERATIONS.BATCH_TOGGLE_LOCKED + data: { + blockIds: string[] + previousStates: Record + } +} + export interface ApplyDiffOperation extends BaseOperation { type: typeof UNDO_REDO_OPERATIONS.APPLY_DIFF data: { @@ -136,6 +144,7 @@ export type Operation = | BatchUpdateParentOperation | BatchToggleEnabledOperation | BatchToggleHandlesOperation + | BatchToggleLockedOperation | ApplyDiffOperation | AcceptDiffOperation | RejectDiffOperation diff --git a/apps/sim/stores/undo-redo/utils.ts b/apps/sim/stores/undo-redo/utils.ts index 5a04579b4..d07225b3f 100644 --- a/apps/sim/stores/undo-redo/utils.ts +++ b/apps/sim/stores/undo-redo/utils.ts @@ -167,6 +167,15 @@ export function createInverseOperation(operation: Operation): Operation { }, } + case UNDO_REDO_OPERATIONS.BATCH_TOGGLE_LOCKED: + return { + ...operation, + data: { + blockIds: operation.data.blockIds, + previousStates: operation.data.previousStates, + }, + } + default: { const exhaustiveCheck: never = operation throw new Error(`Unhandled operation type: ${(exhaustiveCheck as Operation).type}`) diff --git a/apps/sim/stores/workflows/utils.test.ts b/apps/sim/stores/workflows/utils.test.ts index c82d1a712..4f2fc5165 100644 --- a/apps/sim/stores/workflows/utils.test.ts +++ b/apps/sim/stores/workflows/utils.test.ts @@ -432,4 +432,104 @@ describe('regenerateBlockIds', () => { expect(duplicatedBlock.position).toEqual({ x: 280, y: 70 }) expect(duplicatedBlock.data?.parentId).toBe(loopId) }) + + it('should unlock pasted block when source is locked', () => { + const blockId = 'block-1' + + const blocksToCopy = { + [blockId]: createAgentBlock({ + id: blockId, + name: 'Locked Agent', + position: { x: 100, y: 50 }, + locked: true, + }), + } + + const result = regenerateBlockIds( + blocksToCopy, + [], + {}, + {}, + {}, + positionOffset, + {}, + getUniqueBlockName + ) + + const newBlocks = Object.values(result.blocks) + expect(newBlocks).toHaveLength(1) + + // Pasted blocks are always unlocked so users can edit them + const pastedBlock = newBlocks[0] + expect(pastedBlock.locked).toBe(false) + }) + + it('should keep pasted block unlocked when source is unlocked', () => { + const blockId = 'block-1' + + const blocksToCopy = { + [blockId]: createAgentBlock({ + id: blockId, + name: 'Unlocked Agent', + position: { x: 100, y: 50 }, + locked: false, + }), + } + + const result = regenerateBlockIds( + blocksToCopy, + [], + {}, + {}, + {}, + positionOffset, + {}, + getUniqueBlockName + ) + + const newBlocks = Object.values(result.blocks) + expect(newBlocks).toHaveLength(1) + + const pastedBlock = newBlocks[0] + expect(pastedBlock.locked).toBe(false) + }) + + it('should unlock all pasted blocks regardless of source locked state', () => { + const lockedId = 'locked-1' + const unlockedId = 'unlocked-1' + + const blocksToCopy = { + [lockedId]: createAgentBlock({ + id: lockedId, + name: 'Originally Locked Agent', + position: { x: 100, y: 50 }, + locked: true, + }), + [unlockedId]: createFunctionBlock({ + id: unlockedId, + name: 'Originally Unlocked Function', + position: { x: 200, y: 50 }, + locked: false, + }), + } + + const result = regenerateBlockIds( + blocksToCopy, + [], + {}, + {}, + {}, + positionOffset, + {}, + getUniqueBlockName + ) + + const newBlocks = Object.values(result.blocks) + expect(newBlocks).toHaveLength(2) + + // All pasted blocks should be unlocked so users can edit them + for (const block of newBlocks) { + expect(block.locked).toBe(false) + } + }) }) diff --git a/apps/sim/stores/workflows/utils.ts b/apps/sim/stores/workflows/utils.ts index 22531fd4b..03039b7f8 100644 --- a/apps/sim/stores/workflows/utils.ts +++ b/apps/sim/stores/workflows/utils.ts @@ -203,6 +203,7 @@ export function prepareBlockState(options: PrepareBlockStateOptions): BlockState advancedMode: false, triggerMode, height: 0, + locked: false, } } @@ -481,6 +482,8 @@ export function regenerateBlockIds( position: newPosition, // Temporarily keep data as-is, we'll fix parentId in second pass data: block.data ? { ...block.data } : block.data, + // Duplicated blocks are always unlocked so users can edit them + locked: false, } newBlocks[newId] = newBlock @@ -508,15 +511,15 @@ export function regenerateBlockIds( parentId: newParentId, extent: 'parent', } - } else if (existingBlockNames[oldParentId]) { - // Parent exists in existing workflow - keep original parentId (block stays in same subflow) + } else if (existingBlockNames[oldParentId] && !existingBlockNames[oldParentId].locked) { + // Parent exists in existing workflow and is not locked - keep original parentId block.data = { ...block.data, parentId: oldParentId, extent: 'parent', } } else { - // Parent doesn't exist anywhere - clear the relationship + // Parent doesn't exist anywhere OR parent is locked - clear the relationship block.data = { ...block.data, parentId: undefined, extent: undefined } } } diff --git a/apps/sim/stores/workflows/workflow/store.test.ts b/apps/sim/stores/workflows/workflow/store.test.ts index a220db72a..d4814dcfd 100644 --- a/apps/sim/stores/workflows/workflow/store.test.ts +++ b/apps/sim/stores/workflows/workflow/store.test.ts @@ -1144,6 +1144,223 @@ describe('workflow store', () => { }) }) + describe('batchToggleLocked', () => { + it('should toggle block locked state', () => { + const { batchToggleLocked } = useWorkflowStore.getState() + + addBlock('block-1', 'function', 'Test', { x: 0, y: 0 }) + + // Initial state is undefined (falsy) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBeFalsy() + + batchToggleLocked(['block-1']) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBe(true) + + batchToggleLocked(['block-1']) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBe(false) + }) + + it('should cascade lock to children when locking a loop', () => { + const { batchToggleLocked } = useWorkflowStore.getState() + + addBlock('loop-1', 'loop', 'My Loop', { x: 0, y: 0 }, { loopType: 'for', count: 3 }) + addBlock( + 'child-1', + 'function', + 'Child', + { x: 50, y: 50 }, + { parentId: 'loop-1' }, + 'loop-1', + 'parent' + ) + + batchToggleLocked(['loop-1']) + + const { blocks } = useWorkflowStore.getState() + expect(blocks['loop-1'].locked).toBe(true) + expect(blocks['child-1'].locked).toBe(true) + }) + + it('should cascade unlock to children when unlocking a parallel', () => { + const { batchToggleLocked } = useWorkflowStore.getState() + + addBlock('parallel-1', 'parallel', 'My Parallel', { x: 0, y: 0 }, { count: 3 }) + addBlock( + 'child-1', + 'function', + 'Child', + { x: 50, y: 50 }, + { parentId: 'parallel-1' }, + 'parallel-1', + 'parent' + ) + + // Lock first + batchToggleLocked(['parallel-1']) + expect(useWorkflowStore.getState().blocks['child-1'].locked).toBe(true) + + // Unlock + batchToggleLocked(['parallel-1']) + + const { blocks } = useWorkflowStore.getState() + expect(blocks['parallel-1'].locked).toBe(false) + expect(blocks['child-1'].locked).toBe(false) + }) + + it('should toggle multiple blocks at once', () => { + const { batchToggleLocked } = useWorkflowStore.getState() + + addBlock('block-1', 'function', 'Test 1', { x: 0, y: 0 }) + addBlock('block-2', 'function', 'Test 2', { x: 100, y: 0 }) + + batchToggleLocked(['block-1', 'block-2']) + + const { blocks } = useWorkflowStore.getState() + expect(blocks['block-1'].locked).toBe(true) + expect(blocks['block-2'].locked).toBe(true) + }) + }) + + describe('setBlockLocked', () => { + it('should set block locked state', () => { + const { setBlockLocked } = useWorkflowStore.getState() + + addBlock('block-1', 'function', 'Test', { x: 0, y: 0 }) + + setBlockLocked('block-1', true) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBe(true) + + setBlockLocked('block-1', false) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBe(false) + }) + + it('should not update if locked state is already the target value', () => { + const { setBlockLocked } = useWorkflowStore.getState() + + addBlock('block-1', 'function', 'Test', { x: 0, y: 0 }) + + // First set to true + setBlockLocked('block-1', true) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBe(true) + + // Setting to true again should still be true + setBlockLocked('block-1', true) + expect(useWorkflowStore.getState().blocks['block-1'].locked).toBe(true) + }) + }) + + describe('duplicateBlock with locked', () => { + it('should unlock duplicate when duplicating a locked block', () => { + const { setBlockLocked, duplicateBlock } = useWorkflowStore.getState() + + addBlock('original', 'agent', 'Original Agent', { x: 0, y: 0 }) + setBlockLocked('original', true) + + expect(useWorkflowStore.getState().blocks.original.locked).toBe(true) + + duplicateBlock('original') + + const { blocks } = useWorkflowStore.getState() + const blockIds = Object.keys(blocks) + + expect(blockIds.length).toBe(2) + + const duplicatedId = blockIds.find((id) => id !== 'original') + expect(duplicatedId).toBeDefined() + + if (duplicatedId) { + // Original should still be locked + expect(blocks.original.locked).toBe(true) + // Duplicate should be unlocked so users can edit it + expect(blocks[duplicatedId].locked).toBe(false) + } + }) + + it('should create unlocked duplicate when duplicating an unlocked block', () => { + const { duplicateBlock } = useWorkflowStore.getState() + + addBlock('original', 'agent', 'Original Agent', { x: 0, y: 0 }) + + duplicateBlock('original') + + const { blocks } = useWorkflowStore.getState() + const blockIds = Object.keys(blocks) + const duplicatedId = blockIds.find((id) => id !== 'original') + + if (duplicatedId) { + expect(blocks[duplicatedId].locked).toBeFalsy() + } + }) + + it('should place duplicate outside locked container when duplicating block inside locked loop', () => { + const { batchToggleLocked, duplicateBlock } = useWorkflowStore.getState() + + // Create a loop with a child block + addBlock('loop-1', 'loop', 'My Loop', { x: 0, y: 0 }, { loopType: 'for', count: 3 }) + addBlock( + 'child-1', + 'function', + 'Child', + { x: 50, y: 50 }, + { parentId: 'loop-1' }, + 'loop-1', + 'parent' + ) + + // Lock the loop (which cascades to the child) + batchToggleLocked(['loop-1']) + expect(useWorkflowStore.getState().blocks['child-1'].locked).toBe(true) + + // Duplicate the child block + duplicateBlock('child-1') + + const { blocks } = useWorkflowStore.getState() + const blockIds = Object.keys(blocks) + + expect(blockIds.length).toBe(3) // loop, original child, duplicate + + const duplicatedId = blockIds.find((id) => id !== 'loop-1' && id !== 'child-1') + expect(duplicatedId).toBeDefined() + + if (duplicatedId) { + // Duplicate should be unlocked + expect(blocks[duplicatedId].locked).toBe(false) + // Duplicate should NOT have a parentId (placed outside the locked container) + expect(blocks[duplicatedId].data?.parentId).toBeUndefined() + // Original should still be inside the loop + expect(blocks['child-1'].data?.parentId).toBe('loop-1') + } + }) + + it('should keep duplicate inside unlocked container when duplicating block inside unlocked loop', () => { + const { duplicateBlock } = useWorkflowStore.getState() + + // Create a loop with a child block (not locked) + addBlock('loop-1', 'loop', 'My Loop', { x: 0, y: 0 }, { loopType: 'for', count: 3 }) + addBlock( + 'child-1', + 'function', + 'Child', + { x: 50, y: 50 }, + { parentId: 'loop-1' }, + 'loop-1', + 'parent' + ) + + // Duplicate the child block (loop is NOT locked) + duplicateBlock('child-1') + + const { blocks } = useWorkflowStore.getState() + const blockIds = Object.keys(blocks) + const duplicatedId = blockIds.find((id) => id !== 'loop-1' && id !== 'child-1') + + if (duplicatedId) { + // Duplicate should still be inside the loop since it's not locked + expect(blocks[duplicatedId].data?.parentId).toBe('loop-1') + } + }) + }) + describe('updateBlockName', () => { beforeEach(() => { useWorkflowStore.setState({ diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index 487deb39b..ed8db278f 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -207,6 +207,7 @@ export const useWorkflowStore = create()( triggerMode?: boolean height?: number data?: Record + locked?: boolean }>, edges?: Edge[], subBlockValues?: Record>, @@ -231,6 +232,7 @@ export const useWorkflowStore = create()( triggerMode: block.triggerMode ?? false, height: block.height ?? 0, data: block.data, + locked: block.locked ?? false, } } @@ -365,24 +367,69 @@ export const useWorkflowStore = create()( }, batchToggleEnabled: (ids: string[]) => { - const newBlocks = { ...get().blocks } + if (ids.length === 0) return + + const currentBlocks = get().blocks + const newBlocks = { ...currentBlocks } + const blocksToToggle = new Set() + + // For each ID, collect blocks to toggle (skip locked blocks entirely) + // If it's a container, also include non-locked children for (const id of ids) { - if (newBlocks[id]) { - newBlocks[id] = { ...newBlocks[id], enabled: !newBlocks[id].enabled } + const block = currentBlocks[id] + if (!block) continue + + // Skip locked blocks entirely (including their children) + if (block.locked) continue + + blocksToToggle.add(id) + + // If it's a loop or parallel, also include non-locked children + if (block.type === 'loop' || block.type === 'parallel') { + Object.entries(currentBlocks).forEach(([blockId, b]) => { + if (b.data?.parentId === id && !b.locked) { + blocksToToggle.add(blockId) + } + }) } } + + // If no blocks can be toggled, exit early + if (blocksToToggle.size === 0) return + + // Determine target enabled state based on first toggleable block + const firstToggleableId = Array.from(blocksToToggle)[0] + const firstBlock = currentBlocks[firstToggleableId] + const targetEnabled = !firstBlock.enabled + + // Apply the enabled state to all toggleable blocks + for (const blockId of blocksToToggle) { + newBlocks[blockId] = { ...newBlocks[blockId], enabled: targetEnabled } + } + set({ blocks: newBlocks, edges: [...get().edges] }) get().updateLastSaved() }, batchToggleHandles: (ids: string[]) => { - const newBlocks = { ...get().blocks } + const currentBlocks = get().blocks + const newBlocks = { ...currentBlocks } + + // Helper to check if a block is protected (locked or inside locked parent) + const isProtected = (blockId: string): boolean => { + const block = currentBlocks[blockId] + if (!block) return false + if (block.locked) return true + const parentId = block.data?.parentId + if (parentId && currentBlocks[parentId]?.locked) return true + return false + } + for (const id of ids) { - if (newBlocks[id]) { - newBlocks[id] = { - ...newBlocks[id], - horizontalHandles: !newBlocks[id].horizontalHandles, - } + if (!newBlocks[id] || isProtected(id)) continue + newBlocks[id] = { + ...newBlocks[id], + horizontalHandles: !newBlocks[id].horizontalHandles, } } set({ blocks: newBlocks, edges: [...get().edges] }) @@ -527,9 +574,33 @@ export const useWorkflowStore = create()( if (!block) return const newId = crypto.randomUUID() - const offsetPosition = { - x: block.position.x + DEFAULT_DUPLICATE_OFFSET.x, - y: block.position.y + DEFAULT_DUPLICATE_OFFSET.y, + + // Check if block is inside a locked container - if so, place duplicate outside + const parentId = block.data?.parentId + const parentBlock = parentId ? get().blocks[parentId] : undefined + const isParentLocked = parentBlock?.locked ?? false + + // If parent is locked, calculate position outside the container + let offsetPosition: Position + const newData = block.data ? { ...block.data } : undefined + + if (isParentLocked && parentBlock) { + // Place duplicate outside the locked container (to the right of it) + const containerWidth = parentBlock.data?.width ?? 400 + offsetPosition = { + x: parentBlock.position.x + containerWidth + 50, + y: parentBlock.position.y, + } + // Remove parent relationship since we're placing outside + if (newData) { + newData.parentId = undefined + newData.extent = undefined + } + } else { + offsetPosition = { + x: block.position.x + DEFAULT_DUPLICATE_OFFSET.x, + y: block.position.y + DEFAULT_DUPLICATE_OFFSET.y, + } } const newName = getUniqueBlockName(block.name, get().blocks) @@ -557,6 +628,8 @@ export const useWorkflowStore = create()( name: newName, position: offsetPosition, subBlocks: newSubBlocks, + locked: false, + data: newData, }, }, edges: [...get().edges], @@ -1164,6 +1237,70 @@ export const useWorkflowStore = create()( getDragStartPosition: () => { return get().dragStartPosition || null }, + + setBlockLocked: (id: string, locked: boolean) => { + const block = get().blocks[id] + if (!block || block.locked === locked) return + + const newState = { + blocks: { + ...get().blocks, + [id]: { + ...block, + locked, + }, + }, + edges: [...get().edges], + loops: { ...get().loops }, + parallels: { ...get().parallels }, + } + + set(newState) + get().updateLastSaved() + }, + + batchToggleLocked: (ids: string[]) => { + if (ids.length === 0) return + + const currentBlocks = get().blocks + const newBlocks = { ...currentBlocks } + const blocksToToggle = new Set() + + // For each ID, collect blocks to toggle + // If it's a container, also include all children + for (const id of ids) { + const block = currentBlocks[id] + if (!block) continue + + blocksToToggle.add(id) + + // If it's a loop or parallel, also include all children + if (block.type === 'loop' || block.type === 'parallel') { + Object.entries(currentBlocks).forEach(([blockId, b]) => { + if (b.data?.parentId === id) { + blocksToToggle.add(blockId) + } + }) + } + } + + // If no blocks found, exit early + if (blocksToToggle.size === 0) return + + // Determine target locked state based on first block in original ids + const firstBlock = currentBlocks[ids[0]] + if (!firstBlock) return + + const targetLocked = !firstBlock.locked + + // Apply the locked state to all blocks + for (const blockId of blocksToToggle) { + newBlocks[blockId] = { ...newBlocks[blockId], locked: targetLocked } + } + + set({ blocks: newBlocks, edges: [...get().edges] }) + get().updateLastSaved() + }, }), { name: 'workflow-store' } ) diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index 7f58d6d3e..ebf7734da 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -87,6 +87,7 @@ export interface BlockState { triggerMode?: boolean data?: BlockData layout?: BlockLayoutState + locked?: boolean } export interface SubBlockState { @@ -131,6 +132,7 @@ export interface Loop { whileCondition?: string // JS expression that evaluates to boolean (for while loops) doWhileCondition?: string // JS expression that evaluates to boolean (for do-while loops) enabled: boolean + locked?: boolean } export interface Parallel { @@ -140,6 +142,7 @@ export interface Parallel { count?: number // Number of parallel executions for count-based parallel parallelType?: 'count' | 'collection' // Explicit parallel type to avoid inference bugs enabled: boolean + locked?: boolean } export interface Variable { @@ -233,6 +236,8 @@ export interface WorkflowActions { workflowState: WorkflowState, options?: { updateLastSaved?: boolean } ) => void + setBlockLocked: (id: string, locked: boolean) => void + batchToggleLocked: (ids: string[]) => void } export type WorkflowStore = WorkflowState & WorkflowActions diff --git a/packages/db/migrations/0150_flimsy_hemingway.sql b/packages/db/migrations/0150_flimsy_hemingway.sql new file mode 100644 index 000000000..4cca53db3 --- /dev/null +++ b/packages/db/migrations/0150_flimsy_hemingway.sql @@ -0,0 +1 @@ +ALTER TABLE "workflow_blocks" ADD COLUMN "locked" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/packages/db/migrations/meta/0150_snapshot.json b/packages/db/migrations/meta/0150_snapshot.json new file mode 100644 index 000000000..a8a31ca61 --- /dev/null +++ b/packages/db/migrations/meta/0150_snapshot.json @@ -0,0 +1,10354 @@ +{ + "id": "441fdc43-5739-4294-9a60-97b1778f910d", + "prevId": "ae520ea1-0b55-4436-9010-327d904480fb", + "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 + }, + "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_workspace_id_idx": { + "name": "a2a_agent_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "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_id_idx": { + "name": "a2a_push_notification_config_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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.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": {} + }, + "account_user_provider_unique": { + "name": "account_user_provider_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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 + }, + "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": {} + } + }, + "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.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": "'[]'" + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.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_organization_id_idx": { + "name": "credential_set_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_set_id_idx": { + "name": "credential_set_member_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "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_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" + } + }, + "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 + }, + "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, + "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": {} + } + }, + "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 + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "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": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 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": {} + } + }, + "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.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_idx": { + "name": "mcp_servers_workspace_deleted_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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": {} + } + }, + "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.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 + }, + "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.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 + } + }, + "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": {} + } + }, + "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.permission_group": { + "name": "permission_group", + "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 + }, + "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_organization_id_idx": { + "name": "permission_group_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_org_name_unique": { + "name": "permission_group_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": {} + }, + "permission_group_org_auto_add_unique": { + "name": "permission_group_org_auto_add_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "auto_add_new_members = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_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 + }, + "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_user_id_unique": { + "name": "permission_group_member_user_id_unique", + "columns": [ + { + "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_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 + } + }, + "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": "'dark'" + }, + "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 + }, + "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": "'[]'" + }, + "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.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.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": {} + } + }, + "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 + }, + "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 + }, + "is_super_user": { + "name": "is_super_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["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": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'20'" + }, + "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'" + }, + "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": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "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.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 + }, + "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, + "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": {} + } + }, + "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 + }, + "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": "'{}'" + } + }, + "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_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": {} + } + }, + "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": {} + } + }, + "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 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_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 + }, + "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": {} + } + }, + "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": "'{}'" + }, + "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, + "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": 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 + }, + "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 + }, + "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, + "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": {} + } + }, + "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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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 + }, + "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": {} + } + }, + "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_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 + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "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_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_files_key_unique": { + "name": "workspace_files_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_invitation": { + "name": "workspace_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "workspace_invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "org_invitation_id": { + "name": "org_invitation_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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invitation_workspace_id_workspace_id_fk": { + "name": "workspace_invitation_workspace_id_workspace_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invitation_inviter_id_user_id_fk": { + "name": "workspace_invitation_inviter_id_user_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invitation_token_unique": { + "name": "workspace_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "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.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "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.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"] + }, + "public.workspace_invitation_status": { + "name": "workspace_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled"] + } + }, + "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 8d95b78ff..00b3bf160 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1044,6 +1044,13 @@ "when": 1769656977701, "tag": "0149_next_cerise", "breakpoints": true + }, + { + "idx": 150, + "version": "7", + "when": 1769897862156, + "tag": "0150_flimsy_hemingway", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 5c3b8eb9e..6517af8e3 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -189,6 +189,7 @@ export const workflowBlocks = pgTable( isWide: boolean('is_wide').notNull().default(false), advancedMode: boolean('advanced_mode').notNull().default(false), triggerMode: boolean('trigger_mode').notNull().default(false), + locked: boolean('locked').notNull().default(false), height: decimal('height').notNull().default('0'), subBlocks: jsonb('sub_blocks').notNull().default('{}'), diff --git a/packages/testing/src/factories/block.factory.ts b/packages/testing/src/factories/block.factory.ts index 8ef6907f5..1020f651f 100644 --- a/packages/testing/src/factories/block.factory.ts +++ b/packages/testing/src/factories/block.factory.ts @@ -21,6 +21,7 @@ export interface BlockFactoryOptions { triggerMode?: boolean data?: BlockData parentId?: string + locked?: boolean } /** @@ -67,6 +68,7 @@ export function createBlock(options: BlockFactoryOptions = {}): any { height: options.height ?? 0, advancedMode: options.advancedMode ?? false, triggerMode: options.triggerMode ?? false, + locked: options.locked ?? false, data: Object.keys(data).length > 0 ? data : undefined, layout: {}, }