How to call Apex from scripts

Streamscript can access any Callable class directly using the Call command.

Command Name Call
Return Type (any)
Description Run an Apex class that implements System.Callable
Example $result = Call 'My.Apex.Class' 'action' {arguments: ...}

This command enables you to reuse existing Apex in scenarios where the returned data is handled in scripts. It differs from invocable actions in that Streamscript dynamically handles all types of values, allowing for the use of any data structure including nested lists and nested maps. Here are some examples showing Callable in use:

 

NPSP example - call the Recurring Donations Pause API

This script will pause (or unpause) a list of recurring donations from flow. It uses the Callable API offered in the Salesforce Nonprofit Success Pack. See the NPSP docs for details.

# Streamscript
$rdPauseData = {
    '000000RecordId1': {unPause: true} # unpause a recurring donation
    '000000RecordId2': {startDate: '2021-04-01', reason: 'Vacation'}
    '000000RecordId3': {startDate: '2021-07-01', reason: 'Job Change'}
    '000000RecordId4': {startDate: '2021-01-01', reason: 'Valid Dates Test'}
}

# calls the Pause API and passes a map with the data
$results = Call 'npsp.Callable_API' 'rd2.pause' $rdPauseData

# show each result
foreach ($result in $results) {Log $result.isSuccess $result.error}

 

Vlocity example - integrate with VlocityOpenInterface

Streamscript can access the Vlocity Open Interface classes from flows. These are the building blocks implemented in Remote Actions and Integration Procedures. Methods and inputs or outputs are specified the same way, whether the class is custom or standard.

# Streamscript
$in = {}
$out = {}
$opts = {}
$action = 'methodName'

# invoke any class that implements VlocityOpenInterface
Call 'VlocityOpenInterface2' $action {input: $in, output: $out, options: $opts}

# view results
Log $out

More details in Vlocity documentation.

 

Reports example - fetch fact map values in Flow

In this scenario the manager wants to pick specific cells from groups in a matrix report. This flow uses the Call command to load the report into Streamscript. Then it uses the Sum command to collect the correct cells from the fact map. The parameters are adapted slightly:

# Streamscript to sum matrix report values
$report = Call 'ReportRunner' '00O000000000123' {'0': 'TODAY'}
$i = $report.reportMetadata.detailColumns.indexOf('Opportunity.Amount')
$revenueBooked = Sum $report.factMap.get('0!T').rows $_.dataCells.get($i).value.amount
Log `Revenue Booked: $revenueBooked`

 

This Apex helper class gets the values from any report run with filter parameters. The report format is illustrated visually in the Salesforce docs: Reports and Dashboards - The Fact Map.

global class ReportRunner implements Callable
{
    global Object call(String reportId, Map<String,Object> filters)
    {
        // describe report
        Reports.ReportMetadata meta;
        meta = Reports.ReportManager.describeReport(reportId).getReportMetadata();
        
        // add filter parameters
        for (String key : filters.keySet())
        {
            Integer i = Integer.valueOf(key);
            String value = (String)filters.get(key);
            meta.getReportFilters()[i].setValue(value);
        }
        
        // run and return with details
        Object result = Reports.ReportManager.runReport(reportId, meta, true);
        return Json.deserializeUntyped(Json.serialize(result));
    }
}

 

XML example - read SOAP payloads into Flow

Most integrations use JSON for data exchange but we still encounter XML from time to time, like this outbound message. Because there is no straightforward method to process the SOAP envelope, this script calls a helper class to read the XML payload into Flow.

# Streamscript example $Webhook.request
$xml = `
<Envelope
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
 <Body>
  <notifications xmlns="http://soap.sforce.com/2005/09/outbound">
   <Notification>
    <Id>041000000000001AAA</Id>
    <sObject xsi:type="sf:Task" xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
     <sf:Id>00T000000000001AAA</sf:Id>
     <sf:CreatedDate>2023-07-18T12:04:17.000Z</sf:CreatedDate>
     <sf:Description>One</sf:Description>
     <sf:Subject>Foo</sf:Subject>
    </sObject>
   </Notification>
  </notifications>
 </Body>
</Envelope>`

# Parse XML using Call
$Envelope = Call 'ReadXml' $xml

# Make records from the XML
$Task = []
foreach ($notification in $Envelope.0.Body.0.notifications.0.Notification) {
   $new = New-Task
   $new.Subject = $notification.sObject.0.Subject
   $new.Description = $notification.sObject.0.Description
   $Task.add($new)
}

# the variable name must indicate its type
return $Task

 

Here is the callable helper class. It converts any XML document into a map of lists. This recipe may be re-used in other scenarios where SOAP payloads are handled in Streamscript.

global class ReadXml implements Callable
{
    global Object call(String xml, Map<String,Object> args)
    {
        Dom.Document doc = new Dom.Document();
        doc.load(xml); Dom.XmlNode root = doc.getRootElement();
        Map<String,Object> untyped = new Map<String,Object>();
        traverse(untyped, root);
        return untyped.get(root.getName()); // return root node to Streamscript
    }

    void traverse(Map<string,Object> context, Dom.XmlNode parent) {
        String text = '';
        Map<String,Object> child = new Map<String,Object>();
        for (Dom.XmlNode node : parent.getchildren()) switch on node.getNodeType()
        {
            when TEXT { text += node.getText(); }
            when ELEMENT { traverse(child, node); } // recursive
        }
        String name = parent.getName(); // append text or listy value
        Object value = child.isEmpty() ? (Object)text : (Object)child;
        if (context.get(name) instanceof List<Object>) ((List<Object>)context.get(name)).add(value);
        else if (context.get(name) != null) context.put(name, new List<Object>{context.get(name), value});
        else context.put(name, value instanceof Map<String,Object> ? new List<Object>{value} : value);
    }
}

 

Scripts use the Call command to bridge the gap between flows and other building blocks in the org. While these examples showed Salesforce products and features, any managed package with Callables can be leveraged in flows without making changes to the Apex logic.

 

 

Getting started with Streamscript
Install from the Salesforce AppExchange
Package install link: /packaging/installPackage.apexp?p0=04tGA000005NDq5