Source: liveview.js

/**
 * @namespace LiveView
 */
;(function(window, $, undefined){
	'use strict';
	var tmpPrototype;

	/** @alias LiveView.prototype */
	window.LiveView = {
		Table: Table,
		Query: Query,
		Schema: Schema,
		Field: Field,
		Tuple: Tuple,
		Error: Error,
		_internal: {}
	};

	/**
	 * Defines the properties of a LiveView Table. Properties are read-only.
	 * @class Table
	 * @memberOf LiveView
	 * @param {String} name The name of the table.
	 * @param {String} group The group the table belongs to.
	 * @param {String} shortDescription A short description of the table. Ideal for length-limited fields.
	 * @param {String} description A full description of the table.
	 * @param {boolean} isEnabled A flag indicating whether or not the table is enabled.
	 * @param {String} queryLanguages A CSV string of supported query languages for this table.
	 * @param {Array} keys Field names of key fields
	 * @param {LiveView.Schema} schema The Schema that defines the table fields.
	 */
	function Table(name, group, shortDescription, description, isEnabled, queryLanguages, keys, schema){
		if(!(this instanceof Table)){
			return new Table(name, group, shortDescription, description, isEnabled, queryLanguages, keys, schema);
		}
		/**
		 * The name of the table.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {String} name
		 */
		this.name = name;
		/**
		 * The group the table belongs to.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {String} group
		 */
		this.group = group;
		/**
		 * A short description of the table. Ideal for length-limited fields.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {String} shortDescription
		 */
		this.shortDescription = shortDescription;
		/**
		 * A full description of the table.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {String} description
		 */
		this.description = description;
		/**
		 * A flag indicating whether or not the table is enabled.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {boolean} isEnabled
		 */
		this.isEnabled = isEnabled;
		/**
		 * A CSV string of supported query languages for this table.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {String} queryLanguages
		 */
		this.queryLanguages = queryLanguages;
		/**
		 * An array of string field names identifying the table's key fields.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {Array} keys
		 */
		this.keys = keys;
		/**
		 * The {@link LiveView.Schema|Schema} for the table.
		 * @memberOf LiveView.Table
		 * @instance
		 * @member {LiveView.Schema} schema
		 */
		this.schema = schema;
	}
	/** @alias LiveView.Table.prototype */
	Table.prototype = {
		constructor: Table
	};

	/**
	 * Object that defines a LiveView query.
	 * @class Query
	 * @memberOf LiveView
	 * @param {String} query The optionally-parametrized query string
	 * @param {Object} [parameters] A key-value map of parameters to use to compute the query string, if query is a parametrized string. Users are essentially free to define the key value used to identify a parameter as they wish. The query string is generated using regular expression substitution, so avoidance of regular expression special characters is recommended as it will likely cause unexpected behavior. The '@' character as a parameter prefix works well (e.g. <span style="lvCode">{'@priceMin':100, '@lastUpdated':1415230518223}</span>).
	 * @param {boolean} [includeInternal] A flag indicating whether or not to include internal fields in (non-aggregate) query results.
	 */
	function Query(query, parameters, includeInternal){
		if(!(this instanceof Query)){
			return new Query(query, parameters, includeInternal);
		}
		/**
		 * The query string in parametrized form. For example: <span class="lvCode">"Select * From ItemsSales Where lastSoldPrice > @minPrice"</span>
		 * @memberOf LiveView.Query
		 * @instance
		 * @member {String} query
		 */
		this.query = query;
		/**
		 * A key-value map of parameters to use to compute the query string, if query is a parametrized string. Users are essentially free to define the key value used to identify a parameter as they wish. The query string is generated using regular expression substitution, so avoidance of regular expression special characters is recommended as it will likely cause unexpected behavior. The '@' character as a parameter prefix works well (e.g. <span style="lvCode">{'@priceMin':100, '@lastUpdated':1415230518223}</span>).
		 * @memberOf LiveView.Query
		 * @instance
		 * @member {Object} parameters
		 */
		this.parameters = parameters;
		/**
		 * A flag indicating whether or not to include internal fields in (non-aggregate) query results.
		 * @memberOf LiveView.Query
		 * @instance
		 * @member {String} includeInternal
		 */
		this.includeInternal = includeInternal;
	}
	/** @alias LiveView.Query.prototype */
	Query.prototype = {
		constructor: Query,
		/**
		 * Applies parameters to the query and returns the resulting query string. If this is not a parametrized query,
		 * this will be equivalent to the query field.
		 * @function
		 * @memberOf LiveView.Query.prototype
		 * @returns {String} -- The parameter-applied query string
		 */
		getQueryString: function(){
			var key,
				escapedKey,
				queryString = this.query;
			for(key in this.parameters){
				if(this.parameters.hasOwnProperty(key)){
					//we need to escape the key which could contain special characters, we need to treat them as literals
					escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
					queryString = queryString.replace(new RegExp(escapedKey, 'g'), this.parameters[key]);
				}
			}
			return queryString;
		},
		/**
		 * Formats the query string as required by the LiveView server.
		 * @function
		 * @memberOf LiveView.Query.prototype
		 * @returns {String} -- The parameter-applied, server-formatted query string
		 */
		serialize: function(){
			return this.getQueryString.call(this).replace(/^delete\sfrom\s/i ,'SELECT * FROM ').replace(/^delete/i,'SELECT * FROM ');
		}
	};

	/**
	 * A LiveView Schema is used for Queries and Tables to define their respective schemas.
	 * @class Schema
	 * @memberOf LiveView
	 * @param {String} name The name of the schema
	 * @param {Array} fields Array of {@link LiveView.Field|fields} that compose the schema.
	 */
	function Schema(name, fields){
		var i;
		if(!(this instanceof Schema)){
			return new Schema(name, fields);
		}
		/**
		 * Name of the Schema.
		 * @memberOf LiveView.Schema
		 * @instance
		 * @member {String} name
		 */
		this.name = name;
		/**
		 * The fields that define this schema as an array. The array preserves field order as received from the
		 * LiveView server.
		 * @memberOf LiveView.Schema
		 * @instance
		 * @member {Array} fields
		 */
		this.fields = fields;
		/**
		 * The fields that define this schema mapped by field name. The order of fields in the field map may not be
		 * consistent with the actual schema ordering. The fields array maintains fields in the correct order.
		 * @memberOf LiveView.Schema
		 * @instance
		 * @member {Object<String, LiveView.Field>} fieldsMap
		 */
		this.fieldsMap = {};

		for(i = 0; i < fields.length; i++){
			this.fieldsMap[fields[i].name] = fields[i];
		}
	}
	/** @alias LiveView.Schema.prototype */
	Schema.prototype = {
		constructor: Schema
	};

	/**
	 * Defines the properties of a LiveView Field.
	 * @class Field
	 * @memberOf LiveView
	 * @param {String} name The field name. This is used as the unique identifier for this field.
	 * @param {String} description A description of the field.
	 * @param {String} type The type of the field (e.g "string", "int", etc).
	 * @param {LiveView.Schema} [schema] The schema of this field if its type is "tuple".
	 */
	function Field(name, description, type, schema){
		if(!(this instanceof Field)){
			return new Field(name, description, type, schema);
		}
		/**
		 * The field name. This is used as the unique identifier for this field.
		 * @memberOf LiveView.Field
		 * @instance
		 * @member {String} name
		 */
		this.name = name;
		/**
		 * A full length description of the field.
		 * @memberOf LiveView.Field
		 * @instance
		 * @member {String} description
		 */
		this.description = description;
		/**
		 * The type of the field (e.g "string", "int", etc).
		 * @memberOf LiveView.Field
		 * @instance
		 * @member {String} type
		 */
		this.type = type;
		/**
		 * The schema of this field if its type is "tuple".
		 * @memberOf LiveView.Field
		 * @instance
		 * @member {LiveView.Schema} schema
		 */
		this.schema = schema;
	}
	/** @alias LiveView.Field.prototype */
	Field.prototype = {
		constructor: Field
	};

	/**
	 *
	 * @class Tuple
	 * @memberOf LiveView
	 * @param {Number} id The tuple's unique ID
	 * @param {Object} fieldMap Map of fieldName -> fieldValue
	 */
	function Tuple(id, fieldMap){
		if(!(this instanceof Tuple)){
			return new Tuple(id, fieldMap);
		}
		/**
		 * The tuple's unique ID
		 * @memberOf LiveView.Tuple
		 * @instance
		 * @member {Number} id
		 */
		this.id = id;
		/**
		 * Map of fieldName -> fieldValue for all fields in the tuple. Note that the order of fields in the map will not
		 * necessarily correspond to the order of fields in the corresponding schema. One should refer to the
		 * {@link LiveView.Schema#fields|fields} array of the corresponding schema to obtain the correct ordering of
		 * fields.
		 * @memberOf LiveView.Tuple
		 * @instance
		 * @member {Object} fieldMap
		 */
		this.fieldMap = fieldMap;
	}
	Tuple.prototype = {
		constructor: Tuple
	};

	/**
	 * LiveView error object that will be passed as an argument to error handling callbacks and promise rejection
	 * handlers. LiveView.Error inherits from window.Error if available.
	 * @class Error
	 * @memberOf LiveView
	 * @param {Number} code The error code that identifies the error.
	 * @param {String} detail A detailed message about what happened.
	 * @param {String} message A brief description summarizing the error.
	 * @constructor
	 */
	function Error(code, detail, message){
		var envErr;
		if(!(this instanceof Error)){
			return new Error(code, detail, message);
		}

		if(typeof(code) === 'object'){
			detail = code.detail || '';
			message = code.message || '';
			code = code.code || null;
		}

		//Add useful error info to our LiveView.Error from the native Error object (some values won't be set)
		if(typeof(window.Error) === 'function'){
			envErr = new window.Error(message);
			/**
			 * The error stack trace if available in the current browser.
			 * @memberOf LiveView.Error
			 * @instance
			 * @member {String} stack
			 */
			this.stack = envErr.stack;
		}

		//TODO: Enumerate error codes and update Error creations to use appropriate code
		/**
		 * The error code that identifies the error.
		 * @memberOf LiveView.Error
		 * @instance
		 * @member {Number} code
		 */
		this.code = code;
		/**
		 * A detailed message about what happened.
		 * @memberOf LiveView.Error
		 * @instance
		 * @member {String} detail
		 */
		this.detail = detail;

		/**
		 * A brief description summarizing the error.
		 * @memberOf LiveView.Error
		 * @instance
		 * @member {String} message
		 */
		this.message = message;
	}

	tmpPrototype = {
		constructor: Error,
		toString: function(){
			return JSON.stringify(this);
		}
	};

	if(window.Error.prototype){
		Error.prototype = jQuery.extend(Object.create(window.Error.prototype), tmpPrototype); //so LiveView.Error instance of Error is true
	}
	else{
		Error.prototype = tmpPrototype;
	}

}(window, jQuery));