The Workflow Language (WL) defines a data format that expresses actionable steps and control flow instructions to act as a data-driven scripting language for solving simple tasks in our platform.
A workflow is an object that has an array of steps and possibly other information about how to handle execution (TBD).
type Step = Action | Control;
interface Workflow {
steps: Step | Step[];
// TBD: options...
timeout?: number;
}Actions are used to modify external state as part of a workflow. The available actions and their respective
configuration are not necessarily part of the WL specification. The only required field is type s.t. the evaluation
engine can dispatch accordingly.
interface Action {
type: string;
result?: ResultDescription;
}If the result of the action should be available to later steps, result describes how to transform the data and under
what name to store it.
Within the transform logic, the action's immediate result (e.g. a response body) is available under action.result.
interface ResultDescription {
as: string;
transform?: JsonLogic;
}Executes a generic HTTP action.
interface HTTPAction extends Action {
type: "http";
url: string;
method?: string;
path?: string | JsonLogic[];
query?: JsonLogic;
headers?: Record<string, string>;
body?: string | JsonLogic;
auth_token?: string | JsonLogic;
}Mandatory request target, including schema (http/https), hostname, path and query.
If the WORKFLOW_ALLOWED_HTTP_HOSTS environment variable is set to a comma-separated list of host
names in the minimatch syntax, only these hosts are allowed
to be contacted.
HTTP request method to use. Supports get (the default), post, put, patch, and delete.
An optional, additional argument for path which can be a list of JsonLogic segments that will be
joined by /.
Example:
"path": ["api", "v1", %{"var": "params.entityType"}]If url already contains a path, the value of this argument is appended to url's path:
- If
pathis absolute (starts with/), it fully replaces theurl's path. - If
pathis relative (starts with a segment or./), it is appended. Ifurldoesn't end in/,pathreplaces the last segment (or multiple ifpathstarts with../..)
JsonLogic that resolves to an object that will be encoded to query string, values can be strings, numbers, booleans, or arrays of these types.
Binary values will be passed through as-is.
If url already contains a query string, query will be appended to it.
Customize headers by specifying a map of key-value-pairs. Does not support JsonLogic. All header names will be downcased before sending to allow overriding the default headers.
By default, accept: application/json will be sent (and a content-type header depending on body).
Request body. Supported for all methods, but only recommended for POST, PUT and PATCH.
Can be a (pre-encoded) binary or a JsonLogic that resolves to either a map or a list. In the later
case, content-type: application/json will be added to the request headers.
String or JsonLogic. If set to/resolves to a string, it will be used as token in an Authorization: Bearer <TOKEN> header.
Example:
"auth_token": {"var": "params.user_token"}Since linear execution of a series of actions is not sufficient to complete even moderately complex tasks, control elements can be used to dynamically customize the workflow.
type Control = If | Loop | Yield;The conditional executes the then steps/workflow if the if condition is true, or the optional else steps/workflow
otherwise. If a workflow is given for then or else branch, its options might be ignored by the engine (TBD).
interface If {
if: JsonLogic;
then: Workflow | Step | Step[];
else?: Workflow | Step | Step[];
}The loop control executes the sub-workflow (do) for every item of the list extracted using the loop expression.
interface Loop {
loop: JsonLogic;
element?: string;
do: Workflow | Step | Step[];
}To sub-workflows, the current loop element is available as variable loop.element and, if given, under the name defined by element. Additionally, the current element's index is available as loop.element_index and optionally <element>_index.
interface LoopContext {
"loop.element": any;
"loop.element_index": number;
"<element>"?: any;
"<element>_index"?: any;
}When nesting loops, the inner loop's element will be available under loop.element. To keep access to the outer loop's current element, its element option needs to be used to create a named binding.
The yield control emits data to the caller of the workflow. The meaning of yielded data depends entirely on the caller and might even change throughout the workflow.
For example, a workflow could yield items that need to be worked on later (and the caller would collect them in a list) or it could yield metadata about the current workflow which gets collected in an object.
interface Yield {
yield: JsonLogic;
}Context values and variables can be used by steps and controls through the use of the JsonLogic { var: "path" }
operator.
The workflow's caller might give a predefined set of params to the workflow. These are available under the params key
(e.g. params.client_id).
Any (intermediately) stored values, e.g. from action results or loop bindings are directly stored under the given name
(e.g. updateTag.updated_at or currentTag).