Portal Community

Where Resolution Sits in the Pipeline

StageAction
Entry (100)Load node definition from the process template
EntryValidate (200)Parse raw config JSON → build EvaluationContext → resolve expressions → populate settings
PreGuardRails (250)Run rate limit and quota guard rails
PreProcess (300)HIL suspension check
Process (400)Your ExecuteInternalAsync (already has resolved values)
PostProcess (500)Post-execution hooks
Exit (600)Persist result, emit SignalR event

Resolution Steps in EntryValidate

  1. Raw config JSON is read from ProcessElement.Configuration
  2. Config is parsed into a Dictionary<string, object> by INodeConfigurationParser
  3. ExpressionContextFactory.Create(elementContext) builds the EvaluationContext
  4. IConfigExpressionResolver.ResolveAsync(config, context) iterates every field:
  5. 4a. If field value is a literal string without @{ — kept as-is
  6. 4b. If field value contains @{directive:...} — handled by the directive resolver (no Jint)
  7. 4c. If field value is @{js:...} — passed to IExpressionEvaluator.EvaluateAsync()
  8. Resolved config stored in elementContext.ResolvedConfig
  9. BaseNodeExecutor.LoadConfigAsync() reads from ResolvedConfig.ToMergedJson() and populates settings

IConfigExpressionResolver Contract

// IConfigExpressionResolver.cs
public interface IConfigExpressionResolver
{
    Task<Dictionary<string, object>> ResolveAsync(
        Dictionary<string, object> config,
        EvaluationContext context,
        CancellationToken cancellationToken = default);
}

Resolution Failure Handling

If an expression fails to resolve (Jint timeout, syntax error, referenced node not found), the resolver has two strategies depending on the field configuration:

StrategyBehaviour
Fail-fast (default for required fields) Resolution exception propagates → node is marked Failed before executor runs
Null-coerce (optional fields) Failed expression resolves to null; executor receives null value and must handle it

Partial Resolution (Mixed Fields)

Not all fields in a config dictionary must contain expressions. The resolver skips literal fields. A single node can have a mix: some fields are literals, others are expressions, others are nested objects with expressions inside them. The resolver handles nested JSON recursively.

// Input config (before resolution):
{
  "to":      "@{output:form-1.email}",   // expression
  "subject": "Your order is ready",       // literal (no expression)
  "body":    "Hello @{output:form-1.firstName}, your order @{var:orderId} is ready."
}

// Output config (after resolution):
{
  "to":      "alice@example.com",         // resolved
  "subject": "Your order is ready",       // unchanged
  "body":    "Hello Alice, your order ORD-4821 is ready."  // resolved
}