Skip to main content

Function Node

The Function Node allows execution of arbitrary, user-defined JavaScript against a workflow payload.

Function Node

Configuration

The Function Node configuration takes two inputs.

Function Scope

Optionally, you may provide a payload path to serve as the value of the payload variable in the function script. If a path is not provided, the entire workflow payload will act as the payload variable value. The maximum amount of data that can be provided to a Function Node is 5MB. By specifying a path, you can reduce the size of the data to only that which you need, leading to better workflow performance and less risk of exceeding the 5MB limit. In the screenshot above, the Function Node is scoped to the data path on the workflow's payload.

For Edge Workflows, the ability to provide a Function Scope Path is only available for the Gateway Edge Agent v1.30.0 and higher.

Code

The provided code must be valid JavaScript and the web interface provides a code editor with syntax highlighting. ES5 and ES6 syntax are both valid. Other ECMAScript specifications are not supported.

Example: Calculating an Average

if(payload.values?.length > 0) {
const total = payload.values.reduce((acc, val) => {
return acc + val;
});

payload.average = total / payload.values.length;
}

The example code above is calculating the average across values in an array. The result is being placed at payload.average. The payload variable points to whatever was passed in as the Function Scope. If no function scope was provided, payload points to the entire workflow payload object. In this example, the function scope was set to data. This means that when the node completes, the result will placed back on the payload at data.average (since payload is scoped to data).

Generate Code Suggestions

To assist workflow developers, Losant exposes an AI-powered service that accepts a prompt and optionally an example payload, and returns JavaScript that can mutate the workflow payload as directed in the prompt. The model backing the code generator is trained on millions of lines of code from a variety of general-purpose use cases, making the service especially useful for unit conversions; data sorting, filtering, and re-mapping; randomized data generators; and more.

Requesting a Code Suggestion

To generate a function, click the "Request AI Code Generation" button above the Function Node's code editor. Doing so will display a modal where you may enter a prompt and choose a payload to serve as context for the prompt.

Function Node Generator Input

Providing a Prompt

Enter one or more sentences in plain language describing the function you would like to generate. The more detail you can provide about what you would like the Function Node to accomplish, the more likely the generated code will handle your use case as desired.

Providing a Payload

For best results, provide an example payload along with your prompt. This additional context will help the AI assistant generate code that matches your prompt.

Payloads may be provided one of three ways ...

  1. Workflow Trigger: Select one of the triggers currently on the workflow stage. The example payload in the trigger's editor will be provided as context. Note, however, that the actual payload differs across every workflow execution and may be mutated by other workflow nodes preceding the Function Node. This is the default option when there are no messages in the workflow debug log.
  2. Workflow Debug Message: Select a recent debug message out of your workflow debug log. If there are any messages in the log when the modal opens, this most recent message is the default selection. While this is most often the best option, bear in mind the following ...
    • The debug message reflects the payload at the time that the message was generated. If the message came from a Debug Node that follows the Function Node, the payload may have been mutated by other nodes between the Function Node and the Debug Node and thus the payload would not reflect the actual context of the Function Node. For best results, choose a debug message generated by a Debug Node immediately preceding the Function Node.
    • The message may have been generated through a a workflow path that does not connect to the Function Node at all, in which case the payload may have deviated completely from what is expected when executing the Function Node.
    • If the Function Node's scope is limited and the debug message's scope does not match that scope, the value of the payload variable in the returned code will not match the value of the variable that is actually provided to the Function Node.
    • Similarly, if the chosen debug message comes from a Debug Node with a scoped payload value, the Function Node's payload variable will not match the value from the selected debug message.
  3. Custom Payload: Enter a custom object to serve as the payload variable's value when generating code for the node. When selecting the option, the payload will be autofilled with the value from the previous selection - be it an example workflow trigger or a debug message. You may then modify that value to reflect the expected input to the Function Node.
tip

To ensure you are providing an accurate payload when working with scoped Function Nodes or debug messages, place a console.log(payload); line at the top of the Function Node and then execute the node. Then, choose the debug message generated by that output to serve as example context.

Not Providing a Payload

You may also choose not to provide a payload as context. When doing so, the code generation will most likely include a JavaScript function that takes inputs as arguments and returns outputs. If so, you must append the function to the Function Node and ensure it is invoked, and that its returned value is placed on the payload at a path of your choosing.

Using the Suggested Code

Once ready, click the "Request AI Code Generation" button beneath the text input to return a code example matching the provided prompt and sample payload context. Depending on your inputs, the result may include several code blocks and usage descriptions, or a single code block with detailed inline comments.

Function Node Generated Output

Beneath the output is an "Append to Function Node" button. Pressing it will paste the generated code block into the Function Node, after which you may modify the code if desired. You must still save the workflow changes to use the updated Function Node.

If you are not satisfied with the result, click "Change Prompt" to try again with a different prompt and/or example context.

Limitations

  • The service does not maintain the context of previous requests; therefore, if you would like to modify a prompt to receive a function that is more suitable to your use case, you should provide the prompt in full with modifications.
  • As with any AI-powered generative service, output should be tested before put into production use to ensure that the provided prompt is satisfied by the code output.
  • The service has a maximum output per request. If that limit is reached with a given prompt, you will be alerted that the full output could not be displayed. You may either use the output that was retrieved, or you may modify your prompt to receive a more succinct code suggestion.
  • The code generation service can only be used to generate JavaScript code to be used in Losant's Function Node. The agent generating the code is instructed not to provide code in other languages, or to generate content about any other subject.

Considerations

When writing your Function Node script, there are a few points to consider:

Buffers

The Buffer Object is available in the Function Node. The Buffer class is specifically designed to process raw or binary data.

