Skip to main content
Version: 2.4

Scripts and Execution Order

Use script when an effect needs server work, and runEffect when multiple workflows should call the same named actions. Decide whether a script must finish before the next parent action begins.

Action order and waiting

Actions in do[] start from top to bottom. This guarantees their start order, not that every action waits for all of its work to finish. The next action starts when the previous action returns according to its completion behavior:

  • set, clear, and copy complete their own operation, including any required value or default resolution and applicable write, before the next action starts;
  • dispatch resolves its data and dispatches the application action before continuing, but does not wait for the application to finish handling it;
  • runEffect waits for the invoked effect's blocking actions;
  • script with await: true waits for the result and the matching successDo or errorDo branch;
  • script with await: false, or with await omitted, dispatches the request and lets the next parent action start immediately. Its result branch runs later as a detached continuation.

The following example first writes intermediate, then invokes finishValue. The invoked effect can therefore read the updated value. The final script shows the two possible waiting modes:

An explicit later action still runs when an earlier set resolves to the value the control already contains. Only value-change effects depend on an actual change.

See the Form Effects Reference for every property and the exact execution semantics.

script

script dispatches a server-side script and can continue with store actions or form actions after the result arrives:

{
"type": "script",
"scriptCode": "calculate_price",
"scriptData": {"productId": "${$value.productId}"},
"await": true,
"successDo": [
{"type": "set", "field": "price", "value": "${$response.calculatedPrice}"},
{"type": "clear", "field": "errorMessage", "mode": "empty"}
],
"errorDo": [
{"type": "clear", "field": "price", "mode": "empty"},
{"type": "set", "field": "errorMessage", "value": "${$response.error.message || $response.message}"}
]
}
PropertyRequiredTypeDescription
scriptCodeYesstringScript code to execute.
scriptDataNounknownValue or nested object containing expressions, evaluated recursively before dispatch.
hideScriptErrorNobooleanHide the standard script error presentation. Default: false.
successActionsNoStoreAction[]Store-level actions dispatched on success.
errorActionsNoStoreAction[]Store-level actions dispatched on error.
successDoNoEffectAction[]Form actions executed on success with $response.
errorDoNoEffectAction[]Form actions executed on error with $response.
awaitNobooleanWait for the selected outcome branch before continuing the parent effect. Default: false.

successActions and errorActions are handled by the application's action layer. Use them for reloads, messages, navigation, and other store-level UI behavior. See Success / Error Actions.

scriptData can use ordinary effect context expressions, but not datasource calls. Resolve datasource-backed values first with set or pass them through a separate invoked effect.

successDo and errorDo continue the form workflow. They can contain set, clear, copy, or runEffect. A nested script action is rejected; invoke a reusable effect instead.

After a response arrives, the matching successActions or errorActions are dispatched first. The matching successDo or errorDo branch then executes with $response in scope.

Script ordering

awaitBehavior
trueWait for the result, execute and await the matching Do branch in the same execution chain, then continue with later parent actions.
false or omittedDispatch the script and continue the parent effect immediately. Execute the matching Do branch later as a detached continuation.

await controls workflow ordering; it does not block the browser UI thread. Use it whenever later actions depend on the script outcome. Fire-and-forget is appropriate only for an independent side operation.

An awaited script has a 60-second timeout. A timeout runs errorDo with:

{
"error": {
"code": "SCRIPT_TIMEOUT",
"message": "Awaited script did not finish before the timeout.",
"scriptCode": "loadCustomer"
}
}

Once a script request has been sent, its success or error continuation is allowed to finish even if a newer form-state emission supersedes the original value-change run.

For value-change effects, a newer run may still stop the waiting caller's remaining do[] actions, even though the script outcome continuation can run. await preserves their order only while the caller's run remains active.

$response

$response contains parsed response.data when available. A returned failure payload is preserved; a thrown error is normalized as {error: ...}.

It is available only in the script's direct successDo or errorDo branch. Standard context values remain available alongside it. If an invoked effect needs the response, map the required values through runEffect.inputs.

runEffect

runEffect calls one effect declared with trigger.type === 'invoked':

{
"type": "runEffect",
"effectId": "process-customer",
"inputs": {
"customer": "${$response}",
"requestedBy": "${$runtimeInfo.userName}"
}
}

effectId is a literal ID in the same root schema. inputs is evaluated recursively, including expressions nested in objects and arrays. The resulting object becomes $input in the invoked effect.

The runtime rejects:

  • a missing or ambiguous ID;
  • a target that is not an invoked effect;
  • a disabled target;
  • direct or indirect invocation recursion.

Invocation is an explicit context boundary. $event and $response are not implicitly inherited by the target.

The caller waits until the invoked effect finishes its blocking execution chain. This includes awaited scripts and their selected successDo or errorDo branches. A script with await: false inside the invoked effect is dispatched but does not delay the caller; its result branch can run later as a detached continuation. This wait controls workflow order and does not block the browser UI thread.

For example, parent.do[] calls loadCustomer with runEffect and then sets status from $value.customerName. If loadCustomer.do[] runs a script whose successDo sets customerName, set await: true on that script so the caller waits for the new value. With await: false, the caller may set status before the server response arrives.

When an invoked effect ID is renamed in the Form Designer, local runEffect.effectId references are updated with it.

Execution and Cascades

Within one run:

  1. matching effects are evaluated in schema order;
  2. each when must return exactly boolean true;
  3. actions start in array order, and each later action starts after the previous action returns according to its completion behavior;
  4. changed fields are collected;
  5. effects listening to those fields form the next cascade iteration.

Changes produced directly by an effect's do[] are collected during its action loop; listeners for those changes run after that effect's actions. An awaited runEffect or script outcome can, however, execute its own actions and resulting cascades before the caller resumes its next action. Explicit actions do not depend on a changed field: a runEffect following set is invoked whenever execution reaches it, including when set resolved to the control's existing value.

One failing effect is reported without preventing the remaining matching effects in the same run from being evaluated.

Cycle safeguards

SafeguardDefault behavior
Maximum cascade depthStop after 10 iterations.
Depth warningWarn when depth reaches 3.
Field change trackingDetect repeated changes to the same field in one chain.
Invocation stackReject direct and indirect runEffect cycles.
Burst limitStop after more than 10 top-level runs in a 2-second window.
Effects run on the browser UI thread

Synchronous expressions, functions, and transforms share the JavaScript thread with form rendering and user input. A computationally expensive or non-terminating expression can make the page unresponsive before runtime safeguards get a chance to run. Move expensive work to a server-side script and keep the effect graph acyclic.