From efc10cab3b26b9c1ee38bd67f9bc557b4db4bcbd Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 10 Nov 2015 12:16:50 +0000 Subject: [PATCH] initial test of factory --- client.js | 104 +++++++++++++------------ cluster.js | 2 + examples/cluster2/index.js | 8 ++ examples/cluster2/master.js | 51 +++++++++++++ examples/cluster2/worker.js | 118 +++++++++++++++++++++++++++++ examples/factory/index.js | 8 ++ examples/factory/master.js | 48 ++++++++++++ examples/factory/worker.js | 115 ++++++++++++++++++++++++++++ server.js | 147 +++++++++++++++++++++++++----------- wrapper.js | 99 +++++++++++++----------- 10 files changed, 564 insertions(+), 136 deletions(-) create mode 100644 examples/cluster2/index.js create mode 100644 examples/cluster2/master.js create mode 100644 examples/cluster2/worker.js create mode 100644 examples/factory/index.js create mode 100644 examples/factory/master.js create mode 100644 examples/factory/worker.js diff --git a/client.js b/client.js index 180fbd8..2427a49 100644 --- a/client.js +++ b/client.js @@ -56,7 +56,7 @@ function getConnection(opts, verbs, mySocket) { ws.on('error', function (err) { console.error('[ERROR] ws connection failed, retrying'); - console.error(err.stack); + console.error(err.stack || err); function retry() { // TODO eventually throw up @@ -72,7 +72,7 @@ function getConnection(opts, verbs, mySocket) { resolve({ masterClient: client }); }, function (err) { console.error('[ERROR] failed to connect to sqlite3-cluster service. retrying...'); - console.error(err); + console.error(err.stack || err); retry(); }); } @@ -110,6 +110,15 @@ module.exports.createClientFactory = function (conf, verbs, _socket) { if (_socket && _s) { throw new Error("[E_USR_SOCKET] Your parent has decided that you may not choose your own SOCKET. Don't get mad at me, take it up with them."); } + if (opts.key && conf.key) { + throw new Error("[E_USR_KEY] Your parent has decided that you may not choose your own KEY. Don't get mad at me, take it up with them."); + } + if (opts.algo && conf.algo) { + throw new Error("[E_USR_ALGO] Your parent has decided that you may not choose your own ALGO. Don't get mad at me, take it up with them."); + } + if (opts.bits && conf.bits) { + throw new Error("[E_USR_BITS] Your parent has decided that you may not choose your own BITS. Don't get mad at me, take it up with them."); + } if (opts.dirname && conf.dirname) { throw new Error("[E_USR_TENANT] Your parent has decided that you may not choose your own TENANT. Don't get mad at me, take it up with them."); } @@ -173,6 +182,23 @@ module.exports.create = function (opts, verbs, mySocket) { } function create(opts) { + function retryServe() { + return startServer(opts, verbs).then(function (client) { + // ws.masterClient = client; + return { masterClient: client }; + }, function (err) { + console.error('[ERROR] retryServe()'); + console.error(err.stack || err); + retryServe(); + }); + } + + if (opts.serve) { + return retryServe(opts).then(function (servers) { + return servers.masterClient; + }); + } + if (!opts.tenant) { opts.tenant = ""; } @@ -201,37 +227,17 @@ module.exports.create = function (opts, verbs, mySocket) { return require('./wrapper').create(opts, verbs); } - function retryServe() { - return startServer(opts, verbs).then(function (client) { - // ws.masterClient = client; - return { masterClient: client }; - }, function (err) { - console.error('[ERROR] retryServe()'); - console.error(err); - retryServe(); - }); - } - if (!opts.sock) { throw new Error("Please specify opts.sock as the path to the master socket. '/tmp/sqlite3-cluster' would do nicely."); } - if (opts.serve) { - promise = retryServe(opts); - } else { - promise = getConnection(opts, verbs, mySocket).then(function (socket) { - mySocket = socket; - return mySocket; - }); - } + promise = getConnection(opts, verbs, mySocket).then(function (socket) { + mySocket = socket; + return mySocket; + }); // TODO maybe use HTTP POST instead? return promise.then(function (ws) { - if (ws.masterClient) { - // for the server - return ws.masterClient; - } - var db = {}; var proto = sqlite3real.Database.prototype; var messages = []; @@ -243,15 +249,26 @@ module.exports.create = function (opts, verbs, mySocket) { return idprefix + idcount; } - function init(opts) { - return new PromiseA(function (resolve, reject) { - // TODO needs to reject by a timeout + function init(iopts) { + console.log('CLIENT INIT'); + if (db._initPromise) { + return db._initPromise; + } + db._initPromise = new PromiseA(function (resolve, reject) { + // TODO needs to reject by a timeout var id = genId(); ws.send(JSON.stringify({ type: 'init' - , args: [opts] + , args: [{ + // encryption + bits: opts.bits || iopts.bits + , algorithm: opts.algo || opts.algorithm || iopts.algorithm || iopts.algo + , algo: opts.algo || opts.algorithm || iopts.algorithm || iopts.algo + , encmode: opts.mode || iopts.mode + }] , func: 'init' + // db , dirname: opts.dirname , prefix: opts.prefix , subtenant: opts.subtenant @@ -259,34 +276,23 @@ module.exports.create = function (opts, verbs, mySocket) { , dbname: opts.dbname , suffix: opts.suffix , ext: opts.ext + // session , id: id })); function onMessage(data) { var cmd; - if ( - (data.dbname !== opts.dbname) - || (data.dirname !== opts.dirname) - || (data.prefix !== opts.prefix) - || (data.subtenant !== opts.subtenant) - || (data.tenant !== opts.tenant) - || (data.suffix !== opts.suffix) - || (data.ext !== opts.ext) - ) { - return reject(new Error("suxors to rejexors")); - } - try { cmd = JSON.parse(data.toString('utf8')); } catch(e) { console.error('[ERROR] in client, from sql server parse json'); - console.error(e); + console.error(e.stack || e); console.error(data); console.error(); // ignore this message, it came out of order - return reject(new Error("suxors to rejexors")); + return reject(new Error("suxors to rejexors parse")); } if (cmd.id !== id) { @@ -301,17 +307,21 @@ module.exports.create = function (opts, verbs, mySocket) { messages.splice(messages.indexOf(onMessage), 1); if ('error' === cmd.type) { + //console.log('ERROR ARGS'); + //console.log(cmd); reject(cmd.args[0]); return; } - //console.log('RESOLVING INIT'); + console.log('CLIENT RESOLVING INIT'); resolve(cmd.args[0]); return; } messages.push(onMessage); }); + + return db._initPromise; } function rpcThunk(fname, args) { @@ -348,7 +358,7 @@ module.exports.create = function (opts, verbs, mySocket) { cmd = JSON.parse(data.toString('utf8')); } catch(e) { console.error('[ERROR] in client, from sql server parse json'); - console.error(e); + console.error(e.stack || e); console.error(data); console.error(); @@ -398,7 +408,7 @@ module.exports.create = function (opts, verbs, mySocket) { fn(data); } catch(e) { console.error("[ERROR] ws.on('message', fn) (multi-callback)"); - console.error(e); + console.error(e.stack || e); // ignore } }); diff --git a/cluster.js b/cluster.js index 54e78e9..fded6cc 100644 --- a/cluster.js +++ b/cluster.js @@ -20,3 +20,5 @@ function create(opts) { module.exports.sanitize = sqlite3.sanitize; module.exports.escape = sqlite3.escape; module.exports.create = create; +module.exports.createServer = sqlite3.createServer; +module.exports.createMasterClient = sqlite3.createMasterClient; diff --git a/examples/cluster2/index.js b/examples/cluster2/index.js new file mode 100644 index 0000000..7819df8 --- /dev/null +++ b/examples/cluster2/index.js @@ -0,0 +1,8 @@ +var cluster = require('cluster'); + +if (cluster.isMaster) { + require('./master'); +} +else { + require('./worker'); +} diff --git a/examples/cluster2/master.js b/examples/cluster2/master.js new file mode 100644 index 0000000..65752a3 --- /dev/null +++ b/examples/cluster2/master.js @@ -0,0 +1,51 @@ +'use strict'; + +var cluster = require('cluster'); +var minCores = 3; +var numCores = Math.max(minCores, require('os').cpus().length); +var i; +var sock = '/tmp/foobar.sqlite3-cluster.test.sock'; + +function run(connect, ipcKey) { + var sqlite3 = require('../../cluster'); + + return sqlite3.create({ + verbose: null + , standalone: null + , serve: sock + , connect: null + , sock: sock + , ipcKey: ipcKey + }); +} + +var ipcKey = require('crypto').randomBytes(16).toString('hex'); +// not a bad idea to setup the master before forking the workers + +run(false, ipcKey).then(function () { + var w; + + function setupWorker(w) { + function sendKey() { + w.send({ ipcKey: ipcKey, sock: sock }); + } + w.on('online', sendKey); + } + + for (i = 1; i <= numCores; i += 1) { + w = cluster.fork(); + setupWorker(w); + } +}); + +process.on('beforeExit', function () { + console.log("[MASTER] I've got nothing left to live for... ugh... death is upon me..."); +}); +// The native Promise implementation ignores errors because... dumbness??? +process.on('unhandledRejection', function (err) { + console.error('Unhandled Promise Rejection'); + console.error(err); + console.error(err.stack); + + process.exit(1); +}); diff --git a/examples/cluster2/worker.js b/examples/cluster2/worker.js new file mode 100644 index 0000000..ab44b2c --- /dev/null +++ b/examples/cluster2/worker.js @@ -0,0 +1,118 @@ +'use strict'; + +var cluster = require('cluster'); + +console.log("[Worker #" + cluster.worker.id + "]"); + +function testSelect(client) { + var PromiseA = require('bluebird'); + + return new PromiseA(function (resolve, reject) { + client.run('CREATE TABLE IF NOT EXISTS meta (version TEXT)', function (err) { + if (err) { + if ('E_NO_INIT' === err.code) { + console.error(err.message); + resolve(client); + return; + } + + reject(err); + return; + } + + client.get("SELECT version FROM meta", [], function (err, result) { + if (err) { + console.error('[ERROR] create table', cluster.isMaster && '0' || cluster.worker.id); + console.error(err.stack || err); + reject(err); + return; + } + + console.log('[this] Worker #', cluster.isMaster && '0' || cluster.worker.id); + console.log(this); + + console.log('[result] Worker #', cluster.isMaster && '0' || cluster.worker.id); + console.log(result); + + resolve(client); + }); + }); + }); +} + +function run(ipcKey, sock) { + var sqlite3 = require('../../cluster'); + + return sqlite3.create({ + bits: 128 + , dirname: '/tmp/' + , prefix: 'foobar.' + , dbname: 'cluster' + , suffix: '.test' + , ext: '.sqlcipher' + , verbose: null + , standalone: null + , serve: null + , connect: sock + , sock: sock + , ipcKey: ipcKey + }); +} + +function init(client) { + //console.log('[INIT] begin'); + return client.init({ + algorithm: 'aes' + , bits: 128 + , mode: 'cbc' + , key: '00000000000000000000000000000000' + }).then(function (/*args*/) { + //console.log('[INIT]', args); + return client; + }); +} + +function onMessage(msg) { + function loseTheWillToLive() { + process.removeListener('message', onMessage); + // child processes do not exit when their event loop is empty + setTimeout(function () { + console.log('#' + cluster.worker.id + ' dead'); + process.exit(0); + }, 100); + } + + console.log('New Worker', cluster.worker.id, msg); + if (1 === cluster.worker.id) { + //setTimeout(function () { + run(msg.ipcKey, msg.sock).then(testSelect).then(function (client) { + setTimeout(function () { + console.log('init worker closing...'); + client.close(loseTheWillToLive); + loseTheWillToLive(); + }, 1000); + // waiting for https://github.com/websockets/ws/issues/613 to land + }); + //}, 1000); + } else { + setTimeout(function () { + run(msg.ipcKey, msg.sock).then(init).then(testSelect).then(function (client) { + console.log('#' + cluster.worker.id + ' other working closing...'); + client.close(loseTheWillToLive); + loseTheWillToLive(); + }); + }, 100 * cluster.worker.id); + } +} +process.on('message', onMessage); + +// The native Promise implementation ignores errors because... dumbness??? +process.on('beforeExit', function () { + console.log("[WORKER] I've got nothing left to do"); +}); +process.on('unhandledRejection', function (err) { + console.error('Unhandled Promise Rejection'); + console.error(err.stack || err); + + process.exit(1); +}); diff --git a/examples/factory/index.js b/examples/factory/index.js new file mode 100644 index 0000000..7819df8 --- /dev/null +++ b/examples/factory/index.js @@ -0,0 +1,8 @@ +var cluster = require('cluster'); + +if (cluster.isMaster) { + require('./master'); +} +else { + require('./worker'); +} diff --git a/examples/factory/master.js b/examples/factory/master.js new file mode 100644 index 0000000..daaf256 --- /dev/null +++ b/examples/factory/master.js @@ -0,0 +1,48 @@ +'use strict'; + +var cluster = require('cluster'); +var minCores = 3; +var numCores = Math.max(minCores, require('os').cpus().length); +var i; +var sock = '/tmp/foobar.sqlite3-cluster.test.sock'; + +function run(connect, ipcKey) { + var sqlite3 = require('../../server'); + + return sqlite3.createServer({ + verbose: null + , sock: sock + , ipcKey: ipcKey + }); +} + +var ipcKey = require('crypto').randomBytes(16).toString('hex'); +// not a bad idea to setup the master before forking the workers + +run(false, ipcKey).then(function () { + var w; + + function setupWorker(w) { + function sendKey() { + w.send({ ipcKey: ipcKey, sock: sock }); + } + w.on('online', sendKey); + } + + for (i = 1; i <= numCores; i += 1) { + w = cluster.fork(); + setupWorker(w); + } +}); + +process.on('beforeExit', function () { + console.log("[MASTER] I've got nothing left to live for... ugh... death is upon me..."); +}); +// The native Promise implementation ignores errors because... dumbness??? +process.on('unhandledRejection', function (err) { + console.error('Unhandled Promise Rejection'); + console.error(err); + console.error(err.stack); + + process.exit(1); +}); diff --git a/examples/factory/worker.js b/examples/factory/worker.js new file mode 100644 index 0000000..3a2fd36 --- /dev/null +++ b/examples/factory/worker.js @@ -0,0 +1,115 @@ +'use strict'; + +var cluster = require('cluster'); + +console.log("[Worker #" + cluster.worker.id + "]"); + +function testSelect(client) { + var PromiseA = require('bluebird'); + + return new PromiseA(function (resolve, reject) { + client.run('CREATE TABLE IF NOT EXISTS meta (version TEXT)', function (err) { + if (err) { + if ('E_NO_INIT' === err.code) { + console.error(err.message); + resolve(client); + return; + } + + reject(err); + return; + } + + client.get("SELECT version FROM meta", [], function (err, result) { + if (err) { + console.error('[ERROR] create table', cluster.isMaster && '0' || cluster.worker.id); + console.error(err.stack || err); + reject(err); + return; + } + + console.log('Worker #', (cluster.isMaster && '0' || cluster.worker.id), '[this], [result]:'); + console.log(this); + console.log(result); + + resolve(client); + }); + }); + }); +} + +function run(clientFactory) { + return clientFactory.create({ + init: false + , dbname: 'factory.' + cluster.worker.id + }); +} + +function init(client) { + return client.init({ + algorithm: 'aes' + , bits: 128 + , mode: 'cbc' + , key: '00000000000000000000000000000000' + }).then(function (/*db*/) { + return client; + }); +} + +function onMessage(msg) { + function loseTheWillToLive() { + process.removeListener('message', onMessage); + // child processes do not exit when their event loop is empty + setTimeout(function () { + console.log('#' + cluster.worker.id + ' dead'); + process.exit(0); + }, 100); + } + + var clientFactory = require('../../client').createClientFactory({ + algorithm: 'aes' + , bits: 128 + , mode: 'cbc' + , dirname: '/tmp/' + , prefix: 'foobar.' + //, dbname: 'cluster' + , suffix: '.test' + , ext: '.sqlcipher' + , sock: msg.sock + , ipcKey: msg.ipcKey + }); + + console.log('New Worker', cluster.worker.id, msg); + if (1 === cluster.worker.id) { + //setTimeout(function () { + run(clientFactory).then(testSelect).then(function (client) { + setTimeout(function () { + console.log('init worker closing...'); + client.close(loseTheWillToLive); + loseTheWillToLive(); + }, 1000); + // waiting for https://github.com/websockets/ws/issues/613 to land + }); + //}, 1000); + } else { + setTimeout(function () { + run(clientFactory).then(init).then(testSelect).then(function (client) { + console.log('#' + cluster.worker.id + ' other working closing...'); + client.close(loseTheWillToLive); + loseTheWillToLive(); + }); + }, 100 * cluster.worker.id); + } +} +process.on('message', onMessage); + +// The native Promise implementation ignores errors because... dumbness??? +process.on('beforeExit', function () { + console.log("[WORKER] I've got nothing left to do"); +}); +process.on('unhandledRejection', function (err) { + console.error('Unhandled Promise Rejection'); + console.error(err.stack || err); + + process.exit(1); +}); diff --git a/server.js b/server.js index 9ad97a6..f248540 100644 --- a/server.js +++ b/server.js @@ -4,33 +4,25 @@ var PromiseA = require('bluebird').Promise; var wsses = {}; function createApp(servers, options) { - if (wsses[options.sock]) { - return PromiseA.resolve(wsses[options.sock]); - } - - var url = require('url'); + var wss = servers.wss; + //var server = servers.server; + //var express = require('express'); //var app = express(); - var wss = servers.wss; - var server = servers.server; - + /* function app(req, res) { res.end('NOT IMPLEMENTED'); } + */ wss.on('connection', function (ws) { - if (!wss.__count) { - wss.__count = 0; - } - wss.__count += 1; - var location = url.parse(ws.upgradeReq.url, true); // you might use location.query.access_token to authenticate or share sessions // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312 if (!options.ipcKey) { - console.warn("[SECURITY] please include { ipcKey: crypto.randomBytes(16).toString('base64') }" + console.warn("[S] [SECURITY] please include { ipcKey: crypto.randomBytes(16).toString('base64') }" + " in your options and pass it from master to worker processes with worker.send()"); ws._authorized = true; } else { @@ -43,13 +35,33 @@ function createApp(servers, options) { return; } - ws.on('close', function () { + if (!wss.__count) { + wss.__count = 0; + } + wss.__count += 1; + + function decrWs() { wss.__count -= 1; if (!wss.__count) { - wss.close(); - server.close(); + console.log('[S] client count is zero, but server will be left open'); + /* + wss.close(function () { + console.log('wss.closed'); + }); + server.close(function () { + console.log('server closed, but will not exit due to bug? in wss.close()'); + process.exit(0); + }); + */ } + } + + ws.on('error', function (err) { + console.error('[S] [WebSocket error]'); + console.error(err.stack); + decrWs(); }); + ws.on('close', decrWs); ws.on('message', function (buffer) { var cmd; var promise; @@ -57,19 +69,22 @@ function createApp(servers, options) { try { cmd = JSON.parse(buffer.toString('utf8')); } catch(e) { - console.error('[ERROR] parse json'); - console.error(e); + console.error('[S] [ERROR] parse json'); + console.error(e.stack || e); console.error(buffer); console.error(); ws.send(JSON.stringify({ type: 'error', value: { message: e.message, code: "E_PARSE_JSON" } })); return; } + //console.log('cmd'); + //console.log(cmd); // caching and create logic happens in the wrapper stored here below - promise = require('./wrapper').create(options, cmd).then(function (db) { + promise = require('./wrapper').create(cmd && cmd.dbname && cmd || options).then(function (db) { switch(cmd.type) { case 'init': + console.log('[S] init', cmd); db[cmd.func].apply(db, cmd.args).then(function () { var args = Array.prototype.slice.call(arguments); var myself; @@ -90,13 +105,13 @@ function createApp(servers, options) { break; case 'rpc': - if (!db._initialized) { + if ('close' !== cmd.func && !db._initialized) { //console.log('[RPC NOT HAPPENING]'); ws.send(JSON.stringify({ type: 'error' , id: cmd.id - , args: [{ message: 'database has not been initialized' }] - , error: { message: 'database has not been initialized' } + , args: [{ message: 'database has not been initialized', code: 'E_NO_INIT' }] + , error: { message: 'database has not been initialized', code: 'E_NO_INIT' } })); return; } @@ -132,37 +147,77 @@ function createApp(servers, options) { }); }); - //app.masterClient = db; - wsses[options.sock] = app; + // wsses[options.sock] = app; + return PromiseA.resolve(); +} - return PromiseA.resolve(app); +function newSocket(options) { + if (wsses[options.sock]) { + return PromiseA.resolve(wsses[options.sock]); + } + + wsses[options.sock] = new PromiseA(function (resolve) { + var fs = require('fs'); + fs.unlink(options.sock, function () { + var server = require('http').createServer(); + // ignore error when socket doesn't exist + server.listen(options.sock, function () { + resolve(server); + }); + }); + }).then(function (server) { + var WebSocketServer = require('ws').Server; + var servers = { + server: require('http').createServer() + , wss: new WebSocketServer({ server: server }) + }; + + return createApp(servers, options).then(function (/*app*/) { + // server.on('request', app); + wsses[options.sock] = servers; + + return wsses[options.sock]; + }); + }); + + return wsses[options.sock]; +} + +function createMasterClient(options) { + return require('./wrapper').create(options, null).then(function (db) { + return db; + }); +} + +function createServer(options) { + if (!options.sock) { + throw new Error("Please provide options.sock as the socket to serve from"); + } + options.server = options.sock; + + return newSocket(options).then(function () { + var result = {}; + + Object.keys(wsses[options.sock]).forEach(function (key) { + result[key] = wsses[options.sock][key]; + }); + }); } function create(options) { - var server = require('http').createServer(); - var WebSocketServer = require('ws').Server; - var wss = new WebSocketServer({ server: server }); - //var port = process.env.PORT || process.argv[0] || 4080; + return createServer(options).then(function (result) { + if (!options.dbname) { + return result; + } - var fs = require('fs'); - var ps = []; + return createMasterClient(options).then(function (db) { + result.masterClient = db; - ps.push(new PromiseA(function (resolve) { - fs.unlink(options.sock, function () { - // ignore error when socket doesn't exist - - server.listen(options.sock, resolve); + return result; }); - })); - - ps.push(createApp({ server: server, wss: wss }, options).then(function (app) { - server.on('request', app); - return { masterClient: app.masterClient || true }; - })); - - return PromiseA.all(ps).then(function (results) { - return results[1]; }); } module.exports.create = create; +module.exports.createMasterClient = createMasterClient; +module.exports.createServer = createServer; diff --git a/wrapper.js b/wrapper.js index 6f4284a..e7c40cd 100644 --- a/wrapper.js +++ b/wrapper.js @@ -1,6 +1,7 @@ 'use strict'; var sqlite3 = require('sqlite3'); +// TODO expire unused dbs from cache var dbs = {}; function sanitize(str) { @@ -11,52 +12,31 @@ function create(opts, verbs) { if (!verbs) { verbs = {}; } - var db; - var PromiseA = verbs.Promise || require('bluebird'); - if (!opts) { opts = {}; } - if (opts.verbose) { - sqlite3.verbose(); - } - - // TODO expire unused dbs from cache + var db; + var PromiseA = verbs.Promise || require('bluebird'); var dbname = ""; - if (opts.dirname) { - dbname += opts.dirname; - } - if (opts.prefix) { - dbname += opts.prefix; - } + + dbname += (opts.dirname || ''); + dbname += (opts.prefix || ''); if (opts.subtenant) { dbname += opts.subtenant + '.'; } if (opts.tenant) { dbname += opts.tenant + '.'; } - if (opts.dbname) { - dbname += opts.dbname; - } - if (opts.suffix) { - dbname += opts.suffix; - } - if (opts.ext) { - dbname += opts.ext; - } + dbname += (opts.dbname || ''); + dbname += (opts.suffix || ''); + dbname += (opts.ext || ''); - if (dbs[dbname]) { - return PromiseA.resolve(dbs[dbname]); - } + function initDb(newOpts) { + if (dbs[dbname].initPromise) { + return dbs[dbname].initPromise; + } - - db = new sqlite3.Database(dbname); - // dbs[dbname] = db // - db.sanitize = sanitize; - db.escape = sanitize; - - db.init = function (newOpts) { if (!newOpts) { newOpts = {}; } @@ -64,18 +44,19 @@ function create(opts, verbs) { var key = newOpts.key || opts.key; var bits = newOpts.bits || opts.bits; - return new PromiseA(function (resolve, reject) { - if (db._initialized) { - dbs[dbname] = db; + dbs[dbname].initPromise = new PromiseA(function (resolve, reject) { + if (dbs[dbname].db._initialized) { resolve(db); return; } if (!key) { if (!bits) { - db._initialized = true; + //console.log("INITIALIZED WITHOUT KEY"); + //console.log(opts); + dbs[dbname].db._initialized = true; } - dbs[dbname] = db; + dbs[dbname].db = db; resolve(db); return; } @@ -107,16 +88,48 @@ function create(opts, verbs) { PromiseA.all(setup).then(function () { // restore original functions - db._initialized = true; - dbs[dbname] = db; + dbs[dbname].db._initialized = true; + dbs[dbname].db = db; + resolve(db); }, reject); }); }); - }; - dbs[dbname] = db.init(opts); - return dbs[dbname]; + return dbs[dbname].initPromise; + } + + function newDb() { + // dbs[dbname] = db // + db = new sqlite3.Database(dbname); + db.init = initDb; + db.sanitize = sanitize; + db.escape = sanitize; + + if (opts.verbose) { + sqlite3.verbose(); + } + + return db; + } + + // Could be any of: + // * db object + // * init promise + + if (!dbs[dbname]) { + dbs[dbname] = { db: newDb() }; + } + + if (dbs[dbname].db._initialized) { + return PromiseA.resolve(dbs[dbname].db); + } + + if (opts.init || ('init' === opts.type) || (opts.bits && opts.key)) { + dbs[dbname].initPromise = db.init(opts); + } + + return dbs[dbname].initPromise || PromiseA.resolve(dbs[dbname].db); } module.exports.sanitize = sanitize;