For example, if your payload contains a hex-encoded string that represents the float 3.14159, it can be converted back to the float using the following code:

var hex = payload.data.hex;
var b = Buffer.from(hex, 'hex');
var value = b.readFloatLE(0);
payload.data.value = value; // 3.14159

Console Output

Several console methods are available and can be used to provide feedback on code execution, debug errors, and aid in the development process. When a console function is invoked, an entry is added to the debug log with whatever message you provided.

Example - Console Output

if(payload.values && payload.values.length > 0) {
const total = payload.values.reduce((acc, val) => {
return acc + val;
});

payload.average = total / payload.values.length;
} else {

console.error('Invalid array. Could not calculate average.');

}

Console outputs can be categorized based on their level of importance, with each console method corresponding to a particular debug level in the debug log as described below.

Console MethodDebug Level
console.errorerror
console.warnwarning
console.infoinfo
console.loginfo
console.traceverbose
console.debugverbose

The above console methods will output to the debug log only if their corresponding debug level is configured to be displayed.

For Edge Workflows, debug levels are only applicable for the Gateway Edge Agent v1.38.0 or higher. In earlier versions, all console methods will have a debug level of verbose.

Asynchronous Operations

Asynchronous operations (Promises, setTimeout, setInterval, async, and await) are only supported in Edge Workflows and while running the Gateway Edge Agent v1.43.2 or higher. Asynchronous operations are not supported in Application Workflows or Experience Workflows.

Callbacks can be used, but they must be wrapped in a Promise.

The example below uses the built-in DNS module to perform a DNS lookup. The lookup function is asynchronous, uses a callback, and must be wrapped in a Promise.

const dns = require('node:dns')

const [address, family] = await new Promise((resolve, reject) => {
dns.lookup('losant.com', (err, address, family) => {
if(err) { return reject(err); }
resolve([address, family]);
});
});

payload.working = payload.working || {};
payload.working.dns = { address: address, family: family };

A good use case for asynchronous operations is to connect to controllers not natively supported by the Gateway Edge Agent. When doing this, you must connect, perform your action (read PLC tags, etc.) and disconnect in a single function node. The function node cannot be used for long-running asynchronous processes. For example, you cannot open a persistent connection to a controller or start any kind of server. The function must complete within the workflow's timeout period. If it does not, the Gateway Edge Agent will automatically end the function.

Modules and Libraries

Edge Workflow functions have access to nearly every built-in Node.js module. Application and Experience workflows only have access to the Buffer module.

Third-party libraries, such as those published on npmjs.com and referenced through require, are supported for Edge Workflows. Application and Experience workflows do not support any third-party libraries.

There are two primary ways to make third-party modules available to your function node. The first is to mount the library into the container using a Docker Volume (the -v argument). You can then access this module by its location on disk:

const _ = require('/path/to/my/scripts/lodash.min.js');

Another option is to extend the base Gateway Edge Agent image with your desired modules already installed. The example Dockerfile below creates a new image with two modules installed.

FROM losant/edge-agent:latest
RUN npm install -g numjs
RUN npm install -g danfojs

Once this image is built, you can run it identically to how you'd run the base Gateway Edge Agent. You can access these modules using the following syntax:

const npmjs = require('numjs');
const danfo = require('danfojs-node');

This option is recommended for production use cases. This gives you a self-contained solution that you can push to your own Docker repository. You can then deploy this image as you would any other Docker image. Please keep in mind that you must build the Docker image on the same architecture as your gateway. For example, if you're deploying to a Raspberry Pi, you must build your Docker image on an ARM64 system.

Performance

For security purposes, each time a Function Node is invoked, a discrete sandbox is created in which the code executes. Setting up this sandbox and copying the payload into it introduces a fixed time cost. This has several implications:

  • A Function Node will almost always be slower than a native node when running an equivalent task.

  • We advise against using Function Nodes in Loops when possible. If you're using a loop and a function is required to process each item, consider putting the function node after the loop to process every item at once (instead of inside the loop processing each item individually).

Additionally, there is a limit on the number of concurrent Function Nodes that can be running in an application. This may lead to Function Nodes being queued, resulting in long workflow execution times and potentially workflow timeouts.

For more information on best practices for using Function Nodes, please see our Building Performant Workflows reference guide.

Payload Modification

If you provided a Function Scope Path, the data at the provided path will be available at the variable payload; otherwise the payload variable will be the entire current payload of the workflow. There are two ways a Function Node can modify this value.

Mutating the Payload

In most cases, users opt to modify properties on the incoming payload. For example, given the following script:

payload.data.newItem = 'Something Special';
payload.data.oldNumber = payload.data.oldNumber + 1;

And an example payload of:

{
...
"data": {
"oldNumber": 5
}
...
}

The payload after the execution of that Function Node would be:

{
...
"data": {
"newItem": "Something Special",
"oldNumber": 6
}
...
}

Returning a New Payload

Alternatively, you may return any value within the Function Node. If a Function Scope Path has been provided, this value will serve as the new value at that path; otherwise that value will then serve as the full workflow payload from that point forward.

return { value: 'Total Replacement' }

If the above were the entire contents of a Function Node with no Function Scope Path provided, the payload after the execution of the Function Node would entirely be replaced and be the object:

{ "value": "Total Replacement" }

Replacing the entire payload is generally not recommended. There are many values that are automatically placed on the payload that are required by other nodes. For example, the Endpoint Trigger places a replyId on the payload that is required by the Endpoint: Reply Node. It's very easy to inadvertently remove required fields when replacing the payload, therefore it is not a recommended practice.

Note: If a return statement is used, but no value is returned, the value of the payload variable at the end of the node's execution is used as the new payload.

Was this page helpful?


Still looking for help? You can also search the Losant Forums or submit your question there.