initial commit
This commit is contained in:
		
						commit
						9a25ad99a1
					
				
							
								
								
									
										7
									
								
								config.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								config.test.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  // crypto.randomBytes(16).toString('hex');
 | 
			
		||||
  key: '1892d335081d8d346e556c9c3c8ff2c3'
 | 
			
		||||
, filename: '/tmp/dbwrap.test.sqlcipher'
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										345
									
								
								lib/dbwrap.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								lib/dbwrap.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,345 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
function wrap(db, dir) {
 | 
			
		||||
  // TODO if I put a failure right here,
 | 
			
		||||
  // why doesn't the unhandled promise rejection fire?
 | 
			
		||||
  var PromiseA = require('bluebird');
 | 
			
		||||
  var promises = [];
 | 
			
		||||
  var dbsMap = {};
 | 
			
		||||
  var arr = true;
 | 
			
		||||
 | 
			
		||||
  function lowerFirst(str) {
 | 
			
		||||
    return str.charAt(0).toLowerCase() + str.slice(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function snakeCase(str) {
 | 
			
		||||
    return lowerFirst(str).replace(
 | 
			
		||||
      /([A-Z])/g
 | 
			
		||||
    , function ($1) {
 | 
			
		||||
        return "_" + $1.toLowerCase();
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function camelCase(str) {
 | 
			
		||||
    return str.replace(
 | 
			
		||||
      /_([a-z])/g
 | 
			
		||||
    , function (g) {
 | 
			
		||||
      return g[1].toUpperCase();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function upperCamelCase(str) {
 | 
			
		||||
    // TODO handle UTF-8 properly (use codePointAt, don't use slice)
 | 
			
		||||
    return camelCase(str).charAt(0).toUpperCase() + str.slice(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function createTable(opts) {
 | 
			
		||||
    var DB = {};
 | 
			
		||||
    var tablename = db.escape(snakeCase(opts.tablename) || 'data');
 | 
			
		||||
    var idname = db.escape(snakeCase(opts.idname) || 'id');
 | 
			
		||||
 | 
			
		||||
    db = PromiseA.promisifyAll(db);
 | 
			
		||||
 | 
			
		||||
    if (opts && opts.verbose || db.verbose) {
 | 
			
		||||
      console.log('Getting Verbose up in here');
 | 
			
		||||
      db.on('trace', function (str) {
 | 
			
		||||
        console.log('SQL:', str);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      db.on('profile', function (sql, ms) {
 | 
			
		||||
        console.log('Profile:', ms);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function simpleParse(row) {
 | 
			
		||||
      var obj;
 | 
			
		||||
 | 
			
		||||
      if (!row) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (row.json) {
 | 
			
		||||
        obj = JSON.parse(row.json);
 | 
			
		||||
      } else {
 | 
			
		||||
        obj = {};
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      obj[idname] = row[idname];
 | 
			
		||||
 | 
			
		||||
      return obj;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function simpleMap(rows) {
 | 
			
		||||
      if (!rows) {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var results = rows.map(function (row, i) {
 | 
			
		||||
        // set up for garbage collection
 | 
			
		||||
        rows[i] = null;
 | 
			
		||||
 | 
			
		||||
        var obj;
 | 
			
		||||
 | 
			
		||||
        if (row.json) {
 | 
			
		||||
          obj = JSON.parse(row.json);
 | 
			
		||||
        } else {
 | 
			
		||||
          obj = {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        obj[idname] = row[idname];
 | 
			
		||||
      });
 | 
			
		||||
      // set up for garbage collection
 | 
			
		||||
      rows.length = 0;
 | 
			
		||||
      rows = null;
 | 
			
		||||
 | 
			
		||||
      return results;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DB.find = function (opts) {
 | 
			
		||||
      var sql = 'SELECT * FROM ' + tablename + ' WHERE ';
 | 
			
		||||
 | 
			
		||||
      Object.keys(opts).forEach(function (key, i) {
 | 
			
		||||
        if (i !== 0) {
 | 
			
		||||
          sql += 'AND ';
 | 
			
		||||
        }
 | 
			
		||||
        sql += db.escape(snakeCase(key)) + ' ' + db.escape(opts[key]);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return db.allAsync("SELECT * FROM " + tablename + " " + sql, []).then(simpleMap);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB.get = function (id) {
 | 
			
		||||
      var sql = "SELECT * FROM " + tablename + " WHERE " + idname + " = ?";
 | 
			
		||||
      var values = [id];
 | 
			
		||||
 | 
			
		||||
      return db.getAsync(sql, values).then(function (rows) {
 | 
			
		||||
        if (Array.isArray(rows)) {
 | 
			
		||||
          if (!rows.length) {
 | 
			
		||||
            return null;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return rows[0] || null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return rows;
 | 
			
		||||
      }).then(simpleParse);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB.upsert = function (id, data) {
 | 
			
		||||
      return DB.set(id, data).then(function (result) {
 | 
			
		||||
        var success = result.changes >= 1;
 | 
			
		||||
 | 
			
		||||
        if (success) {
 | 
			
		||||
          return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return DB.create(id, data);
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB.save = function (data) {
 | 
			
		||||
      if (!data[idname]) {
 | 
			
		||||
        // NOTE saving the id both in the object and the id for now
 | 
			
		||||
        var UUID = require('node-uuid');
 | 
			
		||||
        data[idname] = UUID.v4();
 | 
			
		||||
        return DB.create(data[idname], data).then(function (/*stats*/) {
 | 
			
		||||
          //data._rowid = stats.id;
 | 
			
		||||
          return data;
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return DB.set(data[idname], data).then(function (result) {
 | 
			
		||||
        var success = result.changes >= 1;
 | 
			
		||||
 | 
			
		||||
        if (success) {
 | 
			
		||||
          return result;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB.create = function (id, data) {
 | 
			
		||||
      var json = JSON.stringify(data);
 | 
			
		||||
 | 
			
		||||
      return new PromiseA(function (resolve, reject) {
 | 
			
		||||
        var sql = "INSERT INTO " + tablename + " (" + idname + ", json) VALUES (?, ?)";
 | 
			
		||||
        var values = [id, json];
 | 
			
		||||
 | 
			
		||||
        db.run(sql, values, function (err) {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            reject(err);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // NOTE changes is 1 even if the value of the updated record stays the same
 | 
			
		||||
          // (PostgreSQL would return 0 in that case)
 | 
			
		||||
          // thus if changes is 0 then it failed, otherwise it succeeded
 | 
			
		||||
          /*
 | 
			
		||||
          console.log('[log db wrapper insert]');
 | 
			
		||||
          console.log(this); // sql, lastID, changes
 | 
			
		||||
          console.log(this.sql);
 | 
			
		||||
          console.log('insert lastID', this.lastID); // sqlite's internal rowId
 | 
			
		||||
          console.log('insert changes', this.changes);
 | 
			
		||||
          */
 | 
			
		||||
 | 
			
		||||
          //this.id = id;
 | 
			
		||||
          resolve(this);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB.set = function (id, obj) {
 | 
			
		||||
      var json = JSON.stringify(obj);
 | 
			
		||||
      var data = JSON.parse(json);
 | 
			
		||||
 | 
			
		||||
      return new PromiseA(function (resolve, reject) {
 | 
			
		||||
        var sql;
 | 
			
		||||
        var fieldable = [];
 | 
			
		||||
        var vals = [];
 | 
			
		||||
 | 
			
		||||
        (opts.indices || []).forEach(function (col) {
 | 
			
		||||
          if ('string' === typeof col) {
 | 
			
		||||
            col = { name: col, type: 'TEXT' };
 | 
			
		||||
          }
 | 
			
		||||
          if (!col.type) {
 | 
			
		||||
            col.type = 'TEXT';
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          var val = data[camelCase(col.name)];
 | 
			
		||||
 | 
			
		||||
          //if (col.name in data)
 | 
			
		||||
          if ('undefined' !== typeof val) {
 | 
			
		||||
            /*
 | 
			
		||||
            fieldable.push(
 | 
			
		||||
              db.escape(snakeCase(col.name))
 | 
			
		||||
            + " = '" + db.escape(val) + "'"
 | 
			
		||||
            );
 | 
			
		||||
            */
 | 
			
		||||
            fieldable.push(db.escape(snakeCase(col.name)) + " = ?");
 | 
			
		||||
            vals.push(val);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          delete data[col.name];
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        json = JSON.stringify(data);
 | 
			
		||||
        fieldable.push("json = ?");
 | 
			
		||||
        //fieldable.push("json = '" + db.escape(json) + "'");
 | 
			
		||||
        vals.push(json);
 | 
			
		||||
 | 
			
		||||
        vals.push(id);
 | 
			
		||||
        sql = "UPDATE " + tablename + " SET " + fieldable.join(', ') + " WHERE " + idname + " = ?";
 | 
			
		||||
 | 
			
		||||
        while (vals.length) {
 | 
			
		||||
          sql = sql.replace(/\?/, "'" + db.escape(vals.shift()) + "'");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.log('[debug] sql:', sql);
 | 
			
		||||
        db.run(sql, vals, function (err) {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            reject(err);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // it isn't possible to tell if the update succeeded or failed
 | 
			
		||||
          // only if the update resulted in a change or not
 | 
			
		||||
          /*
 | 
			
		||||
          console.log('[log db wrapper set]');
 | 
			
		||||
          console.log(this); // sql, lastID, changes
 | 
			
		||||
          console.log(this.sql);
 | 
			
		||||
          console.log('update lastID', this.lastID); // always 0 (except on INSERT)
 | 
			
		||||
          console.log('update changes', this.changes);
 | 
			
		||||
          */
 | 
			
		||||
 | 
			
		||||
          resolve(this);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB.destroy = function (id) {
 | 
			
		||||
      if ('object' === typeof id) {
 | 
			
		||||
        id = id[idname];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return new PromiseA(function (resolve, reject) {
 | 
			
		||||
        var sql = "DELETE FROM " + tablename + " WHERE " + idname + " = ?";
 | 
			
		||||
        var values = [id];
 | 
			
		||||
 | 
			
		||||
        db.run(sql, values, function (err) {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            reject(err);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // it isn't possible to tell if the update succeeded or failed
 | 
			
		||||
          // only if the update resulted in a change or not
 | 
			
		||||
          /*
 | 
			
		||||
          console.log('[log db wrapper delete]');
 | 
			
		||||
          console.log(this); // sql, lastID, changes
 | 
			
		||||
          console.log(this.sql);
 | 
			
		||||
          console.log('delete lastID', this.lastID); // always 0 (except on INSERT)
 | 
			
		||||
          console.log('delete changes', this.changes);
 | 
			
		||||
          */
 | 
			
		||||
 | 
			
		||||
          resolve(this);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DB._db = db;
 | 
			
		||||
 | 
			
		||||
    return new PromiseA(function (resolve, reject) {
 | 
			
		||||
      var indexable = [idname + ' TEXT'];
 | 
			
		||||
      var sql;
 | 
			
		||||
 | 
			
		||||
      (opts.indices || []).forEach(function (col) {
 | 
			
		||||
        if ('string' === typeof col) {
 | 
			
		||||
          col = { name: col, type: 'TEXT' };
 | 
			
		||||
        }
 | 
			
		||||
        if (!col.type) {
 | 
			
		||||
          col.type = 'TEXT';
 | 
			
		||||
        }
 | 
			
		||||
        indexable.push(
 | 
			
		||||
          db.escape(snakeCase(col.name))
 | 
			
		||||
        + ' ' + db.escape(col.type)
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
      indexable.push('json TEXT');
 | 
			
		||||
 | 
			
		||||
      sql = "CREATE TABLE IF NOT EXISTS '" + tablename + "' "
 | 
			
		||||
        + "(" + indexable.join(', ') + ", PRIMARY KEY(" + idname + "))"
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      db.runAsync(sql).then(function () { resolve(DB); }, reject);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!Array.isArray(dir)) {
 | 
			
		||||
    arr = false;
 | 
			
		||||
    dir = [dir];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dir.forEach(function (opts) {
 | 
			
		||||
    promises.push(createTable(opts).then(function (dbw) {
 | 
			
		||||
      var tablename = (opts.tablename || 'data');
 | 
			
		||||
 | 
			
		||||
      tablename = upperCamelCase(tablename);
 | 
			
		||||
 | 
			
		||||
      dbsMap[tablename] = dbw;
 | 
			
		||||
 | 
			
		||||
      return dbw;
 | 
			
		||||
    }));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  dbsMap.sql = db;
 | 
			
		||||
 | 
			
		||||
  return PromiseA.all(promises).then(function (dbs) {
 | 
			
		||||
    if (!arr) {
 | 
			
		||||
      return dbs[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return dbsMap;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports.wrap = wrap;
 | 
			
		||||
							
								
								
									
										19
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "dbwrap",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "The world's worst database abstraction layer - just in case the others didn't suck enough",
 | 
			
		||||
  "main": "lib/dbwrap",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "test": "node tests/dbwrap"
 | 
			
		||||
  },
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "git+ssh://git@bitbucket.org/daplie/dbwrap.git"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.com/)",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "homepage": "https://bitbucket.org/daplie/dbwrap#readme",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "bluebird": "^2.9.34"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								tests/dbwrap.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								tests/dbwrap.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var PromiseA = require('bluebird').Promise;
 | 
			
		||||
 | 
			
		||||
function testDb(DB) {
 | 
			
		||||
  return PromiseA.resolve(DB).then(function (DB) {
 | 
			
		||||
    var data = { secret: 'super secret', verifiedAt: 1437207288791 };
 | 
			
		||||
    //return DB.set('aj@the.dj', data)
 | 
			
		||||
    //return DB.set('john.doe@email.com', data)
 | 
			
		||||
    // return DB.upsert('jane.doe@email.com', data)
 | 
			
		||||
    return DB.upsert('jane.doe@email.com', data).then(function () {
 | 
			
		||||
      console.info('[PASS] added user');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    return DB.create('john.doe@email.com', data).then(function () {
 | 
			
		||||
      console.log('added user');
 | 
			
		||||
    });
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    // need to 'DELETE FROM authn;' first
 | 
			
		||||
    return DB.get('john.doe@email.com').then(function (user) {
 | 
			
		||||
      if (user) {
 | 
			
		||||
        console.info('user', user);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //var data = { secret: 'super secret', verifiedAt: Date.now() };
 | 
			
		||||
      var data = { secret: 'super secret', verifiedAt: 1437207288790 };
 | 
			
		||||
      return DB.create('john.doe@email.com', data).then(function () {
 | 
			
		||||
        console.info('added user');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function () {}, function (err) {
 | 
			
		||||
    // code SQLITE_CONSTRAINT
 | 
			
		||||
    // errno 19
 | 
			
		||||
 | 
			
		||||
    console.error('[ERROR] during test');
 | 
			
		||||
    //console.error(Object.keys(err)); // errno, code
 | 
			
		||||
    console.error(err);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function run(/*isMaster*/) {
 | 
			
		||||
  require('./setup').run().then(testDb);
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
  if (require.main === module) {
 | 
			
		||||
    // crypto.randomBytes(16).toString('hex');
 | 
			
		||||
    create({
 | 
			
		||||
      key: '1892d335081d8d346e556c9c3c8ff2c3'
 | 
			
		||||
    , bits: 128
 | 
			
		||||
    , filename: '/tmp/authn.sqlcipher'
 | 
			
		||||
    }).then(function (DB) {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
run();
 | 
			
		||||
							
								
								
									
										32
									
								
								tests/setup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/setup.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
function run(opts) {
 | 
			
		||||
  var config = require('../config.test.js');
 | 
			
		||||
  var sqlite3 = require('sqlite3-cluster');
 | 
			
		||||
  var wrap = require('../lib/dbwrap');
 | 
			
		||||
 | 
			
		||||
  var promise = sqlite3.create({
 | 
			
		||||
      standalone: true
 | 
			
		||||
    , bits: 128
 | 
			
		||||
    , filename: config.filename
 | 
			
		||||
    , verbose: false
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return promise.then(function (db) {
 | 
			
		||||
    return db.init({ bits: 128, key: config.key });
 | 
			
		||||
  }).then(function (db) {
 | 
			
		||||
    return wrap.wrap(db, Array.isArray(opts) && opts || { idname: 'uuid', tablename: opts && opts.tablename || 'authn' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
  if (require.main === module) {
 | 
			
		||||
    create({
 | 
			
		||||
      key: '1892d335081d8d346e556c9c3c8ff2c3'
 | 
			
		||||
    , bits: 128
 | 
			
		||||
    , filename: '/tmp/authn.sqlcipher'
 | 
			
		||||
    }).then(function (DB) {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = run;
 | 
			
		||||
module.exports.run = run;
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user