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, andcopycomplete their own operation, including any required value or default resolution and applicable write, before the next action starts;dispatchresolves its data and dispatches the application action before continuing, but does not wait for the application to finish handling it;runEffectwaits for the invoked effect's blocking actions;scriptwithawait: truewaits for the result and the matchingsuccessDoorerrorDobranch;scriptwithawait: false, or withawaitomitted, 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}"}
]
}
| Property | Required | Type | Description |
|---|---|---|---|
scriptCode | Yes | string | Script code to execute. |
scriptData | No | unknown | Value or nested object containing expressions, evaluated recursively before dispatch. |
hideScriptError | No | boolean | Hide the standard script error presentation. Default: false. |
successActions | No | StoreAction[] | Store-level actions dispatched on success. |
errorActions | No | StoreAction[] | Store-level actions dispatched on error. |
successDo | No | EffectAction[] | Form actions executed on success with $response. |
errorDo | No | EffectAction[] | Form actions executed on error with $response. |
await | No | boolean | Wait 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
await | Behavior |
|---|---|
true | Wait for the result, execute and await the matching Do branch in the same execution chain, then continue with later parent actions. |
false or omitted | Dispatch 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:
- matching effects are evaluated in schema order;
- each
whenmust return exactly booleantrue; - actions start in array order, and each later action starts after the previous action returns according to its completion behavior;
- changed fields are collected;
- 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
| Safeguard | Default behavior |
|---|---|
| Maximum cascade depth | Stop after 10 iterations. |
| Depth warning | Warn when depth reaches 3. |
| Field change tracking | Detect repeated changes to the same field in one chain. |
| Invocation stack | Reject direct and indirect runEffect cycles. |
| Burst limit | Stop after more than 10 top-level runs in a 2-second window. |
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.