TypeScript SDK

TypeScript SDK is a collection of objects and methods for scripting in ELMA365.

What is a script

System scripts allow you to implement complex logic for working with system objects, and are a powerful tool for adjusting the system to the company's needs. A script is a set of functions written in TypeScript. Every script has a launch context and access to other Global constants.

Where can scripts be used

Scripts can be run both on the server-side and in the client browser. At the moment, the server and client scripts work in the same with just one difference: when scripting in a browser, the scripts are limited by the permissions of the user in whose browser they run. Server-side scripts can be run only with the administrator permissions.

Server-side scripts

Scripts can be used in a business process through the Script activity. The process instance serves as the context for such scripts.

Important! Server-side scripts are time-limited. If the script is not completed within a minute, it is aborted.

Client scripts

Client scripts can be used when building pages with widgets.

What can be done with scripts

With scripts we can create, request, adjust and delete app items, and interact with external systems through the HTTP protocol.

async function createOrder() {
    // Shopping cart
    const items = await Context.fields.items.fetchAll();
    // Total cost
    const total = items
        .map(item => item.price)
        .reduce((acc, price) => acc.add(price));
    // Create order
    const order = Context.fields.order.app.create();
    order.data.agent = Context.data.__createdBy;
    // Apply discount
    order.data.total = total.multiply(0.75);
    // Save order
    await order.save();
    Context.data.order = order;
    // Send order ID to site
    await fetch(`https://my-store.ru/orders/${ Context.data.storeId }`, {
        method: 'PATCH',
        headers: {
            Authorization: 'bearer MY-SECRET-TOKEN',
        }
        body: JSON.stringify({
            QBPMOrderId: order.data.__id,
        }),
    });
}

How it works

A script is a file with TypeScript code. For working with scripts, the system provides a built-in editor based on Monaco , where you can add TypeScript SDK definition files for autocompletion. When publishing a process or a widget, the scripts are validated and converted to javascript that will be run later.

While running, the sandbox is created where the entire script file is executed, and after that a certain function is called. In processes, the scripts are run for every step. In a widget, the script runs once when the widget is loading, then only single functions are executed.

Async / await

When working with scripts, keep in mind that many operations are asynchronous: getting an item by URL, saving an item, working with an external service. These methods return Promise. Promise guarantees getting the result of the operation. In case we need the result of the operation in order to execute other activities, we need to wait for the Promise. For better code readability, we recommend using syntax async/await.

Syntax:

  • async - means that asynchronous operations will be run in the function
  • await - ensures that Promise will be executed, and we will obtain the result of the asynchronous operation

Let's take a look at the following case of getting the current user by indicated email from the list of all the users transferred in the context:

// Put async ahead of our function, defining that asynchronous operations will be run
async function getUsers(): Promise<UserItem> {
    // method fetchAll is asynchronous method, so we put await to wait for the result
    // in case we do not wait for the result, the script will continue to run 
    // so, the script will return the Promise-type object, not the array of users
    const users = await Context.fields.users.fetchAll();
    const currentUser = users.find((user: UserItem) => user.data.email === Context.data.email);
    return currentUser;
}

Promise.all

Promise.all converts the Promise array into the Promise of the result array. In other words, it allows you to wait for all the promises transferred into this structure to be resolved, and, after that, it returns the array of execution result. Let’s assume that we need to download several files from external resources:

async function loadFiles(): Promise<Array<ApplicationItem<T>>> {
    // Obtain an array of parameters to download files of app type
    const filesParams = await Context.fields.filesParams!.fetchAll();
    // using the map array method, obtain a promises array
    const promises = filesParams.map(async (fileParams: Array<ApplicationItem<T>>) => {
        // write the corresponding array values into the corresponding variables,
        // which return Promise.all
        const [name, link] = Promise.all([
            await fileParams.data.name!.fetch(),
            await fileParams.data.link!.fetch()
        ]);
        // The file with an index-based name is created in the process context
        Context.fields.file.createFromLink(`${ name }`, `${ link }`);
    });
    // Put the obtained promises array into Promise.all to get the downloaded files
    // Wait for the result
    const files = await Promise.all(promises);
    // Return the array of downloaded files for further use
    return files;
}

Non-Null Assertion Operator (!)

This is a postfix operator. When placing it after the expression statement, we confirm that this statement cannot be null and undefined. In this regard, one must be careful in using "!" when modeling the solution, and should be aware that by the moment the script runs, this field is neither null nor undefined, otherwise the script will fail at this line.

// Let's consider a standard case of obtaining an app item.
// The app! structure means that app cannot be null or undefined, and we can use it
const app = await Context.data.app!.fetch();
app.data.file = Context.data.file;
await app.save();
// If there is a possibility that the field is null or undefined by the time the script is executed,
// then it is better to add condition checks
deal.data.outstanding = (deal.data.contract_amount || new Money(0, 'EURO').add(deal.data.received_payments.multiply(-1));
// Without a check, this line would be written like this:
deal.data.outstanding = new Money(deal.data.contract_amount!.asFloat() - deal.data.received_payments.asFloat(), 'EURO' );

Help Center Layout

Help Center includes the following sections:

  • Object types — this section describes object interfaces available from the scripts.
  • Data types — this section is dedicated to basic system types.
  • Global constants — a list of global constants available in scripts.
  • Working with applications — this section determines such techniques for working with app items as creation, search, modification, and deletion.
  • Working with external services — description of the fetch function, which can be used to send HTTP requests to external systems.