letgo
Version:
Construct a catcher flow procedure.
1,121 lines (886 loc) • 120 kB
JavaScript
"use strict";var _symbol = require("babel-runtime/core-js/symbol");var _symbol2 = _interopRequireDefault(_symbol);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };} /*;
@submodule-license:
The MIT License (MIT)
@mit-license
Copyright (@c) 2017 Richeve Siodina Bebedor
@email: richeve.bebedor@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@end-submodule-license
@submodule-configuration:
{
"package": "letgo",
"path": "letgo/catcher.module.js",
"file": "catcher.module.js",
"module": "letgo",
"author": "Richeve S. Bebedor",
"eMail": "richeve.bebedor@gmail.com",
"contributors": [
"John Lenon Maghanoy <johnlenonmaghanoy@gmail.com>",
"Vinse Vinalon <vinsevinalon@gmail.com>"
],
"repository": "https://github.com/volkovasystems/letgo.git",
"test": "letgo-test.js",
"global": false
}
@end-submodule-configuration
@submodule-documentation:
Catcher class factory for handling catcher-flow procedure.
Later method will be executed once, and all callbacks will be executed once.
@end-submodule-documentation
@include:
{
"arid": "arid",
"asea": "asea",
"backd": "backd",
"burne": "burne",
"called": "called",
"diatom": "diatom",
"edo": "edo",
"execd": "execd",
"falzy": "falzy",
"filled": "filled",
"heredito": "heredito",
"idntty": "idntty",
"kein": "kein",
"mrkd": "mrkd",
"protype": "protype",
"raze": "raze",
"shft": "shft",
"statis": "statis",
"stringe": "stringe",
"symbiote": "symbiote",
"truly": "truly",
"zelf": "zelf"
}
@end-include
*/
var arid = require("arid");
var asea = require("asea");
var backd = require("backd");
var burne = require("burne");
var called = require("called");
var diatom = require("diatom");
var edo = require("edo");
var execd = require("execd");
var falzy = require("falzy");
var filled = require("filled");
var heredito = require("heredito");
var idntty = require("idntty");
var kein = require("kein");
var mrkd = require("mrkd");
var protype = require("protype");
var raze = require("raze");
var shft = require("shft");
var statis = require("statis");
var stringe = require("stringe");
var symbiote = require("symbiote");
var truly = require("truly");
var zelf = require("zelf");
var CACHE = (0, _symbol2.default)("cache");
var CALLBACK = (0, _symbol2.default)("callback");
var DEFER = (0, _symbol2.default)("defer");
var EVENT = (0, _symbol2.default)("event");
var INSTANCE = (0, _symbol2.default)("instance");
var PAUSED = (0, _symbol2.default)("paused");
var RESULT = (0, _symbol2.default)("result");
var STOPPED = (0, _symbol2.default)("stopped");
var catcher = function catcher(method) {
/*;
@meta-configuration:
{
"method": "function"
}
@end-meta-configuration
*/
if (truly(method) && !protype(method, FUNCTION)) {
throw new Error("invalid method");
}
var context = zelf(this);
if (truly(method)) {
method = called.bind(context)(method);
}
/*;
@note:
These methods should not be accessible outside through the catcher.
@end-note
*/
var push = function push(callback) {
/*;
@meta-configuration:
{
"callback": "function"
}
@end-meta-configuration
*/
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
this[CALLBACK].push(backd.bind(context)(callback));
return this;
};
var next = function next(error, result, parameter) {
/*;
@meta-configuration:
{
"error": Error,
"result:required": "*",
"parameter": "..."
}
@end-meta-configuration
*/
if (error instanceof Error && protype(this[DEFER], FUNCTION)) {
this[DEFER](error);
}
var callback = this[CALLBACK].splice(0, 1).pop();
if (falzy(callback)) {
this.set("result", result);
this.emit("lastly");
return result;
}
try {
/*;
@note:
If the method intentionally calls the callback without parameters
then it halts the chain.
@end-note
*/
if (arid(arguments)) {
result = callback.call(context, error, result);
flush.bind(this)();
this.set("result", result);
return result;
} else {
parameter = shft(arguments, 2);
result = callback.apply(context, [error, result].concat(parameter));
}
} catch (issue) {
error = issue;
result = undefined;
}
if (result instanceof Error) {
error = result;
result = undefined;
}
if (!(result instanceof Catcher)) {
this.set("result", result);
}
/*;
@note:
The result of the last callback is passed on the next callback.
If the callback encounters an error, it is up for the next callback
to continue the chain or halts the chain.
Automatic call of the next callback if the result is a catcher,
if the callbacks are not empty and the catcher is not paused.
@end-note
*/
if (!(result instanceof Catcher) && filled(this[CALLBACK]) && !this[PAUSED]) {
next.apply(this, [error, result].concat(parameter));
}
return result;
};
var flow = function flow(parameter) {
/*;
@meta-configuration:
{
"parameter": "..."
}
@end-meta-configuration
*/
parameter = raze(arguments);
this.set("parameter", parameter);
if (falzy(method)) {
return this;
}
/*;
@note:
Possibility that the catcher is paused before flowing.
@end-note
*/
this.unpause();
try {
if (asea.server) {
process.nextTick(function later() {var
self = this.self,context = this.context,parameter = this.parameter,method = this.method,next = this.next;
self.record(method.apply(context, [
backd.bind(self)(next)].
concat(parameter)));
}.bind({
"self": this,
"context": context,
"parameter": parameter,
"method": method,
"next": next }));
} else if (asea.client) {
var timeout = setTimeout(function later() {var
self = this.self,context = this.context,parameter = this.parameter,method = this.method,next = this.next;
self.record(method.apply(context, [
backd.bind(self)(next)].
concat(parameter)));
clearTimeout(timeout);
}.bind({
"self": this,
"context": context,
"parameter": parameter,
"method": method,
"next": next }));
} else {
throw new Error("cannot determine platform, platform not supported");
}
return this;
} catch (error) {
throw new Error("failed flow method, " + error.stack);
}
};
var flush = function flush() {
while (this[CALLBACK].length) {this[CALLBACK].pop();}
return this;
};
var Catcher = diatom("Catcher");
Catcher.prototype.initialize = function initialize(callback, parameter) {
/*;
@meta-configuration:
{
"callback:required": "function",
"parameter": "..."
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
parameter = shft(arguments);
var self = Catcher[INSTANCE] = this;
this[CALLBACK] = Catcher[CALLBACK];
this[CACHE] = Catcher[CACHE];
try {
this.merge(Catcher[EVENT]);
if (protype(callback, FUNCTION)) {
push.bind(this)(callback);
}
if (truly(method) && !execd(method)) {
flow.apply(this, parameter);
}
if (kein(DEFER, Catcher)) {
this.defer(Catcher[DEFER]);
}
if (kein(PAUSED, Catcher)) {
this[PAUSED] = Catcher[PAUSED];
}
var identity = idntty(Catcher).toString();
this.on(identity + ":pass", function pass() {
self.pass.apply(self, raze(arguments));
}, { "disableOnListenerNotification": true });
this.lastly(function lastly() {
self.stop();
});
return this;
} catch (error) {
next.bind(this)(new Error("failed catcher, " + error.stack));
} finally {
delete this.initialize;
}
};
Catcher.prototype.done = function done() {
if (mrkd(STOPPED, Catcher, true)) {
return true;
}
if (truly(method)) {
return arid(this[CALLBACK]) && execd(method);
} else {
return arid(this[CALLBACK]);
}
};
Catcher.prototype.push = function push(callback) {
/*;
@meta-configuration:
{
"callback": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (!kein(CALLBACK, this)) {
throw new Error("catcher has been released, cannot push callback");
}
if (filled(this[CALLBACK])) {
throw new Error("push callback once, cannot push callback again");
}
if (truly(method) && execd(method)) {
throw new Error("later method executed, cannot push callback");
}
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
push.bind(this)(callback);
return this;
};
Catcher.prototype.then = function then(callback) {
/*;
@meta-configuration:
{
"callback:required": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (!kein(CALLBACK, this)) {
throw new Error("catcher has been released, cannot push callback");
}
if (falzy(method) && arid(this[CALLBACK])) {
throw new Error("empty later method, cannot follow with callback");
}
if (truly(method) && execd(method)) {
throw new Error("later method executed, cannot follow with callback");
}
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
push.bind(this)(callback);
return this;
};
Catcher.prototype.pass = function pass(parameter) {
/*;
@meta-configuration:
{
"parameter": "..."
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
parameter = raze(arguments);
/*;
@note:
Flow the method if not yet called.
It is the developer responsibility to push a callback
before passing flow.
@end-note
*/
if (truly(method) && !execd(method)) {
return flow.apply(this, parameter);
}
this.unpause();
next.apply(this, parameter);
return this;
};
Catcher.prototype.defer = function defer(handler, strict) {
/*;
@meta-configuration:
{
"handler:required": "function",
"strict": "boolean"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (kein(DEFER, this)) {
return this;
}
if (falzy(handler) || !protype(handler, FUNCTION)) {
throw new Error("invalid defer handler");
}
if (strict === true) {
var self = this;
this[DEFER] = called.bind(context)(function delegate(error) {
handler.call(this, error);
flush.bind(self)();
return this;
});
} else {
this[DEFER] = called.bind(context)(handler);
}
return this;
};
Catcher.prototype.record = function record(result) {
/*;
@meta-configuration:
{
"result:required": "*",
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
this[RESULT] = result;
return this;
};
Catcher.prototype.release = function release() {
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
flush.bind(this)();
delete this[CALLBACK];
delete this[DEFER];
var result = this[RESULT];
delete this[RESULT];
return result;
};
Catcher.prototype.stop = function stop(error, result, parameter) {
/*;
@meta-configuration:
{
"error": Error,
"result": "*",
"parameter": "..."
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (arguments.length >= 1) {
this.detour.apply(this, raze(arguments));
}
this.release();
this.emit("release");
Catcher.flush();
burne(STOPPED, Catcher);
return this;
};
Catcher.prototype.detour = function detour(error, result, parameter) {
/*;
@meta-configuration:
{
"error": Error,
"result": "*",
"parameter": "..."
}
@end-meta-configuration
*/
this[CALLBACK].reverse().pop().
apply(context, [error, result].concat(shft(arguments, 3)));
return this;
};
Catcher.prototype.result = function result() {
return this[RESULT];
};
Catcher.prototype.set = function set(property, value) {
/*;
@meta-configuration:
{
"property:required": [
"number",
"string",
"symbol"
],
"value": "*"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (falzy(property) || !protype(property, NUMBER + STRING + SYMBOL)) {
throw new Error("invalid property");
}
this[CACHE][property] = value;
return this;
};
Catcher.prototype.get = function get(property) {
/*;
@meta-configuration:
{
"property:required": [
"number",
"string",
"symbol"
]
}
@end-meta-configuration
*/
if (falzy(property) || !protype(property, NUMBER + STRING + SYMBOL)) {
throw new Error("invalid property");
}
return this[CACHE][property];
};
Catcher.prototype.lastly = function lastly(callback) {
/*;
@meta-configuration:
{
"callback:required": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
this.once("lastly", callback);
return this;
};
Catcher.prototype.pause = function pause() {
this[PAUSED] = true;
Catcher[PAUSED] = true;
return this;
};
Catcher.prototype.unpause = function unpause() {
this[PAUSED] = false;
Catcher[PAUSED] = false;
return this;
};
Catcher.prototype.through = function through(flow, error, result, parameter) {
/*;
@meta-configuration:
{
"flow:required": "string",
"error:required": [
null,
Error
],
"result:required": "*"
"parameter": "..."
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true) || arid(this[CALLBACK])) {
return this;
}
if (falzy(flow) || !protype(flow, STRING)) {
throw new Error("invalid flow");
}
parameter = shft(arguments, 3);
this.emit.apply(this, ["flow:" + flow, error, result].concat(parameter));
return this;
};
Catcher.prototype.flow = function flow(name, handler) {
/*;
@meta-configuration:
{
"name:required": "string",
"handler:required": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true) || arid(this[CALLBACK])) {
return this;
}
if (falzy(name) || !protype(name, STRING)) {
throw new Error("invalid flow name");
}
if (falzy(handler) || !protype(handler, FUNCTION)) {
throw new Error("invalid flow handler");
}
this.once("flow:" + flow, handler);
return this;
};
Catcher.prototype.error = function error(issue) {
/*;
@meta-configuration:
{
"issue:required": Error
}
@end-meta-configuration
*/
if (!(issue instanceof Error)) {
throw new Error("invalid issue");
}
if (kein(DEFER, this)) {
return this[DEFER](issue);
}
if (this.hasEvent("error")) {
this.emit("error", issue);
}
return this;
};
Catcher.prototype.valueOf = function valueOf() {
return this.result();
};
Catcher.prototype.toString = function toString() {
return stringe(this.result());
};
Catcher = heredito(Catcher, edo.bind(context)());
Catcher = symbiote(Catcher, "Event");
/*;
@note:
We should create an instance of the Event here.
@end-note
*/
var event = edo.bind(context)()();
statis(Catcher).
attach(EVENT, event).
attach(CACHE, {}).
attach(CALLBACK, []).
implement("done", function done() {
if (mrkd(STOPPED, Catcher, true)) {
return true;
}
if (!kein(INSTANCE, this)) {
return false;
}
return this[INSTANCE].done();
}).
implement("release", function release() {
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (!kein(INSTANCE, this)) {
throw new Error("cannot release inactive catcher");
}
return this[INSTANCE].release();
}).
implement("record", function record(result) {
/*;
@meta-configuration:
{
"result:required": "*"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (!kein(INSTANCE, this)) {
throw new Error("cannot record result on inactive catcher");
}
return this[INSTANCE].record(result);
}).
implement("pass", function pass(parameter) {
/*;
@meta-configuration:
{
"parameter:required": "..."
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
parameter = raze(arguments);
if (kein(INSTANCE, this)) {
return this[INSTANCE].pass.apply(this[INSTANCE], parameter);
}
var identity = idntty(Catcher).toString();
this.emit.apply(context, [identity + ":pass"].concat(parameter));
return this;
}).
implement("stop", function stop(error, result, parameter) {
/*;
@meta-configuration:
{
"error": Error,
"result": "*",
"parameter": "..."
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (arguments.length >= 1) {
this.detour.apply(this, raze(arguments));
}
if (kein(INSTANCE, this)) {
this.release();
} else {
flush.bind(this)();
}
this.emit("release");
this.flush();
burne(STOPPED, Catcher);
return this;
}).
implement("detour", function detour(error, result, parameter) {
/*;
@meta-configuration:
{
"error": Error,
"result": "*",
"parameter": "..."
}
@end-meta-configuration
*/
this[CALLBACK].reverse().pop().
apply(context, [error, result].concat(shft(arguments, 3)));
return this;
}).
implement("set", function set(property, value) {
/*;
@meta-configuration:
{
"property:required": [
"number",
"string",
"symbol"
],
"value": "*"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (falzy(property) || !protype(property, NUMBER + STRING + SYMBOL)) {
throw new Error("invalid property");
}
this[CACHE][property] = value;
return this;
}).
implement("get", function get(property) {
/*;
@meta-configuration:
{
"property:required": [
"number",
"string",
"symbol"
]
}
@end-meta-configuration
*/
if (falzy(property) || !protype(property, NUMBER + STRING + SYMBOL)) {
throw new Error("invalid property");
}
return this[CACHE][property];
}).
implement("lastly", function lastly(callback) {
/*;
@meta-configuration:
{
"callback:required": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
this.once("lastly", callback);
return this;
}).
implement("push", function push(callback) {
/*;
@meta-configuration:
{
"callback": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (filled(this[CALLBACK])) {
throw new Error("push callback once, cannot push callback again");
}
if (truly(method) && execd(method)) {
throw new Error("later method executed, cannot follow with callback");
}
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
if (kein(INSTANCE, this)) {
return this[INSTANCE].push(callback);
}
this[CALLBACK].push(backd.bind(context)(callback));
return this;
}).
implement("then", function then(callback) {
/*;
@meta-configuration:
{
"callback": "function"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (falzy(method) && arid(this[CALLBACK])) {
throw new Error("empty later method, cannot follow with callback");
}
if (truly(method) && execd(method)) {
throw new Error("later method executed, cannot follow with callback");
}
if (falzy(callback) || !protype(callback, FUNCTION)) {
throw new Error("invalid callback");
}
if (kein(INSTANCE, this)) {
return this[INSTANCE].then(callback);
}
this[CALLBACK].push(backd.bind(context)(callback));
return this;
}).
implement("defer", function defer(handler, strict) {
/*;
@meta-configuration:
{
"handler:required": "function",
"strict": "boolean"
}
@end-meta-configuration
*/
if (mrkd(STOPPED, Catcher, true)) {
return this;
}
if (falzy(handler) || !protype(handler, FUNCTION)) {
throw new Error("invalid defer handler");
}
if (kein(INSTANCE, this)) {
return this[INSTANCE].then(handler, strict);
}
if (kein(DEFER, this)) {
return this;
}
if (strict === true) {
var self = this;
this[DEFER] = called.bind(context)(function delegate(error) {
handler.call(this, error);
flush.bind(self)();
return this;
});
} else {
this[DEFER] = called.bind(context)(handler);
}
return this;
}).
implement("pause", function pause() {
if (mrkd(STOPPED, Catcher, true) || arid(this[CALLBACK])) {
return this;
}
this[PAUSED] = true;
if (kein(INSTANCE, this)) {
return this[INSTANCE].pause();
}
return this;
}).
implement("unpause", function pause() {
if (mrkd(STOPPED, Catcher, true) || arid(this[CALLBACK])) {
return this;
}
this[PAUSED] = false;
if (kein(INSTANCE, this)) {
return this[INSTANCE].unpause();
}
return this;
}).
implement("through", function through(flow, error, result, parameter) {
/*;
@meta-configuration:
{
"flow:required": "string",
"error:required": [