A configurable data workflow engine. Configure an engine with:
- a workflow configuration JSON
- a
computation_context
The engine is designed as generically as possible, so that a wide range of workflows can be implemented. Some examples might include:
- A dynamic loan application
- Customer support tool
The engine is environment agnostic, so the same workflow configuration can be applied client and server side for secure workflow instance verification/validation.
View an online demo here: https://lukepur.github.io/data-workflow-engine.
The workflow configuration object has the following properties:
sections
: an array of items with atype
of 'section' - nodes which make up the input components of the workflowdecisions
: nodes which allow conditional paths through the workflowedges
: an array ofedge
items which describe the paths connecting the nodes of the workflowderived_data
: an array of derived data items which compute the output of running custom functions on a data instance
These entities have the following properties:
id
: a unique string reference to thissection
children
: an array of thesection
's items (eithergroup
, orvalue
)
An section
of a data instance can be in one of the following states:
invalid
: there are validation errors in thesection
valid
: there are no validation errors in thesection
id
: a unique string reference to thisdecision
output
: thefunc_ref
that will be evaluated on the data instance to return either true or false as the input for a connected outgoingedge
from
: the id of the node this edge directs fromto
: the id of the node this edge direct towhen_input_is
: [optional] activate this edge when the input node istrue
orfalse
. This property is only appropriate when thefrom
id refers to adecision
node
id
: a unique string reference for thisderived_data
fn
: thefunc_ref
that will be evaluated on the current data instance to determine the value assigned toid
in the output data
A func_ref
is a descriptor for run-time computations against a data instance. A func_ref
has the following properties:
fn
: the name of the function to invoke. This must be a pre-configured method available on thecomputation_context
used to configure the engine instanceargs
: an array of arguments to pass into the function. The following special tokens can be used as items:$.<path>
: de-reference the value atpath
of the data instance. See path resolving for more details$value
: de-reference the value of the current node. Undefined if nodetype
is notvalue
The following special characters can be used in paths:
*
: selects all array items at this level in the path. Note, can be used more than once in a path, and items from other path branches will be included^
: selects the array index that matches the instance index of this node. Useful, for example, to select sibling values
If a different structure of the data returned by the getWorkflowState
method is
required, the data_mapping
property can be used to specify what a value
or group
node's value is bound to in the mapped_data
object. The mapped_data
object
is included in addition to the data
property which maintains the hierarchy defined in the
configuration.
For example, consider the following configuration snippet:
{
id: name,
type: group
children: [
{
id: title,
type: value
data_mapping: title
}
]
}
By default, the data
property returned in the response of getWorkflowState
would assign a title value as follows:
{
name: {
title: 'miss'
}
}
But with the above data_mapping: title
configuration, the value of title would
be assigned directly to the root (or relative to any array ancestor paths) property
of 'title' in the mapped_data
object:
{
title: 'miss'
}
Note that data_mapping
only applies to the output data - all refs in func_ref
s
must use the full (unmapped) data path.
To get started, create a Data Engine instance:
- Import data-engine:
const DataEngine = require('data-workflow-engine');
- Create a data engine instance:
const configuration = require('./path/to/configuration');
// optional - use a custom computation_context which is merged with the default context
const ctx = require('./path/to/computation-context');
const engine = DataEngine.create(configuration, ctx);
Usage: engine.getWorkflowState(dataInstanceObject)
Returns an object with the following shape:
{
data: Object, // a 'pruned' representation of `dataInstanceObject` - unmet preconditions and unspecified data items are removed
derived: Object, // object containing the results of the derived calculations (derived id's are object keys, with results the values)
section_states: Object, // object containing the state of the workflow nodes for `dataInstanceObject`. Each section's ID is a key in the object, and the value has the properties: `status` (either 'valid' or 'invalid') and `validationMessages` which contains an array of `validationMessage` objects
edge_states: Array // an array of the edge states of the configuration, enhanced with a `status` property - 'active' or 'inactive' depending on whether the `dataInstanceObject` activates this edge
}
Usage: engine.nextSection(currentSectionId, dataInstanceObject)
Returns an object representing the next section
node that should be visited in
the workflow:
{
sectionId: id_of_next_section,
validationMessages: [{path: path.to.target, message: message}]
}
The next section
will be determined according to the following rules:
- If the current section is reachable by active edges and is valid, the next section will be determined by the next active edge(s) which point to that section
- If the current section is reachable by active edges and is invalid, the same
section's id will be returned, indicating the section needs to be made valid before
the next section can be reached. In this case, the return object will also have a
validationMessages
property - If the current section is unreachable by active edges, then the last reachable section's
id will be returned, and any applicable
validationMessages
Usage: engine.previousSection(currentSectionId, dataInstanceObject)
Returns an object representing the previous section
in the workflow tree:
{
sectionId: id_of_previous_section
}
The previous section
will be determined according to the following rules:
- If the current section is reachable by active edges, the previous section will be determined by the previous active edge(s) which point from that section
- If the current section is unreachable by active edges, then the last reachable section's id will be returned
Usage: engine.isSectionReachable(requestedSectionId, dataInstanceObject)
Return a boolean determining whether requestedSectionId
is reachable for the
given dataInstanceObject
See the file test/test-configuration.yaml
for an up-to-date example of how to configure a workflow.