Extension Development
The goal of this section is to provide a detailed overview over the automation script development process, while omitting low-level details about the system itself.
The section also provides some tips and tricks for faster, better and more secure development process.
This section is stand alone, meaning it’s independent of the previous section [ext-facility]. |
Naming Conventions
We define the following set of naming conventions for a more consistent experience across different extensions:
@wip
- File names
-
File names should be written in pascal case, ending with a
.js
extension (eg.SetLabel.js
), - conv1
-
thing…
Helper Classes
Helper classes consist of methods that greatly reduce the needed effort and the amount of required code to perform a specific operation, so it’s recommended to use them as much as possible. These classes are also context aware, meaning that the classes are able to determine some base parameters on their own, such as the current module, record and namespace.
Example of creating a new record with the help of Compose
helper class (note how we don’t need to specify the namespace):
Compose.makeRecord({ Title: 'Lead title' }, 'Lead')
.then(myLead => Compose.saveRecord(myLead))
.then(mySavedLead => {
mySavedLead.recordID // saved recordID
})
All helper classes are available in the automation script’s execution context:
ctx.Compose
-
Provides functions to work with Corteza Low Code, such as accessing, creating and deleting records. See the definition for details,
ctx.ComposeUI
-
Provides functions to work with user interface for Corteza Low Code, such as opening record pages and showing notifications. See the definition for details,
ctx.Messaging
-
Provides functions to work with Corteza Messaging, such as creating channels and creating messages. See the definition for details,
ctx.System
-
Provides functions to work with Corteza core system, such as finding and creating users and managing role membership. See the definition for details.
Automation Script Anatomy
To better understand the process of implementing custom functionalities with the automation system, we define and describe the anatomy of automation scripts, and establish a few rules. An automation script is considered valid if:
-
a given
.js
file is structured underclient-scripts
orserver-scripts
directory, -
a given
.js
file exports exactly one JS object with theexport default
keyword, -
a given script defines at least one valid trigger or iterator definition,
-
a given script defines the security context if the script is a deferred or sink script,
-
a given JS object defines an
exec (args, ctx)
function.
Invalid automation scripts are still processed by the Corredor, but are excluded from further operations. Detected errors are visible in the Corredor logs. This allows for easier debugging. |
Overview of script object properties and function
label
-
Automation script’s label, used to provide a user-friendly identification of the automation script,
description
-
A more verbose automation script description,
security
-
Defines automation script’s security context and role based filtering,
triggers
-
Defines automation script’s triggers as propery or method. Not compatible with
iterator
, iterator
-
Defines automation script’s iterator as property or method Not compatible with
triggers
, exec
-
Execution function that is invoked when the automation script is triggered.
Security
Script’s security
property allows definition of user who’s credentials will be used when executing the script.
It also allows access control by defining list of allowed and denied roles that can execute it.
security.runAs
-
Define security context for the automation script,
security.allow
-
Explicitly define what roles have access to the automation script,
security.deny
-
Explicitly define what roles do not have access to the automation script,
|
Exec function
The execution function defines the actual code for the automation script, and conforms to the following interface:
interface ScriptFn {
(args: exec.Args, ctx?: exec.Ctx): unknown;
}
Execution arguments
Execution arguments (first argument) contain the arguments that the automation script can work with, such as a newly created record, updated module, deleted page, … Arguments depend on the automation script’s event and resource (see [ext-resevt]).
Execution context
Execution context (second argument) contains contextual information about the execution, such as security context, helper class instances, API clients, loggers, …
ctx.console
-
console
object that can be used for logging. When running in the UA, this will be nativewindow.console
. When running in Corredor, this will be aPino
instance, ctx.log
-
Shortcut for
ctx.console.log
, ctx.$authUser
-
User object corresponding to the security context,
ctx.SystemAPI
-
Full blown API for Corteza System interaction,
ctx.ComposeAPI
-
Full blown API for Corteza Compose interaction,
ctx.MessagingAPI
-
Full blown API for Corteza Messaging interaction,
ctx.System
-
Helper class instance for the Corteza System,
ctx.Compose
-
Helper class instance for the Corteza Compose,
ctx.ComposeUI
-
Helper class instance for the Corteza Compose user interface,
ctx.Messaging
-
Helper class instance for the Corteza Messaging,
ctx.frontendBaseURL
-
Base URL used by front-end web applications. This is useful when generating URL’s inside server scripts.
Automation script execution result
When the script’s execution is complete, it should provide one of the following results:
- Unknown Error
-
An error aborts the script’s execution chain (the current script and all following scripts).
- Aborted Error
-
An error with the value of
'Aborted'
stops the execution of the current automation script. Any further scripts down the chain are executed as usual. false
-
A return value of
false
stops the execution of the current automation script. Any further scripts down the chain are executed as usual. - unknown
-
Any other return value specifies that the execution was successful and the following script (if any) can execute.
If the resource defines a before event variant, the return value will be used as the updated version of that resource.
For example: if the automation script is defined with the resource type of |
Server script vs. client script
There is a small deviation when it comes to client and server script executions. One of the differences is that, when it comes to client scripts, the arguments are provided as a reference, meaning that any changes to the resource are reflected to the original object. Because of that the resource does not need to be returned in order for the changes to take effect.
For example, running the following code in the client script’s exec function will reflect the values without the need of returning the updated record.
{
...
async exec ({ $record }) {
$record.values.Field1 = 'value1'
$record.values.Field2 = 10
},
}
If your script should be able to revert the changes, in case of an error, you should use an intermediate object for the new values. For example:
{
...
async exec ({ $record }) {
const v = { ...$record.values }
v.Field1 = 'value1'
v.Field2 = 10
try {
await apiOperation($record)
} catch (e) {
throw new Error(e)
}
$record.setValues(v)
},
}
Automation Trigger Anatomy
To better understand the power of automation triggers and their definition, we define and describe the anatomy of automation triggers.
Automation triggers are constructed with the following methods:
t.on
-
Defines the event type, such as
'manual'
,'request'
(see [ext-resevt]). t.for
-
Defines the resource type, such as
'compose:record'
(see [ext-resevt]), t.where
-
Defines the constraints that must match in order for the automation script to be executed (eg.
t.where('module', 'Lead')
). Refer to Constraints for details, t.before
-
Defines that the automation script is executed before a specific operation occurs,
t.after
-
Defines that the automation script is executed after a specific operation occurs,
t.at
-
Defines that the automation script is executed at a specific time,
t.every
-
Defines the interval in which the automation script is executed,
t.uiProp
-
Defines the visual representation of the manual automation trigger, such as its color and label.
Trigger and iterator are not compatible; you can only use one or the other within the same automation script. |
Triggers will be evaluated in an isolated context outside of the actual automation script file. This prevents use of any constants or other symbols defiled outside of the trigger. For example, the following trigger will cause:
|
When defining the automation script, you can use object destructuring to remove a few bits of code.
|
If the automation script contains just a single trigger, you can omit the array and just return the one.
|
Constraints
A constraint consists of 3 components:
-
resource attribute name (see [ext-constraintResources]),
-
operation,
-
value.
Constraint operations
-
Equal: check for equality between the two values. Available operators:
-
eq
-
=
-
==
-
===
-
Equality check is default operator. When using only 2 parameters for constraint, "equal" operator is used |
-
Not equal: negated check for equality between the two values. Available operators:
-
not eq
-
ne
-
!=
-
!==
-
-
Like: check for string equality, with support for wildcards. Available operators:
-
like
-
-
Not like: negated check for string equality, with support for wildcards. Available operators:
-
not like
-
Available wildcards:
|
-
Match: check for string equality, defined as a regular expression. Available operators:
-
~
-
-
Not match: negated check for string equality, defined as a regular expression. Available operators:
-
!~
-
Automation Iterator Anatomy
To better understand the power of automation iterators and their definition, we define and describe their anatomy.
Automation iterator are constructed with the following methods:
i
-
Defines the resource, action and the filter used for resource fetching. Passed object must conform to the interface:
interface {
resourceType: string; (1)
eventType: 'onManual' | 'onInterval' | 'onTimestamp' = 'onManual'; (2)
action: 'update' | 'delete' | 'clone' | '' = ''; (3)
filter: IteratorFilter; (4)
}
1 | define the resource type; see [ext-resevt] for details. |
2 | define the event type; see [ext-resevt] for details. |
3 | define what action the script performs; see Iterator actions for details, |
4 | define the filter to use; see Filter for details.
|
Iterator actions
- update
-
The result of the automation script will be used to update the original resource,
- delete
-
The result of the automation script will be used to delete the original resource,
- clone
-
The result of the automation script will be used to create a new resource with updated values; original resource is left unchanged,
Iterator script doesn’t perform any operation on the resource except if explicitly specified by the |
Iterator and trigger are not compatible; you can only use one or the other within the same automation script. |
Filter
Iterator filter provides ability to write query (conditions), sorting, limit and offset rules It closely resembles Corteza’s filter object and conforms to the following interface:
interface {
query: string; (1)
sort: string; (2)
limit: number | string; (3)
offset: number | string; (4)
[_: string]: number | string; (5)
}
1 | SQL like query filter to use (WHERE <query> ). |
2 | SQL like sort to use (ORDER BY <sort> ). |
3 | Maximum number of resource. |
4 | Offset from first record. |
5 | additional non-standard resource specific parameters. |
Client Scripts
Client scripts are automation scripts executed in the client’s browser (user agent; UA). Client scripts should be used when:
-
interaction with the user is required,
-
response latency should be minimal,
-
execution reliability is not important,
-
we are not working with sensitive information such as authentication tokens or api credentials,
-
automation script produces a light load on system resources.
Example use cases
-
prompt user to confirm form submission,
-
validate or modify form’s data before submission,
-
redirect the user to another page; either in Corteza Low Code or other,
-
open an external resource inside a popup window.
Trigger types
Client scripts support two types of triggers; explicit and implicit.
Explicit
Explicit triggers execute on a specific user invocation, such as a button press. These include:
- Manual
-
Manual automation triggers are defined as buttons inside the user interface. They most commonly appear inside Low Code automation page blocks or inside Low Code record list toolbars. They are invoked and executed inside the web application.
Manual automation triggers don’t need to be defined as client scripts. When defined as a server script it will be invoked from the UA and executed from inside Corredor. |
Implicit
Implicit triggers execute as a collateral to another system event such as form submission. These triggers include before/after events. For a full list of available events refer to [ext-resevt].
Server Scripts
Server scripts are processed, served and executed by Corredor server.
Server scripts should be used when:
-
interaction with the user is not required,
-
response latency is not as important,
-
execution consistency is important,
-
automation script produces a heavy load on system resources
Example use case
-
insert additional record fields based on an external data source,
-
send an email when a new user signs up,
-
run statistic operations once a month for reporting purposes.
Trigger types
Server scripts support a few different trigger types; explicit, implicit, deferred, iterators and sink.
Explicit
Explicit triggers execute on a specific user invocation, such as a button press. These include:
- Manual
-
Manual automation triggers are defined as buttons inside the user interface. They most commonly appear inside Low Code automation page blocks or inside Low Code record list toolbars. They are invoked in UA and executed inside Corredor.
Implicit
Implicit triggers execute as a collateral to another system event such as record creation. These triggers include before/after events. For a full list of available events refer to [ext-resevt].
Deferred
Deferred automation triggers are executed at a specific point in time; once or multiple times, such as a reminder. These include:
- Scheduled
-
scheduled triggers are executed at an exact time specified by a time stamp in ISO 8601 format (
YYYY-MM-DDTHH:mm:ss.sssZ
). This trigger is executed exactly once, - Interval
-
interval triggers are executed periodically in an interval defined by a cron expression (see robfig/cron package for details).
Deferred automation triggers are executed at most once every minute, so you should not define an interval or timestamp that uses higher precision (seconds or milliseconds). |
Record Validation
Automation scripts provide the ability to validate records before they are created or updated. This helps us define a system that is more resilient to the dangers of malformed user input.
Record validation from a script can be performed by scripts that are executed on: server scripts:
client scripts:
|
When we wish to warn the user about a value error, the flow differs from client and server scripts, but the result is the same — a set of validator.ValidatorError
objects contained inside a validator.Validated
object (refer to [coredev-compose-recordValidation] for details).
interface ValidatorError {
kind: string;
message: string;
meta: { [key: string]: unknown };
}
interface Validated {
set: ValidatorError[];
}
Server scripts
When we wish to provide value errors from server scripts, we simply throw an instance of validator.ValidatorError
.
For example:
import { validator } from '@cortezaproject/corteza-js'
export default {
...
async exec ({ $record }) {
if ($record.value.Field !== 'Super Specific Value') {
throw new validator.ValidatorError({
kind: 'invalidValue',
message: 'You didn\'t inter the super specific value',
meta: {
field: 'Field',
recordID: $record.recordID,
},
})
}
},
}
Client Scripts
When executing client scripts, two extra parameters are present:
ctx.validator
-
The parameter contains the
compose.RecordValidator
object, that can be used to validate the record this script is executing for. ctx.errors
-
The parameter contains the
validator.Validated
object, that contains current errors, and provides a place to store new errors.
When we wish to provide value errors from inside client scripts, we either use the ctx.validator
or construct a validator.ValidatorError
object manually.
The validator.ValidatorErrors
should then be pushed into ctx.errors
.
For example:
import { validator } from '@cortezaproject/corteza-js'
export default {
...
async exec ({ $record }, { errors, validator }) {
const errs = new validator.Validated()
if ($record.values.Field !== 'Super Specific Value') {
errs.push(new validator.ValidatorError({
kind: 'invalidValue',
message: 'You didn\'t inter the super specific value',
meta: {
field: 'Field',
recordID: $record.recordID,
},
}))
}
$record.values.FullName = `${$record.values.FirstName} ${$record.values.LastName}`
errs.push(...validator.run($record).set)
errors.push(...errs)
},
}
When the validator is unable to find any errors, the returned
|