UNPKG

dynamoose

Version:

Dynamoose is a modeling tool for Amazon's DynamoDB (inspired by Mongoose)

324 lines (272 loc) 8.95 kB
'use strict'; const Attribute = require('./Attribute'); const errors = require('./errors'); const VirtualType = require('./VirtualType'); //const util = require('util'); const debug = require('debug')('dynamoose:schema'); function Schema(obj, options) { debug('Creating Schema', obj); this.tree = {}; this.methods = {}; this.statics = {}; this.virtuals = {}; this.options = options || {}; if(this.options.throughput) { let throughput = this.options.throughput; if(typeof throughput === 'number') { throughput = {read: throughput, write: throughput}; } this.throughput = throughput; } else { this.throughput = {read: 1, write: 1}; } if((!this.throughput.read || !this.throughput.write) && this.throughput.read >= 1 && this.throughput.write >= 1) { throw new errors.SchemaError('Invalid throughput: '+ this.throughput); } /* * Added support for timestamps attribute */ if (this.options.timestamps) { let createdAt = null; let updatedAt = null; if (this.options.timestamps === true) { createdAt = 'createdAt'; updatedAt = 'updatedAt'; } else if (typeof this.options.timestamps === 'object') { if (this.options.timestamps.createdAt && this.options.timestamps.updatedAt) { createdAt = this.options.timestamps.createdAt; updatedAt = this.options.timestamps.updatedAt; } else { throw new errors.SchemaError('Missing createdAt and updatedAt timestamps attribute. Maybe set timestamps: true?'); } } else { throw new errors.SchemaError('Invalid syntax for timestamp: ' + this.options.timestamps); } obj[createdAt] = obj[createdAt] || {}; obj[createdAt].type = Date; obj[createdAt].default = Date.now; obj[updatedAt] = obj[updatedAt] || {}; obj[updatedAt].type = Date; obj[updatedAt].default = Date.now; obj[updatedAt].set = function() { return Date.now(); }; this.timestamps = { createdAt: createdAt, updatedAt: updatedAt }; } /* * Added support for expires attribute */ if (this.options.expires !== null && this.options.expires !== undefined ) { let expires = { attribute: 'expires', returnExpiredItems: true }; if (typeof this.options.expires === 'number') { expires.ttl = this.options.expires; } else if (typeof this.options.expires === 'object') { if (typeof this.options.expires.ttl === 'number') { expires.ttl = this.options.expires.ttl; } else { throw new errors.SchemaError('Missing or invalided ttl for expires attribute.'); } if(typeof this.options.expires.attribute === 'string') { expires.attribute = this.options.expires.attribute; } if (typeof this.options.expires.returnExpiredItems === "boolean") { expires.returnExpiredItems = this.options.expires.returnExpiredItems; } if (typeof this.options.expires.defaultExpires === "function") { expires.defaultExpires = this.options.expires.defaultExpires; } } else { throw new errors.SchemaError('Invalid syntax for expires: ' + this.options.expires); } let defaultExpires = function () { return new Date(Date.now() + (expires.ttl * 1000)); }; obj[expires.attribute] = { type: Number, default: expires.defaultExpires || defaultExpires, set: function(v) { return Math.floor(v.getTime() / 1000); }, get: function (v) { return new Date(v * 1000); } }; this.expires = expires; } this.useDocumentTypes = this.options.useDocumentTypes === undefined ? true : this.options.useDocumentTypes; this.useNativeBooleans = this.options.useNativeBooleans === undefined ? true : this.options.useNativeBooleans; this.attributeFromDynamo = this.options.attributeFromDynamo; this.attributeToDynamo = this.options.attributeToDynamo; this.attributes = {}; this.indexes = {local: {}, global: {}}; for(const n in obj) { if(this.attributes[n]) { throw new errors.SchemaError('Duplicate attribute: ' + n); } debug('Adding Attribute to Schema (%s)', n, obj); this.attributes[n] = Attribute.create(this, n, obj[n]); } } /*Schema.prototype.attribute = function(name, obj) { debug('Adding Attribute to Schema (%s)', name, obj); this Attribute.create(name, obj); };*/ Schema.prototype.toDynamo = function(model, options) { let dynamoObj = {}; let name, attr; for(name in model) { if(!model.hasOwnProperty(name)){ continue; } if (model[name] === undefined || model[name] === null || Number.isNaN(model[name])) { debug('toDynamo: skipping attribute: %s because its definition or value is null, undefined, or NaN', name); continue; } attr = this.attributes[name]; if((!attr && this.options.saveUnknown === true) || (this.options.saveUnknown instanceof Array && this.options.saveUnknown.indexOf(name) >= 0)) { attr = Attribute.create(this, name, model[name]); this.attributes[name] = attr; } } for(name in this.attributes) { attr = this.attributes[name]; attr.setDefault(model); let dynamoAttr; if (this.attributeToDynamo) { dynamoAttr = this.attributeToDynamo(name, model[name], model, attr.toDynamo.bind(attr), options); } else { dynamoAttr = attr.toDynamo(model[name], undefined, model, options); } if(dynamoAttr) { dynamoObj[attr.name] = dynamoAttr; } } debug('toDynamo: %s', dynamoObj ); return dynamoObj; }; Schema.prototype.parseDynamo = function(model, dynamoObj) { for(const name in dynamoObj) { let attr = this.attributes[name]; if((!attr && this.options.saveUnknown === true) || (this.options.saveUnknown instanceof Array && this.options.saveUnknown.indexOf(name) >= 0)) { attr = Attribute.createUnknownAttrbuteFromDynamo(this, name, dynamoObj[name]); this.attributes[name] = attr; } if(attr) { let attrVal; if (this.attributeFromDynamo) { attrVal = this.attributeFromDynamo(name, dynamoObj[name], attr.parseDynamo.bind(attr), model); } else { attrVal = attr.parseDynamo(dynamoObj[name]); } if (attrVal !== undefined && attrVal !== null) { model[name] = attrVal; } } else { debug('parseDynamo: received an attribute name (%s) that is not defined in the schema', name); } } if (model.$__) { model.$__.originalItem = JSON.parse(JSON.stringify(model)); } debug('parseDynamo: %s', model); return dynamoObj; }; /** * Adds an instance method to documents constructed from Models compiled from this schema. * * ####Example * * let schema = kittySchema = new Schema(..); * * schema.method('meow', function () { * console.log('meeeeeoooooooooooow'); * }) * * let Kitty = mongoose.model('Kitty', schema); * * let fizz = new Kitty; * fizz.meow(); // meeeeeooooooooooooow * * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods. * * schema.method({ * purr: function () {} * , scratch: function () {} * }); * * // later * fizz.purr(); * fizz.scratch(); * * @param {String|Object} method name * @param {Function} [fn] * @api public */ Schema.prototype.method = function (name, fn) { if (typeof name !== 'string' ){ for (const i in name){ this.methods[i] = name[i]; } } else { this.methods[name] = fn; } return this; }; /** * Adds static "class" methods to Models compiled from this schema. * * ####Example * * let schema = new Schema(..); * schema.static('findByName', function (name, callback) { * return this.find({ name: name }, callback); * }); * * let Drink = mongoose.model('Drink', schema); * Drink.findByName('sanpellegrino', function (err, drinks) { * // * }); * * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics. * * @param {String} name * @param {Function} fn * @api public */ Schema.prototype.static = function(name, fn) { if (typeof name !== 'string' ){ for (const i in name){ this.statics[i] = name[i]; } } else { this.statics[name] = fn; } return this; }; /** * Creates a virtual type with the given name. * * @param {String} name * @param {Object} [options] * @return {VirtualType} */ Schema.prototype.virtual = function (name, options) { //let virtuals = this.virtuals; const parts = name.split('.'); return this.virtuals[name] = parts.reduce(function (mem, part, i) { mem[part] || (mem[part] = (i === parts.length-1) ? new VirtualType(options, name) : {}); return mem[part]; }, this.tree); }; /** * Returns the virtual type with the given `name`. * * @param {String} name * @return {VirtualType} */ Schema.prototype.virtualpath = function (name) { return this.virtuals[name]; }; module.exports = Schema;