JSVM

The JS VM is the most powerful language, as it allows you to execute es6 javascript code directly on ubsub.

The incoming event is represented by the global payload. Contextual information will be in context. Use module.exports to write out your final transform.

Your environment is locked down, and you can only do very basic data manipulation.

Simple example:

module.exports = {
  a: payload.b[0].c,
  b: {
    c: 123
  }
};

You can also leverage certain libraries, such as lodash _:

module.exports = {
  keys: _.keys(payload),
};

If your script throws an error, it is equivalent to a payload rejection. console.log will be written to the output object, and sent to the response if an error occurs.

Restrictions

You can only use the libraries listed below.

Your script is capped at 100 ms execution time and 1500 ms total time (for delayed io).

Deferables / MicroPromises

The JSVM environment implements its own promise infrastructure for security and error-handling reasons. All code that is executed across boundaries is stringified and the callback is executed back in the VM. There are a few oddities that occur because of this.

As currently implemented, MPromises do NOT follow the A+ spec (though there are plans to get it as close as possible)

Using

Whenever you execute any code that contains a promise your entire script will be put into defer-mode. This means that the script will not finish evaluating until done(val) is called. Once done is called, no more deferables will be invoked, regardless of whether or not the .then chain has finished, and all outstanding promises will be canceled.

A very simple script:

time.sleep(500, () => {
  done(payload.val);
});

Alternatively, you can assign the promise to module.exports and the result of that promise will be exported.

module.exports = time.sleep(100)
  .then(x => 55); // Same as if done(55) was called

What happened to my scope?

Because each callback is executed within its own context, that means your can lose scope. There are a few ways around this, mainly:

  • Always pass variables into the next promise function
  • Use global variables to keep track of state

eg. This will not work:

request.get('http://www.example.com')
  .then(({status}) => {
    return store.set('result', status)
      .then(() => {
        console.log(`The status is: ${status}`); // ERROR HERE.  It does not have the scoped `status` as its being executed on its own.
      });
  });

Instead, you can simply do this:

request.get('http://www.example.com')
  .then(({status}) => {
    return store.set('result', status);
  }).then((store) => {
    console.log(`Stored status: ${store}`);
  });

Supported Libraries

There are a few libraries that support deferables. At this time, all accounts are allowed to use them.

  • time
  • request
  • store
  • MPromise

Context

Certain information about how the template was called will be present in the context variable. It contains:

{
  userId: 'xxx', // Your user id
  source: 'topic', // Source of the call (either 'topic', 'subscription' or `api`)
  sourceId: 'yyy', // The source trigger of template (either topic or subscription id)
}

Internal Libraries

Lodash

Export: _

Lodash is provided as a library you can use in JSVM.

Example:

module.exports = _.map(payload, x => x.name);

id.js

Export: id

Simple module to generate unique ids.

Functions:

short()

Generates a shortid eg frnEq3iuY.

id.short();
uuid()

Generates a v4 UUID.

id.uuid();

Check.js

Export: check

check.js is a small library that provides a way to execute checks and assertions against data and returns a bad request upon failure (as opposed to a 500 that would normally be returned by an error).

Short Example:

check.assert(1 == 2, 'The laws of the universe are failing');

Functions:

assert(truthy, msg = 'Truthy expression failed')

If first argument is falsy, will throw an error with the given message.

Example:

check.assert(1 === 2);
equals(actual, expected, msg = 'Deep equality failed')

Deep-compare the left and right value. Throw an error if there is any mismatch.

Example:

check.equals(payload, {
  a: 22  
});
lessThan(actual, comparator, msg = '...')

Check that actual is less than the comparator, otherwise throw an error with a message.

Example:

check.lessThan(5, 6);
greaterThan(actual, comparator, msg = '...')

Check that actual is greater than the comparator, otherwise throw an error with a message.

Example:

check.greaterThan(5, 6);

Time.js

Export: time

Functions

time()

Return the number of ms since epoch (same as new Date())

sleep(ms, [result])

Defers the requested number of milliseconds. Optionally passes a value to the next promise.


MPromise

Export: MPromise

Promise functionality.

Functions:

resolve(val)

Resolves a current promise with a given value.

MPromise.resolve(5)
  .then(ret => {
    done(ret);
  });
reject(err)

Rejects the current promise. Either handled via a .catch or will end the script.

MPromise.reject(new Error('Boom'));
all([promise1, promise2, ...])

Creates a promise that resolves once all promises within the array resolve. Array can also contain values that do not need resolving.

MPromise.all([
    time.sleep(5, 1),
    time.sleep(5, 2),
    22
  ]).then(result => {
    // result == [1, 2, 22]
    done(result);
  });
map([promise1, promise2, ...], mapFunc)

Executes each promise or value, and runs the mapping function against it. Resolves once all promises and values have been mapped.

MPromise.map([
    time.sleep(5, 1),
    time.sleep(5, 2),
    22
  ], x => x * 2)
  .then(result => {
    // result == [2, 4, 44]
    done(result);
  });

Request

Export: request

Make Http Requests

Functions:

In all cases opts can contain the following keys:

{
  params: {a: 123, }, // Query parameters to add to the URL
  headers: {}, // Headers to add to the request
  auth: {}, // Authentication to add to the request
}

The return is:

{
  status: 200, // HTTP Status code
  data: {}, // Any data returned by response (in JSON)
  headers: {}, // Headers returned by response
}
get(url, [opts])

Do an HTTP GET request with optional options.

post(url, data, [opts])

Do a HTTP POST request with a payload with data.

put(url, data, [opts])

Do a HTTP PUT request with a payload with data.

patch(url, data, [opts])

Do a HTTP PATCH request with a payload with data.

delete(url, [opts])

Do a HTTP DELETE request.

Store

Export: store

Execute functions against the simple data store.

Functions:

get(name, [default])

Get a value by its name. If value doesn't exist, throw an error unless default is set, then return default.

Example:

store.get('test')
  .then(val => {
    done(val);
  });
getEx(name, [default])

Get a value by its name. If value doesn't exist, throw an error unless default is set, then return default.

Returns extended object rather than just the value. If value didn't exist, createdAt and updatedAt will be undefined.

{
    "name": "test2",
    "value": "hi2",
    "createdAt": "2018-07-28T20:08:26.718Z",
    "updatedAt": "2018-07-28T20:08:26.718Z"
}
set(name, value, [key])

Set a value at a name.

Example:

store.set('abc', 123)
  .then(val => {
    done(val); //123
  });
getAll()

Returns key-value pairs of all values stored for your user.

store.getAll()
  .then(values => {
    done(values);
    /*
    {
      a: 123,
      b: 22,
      ...
    }
    */
  });
delete(name)

Delete a value with the given name.

Execute functions against the cron service.

Cron

Export: cron

Functions

schedule(date, topicId, topicKey = null, payload = null)

Schedule a topic to be triggered at a given date in the future.

cron.schedule(new Date() + 1000, 'abcd')
  .then(job => {
    done(job);
  });
delete(tabId)

Deletes a previously created tab id (returned from schedule)

cron.schedule(new Date() + 1000, 'abcd')
  .then(job => {
    return cron.delete(job.id); // Will never run!
  });