Endpoints, no matter if they are in Java, Node.js or another technology, they will share some common things like:
- Descriptor file
- Basic settings
- Deployment settings
- Stores
- Functions
- Events
- Configuration
- Scripts
- Listeners
- Flow steps
Descriptor file
The descriptor file should be located at the root of your project and has the name endpoint.json
. It contains
the definition of your endpoint, like name, type of endpoint, resources, functions, events, configuration, etc.
Basic settings
...
"label": "Sample",
"name": "sample",
"apiVersion": "v1",
"configurationType": "GLOBAL",
"configurationHelpUrl": "https://slingr-stack.github.io/platform/app_development_model_endpoints.html",
...
These are the basic settings of the endpoint. Here is a more detailed description of each of them:
label
: this is the visible name of the endpoint.name
: this is the internal name of the endpoint and will be used to determine the type of endpoint. Usually in lower case with dashes.apiVersion
: this is the version of the API to communicate to the Slingr Platform. For now only valid value isv1
.configurationType
: this indicates if the configuration would be per app or at the user-level. These are the possible values:GLOBAL
: in this case the endpoint will only have a configuration at the app level (in the app builder).PER_USER
: in this case, apart from the global configuration section, each user might need to have specific settings to connect to the endpoint. For example, this is the case for the Google Calendar endpoint, where each user needs to authorize the endpoint using OAuth.
configurationHelpUrl
: this is the URL that has the documentation about the configuration of the endpoint. Optional.
Deployment settings
...
"deployment": {
"profiles":[
{
"name": "default",
"label": "Default",
"memory": 512,
"offHeapMinFactor": 1.5,
"description": "Default configuration"
}
],
"type": "java",
"allowMultipleInstances": true
},
...
The deployment
section allows to adjust technical options for the component to be run.
In this section you can have different profiles for one endpoint. Basically a profile will indicate how much memory the endpoint will use (and CPU will be adjusted automatically). These are the settings for each profile:
name
: internal name of the endpoint. It cannot have spaces or special characters.label
: visible name of the profile. It will be displayed to the user in the app builder.memory
: the amount of memory needed by the endpoint. This is the total memory used by the process.offHeapMinFactor
: this is optional and only available for Java endpoints. It indicates the factor for non-heap memory allocation. If you endpoint needs more native memory, you will probably want to increase it. Default an minimum is1
.description
: this is the description of the profile that will be showed to the developer. It should give useful information to decide which profile will be used.
There are some other options that you can configure for the deployment of the endpoint:
allowMultipleInstances
, which can betrue
orfalse
. If it istrue
, it will be possible to configure many instances of the endpoint. When there are multiple instances request will go to any instance indistinctly, so your endpoint shouldn’t keep any status at the instance level. The safest option is to set this tofalse
and only enable it if you need scaling and you make sure that your instances are stateless.type
: indicates the technology used for the endpoint. This allows the platform to proper perform the build of the endpoint. Possible values are:java
,java8
,java11
andnodejs
.
Stores
...
"stores": [
{
"name": "test_data_store",
"indexes": [
{"fieldA": 1},
{"fieldB": 1, "fieldC": -1}
]
}
],
...
If you endpoint needs to store data persistently, you will need to define “stores”. In this stores you can save JSON documents, find them by keys, update or remove them. It is like a simple database you can use in endpoints.
For each store you need to define the following settings:
name
: the name of the store. It cannot contain spaces or special characters.indexes
: if the store is going to have many documents, it is good to add indexes to optimize how you access the data. You can define several indexes, where you need to specify the field(s) in the index and the direction, where1
means ascending and-1
descending.
Functions
...
"functions": [
{
"label": "Random number generator",
"name": "randomNumber",
"description": "Generates a random integer"
},
{
"label": "Ping",
"name": "ping",
"description": "The service returns the sent value for the Pong event",
"callbacks": [
{
"name": "pong",
"maxWaitingTime": 60000,
"maxExpectedResponses": 1
}
]
},
...
],
...
Functions are the basic way to interact with the endpoint, as they can be invoked from the app in scripts. A set of parameters will be sent to the function, that will be received by the endpoint and can return a result. You can find more information about how functions work here.
Here you don’t need to specify any kind parameters. The implementation of the function will just get a JSON and should use the needed parameters.
Here are the settings for a function:
label
: the human-friendly name of the function.name
: the name of the function. This will be the name used to call it and should match the name defined in your implementation.description
: a description of what your functions does. Optional.callbacks
: when the function does something that could take time, instead of blocking the caller, it is possible to define a callbacks that will be called once the results are ready or an event related to this call happens. This callbacks have to be defined as events too, as they are basically events related to a function call. Please check the documentation about endpoint callbacks for more information about how they are used in apps. Callbacks have the following properties:name
: this is the name of the callback that should be used in the code and should also match the name of the event defined in the endpoint (see events below).maxWaitingTime
: this is the maximum time we could expect the callback to be called since the function is invoked. It is important to set a reasonable waiting time as resources will be freed only after this times out or the callback is called (see next property too).maxExpectedResponses
: by default, once the callback is called, resources will be freed and it won’t called again. However there are cases where the callback might be called several times by only executing the function once. In these cases you should specify the maximum number of calls to this callback that are possible.
Events
...
"events": [
{
"label": "Inbound Event",
"name": "inboundEvent",
"description": "Event send for the endpoint each time that a POST request to the root path of the web service."
},
{
"label": "Pong",
"name": "pong",
"description": "Callback event for the Ping function"
},
...
],
...
Events are originated on the endpoint and sent to the app that can process them through listeners. A more detailed description of how to process events from endpoints in found here.
The definition of the events just contains a few properties. The content of the event is defined by the endpoint and the app will receive it as a JSON, but no need to define the structure here.
These are the properties of events:
label
: this is the name developers will see in the app builder.name
: this is the internal name of the event that will be used to identify it in the code.description
: a brief description about what this event is and when it is triggered.
Configuration
The endpoint can be configured in the app builder. Usually, developers will need to enter API keys and other settings needed by the endpoint in order to work.
There are two types of configurations that an endpoint can have:
- Global configuration: this is the configuration of the endpoint when you open it in the app builder. All endpoints can have this configuration.
- User configuration: this is the configuration that users will see when they go to
My Integrations
in the secondary menu of the app runtime. This configuration is per user and it is only available for endpoints where the settingconfigurationType
is set toPER_USER
.
This is how the configurations are defined at the root of the descriptor:
...
"configuration":[
...
],
"userConfiguration":[
...
]
Basically each configuration is described by a set of fields, which have the following structure:
{
"label": "Field",
"name": "field",
"type": "password",
"multiplicity": "one",
"required": true,
"visibility": @config.otherField,
"showLabel": true,
"defaultValue": "12345678",
"typeOptions": {}
}
Here is a brief description of each property:
label
: this is the label that will be shown on the left of the field. It is required ifshowLabel
is set totrue
(default value).name
: represents the key under value will be stored in configuration. This key is also accessible throughconfig
global variable.type
: the type of the field. This will determine thetypeOptions
field. More information about types below.multiplicity
: there are two possible values:one
(default value) ormany
. The second value will allow to define multiple values for this field (it will be an array).required
: indicates that the field is mandatory. It can be represented with a booleantrue
orfalse
, a reference like@config.otherField
or and expression asconfig.otherField && utils.isPlaceHolder(config.anotherValue)
visibility
: indicates if the field is visible in UI. It can be represented with a booleantrue
orfalse
, a reference like@config.otherField
or and expression asconfig.otherField && utils.isPlaceHolder(config.anotherValue)
showLabel
: iffalse
, the label will not be displayed and the field will use all the width available. It is represented with a booleantrue
orfalse
.defaultValue
: value set by default if a value has not be set by the user.typeOptions
: only valid for some types, check next section.
Next we will describe the available types:
Label
Type: label
A label represents a read only value that will not be stored as part of configuration. It only represents information for user or developer.
{
"label": "Simple",
"name": "simpleLabel",
"type": "label",
"value": "Sample Complex endpoint"
}
This field type has no options.
Information
Type: info
Allows to display an alert where HTML code can be inserted.
{
"label": "Information",
"name": "information",
"type": "info",
"typeOptions": {
"alertType": "warning"
},
"value": "Follow these points to generate a new credentials:<ul><li>Access to the Dev Console</li><li>Create a new project. Copy the 'Project Name' in the configuration form.</li><li>...</li></ul>"
}
Options:
alertType
: describes the color or format to display it. Valid values are:info
,success
,warning
anddanger
.
Fields group
Type: fieldsGroup
This is a special kind of field and allows to nest other fields inside it.
{
"label": "Labels",
"name": "labels",
"type": "fieldsGroup",
"typeOptions": {
"fields":[
{
"label": "Simple",
"name": "simpleLabel",
"type": "label",
"value": "Sample Complex endpoint"
},
{
"label": "Concatenation",
"name": "concatenation",
"type": "label",
"value": "'Prefix ['+(config.entity ? config.entity : 'No entity') +'] > ['+(config.entityField ? config.entityField : 'No field')+'] > ['+(config.entityAction ? config.entityAction : 'No action')+']'"
},
{
"label": "Multi",
"name": "multiLabel",
"type": "label",
"multiplicity": "many",
"value": [
"config.entity ? config.entity : 'No entity'",
"config.entityField ? config.entityField : 'No field'",
"config.entityAction ? config.entityAction : 'No action'"
]
}
]
}
}
Options:
fields
: must respect the same structure thanconfiguration
oruserConfiguration
, which means it is basically an array of fields. Any number of levels are supported but not recommended more than two levels due to UI available space.
Text
Type: text
This is a common text input, the value will be stored as a simple string. It can have validations set through type options
{
"label": "Email",
"name": "email",
"type": "text",
"required": true,
"typeOptions": {
"validation": "email"
}
}
{
"label": "Description",
"name": "description",
"type": "text",
"typeOptions": {
"representation": "textArea",
"numberOfRows": 4
}
}
Options:
validation
: validations available are:email
,number
andurl
. All these validations allow placeholders.representation
: the component to be used to represent the field. Valid options are:inputText
(default) andtextArea
.numberOfRows
: in the case that the selected representation istextArea
it is possible to set the number of rows to be displayed.
Password
Type: password
Represented by a password input, the value will not be shown to the developer, however it will be stored as a simple string. Keep in mind that all settings of endpoints are encrypted as usually there is sensitive information.
{
"label": "Password",
"name": "password",
"type": "password",
"required": true
}
This field type has no options.
Toggle
Type: toggle
Creates a toggle widget and will store a boolean value.
{
"label": "Sync Automatically",
"name": "syncAutomatically",
"multiplicity": "one",
"type": "toggle"
}
This field type has no options.
Script
Type: script
This field can store a script (Javascript) that can be used later in the app:
{
"label": "Action Script",
"name": "actionScript",
"type": "script",
"multiplicity": "one",
"typeOptions": {
"parameters": ["day", "amount"]
}
}
Then, during runtime you can call this script like this:
app.endpoints.sampleEndpoint.config.actionScript({day: '2019-02-15', amount: 5});
Options:
params
: a list of parameters that will be taken by this function. Only these params will be passed to the function when executed.
Button
Type: button
This component allows the execution of an action when clicked. No information is stored as value of this field. This
component does not allow multiplicity
equals to many
.
{
"label": "Set email",
"name": "setEmail",
"type": "button",
"typeOptions": {
"color": "info",
"action": "if (!config.inputs.email) { config.inputs.email = 'test1@slingr.io'; }"
}
}
Options:
color
: available values areinfo
,default
,primary
,success
,warning
anddanger
.action
: this is an expression that will be parsed and executed as Javascript function on client side. Some interesting example can be found in the official endpoints. For example the Google Calendar endpoint uses buttons to trigger the OAuth process.
Buttons group
Type: buttonsGroup
This component creates a group of buttons. If multiplicity
is set to true
, many values could be selected.
{
"label": "Multi",
"name": "multiSwitcher",
"type": "buttonsGroup",
"multiplicity": "many",
"required": true,
"defaultValue": ["danger", "info"],
"typeOptions": {
"possibleValues":[
{
"label":"Danger",
"name":"danger"
},
{
"label":"Warning",
"name":"warn"
},
{
"label":"Information",
"name":"info"
}
],
"allowCustom": true
}
}
Options:
possibleValues
: this is an array of elements withlabel
(text shown as options) andname
(string stored as value). These will be the options.allowCustom
: indicates that a placeholder can be set as value, creating an input next right to buttons to select it. This is important if the value of this field might need to change between different environments of your app.
Drop down
Type: dropDown
This component creates a combo-box. In this case, if multiplicity
is many
, more than one value can be selected.
{
"label": "Multi Custom",
"name": "multiCustomDropDown",
"type": "dropDown",
"multiplicity": "many",
"defaultValue": ["${TEST2}", "CA"],
"typeOptions": {
"allowCustom": true,
"possibleValues":[
{
"label":"New York",
"name":"NY"
},
{
"label":"Arizona",
"name":"AZ"
},
{
"label":"California",
"name":"CA"
}
]
}
}
Options:
possibleValues
: this is an array of elements withlabel
(text shown as options) andname
(string stored as value). These will be the options.allowCustom
: indicates that a placeholder can be set as value, creating an input next right to buttons to select it. This is important if the value of this field might need to change between different environments of your app.
Entity
Type: entity
Allows to select an existing entity that is defined in the app. The value will be stored as a reference to metadata, being refactored in the same way when changes happen. For example, if the entity name changes, the configuration of the endpoint will be updated, or if the entity gets deleted, the value will be cleared.
{
"label": "Entity",
"name": "entity",
"type": "entity"
}
This field type has no options.
Entity field
Type: entityField
Allows to select an existing entity field. The value will be stored as a reference to metadata, being refactored in the same way when changes happen. For example, if the entity field’s name changes, the configuration of the endpoint will be updated, or if the field gets deleted, the value will be cleared.
{
"label": "Field",
"name": "entityField",
"type": "entityField",
"visibility": "@config.entity",
"required": "@config.entity",
"typeOptions": {
"entity": "@config.entity",
"filterTypes": ["user", "choice"]
}
}
Options:
entity
: this is the entity the field should belong to. Valid values are the entity name or id, or a direct reference like@config.entity
.filterTypes
: a list of valid entity field types (represented in camel case) is accepted to filter the type of the fields listed in the selector.
Entity action
Type: entityAction
Allows to select an existing entity action. The value will be stored as a reference to metadata, being refactored in the same way when changes happen. For example, if the entity action’s name changes, the configuration of the endpoint will be updated, or if the action gets deleted, the value will be cleared.
{
"label": "Action",
"name": "entityAction",
"type": "entityAction",
"visibility": "@config.entity",
"defaultValue": "addANote",
"typeOptions": {
"entity": "@config.entity"
}
}
Options:
entity
: this is the entity the action should belong to. Valid values are the entity name or id, or a direct reference like@config.entity
.
Entity filters
Type: entityFilters
Allows the developer to define an entity filter while configuring the endpoint. The value will be stored as metadata, which means it will be refactored for example if fields involved in this filter are renamed or deleted.
{
"label": "Filters",
"name": "entityFilters",
"type": "entityFilters",
"typeOptions": {
"entity": "@config.entity"
},
"value": {
"_or": {
"companyType":"Self",
"name":"notEmpty()"
},
"entity":"companies"
}
}
Its value is represented as complex queries, for more information check the documentation for expressions to undertand how it works and how to use it in a query.
Options:
entity
: this is the entity the filter is associated with. Valid values are the entity name or id, or a direct reference like@config.entity
.
Entity events
Type: entityEvents
Allows the developer to define an entity event while configuring the endpoint. The value will be stored as metadata, which means it will be refactored for example if fields involved in this filter are renamed or deleted.
{
"label": "Events",
"name": "entityEvents",
"type": "entityEvents",
"typeOptions": {
"entity": "@config.entity"
},
"value": {
"events": [
{
"id":"59b029ad57abd274c50074c6",
"conditionType":"none",
"type":"recordCreated",
"sourceEvents": [
"userEvents",
"systemEvents"
]
},
{
"id":"59b02a604b31a11b77001b78",
"conditionType":"expression",
"expression": {
"filters": [
{
"type":"byValue",
"operation":"notEmpty",
"field":"companyLinks.link",
"values":[]
}
],
"entity":"companies"
},
"type":"conditionMet",
"sourceEvents": [
"userEvents",
"systemEvents"
]
},
{
"id":"59b02bf7bdcf466302006303",
"conditionType":"script",
"script": "1 == '1'",
"type":"actionPerformed",
"actions": [
"assignCompanyType",
"addANote"
],
"sourceEvents": [
"scriptEvents",
"systemEvents"
]
}
]
}
}
This events can be used later to define listeners.
Options:
entity
: this is the entity the event is associated with. Valid values are the entity name or id, or a direct reference like@config.entity
.
Entity mapper
Type: entityMapper
The entity mapper field type helps to define a mapping between the app and the endpoint. For example, let’s suppose that the endpoint integrates with a service tha has information about companies. Instead of having the user to manually create a mapping between the companies in the external service and the entity in the app, you could you this entity mapper to configure how the mapping should be done. Then the endpoint will use this information to perform the mapping automatically.
{
"label": "Accounts",
"name": "accounts",
"type": "entityMapper",
"typeOptions": {
"allowToCreateEntity": true,
"allowToSelectDirection": true,
"recordNameField": "name",
"fields": [
{
"label": "Name",
"name": "name",
"type": "text",
"multiplicity": "one"
},
{
"label": "Type",
"name": "type",
"type": "choice",
"multiplicity": "one",
"options": {
"possibleValues": [
{"label": "Client", "name": "client"},
{"label": "Prospect", "name": "prospect"},
{"label": "Other", "name": "other"}
]
}
},
{
"label": "Relationship",
"name": "relation",
"type": "relationship",
"multiplicity": "one",
"options": {
"entity": "@config.entity"
}
},
{
"label": "Address",
"name": "address",
"type": "nestedFields",
"multiplicity": "one",
"options": {
"nestedFields": [
{
"label": "Streets",
"name": "streets",
"type": "text",
"multiplicity": "many"
},
{
"label": "State",
"name": "state",
"type": "choice",
"multiplicity": "one",
"options": {
"possibleValues": [
{"label": "Arizona", "name": "AZ"},
{"label": "New York", "name": "NY"},
{"label": "California", "name": "CA"}
]
}
},
{
"label": "Contact",
"name": "contact",
"type": "nestedFields",
"multiplicity": "many",
"options": {
"nestedFields": [
{
"label": "Name",
"name": "name",
"type": "text",
"multiplicity": "one"
},
{
"label": "Phones",
"name": "phones",
"type": "phone",
"multiplicity": "many"
}
]
}
}
]
}
}
],
"hooks": [
{
"label": "Syncing to app",
"name": "syncingToApp",
"params": ["record", "oldRecord", "data"]
},
{
"label": "Syncing to Endpoint",
"name": "syncingToEndpoint",
"params": ["record", "data", "other"]
},
{
"label": "Other script",
"name": "otherScript"
}
]
}
}
Options:
allowToCreateEntity
: if this is set astrue
, aCreate entity
button will appear allowing developer to create an entity with configuration described in the mapper, displaying a popup to insertlabel
andname
. If the process finishes correctly, the popup will be closed and the configuration will be automatically filled with entity information. This is very useful to speed-up the integration process. For example a Google Calendar endpoint could have a mapper for events and instead of having to create the entity manually you could just click onCreate entity
which will automatically create the entity and configured it to sync with events in Google Calendar.- WARNING: if configuration has dependencies to other fields, like relationship fields pointing to an entity, and these dependencies are not set at the moment of create was triggered, the creation process will fail showing a validation error.
allowToSelectDirection
: if this is set astrue
, a buttons group will be displayed allowing to select one of three possible values:App to Endpoint
: changes on app are synchronized to endpointEndpoint to App
: changes on endpoint side must be synced to the Slingr appBoth
. IfallowToSelectDirection
isfalse
then thedefaultDirection
field is required.
defaultDirection
: this is the default sync direction. Could beappToEndpoint
,endpointToApp
orboth
.recordNameField
: indicates which of root fields will be used into the entity as record name. This value is only used when clicking onCreate entity
button.-
fields
: this is the list of fields that can be mapped. Mandatory fields arelabel
,name
andtype
. Themultiplicity
is optional and its default value isone
.For types
choice
,relationship
andnestedFields
theoptions
object is required to specifiedpossibleValues
,entity
andnestedFields
respectively.All these configurations are necessary for entity creation, but they also act as filters when an entity is selected through combo-box to filter valid fields for each configuration.
Values are stored as
references
to entity and it will come to endpoint as path of fields. hooks
: hooks are mainlyscript
fields that will be available for endpoints, here user can describe function headers and the behavior when mapping is not enough to convert fields from one side to the other.
Scripts
You need to configure scripts in the descriptor file:
...
"scripts":[
"sample.js",
"other.js"
],
...
Scripts should be put in the scripts
folder of your endpoint and the names of the files need to match the ones in
the descriptor.
This are scripts provided by the endpoint that are executed in the context of the app as any other script. For example, like scripts in libraries. These scripts are useful to simplify the usage of the endpoint or provide some features that need to be executed on the app side.
Here are some common usages of scripts in endpoints:
- Wrappers for endpoints functions: for example, sometimes the wrapper can do things to expose the function in a
simpler way. Look at this sample:
endpoint.wrapperFunc = function(a, b, c) { endpoint.func({a: a, b: b, c: c}); }
In the above sample the user doesn’t have to send a map when calling the function, but can pass the parameters directly.
- Access to app data: if you have to automate some process that needs access to the app data, you will need to execute that on the app side, because from the endpoint it isn’t possible to query the app data.
A script file will look like this:
var s = function(a, b){
return a+b;
};
endpoint.sum = s;
endpoint.rnd = function(){
return Math.random();
};
endpoint.PI_VALUE = Math.PI;
endpoint.rndSum = function(){
return this.sum(this.rnd(), this.PI_VALUE);
};
endpoint.showConfig = function(){
sys.logs.info("Endpoint: "+this._name);
sys.logs.info(" - Token: "+this._configuration.token); // check that the variable 'endpoint' is isolated on the context
sys.logs.info(" - Token (by ref): "+endpoint._configuration.token);
};
Here you can see an special variable called endpoint
. You can add properties to this object that can be available
at the endpoint. For example, in the above example, you could call the function rnd
by calling app.endpoints.sample.rnd()
(assuming your endpoint’s name is sample
).
It is also worth noticing the following elements:
endpoint._name
: this is the name of the endpoint configured in the app.endpoint._configuration
: this is a map with the configuration of the endpoint.
Listeners
You need to define listeners in the descriptor file:
...
"listeners":[
"dataListener.js",
"jobListener.js",
"endpointListener.js"
],
...
These scripts should be located in the listeners
folder of the endpoint.
In this scripts you will be able to define listeners dynamically. This way, when developers add an endpoint to their apps, listeners will be automatically appended.
For example, you could use this feature to provide automatic syncing of data between the app and the external service. In this case you will create a listener when a record is modified in the app, to sync changes to the external service, and another listener when a webhook arrive from the external app, so you update the data in the app.
Listeners should be added as properties to the object listeners
:
var getEntity = function(){
return 'companies';
};
listeners.listenerForCompaniesChanges = {
label: 'Listener for Companies changes',
type: 'data',
options: {
executeInBackground: true,
entity: getEntity(),
events: [
{ type: 'recordCreated' },
{type: 'actionPerformed', action: 'assignCompanyType'}
]
},
callback: function(event, record, oldRecord) {
sys.logs.info('Entering to listener handler');
sys.logs.info('Event: '+JSON.stringify(event));
sys.logs.info('Record: '+JSON.stringify(record));
sys.logs.info('Old record: '+(oldRecord ? JSON.stringify(oldRecord) : ''));
}
};
The script can contain its own functions like getEntity
, the name of the variable will be the name of listener, in addition
the user must configure the label
of the listener, then type
(it can be data
, endpoint
and job
). The options
field represents the proper configuration for every type. Finally, the callback
is the action executed by the listener.
The header of this function is ignored, only the inner code is used, developers must consider the parameters coming based
on listener type. See documentation
Next we will briefly describe the different types of listeners:
Data
See the example:
listeners.listenerForCompaniesChanges = { //The name will be taken from this namespace
label: 'Listener for Companies changes', //label configuration
type: 'data',
options: {
executeInBackground: true, //Indicates if the listener must be executed in background
entity: 'companies', //The name or id of the entity listened
events: [ //entity events configuration
{type: 'recordCreated'}, //the type of entity event, it can be 'recordCreated', 'recordChanged', 'recordDeleted', 'actionPerformed'
{type: 'actionPerformed', action: 'assignCompanyType'} //not all event types are available, in addition, for 'actionPerformed' the name or id can be specify
]
},
callback: function(event, record, oldRecord) {// available parameters are 'event', 'record' and 'oldRecord'
sys.logs.info('Entering to listener handler'); //JS API functions are available
sys.logs.info('Event: '+JSON.stringify(event));
sys.logs.info('Record: '+JSON.stringify(record));
sys.logs.info('Old record: '+(oldRecord ? JSON.stringify(oldRecord) : ''));
}
};
Endpoint
See the example:
listeners.listenerForSample = { //The name will be taken from this namespace
label: 'Listener for Sample endpoint', //label configuration
type: 'endpoint',
options: {
endpoint: endpoint.name, //name of listened endpoint, it can be taken for special var called `endpoint` referring to current endpoint
event: 'inboundEvent' //a valid event defined into `endpoint.json` dispatched by selected endpoint
},
callback: function(event) {// only `event` is a available parameter for this function
sys.logs.info('Entering to listener handler'); //JS API functions are available
sys.logs.info('Event: '+JSON.stringify(event));
}
};
Job
See the example:
listeners.listenerForExportRecords = { //The name will be taken from this namespace
label: 'Listener for Export records', //label configuration
type: 'job',
options: {
jobType: 'exportRecords',//job type, available are: `startApp`, `stopApp`, `importRecords`, `exportRecords`, `importUsers`, `exportUsers`
event: 'finished'// status listened, available are: `created`, `started`, `finished`, `stopped`, `resumed`, and `canceled`
},
callback: function(event) {// only `event` is a available parameter for this function
sys.logs.info('Entering to listener handler'); //JS API functions are available
sys.logs.info('Event: '+JSON.stringify(event));
}
};
Flow steps
You can create customized flow steps for your endpoint. To do that you need to define the endpoint flow steps in the descriptor file:
...
"flowSteps": [
"numberGenerator",
"weatherService",
"simpleCalculator"
],
...
Flow steps should be defined in the flowSteps
folder of your endpoint and the names of the files need to match the ones in the descriptor.
For each flow step defined you should provide 3 files for it:
icon.png
: this is the icon for the step in the flow editor.step.js
: this file should contain the function that will be executed as part of the step. This function must receive a unique object parameter called ‘inputs’ containing the function parameters.step.json
: this file should contain the name of the step along with inputs and outputs for it.
Once you’ve created the required files you’ll need to register your endpoint with a new version. More information can be found at: Endpoint Registration
After registering the new endpoint version and setting that new version from the developer portal you will be able to see the new flow steps available at the flow editor.

