Comparing version 0.0.2 to 0.0.4
@@ -10,3 +10,7 @@ var keydb = require('./lib/keydb'); | ||
keydb.driver('version', require('./lib/drivers/version')); | ||
keydb.driver('table', require('./lib/drivers/table')); | ||
keydb.driver('kv-mysql', require('./lib/drivers/kv-mysql')); | ||
keydb.sugar('kv-mysql', require('./lib/sugar/kv-mysql')); | ||
module.exports = keydb; |
@@ -6,2 +6,12 @@ var q = require('q'); | ||
var dbTypes = { | ||
string: function (prop) { | ||
if (prop.maxLength && prop.maxLength < 10000) { | ||
return 'varchar(' + prop.maxLength + ') collate latin1_bin'; | ||
} else { | ||
return 'longblob'; | ||
} | ||
} | ||
}; | ||
var Db = function (options) { | ||
@@ -38,8 +48,3 @@ this.database = options.database || options.id || 'keydb'; | ||
var sql = 'create table if not exists ' + self.database + '.' + name + ' ('; | ||
columns.forEach(function (column, i) { | ||
if (i > 0) { | ||
sql += ', '; | ||
} | ||
sql += ' ' + column; | ||
}); | ||
sql += columns.join(', '); | ||
sql += ')'; | ||
@@ -67,59 +72,2 @@ return q.when(self.query(sql)); | ||
var valueToRow = function (command) { | ||
var row = { | ||
media_value: null, | ||
media_type: null | ||
}; | ||
if (command.isBinary) { | ||
row.value_type = 'bin'; | ||
row.media_type = command.mediaType; | ||
// TODO: need to parse base64, blah, blah | ||
} else if (command.mediaType) { | ||
row.value_type = 'txt'; | ||
row.media_type = command.mediaType; | ||
row.media_value = command.value; | ||
} else if (typeof command.value === 'string') { | ||
row.value_type = 'str'; | ||
row.media_value = command.value; | ||
} else if (typeof command.value === 'number') { | ||
row.value_type = 'num'; | ||
row.media_value = JSON.stringify(command.value); | ||
} else if (typeof command.value === 'boolean') { | ||
row.value_type = 'bool'; | ||
row.media_value = JSON.stringify(command.value); | ||
} else if (typeof command.value === 'undefined' || command.value === 'null') { | ||
row.value_type = 'null'; | ||
} else { | ||
row.value_type = 'json'; | ||
row.media_value = JSON.stringify(command.value); | ||
} | ||
return row; | ||
}; | ||
var rowToObject = function (row) { | ||
var obj = {value: null}; | ||
if (row.value_type === 'bin') { | ||
obj.isBinary = true; | ||
obj.mediaType = row.media_type; | ||
// TODO: convert to base64, blah, blah | ||
} else if (row.value_type === 'txt') { | ||
obj.mediaType = row.media_type; | ||
obj.value = row.media_value.toString(); | ||
} else if (row.value_type === 'str') { | ||
obj.value = row.media_value.toString(); | ||
} else if (row.value_type === 'num') { | ||
obj.value = JSON.parse(row.media_value.toString()); | ||
} else if (row.value_type === 'bool') { | ||
obj.value = JSON.parse(row.media_value.toString()); | ||
} else if (row.value_type === 'null') { | ||
obj.value = null; | ||
} else if (row.value_type === 'json') { | ||
// cross fingers | ||
obj.value = JSON.parse(row.media_value.toString()); | ||
} else { | ||
obj.value = null; | ||
} | ||
return obj; | ||
}; | ||
var createMySqlSource = function (next, options) { | ||
@@ -130,69 +78,112 @@ options = options || {}; | ||
var init = function () { | ||
var modifiedTimeColumn = 'modified_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'; | ||
return db.createDatabase() | ||
var initDatabase = function () { | ||
return db.createDatabase(); | ||
}; | ||
var readyDatabase = utils.createReady(initDatabase); | ||
var makeInitTableFn = function (tableName) { | ||
return function () { | ||
var defs = []; | ||
var table = options.tables[tableName]; | ||
if (!table) { | ||
throw new error.TableSchemaNotFound({table: tableName}); | ||
} | ||
var props = table.properties; | ||
Object.keys(props).forEach(function (key) { | ||
var prop = props[key]; | ||
var def = key + ' ' + dbTypes[prop.type](prop); | ||
if (table.required && Array.isArray(table.required) && table.required.indexOf(key) >= 0) { | ||
def += ' not null'; | ||
} | ||
defs.push(def); | ||
}); | ||
if (!table.primaryKey) { | ||
throw new error.PrimaryKeyRequired({table: tableName}); | ||
} | ||
defs.push('primary key (' + table.primaryKey + ')'); | ||
return db.createTable(tableName, defs); | ||
}; | ||
}; | ||
var readyTable = {}; | ||
var ready = function (msg) { | ||
return readyDatabase() | ||
.then(function () { | ||
return db.createTable('item', [ | ||
'item_key varchar(500) collate latin1_bin', | ||
'tag_key varchar(500) collate latin1_bin', | ||
'value_type varchar(8)', | ||
'media_value longblob', | ||
'media_type varchar(250)', | ||
'version varchar(50) not null', | ||
'source_key varchar(500) collate latin1_bin', | ||
'primary key (item_key)', | ||
'modified_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', | ||
'index(source_key)' | ||
]); | ||
}) | ||
.then(function (result) { | ||
if (result.warningCount === 1) { | ||
// table was already created | ||
return db.ensureColumn('item', modifiedTimeColumn); | ||
if (!msg.table) { | ||
return; | ||
} | ||
if (!readyTable[msg.table]) { | ||
readyTable[msg.table] = utils.createReady(makeInitTableFn(msg.table)); | ||
} | ||
return readyTable[msg.table](); | ||
}); | ||
}; | ||
// make a function that will return a promise that resolves when initialized | ||
var ready = utils.createReady(init); | ||
var reset = function () { | ||
readyDatabase = utils.createReady(initDatabase); | ||
readyTable = {}; | ||
}; | ||
var msgKeys = function (msg, attrs) { | ||
try { | ||
var primaryKeys = options.tables[msg.table].primaryKey; | ||
if (typeof primaryKeys === 'string') { | ||
primaryKeys = [primaryKeys]; | ||
} | ||
primaryKeys.forEach(function (key) { | ||
if (!attrs[key]) { | ||
throw new error.PrimaryKeyRequiredForOp({op: msg.op}); | ||
} | ||
}); | ||
return primaryKeys; | ||
} catch (e) { | ||
throw new error.PrimaryKeyRequiredForOp({op: msg.op}); | ||
} | ||
}; | ||
var ops = { | ||
get: function (msg) { | ||
// if (msg.keys) { | ||
// return ops.getKeys(msg); | ||
// } | ||
query: function (msg) { | ||
var sql = [ | ||
'select * from ' + db.tableId('item'), | ||
'select', | ||
msg.attributes ? msg.attributes.join(',') : '*', | ||
'from ' + db.tableId(msg.table), | ||
'where', | ||
'item_key = ' + mysql.escape(msg.key) | ||
Object.keys(msg.filters).map(function (key) { | ||
return key + ' = ' + mysql.escape(msg.filters[key]); | ||
}) | ||
].join('\n'); | ||
return db.query(sql) | ||
.then(function (result) { | ||
if (result.length < 1) { | ||
throw new error.NotFound({key: msg.key}); | ||
} | ||
var reply = rowToObject(result[0]); | ||
reply.key = msg.key; | ||
return reply; | ||
.then(function (results) { | ||
// Need to remove non-column properties. | ||
var items = []; | ||
return { | ||
items: results.map(function (result) { | ||
var obj = {}; | ||
Object.keys(result).forEach(function (key) { | ||
obj[key] = result[key]; | ||
}); | ||
return obj; | ||
}) | ||
}; | ||
}); | ||
}, | ||
// getKeys: function (msg) { | ||
// var prefix = msg.key || ''; | ||
// var keys = msg.keys.map(function (key) { | ||
// return prefix + key; | ||
// }); | ||
// }, | ||
create: function (msg) { | ||
var valueFields = valueToRow(msg); | ||
var sql = 'insert into ' + db.tableId('item'); | ||
sql += ' (item_key, tag_key, value_type, media_value, media_type, version) select '; | ||
sql += ' ' + mysql.escape(msg.key); | ||
sql += ', ' + mysql.escape(msg.tag || null); | ||
sql += ', ' + mysql.escape(valueFields.value_type); | ||
sql += ', ' + mysql.escape(valueFields.media_value); | ||
sql += ', ' + mysql.escape(valueFields.media_type); | ||
sql += ', ' + mysql.escape(msg.version); | ||
sql += ' from dual where not exists ( select * from ' + db.tableId('item'); | ||
sql += ' where item_key = ' + mysql.escape(msg.key); | ||
sql += ')'; | ||
var sql = [ | ||
'insert into ' + db.tableId(msg.table), | ||
'(' + Object.keys(msg.attributes).join(',') + ')', | ||
'select', | ||
Object.keys(msg.attributes).map(function (key) { | ||
return mysql.escape(msg.attributes[key]); | ||
}).join(','), | ||
'from dual where not exists', | ||
'(', | ||
'select * from ' + db.tableId(msg.table), | ||
'where ', | ||
msgKeys(msg, msg.attributes).map(function (key) { | ||
return key + ' = ' + mysql.escape(msg.attributes[key]); | ||
}).join(','), | ||
')' | ||
].join('\n'); | ||
return db.query(sql) | ||
@@ -208,14 +199,13 @@ .then(function (result) { | ||
update: function (msg) { | ||
var valueFields = valueToRow(msg); | ||
var sql = 'update ' + db.tableId('item') + ' '; | ||
sql += 'set item_key = ' + mysql.escape(msg.key) + ' '; | ||
sql += ', tag_key = ' + mysql.escape(msg.tag || null) + ' '; | ||
sql += ', value_type = ' + mysql.escape(valueFields.value_type) + ' '; | ||
sql += ', media_value = ' + mysql.escape(valueFields.media_value) + ' '; | ||
sql += ', media_type = ' + mysql.escape(msg.mediaType) + ' '; | ||
sql += ', version = ' + mysql.escape(msg.version) + ' '; | ||
sql += 'where item_key = ' + mysql.escape(msg.key) + ' '; | ||
if (msg.ifVersion) { | ||
sql += 'and version = ' + mysql.escape(msg.ifVersion) + ' '; | ||
} | ||
var sql = [ | ||
'update ' + db.tableId(msg.table), | ||
'set ', | ||
Object.keys(msg.attributes).map(function (key) { | ||
return key + ' = ' + mysql.escape(msg.attributes[key]); | ||
}).join(','), | ||
'where', | ||
msgKeys(msg, msg.filters).map(function (key) { | ||
return key + ' = ' + mysql.escape(msg.filters[key]); | ||
}).join(',') | ||
].join('\n'); | ||
return db.query(sql) | ||
@@ -232,5 +222,7 @@ .then(function (result) { | ||
var sql = [ | ||
'delete from ' + db.tableId('item'), | ||
'delete from ' + db.tableId(msg.table), | ||
'where', | ||
'item_key = ' + mysql.escape(msg.key) | ||
msgKeys(msg, msg.filters).map(function (key) { | ||
return key + ' = ' + mysql.escape(msg.filters[key]); | ||
}).join(',') | ||
].join('\n'); | ||
@@ -245,2 +237,9 @@ return db.query(sql) | ||
}); | ||
}, | ||
'delete-database': function (msg) { | ||
return db.query('drop database ' + mysql.escapeId(options.database)) | ||
.then(function () { | ||
console.log("DELETED ALL DATA FOREVER FROM: " + options.database); | ||
reset(); | ||
}); | ||
} | ||
@@ -250,3 +249,3 @@ }; | ||
var source = function (msg) { | ||
return ready() | ||
return ready(msg) | ||
.then(function () { | ||
@@ -253,0 +252,0 @@ return ops[msg.op](msg); |
var utils = require('../utils'); | ||
var error = require('../error'); | ||
@@ -24,2 +25,5 @@ var createStack = function (next) { | ||
stack.driver = function (driver) { | ||
if (typeof driver !== 'function') { | ||
throw new error.InvalidDriver({}); | ||
} | ||
var args = utils.slice(arguments, 1); | ||
@@ -26,0 +30,0 @@ drivers.push(function (next) { |
@@ -7,3 +7,3 @@ var _ = require('underscore'); | ||
var source = function (msg) { | ||
if (msg.op === 'set') { | ||
if (msg.op === 'set' || msg.op === 'upsert') { | ||
var createMsg = _.extend({}, msg); | ||
@@ -10,0 +10,0 @@ var updateMsg = _.extend({}, msg); |
var errman = require('errman')(); | ||
errman.registerType('InvalidDriver', { | ||
status: 500, | ||
code: 'invalid_driver', | ||
message: 'An invalid driver was added to a stack.' | ||
}); | ||
errman.registerType('TableSchemaNotFound', { | ||
status: 500, | ||
code: 'table_schema_not_found', | ||
message: 'No table schema defined for table: {{table}}' | ||
}); | ||
errman.registerType('PrimaryKeyRequired', { | ||
status: 500, | ||
code: 'primary_key_required', | ||
message: 'No primary key defined for table: {{table}}' | ||
}); | ||
errman.registerType('PrimaryKeyRequiredForOp', { | ||
status: 500, | ||
code: 'primary_key_required_for_op', | ||
message: 'No primary key defined for operation: {{op}}' | ||
}); | ||
errman.registerType('InvalidKey', { | ||
@@ -4,0 +28,0 @@ status: 400, |
@@ -6,5 +6,31 @@ var q = require('q'); | ||
var keydb = function (source) { | ||
return createStackSource(source); | ||
if (typeof source === 'string') { | ||
var stack = createStackSource(); | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
stack.driver.apply(stack, [keydb.drivers[source]].concat(args)); | ||
if (keydb.sugars[source]) { | ||
stack = keydb.sugars[source](stack); | ||
} | ||
return stack; | ||
} else { | ||
return createStackSource(source); | ||
} | ||
}; | ||
var camelize = function (id) { | ||
if (id.indexOf('-') >= 0) { | ||
var parts = id.split('-'); | ||
if (parts.length > 1) { | ||
return parts[0] + parts.slice(1).map(function (part) { | ||
if (part.length > 0) { | ||
return part[0].toUpperCase() + part.slice(1); | ||
} else { | ||
return part; | ||
} | ||
}).join(''); | ||
} | ||
} | ||
return id; | ||
}; | ||
keydb.drivers = {}; | ||
@@ -14,4 +40,12 @@ | ||
keydb.drivers[id] = driver; | ||
keydb.drivers[camelize(id)] = driver; | ||
}; | ||
keydb.sugars = {}; | ||
keydb.sugar = function (id, wrap) { | ||
keydb.sugars[id] = wrap; | ||
keydb.sugars[camelize(id)] = wrap; | ||
}; | ||
keydb.error = require('./error'); | ||
@@ -18,0 +52,0 @@ |
{ | ||
"name": "keydb", | ||
"version": "0.0.2", | ||
"version": "0.0.4", | ||
"description": "Key/value data/query API to use on the server and in the browser.", | ||
@@ -5,0 +5,0 @@ "main": "index-server.js", |
@@ -6,3 +6,3 @@ keydb | ||
Key/value data/query API to use on the server or in the browser. | ||
Build data APIs using the middleware concept. | ||
@@ -42,12 +42,69 @@ ## Installation | ||
db.driver(keydb.drivers.upsert); | ||
db.driver(keydb.drivers.version); | ||
db.driver(keydb.drivers.mysql); | ||
db.driver(keydb.drivers.mysql, { | ||
database: 'test', | ||
tables: { | ||
user: { | ||
properties: { | ||
user_id: { | ||
type: 'string', | ||
maxLength: 100 | ||
}, | ||
first_name: { | ||
type: 'string', | ||
maxLength: 100 | ||
}, | ||
last_name: { | ||
type: 'string', | ||
maxLength: 100 | ||
} | ||
}, | ||
primaryKey: 'user_id' | ||
} | ||
} | ||
}); | ||
db({op: 'set', key: 'users/joe', value: {fn: 'Joe'}}) | ||
db({ | ||
op: 'upsert', | ||
attributes: { | ||
user_id: 'joe', first_name: 'Joe', last_name: 'Foo' | ||
}, | ||
filters: {user_id: 'joe'} | ||
}) | ||
.then(function () { | ||
return db({op: 'get', key: 'users/joe'}); | ||
return db({op: 'query', filters: {user_id: 'joe'}}); | ||
}) | ||
.then(function (msg) { | ||
console.log(msg); | ||
console.log(msg.items); | ||
}) | ||
``` | ||
In the above example, an upsert driver is stacked on top of a mysql driver so | ||
that upsert semantics can be added to mysql without the underlying driver | ||
actually supporting upsert. | ||
Some drivers are preconfigured stacks, and these can be easily created by their | ||
IDs. They may also add sugar methods. | ||
```js | ||
var keydb = require('keydb'); | ||
var db = keydb('kv-mysql'); | ||
db.set('users/joe', {firstName: 'Joe'}) | ||
.then(function () { | ||
return db.get('users/joe'); | ||
}) | ||
``` | ||
The above is the same as: | ||
```js | ||
var keydb = require('keydb'); | ||
var db = keydb('kv-mysql'); | ||
db({op: 'set', key: 'users/joe', value: {firstName: 'Joe'}}) | ||
.then(function () { | ||
return db({op: 'get', key: 'users/joe'}); | ||
}) | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
42115
30
1273
108
0