@@ -35,17 +35,21 @@ export function parseVariables(variables) {
3535 return ;
3636 }
3737
38- const { expression, unresolved, analysis, isInputExpression } = expressionDetails ;
38+ const {
39+ expression,
40+ unresolved,
41+ requirementAnalyses
42+ } = expressionDetails ;
3943
4044 variablesToResolve . push ( { variable, expression, unresolved } ) ;
4145
42- // Collect analysis for input requirement extraction
43- if ( isInputExpression && analysis && analysis . inputs . length > 0 ) {
46+ // Collect analyses for input requirement extraction
47+ for ( const { expressionType , inputs } of requirementAnalyses ) {
4448 analysisResults . push ( {
4549 origin,
4650 targetName : variable . name ,
47- inputs : analysis . inputs ,
48- isLocalMapping : variable . scope === origin
51+ inputs,
52+ expressionType
4953 } ) ;
5054 }
5155 } ) ;
@@ -184,73 +188,125 @@ export function getResultContext(expression, variables = {}) {
184188 return latestVariables ;
185189}
186190
191+ /**
192+ * Find all matching expression sources for a variable on a given origin element.
193+ *
194+ * Returns candidates ordered by resolution priority (first = highest).
195+ * Local variables: script > input-mapping (script result overwrites at runtime).
196+ * Global variables: output-mapping > script.
197+ *
198+ * @param {ProcessVariable } variable
199+ * @param {djs.model.Base } origin
200+ * @returns {Array<{ type: string, value: string }> }
201+ */
202+ function findExpressions ( variable , origin ) {
203+ const isLocal = variable . scope === origin ;
204+
205+ const candidates = isLocal
206+ ? [
207+ { type : 'script' , value : getScriptExpression ( variable , origin ) } ,
208+ { type : 'input-mapping' , value : getIoInputExpression ( variable , origin ) } ,
209+ ]
210+ : [
211+ { type : 'output-mapping' , value : getIoOutputExpression ( variable , origin ) } ,
212+ { type : 'script' , value : getScriptExpression ( variable , origin ) } ,
213+ ] ;
214+
215+ return candidates . filter ( c => c . value ) ;
216+ }
217+
187218/**
188219 * Given a Variable and a specific origin, return the mapping expression and all
189220 * unresolved variables used in that expression. Returns undefined if no mapping
190221 * exists for the given origin.
191222 *
223+ * The primary (highest-priority) expression is used for variable resolution.
224+ * All non-output-mapping expressions are analyzed for input requirements,
225+ * since a variable may be produced by both a script and an input mapping.
226+ *
192227 * @param {ProcessVariable } variable
193228 * @param {djs.model.Base } origin
194- * @returns {{ expression: String, unresolved: Array<String>, analysis: Object } }}
229+ * @returns {{ expression: String, unresolved: Array<String>, requirementAnalyses: Array, expressionType: String } }
195230 */
196231function getExpressionDetails ( variable , origin ) {
197232
198- // if variable scope is !== origin (global), prioritize IoExpression over ScriptExpression
199- // if variable scope is === origin (local), prioritize ScriptExpression over IoExpression
200- const expression = variable . scope !== origin
201- ? getIoExpression ( variable , origin ) || getScriptExpression ( variable , origin )
202- : getScriptExpression ( variable , origin ) || getIoExpression ( variable , origin ) ;
203-
204- // Output mappings don't produce input requirements
205- const isInputExpression = variable . scope !== origin
206- ? ! getIoExpression ( variable , origin )
207- : true ;
233+ const matches = findExpressions ( variable , origin ) ;
208234
209- if ( ! expression ) {
235+ if ( matches . length === 0 ) {
210236 return ;
211237 }
212238
213- const result = getResultContext ( expression ) ;
239+ // Primary expression for variable resolution (highest priority)
240+ const { value : expression , type : expressionType } = matches [ 0 ] ;
214241
242+ const result = getResultContext ( expression ) ;
215243 const unresolved = findUnresolvedVariables ( result ) ;
216244
217- // Analyze the expression to extract input requirements
218- let analysis ;
219- try {
220- const analysisResult = feelAnalyzer . analyzeExpression ( `=${ expression } ` ) ;
221- if ( analysisResult . valid !== false ) {
222- analysis = {
223- inputs : analysisResult . inputs || [ ]
224- } ;
245+ // Analyze each non-output-mapping expression for input requirements
246+ const requirementAnalyses = [ ] ;
247+
248+ for ( const { type, value } of matches ) {
249+ if ( type === 'output-mapping' ) {
250+ continue ;
251+ }
252+
253+ try {
254+ const analysisResult = feelAnalyzer . analyzeExpression ( `=${ value } ` ) ;
255+
256+ if ( analysisResult . valid !== false && analysisResult . inputs && analysisResult . inputs . length > 0 ) {
257+ requirementAnalyses . push ( {
258+ expressionType : type ,
259+ inputs : analysisResult . inputs
260+ } ) ;
261+ }
262+ } catch ( error ) {
263+ console . warn ( `Failed to analyze expression for variable ${ variable . name } :` , error ) ;
225264 }
226- } catch ( error ) {
227- console . warn ( `Failed to analyze expression for variable ${ variable . name } :` , error ) ;
228265 }
229266
230- return { expression, unresolved, analysis, isInputExpression } ;
267+ return { expression, unresolved, requirementAnalyses, expressionType } ;
268+ }
269+
270+ /**
271+ * Given a variable and origin, return input mapping expression targeting the variable.
272+ *
273+ * @param {ProcessVariable } variable
274+ * @param {djs.model.Base } origin
275+ * @returns {string|undefined }
276+ */
277+ function getIoInputExpression ( variable , origin ) {
278+ return getIoExpressionByType ( variable , origin , 'input' ) ;
231279}
232280
233281/**
234- * Given a Variable and a specific origin, return the mapping expression for all
235- * input outputs mapping. Returns undefined if no mapping exists for the given origin.
282+ * Given a variable and origin, return output mapping expression targeting the variable.
236283 *
237284 * @param {ProcessVariable } variable
238285 * @param {djs.model.Base } origin
239- * @returns { expression: String }
286+ * @returns {string|undefined }
240287 */
241- function getIoExpression ( variable , origin ) {
288+ function getIoOutputExpression ( variable , origin ) {
289+ return getIoExpressionByType ( variable , origin , 'output' ) ;
290+ }
291+
292+ /**
293+ * Given a variable and origin, return mapping expression by mapping type.
294+ *
295+ * @param {ProcessVariable } variable
296+ * @param {djs.model.Base } origin
297+ * @param {'input'|'output' } mappingType
298+ * @returns {string|undefined }
299+ */
300+ function getIoExpressionByType ( variable , origin , mappingType ) {
242301 const ioMapping = getExtensionElementsList ( origin , 'zeebe:IoMapping' ) [ 0 ] ;
243302
244303 if ( ! ioMapping ) {
245304 return ;
246305 }
247306
248- let mappings ;
249- if ( origin === variable . scope ) {
250- mappings = ioMapping . inputParameters ;
251- } else {
252- mappings = ioMapping . outputParameters ;
253- }
307+ const mappings = mappingType === 'input'
308+ ? ioMapping . inputParameters
309+ : ioMapping . outputParameters ;
254310
255311 if ( ! mappings ) {
256312 return ;
@@ -263,7 +319,6 @@ function getIoExpression(variable, origin) {
263319 }
264320
265321 return mapping . source . substring ( 1 ) ;
266-
267322}
268323
269324/**
@@ -482,22 +537,24 @@ export function getElementNamesToRemove(moddleElement, inputOutput) {
482537/**
483538 * Build input requirement variables from pre-collected analysis results.
484539 *
485- * @param {Array<{ origin: Object, targetName: String, inputs: Array, isLocalMapping: boolean }> } analysisResults
540+ * @param {Array<{ origin: Object, targetName: String, inputs: Array, expressionType: String }> } analysisResults
486541 * @returns {Array<ProcessVariable> } input requirements
487542 */
488543function buildInputRequirements ( analysisResults ) {
489544 const inputRequirements = { } ;
490545 const inputMappingTargetsCache = { } ;
491546
492- for ( const { origin, targetName, inputs, isLocalMapping } of analysisResults ) {
547+ for ( const { origin, targetName, inputs, expressionType } of analysisResults ) {
493548
494549 if ( ! inputMappingTargetsCache [ origin . id ] ) {
495550 inputMappingTargetsCache [ origin . id ] = getInputMappingTargetNames ( origin ) ;
496551 }
497552 const orderedTargets = inputMappingTargetsCache [ origin . id ] ;
498553
554+ // Input mappings are order-sensitive: only earlier targets are available.
555+ // Scripts can reference all input mapping targets.
499556 let availableLocalTargets ;
500- if ( isLocalMapping ) {
557+ if ( expressionType === 'input-mapping' ) {
501558 const targetIndex = orderedTargets . indexOf ( targetName ) ;
502559 availableLocalTargets = new Set ( orderedTargets . slice ( 0 , targetIndex ) ) ;
503560 } else {
0 commit comments