@@ -16,6 +16,7 @@ import {
1616import { getActiveWorkflowContext } from '@sim/workflow-authz'
1717import { loadWorkflowFromNormalizedTablesRaw } from '@sim/workflow-persistence/load'
1818import { mergeSubBlockValues } from '@sim/workflow-persistence/subblocks'
19+ import { isWorkflowBlockProtected } from '@sim/workflow-types/workflow'
1920import { and , eq , inArray , isNull , or , sql } from 'drizzle-orm'
2021import { drizzle } from 'drizzle-orm/postgres-js'
2122import postgres from 'postgres'
@@ -47,26 +48,6 @@ interface DbBlockRef {
4748 data : unknown
4849}
4950
50- /**
51- * Checks if a block is protected (locked or inside a locked ancestor).
52- * Works with raw DB records.
53- */
54- function isDbBlockProtected ( blockId : string , blocksById : Record < string , DbBlockRef > ) : boolean {
55- const block = blocksById [ blockId ]
56- if ( ! block ) return false
57- if ( block . locked ) return true
58- const visited = new Set < string > ( )
59- let parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as string | undefined
60- while ( parentId && ! visited . has ( parentId ) ) {
61- visited . add ( parentId )
62- if ( blocksById [ parentId ] ?. locked ) return true
63- parentId = ( blocksById [ parentId ] ?. data as Record < string , unknown > | null ) ?. parentId as
64- | string
65- | undefined
66- }
67- return false
68- }
69-
7051/**
7152 * Finds all descendant block IDs of a container (recursive).
7253 * Works with raw DB block arrays.
@@ -880,7 +861,7 @@ async function handleBlocksOperationTx(
880861 )
881862
882863 // Filter out protected blocks from deletion request
883- const deletableIds = ids . filter ( ( id ) => ! isDbBlockProtected ( id , blocksById ) )
864+ const deletableIds = ids . filter ( ( id ) => ! isWorkflowBlockProtected ( id , blocksById ) )
884865 if ( deletableIds . length === 0 ) {
885866 logger . info ( 'All requested blocks are protected, skipping deletion' )
886867 return
@@ -995,14 +976,14 @@ async function handleBlocksOperationTx(
995976 // Collect all blocks to toggle including descendants of containers
996977 for ( const id of blockIds ) {
997978 const block = blocksById [ id ]
998- if ( ! block || isDbBlockProtected ( id , blocksById ) ) continue
979+ if ( ! block || isWorkflowBlockProtected ( id , blocksById ) ) continue
999980
1000981 blocksToToggle . add ( id )
1001982
1002983 // If it's a loop or parallel, also include all non-locked descendants
1003984 if ( block . type === 'loop' || block . type === 'parallel' ) {
1004985 for ( const descId of findDbDescendants ( id , allBlocks ) ) {
1005- if ( ! isDbBlockProtected ( descId , blocksById ) ) {
986+ if ( ! isWorkflowBlockProtected ( descId , blocksById ) ) {
1006987 blocksToToggle . add ( descId )
1007988 }
1008989 }
@@ -1057,7 +1038,7 @@ async function handleBlocksOperationTx(
10571038
10581039 // Filter to only toggle handles on unprotected blocks
10591040 const blocksToToggle = blockIds . filter (
1060- ( id ) => blocksById [ id ] && ! isDbBlockProtected ( id , blocksById )
1041+ ( id ) => blocksById [ id ] && ! isWorkflowBlockProtected ( id , blocksById )
10611042 )
10621043 if ( blocksToToggle . length === 0 ) {
10631044 logger . info ( 'All requested blocks are protected, skipping handles toggle' )
@@ -1169,13 +1150,13 @@ async function handleBlocksOperationTx(
11691150 if ( ! id ) continue
11701151
11711152 // Skip protected blocks (locked or inside locked container)
1172- if ( isDbBlockProtected ( id , blocksById ) ) {
1153+ if ( isWorkflowBlockProtected ( id , blocksById ) ) {
11731154 logger . info ( `Skipping block ${ id } parent update - block is protected` )
11741155 continue
11751156 }
11761157
11771158 // Skip if trying to move into a locked container (or any of its ancestors)
1178- if ( parentId && isDbBlockProtected ( parentId , blocksById ) ) {
1159+ if ( parentId && isWorkflowBlockProtected ( parentId , blocksById ) ) {
11791160 logger . info ( `Skipping block ${ id } parent update - target parent ${ parentId } is protected` )
11801161 continue
11811162 }
@@ -1299,7 +1280,7 @@ async function handleEdgeOperationTx(tx: any, workflowId: string, operation: str
12991280 }
13001281 }
13011282
1302- if ( isDbBlockProtected ( payload . target , blocksById ) ) {
1283+ if ( isWorkflowBlockProtected ( payload . target , blocksById ) ) {
13031284 logger . info ( `Skipping edge add - target block is protected` )
13041285 break
13051286 }
@@ -1387,7 +1368,7 @@ async function handleEdgeOperationTx(tx: any, workflowId: string, operation: str
13871368 }
13881369 }
13891370
1390- if ( isDbBlockProtected ( edgeToRemove . targetBlockId , blocksById ) ) {
1371+ if ( isWorkflowBlockProtected ( edgeToRemove . targetBlockId , blocksById ) ) {
13911372 logger . info ( `Skipping edge remove - target block is protected` )
13921373 break
13931374 }
@@ -1498,7 +1479,7 @@ async function handleEdgesOperationTx(
14981479 }
14991480
15001481 const safeEdgeIds = edgesToRemove
1501- . filter ( ( e : EdgeToRemove ) => ! isDbBlockProtected ( e . targetBlockId , blocksById ) )
1482+ . filter ( ( e : EdgeToRemove ) => ! isWorkflowBlockProtected ( e . targetBlockId , blocksById ) )
15021483 . map ( ( e : EdgeToRemove ) => e . id )
15031484
15041485 if ( safeEdgeIds . length === 0 ) {
@@ -1585,7 +1566,7 @@ async function handleEdgesOperationTx(
15851566
15861567 // Filter edges - only add edges where target block is not protected
15871568 const safeEdges = ( edges as Array < Record < string , unknown > > ) . filter (
1588- ( e ) => ! isDbBlockProtected ( e . target as string , blocksById )
1569+ ( e ) => ! isWorkflowBlockProtected ( e . target as string , blocksById )
15891570 )
15901571
15911572 if ( safeEdges . length === 0 ) {
@@ -1756,43 +1737,34 @@ async function handleSubblockOperationTx(
17561737 return
17571738 }
17581739
1740+ const allBlocks = await tx
1741+ . select ( {
1742+ id : workflowBlocks . id ,
1743+ subBlocks : workflowBlocks . subBlocks ,
1744+ locked : workflowBlocks . locked ,
1745+ data : workflowBlocks . data ,
1746+ } )
1747+ . from ( workflowBlocks )
1748+ . where ( eq ( workflowBlocks . workflowId , workflowId ) )
1749+
1750+ type SubblockUpdateBlockRecord = ( typeof allBlocks ) [ number ]
1751+ const blocksById : Record < string , SubblockUpdateBlockRecord > = Object . fromEntries (
1752+ allBlocks . map ( ( block : SubblockUpdateBlockRecord ) => [ block . id , block ] )
1753+ )
1754+
17591755 for ( const update of updates ) {
17601756 const { blockId, subblockId, value, expectedValue } = update
17611757 if ( ! blockId || ! subblockId ) {
17621758 throw new Error ( 'Missing required fields for subblock batch update' )
17631759 }
17641760
1765- const [ block ] = await tx
1766- . select ( {
1767- subBlocks : workflowBlocks . subBlocks ,
1768- locked : workflowBlocks . locked ,
1769- data : workflowBlocks . data ,
1770- } )
1771- . from ( workflowBlocks )
1772- . where ( and ( eq ( workflowBlocks . id , blockId ) , eq ( workflowBlocks . workflowId , workflowId ) ) )
1773- . limit ( 1 )
1774-
1761+ const block = blocksById [ blockId ]
17751762 if ( ! block ) {
17761763 throw new Error ( `Block ${ blockId } not found` )
17771764 }
17781765
1779- if ( block . locked ) {
1780- throw new Error ( `Block ${ blockId } is locked` )
1781- }
1782-
1783- const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
1784- | string
1785- | undefined
1786- if ( parentId ) {
1787- const [ parentBlock ] = await tx
1788- . select ( { locked : workflowBlocks . locked } )
1789- . from ( workflowBlocks )
1790- . where ( and ( eq ( workflowBlocks . id , parentId ) , eq ( workflowBlocks . workflowId , workflowId ) ) )
1791- . limit ( 1 )
1792-
1793- if ( parentBlock ?. locked ) {
1794- throw new Error ( `Parent block ${ parentId } is locked` )
1795- }
1766+ if ( isWorkflowBlockProtected ( blockId , blocksById ) ) {
1767+ throw new Error ( `Block ${ blockId } is locked or inside a locked container` )
17961768 }
17971769
17981770 const subBlocks = { ...( ( block . subBlocks as Record < string , any > ) || { } ) }
@@ -1813,6 +1785,8 @@ async function handleSubblockOperationTx(
18131785 updatedAt : new Date ( ) ,
18141786 } )
18151787 . where ( and ( eq ( workflowBlocks . id , blockId ) , eq ( workflowBlocks . workflowId , workflowId ) ) )
1788+
1789+ blocksById [ blockId ] = { ...block , subBlocks }
18161790 }
18171791
18181792 logger . debug ( `Batch updated ${ updates . length } subblocks for workflow ${ workflowId } ` )
0 commit comments