node package manager
Orgs are free. Discover, share, and reuse code in your team. Create a free org »

choreographer-express

Choreographer SDK for ExpressJS

A client SDK for Choreographer, used for building express.js based web applications. Hides most of the complexity by wrapping javascript wrappers around ExpressJS middleware. Below are the key features –

  • easily deploy the model file
  • re-create instances at a route
  • complete tasks
  • get information about the model instances
  • add custom variable into active instances
  • retrieve all available variables at a given time

You can use this SDK in two approaches, depending on your use cases.

  • Use install middleware and bind CG context to Express request
  • Create your own CG instance and handle all operations with your own logic. You should avoid this approach unless you've pressing reasons to do so.

But that's not all, you can also mix both these approaches.

Getting started

This section assumes you've working knowledge of Nodejs and Express web framework.

Prerequisites

Before you begin, please install few required libraries/tools to make sure you have everything to run the app. Run below commands in your terminal or console one by one and ensure these are installed. If you already have ES6 installed or you're using Webpack, ignore below steps.

$ npm install -g babel-cli
$ npm install -g babel-boilerplate

From the folder of your choice, run –

$ es6-babel-generate ask.name && cd ask.name && rm src/*.js && touch src/index.js model.conf

If you are running on windows command prompt use this one instead –

$ es6-babel-generate ask.name && cd ask.name && del "src\*.js" && echo. > src/index.js && echo. > model.conf

This command will create a folder named ask.name and install all required ES6 libraries. At this point, your folder structure should look like this –

$ /ask.name
.
├── .babelrc
├── .npmignore
├── model.conf
├── package.json
└── src
    └── index.js

1 directory, 5 files

Now, install Express and Choreographer Express SDK in this folder –

$ npm install
$ npm install --save express
$ npm install --save choreographer-express

You also need Choreographer server to be running for this example code to run, you can do so by running Choreographer Console, which self hosts a Choreographer server.

Model

Copy and paste below model in model.conf present in the root folder.

modelInfo.name = "ask.name"
 
userTasks.askName {
  expectedInputs {
    name {
      type = "string"
    }
  }
}
 
sequenceFlows.1.start = askName
sequenceFlows.1.end = askName

This is a simple model that expects user to provide their name. Nothing useful however we will eventually build complex scenarios in this model later on in this documentation.

Notice the model.conf file is kept in the root. CG expects model.conf to be in the root as it automatically finds and deploys it. You can also customize the defaults, you will see that in a bit.

You need not build any UI but only REST endpoints exposed through a simple Express server and SDK helps you to execute the model you created. Now write src/index.js file, essentially the express server –

import cg from 'choreographer-express';
import express from 'express';
 
let app = express();
 
app.use(cg.install({
  newInstanceTrigger: (req) =>
    req._parsedUrl.pathname === 'askName'
}));
 
app.get('/', (req, res) => res.send("Running ... "))
 
// user submits the form so we need to complete the task askName 
app.post('/ask-name', (req, res) => {
  var value = req.body.data.name;
  req.completeTask('askName', {
    name: value
  })
  .then((taskInfo) => res.send(`Hello ${taskInfo.vars.name}`))
  .catch((err) => res.send(`Something went wrong - ${err}`));
});
 
app.listen(3000);
 

Above example uses req.completeTask to complete the task askName. The cg.install takes care of installing the model that is present in the root folder.

Now run npm run build && node dist/index.js. At this point, the server should be running and listening at port 3000.

Visit http://localhost:3000 and you should see Running ... text in your browser.

API

The SDK lets you tune its strings, if you are not satisfied with the default settings, you can configure it using wrenches that come along in form of options and other parameters

passRequestHeaders ()

Retuns middleware function. Set the header in Choreographer server requests. A simple example –

app.use(cg.passRequestHeaders());

install (options)

If you are easy-going, don't like that extra baggage, no worries – install will do all the heavy lifting!

options.modelName

Model configuration file name, defaults to model.conf in the root path.

options.serviceUrl

Choreographer url, defaults to http://0.0.0.0:9000

options.newInstanceTrigger

A trigger function that returns true or false to decide whether to start a new instance. By default, whenever user visits the root path (/) a new instance will be created and older ones are ignored (older ones are just left as it is in CG).

A simple example –

app.use(cg.install({
  newInstanceTrigger: (req) => req._parsedUrl.pathname === '/dashboard' && req.method === 'POST';
}));

This example starts a new instance of the model every time the user logs in and reaches dashboard.

options.cookieSettings.name & options.cookieSettings.maxAge

A cookie name to be used while setting the modelInstanceId in the cookies. SDK takes care of setting modelInstanceId in the cookies to remember when newInstanceTrigger returns true therefore it will reuse the same instance id. CG does not provide any session-like features and left it to client sdks to handle the way sdks will be implemented depending upon the underlying framework.

app.use(cg.install({
  cookieSettings: {
    name: 'mt-instance',
    maxAge: 60 * 60 * 24  // 1 day 
  }
}));

options.cookieSettings.name defaults to cg-instance and options.cookieSettings.maxAge defaults to 0 days, meaning, until the current browser session.

options.initialData

Initial JSON object to be used while starting a new instance. By default, its value is an empty object.

A simple example –

app.use(cg.install({
  initialData: {
    "someVariable" : "value"
  }
}));

This example starts a new instance of the model with initial JSON

{
    "someVariable" : "value"
}

options.headers

This contains the list of keys or a string which can be consider as regx expression. This key will provide the headers, which we want to set in Choreographer request's header. By default, its value is an empty array.

app.use(cg.install({
  headers: "x-custom"
}));

Example of header list:

app.use(cg.install({
  headers: ["x-custom-header-1","x-custom-header-2"]
}));

Below is a complete example of install with custom settings –

const triggerFunc = (req) => {
  return req.isLoggedIn == true;
};
 
app.use(cg.install({
  serviceUrl: 'http://cgdev.exzeo.com',
  newInstanceTrigger: triggerFunc,
  cookieSettings: {
    name: 'someName',
    maxAge: 60 // 60 minutes 
  },
  initialData: {
    "someVariable" : "value"
  }
}));

deploy (modelFileName, options)

Note: In most cases you don't need to call this method if you're using install middleware.

Deploys a model file mentioned in modelFileName parameter which defaults to /model.conf. This function takes care of memoizing model deployments, meaning, already deployed models will not be deployed every time it is called during runtime.

app.use((req, res, next) -> {
  cg.deploy('conf/some.conf')
    .then((s) => {
      next();
    })
    .catch(s) => {
      console.error(`couldn't deploy the model - ${s}`);
      next();
    });  
});

modelFileName

The name of the model file along with path.

options.forceDeploy

Deploys the model forcefully. Note that setting forceDeploy is merely suggesting SDK to ignore memoization of the previous deployments and forcefully call CG to deploy the model. However, CG only re-deploys in case the model has some changes in model content.

app.use(function(req, res, next) {
  cg.deploy('conf/some.conf', {forceDeploy: true});
})

deployStub (modelName, stubFileName)

Deploys a js stub file mentioned in stubFileName parameter which defaults to /model.stub.js, aganist the model mentioned in modelName parameter.

Note: In cases of using install middleware. You dont need to pass the modelName.

app.use((req, res, next) -> {
  cg.deployStub('modelName', 'conf/model.stub.js')
    .then((s) => {
      next();
    })
    .catch(s) => {
      console.error(`couldn't deploy the stub - ${s}`);
      next();
    });  
});

modelName

the name of model defined in model conf file.

stubFileName

The name of the js stub file along with path for a deployed model.

start ([modelName], [data], [options])

NOTE: If you've used install middleware, you don't need to use this method to start an instance.

Starts new instance for a given model name (not the file name, it can be different). The modelName is optional. In case, one is not passed and install middleware is already called, this method try to look for modelName from Router's request object otherwise throws an error. A method called cg.modelInfo() provides complete response after starting the model instance.

modelName

Name of the model. The file name and modelName can be different.

[data]

Optional. Initial JSON object to be used while starting the model.

[options.forceStart]

Optional. Defaults to false. In case of true and install middleware was called before it starts new instance even there are active instances available. It also replaces current modelInstanceId to the new instance id in the cookies. Also note, if middleware wasn't used then this flag has no effect.

Example

NOTE: If you'e used install middleware and want to start an instance of the model from the Express request object

let initialData = {
  name: "blah"
};
 
let options = {
  forceStart: true
};
 
req.start(initialData, options);

NOTE: If you've not used install and want to handle your own way of starting model instances then you should've deployed your model before calling start, like below –

import cg from `choreographer-express`;
 
let initialData = {
  name: "blah"
};
 
let options = {
  forceStart: true
};
 
cg.deploy('some-conf.conf')
  .then(cg.start('someModelName', initialData, options)
  .then((res) => {
    console.log(`someModelName and started ...`)
  })
  .catch((err) => {
    console.err (`something went wrong`)
  }));
 

completeTask (taskName, [modelInstanceId], [data])

Completes a task with any input data required for the task to be completed in CG. In case, a modelInstanceId is not provided, this method assumes install middleware was called earlier.

taskName

Required. Name of the task.

[modelInstanceId]

Optional, only required when install middleware was not used to setup CG.

[data]

Optional. JSON object to be used as variables while completing the task.

Example

req.completeTask('askName', { data: "some"})

moveToTask (taskName, [modelInstanceId])

Converts a completed task to an active task with its field values as default. Field values would be the values that were provided at the time of marking this task as complete. In case, a modelInstanceId is not provided, this method assumes install middleware was called earlier.

Note: Response will give you new instance id. Use new instance id to work on new active task.

taskName

Required. Name of the task.

[modelInstanceId]

Optional, only required when install middleware was not used to setup CG.

Example

cg.moveToTask('askName', 'instanceId')

addVariables ([modelInstanceId], data)

Adds new variable into given modelInstanceId. The data should be a valid JSON object.

[modelInstanceId]

Optional, only required when install middleware was not used to setup CG.

[data]

Optional. JSON object to be used as variables while completing the task.

Example

req.addVariables({
  "var1" : "some-value"
});

variables([modelInstanceId])

Returns list of available variables for a given modelInstanceId.

Example

req.variables();

[modelInstanceId]

Optional, only required when install middleware was not used to setup CG.

instance([modelInstanceId])

Returns complete status information about a given modelInstanceId.

[modelInstanceId]

Optional, only required when install middleware was not used to setup CG.

Example

req.instance();

getDefinition([modelName])

Returns model definetion for the given modelName.

[modelName]

Optional, only required when install middleware was not used to setup CG.

Example

//When install middleware is used 
req.getDefinition();
 
//when install middleware is not used 
cg.getDefinition('someModelName');

configure (options)

You can configure the SDK at your own by calling this function and pass optionsoptions here has same properties as in install(options)

Below is a complete example of configure with custom settings –

const triggerFunc = (req) => {
  return req.isLoggedIn == true;
};
 
cg.configure({
  serviceUrl: 'http://cgdev.exzeo.com',
  newInstanceTrigger: triggerFunc,
  cookieSettings: {
    name: 'someName',
    maxAge: 60 // 60 minutes 
  }
});