From 2de386803aa357040eb99918754e0ca21952531f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 18 Feb 2025 16:47:37 -0800 Subject: [PATCH] feat(executor): improve executor to allow bi-directional dependencies if the block is in the loop --- executor/index.ts | 5 ++-- executor/utils.ts | 70 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/executor/index.ts b/executor/index.ts index f03453eaf..be9cce9f5 100644 --- a/executor/index.ts +++ b/executor/index.ts @@ -1020,7 +1020,7 @@ export class Executor { const blockById = new Map(this.workflow.blocks.map((b) => [b.id, b])) const blockByName = new Map( this.workflow.blocks.map((b) => [ - b.metadata?.name?.toLowerCase().replace(/\s+/g, '') || '', + b.metadata?.name ? b.metadata.name.toLowerCase().replace(/\s+/g, '') : b.id, b, ]) ) @@ -1035,7 +1035,8 @@ export class Executor { blockByName, context.blockStates, block.metadata?.name || '', - block.metadata?.id || '' + block.metadata?.id || '', + this.workflow.loops ) // Resolve environment variables diff --git a/executor/utils.ts b/executor/utils.ts index de7a238df..e7fc3446d 100644 --- a/executor/utils.ts +++ b/executor/utils.ts @@ -41,9 +41,10 @@ export function resolveBlockReferences( value: string, blockById: Map, blockByName: Map, - contextBlockStates: Map, - currentBlockTitle: string, - currentBlockType: string + blockStates: Map, + blockName: string, + blockType: string, + workflowLoops?: Record ): string { const blockMatches = value.match(/<([^>]+)>/g) let resolvedValue = value @@ -61,26 +62,51 @@ export function resolveBlockReferences( } if (sourceBlock.enabled === false) { throw new Error( - `Block "${sourceBlock.metadata?.title}" is disabled, and block "${currentBlockTitle}" depends on it.` + `Block "${sourceBlock.metadata?.title || sourceBlock.name}" is disabled, and block "${blockName}" depends on it.` ) } - const sourceState = contextBlockStates.get(sourceBlock.id) + let sourceState = blockStates.get(sourceBlock.id) + let defaulted = false if (!sourceState) { - throw new Error( - `No state found for block "${sourceBlock.metadata?.title}" (ID: ${sourceBlock.id}).` - ) + if (workflowLoops) { + for (const loopKey in workflowLoops) { + const loop = workflowLoops[loopKey] + if (loop.nodes.includes(sourceBlock.id)) { + defaulted = true + sourceState = {} // default to empty object + break + } + } + } + if (!sourceState) { + throw new Error( + `No state found for block "${sourceBlock.metadata?.title || sourceBlock.name}" (ID: ${sourceBlock.id}).` + ) + } } // Drill into the property path. let replacementValue: any = sourceState for (const part of pathParts) { if (!replacementValue || typeof replacementValue !== 'object') { - throw new Error(`Invalid path "${part}" in "${path}" for block "${currentBlockTitle}".`) + if (defaulted) { + replacementValue = '' + break + } else { + throw new Error(`Invalid path "${part}" in "${path}" for block "${blockName}".`) + } } replacementValue = replacementValue[part] } + if (replacementValue === undefined && defaulted) { + replacementValue = '' + } else if (replacementValue === undefined) { + throw new Error( + `No value found at path "${path}" in block "${sourceBlock.metadata?.title || sourceBlock.name}".` + ) + } if (replacementValue !== undefined) { // For condition blocks, we need to properly stringify the value - if (currentBlockType === 'condition') { + if (blockType === 'condition') { resolvedValue = resolvedValue.replace(match, stringifyValue(replacementValue)) } else { resolvedValue = resolvedValue.replace( @@ -90,13 +116,29 @@ export function resolveBlockReferences( : String(replacementValue) ) } - } else { - throw new Error( - `No value found at path "${path}" in block "${sourceBlock.metadata?.title}".` - ) } } } + if (typeof resolvedValue === 'undefined') { + const refRegex = /<([^>]+)>/ + const match = value.match(refRegex) + const ref = match ? match[1] : '' + const refParts = ref.split('.') + const refBlockId = refParts[0] + + if (workflowLoops) { + for (const loopKey in workflowLoops) { + const loop = workflowLoops[loopKey] + if (loop.nodes.includes(refBlockId)) { + // Block exists in a loop, so return empty string instead of error + return '' + } + } + } + + throw new Error(`No state found for block "${refBlockId}" (ID: ${refBlockId})`) + } + return resolvedValue }