App Runner manages app lifecycle
- Flexible module loading provide choice of implementation with no code change
- Api lifecycle allows module spin-up/shutdown and settings from file data.
- Easier troubleshooting with emailed anomaly reports
- Orderly shutdown on exception and signal avoids data loss
- Self-contained email forwarding and anomaly detection/reporting
- require replacement
- api module support with api names, error emiters, request timers, across-api function, settings mix-in and ready watchers
- Separation between web server implementation and app code
- Configurable handlers for SIGINT SIGURS2, uncaught exception
Initialize apprunner
require('apprunner').initApp(require('haraldops').init({
appName: 'Node God',
api: {
apiMap: {
nodegodweb: {
onLoad: true,
sessionSecret: 'veryGreat',
PORT: 1111,
}
}
}
}))
-
defaults: options, typically loaded by haraldops
- .init.noInfoLog: boolean: no logging by appInit
- .anomaly: object for anomalysettings of false for disable
Retrieve key app data
console.log(require('apprunner').getAppData())
{
appName: 'Cloud Clearing',
appId: 'cloudclearing',
launchFolder: '/home/foxyboy/Desktop/c505/node/cloudclearing',
sendMail: [Function: send],
logger: [Function],
registerHandler: [Function: registerHandler],
views: { dbview: { db: [Object] } },
defaultsFile: '/home/foxyboy/apps/cloudclearing.json'
}
- appInfo: optional object PORT:numer, URL: string Forwarded on signal to Node God
Provides a require with a flexible search for modules
require = require('apprunner').getRequire(require)
require('myaliasedmodule')
Registers an api with apprunner, providing emitter, rqs object and initApi wrapper
require = require('apprunner').getRequire(require, exports, {
api: 'FB Friends', initApi: initApi, endApi: endApi,
rqScope: true, timeoutMs: time10s, ready: false})
- options
- .api: optional string: unique api name eg. 'Server Helper'
- .emScope if emitter, this emitter will be used. id property will be updated
- .emScope if string: scope for emitter
- .initApi: optional function: the internal initApi implementation
- .rqScope: optional string or boolen, string slogan for request timer, true if using api name
- .cb(err): optional function: rqs error callback
- .timeoutMs: number ms: default rqs timeout if not 3 seconds
- .ready: positive number: timeout for ready in ms
- .ready false: this api does not emit ready
- .ready defaults: ready with timeout 3 s
- .saveApi: optional function
- .endApi: optional function
- .apiState: optional function
- An emitter will be created if emScope or apiName is non-zero string at require.emitter
- A request timer will be provided if rqScope is true or non-zero string at require.rqs
- The api will be managed if there is a non-zero api name. emitter and initApi are required. api name at require.emitter.id
Surveys all apis with singleton ready to see if they have concluded initializing.
Report any argument as an anomaly, to the log and possibly via email
require('apprunner').anomaly(err, {location: 'kitchen', user: 'fail'}, err2)
control emailing of anomaly reports.
require('apprunner').enableAnomalyMail('1/1/2013')
- flag either a date string or boolean flag, default: false
Anomaly reports are not sent until the day after the date provided.
Adds an error listener to the EventEmitter.
Safe: can be invoked with any value, or repeatedly invoked.
Removes an error listener to the EventEmitter.
Safe: can be invoked with any value, or repeatedly invoked.
Shuts the application down. All apis that have exposed an saveApi or endApi gets these functions invoked prior to process.exit
App Runner handles process exceptions and SIGINT (ie. ctrl-Break.)
- Exit code 0 is SIGINT
- Exit code 2 is unhandled exception
Provides a function for a Web server instance that registers incoming uri and handlers
var apprunner = require('apprunner')
var express = require('express')
var app = apprunner.addErrorListener(express())
apprunner.addUriHandler(app.get.bind(app))
- fn(uri, uriHandler): function, uri: string, uriHandler: function
Get a timer factory
var rqs = require('apprunner').getRqs(timeoutFn, 'UserStore Timers', 1000)
var timer = rqs.addRq('Getting Token')
userStore.getUser(fbId, saveOauthToken)
function saveOauthToken(err, user) {
timer.clear()
...
}
function timeoutFn(err) {
console.log('an anomaly was reported for a timed out request')
// maybe take alternative action
if (err.isTimer) {
if (err.param === 'Getting Token') ...
}
}
- errorCallback(err): function: invoked with err for timeouts and inconsitencies
- scopeName: optional string or object: scope name for this factory eg. 'sync Db'
- defaultTimeoutMs: optional number: default timeout in ms, min 100, default 3 s
- rqs.addRq(parameter, time)
- rqs.clearRq(parameter) : same as .clear
- rqs.getState()
- rqs.shutdown()
An api must be loaded using these two things:
- Being loaded using require from getRequire
- Provide its initApi function and api name to getRequire
- An singleton api or api instances returned by initApi can be required to emit ready
- An api's initApi is provided options that is json-configured options overriden by invocation options
- An api can have onLoad: true
- An api has a sigleton emitter at require.emitter
- An api has a module name like all modules but also an api name at require.emitter.id
- An api can export the special endApi, saveApi and apiState functions
- An api can get a timer factory at require.rqs
What source files should be designed as an api?
- Modules that are shared across multiple apps or apis without being an npm package.
- modules with specific needs:
- Instance matching
- Managed lifecycle due to timers, open ports to or tcp connections
- Singleton ready to delay app start
- Configuration information like urls or passwords
- Diagnostic error emitting
- Provide ops information
- Start using onLoad
Singleton
require = require('apprunner').getRequire(require)
require('serverhelp').listen()
require('mongo').once('ready', mongoReady)
Instance
require = require('apprunner').getRequire(require)
var fb = require('fb')
fb.initApi({user: userId}).once('ready', fbReady)
Singleton
require = require('apprunner').getRequire(require, exports, {
api: 'MongoD', initApi: initApi, endApi: endApi,
rqScope: true, ready: false})
function someExport(...) ...
Instance
require = require('apprunner').getRequire(require, exports, {
api: 'User Store', initApi: initApi,
})
function initApi(opts) {
...
var readyState
doSome(someCb)
function someCb(err, data) {
readyState = err || true
var eArgs = ['ready']
if (err) eArgs.push(err)
e.emit.apply(e, eArgs)
}
var e = new events.EventEmitter
e.isReady = isReady
return e
function isReady() {
return readyState
}
}
Api settings are provided in the defaults object for appInit()
{
"api": {
"path": [
{
"folder": "lib"
},
{
"file": "applego"
}
],
"apiMap": {
"expressapi": {
"onLoad": true,
"sessionSecret": "secrets",
"port": 3003
},
}
- path: optional array of locations
- apiMap: optional object with api settings
- each entry can have onLoad, folder/subPath/file along with other settings
- A location can have folder/subPath: a string relative to the app's launch folder and a dot-separated name spacing in the loaded module
- A location can have file/subpath where file is a module name and subpath is a dot-separated name spacing in the loaded module
(c) Harald Rudell wrote this for node in September, 2012
No warranty expressed or implied. Use at your own risk.
Please suggest better ways, new features, and possible difficulties on github