apptivity
An activity workflow player that sequentially runs action, condition, fork, merge and prompt input actions.
This library has the following features.
Convenient and rich definition API to define and configure Workflow activities and actions. It helps developers to focus on overall activity logic before detailing them with their implementations.
Flexible definition API allows splitting of Worfklow activity definition with their implementation, keeping files and directories of Workflow modules organized.
Workflow activities and actions are more predictable as they each publish an immutable state object containing unique state name, action name, request data, and response data per completed run of Activity action.
Activity action supports asynchronous runs by supplying a
callback
function that returns Promise Object when defining Action implementations using handler(callback:Function) or guard(callback:Function) methods.Workflows' internal Finite State Machine can be exported into any custom JSON schema using workflow.trasform(workflowName:String).
Installation
This little library is packaged by npm. Source can be found in github repository
npm install apptivity --save
Usage
Create a Workflow activity and chain-configure actions with definition API.
var workflow = require('./index.js');
workflow.create("createUser").
// create action
action("fetchFromBackend").
describe("fetch something from the backend",
"or find something to populate the initial input data",
"Remember: [describe], [guard] and [handler] is optional").
guard(function (input) {
return Promise.resolve(input);
}).
handler(function (input) {
return Ajax.requestAndReturnPromise(input);
}).
// your last action
action("renderAndExit").
describe("Nothing special if no [handler]").
handler(function (data) {
return { name: "whatever! this will be your last data" };
});
Run the newly created Workflow activity and monitor state changes.
workflow("createUser").
// register event listener
on("state-change",
function (session, data) {
console.log("state: ", session.state === data);
console.log("yes! do something about this immutable [data]");
}).
// run the workflow
run({ name: "first input" });
Listen to other running Workflow activity sessions' state-change
event.
var stopListening = workflow.subscribe("createUser", "state-change",
function (session, data) {
console.log("what should I do with this?");
});
// I change my mind, I don't have to listen to "createUser" workflow's "state-change" events
stopListening();
Note: You can subscribe to a workflow event anytime even if the workflow does not exist.
Export the workflow into Finite State Machine configuration to create an instance of javascript-state-machine.
var StateMachine = require("javascript-state-machine");
var createUserStateMachine = StateMachine.create(
workflow.transform('createUser'));
API
workflow(name:String):sessionAPI
Creates session endpoint object from a registered workflow. The session object has the following convenience methods to handle most of the use-cases to manage a Workflow session:
sessionAPI.on(eventName:String|RegExp, handler:Function):sessionAPI Listen to events specific to the current session.
sessionAPI.un(eventName:String|RegExp, handler:Function):sessionAPI Removes event listener specific to the current session.
sessionAPI.purge():sessionAPI Removes all listeners of the current session.
sessionAPI.run(inputData:Mixed, context:Mixed):Promise Runs the Workflow session with
inputData
parameter as initial request data.context
parameter becomesthis
for all action guards and handlers. Defaultcontext
isglobal
for NodeJS orwindow
for browsers.sessionAPI.runOnce(inputData:Mixed, context:Mixed):Promise Runs the Workflow session then destroys it when done.
sessionAPI.answer(value:Mixed):sessionAPI Replies to prompts whenever the session runs an input action.
sessionAPI.get():Session Returns the underlying Session instance that processes the Workflow.
sessionAPI.currentPrompt():String Returns the currently active prompt (input action name) that is waiting for an answer. It can be answered by calling
sessionAPI.answer(Mixed) or session.asnwer(Mixed)
. Returnsfalse
if no prompt is waiting.sessionAPI.currentState():Immutable Returns Immutable or scalar value of the last process output.
sessionAPI.destroy():Immutable Destroys the session and applies the necessary cleanup.
workflow.create(name:String):Activity
Creates and registers workflow and returns Activity Definition Object in order to chain-define actions for it. The following are the actions that Activity can configure:
Activity.action(actionName:String):Activity Creates action activity.
Activity.describe(description:String, [nextLine:String]):Activity Describes the last defined action.
-
Activity.guard([name:String], condition:String|Function):Activity Guards the action from calling its handler. Defined handler and the rest of the actions will stop running when
condition
function returns a rejected Promise.Note:
condition
parameter can also be a named task where it can be defined later.Guard
condition
runs withinput
andsession
arguments like the example belowhandler(input:Mixed, session:Session)
function guard(input, session) { var context = this; return Promise.reject("no entry!"); }
-
Activity.handler(handler:String|Function):Activity Defines the callback of the action. The returned data or resolved Promise of the
handler
parameter becomes the response data of the current action and input data of the next action. If it is the last action, then it will become the reponse data of the whole activity.Note:
handler
parameter can also be a named task where it can be defined later.Handler
handler
like guardcondition
runs withinput
andsession
arguments like the example belowhandler(input:Mixed, session:Session)))
function handler(input, session) { var context = this; return processInput(input); }
Warning: Action must be defined first before chain-calling describe(), guard() and handler().
-
Activity.input(actionName:String):Activity
Creates an action that waits for input before running the handler. Input actions are answered by calling
sessionAPI.answer(input:Mixed)
orsesion.answer(input:Mixed)
replacing the action's request data withinput
argument. > Warning! Prompt is called after guard callback is executed. Activity.end():Activity Creates an action that abruptly ends the whole Workflow. The input for this action becomes the last output and state data of the Workflow session.
-
Activity.condition(activity:Activity, [other:Activity, ...]):Activity Runs the first
activity
(andother
parameters) that satisfies the guard condition of their first action.Warning! Activities (
activity
andother
parameters) having Guard condition in their first Action are evaluated first before falling back to other Activity without Guard condition in their first Action. Other activities after that will be ignored. So, there is no need to add two or more Activities without Guard condition in their first Action. Treat activity having an unguarded first action as the onlyelse
block of theif
andelse if
blocks. -
Activity.fork(activity:Activity, [other:Activity, ...]):Activity Creates an action that forks multiple process that simultaneously run all Activities (
activity
andother
parameters) then merge all their output when done running. Merged output is an object with property names named with first Action of the forked Activities.Example output object is
{ activity1: output, activity2: output}
when running the Forked Activities defined below:workflow.create("test-fork"). fork( workflow.activity('a1'). action('activity1'), workflow.activity('a2'). action('activity2') );
workflow.exist(workflowName:String):Boolean
Finds named workflow defined in workflowName
parameter. Returns true
if workflow exist or false
otherwise.
workflow.subscribe([workflowName:String], eventName:String|RegExp, handler:Function):Function
Subscribes all session events filtered by workflowName
and eventName
parameters. Then, returns a Function that can unsubscribe the event when called. workflowName
parameter is optional and matches all workflow events when omitted.
The following are the events the session can broadcast with their callback parameters:
process-start (session:sessionAPI, stateData:Immutable) Event is broadcasted after running the first process for the first time. Usually the first action of the root workflow.
process-end (session:sessionAPI, stateData:Immutable) Event is broadcasted after running the last process. Usually the last action of the root workflow or if the workflow encounters an abrupt end action.
state-change (session:sessionAPI, stateData:Immutable) Event is broadcasted after action was completely processed.
prompt (session:sessionAPI, actionName:String, input:Mixed) Event is broadcasted after workflow encounters an input action and waiting for an answer.
answer (session:sessionAPI, actionName:String, input:Mixed) Event is broadcasted after workflow has answered a prompt from an input action by calling
session.answer(data)
orsessionAPI.answer(data)
.destroy (session:sessionAPI) Event is broadcasted after workflow session is destroyed. After this event, other session process will be killed and apply cleanup to the current workflow session.
workflow.activity(activityName:String):Activity
Creates an Activity instance used chain-define Actions and configurations. This is useful for defining Activity instances as parameters for condition
and fork
Action definition methods like the example below:
var workflow = require("apptivity");
workflow.create("myActivity").
condition(
workflow.activity("removeData").
guard("data/checkpermission").
handler("data/doRemove"),
workflow.activity("showMessage").
handler("data/showPermissionError")
);
workflow.task(name:String, runner:Function):workflowAPI
Registers named tasks. Useful for assigning named task to action handlers and guards when splitting definition and implementation into separate files.
Warning! Named task can only be registered once. Registering task with the same name of an existing task will throw an exception from this method.
workflow.createTransformer(name:String, handler:Function):workflowAPI
Registers a named transformer to customize the exported Finite State Machine definition object. Named transformers are used as transformer
parameter of the workflow.transform(workflowName, transformer) method.
handler
Function is the middleware used to customize state name, node name, node definition object, and resulting FSM definition object while building the exported object in workflow.transform(workflowName, transformer) method.
handler
Function's basic parameter is handler(type:String, operation:Object, [others...])
. Other parameters excluding operation
will vary according to type
parameter.
handler(type:
"initialize"
, operation:Object, fsmName:String, configSettings:Object):Object Customizes the resulting FSM definition object by returning customization object. As for now, onlytransitions:Object|Array
property in returned customization object is processed.handler(type:
"state"
, operation:Object, stateName:String, stateType:String):String Customizes state name by returning newstateName
String.stateType
parameter determines the nature of the state. Possible values ofstateType
is link, condition, fork, and end.handler(type:
"node"
, operation:Object, customStateName:String, proposedNode:Object):String Customizes node name by returning newnodeName
String. Default node name isActivity.prototype.id
. node definition properties can be customized by replacing or adding properties inproposedNode
object parameter.-
handler(type:
"transition"
, operation:Object, sourceCustomStateName:String, customNodeName:Object, targetCustomStateName):Mixed Customizes transition definition. Transition is a row in State Table of a Finite State Machine which contains the following columns:sourceCustomStateName
,customNodeName
andtargetCustomStateName
. There are two possible type of value to return depeding on thetransitions
property customized in "initialize" phase.-
Array transitions should return
Object
value representing the transition. Default transition value is{ from: "sourceState", name: "nodeName", to: "targetState" }
. Returning non-object value will use the latter default value as transitions item. -
Object transitions can return any value representing the target state. Default target state is the
targetCustomStateName
parameter. Returningnull
orundefined
will use the defaulttargetCustomStateName
parameter.
-
Array transitions should return
workflow.transform(workflowName:String, [transformer:String|Function]):workflowAPI
Exports internal Finite State Machine of workflowName
workflow using the optional transformer
parameter middleware. The optional transformer
parameter can be any of the descriptions below:
- Providing String
transformer
parameter oftransform()
method will use the registered transformer defined with workflow.createTransformer(name:String, handler:Function):workflowAPI method. - Providing Function
transformer
parameter oftransform()
method will directly usetransformer
parameter as customization callback middleware. - Leaving out
transformer
parameter will use"default"
transformer which results into an exported Object used to create an instance of javascript-state-machine withaction
property containing node definitions.