(workflowState: WorkflowState)
| 12 | } |
| 13 | |
| 14 | export function normalizeWorkflowState(workflowState: WorkflowState): NormalizationResult { |
| 15 | const warnings: string[] = [] |
| 16 | const blocks = structuredClone(workflowState.blocks || {}) |
| 17 | |
| 18 | for (const [blockId, block] of Object.entries(blocks)) { |
| 19 | if (!block?.type || !block?.name) { |
| 20 | warnings.push(`Dropped invalid block "${blockId}" because it is missing type or name`) |
| 21 | delete blocks[blockId] |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | for (const [blockId, block] of Object.entries(blocks)) { |
| 26 | const parentId = block.data?.parentId |
| 27 | if (!parentId) { |
| 28 | continue |
| 29 | } |
| 30 | |
| 31 | const parentBlock = blocks[parentId] |
| 32 | const parentIsValidContainer = Boolean(parentBlock && isContainerType(parentBlock.type)) |
| 33 | |
| 34 | if (!parentIsValidContainer || parentId === blockId) { |
| 35 | warnings.push(`Cleared invalid parentId for block "${blockId}"`) |
| 36 | block.data = { |
| 37 | ...(block.data || {}), |
| 38 | parentId: undefined, |
| 39 | extent: undefined, |
| 40 | } |
| 41 | continue |
| 42 | } |
| 43 | |
| 44 | if (block.data?.extent !== 'parent') { |
| 45 | block.data = { |
| 46 | ...(block.data || {}), |
| 47 | extent: 'parent', |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | const edgeValidation = validateEdges(workflowState.edges || [], blocks) |
| 53 | warnings.push( |
| 54 | ...edgeValidation.dropped.map(({ edge, reason }) => `Dropped edge "${edge.id}": ${reason}`) |
| 55 | ) |
| 56 | |
| 57 | return { |
| 58 | state: { |
| 59 | ...workflowState, |
| 60 | blocks, |
| 61 | edges: edgeValidation.valid, |
| 62 | loops: generateLoopBlocks(blocks), |
| 63 | parallels: generateParallelBlocks(blocks), |
| 64 | }, |
| 65 | warnings, |
| 66 | } |
| 67 | } |
no test coverage detected