Behaviour
Last updated
Last updated
Components receive incoming messages, process them, and generate outgoing messages. The way messages are processed is called component behaviour. It defines what components do internally and how they react to inputs.
Components are implemented as NodeJS modules that return an object with a set of methods (Component Virtual Methods) that the Appmixer engine understands. Let's start with a simple example, a SendSMS component that has one input port (message
), no output ports and its purpose is to send an SMS using the Twilio API.
As was mentioned in the previous paragraph, components are simple NodeJS modules that can implement a certain set of methods the Appmixer engine understands. The one most important method is the receive() method. This method is called by the engine every time messages are available on the input ports and the component is ready to execute. The method must return a promise that when resolved, acknowledges the processing of the input messages. If the promise is rejected, the Appmixer engine automatically retries to send the messages later using an exponential back-off strategy that prolongs intervals between the retries.
Messages that have been rejected 30-times are put in a special internal "dead-letter" queue and never returned to the flow for processing again. They can be managed and recovered using the Unprocessed Messages Appmixer REST API.
For trigger-type of components, the most important virtual methods to remember is tick() and start().
Virtual Method
Description
receive(context)
Called whenever there are new messages on input ports that the component is ready to consume. This method must return a promise that when resolved, tells Appmixer that the messages were successfully processed. When rejected, the engine retries to send the messages to the component again later.
tick(context)
Called whenever the polling timer sends a tick. This method is usually used by trigger Components to implement a API polling mechanism or for schedulers.
start(context)
Called when Appmixer signals the component to start (when the flow starts). This method is usually used by trigger components that might schedule an internal timer to generate outgoing messages in regular intervals or to register a webhook URL (context.getWebhookUrl()
with a 3rd party API).
stop(context)
Called when Appmixer signals the component to stop (when the flow stops). This is the right place to do a graceful shutdown if necessary. Webhook-based trigger components use this place to unregister their webhook URLs with 3rd a party API.
All virtual methods have one argument, the context
. The context object contains all the information you need to process your messages and send new messages to the output ports.
(applies to receive()
)
Incoming messages. An object with keys pointing to the input ports. Each message has a content
property that contains the actual data of the message after all variables have been resolved (replaced with actual data). For example:
Remember, if before running the flow, the input port message
was defined in the Inspector using variables:
where the flow descriptor would contain something like this:
the context.messages
object contains the result of replacing variables with actual data that was sent through the output port of the connected component, i.e.
Each message also contains the correlation ID in the context.messages.myInputPort.correlationId
property.
correlationId
is a "session ID" that associates all the messages in one pass through the flow. Every time a trigger component sends a message to the flow (e.g. webhook, timer, ...) and the message does not have a correlation ID yet, the Appmixer engine assigns a new correlation ID to the message. This correlation ID is then copied to all the messages that were generated as a reaction to the original trigger message.
Call this method to emit a message on one of the components output ports. The first argument can be any JSON object and the second argument is the name of an output port. The function returns a promise that has to be either returned from the receive()
, tick()
or start()
methods or awaited.
A convenient method for sending an array of objects to an output port. Note that this method does not send the entire array to the output port in one go but rather sends items in the array one-by-one to the output port. Therefore, your output port schema definition should contain the schema of the items of the array, not the array itself.
The authentication object. It contains all the tokens you need to call your APIs. The authentication object contains properties that you defined in the auth
object in your Authentication module (auth.js
) for your connector or implicit properties in case of OAuth (context.auth.accessToken
). For example, if our authentication module for our service (auth.js
) looks like this:
we can use the context.auth.accountSID
and context.auth.authenticationToken
in the component virtual methods to access the values for those properties that Appmixer requested from end-users when they authenticated to the connector:
When you configure your connector in the Backoffice, you can access the values in the context.auth
or context.config
objects. context.config
is an alias to the original context.auth
. This is especially handy for any configuration that you might want to have dynamically changed without the need to redeploy your connector with new configuration.
The configuration properties of the component. This corresponds to the properties
object from the component manifest file. For example, if our component defines the following properties in the manifest file:
context.properties.fromNumber
will contain the value the user entered in the Designer UI Inspector:
A persistent state of the component. Sometimes you need to store data for the component that must be available across multiple receive()
calls for the same component instance. If you also need the data to be persistent when the flow is stopped and restarted again, set the state: { persistent: true }
property in your component manifest, otherwise, the context.state
will be cleared when the flow containing the component stops.
context.state
is a simple object with keys mapped to values that are stored in the internal Appmixer database. This object is loaded on-demand in each receive()
call. It is not recommended to store large amounts of data here. Example:
The context.state
is especially useful for trigger-type of components when polling an API for changes to e.g. store the ID of the latest processed item from the API.
The context.state
object should not be used to store large amounts of data. The state is loaded with each received message on a component input port. The maximum limit is 16MB but storing such large objects will heavily slow down the processing of the component input messages.
Load the component's state from internal DB. Normally, you do not need to call this method explicitely since the component's state is loaded just before the component is triggered and the state is available in context.state
. However, there are cases when a component needs to reload its state from the DB where this function is useful.
Save an updated state object. See context.state
for details. The function returns a promise that resolves if storing of the state was successful.
Set a state key
to hold the value
. key
must be a string. value
can be any JSON object.
Get a state value stored under key
.
Remove a value under key
.
Clears the entire state.
Add value into set under key
.
Remove value from set under key
.
Increment value under key
. The second parameter is optional and can be used to set the increment value. The function return by default the new value (after incremented), if returnOriginal
is set to true, it will return the value before the increment.
Similar to the component state, this state is available to all components in the flow.
Load the state from the DB.
Set a state key
to hold the value
. key
must be a string. value
can be a string, number or a JSON object.
Get a state value stored under key
.
Remove a value under key
.
Clears the entire state.
Add value
into a Set stored under key
.
Remove value
from Set stored under key
.
Increment value under key
. The second parameter is optional and can be used to set the increment value. The function return by default the new value (after incremented), if returnOriginal
is set to true, it will return the value before the increment.
This is similar to the component state, but the service state is available across all components in the connector.
Load the state from the DB. The returned value is an array of state items each having the key
and value
properties, e.g. [{ key: "A", value: 1 }, { key: "B", value: 2 }]
.
Set a state key
to hold the value
. key
must be a string. value
can be anything that can be stored in Mongo DB.
Get a state value stored under key
.
Remove a value under key
.
Clears the entire state.
Add value
into a Set stored under key
.
Remove value
from Set stored under key
.
Increment value under key
. The second parameter is optional and can be used to set the increment value. The function return by default the new value (after incremented), if returnOriginal
is set to true, it will return the value before the increment.
This method has been deprecated. Use context.saveFileStream instead. Save a file to the Appmixer file storage. This function returns a promise that when resolved gives you a UUID that identifies the stored file. You can pass this ID through your flow (send it to an output port of your component) so that later components can load the file from the Appmixer storage using the file ID.
Save a file to the Appmixer file storage. The function returns a Promise that resolves with the ID of the stored file ({ fileId }
). This is a more efficient and recommended version of context.saveFile(name, mimeType, buffer)
.
The structure of the returned object looks like this:
See, for example, the AWS S3 GetFileObject component for an example of how safeFileStream()
can be used.
Replaces the content of the file. Returns a Promise with the ID of the file { fileId }
. The fileId remains the same.
Return object:
Returns a promise, which when resolved returns the file information (name, length, content type...).
Example return object:
Load a file from the Appmixer file storage. The function returns a promise that when resolved, returns the file data as a Buffer.
This method has been deprecated. Use context.getFileReadStream instead. Read a file stream from the Appmixer file storage. The function returns a NodeJS read stream that you can e.g. pipe to other, write streams (usually to a request object when uploading a file to a 3rd party API). This is a more efficient and recommended version of context.loadFile(fileId)
.
Read a file stream from the Appmixer file storage. The function returns a Promise, which when resolved, returns a NodeJS read stream that you can e.g. pipe to other, write streams (usually to a request object when uploading a file to a 3rd party API). This is a more efficient and recommended version of context.loadFile(fileId)
.
Remove a file from the Appmixer file storage. The function returns a promise.
Get a URL that you can send data to with HTTP POST or GET requests. When the webhook URL is called, the receive()
method of your component is called by Appmixer with context.messages.webhook
object set and context.messages.webhook.content.data
containing the actual data sent to the webhook URL:
Note: The context.getWebhookUrl()
is only available if you set webhook: true
in your component manifest file (component.json). This tells Appmixer that this is a "webhook"-type of component.
The full context.messages.webhook
object contains the following properties:
Property
Description
content.method
HTTP method of the request.
content.hostname
Hostname of the Appmixer API.
content.headers
HTTP headers of the request.
content.query
Object with query parameters, i.e. query string parsed into a JSON object.
content.data
Object with the body parameters of the request.
correlationId
A special ID generated by Appmixer that uniquely identifies the input message which resulted in generating the webhook URL. In other words, if you call context.getWebhookUrl()
in the receive()
method in a reaction to an input message that arrived on an input port of the webhook component, the correlationId
will be part of the returned webhook URL. This allows you to later associate the input message with the HTTP call to the webhook. A common pattern is to store the input message in the context.state
object and later use the context.messages.webhook.correlationId
to retrieve it back. For example, if you have an input port named myInPort
, you can get the correlationId
of the input message that just arrived by accessing the context.messages.myInPort.correlationId.
Send a response to the webhook HTTP call. When you set your component to be a webhook-type of component (webhook: true
in your component.json file), context.getWebhookURL()
becomes available to you inside your component virtual methods. You can use this URL to send HTTP POST or GET requests to.
When a request is received by the component, the context.messages.webhook.content.data
contains the body of your HTTP request. In order to send a response to this HTTP call, you can use the context.response()
method. See context.getWebhookUrl()
for details and examples.
Since it is very common for components to initiate HTTP requests, Appmixer provides a convenient method to do so. The httpRequest
object/function is a wrapper around the well known axios library.
Get the list of user's Data Stores.
Get value from the Data Store, stored under the key.
Set value to the Data Store under the key.
Remove the key from the Data Store.
Clear all data from the Data Store.
Find items in the Data Store.
Get a cursor.
Register Data Store webhook. If no events are specified, then the component will get all events from the Data Store. Possible events are insert, update and delete.
And the same functionality with registering only for the insert events.
Unregister a webhook.
Set a timer that causes the component to receive messageContent
in the receive()
method in the special context.messages.timeout.content
object. delay
is the time, in milliseconds, the timer should wait before sending the messageContent
to the component itself. This is especially useful for any kind of scheduling components.
The context.setTimeout()
function works in a cluster environment as opposed to using the global setTimeout()
JavaScript function. For example, a component that just slows down incoming messages before sending them to its output port, waiting e.g. 5 minutes, can look like this:
You can also access the correlation ID of the timeout message which can be useful in some scenarios. The correlation ID is available in the context.messages.timeout.correlationId
property.
The return value from this context method is a timeout Id (a UUID string). Each timeout has its own unique identifier. That can be used to clear the timeout.
Clear (cancel) a scheduled timeout.
Call an Appmixer REST API endpoint. You can call any of the Appmixer endpoints defined in the API section. The main advantage of this method (as opposed to calling the API endpoint manually) is that the method automatically populates the "Authorization" header of the request to the access token of the user who owns the flow this component runs in. For example:
Stop the running flow. Example:
The ID of the component.
The ID of the flow the component runs in.
The flow descriptor of the running flow. This allows you to access configuration of the entire flow within your component virtual methods. To get the configuration of the component itself, you can use context.flowDescriptor[context.componentId]
. Note that this is normally not necessary since you can access the properties of the component with context.properties
and the current input message with context.messages.myInPort.content
but it can be useful in some advanced scenarios.
Flow customFields properties are available in this object.
This function lets you evaluate a JavaScript code in a sandbox. The first argument is the JavaScript code and the second is an object with data available to the code. The object is then available under $data
variable.
To support components that receive inputs and wait for some future asynchronous response to continue the flow execution from the state of the flow at the time the inputs arrived, Appmixer internally stores a data structure called Continuity scope. The Continuity scope is a document stored in the Appmixer internal DB and contains all the data the flow produced until it reached the component with the first message.
For example, some components receive an input message, call a 3rd party API, and wait for an asynchronous push-type of webhook call originating from the 3rd party API - that arrives at a later time - to receive at the component webhook URL. When the webhook arrives, the component produces a JSON and sends it to its output port. At this point, the component must have the state of the flow (all the related data from the same flow "run") to be able to resolve variables and continue execution.
The Continuity scope documents are deleted after a certain time (by default 100 days). If you need to have a component that can wait more than 100 days for the incoming webhook to resume the flow, you can use this function to adjust the timeout. The function excepts Date, number or a string
. If the argument is a number, it is considered to be the number of milliseconds from now (the time of the function call). If it is a string, it will be converted to Date
object using new Date(string)
function.
Load variables in your component. Variables are data available from components connected back in the chain. loadVariables()
returns a promise that resolves to an array that looks like this:
The array has as many items as there are other components connected to this component.
Example:
Log a message. The log message will be available to the end-users in the log panel of the Designer UI or in the Insights page of the Appmixer Studio. The argument has to be an object that can be stringified into JSON.
Example:
And the object can be seen in the log panel as:
This method allows components to create a cluster lock. This is useful when creating a mutually exclusive section inside the component's code. Such a thing can be achieved in Appmixer using either quota (you can define a quota the way that only one receive
call can be executed at a time) or using locks. This method returns the lock
instance. Don't forget to call lock.unlock()
when you're done. Otherwise, the lock will be released after TTL.
lockName
string will be automatically prefixed with vendor.service:
. If a component type is appmixer.google.gmail.NewEmail
, the lockName will be prefixed with appmixer.google:
. This allows you to create a lock that is shared among all components within a service and prevents possible collisions between components from different vendors or services.
The first parameter is required, the second (options) is optional with the following optional properties:
ttl
, number, 20000 by default (ms)
retryDelay
, number, 200 by default (ms)
maxRetryCount
, number, 30 by default
Example:
Every function a component implements may throw an exception (or return a rejected promise).
If this function throws an exception, Appmixer will try to process the message that triggered this receive
call again later using an exponential backoff strategy. In total, Appmixer will try to process the failing message 30 times before it is saved into unprocessedMessages
collection. Every unsuccessful attempt will be logged and visible in Insights.
Sometimes you, as a developer of a component, know that there is no point in retrying a message since no matter how many times Appmixer tries, the message will fail repeatedly. In such cases, you can tell Appmixer to cancel the message by throwing the context.CancelError(reason)
error object. This instructs Appmixer not to apply the auto-retry mechanism and the message will simply be discarded.
If a tick
function throws an exception, the exception will be logged (and visible in Insights). Appmixer will not apply the auto-retry mechanism since it assumes the developer handles the error manually inside the component code next time the tick()
function is executed.
Appmixer won't start a flow if any component in the flow throws an exception in the start
function. Such error will be logged and visible in Insights.
Appmixer will stop the flow even when a component in the flow throws an exception in the stop
function. Such errors will be logged and visible in Insights.