@@ -1138,6 +1138,42 @@ async function handleEdgeOperationTx(tx: any, workflowId: string, operation: str
11381138 throw new Error ( 'Missing required fields for add edge operation' )
11391139 }
11401140
1141+ // Check if source or target blocks are protected (locked or inside locked parent)
1142+ const edgeBlocks = await tx
1143+ . select ( {
1144+ id : workflowBlocks . id ,
1145+ locked : workflowBlocks . locked ,
1146+ data : workflowBlocks . data ,
1147+ } )
1148+ . from ( workflowBlocks )
1149+ . where (
1150+ and (
1151+ eq ( workflowBlocks . workflowId , workflowId ) ,
1152+ inArray ( workflowBlocks . id , [ payload . source , payload . target ] )
1153+ )
1154+ )
1155+
1156+ type EdgeBlockRecord = ( typeof edgeBlocks ) [ number ]
1157+ const blocksById : Record < string , EdgeBlockRecord > = Object . fromEntries (
1158+ edgeBlocks . map ( ( b : EdgeBlockRecord ) => [ b . id , b ] )
1159+ )
1160+
1161+ const isBlockProtected = ( blockId : string ) : boolean => {
1162+ const block = blocksById [ blockId ]
1163+ if ( ! block ) return false
1164+ if ( block . locked ) return true
1165+ const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
1166+ | string
1167+ | undefined
1168+ if ( parentId && blocksById [ parentId ] ?. locked ) return true
1169+ return false
1170+ }
1171+
1172+ if ( isBlockProtected ( payload . source ) || isBlockProtected ( payload . target ) ) {
1173+ logger . info ( `Skipping edge add - source or target block is protected` )
1174+ break
1175+ }
1176+
11411177 await tx . insert ( workflowEdges ) . values ( {
11421178 id : payload . id ,
11431179 workflowId,
@@ -1156,15 +1192,63 @@ async function handleEdgeOperationTx(tx: any, workflowId: string, operation: str
11561192 throw new Error ( 'Missing edge ID for remove operation' )
11571193 }
11581194
1159- const deleteResult = await tx
1160- . delete ( workflowEdges )
1195+ // Get the edge to check if connected blocks are protected
1196+ const [ edgeToRemove ] = await tx
1197+ . select ( {
1198+ sourceBlockId : workflowEdges . sourceBlockId ,
1199+ targetBlockId : workflowEdges . targetBlockId ,
1200+ } )
1201+ . from ( workflowEdges )
11611202 . where ( and ( eq ( workflowEdges . id , payload . id ) , eq ( workflowEdges . workflowId , workflowId ) ) )
1162- . returning ( { id : workflowEdges . id } )
1203+ . limit ( 1 )
11631204
1164- if ( deleteResult . length === 0 ) {
1205+ if ( ! edgeToRemove ) {
11651206 throw new Error ( `Edge ${ payload . id } not found in workflow ${ workflowId } ` )
11661207 }
11671208
1209+ // Check if source or target blocks are protected
1210+ const connectedBlocks = await tx
1211+ . select ( {
1212+ id : workflowBlocks . id ,
1213+ locked : workflowBlocks . locked ,
1214+ data : workflowBlocks . data ,
1215+ } )
1216+ . from ( workflowBlocks )
1217+ . where (
1218+ and (
1219+ eq ( workflowBlocks . workflowId , workflowId ) ,
1220+ inArray ( workflowBlocks . id , [ edgeToRemove . sourceBlockId , edgeToRemove . targetBlockId ] )
1221+ )
1222+ )
1223+
1224+ type RemoveEdgeBlockRecord = ( typeof connectedBlocks ) [ number ]
1225+ const blocksById : Record < string , RemoveEdgeBlockRecord > = Object . fromEntries (
1226+ connectedBlocks . map ( ( b : RemoveEdgeBlockRecord ) => [ b . id , b ] )
1227+ )
1228+
1229+ const isBlockProtected = ( blockId : string ) : boolean => {
1230+ const block = blocksById [ blockId ]
1231+ if ( ! block ) return false
1232+ if ( block . locked ) return true
1233+ const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
1234+ | string
1235+ | undefined
1236+ if ( parentId && blocksById [ parentId ] ?. locked ) return true
1237+ return false
1238+ }
1239+
1240+ if (
1241+ isBlockProtected ( edgeToRemove . sourceBlockId ) ||
1242+ isBlockProtected ( edgeToRemove . targetBlockId )
1243+ ) {
1244+ logger . info ( `Skipping edge remove - source or target block is protected` )
1245+ break
1246+ }
1247+
1248+ await tx
1249+ . delete ( workflowEdges )
1250+ . where ( and ( eq ( workflowEdges . id , payload . id ) , eq ( workflowEdges . workflowId , workflowId ) ) )
1251+
11681252 logger . debug ( `Removed edge ${ payload . id } from workflow ${ workflowId } ` )
11691253 break
11701254 }
@@ -1191,11 +1275,80 @@ async function handleEdgesOperationTx(
11911275
11921276 logger . info ( `Batch removing ${ ids . length } edges from workflow ${ workflowId } ` )
11931277
1278+ // Get edges to check connected blocks
1279+ const edgesToRemove = await tx
1280+ . select ( {
1281+ id : workflowEdges . id ,
1282+ sourceBlockId : workflowEdges . sourceBlockId ,
1283+ targetBlockId : workflowEdges . targetBlockId ,
1284+ } )
1285+ . from ( workflowEdges )
1286+ . where ( and ( eq ( workflowEdges . workflowId , workflowId ) , inArray ( workflowEdges . id , ids ) ) )
1287+
1288+ if ( edgesToRemove . length === 0 ) {
1289+ logger . debug ( 'No edges found to remove' )
1290+ return
1291+ }
1292+
1293+ type EdgeToRemove = ( typeof edgesToRemove ) [ number ]
1294+
1295+ // Get all connected block IDs
1296+ const connectedBlockIds = new Set < string > ( )
1297+ edgesToRemove . forEach ( ( e : EdgeToRemove ) => {
1298+ connectedBlockIds . add ( e . sourceBlockId )
1299+ connectedBlockIds . add ( e . targetBlockId )
1300+ } )
1301+
1302+ // Fetch blocks to check lock status
1303+ const connectedBlocks = await tx
1304+ . select ( {
1305+ id : workflowBlocks . id ,
1306+ locked : workflowBlocks . locked ,
1307+ data : workflowBlocks . data ,
1308+ } )
1309+ . from ( workflowBlocks )
1310+ . where (
1311+ and (
1312+ eq ( workflowBlocks . workflowId , workflowId ) ,
1313+ inArray ( workflowBlocks . id , Array . from ( connectedBlockIds ) )
1314+ )
1315+ )
1316+
1317+ type EdgeBlockRecord = ( typeof connectedBlocks ) [ number ]
1318+ const blocksById : Record < string , EdgeBlockRecord > = Object . fromEntries (
1319+ connectedBlocks . map ( ( b : EdgeBlockRecord ) => [ b . id , b ] )
1320+ )
1321+
1322+ const isBlockProtected = ( blockId : string ) : boolean => {
1323+ const block = blocksById [ blockId ]
1324+ if ( ! block ) return false
1325+ if ( block . locked ) return true
1326+ const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
1327+ | string
1328+ | undefined
1329+ if ( parentId && blocksById [ parentId ] ?. locked ) return true
1330+ return false
1331+ }
1332+
1333+ const safeEdgeIds = edgesToRemove
1334+ . filter (
1335+ ( e : EdgeToRemove ) =>
1336+ ! isBlockProtected ( e . sourceBlockId ) && ! isBlockProtected ( e . targetBlockId )
1337+ )
1338+ . map ( ( e : EdgeToRemove ) => e . id )
1339+
1340+ if ( safeEdgeIds . length === 0 ) {
1341+ logger . info ( 'All edges are connected to protected blocks, skipping removal' )
1342+ return
1343+ }
1344+
11941345 await tx
11951346 . delete ( workflowEdges )
1196- . where ( and ( eq ( workflowEdges . workflowId , workflowId ) , inArray ( workflowEdges . id , ids ) ) )
1347+ . where (
1348+ and ( eq ( workflowEdges . workflowId , workflowId ) , inArray ( workflowEdges . id , safeEdgeIds ) )
1349+ )
11971350
1198- logger . debug ( `Batch removed ${ ids . length } edges from workflow ${ workflowId } ` )
1351+ logger . debug ( `Batch removed ${ safeEdgeIds . length } edges from workflow ${ workflowId } ` )
11991352 break
12001353 }
12011354
@@ -1208,7 +1361,55 @@ async function handleEdgesOperationTx(
12081361
12091362 logger . info ( `Batch adding ${ edges . length } edges to workflow ${ workflowId } ` )
12101363
1211- const edgeValues = edges . map ( ( edge : Record < string , unknown > ) => ( {
1364+ // Get all connected block IDs to check lock status
1365+ const connectedBlockIds = new Set < string > ( )
1366+ edges . forEach ( ( e : Record < string , unknown > ) => {
1367+ connectedBlockIds . add ( e . source as string )
1368+ connectedBlockIds . add ( e . target as string )
1369+ } )
1370+
1371+ // Fetch blocks to check lock status
1372+ const connectedBlocks = await tx
1373+ . select ( {
1374+ id : workflowBlocks . id ,
1375+ locked : workflowBlocks . locked ,
1376+ data : workflowBlocks . data ,
1377+ } )
1378+ . from ( workflowBlocks )
1379+ . where (
1380+ and (
1381+ eq ( workflowBlocks . workflowId , workflowId ) ,
1382+ inArray ( workflowBlocks . id , Array . from ( connectedBlockIds ) )
1383+ )
1384+ )
1385+
1386+ type AddEdgeBlockRecord = ( typeof connectedBlocks ) [ number ]
1387+ const blocksById : Record < string , AddEdgeBlockRecord > = Object . fromEntries (
1388+ connectedBlocks . map ( ( b : AddEdgeBlockRecord ) => [ b . id , b ] )
1389+ )
1390+
1391+ const isBlockProtected = ( blockId : string ) : boolean => {
1392+ const block = blocksById [ blockId ]
1393+ if ( ! block ) return false
1394+ if ( block . locked ) return true
1395+ const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
1396+ | string
1397+ | undefined
1398+ if ( parentId && blocksById [ parentId ] ?. locked ) return true
1399+ return false
1400+ }
1401+
1402+ // Filter edges - only add edges where neither block is protected
1403+ const safeEdges = ( edges as Array < Record < string , unknown > > ) . filter (
1404+ ( e ) => ! isBlockProtected ( e . source as string ) && ! isBlockProtected ( e . target as string )
1405+ )
1406+
1407+ if ( safeEdges . length === 0 ) {
1408+ logger . info ( 'All edges connect to protected blocks, skipping add' )
1409+ return
1410+ }
1411+
1412+ const edgeValues = safeEdges . map ( ( edge : Record < string , unknown > ) => ( {
12121413 id : edge . id as string ,
12131414 workflowId,
12141415 sourceBlockId : edge . source as string ,
@@ -1219,7 +1420,7 @@ async function handleEdgesOperationTx(
12191420
12201421 await tx . insert ( workflowEdges ) . values ( edgeValues )
12211422
1222- logger . debug ( `Batch added ${ edges . length } edges to workflow ${ workflowId } ` )
1423+ logger . debug ( `Batch added ${ safeEdges . length } edges to workflow ${ workflowId } ` )
12231424 break
12241425 }
12251426
0 commit comments