(function (Arsenal) {
var Event = Arsenal.Event;
Stablity: DEPRECATED
CAVEAT: This mechanism is deprecated, and will be removed at version 2.0.0
Requires: Event, Functional
A functional pipeline mechanism, by utilizing the Event and Functional mechanism.
(function (Arsenal) {
var Event = Arsenal.Event;
Use node's built-in Event instead if available
if (Arsenal.node) {
Event = require("events").EventEmitter;
}
function Pipeline(options) {
/* Inherits with Event mechanism */
Event.call(this);
/* Initialize options */
this._options = Arsenal.extend({
consistent: true
}, options || {});
/* Initialize variables */
this._flows = {};
}
pipeline.create(name, definition)
Create a new flow in the pipeline
Parameters:
name
string Flow name
definition
function Flow definition function
Returns:
boolean Whether the flow is loaded successfully
The flow definition function accepts one parameter uses as the Functional mechanism:
function (F) { ... }
function create(name, definition) {
return this.load(new Flow(name, definition));
}
pipeline.load(flow)
Load the specified flow into pipeline
Parameters:
flow
Flow|Array|object This parameter can be in 3 types:
Flow The flow instance of Pipeline.Flow
Array An array of flow instances
object Object map with key/value: name => definition
Returns:
boolean Whether the flow is loaded successfully
function load(flow) {
var result, i;
/* Multiple flows in an array (elements in flow instance) */
if (Arsenal.isArray(flow)) {
result = true;
for (i = 0; i < flow.length; i++) {
result = result && this.load(flow[i]);
}
return result;
/* Multiple flows via object map (name => definition) */
} else if (Arsenal.isHash(flow)) {
result = true;
for (i in flow) {
result = result && this.create(i, flow[i]);
}
return result;
}
/* Single flow */
if (! (flow instanceof Flow)) {
return false;
}
var flowName = flow.name();
/* Unload existed flow first */
this.unload(flowName);
/* Handle flow events */
flow.on("done", this.doneHandler.bind(this));
flow.on("trouble", this.troubleHandler.bind(this));
this._flows[flowName] = flow;
return true;
}
pipeline.unload(name)
Unload the specific flow from pipeline and return back
Parameters:
name
string Name of the flow to be unloaded
Returns:
Flow The unloaded flow object instance of Pipeline.Flow
function unload(name) {
var flow = this._flows[name];
if (this._flows[name]) {
this._flows[name].removeAllListeners("done");
this._flows[name].removeAllListeners("trouble");
delete this._flows[name];
}
return flow;
}
pipeline.clear()
Remove all loaded flows
function clear() {
var keys = Arsenal.getKeys(this._flows);
for (var i = 0; i < keys; i++) {
this.unload(keys[i]);
}
}
pipeline.check()
Check for the existance of the specified flow
Parameters:
name
string Flow name to be checked function check(name) {
return Arsenal.isObject(this._flows[name]) &&
this._flows[name] instanceof Pipeline.Flow;
}
pipeline.get(name, [args], [context], [callback])
Get entry function of the specific flow
Parameters:
name
string Flow name
args
Array Arguments pass to the flow entry
context
object Context object of the flow
callback
function Callback function to be called after the flow completed
Returns:
function The entry function of the flow, or null when the flow has no functions
function get(name, args, context, callback) {
var flow = this._flows[name];
if (! flow) {
throw new ReferenceError("Flow not found: " + name);
}
/* Initialize flow context */
if (! Arsenal.isObject(context)) {
context = {
_stack: [],
_checkpoints: []
};
}
/* Set up callback stack and checkpoints */
if (! Arsenal.isArray(context._stack)) {
context._stack = [];
}
if (! Arsenal.isArray(context._checkpoints)) {
context._checkpoints = [];
}
if (Arsenal.isFunction(callback)) {
context._stack.push(callback);
}
return flow.entry(args, context);
}
pipeline.run(name, [args], [context], [callback])
Run the specified flow
Parameters:
name
string Flow name
args
Array Arguments pass to the flow entry
context
object Context object of the flow
callback
function Callback function to be called after the flow has finished
Returns:
ANY Return value of the flow entry
function run(name, args, context, callback) {
var r = this.get(name, args, context, callback);
if (! r) {
throw new ReferenceError("Flow is empty: " + name);
}
return r.apply(null, args);
}
Event done
Emit when the flow has done successfully
Listener pattern: function (name, result)
Parameters for listeners:
name
string Flow name
result
ANY Flow result
function doneHandler(snapshot, result) {
var context = snapshot.context;
this.emit("done", context._name, result);
Clean up checkpoints
if (context._checkpoints) {
var checkpoint = context._checkpoints.pop();
if (context._stack) {
if (checkpoint < context._stack.length) {
context._checkpoints.push(checkpoint);
}
} else {
context._checkpoints = [];
}
}
Continue to next callback from the stack
if (context._stack) {
var callback = context._stack.pop();
if (Arsenal.isFunction(callback)) {
callback(result);
}
}
}
Event error
Emit when the required flow is not found
Listener pattern: function (name, snapshot)
Parameters for listeners:
snapshot
object Current snapshot function troubleHandler(snapshot) {
var context = snapshot.context;
if (! this._flows[snapshot.name]) {
this.emit("error", context._name, snapshot);
} else {
var consistent = this._options.consistent,
newContext = {
_name: snapshot.name,
_stack: [],
_checkpoints: []
},
checkpoint = -1;
if (! Arsenal.isArray(context._stack)) {
context._stack = [];
}
if (! Arsenal.isArray(context._checkpoints)) {
context._checkpoints = [];
}
Option: context consistent
if (consistent) {
newContext = context;
}
Stack checkpoint processing --
if (snapshot.nested && snapshot.callback) {
checkpoint = context._stack.length;
} else if (context._checkpoints.length > 0) {
checkpoint = context._checkpoints.pop();
if (checkpoint === context._stack.length) {
Rewrite nested mark
snapshot.nested = true;
} else if (checkpoint < context._stack.length) {
Push checkpoint back
context._checkpoints.push(checkpoint);
}
checkpoint = -1;
}
if (snapshot.nested || consistent) {
Inherit checkpoints
newContext._checkpoints = context._checkpoints;
} else {
newContext._checkpoints = [];
}
if (checkpoint !== -1 &&
newContext._checkpoints.indexOf(checkpoint) === -1) {
Save checkpoint
newContext._checkpoints.push(checkpoint);
}
if (snapshot.nested || consistent) {
Clone the stack
newContext._stack = context._stack;
} else {
newContext._stack = [];
}
if (Arsenal.isFunction(snapshot.callback)) {
newContext._stack.push(snapshot.callback);
}
--
this.run(snapshot.name, snapshot.args, newContext);
}
}
/* Inherits with Event mechanism */
Arsenal.extend(Pipeline.prototype, Event.prototype);
Primary functions
Pipeline.prototype.create = create;
Pipeline.prototype.load = load;
Pipeline.prototype.unload = unload;
Pipeline.prototype.clear = clear;
Pipeline.prototype.check = check;
Pipeline.prototype.get = get;
Pipeline.prototype.run = run;
Pipeline.prototype.doneHandler = doneHandler;
Pipeline.prototype.troubleHandler = troubleHandler;
Statical functions
Pipeline.createFlow(name, definition)
Create a new flow, individually
Parameters:
name
string Flow name
definition
function Flow definition function
The flow definition function accepts one parameter uses as the Functional mechanism:
function (F) { ... }
function createFlow(name, definition) {
return new Flow(name, definition);
}
Pipeline.createFlow = createFlow;
var Flow = (function (Arsenal) {
Constructor
Create a new flow instance
```var flow = new Flow(name, definition)```
NOTICE: For production, use Pipeline to create flow
Parameters:
name
string Flow name
definition
function Flow definition function
The flow definition function accepts one parameter uses as the Functional mechanism:
function (F) { ... }
function Flow(name, definition) {
/* Inherits with Event mechanism */
Event.call(this);
if (! name) {
throw new Error("Flow name cannot be empty");
}
this._name = name;
this._entry = null;
this._functional = new Arsenal.Functional(this.hook.bind(this));
if (Arsenal.isFunction(definition)) {
definition(this._functional);
}
}
flow.hook(err, snapshot, result)
The hook callback for Functional mechanism
Parameters:
err
Error or null null or Error object when required function was not found
snapshot
object The snapshot of the Functional mechanism
result
ANY Return value of the function, or undefined when an error occurs
function hook(err, snapshot, result) {
if (err) {
/* Redirect to other flow */
this.emit("trouble", snapshot);
} else {
/* Done with current flow */
this.emit("done", snapshot, result);
}
}
flow.name(name)
Get/set name of the flow
Parameters:
name
string Flow name
Returns:
string Name of the flow
function name(name) {
if (name) {
this._name = name;
}
return this._name;
}
flow.entry([args], [context], [callback])
Get entry function of the flow
Parameters:
args
Array Arguments pass to the flow entry
context
object Context object of the flow
callback
function Callback function to be called after the flow has finished
Returns:
function The entry function of the flow, or null when the flow has no functions
function entry(args, context, callback) {
var entry = this._entry,
f = this._functional;
/* Check for flow entry */
if (! entry) {
var keys = Arsenal.getKeys(f());
if (keys.length > 0) {
entry = keys[0];
} else {
/* Empty flow */
return null;
}
}
/* Initialize flow arguments */
if (! Arsenal.isArray(args)) {
args = [];
}
/* Store flow name in the context */
if (! Arsenal.isObject(context)) {
context = { _name: this._name };
} else {
context._name = this._name;
}
return f(entry, args, context, callback);
}
flow.run([args], [context], [callback])
Run flow from the entry
Parameters:
name
string Flow name
args
Array Arguments pass to the flow entry
context
object Context object of the flow
callback
function Callback function to be called after the flow has finished
Returns:
ANY Return value of the flow entry
function run(args, context, callback) {
var r = this.entry(args, context, callback);
if (r) {
return r.apply(null, args);
} else {
return null;
}
}
/* Inherits with Event mechanism */
Arsenal.extend(Flow.prototype, Event.prototype);
Primary functions
Flow.prototype.hook = hook;
Flow.prototype.name = name;
Flow.prototype.entry = entry;
Flow.prototype.run = run;
return Flow;
})(Arsenal);
/* /Flow */
Pipeline.Flow = Flow;
/* Export the Pipeline mechanism */
Arsenal.Pipeline = Pipeline;
return Pipeline;
})(this);