Comparing version 0.0.2 to 0.0.5
@@ -14,9 +14,16 @@ 'use strict'; | ||
var events = require('events'); | ||
var testing = require('testing'); | ||
var domain = require('domain'); | ||
// globals | ||
var log = new Log('info'); | ||
var globalDomain = domain.create(); | ||
// constants | ||
var EVENTS = ['first', 'start', 'stop', 'last']; | ||
var EVENTS = ['init', 'start', 'stop', 'finish']; | ||
var DEFAULT_OPTIONS = { | ||
catchErrors: true, | ||
catchSignals: true, | ||
showErrors: true, | ||
logTimes: true, | ||
}; | ||
@@ -29,13 +36,17 @@ | ||
// attributes | ||
var callbackQueues = {}; | ||
var flags = {}; | ||
var standaloneCallback = null; | ||
var flags = {}; | ||
var callbackQueues = {}; | ||
var initing = false; | ||
var finishing = false; | ||
var startingUp = false; | ||
var shuttingDown = false; | ||
var errored = false; | ||
var phase = 'pre'; | ||
self.options = {}; | ||
self.options.overwriteWith(DEFAULT_OPTIONS); | ||
// init | ||
init(); | ||
startup(); | ||
function init() | ||
function startup() | ||
{ | ||
@@ -48,3 +59,2 @@ EVENTS.forEach(function(event) | ||
events.EventEmitter.call(self); | ||
self.on('error', showError); | ||
setImmediate(function() | ||
@@ -54,3 +64,3 @@ { | ||
{ | ||
reallyInit(); | ||
reallyStartup(); | ||
}, 0); | ||
@@ -66,3 +76,3 @@ }); | ||
{ | ||
return self.emit('error', 'Could not add ' + event + ' to queue after it has run'); | ||
return manageInternalError('Could not add ' + event + ' to queue after it has run'); | ||
} | ||
@@ -73,20 +83,17 @@ callbackQueues[event].push(callback); | ||
function showError(error) | ||
function reallyStartup() | ||
{ | ||
log.error('Initialization error: %s', error); | ||
} | ||
function reallyInit() | ||
{ | ||
if (initing) | ||
if (startingUp) | ||
{ | ||
self.emit('error', 'Could not init again'); | ||
return; | ||
return manageInternalError('Could not start up again'); | ||
} | ||
initing = true; | ||
runAll('first', function(error) | ||
startingUp = true; | ||
var start = Date.now(); | ||
self.emit('startup'); | ||
setupErrors(); | ||
runAll('init', function(error) | ||
{ | ||
if (error) | ||
{ | ||
return self.emit('error', 'Could not run first callbacks: ' + error); | ||
return manageInternalError('Could not run init callbacks: ' + error); | ||
} | ||
@@ -97,8 +104,9 @@ runAll('start', function(error) | ||
{ | ||
return self.emit('error', 'Could not run start callbacks: ' + error); | ||
return manageInternalError('Could not run start callbacks: ' + error); | ||
} | ||
self.emit('ready'); | ||
if (standaloneCallback) | ||
if (self.options.logTimes) | ||
{ | ||
runStandalone(); | ||
var elapsedSeconds = (Date.now() - start) / 1000; | ||
log.info('Initialization took %s seconds', elapsedSeconds.toFixed(1)); | ||
} | ||
@@ -109,2 +117,12 @@ }); | ||
function manageInternalError(error) | ||
{ | ||
self.emit('error', error); | ||
if (self.options.showErrors) | ||
{ | ||
log.error('Error in phase %s: %s', phase, error); | ||
} | ||
startShutdownByError(); | ||
} | ||
function runAll(event, callback) | ||
@@ -116,2 +134,4 @@ { | ||
} | ||
phase = event; | ||
self.emit(event + 'ing'); | ||
runQueue(callbackQueues[event], function(error) | ||
@@ -124,2 +144,3 @@ { | ||
flags[event] = true; | ||
self.emit(event + 'ed'); | ||
return callback(null); | ||
@@ -135,27 +156,95 @@ }); | ||
} | ||
var next = queue.shift(); | ||
next(callback); | ||
var current = queue.shift(); | ||
run(current, function(error) | ||
{ | ||
if (error) | ||
{ | ||
return callback(error); | ||
} | ||
return runQueue(queue, callback); | ||
}); | ||
} | ||
function runStandalone() | ||
function run(current, callback) | ||
{ | ||
standaloneCallback(function(error) | ||
globalDomain.run(function() | ||
{ | ||
if (error) | ||
current(callback); | ||
}); | ||
} | ||
function setupErrors() | ||
{ | ||
if (self.options.catchErrors) | ||
{ | ||
globalDomain.on('error', function(error) | ||
{ | ||
if (error) | ||
{ | ||
return self.emit('error', 'Could not run start callbacks: ' + error); | ||
} | ||
manageUnexpectedError('error', error); | ||
}); | ||
process.on('uncaughtException', function(error) | ||
{ | ||
manageUnexpectedError('uncaught exception', error); | ||
}); | ||
} | ||
if (self.options.catchSignals) | ||
{ | ||
process.on('SIGINT', function() | ||
{ | ||
log.notice('User pressed control-C'); | ||
signalled(); | ||
}); | ||
process.on('SIGTERM', function() | ||
{ | ||
log.notice('Process killed'); | ||
signalled(); | ||
}); | ||
} | ||
} | ||
function manageUnexpectedError(type, error) | ||
{ | ||
if (!self.options.catchErrors) | ||
{ | ||
return; | ||
} | ||
self.emit('error', 'Unexpected ' + type + ': ' + error); | ||
if (self.options.showErrors) | ||
{ | ||
log.alert('Unexpected %s: %s', type, error); | ||
if (error.stack) | ||
{ | ||
log.alert(error.stack); | ||
} | ||
}); | ||
} | ||
startShutdownByError(); | ||
} | ||
self.finish = function() | ||
function startShutdownByError() | ||
{ | ||
if (finishing) | ||
if (errored) | ||
{ | ||
return self.emit('error', 'Could not finish again'); | ||
return; | ||
} | ||
finishing = true; | ||
errored = true; | ||
self.shutdown(); | ||
} | ||
function signalled() | ||
{ | ||
if (!self.options.catchSignals) | ||
{ | ||
return; | ||
} | ||
self.shutdown(); | ||
} | ||
self.shutdown = function() | ||
{ | ||
if (shuttingDown) | ||
{ | ||
return manageInternalError('Could not shutdown again'); | ||
} | ||
shuttingDown = true; | ||
var start = Date.now(); | ||
self.emit('shutdown'); | ||
runAll('stop', function(error) | ||
@@ -165,10 +254,15 @@ { | ||
{ | ||
return self.emit('error', 'Could not run stop callbacks: ' + error); | ||
return manageInternalError('Could not run stop callbacks: ' + error); | ||
} | ||
runAll('last', function(error) | ||
runAll('finish', function(error) | ||
{ | ||
if (error) | ||
{ | ||
return self.emit('error', 'Could not run last callbacks: ' + error); | ||
return manageInternalError('Could not run finish callbacks: ' + error); | ||
} | ||
if (self.options.logTimes) | ||
{ | ||
var elapsedSeconds = (Date.now() - start) / 1000; | ||
log.info('Shutdown took %s seconds', elapsedSeconds.toFixed(1)); | ||
} | ||
self.emit('end'); | ||
@@ -178,60 +272,30 @@ }); | ||
}; | ||
}; | ||
util.inherits(InitSystem, events.EventEmitter); | ||
module.exports = InitSystem; | ||
self.standalone = function(callback) | ||
{ | ||
if (standaloneCallback) | ||
{ | ||
return manageInternalError('Already have standalone callback'); | ||
} | ||
standaloneCallback = callback; | ||
self.on('ready', runStandalone); | ||
}; | ||
function testInitSystem(callback) | ||
{ | ||
var system = new InitSystem(); | ||
system.on('error', function(error) | ||
function runStandalone() | ||
{ | ||
console.trace('here'); | ||
testing.failure(error, callback); | ||
}); | ||
system.first(function(next) | ||
{ | ||
log.debug('first'); | ||
next(null); | ||
}); | ||
system.start(function(next) | ||
{ | ||
log.debug('start'); | ||
system.finish(); | ||
next(null); | ||
}); | ||
system.stop(function(next) | ||
{ | ||
log.debug('stop'); | ||
next(null); | ||
}); | ||
system.last(function(next) | ||
{ | ||
log.debug('last'); | ||
next(null); | ||
}); | ||
system.on('started', function() | ||
{ | ||
system.finish(); | ||
}); | ||
system.on('end', function() | ||
{ | ||
testing.success(callback); | ||
}); | ||
} | ||
/** | ||
* Run all tests. | ||
*/ | ||
exports.test = function(callback) | ||
{ | ||
testing.run([testInitSystem], callback); | ||
standaloneCallback(function(error) | ||
{ | ||
if (error) | ||
{ | ||
return manageInternalError('Could not run start callbacks: ' + error); | ||
} | ||
self.shutdown(); | ||
}); | ||
} | ||
}; | ||
// run tests if invoked directly | ||
if (__filename == process.argv[1]) | ||
{ | ||
log = new Log('debug'); | ||
exports.test(testing.show); | ||
} | ||
util.inherits(InitSystem, events.EventEmitter); | ||
module.exports = new InitSystem(); | ||
// exported for tests | ||
module.exports.InitSystem = InitSystem; | ||
{ | ||
"name": "inits", | ||
"version": "0.0.2", | ||
"version": "0.0.5", | ||
"description": "Init system for Node.js.", | ||
@@ -15,3 +15,5 @@ "homepage": "https://github.com/alexfernandez/inits", | ||
"prototypes": "*", | ||
"log": "1.4.*", | ||
"log": "1.4.*" | ||
}, | ||
"devDependencies": { | ||
"testing": "*" | ||
@@ -25,5 +27,5 @@ }, | ||
"scripts": { | ||
"test": "node test.js" | ||
"test": "node runTests.js" | ||
}, | ||
"private": false | ||
} |
239
README.md
# inits | ||
Init system for Node.js | ||
A simple init system for Node.js. | ||
Manages initialization tasks, and optionally also shutdown tasks. | ||
Useful to simplify initialization of complex systems | ||
with asynchronous tasks. | ||
## Installation | ||
Simply run: | ||
``` | ||
npm install inits | ||
``` | ||
Or add `inits` to your `package.json`: | ||
``` | ||
"dependencies": { | ||
... | ||
"inits": "*", | ||
... | ||
}, | ||
``` | ||
and run `npm install`. | ||
## Lifecycle of a System | ||
There are four distinct phases in `inits`: | ||
* init, | ||
* start, | ||
* stop, | ||
* and finish. | ||
They are intended to be symmetric: | ||
if a certain capability is open in `init` | ||
it should be closed in `finish`, | ||
and whatever starts in `start` | ||
should be stopped in (surprise!) `stop`. | ||
### Init Phase | ||
Initialization tasks, such as connecting to the database. | ||
The init system will make sure that all `require`'d code files have been loaded | ||
before starting this phase. | ||
### Start Phase | ||
Tasks to start the system, such as starting a web server. | ||
These run after all `init` tasks have finished. | ||
### Stop Phase | ||
Tasks to stop the system, such as stopping any open servers. | ||
These run when the system initiates shutdown: either by a signal | ||
(SIGTERM, SIGKILL or control-C) or by an uncaught exception | ||
or an error. | ||
### Finish Phase | ||
Final shutdown tasks, such as disconnecting from the database: | ||
whatever needs to be done before the system definitely closes down. | ||
### Callbacks | ||
All callbacks passed to the four phases must receive another callback | ||
of the form `function(error)` following the Node.js convention, | ||
and chain-call them at the end with either an error | ||
or a falsy value (`null`, `undefined`, nothing) to signal success. | ||
Example: | ||
``` | ||
inits.init(function(next) | ||
{ | ||
DatabaseDriver.connect(url, function(error, connected) | ||
{ | ||
if (error) | ||
{ | ||
return next(error); | ||
} | ||
db = connected; | ||
next(null); | ||
}); | ||
}); | ||
``` | ||
Note how the callback `next` is invoked before the function ends; | ||
this allows `inits` to run asynchronous tasks, | ||
and to regain execution and run any other callbacks. | ||
If your callback is synchronous, simply invoke the callback at the end: | ||
``` | ||
inits.finish(function(next) | ||
{ | ||
db.close(); | ||
next(null); | ||
}); | ||
``` | ||
Note: the choice of callback names is not important, | ||
we have used `next` here but `callback` elsewhere; | ||
whatever is clearer to you. | ||
### No Dependencies | ||
There is no dependency management in `inits`. | ||
This is deliberate; it would be much more complex | ||
for something that is not generally needed. | ||
If you need a certain task to run before another one, | ||
just run them in sequence. | ||
### Other Lifecycles | ||
Sometimes four phases are not enough. | ||
`inits` might be designed to support custom phases in the future | ||
if there is interest; just create an issue if you are interested, | ||
or even better, send a pull request. | ||
## API | ||
The following functions and events are available. | ||
### inits.init(callback) | ||
Add a callback to the initialization. | ||
### inits.start(callback) | ||
Add a callback to be called when starting (after initialization). | ||
### inits.stop(callback) | ||
Add a callback to be called when stopping. | ||
### inits.finish(callback) | ||
Add a callback to be called before finishing. | ||
### inits.standalone(callback) | ||
Set a callback as a standalone task. | ||
Useful when your script consists solely of a task | ||
that must run after startup, followed by shutdown. | ||
### Event: 'ready' | ||
Sent when initialization has finished and the system is ready. | ||
### Event: 'end' | ||
Sent after the system has finished and is about to exit. | ||
Can be used e.g. to call `process.exit()` (which `inits` doesn't do by itself). | ||
### Event: 'error' | ||
Sent when there is an error in any phase. | ||
## Options | ||
To configure `inits` you can set some attributes in `inits.options` | ||
that will modify how the init system behaves. | ||
### catchErrors | ||
If set to `true` (or any other truthy value), | ||
`inits` will catch uncaught exceptions and errors | ||
and shutdown automatically when any of those happens. | ||
Default: `true`. | ||
### catchSignals | ||
If set to `true` (or any other truthy value), | ||
`inits` will intercept SIGTERM and SIGKILL (e.g. control-C) signals | ||
and shutdown when one of them is received. | ||
Default: `true`. | ||
### showErrors | ||
If set to `true` (or any other truthy value), | ||
`inits` will show a log message for every error. | ||
Default: `true`. | ||
### logTimes | ||
If set to `true` (or any other truthy value), | ||
`inits` will log how long initialization and shutdown took. | ||
Default: `true`. | ||
## Full example | ||
How to make a web server that connects to a MongoDB database. | ||
We will hook database startup to the `init` phase, | ||
and server start to the `start` phase. | ||
On `stop` we stop the server, | ||
and on `finish` we close the connection to the database. | ||
``` javascript | ||
var inits = require('inits'); | ||
var MongoClient = require('mongodb').MongoClient | ||
var mongodb = require('mongodb'); | ||
var http = require('http'); | ||
var db; | ||
inits.init(function(callback) | ||
{ | ||
MongoClient.connect(url, function(error, connected) | ||
{ | ||
if (error) | ||
{ | ||
return callback(error); | ||
} | ||
db = connected; | ||
callback(null); | ||
}); | ||
}); | ||
inits.start(function(callback) | ||
{ | ||
server = http.createServer(listener); | ||
server.on('error', function(error) | ||
{ | ||
return callback(error); | ||
}); | ||
server.on('listening', function() | ||
{ | ||
callback(null); | ||
}); | ||
}); | ||
inits.stop(function(callback) | ||
{ | ||
server.close(callback); | ||
}); | ||
inits.finish(function(callback) | ||
{ | ||
db.close(); | ||
callback(null); | ||
}); | ||
``` | ||
## Licensed under The MIT License | ||
@@ -8,0 +245,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
19274
2
9
502
2
257
1
1
- Removedtesting@*
- Removedtesting@3.1.0(transitive)