Execution model

Execution process

Every action, regardless of its type, goes through the same execution process:

  1. Constraint check: Check whether another execution of the same action is already running, or whether a potential minimum delay has not passed yet. If yes, the execution is added to the queue for later execution. Please refer to the documentation below to learn more.

  2. JS evaluation: Once the constraint check has been passed, JavaScript code inside the action configuration is evaluated. For example, this might be embedded snippets in a SQL query, or evaluating the JSON-body of a REST request. In case the evaluation encounters an error anywhere, the action execution is aborted and marked as unsuccessful.

  3. Action run: The action is now executed as configured, using the JS evaluation results from the second step. The execution highly depends on the action type and, except for JavaScript actions, requires a round-trip to backend. In case the backend responds with an error, or in case the action times out, the execution is aborted and marked as unsuccessful.

  4. JS transformation: Actions might have JS transformation scripts configured. In this case, this code is executed, providing the raw result as input. In case this evaluation leads to an error, the raw action result is still kept as successful execution "before transformation", but the action result "after transformation" is marked as unsuccessful.

  5. Dependency reevaluation: Following the reactive principles of the engine, there might be usages of the executed action that depend on specific properties, like for example data, dataBeforeTransform, success, or executionTime. After successful or unsuccessful execution of the action, these usages are reevaluated - which can trigger cascaded dependency reevaluation.

The following sections describe some of these steps in more detail.

Parallelization and timeouts

The same action will never run more than once in parallel, regardless of how it has been triggered. In case an action is triggered while another execution of the same action is still running, this newly triggered execution will wait in the queue until other executions of that action have been finished.

The following example illustrates this mechanism very well:

This example app performs the following operations:

  • The click-event handler of the "Run" button increments the state variable that fuels the number on the left. You see that it increases immediately every time the button is clicked.

  • Then, the longRunningAction is triggered, which waits for 2 seconds and then increments a state variable that is shown on the right.

  • As you can see, the right-hand number is incremented with exactly 2 seconds delay, because the four executions, which are triggered by quickly clicking the button at the beginning, are strictly executed in sequence.

In this example, it is important that the action that handles the button click does not await the result of longRunningAction. If we used await here, the left-hand figure would also increment with 2 seconds delay, because the handleButtonClick action will always wait for the longRunningAction to finish. Executions of handleButtonClick would then queue up in a similar way like the executions of longRunningAction do in the example.

With regards to timeouts, actions are currently not restricted in terms of how long they execute. The application will always wait until an action finishes, no matter how long this will take. Due to the sequencing of executions, that means that very long-running actions will block any further executions. It is therefore recommended to keep actions small and atomic, and of course to prevent infinite loops.

The only exception of not having a timeout is the execution of a Salesforce bulk job. This action type is timed out after 120 seconds. We are working on a more consistent timeout behavior, and making it configurable for individual actions.

Result transformation

In many use cases, data that is pulled in from external datasources must be transformed in some way. To accomplish this, any action can be configured to execute JavaScript code to transform the original result of the action. For example, a MySQL action returns a set of records, and a JavaScript snippets adds aggregations and data cleaning on top.

JavaScript actions do not allow transformation, because the transformation logic can simply be added to the action itself.

A transformation script can be added to an action by navigating to the "Transformation" tab of the action configuration, and activating the toggle there:

When this toggle is activated, a code input appears to write the transformation code. The original result of the action can be accessed via the data variable. The value that is returned by this transformation code will be placed in the data property of the action.

You will still be able to access the original result of the action via the dataBeforeTransform property. In summary:

  • Use the data property of an action to access the result after transformation.

  • Use the dataBeforeTransform property to access the original result before transformation.

  • In case no transformation is configured, data and dataBeforeTransform will reference to the same result.

In case there will be errors in either the original action, or the data transformation, the action properties will be set as follows:

  • success will only be true if both the original action and the JavaScript transformation ran without errors, and false otherwise

  • error will contain the message of the error that occurred either in the original action or the JavaScript transformation. In case both ran successfully, it will be null

  • dataBeforeTransform will contain the result of the original action, even if there was an error in the JavaScript transformation (and hence, success being false). In case there is an error in that original action run, it will be null

  • data will contain the result after applying the JavaScript transformation. In case there was an error in either the original action, or the transformation, it will be null

Action arguments

Passing arguments to action runs

Actions can receive arguments for an execution, similar to how regular functions can receive arguments. You can pass arguments of any type to an action run by providing an object as first argument to the trigger function with args as key. Formally, the signature of the trigger function is as follows:

interface Action {
    ...
    trigger: (arguments?: { args: unknown }) => Promise<unknown>;
}

You can access these arguments by using the args variable inside of an action:

// action1: Calling action2 with arguments
action2(5);

// action2: Using the provided arguments
console.log(args); // This will log the value 5

args will always be undefined when an action is previewed. In case you want preview to work properly for an action, you need to use sensible defaults in your code for that case. This certainly is unnecessary and undesired code, and we are working on a feature to deliberately provide explicit arguments in case an action is previewed that is depending on args.

Default arguments

One possible trigger of an action is a UI event, like the click of a button or the change of a toggle. To react to such events, an action must be associated with an event-property of a component in the properties panel. Some of these events will pass specific args to such action runs by default:

  • Custom actions of input validation rules will receive the input value of the validated component

  • An action associated with a table row action will receive the data array of the clicked row

  • Actions associated with the "On rows update" and "On row add" events of a table will receive an object summarizing the changes

We will continue to add more default arguments like that to specific events, to make it easier for engineers to consider the context of the particular event.

Dependency reevaluation

JavaScript snippets in dynamic component properties can not only use properties of other components, but also properties of actions. Due to the reactive approach of our engine, such snippets should also be reevaluated as soon as a utilized property of an action changes. An action run will therefore trigger such updates as soon as it has finished, regardless of whether it was successful or not. You can therefore rely on your snippets always being updated with the latest action property values:

Last updated