Skip to content

Instantly share code, notes, and snippets.

@geek
Created December 2, 2015 21:00
Show Gist options
  • Save geek/0617b094c1e297f852c3 to your computer and use it in GitHub Desktop.
Save geek/0617b094c1e297f852c3 to your computer and use it in GitHub Desktop.
attempt to pull out actions
/* Copyright (c) 2010-2015 Richard Rodger, MIT License */
'use strict'
var _ = require('lodash')
var Jsonic = require('jsonic')
var Common = require('./common')
var internals = {}
module.exports = function () {
var seneca = this
seneca.decorate('add', internals.add)
seneca.decorate('find', internals.find)
seneca.decorate('has', internals.has)
seneca.decorate('list', internals.list)
seneca.decorate('actroutes', internals.routes)
seneca.decorate('stats', internals.actions.stats)
// Legacy
seneca.decorate('findact', internals.find)
seneca.decorate('hasact', internals.has)
// Add builtin actions.
seneca.add({role: 'seneca', cmd: 'stats'}, internals.actions.stats)
seneca.add({role: 'seneca', cmd: 'close'}, internals.actions.close)
seneca.add({role: 'seneca', info: 'ready'}, internals.actions.ready)
seneca.add({role: 'seneca', info: 'fatal'}, internals.actions.fatal)
seneca.add({role: 'seneca', get: 'options'}, internals.actions.get)
// Legacy builtin actions.
seneca.add({role: 'seneca', stats: true}, internals.actions.stats)
seneca.add({role: 'seneca', ready: true}, internals.actions.ready)
seneca.add({role: 'options', cmd: 'get'}, internals.actions.get)
}
// ### seneca.add
// Add an message pattern and action function.
//
// `seneca.add(pattern, action)`
// * _pattern_ `o|s` → pattern definition
// * _action_ `f` → pattern action function
//
// `seneca.add(pattern_string, pattern_object, action)`
// * _pattern_string_ `s` → pattern definition as jsonic string
// * _pattern_object_ `o` → pattern definition as object
// * _action_ `f` → pattern action function
//
// The pattern is defined by the top level properties of the
// _pattern_ parameter. In the case where the pattern is a string,
// it is first parsed by
// [jsonic](https://github.com/rjrodger/jsonic)
//
// If the value of a pattern property is a sub-object, this is
// interpreted as a
// [parambulator](https://github.com/rjrodger/parambulator)
// validation check. In this case, the property is not considered
// part of the pattern, but rather an argument to validate when
// _seneca.act_ is called.
internals.add = function () {
var self = this
var args = Common.parsePattern(self, arguments, 'action:f? actmeta:o?')
var raw_pattern = args.pattern
var action = args.action || function (msg, done) {
done.call(this, null, msg.default$ || null)
}
var actmeta = args.actmeta || {}
// TODO: refactor plugin name, tag and fullname handling.
actmeta.plugin_name = actmeta.plugin_name || 'root$'
actmeta.plugin_fullname = actmeta.plugin_fullname ||
actmeta.plugin_name +
(('-' === actmeta.plugin_tag ? void 0 : actmeta.plugin_tag)
? '/' + actmeta.plugin_tag : '')
var add_callpoint = callpoint()
if (add_callpoint) {
actmeta.callpoint = add_callpoint
}
actmeta.sub = !!raw_pattern.sub$
actmeta.client = !!raw_pattern.client$
// Deprecate a pattern by providing a string message using deprecate$ key.
actmeta.deprecate = raw_pattern.deprecate$
var strict_add = (raw_pattern.strict$ && raw_pattern.strict$.add !== null)
? !!raw_pattern.strict$.add : !!so.strict.add
var pattern = self.util.clean(raw_pattern)
if (0 === _.keys(pattern)) {
throw internals.error('add_empty_pattern', {args: Common.clean(args)})
}
var pattern_rules = _.clone(action.validate || {})
_.each(pattern, function (v, k) {
if (_.isObject(v)) {
pattern_rules[k] = v
delete pattern[k]
}
})
if (0 < _.keys(pattern_rules).length) {
actmeta.parambulator = Parambulator(pattern_rules, pm_custom_args)
}
var addroute = true
actmeta.args = _.clone(pattern)
actmeta.pattern = Common.argpattern(pattern)
// deprecated
actmeta.argpattern = actmeta.pattern
// actmeta.id = self.idgen()
actmeta.id = refnid()
actmeta.func = action
var priormeta = self.find(pattern)
// only exact action patterns are overridden
// use .wrap for pin-based patterns
if (strict_add && priormeta && priormeta.pattern !== actmeta.pattern) {
priormeta = null
}
if (priormeta) {
if (_.isFunction(priormeta.handle)) {
priormeta.handle( args.pattern, action )
addroute = false
}
else {
actmeta.priormeta = priormeta
}
actmeta.priorpath = priormeta.id + ';' + priormeta.priorpath
}
else {
actmeta.priorpath = ''
}
// FIX: need a much better way to support layered actions
// this ".handle" hack is just to make seneca.close work
if (action && actmeta && _.isFunction(action.handle)) {
actmeta.handle = action.handle
}
var stats = {
id: actmeta.id,
plugin: {
full: actmeta.plugin_fullname,
name: actmeta.plugin_name,
tag: actmeta.plugin_tag
},
prior: actmeta.priorpath,
calls: 0,
done: 0,
fails: 0,
time: {}
}
private$.stats.actmap[actmeta.argpattern] =
private$.stats.actmap[actmeta.argpattern] || stats
if (addroute) {
var addlog = [ actmeta.sub ? 'SUB' : 'ADD',
actmeta.id, Common.argpattern(pattern), action.name,
callpoint() ]
var isplugin = self.context.isplugin
var logger = self.log.log || self.log
if (!isplugin) {
// addlog.unshift('-')
// addlog.unshift('-')
// addlog.unshift('-')
addlog.unshift(actmeta.plugin_tag)
addlog.unshift(actmeta.plugin_name)
addlog.unshift('plugin')
}
logger.debug.apply(self, addlog)
private$.actrouter.add(pattern, actmeta)
}
return self
}
internals.find = function (args) {
var seneca = this
if (_.isString(args)) {
args = Jsonic(args)
}
var actmeta = seneca.private$.actrouter.find(args)
// if we have no destination, we look for
// a catch-all pattern and assign this, if
// it exists.
if (!actmeta) {
actmeta = seneca.private$.actrouter.find({})
}
return actmeta
}
internals.has = function (args) {
return !!this.private$.actrouter.find(args)
}
internals.list = function (args) {
var seneca = this
args = _.isString(args) ? Jsonic(args) : args
var found = seneca.private$.actrouter.list(args)
found = _.map(found, function (entry) {
return entry.match
})
return found
}
internals.routes = function () {
return this.private$.actrouter.toString(function (d) {
var s = 'F='
if (d.plugin_fullname) {
s += d.plugin_fullname + '/'
}
s += d.id
while (d.priormeta) {
d = d.priormeta
s += ';'
if (d.plugin_fullname) {
s += d.plugin_fullname + '/'
}
s += d.id
}
return s
})
}
internals.actions = {
close: function (args, done) {
this.emit('close')
done()
},
get: function (args, done) {
var seneca = this
var options = seneca.private$.optioner.get()
var base = args.base || null
var root = base ? (options[base] || {}) : options
var val = args.key ? root[args.key] : root
done(null, Common.copydata(val))
},
fatal: function (args, done) {
done()
},
ready: function (args, done) {
this.private$.wait_for_ready = false
this.emit('ready')
done()
},
stats: function (args, done) {
var seneca = this
var private$ = seneca.private$
var stats
args = args || {}
if (args.pattern && private$.stats.actmap[args.pattern]) {
stats = private$.stats.actmap[args.pattern]
stats.time = private$.timestats.calculate(args.pattern)
}
else {
stats = _.clone(private$.stats)
stats.now = new Date()
stats.uptime = stats.now - stats.start
stats.now = new Date(stats.now).toISOString()
stats.start = new Date(stats.start).toISOString()
var summary =
(null == args.summary && false) ||
(/^false$/i.exec(args.summary) ? false : !!(args.summary))
if (summary) {
stats.actmap = void 0
}
else {
_.each(private$.stats.actmap, function (a, p) {
private$.stats.actmap[p].time = private$.timestats.calculate(p)
})
}
}
if (done) {
done(null, stats)
}
return stats
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment