simple-dynamo
Version:
Abstraction of Amazons Dynamo DB Service. Usage of AWS DynamoDB incredible simple.
855 lines (808 loc) • 28.2 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var Attributes, DynamoTable, ERRORMAPPING, EventEmitter, _, attributesHelper, utils, uuid,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty,
slice = [].slice;
uuid = require('node-uuid');
_ = require("underscore");
Attributes = require("./attributes");
attributesHelper = Attributes.helper;
utils = require("./utils");
EventEmitter = require("events").EventEmitter;
module.exports = DynamoTable = (function(superClass) {
extend(DynamoTable, superClass);
function DynamoTable(table, options1) {
this.options = options1;
this.scan = bind(this.scan, this);
this._getThroughput = bind(this._getThroughput, this);
this._getShema = bind(this._getShema, this);
this._generate = bind(this._generate, this);
this._checkSetOptions = bind(this._checkSetOptions, this);
this._convertValue = bind(this._convertValue, this);
this._defaultRangeKey = bind(this._defaultRangeKey, this);
this._defaultHashKey = bind(this._defaultHashKey, this);
this._createRangeKey = bind(this._createRangeKey, this);
this._createHashKey = bind(this._createHashKey, this);
this._createId = bind(this._createId, this);
this._validateHash = bind(this._validateHash, this);
this._deFixHash = bind(this._deFixHash, this);
this._fixHash = bind(this._fixHash, this);
this._dynamoItem2JSONSingle = bind(this._dynamoItem2JSONSingle, this);
this._dynamoItem2JSON = bind(this._dynamoItem2JSON, this);
this._del = bind(this._del, this);
this._create = bind(this._create, this);
this._update = bind(this._update, this);
this._mget = bind(this._mget, this);
this._get = bind(this._get, this);
this._isExistend = bind(this._isExistend, this);
this._getOptions = bind(this._getOptions, this);
this._fixCombinedHash = bind(this._fixCombinedHash, this);
this._error = bind(this._error, this);
this.destroy = bind(this.destroy, this);
this.find = bind(this.find, this);
this.del = bind(this.del, this);
this.set = bind(this.set, this);
this.mget = bind(this.mget, this);
this.get = bind(this.get, this);
this.meta = bind(this.meta, this);
this.generate = bind(this.generate, this);
this.init = bind(this.init, this);
this.mng = this.options.manager;
this.defaults = this.options.defaults;
this.external = this.options.external;
this.__defineGetter__("name", (function(_this) {
return function() {
return _this._model_settings.name;
};
})(this));
this.__defineGetter__("tableName", (function(_this) {
return function() {
return _this._model_settings.combineTableTo || _this._model_settings.name || null;
};
})(this));
this.__defineGetter__("isCombinedTable", (function(_this) {
return function() {
return _this._model_settings.combineTableTo != null;
};
})(this));
this.__defineGetter__("combinedHashDelimiter", (function(_this) {
return function() {
return "";
};
})(this));
this.__defineGetter__("existend", (function(_this) {
return function() {
return _this.external != null;
};
})(this));
this.__defineGetter__("hasRange", (function(_this) {
return function() {
var ref, ref1;
if ((ref = _this._model_settings) != null ? (ref1 = ref.rangeKey) != null ? ref1.length : void 0 : void 0) {
return true;
} else {
return false;
}
};
})(this));
this.__defineGetter__("hashKey", (function(_this) {
return function() {
var ref;
return ((ref = _this._model_settings) != null ? ref.hashKey : void 0) || null;
};
})(this));
this.__defineGetter__("hashKeyType", (function(_this) {
return function() {
var ref;
if (_this.isCombinedTable) {
return "S";
} else {
return ((ref = _this._model_settings) != null ? ref.hashKeyType : void 0) || "S";
}
};
})(this));
this.__defineGetter__("rangeKey", (function(_this) {
return function() {
var ref;
return ((ref = _this._model_settings) != null ? ref.rangeKey : void 0) || null;
};
})(this));
this.__defineGetter__("rangeKeyType", (function(_this) {
return function() {
var ref;
if (_this.hasRange) {
return ((ref = _this._model_settings) != null ? ref.rangeKeyType : void 0) || "N";
} else {
return null;
}
};
})(this));
this.__defineGetter__("overwriteExistingHash", (function(_this) {
return function() {
var ref;
if (((ref = _this._model_settings) != null ? ref.overwriteExistingHash : void 0) != null) {
return _this._model_settings.overwriteExistingHash;
} else if (_this.defaults.overwriteExistingHash != null) {
return _this.defaults.overwriteExistingHash;
} else {
return false;
}
};
})(this));
this.init(table);
return;
}
DynamoTable.prototype.init = function(table) {
this._model_settings = table;
this._attrs = new Attributes(table.attributes, this);
if (this.isCombinedTable) {
this._regexRemCT = new RegExp("^" + this.name, "i");
}
};
DynamoTable.prototype.generate = function(cb) {
var err;
err = {};
if (this.external == null) {
this._generate(cb);
} else {
this.emit("create-status", "already-active");
cb(null, false);
}
};
DynamoTable.prototype.meta = function(cb) {
if (this._meta != null) {
cb(null, this._meta);
} else if (this._isExistend(cb)) {
this.external.fetch((function(_this) {
return function(err, _meta) {
if (err) {
_this._error(cb, err);
} else {
_this._meta = _meta;
cb(null, _meta);
}
};
})(this));
}
};
DynamoTable.prototype.get = function() {
var _id, args, cb, i, options, query;
args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++];
if (this._isExistend(cb)) {
options = null;
switch (args.length) {
case 1:
_id = args[0];
break;
case 2:
_id = args[0], options = args[1];
}
options = this._getOptions(options);
query = this._deFixHash(_id, cb);
if (query instanceof Error) {
this._error(cb, query);
return;
}
this._get(query, options, (function(_this) {
return function(err, _item) {
var _obj;
if (err) {
_this._error(cb, err);
} else {
if (_item) {
_obj = _this._dynamoItem2JSON(_item, false);
_this.emit("get", _obj);
cb(null, _obj);
} else {
_this.emit("get-empty");
cb(null, null);
}
}
};
})(this));
}
};
DynamoTable.prototype.mget = function() {
var _id, _ids, args, cb, i, j, len, mQuery, options, query;
args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++];
if (this._isExistend(cb)) {
options = null;
switch (args.length) {
case 1:
_ids = args[0];
break;
case 2:
_ids = args[0], options = args[1];
}
options = this._getOptions(options);
mQuery = [];
for (j = 0, len = _ids.length; j < len; j++) {
_id = _ids[j];
query = this._deFixHash(_id, cb);
if (query instanceof Error) {
this._error(cb, query);
return;
} else {
mQuery.push(query);
}
}
this._mget(mQuery, options, (function(_this) {
return function(err, _item) {
var _obj;
if (err) {
_this._error(cb, err);
} else {
if (_item.length) {
_obj = _this._dynamoItem2JSON(_item, false);
_this.emit("mget", _obj);
cb(null, _obj);
} else {
_this.emit("mget-empty");
cb(null, []);
}
}
};
})(this));
}
};
DynamoTable.prototype.set = function() {
var _create, _id, args, attributes, cb, i, options;
args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++];
if (this._isExistend(cb)) {
options = null;
switch (args.length) {
case 1:
_create = true;
_id = null;
attributes = args[0];
break;
case 2:
if (_.isString(args[0]) || _.isNumber(args[0]) || _.isArray(args[0])) {
_create = false;
_id = args[0], attributes = args[1];
} else {
_create = true;
_id = null;
attributes = args[0], options = args[1];
}
break;
case 3:
_create = false;
_id = args[0], attributes = args[1], options = args[2];
}
options = this._getOptions(options);
this._attrs.validateAttributes(_create, attributes, (function(_this) {
return function(err, attributes) {
if (err) {
return _this._error(cb, err);
} else {
if (_create) {
return _this._create(attributes, options, function(err, _item) {
var _obj, ref;
if (err) {
_this._error(cb, err);
} else {
_obj = _this._dynamoItem2JSON(_item, true);
_this.emit("create", _obj);
if (options != null ? (ref = options.fields) != null ? ref.length : void 0 : void 0) {
_obj = utils.reduceObj(_obj, options != null ? options.fields : void 0);
}
cb(null, _obj);
}
});
} else {
return _this._update(_id, attributes, options, function(err, item) {
var _obj, _reducedItem, ref;
if (err) {
_this._error(cb, err);
} else {
_obj = _this._dynamoItem2JSON(item, true);
_this.emit("update", _obj);
if (options != null ? (ref = options.fields) != null ? ref.length : void 0 : void 0) {
_reducedItem = utils.reduceObj(_obj, options.fields);
}
cb(null, _reducedItem || _obj);
}
});
}
}
};
})(this));
}
};
DynamoTable.prototype.del = function(_id, cb) {
var args, i, options, query;
args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++];
_id = args[0], options = args[1];
options || (options = {});
if (this._isExistend(cb)) {
query = this._deFixHash(_id);
if (query instanceof Error) {
this._error(cb, query);
} else {
this._del(query, options, (function(_this) {
return function(err, success) {
if (err) {
_this._error(cb, err);
} else {
_this.emit("delete", success);
cb(null, success);
}
};
})(this));
}
}
};
DynamoTable.prototype.find = function() {
var _fetchOpts, _fnHandle, _op, _query, _val, _x, args, cb, i, isScan, options, query, ref, ref1, startAt;
args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), cb = arguments[i++];
if (this._isExistend(cb)) {
options = null;
startAt = null;
query = {};
switch (args.length) {
case 1:
query = args[0];
break;
case 2:
query = args[0], _x = args[1];
if (_.isString(_x) || _.isNumber(_x)) {
startAt = _x;
} else {
options = _x;
}
break;
case 3:
query = args[0], startAt = args[1], options = args[2];
}
options = this._getOptions(options);
if (startAt != null) {
startAt = this._deFixHash(startAt);
if (startAt instanceof Error) {
this._error(cb, startAt);
return;
}
}
if (this.isCombinedTable) {
if (query != null ? query[this.hashKey] : void 0) {
_op = _.first(Object.keys(query[this.hashKey]));
_val = query[this.hashKey][_op];
_val = ((ref = this._deFixHash(_val)) != null ? ref[this.hashKey] : void 0) || _val;
if (_val instanceof Error) {
this._error(cb, _val);
return;
}
switch (_op) {
case "==":
_val = _val;
}
query[this.hashKey][_op] = _val;
} else {
query[this.hashKey] = {
"startsWith": this.name
};
}
}
if (this._isExistend(cb)) {
ref1 = this._attrs.getQuery(this.external, query, startAt, options), _query = ref1[0], isScan = ref1[1];
_fnHandle = (function(_this) {
return function(err, _items) {
if (err) {
_this._error(cb, err);
} else {
cb(null, _this._dynamoItem2JSON(_items, false));
}
};
})(this);
if (isScan) {
_query.fetch(_fnHandle);
} else {
_fetchOpts = {
consistent: options.consistent
};
_query.fetch(_fetchOpts, _fnHandle);
}
}
}
};
DynamoTable.prototype.destroy = function(cb) {
if (this._isExistend(cb)) {
return this.external.destroy(cb);
}
};
DynamoTable.prototype._error = function(cb, err) {
var _err, error;
if (ERRORMAPPING[err.name] != null) {
_err = ERRORMAPPING[err.name];
error = new Error;
error.name = _err.name;
error.message = _err.message;
cb(error);
} else {
cb(err);
}
};
DynamoTable.prototype._fixCombinedHash = function(hash) {
var _i;
if (this.isCombinedTable) {
_i = this.name.length;
if (hash.slice(0, +(_i - 1) + 1 || 9e9) === this.name) {
return hash.slice(_i);
} else {
return hash;
}
} else {
return hash;
}
};
DynamoTable.prototype._getOptions = function(options) {
var _defOpt;
if (options == null) {
options = {};
}
_defOpt = {
fields: this._model_settings.defaultfields != null ? this._model_settings.defaultfields : void 0,
overwriteExistingHash: this.overwriteExistingHash,
consistent: this._model_settings.consistent != null ? this._model_settings.consistent : false,
forward: this._model_settings.forward != null ? this._model_settings.forward : true,
conditionals: null
};
return _.extend(_defOpt, options || {});
};
DynamoTable.prototype._isExistend = function(cb) {
var error;
if (this.existend) {
return true;
} else {
if (_.isFunction(cb)) {
error = new Error;
error.name = "table-not-created";
error.message = "Table '" + this.tableName + "' not existend at AWS. please run `Table.generate()` or `Manager.generateAll()` first.";
this._error(cb, error);
}
return false;
}
};
DynamoTable.prototype._get = function(query, options, cb) {
var _fetchOpts, _item, ref;
_item = this.external.get(query);
if (options != null ? (ref = options.fields) != null ? ref.length : void 0 : void 0) {
_item.get(options.fields);
}
_fetchOpts = {
consistent: options.consistent
};
_item.fetch(_fetchOpts, (function(_this) {
return function(err, item) {
if (err) {
cb(err);
} else {
cb(null, item);
}
};
})(this));
};
DynamoTable.prototype._mget = function(mquery, options, cb) {
var _batch, _self;
_self = this;
_batch = this.mng.client.get(function() {
var ref;
if ((options != null ? (ref = options.fields) != null ? ref.length : void 0 : void 0) && _.isArray(options.fields)) {
return this.get(_self.tableName, mquery, options.fields);
} else {
return this.get(_self.tableName, mquery);
}
});
_batch.fetch((function(_this) {
return function(err, items) {
if (err) {
cb(err);
} else {
cb(null, items[_this.tableName] || []);
}
};
})(this));
};
DynamoTable.prototype._update = function(id, attributes, options, cb) {
var _id, _upd, item;
if (options == null) {
options = {};
}
_id = this._deFixHash(id);
if (_id instanceof Error) {
this._error(cb, _id);
return;
}
item = this.external.get(_id);
_upd = item.update(this._attrs.updateAttrsFn(attributes, options));
_upd.returning("ALL_NEW");
_upd = this._checkSetOptions("update", _upd, attributes, options);
if (_upd.AttributeUpdates != null) {
_upd.save((function(_this) {
return function(err, _saved) {
if (err) {
cb(err);
} else {
cb(null, _saved.Attributes || {});
}
};
})(this));
} else {
cb(null, null);
}
};
DynamoTable.prototype._create = function(attributes, options, cb) {
if (attributes == null) {
attributes = {};
}
this._createId(attributes, (function(_this) {
return function(err, attributes) {
var _upd;
if (err) {
cb(err);
} else {
_upd = _this.external.put(attributes);
_upd = _this._checkSetOptions("create", _upd, attributes, options);
_upd.save(function(err) {
if (err) {
cb(err);
} else {
cb(null, _upd);
}
});
}
};
})(this));
};
DynamoTable.prototype._del = function(query, options, cb) {
var _del;
_del = this.external.get(query);
_del.returning("ALL_OLD");
_del = this._checkSetOptions("update", _del, {}, options);
_del.destroy((function(_this) {
return function(err, item) {
if (err) {
cb(err);
} else {
cb(null, item || null);
}
};
})(this));
};
DynamoTable.prototype._dynamoItem2JSON = function(items, convertAttrs) {
var i, idx, item, len;
if (convertAttrs == null) {
convertAttrs = false;
}
if (_.isArray(items)) {
for (idx = i = 0, len = items.length; i < len; idx = ++i) {
item = items[idx];
items[idx] = this._dynamoItem2JSONSingle(item, convertAttrs);
}
return items;
} else {
return this._dynamoItem2JSONSingle(items, convertAttrs);
}
};
DynamoTable.prototype._dynamoItem2JSONSingle = function(item, convertAttrs) {
var _obj;
if (convertAttrs == null) {
convertAttrs = false;
}
if (convertAttrs) {
_obj = attributesHelper.dyn2obj((item != null ? item.Item : void 0) || item);
} else {
_obj = item;
}
return this._fixHash(_obj);
};
DynamoTable.prototype._fixHash = function(attrs) {
return attrs;
};
DynamoTable.prototype._deFixHash = function(attrs) {
var _attrs, _h, _hName, _hType, _r, _rName, _rType, error, ref;
if (_.isString(attrs) || _.isNumber(attrs) || _.isArray(attrs)) {
_hName = this.hashKey;
_attrs = {};
_attrs[_hName] = _.clone(attrs);
} else {
_attrs = _.clone(attrs);
}
if (this.hasRange) {
_hType = this.hashKeyType;
_rName = this.rangeKey;
_rType = this.rangeKeyType;
if (!_.isArray(_attrs[_hName])) {
error = new Error;
error.name = "invalid-range-call";
error.message = "If you try to access a hash/range item you have to pass a Array of `[hash,range]` as id.";
return error;
}
ref = _attrs[_hName], _h = ref[0], _r = ref[1];
_attrs[_hName] = this._convertValue(_h, _hType);
_attrs[_rName] = this._convertValue(_r, _rType);
}
return _attrs;
};
DynamoTable.prototype._validateHash = function(hash) {
var _l, _pre;
_pre = this.name + this.combinedHashDelimiter;
_l = _pre.length;
return hash.slice(0, _l) === _pre;
};
DynamoTable.prototype._createId = function(attributes, cb) {
this._createHashKey(attributes, (function(_this) {
return function(attributes) {
var error;
if (_this.isCombinedTable) {
if (!_this._validateHash(attributes[_this.hashKey])) {
error = new Error;
error.name = "combined-hash-invalid";
error.message = "The hash of a combined-table has to start with the `name` of this table defined in the configuartion. Please try `" + (_this.name + _this.combinedHashDelimiter + attributes[_this.hashKey]) + "`";
_this._error(cb, error);
return;
}
}
if (_this.hasRange) {
_this._createRangeKey(attributes, function(attributes) {
cb(null, attributes);
});
} else {
cb(null, attributes);
}
};
})(this));
};
DynamoTable.prototype._createHashKey = function(attributes, cbH) {
var _hName, _hType;
_hName = this.hashKey;
_hType = this.hashKeyType;
if (this._model_settings.fnCreateHash && _.isFunction(this._model_settings.fnCreateHash)) {
this._model_settings.fnCreateHash(attributes, (function(_this) {
return function(_hash) {
attributes[_hName] = _this._convertValue(_hash, _hType);
cbH(attributes);
};
})(this));
} else if (attributes[_hName] != null) {
attributes[_hName] = this._convertValue(attributes[_hName], _hType);
cbH(attributes);
} else {
attributes[_hName] = this._convertValue(this._defaultHashKey(), _hType);
cbH(attributes);
}
};
DynamoTable.prototype._createRangeKey = function(attributes, cbR) {
var _rName, _rType;
_rName = this.rangeKey;
_rType = this.rangeKeyType;
if (this._model_settings.fnCreateRange && _.isFunction(this._model_settings.fnCreateRange)) {
this._model_settings.fnCreateRange(attributes, (function(_this) {
return function(__range) {
attributes[_rName] = _this._convertValue(__range, _rType);
cbR(attributes);
};
})(this));
} else if (attributes[_rName] != null) {
attributes[_rName] = this._convertValue(attributes[_rName], _rType);
cbR(attributes);
} else {
attributes[_rName] = this._convertValue(this._defaultRangeKey(), _rType);
cbR(attributes);
}
};
DynamoTable.prototype._defaultHashKey = function() {
if (this.isCombinedTable) {
return this.name + this.combinedHashDelimiter + uuid.v1();
} else {
return uuid.v1();
}
};
DynamoTable.prototype._defaultRangeKey = function() {
return Date.now();
};
DynamoTable.prototype._convertValue = function(val, type) {
switch (type.toUpperCase()) {
case "N":
return parseFloat(val, 10);
case "S":
if (val) {
return val.toString(10);
}
break;
default:
return val;
}
};
DynamoTable.prototype._checkSetOptions = function(type, _upd, attributes, options) {
var _pred;
if (type === "create" && (!(options != null ? options.overwriteExistingHash : void 0) || !this.overwriteExistingHash)) {
_pred = {};
_pred[this.hashKey] = {
"==": null
};
_upd.when(_pred);
}
if (type === "update") {
if (!_.isEmpty(options.conditionals)) {
_upd.when(options.conditionals);
}
}
return _upd;
};
DynamoTable.prototype._generate = function(cb) {
var _cr;
_cr = this.mng.client.add({
name: this.tableName,
throughput: this._getThroughput(),
schema: this._getShema()
});
_cr.save((function(_this) {
return function(err, _table) {
if (err) {
cb(err);
} else {
_this.emit("create-status", "waiting");
_table.watch(function(err, _table) {
if (err) {
cb(err);
} else {
_this.emit("create-status", "active");
_this.external = _table;
cb(null, _table);
}
});
}
};
})(this));
};
DynamoTable.prototype._getShema = function() {
var _hName, _hType, _rName, _rType, oShema;
oShema = {};
_hName = this.hashKey;
_hType = this.hashKeyType;
oShema[_hName] = _hType === "S" ? String : Number;
if (this.hasRange) {
_rName = this.rangeKey;
_rType = this.rangeKeyType;
oShema[_rName] = _rType === "S" ? String : Number;
}
return oShema;
};
DynamoTable.prototype._getThroughput = function() {
var oRet, ref, ref1, ref2, ref3;
oRet = this.defaults.throughput;
if (((ref = this.options) != null ? (ref1 = ref.throughput) != null ? ref1.read : void 0 : void 0) != null) {
oRet.read = this.options.throughput.read;
}
if (((ref2 = this.options) != null ? (ref3 = ref2.throughput) != null ? ref3.write : void 0 : void 0) != null) {
oRet.write = this.options.throughput.write;
}
return oRet;
};
DynamoTable.prototype.scan = function(_table, query, cb) {
this.fetchTable(_table, (function(_this) {
return function(err, table) {
var scan;
scan = table.scan(query);
scan.fetch(function(err, data) {
if (err) {
cb(err);
} else {
cb(null, data);
}
});
};
})(this));
};
return DynamoTable;
})(EventEmitter);
ERRORMAPPING = {
"com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException": {
name: "conditional-check-failed",
message: "This is not a valid request. It doesnt match the conditions or you tried to insert a existing hash."
}
};
}).call(this);