|
/**! |
|
* Node Type let you easily work with types and its core possibilities |
|
* such as enumerability, writability and readonly settings. Properties |
|
* can be declared with a specific type and a livetime validator. |
|
* |
|
* @name node-type |
|
* @author Jan Biasi <biasijan@gmail.com> |
|
* @license MIT License |
|
* |
|
* YOU CAN INSTALL TYPE.JS WITH NPM, USE THE FOLLOWING COMMAND |
|
* NPM INSTALL --SAVE NODE-TYPE |
|
*/ |
|
(function() { |
|
'use strict'; |
|
|
|
// Establish the root object, `window` in the browser, or `global` on the server. |
|
var root = this; |
|
|
|
/** |
|
* Main Type class, do not instanciate manually, use |
|
* the create method instead. |
|
* |
|
* @see Type.create (line 216) |
|
* @param {Object} definition Definition for typeset |
|
*/ |
|
function Type(definition) { |
|
var self = this; |
|
definition = definition || null; |
|
|
|
/** |
|
* Default settings for all Type instances, can be overwritten in the |
|
* decleration of each individual type. These settings are the default |
|
* settings for each instances. |
|
* @type {Object} |
|
* |
|
* errorOnReadOnlyAccess turn on to throw errors on ReadOnly access |
|
* defaultWritable if the defined properties should be writeable |
|
* defaultEnumerable if the defined properties should be enumerable |
|
*/ |
|
self.__config__ = { |
|
errorOnReadOnlyAccess: false, |
|
defaultWritable: true, |
|
defaultEnumerable: true |
|
}; |
|
|
|
/** |
|
* Storage for variables, because values can't be stored in the Type itself |
|
* causing a endless loop and stack size exceeded error. |
|
* @type {Object} |
|
*/ |
|
self.__store__ = {}; |
|
|
|
if(typeof definition === 'object') { |
|
|
|
/** |
|
* You can define properties within the properties object, |
|
* like this: |
|
* |
|
* var myType = Type.create({ |
|
* properties: { |
|
* code: Number, |
|
* address: { |
|
* type: String, |
|
* enumerable: false |
|
* } |
|
* } |
|
* }); |
|
* |
|
*/ |
|
var props = definition.properties; |
|
if(typeof props === 'object') { |
|
for(var field in props) { |
|
self._addProperty(props[field], field); |
|
} |
|
} else if(Array.isArray(props)) { |
|
|
|
/** |
|
* You can define properties within the properties array, |
|
* like this: |
|
* |
|
* var myType = Type.create({ |
|
* properties: [ |
|
* { |
|
* name: 'buylist', |
|
* type: Array |
|
* }, |
|
* { |
|
* name: 'due-until', |
|
* type: String, |
|
* writable: false |
|
* } |
|
* ] |
|
* }); |
|
* |
|
*/ |
|
props.forEach(function(propertie) { |
|
self._addProperty(propertie); |
|
}); |
|
} else { |
|
throw new Error('No properites found to define, please add some.'); |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* Validates a value with an expected type which is needed. |
|
* |
|
* @param {*} expect Expected class |
|
* @param {*} got Input value |
|
* @return {Boolean} |
|
*/ |
|
Type.prototype._validateTypes = function(expect, got) { |
|
switch(expect.name.toLowerCase()) { |
|
case 'number': |
|
return (typeof got === 'number'); |
|
case 'string': |
|
return (typeof got === 'string'); |
|
case 'function': |
|
var gt = {}; |
|
if(got && gt.toString.call(got) === '[object Function]') { |
|
return true; |
|
} |
|
case 'array': |
|
return Array.isArray(got); |
|
case 'object': |
|
return (typeof got === 'object'); |
|
default: |
|
return false; |
|
} |
|
}; |
|
|
|
/** |
|
* Wrapper for adding a new property to the current Type instance, |
|
* generating the configuration stub and defining the property with its |
|
* settings onto the Type wrapper. |
|
* |
|
* @param {Object} propset Property settings |
|
* @param {String?} altName Direct assigned name |
|
*/ |
|
Type.prototype._addProperty = function(propset, altName) { |
|
var configuration = this._generateConfiguration(propset, altName); |
|
var property = altName ? altName : obj.name; |
|
|
|
Object.defineProperty(this, property, configuration); |
|
}; |
|
|
|
/** |
|
* Generates the defineProperty valid configuration sets, including |
|
* the getter and setter (with their validators inside). |
|
* |
|
* @param {Object} conf Main configuration object for property |
|
* @param {String?} altName Alternative name, if used direct object assignments |
|
* @return {Object} Configuration stub |
|
*/ |
|
Type.prototype._generateConfiguration = function(conf, altName) { |
|
var self = this; |
|
|
|
if(typeof conf === 'object') { |
|
conf.name = conf.name || altName; |
|
} else { |
|
var typesave = conf; |
|
conf = { |
|
type: typesave, |
|
name: altName |
|
}; |
|
} |
|
|
|
var getter = function(value) { |
|
return self.__store__[conf.name]; |
|
}; |
|
|
|
var setter = function(value) { |
|
if(typeof conf.type !== 'undefined' && conf.type !== null) { |
|
if(!self._validateTypes(conf.type, value)) { |
|
throw new TypeError( |
|
'Type expected ' + conf.type.name + ' but received ' + |
|
typeof value + ' instead. Please use the right type.' |
|
); |
|
} |
|
|
|
if(conf.readonly) { |
|
if(self.__config__.errorOnReadOnlyAccess === true) { |
|
throw new Error( |
|
'Property ' + conf.name + ' is set to readonly ' + |
|
'and can\'t be modified.' |
|
); |
|
} else { |
|
return; |
|
} |
|
} |
|
} |
|
|
|
self.__store__[conf.name] = value; |
|
}; |
|
|
|
if(conf.value !== undefined) { |
|
// Setting initial values if is defined in propset |
|
if(self._validateTypes(conf.type, conf.value)) { |
|
self.__store__[conf.name] = conf.value; |
|
} |
|
} |
|
|
|
return { |
|
enumerable: conf.enumerable || self.__config__.defaultEnumerable, |
|
configurable: conf.configurable || self.__config__.defaultWritable, |
|
get: getter, |
|
set: setter |
|
}; |
|
}; |
|
|
|
/** |
|
* Update a default configuration for all types |
|
* |
|
* @param {String} key Configuration key |
|
* @param {*} value New value for configuration |
|
*/ |
|
Type.prototype.configure = function(key, value) { |
|
var setting = this.__config__[key]; |
|
if(setting !== undefined) { |
|
this.__config__[key] = value; |
|
} |
|
}; |
|
|
|
/** |
|
* Wrapper for instanciating new Types |
|
* |
|
* @param {Object} definition Typeset definition |
|
* @return {Type} Type-instance |
|
*/ |
|
Type.create = function(definition) { |
|
return new Type(definition); |
|
}; |
|
|
|
if(typeof module !== 'undefined' && module.exports) { |
|
// Export the Type module for Node.js |
|
exports = module.exports = Type; |
|
} else { |
|
// Make the Type public in Browser windows |
|
window.Type = Type; |
|
} |
|
})(); |