Steps available in the flow editor
Required files examples
Examples for step.js
file:
/**
* Generates a random number.
*
* @param {object} inputs {number} bound, This is used to get a random number between 0 (inclusive) and the number
* passed in this argument, exclusive.
*/
step.numberGenerator = function (inputs) {
var data = endpoint.randomNumber({bound: inputs.bound});
return {
"generatedNumber": data['number']
};
};
/**
* Returns some real time weather characteristics given a city
*
* @param {object} inputs {string} city, City name, if nothing found it will use a default city (NY)
*/
step.weatherService = function (inputs) {
var data = endpoint.weather({city: inputs.city});
return {
"city": data['city'],
"temperature": data['temperature'],
"pressure": data['pressure'],
"humidity": data['humidity']
};
};
From the step.js
file you can call javascript functions defined in the script files under scripts
folder and also functions annotated with @EndpointFunction
.
The object you return should match the output defined in the step.json
file.
Examples for step.json
file:
{
"label": "Random numb",
"name": "numberGenerator",
"category": "integrations",
"description": "Generates a new random number.",
"inputs": [
{
"label": "Bound",
"name": "bound",
"type": "text",
"description": "This is used to get a random number between 0 (inclusive) and the number passed in this argument, exclusive",
"required": "true",
"defaultValue": 2000
}
],
"outputs": [
{
"label": "Generated number",
"name": "generatedNumber",
"type": "number",
"description": "The generated random number"
}
]
}
Inputs examples:
"inputs": [
{
"label": "Operation",
"name": "operation",
"type": "choice",
"defaultValue": "SUM",
"required": "true",
"options": {
"possibleValues": [
{
"label": "Sum",
"name": "SUM"
},
{
"label": "Rest",
"name": "REST"
},
{
"label": "Division",
"name": "DIV"
},
{
"label": "Multiplication",
"name": "MULT"
}
],
"allowContextSelector": "false"
}
},
{
"label": "First operand",
"name": "firstOperand",
"type": "text",
"description": "First operand",
"required": "true"
},
{
"label": "Second operand",
"name": "secondOperand",
"type": "text",
"description": "Second operand",
"required": "true"
}
]
Output examples:
"outputs": [
{
"label": "result",
"name": "result",
"type": "object",
"description": "Result object containing temperature, pressure and humidity for the given city"
}
]
"outputs": [
{
"label": "city",
"name": "city",
"type": "text",
"description": "name of the city"
},
{
"label": "temperature",
"name": "temperature",
"type": "number",
"description": "temperature for the given city"
},
{
"label": "pressure",
"name": "pressure",
"type": "number",
"description": "pressure for the given city"
},
{
"label": "humidity",
"name": "humidity",
"type": "number",
"description": "humidity for the given city"
}
]
For more information about flows you can go to Developer’s reference: Flows Overview