(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
	(global = global || self, (global.sheetbase = global.sheetbase || {}, global.sheetbase.database = factory()));
}(this, function () { 'use strict';

	var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

	function createCommonjsModule(fn, module) {
		return module = { exports: {} }, fn(module, module.exports), module.exports;
	}

	var papaparse = createCommonjsModule(function (module, exports) {
	/*@license
		Papa Parse
		v4.4.0
		https://github.com/mholt/PapaParse
		License: MIT
	*/
	(function(root, factory)
	{
		/* globals define */
		{
			// Node. Does not work with strict CommonJS, but
			// only CommonJS-like environments that support module.exports,
			// like Node.
			module.exports = factory();
		}
	}(commonjsGlobal, function()
	{

		var global = (function() {
			// alternative method, similar to `Function('return this')()`
			// but without using `eval` (which is disabled when
			// using Content Security Policy).

			if (typeof self !== 'undefined') { return self; }
			if (typeof window !== 'undefined') { return window; }
			if (typeof global !== 'undefined') { return global; }

			// When running tests none of the above have been defined
			return {};
		})();


		var IS_WORKER = !global.document && !!global.postMessage,
			IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search),
			LOADED_SYNC = false, AUTO_SCRIPT_PATH;
		var workers = {}, workerIdCounter = 0;

		var Papa = {};

		Papa.parse = CsvToJson;
		Papa.unparse = JsonToCsv;

		Papa.RECORD_SEP = String.fromCharCode(30);
		Papa.UNIT_SEP = String.fromCharCode(31);
		Papa.BYTE_ORDER_MARK = '\ufeff';
		Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK];
		Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker;
		Papa.SCRIPT_PATH = null;	// Must be set by your code if you use workers and this lib is loaded asynchronously

		// Configurable chunk sizes for local and remote files, respectively
		Papa.LocalChunkSize = 1024 * 1024 * 10;	// 10 MB
		Papa.RemoteChunkSize = 1024 * 1024 * 5;	// 5 MB
		Papa.DefaultDelimiter = ',';			// Used if not specified and detection fails

		// Exposed for testing and development only
		Papa.Parser = Parser;
		Papa.ParserHandle = ParserHandle;
		Papa.NetworkStreamer = NetworkStreamer;
		Papa.FileStreamer = FileStreamer;
		Papa.StringStreamer = StringStreamer;
		Papa.ReadableStreamStreamer = ReadableStreamStreamer;

		if (global.jQuery)
		{
			var $ = global.jQuery;
			$.fn.parse = function(options)
			{
				var config = options.config || {};
				var queue = [];

				this.each(function(idx)
				{
					var supported = $(this).prop('tagName').toUpperCase() === 'INPUT'
									&& $(this).attr('type').toLowerCase() === 'file'
									&& global.FileReader;

					if (!supported || !this.files || this.files.length === 0)
						return true;	// continue to next input element

					for (var i = 0; i < this.files.length; i++)
					{
						queue.push({
							file: this.files[i],
							inputElem: this,
							instanceConfig: $.extend({}, config)
						});
					}
				});

				parseNextFile();	// begin parsing
				return this;		// maintains chainability


				function parseNextFile()
				{
					if (queue.length === 0)
					{
						if (isFunction(options.complete))
							options.complete();
						return;
					}

					var f = queue[0];

					if (isFunction(options.before))
					{
						var returned = options.before(f.file, f.inputElem);

						if (typeof returned === 'object')
						{
							if (returned.action === 'abort')
							{
								error('AbortError', f.file, f.inputElem, returned.reason);
								return;	// Aborts all queued files immediately
							}
							else if (returned.action === 'skip')
							{
								fileComplete();	// parse the next file in the queue, if any
								return;
							}
							else if (typeof returned.config === 'object')
								f.instanceConfig = $.extend(f.instanceConfig, returned.config);
						}
						else if (returned === 'skip')
						{
							fileComplete();	// parse the next file in the queue, if any
							return;
						}
					}

					// Wrap up the user's complete callback, if any, so that ours also gets executed
					var userCompleteFunc = f.instanceConfig.complete;
					f.instanceConfig.complete = function(results)
					{
						if (isFunction(userCompleteFunc))
							userCompleteFunc(results, f.file, f.inputElem);
						fileComplete();
					};

					Papa.parse(f.file, f.instanceConfig);
				}

				function error(name, file, elem, reason)
				{
					if (isFunction(options.error))
						options.error({name: name}, file, elem, reason);
				}

				function fileComplete()
				{
					queue.splice(0, 1);
					parseNextFile();
				}
			};
		}


		if (IS_PAPA_WORKER)
		{
			global.onmessage = workerThreadReceivedMessage;
		}
		else if (Papa.WORKERS_SUPPORTED)
		{
			AUTO_SCRIPT_PATH = getScriptPath();

			// Check if the script was loaded synchronously
			if (!document.body)
			{
				// Body doesn't exist yet, must be synchronous
				LOADED_SYNC = true;
			}
			else
			{
				document.addEventListener('DOMContentLoaded', function() {
					LOADED_SYNC = true;
				}, true);
			}
		}




		function CsvToJson(_input, _config)
		{
			_config = _config || {};
			var dynamicTyping = _config.dynamicTyping || false;
			if (isFunction(dynamicTyping)) {
				_config.dynamicTypingFunction = dynamicTyping;
				// Will be filled on first row call
				dynamicTyping = {};
			}
			_config.dynamicTyping = dynamicTyping;

			if (_config.worker && Papa.WORKERS_SUPPORTED)
			{
				var w = newWorker();

				w.userStep = _config.step;
				w.userChunk = _config.chunk;
				w.userComplete = _config.complete;
				w.userError = _config.error;

				_config.step = isFunction(_config.step);
				_config.chunk = isFunction(_config.chunk);
				_config.complete = isFunction(_config.complete);
				_config.error = isFunction(_config.error);
				delete _config.worker;	// prevent infinite loop

				w.postMessage({
					input: _input,
					config: _config,
					workerId: w.id
				});

				return;
			}

			var streamer = null;
			if (typeof _input === 'string')
			{
				if (_config.download)
					streamer = new NetworkStreamer(_config);
				else
					streamer = new StringStreamer(_config);
			}
			else if (_input.readable === true && isFunction(_input.read) && isFunction(_input.on))
			{
				streamer = new ReadableStreamStreamer(_config);
			}
			else if ((global.File && _input instanceof File) || _input instanceof Object)	// ...Safari. (see issue #106)
				streamer = new FileStreamer(_config);

			return streamer.stream(_input);
		}






		function JsonToCsv(_input, _config)
		{
			// Default configuration

			/** whether to surround every datum with quotes */
			var _quotes = false;

			/** whether to write headers */
			var _writeHeader = true;

			/** delimiting character */
			var _delimiter = ',';

			/** newline character(s) */
			var _newline = '\r\n';

			/** quote character */
			var _quoteChar = '"';

			unpackConfig();

			var quoteCharRegex = new RegExp(_quoteChar, 'g');

			if (typeof _input === 'string')
				_input = JSON.parse(_input);

			if (_input instanceof Array)
			{
				if (!_input.length || _input[0] instanceof Array)
					return serialize(null, _input);
				else if (typeof _input[0] === 'object')
					return serialize(objectKeys(_input[0]), _input);
			}
			else if (typeof _input === 'object')
			{
				if (typeof _input.data === 'string')
					_input.data = JSON.parse(_input.data);

				if (_input.data instanceof Array)
				{
					if (!_input.fields)
						_input.fields =  _input.meta && _input.meta.fields;

					if (!_input.fields)
						_input.fields =  _input.data[0] instanceof Array
							? _input.fields
							: objectKeys(_input.data[0]);

					if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object')
						_input.data = [_input.data];	// handles input like [1,2,3] or ['asdf']
				}

				return serialize(_input.fields || [], _input.data || []);
			}

			// Default (any valid paths should return before this)
			throw 'exception: Unable to serialize unrecognized input';


			function unpackConfig()
			{
				if (typeof _config !== 'object')
					return;

				if (typeof _config.delimiter === 'string'
					&& _config.delimiter.length === 1
					&& Papa.BAD_DELIMITERS.indexOf(_config.delimiter) === -1)
				{
					_delimiter = _config.delimiter;
				}

				if (typeof _config.quotes === 'boolean'
					|| _config.quotes instanceof Array)
					_quotes = _config.quotes;

				if (typeof _config.newline === 'string')
					_newline = _config.newline;

				if (typeof _config.quoteChar === 'string')
					_quoteChar = _config.quoteChar;

				if (typeof _config.header === 'boolean')
					_writeHeader = _config.header;
			}


			/** Turns an object's keys into an array */
			function objectKeys(obj)
			{
				if (typeof obj !== 'object')
					return [];
				var keys = [];
				for (var key in obj)
					keys.push(key);
				return keys;
			}

			/** The double for loop that iterates the data and writes out a CSV string including header row */
			function serialize(fields, data)
			{
				var csv = '';

				if (typeof fields === 'string')
					fields = JSON.parse(fields);
				if (typeof data === 'string')
					data = JSON.parse(data);

				var hasHeader = fields instanceof Array && fields.length > 0;
				var dataKeyedByField = !(data[0] instanceof Array);

				// If there a header row, write it first
				if (hasHeader && _writeHeader)
				{
					for (var i = 0; i < fields.length; i++)
					{
						if (i > 0)
							csv += _delimiter;
						csv += safe(fields[i], i);
					}
					if (data.length > 0)
						csv += _newline;
				}

				// Then write out the data
				for (var row = 0; row < data.length; row++)
				{
					var maxCol = hasHeader ? fields.length : data[row].length;

					for (var col = 0; col < maxCol; col++)
					{
						if (col > 0)
							csv += _delimiter;
						var colIdx = hasHeader && dataKeyedByField ? fields[col] : col;
						csv += safe(data[row][colIdx], col);
					}

					if (row < data.length - 1)
						csv += _newline;
				}

				return csv;
			}

			/** Encloses a value around quotes if needed (makes a value safe for CSV insertion) */
			function safe(str, col)
			{
				if (typeof str === 'undefined' || str === null)
					return '';

				str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar);

				var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
								|| (_quotes instanceof Array && _quotes[col])
								|| hasAny(str, Papa.BAD_DELIMITERS)
								|| str.indexOf(_delimiter) > -1
								|| str.charAt(0) === ' '
								|| str.charAt(str.length - 1) === ' ';

				return needsQuotes ? _quoteChar + str + _quoteChar : str;
			}

			function hasAny(str, substrings)
			{
				for (var i = 0; i < substrings.length; i++)
					if (str.indexOf(substrings[i]) > -1)
						return true;
				return false;
			}
		}

		/** ChunkStreamer is the base prototype for various streamer implementations. */
		function ChunkStreamer(config)
		{
			this._handle = null;
			this._finished = false;
			this._completed = false;
			this._input = null;
			this._baseIndex = 0;
			this._partialLine = '';
			this._rowCount = 0;
			this._start = 0;
			this._nextChunk = null;
			this.isFirstChunk = true;
			this._completeResults = {
				data: [],
				errors: [],
				meta: {}
			};
			replaceConfig.call(this, config);

			this.parseChunk = function(chunk, isFakeChunk)
			{
				// First chunk pre-processing
				if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk))
				{
					var modifiedChunk = this._config.beforeFirstChunk(chunk);
					if (modifiedChunk !== undefined)
						chunk = modifiedChunk;
				}
				this.isFirstChunk = false;

				// Rejoin the line we likely just split in two by chunking the file
				var aggregate = this._partialLine + chunk;
				this._partialLine = '';

				var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);

				if (this._handle.paused() || this._handle.aborted())
					return;

				var lastIndex = results.meta.cursor;

				if (!this._finished)
				{
					this._partialLine = aggregate.substring(lastIndex - this._baseIndex);
					this._baseIndex = lastIndex;
				}

				if (results && results.data)
					this._rowCount += results.data.length;

				var finishedIncludingPreview = this._finished || (this._config.preview && this._rowCount >= this._config.preview);

				if (IS_PAPA_WORKER)
				{
					global.postMessage({
						results: results,
						workerId: Papa.WORKER_ID,
						finished: finishedIncludingPreview
					});
				}
				else if (isFunction(this._config.chunk) && !isFakeChunk)
				{
					this._config.chunk(results, this._handle);
					if (this._handle.paused() || this._handle.aborted())
						return;
					results = undefined;
					this._completeResults = undefined;
				}

				if (!this._config.step && !this._config.chunk) {
					this._completeResults.data = this._completeResults.data.concat(results.data);
					this._completeResults.errors = this._completeResults.errors.concat(results.errors);
					this._completeResults.meta = results.meta;
				}

				if (!this._completed && finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted)) {
					this._config.complete(this._completeResults, this._input);
					this._completed = true;
				}

				if (!finishedIncludingPreview && (!results || !results.meta.paused))
					this._nextChunk();

				return results;
			};

			this._sendError = function(error)
			{
				if (isFunction(this._config.error))
					this._config.error(error);
				else if (IS_PAPA_WORKER && this._config.error)
				{
					global.postMessage({
						workerId: Papa.WORKER_ID,
						error: error,
						finished: false
					});
				}
			};

			function replaceConfig(config)
			{
				// Deep-copy the config so we can edit it
				var configCopy = copy(config);
				configCopy.chunkSize = parseInt(configCopy.chunkSize);	// parseInt VERY important so we don't concatenate strings!
				if (!config.step && !config.chunk)
					configCopy.chunkSize = null;  // disable Range header if not streaming; bad values break IIS - see issue #196
				this._handle = new ParserHandle(configCopy);
				this._handle.streamer = this;
				this._config = configCopy;	// persist the copy to the caller
			}
		}


		function NetworkStreamer(config)
		{
			config = config || {};
			if (!config.chunkSize)
				config.chunkSize = Papa.RemoteChunkSize;
			ChunkStreamer.call(this, config);

			var xhr;

			if (IS_WORKER)
			{
				this._nextChunk = function()
				{
					this._readChunk();
					this._chunkLoaded();
				};
			}
			else
			{
				this._nextChunk = function()
				{
					this._readChunk();
				};
			}

			this.stream = function(url)
			{
				this._input = url;
				this._nextChunk();	// Starts streaming
			};

			this._readChunk = function()
			{
				if (this._finished)
				{
					this._chunkLoaded();
					return;
				}

				xhr = new XMLHttpRequest();

				if (this._config.withCredentials)
				{
					xhr.withCredentials = this._config.withCredentials;
				}

				if (!IS_WORKER)
				{
					xhr.onload = bindFunction(this._chunkLoaded, this);
					xhr.onerror = bindFunction(this._chunkError, this);
				}

				xhr.open('GET', this._input, !IS_WORKER);
				// Headers can only be set when once the request state is OPENED
				if (this._config.downloadRequestHeaders)
				{
					var headers = this._config.downloadRequestHeaders;

					for (var headerName in headers)
					{
						xhr.setRequestHeader(headerName, headers[headerName]);
					}
				}

				if (this._config.chunkSize)
				{
					var end = this._start + this._config.chunkSize - 1;	// minus one because byte range is inclusive
					xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end);
					xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672
				}

				try {
					xhr.send();
				}
				catch (err) {
					this._chunkError(err.message);
				}

				if (IS_WORKER && xhr.status === 0)
					this._chunkError();
				else
					this._start += this._config.chunkSize;
			};

			this._chunkLoaded = function()
			{
				if (xhr.readyState !== 4)
					return;

				if (xhr.status < 200 || xhr.status >= 400)
				{
					this._chunkError();
					return;
				}

				this._finished = !this._config.chunkSize || this._start > getFileSize(xhr);
				this.parseChunk(xhr.responseText);
			};

			this._chunkError = function(errorMessage)
			{
				var errorText = xhr.statusText || errorMessage;
				this._sendError(new Error(errorText));
			};

			function getFileSize(xhr)
			{
				var contentRange = xhr.getResponseHeader('Content-Range');
				if (contentRange === null) { // no content range, then finish!
					return -1;
				}
				return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1));
			}
		}
		NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype);
		NetworkStreamer.prototype.constructor = NetworkStreamer;


		function FileStreamer(config)
		{
			config = config || {};
			if (!config.chunkSize)
				config.chunkSize = Papa.LocalChunkSize;
			ChunkStreamer.call(this, config);

			var reader, slice;

			// FileReader is better than FileReaderSync (even in worker) - see http://stackoverflow.com/q/24708649/1048862
			// But Firefox is a pill, too - see issue #76: https://github.com/mholt/PapaParse/issues/76
			var usingAsyncReader = typeof FileReader !== 'undefined';	// Safari doesn't consider it a function - see issue #105

			this.stream = function(file)
			{
				this._input = file;
				slice = file.slice || file.webkitSlice || file.mozSlice;

				if (usingAsyncReader)
				{
					reader = new FileReader();		// Preferred method of reading files, even in workers
					reader.onload = bindFunction(this._chunkLoaded, this);
					reader.onerror = bindFunction(this._chunkError, this);
				}
				else
					reader = new FileReaderSync();	// Hack for running in a web worker in Firefox

				this._nextChunk();	// Starts streaming
			};

			this._nextChunk = function()
			{
				if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview))
					this._readChunk();
			};

			this._readChunk = function()
			{
				var input = this._input;
				if (this._config.chunkSize)
				{
					var end = Math.min(this._start + this._config.chunkSize, this._input.size);
					input = slice.call(input, this._start, end);
				}
				var txt = reader.readAsText(input, this._config.encoding);
				if (!usingAsyncReader)
					this._chunkLoaded({ target: { result: txt } });	// mimic the async signature
			};

			this._chunkLoaded = function(event)
			{
				// Very important to increment start each time before handling results
				this._start += this._config.chunkSize;
				this._finished = !this._config.chunkSize || this._start >= this._input.size;
				this.parseChunk(event.target.result);
			};

			this._chunkError = function()
			{
				this._sendError(reader.error);
			};

		}
		FileStreamer.prototype = Object.create(ChunkStreamer.prototype);
		FileStreamer.prototype.constructor = FileStreamer;


		function StringStreamer(config)
		{
			config = config || {};
			ChunkStreamer.call(this, config);

			var remaining;
			this.stream = function(s)
			{
				remaining = s;
				return this._nextChunk();
			};
			this._nextChunk = function()
			{
				if (this._finished) return;
				var size = this._config.chunkSize;
				var chunk = size ? remaining.substr(0, size) : remaining;
				remaining = size ? remaining.substr(size) : '';
				this._finished = !remaining;
				return this.parseChunk(chunk);
			};
		}
		StringStreamer.prototype = Object.create(StringStreamer.prototype);
		StringStreamer.prototype.constructor = StringStreamer;


		function ReadableStreamStreamer(config)
		{
			config = config || {};

			ChunkStreamer.call(this, config);

			var queue = [];
			var parseOnData = true;
			var streamHasEnded = false;

			this.pause = function()
			{
				ChunkStreamer.prototype.pause.apply(this, arguments);
				this._input.pause();
			};

			this.resume = function()
			{
				ChunkStreamer.prototype.resume.apply(this, arguments);
				this._input.resume();
			};

			this.stream = function(stream)
			{
				this._input = stream;

				this._input.on('data', this._streamData);
				this._input.on('end', this._streamEnd);
				this._input.on('error', this._streamError);
			};

			this._checkIsFinished = function()
			{
				if (streamHasEnded && queue.length === 1) {
					this._finished = true;
				}
			};

			this._nextChunk = function()
			{
				this._checkIsFinished();
				if (queue.length)
				{
					this.parseChunk(queue.shift());
				}
				else
				{
					parseOnData = true;
				}
			};

			this._streamData = bindFunction(function(chunk)
			{
				try
				{
					queue.push(typeof chunk === 'string' ? chunk : chunk.toString(this._config.encoding));

					if (parseOnData)
					{
						parseOnData = false;
						this._checkIsFinished();
						this.parseChunk(queue.shift());
					}
				}
				catch (error)
				{
					this._streamError(error);
				}
			}, this);

			this._streamError = bindFunction(function(error)
			{
				this._streamCleanUp();
				this._sendError(error);
			}, this);

			this._streamEnd = bindFunction(function()
			{
				this._streamCleanUp();
				streamHasEnded = true;
				this._streamData('');
			}, this);

			this._streamCleanUp = bindFunction(function()
			{
				this._input.removeListener('data', this._streamData);
				this._input.removeListener('end', this._streamEnd);
				this._input.removeListener('error', this._streamError);
			}, this);
		}
		ReadableStreamStreamer.prototype = Object.create(ChunkStreamer.prototype);
		ReadableStreamStreamer.prototype.constructor = ReadableStreamStreamer;


		// Use one ParserHandle per entire CSV file or string
		function ParserHandle(_config)
		{
			// One goal is to minimize the use of regular expressions...
			var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;

			var self = this;
			var _stepCounter = 0;	// Number of times step was called (number of rows parsed)
			var _input;				// The input being parsed
			var _parser;			// The core parser being used
			var _paused = false;	// Whether we are paused or not
			var _aborted = false;	// Whether the parser has aborted or not
			var _delimiterError;	// Temporary state between delimiter detection and processing results
			var _fields = [];		// Fields are from the header row of the input, if there is one
			var _results = {		// The last results returned from the parser
				data: [],
				errors: [],
				meta: {}
			};

			if (isFunction(_config.step))
			{
				var userStep = _config.step;
				_config.step = function(results)
				{
					_results = results;

					if (needsHeaderRow())
						processResults();
					else	// only call user's step function after header row
					{
						processResults();

						// It's possbile that this line was empty and there's no row here after all
						if (_results.data.length === 0)
							return;

						_stepCounter += results.data.length;
						if (_config.preview && _stepCounter > _config.preview)
							_parser.abort();
						else
							userStep(_results, self);
					}
				};
			}

			/**
			 * Parses input. Most users won't need, and shouldn't mess with, the baseIndex
			 * and ignoreLastRow parameters. They are used by streamers (wrapper functions)
			 * when an input comes in multiple chunks, like from a file.
			 */
			this.parse = function(input, baseIndex, ignoreLastRow)
			{
				if (!_config.newline)
					_config.newline = guessLineEndings(input);

				_delimiterError = false;
				if (!_config.delimiter)
				{
					var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines);
					if (delimGuess.successful)
						_config.delimiter = delimGuess.bestDelimiter;
					else
					{
						_delimiterError = true;	// add error after parsing (otherwise it would be overwritten)
						_config.delimiter = Papa.DefaultDelimiter;
					}
					_results.meta.delimiter = _config.delimiter;
				}
				else if(isFunction(_config.delimiter))
				{
					_config.delimiter = _config.delimiter(input);
					_results.meta.delimiter = _config.delimiter;
				}

				var parserConfig = copy(_config);
				if (_config.preview && _config.header)
					parserConfig.preview++;	// to compensate for header row

				_input = input;
				_parser = new Parser(parserConfig);
				_results = _parser.parse(_input, baseIndex, ignoreLastRow);
				processResults();
				return _paused ? { meta: { paused: true } } : (_results || { meta: { paused: false } });
			};

			this.paused = function()
			{
				return _paused;
			};

			this.pause = function()
			{
				_paused = true;
				_parser.abort();
				_input = _input.substr(_parser.getCharIndex());
			};

			this.resume = function()
			{
				_paused = false;
				self.streamer.parseChunk(_input, true);
			};

			this.aborted = function()
			{
				return _aborted;
			};

			this.abort = function()
			{
				_aborted = true;
				_parser.abort();
				_results.meta.aborted = true;
				if (isFunction(_config.complete))
					_config.complete(_results);
				_input = '';
			};

			function processResults()
			{
				if (_results && _delimiterError)
				{
					addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \'' + Papa.DefaultDelimiter + '\'');
					_delimiterError = false;
				}

				if (_config.skipEmptyLines)
				{
					for (var i = 0; i < _results.data.length; i++)
						if (_results.data[i].length === 1 && _results.data[i][0] === '')
							_results.data.splice(i--, 1);
				}

				if (needsHeaderRow())
					fillHeaderFields();

				return applyHeaderAndDynamicTyping();
			}

			function needsHeaderRow()
			{
				return _config.header && _fields.length === 0;
			}

			function fillHeaderFields()
			{
				if (!_results)
					return;
				for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
					for (var j = 0; j < _results.data[i].length; j++)
					{
						var header = _results.data[i][j];

						if (_config.trimHeaders) {
							header = header.trim();
						}

						_fields.push(header);
					}
				_results.data.splice(0, 1);
			}

			function shouldApplyDynamicTyping(field) {
				// Cache function values to avoid calling it for each row
				if (_config.dynamicTypingFunction && _config.dynamicTyping[field] === undefined) {
					_config.dynamicTyping[field] = _config.dynamicTypingFunction(field);
				}
				return (_config.dynamicTyping[field] || _config.dynamicTyping) === true;
			}

			function parseDynamic(field, value)
			{
				if (shouldApplyDynamicTyping(field))
				{
					if (value === 'true' || value === 'TRUE')
						return true;
					else if (value === 'false' || value === 'FALSE')
						return false;
					else if(FLOAT.test(value)) {
						return parseFloat(value);
					}
					else {
						return (value === '' ? null : value);
					}
				}
				return value;
			}

			function applyHeaderAndDynamicTyping()
			{
				if (!_results || (!_config.header && !_config.dynamicTyping))
					return _results;

				for (var i = 0; i < _results.data.length; i++)
				{
					var row = _config.header ? {} : [];

					var j;
					for (j = 0; j < _results.data[i].length; j++)
					{
						var field = j;
						var value = _results.data[i][j];

						if (_config.header)
							field = j >= _fields.length ? '__parsed_extra' : _fields[j];

						value = parseDynamic(field, value);

						if (field === '__parsed_extra')
						{
							row[field] = row[field] || [];
							row[field].push(value);
						}
						else
							row[field] = value;
					}

					_results.data[i] = row;

					if (_config.header)
					{
						if (j > _fields.length)
							addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, i);
						else if (j < _fields.length)
							addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, i);
					}
				}

				if (_config.header && _results.meta)
					_results.meta.fields = _fields;
				return _results;
			}

			function guessDelimiter(input, newline, skipEmptyLines)
			{
				var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
				var bestDelim, bestDelta, fieldCountPrevRow;

				for (var i = 0; i < delimChoices.length; i++)
				{
					var delim = delimChoices[i];
					var delta = 0, avgFieldCount = 0, emptyLinesCount = 0;
					fieldCountPrevRow = undefined;

					var preview = new Parser({
						delimiter: delim,
						newline: newline,
						preview: 10
					}).parse(input);

					for (var j = 0; j < preview.data.length; j++)
					{
						if (skipEmptyLines && preview.data[j].length === 1 && preview.data[j][0].length === 0) {
							emptyLinesCount++;
							continue;
						}
						var fieldCount = preview.data[j].length;
						avgFieldCount += fieldCount;

						if (typeof fieldCountPrevRow === 'undefined')
						{
							fieldCountPrevRow = fieldCount;
							continue;
						}
						else if (fieldCount > 1)
						{
							delta += Math.abs(fieldCount - fieldCountPrevRow);
							fieldCountPrevRow = fieldCount;
						}
					}

					if (preview.data.length > 0)
						avgFieldCount /= (preview.data.length - emptyLinesCount);

					if ((typeof bestDelta === 'undefined' || delta < bestDelta)
						&& avgFieldCount > 1.99)
					{
						bestDelta = delta;
						bestDelim = delim;
					}
				}

				_config.delimiter = bestDelim;

				return {
					successful: !!bestDelim,
					bestDelimiter: bestDelim
				};
			}

			function guessLineEndings(input)
			{
				input = input.substr(0, 1024 * 1024);	// max length 1 MB

				var r = input.split('\r');

				var n = input.split('\n');

				var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);

				if (r.length === 1 || nAppearsFirst)
					return '\n';

				var numWithN = 0;
				for (var i = 0; i < r.length; i++)
				{
					if (r[i][0] === '\n')
						numWithN++;
				}

				return numWithN >= r.length / 2 ? '\r\n' : '\r';
			}

			function addError(type, code, msg, row)
			{
				_results.errors.push({
					type: type,
					code: code,
					message: msg,
					row: row
				});
			}
		}





		/** The core parser implements speedy and correct CSV parsing */
		function Parser(config)
		{
			// Unpack the config object
			config = config || {};
			var delim = config.delimiter;
			var newline = config.newline;
			var comments = config.comments;
			var step = config.step;
			var preview = config.preview;
			var fastMode = config.fastMode;
			var quoteChar;
			/** Allows for no quoteChar by setting quoteChar to undefined in config */
			if (config.quoteChar === undefined) {
				quoteChar = '"';
			} else {
				quoteChar = config.quoteChar;
			}
			var escapeChar = quoteChar;
			if (config.escapeChar !== undefined) {
				escapeChar = config.escapeChar;
			}

			// Delimiter must be valid
			if (typeof delim !== 'string'
				|| Papa.BAD_DELIMITERS.indexOf(delim) > -1)
				delim = ',';

			// Comment character must be valid
			if (comments === delim)
				throw 'Comment character same as delimiter';
			else if (comments === true)
				comments = '#';
			else if (typeof comments !== 'string'
				|| Papa.BAD_DELIMITERS.indexOf(comments) > -1)
				comments = false;

			// Newline must be valid: \r, \n, or \r\n
			if (newline !== '\n' && newline !== '\r' && newline !== '\r\n')
				newline = '\n';

			// We're gonna need these at the Parser scope
			var cursor = 0;
			var aborted = false;

			this.parse = function(input, baseIndex, ignoreLastRow)
			{
				// For some reason, in Chrome, this speeds things up (!?)
				if (typeof input !== 'string')
					throw 'Input must be a string';

				// We don't need to compute some of these every time parse() is called,
				// but having them in a more local scope seems to perform better
				var inputLen = input.length,
					delimLen = delim.length,
					newlineLen = newline.length,
					commentsLen = comments.length;
				var stepIsFunction = isFunction(step);

				// Establish starting state
				cursor = 0;
				var data = [], errors = [], row = [], lastCursor = 0;

				if (!input)
					return returnable();

				if (fastMode || (fastMode !== false && input.indexOf(quoteChar) === -1))
				{
					var rows = input.split(newline);
					for (var i = 0; i < rows.length; i++)
					{
						row = rows[i];
						cursor += row.length;
						if (i !== rows.length - 1)
							cursor += newline.length;
						else if (ignoreLastRow)
							return returnable();
						if (comments && row.substr(0, commentsLen) === comments)
							continue;
						if (stepIsFunction)
						{
							data = [];
							pushRow(row.split(delim));
							doStep();
							if (aborted)
								return returnable();
						}
						else
							pushRow(row.split(delim));
						if (preview && i >= preview)
						{
							data = data.slice(0, preview);
							return returnable(true);
						}
					}
					return returnable();
				}

				var nextDelim = input.indexOf(delim, cursor);
				var nextNewline = input.indexOf(newline, cursor);
				var quoteCharRegex = new RegExp(escapeChar.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') + quoteChar, 'g');
				var quoteSearch;

				// Parser loop
				for (;;)
				{
					// Field has opening quote
					if (input[cursor] === quoteChar)
					{
						// Start our search for the closing quote where the cursor is
						quoteSearch = cursor;

						// Skip the opening quote
						cursor++;

						for (;;)
						{
							// Find closing quote
							quoteSearch = input.indexOf(quoteChar, quoteSearch + 1);

							//No other quotes are found - no other delimiters
							if (quoteSearch === -1)
							{
								if (!ignoreLastRow) {
									// No closing quote... what a pity
									errors.push({
										type: 'Quotes',
										code: 'MissingQuotes',
										message: 'Quoted field unterminated',
										row: data.length,	// row has yet to be inserted
										index: cursor
									});
								}
								return finish();
							}

							// Closing quote at EOF
							if (quoteSearch === inputLen - 1)
							{
								var value = input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar);
								return finish(value);
							}

							// If this quote is escaped, it's part of the data; skip it
							// If the quote character is the escape character, then check if the next character is the escape character
							if (quoteChar === escapeChar &&  input[quoteSearch + 1] === escapeChar)
							{
								quoteSearch++;
								continue;
							}

							// If the quote character is not the escape character, then check if the previous character was the escape character
							if (quoteChar !== escapeChar && quoteSearch !== 0 && input[quoteSearch - 1] === escapeChar)
							{
								continue;
							}

							var spacesBetweenQuoteAndDelimiter = extraSpaces(nextDelim);

							// Closing quote followed by delimiter or 'unnecessary steps + delimiter'
							if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter] === delim)
							{
								row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
								cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen;
								nextDelim = input.indexOf(delim, cursor);
								nextNewline = input.indexOf(newline, cursor);
								break;
							}

							var spacesBetweenQuoteAndNewLine = extraSpaces(nextNewline);

							// Closing quote followed by newline or 'unnecessary spaces + newLine'
							if (input.substr(quoteSearch + 1 + spacesBetweenQuoteAndNewLine, newlineLen) === newline)
							{
								row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
								saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen);
								nextDelim = input.indexOf(delim, cursor);	// because we may have skipped the nextDelim in the quoted field

								if (stepIsFunction)
								{
									doStep();
									if (aborted)
										return returnable();
								}

								if (preview && data.length >= preview)
									return returnable(true);

								break;
							}


							// Checks for valid closing quotes are complete (escaped quotes or quote followed by EOF/delimiter/newline) -- assume these quotes are part of an invalid text string
							errors.push({
								type: 'Quotes',
								code: 'InvalidQuotes',
								message: 'Trailing quote on quoted field is malformed',
								row: data.length,	// row has yet to be inserted
								index: cursor
							});

							quoteSearch++;
							continue;

						}

						continue;
					}

					// Comment found at start of new line
					if (comments && row.length === 0 && input.substr(cursor, commentsLen) === comments)
					{
						if (nextNewline === -1)	// Comment ends at EOF
							return returnable();
						cursor = nextNewline + newlineLen;
						nextNewline = input.indexOf(newline, cursor);
						nextDelim = input.indexOf(delim, cursor);
						continue;
					}

					// Next delimiter comes before next newline, so we've reached end of field
					if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1))
					{
						row.push(input.substring(cursor, nextDelim));
						cursor = nextDelim + delimLen;
						nextDelim = input.indexOf(delim, cursor);
						continue;
					}

					// End of row
					if (nextNewline !== -1)
					{
						row.push(input.substring(cursor, nextNewline));
						saveRow(nextNewline + newlineLen);

						if (stepIsFunction)
						{
							doStep();
							if (aborted)
								return returnable();
						}

						if (preview && data.length >= preview)
							return returnable(true);

						continue;
					}

					break;
				}


				return finish();


				function pushRow(row)
				{
					data.push(row);
					lastCursor = cursor;
				}

				/**
	             * checks if there are extra spaces after closing quote and given index without any text
	             * if Yes, returns the number of spaces
	             */
				function extraSpaces(index) {
					var spaceLength = 0;
					if (index !== -1) {
						var textBetweenClosingQuoteAndIndex = input.substring(quoteSearch + 1, index);
						if (textBetweenClosingQuoteAndIndex && textBetweenClosingQuoteAndIndex.trim() === '') {
							spaceLength = textBetweenClosingQuoteAndIndex.length;
						}
					}
					return spaceLength;
				}

				/**
				 * Appends the remaining input from cursor to the end into
				 * row, saves the row, calls step, and returns the results.
				 */
				function finish(value)
				{
					if (ignoreLastRow)
						return returnable();
					if (typeof value === 'undefined')
						value = input.substr(cursor);
					row.push(value);
					cursor = inputLen;	// important in case parsing is paused
					pushRow(row);
					if (stepIsFunction)
						doStep();
					return returnable();
				}

				/**
				 * Appends the current row to the results. It sets the cursor
				 * to newCursor and finds the nextNewline. The caller should
				 * take care to execute user's step function and check for
				 * preview and end parsing if necessary.
				 */
				function saveRow(newCursor)
				{
					cursor = newCursor;
					pushRow(row);
					row = [];
					nextNewline = input.indexOf(newline, cursor);
				}

				/** Returns an object with the results, errors, and meta. */
				function returnable(stopped)
				{
					return {
						data: data,
						errors: errors,
						meta: {
							delimiter: delim,
							linebreak: newline,
							aborted: aborted,
							truncated: !!stopped,
							cursor: lastCursor + (baseIndex || 0)
						}
					};
				}

				/** Executes the user's step function and resets data & errors. */
				function doStep()
				{
					step(returnable());
					data = [];
					errors = [];
				}
			};

			/** Sets the abort flag */
			this.abort = function()
			{
				aborted = true;
			};

			/** Gets the cursor position */
			this.getCharIndex = function()
			{
				return cursor;
			};
		}


		// If you need to load Papa Parse asynchronously and you also need worker threads, hard-code
		// the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358
		function getScriptPath()
		{
			var scripts = document.getElementsByTagName('script');
			return scripts.length ? scripts[scripts.length - 1].src : '';
		}

		function newWorker()
		{
			if (!Papa.WORKERS_SUPPORTED)
				return false;
			if (!LOADED_SYNC && Papa.SCRIPT_PATH === null)
				throw new Error(
					'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' +
					'You need to set Papa.SCRIPT_PATH manually.'
				);
			var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH;
			// Append 'papaworker' to the search string to tell papaparse that this is our worker.
			workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker';
			var w = new global.Worker(workerUrl);
			w.onmessage = mainThreadReceivedMessage;
			w.id = workerIdCounter++;
			workers[w.id] = w;
			return w;
		}

		/** Callback when main thread receives a message */
		function mainThreadReceivedMessage(e)
		{
			var msg = e.data;
			var worker = workers[msg.workerId];
			var aborted = false;

			if (msg.error)
				worker.userError(msg.error, msg.file);
			else if (msg.results && msg.results.data)
			{
				var abort = function() {
					aborted = true;
					completeWorker(msg.workerId, { data: [], errors: [], meta: { aborted: true } });
				};

				var handle = {
					abort: abort,
					pause: notImplemented,
					resume: notImplemented
				};

				if (isFunction(worker.userStep))
				{
					for (var i = 0; i < msg.results.data.length; i++)
					{
						worker.userStep({
							data: [msg.results.data[i]],
							errors: msg.results.errors,
							meta: msg.results.meta
						}, handle);
						if (aborted)
							break;
					}
					delete msg.results;	// free memory ASAP
				}
				else if (isFunction(worker.userChunk))
				{
					worker.userChunk(msg.results, handle, msg.file);
					delete msg.results;
				}
			}

			if (msg.finished && !aborted)
				completeWorker(msg.workerId, msg.results);
		}

		function completeWorker(workerId, results) {
			var worker = workers[workerId];
			if (isFunction(worker.userComplete))
				worker.userComplete(results);
			worker.terminate();
			delete workers[workerId];
		}

		function notImplemented() {
			throw 'Not implemented.';
		}

		/** Callback when worker thread receives a message */
		function workerThreadReceivedMessage(e)
		{
			var msg = e.data;

			if (typeof Papa.WORKER_ID === 'undefined' && msg)
				Papa.WORKER_ID = msg.workerId;

			if (typeof msg.input === 'string')
			{
				global.postMessage({
					workerId: Papa.WORKER_ID,
					results: Papa.parse(msg.input, msg.config),
					finished: true
				});
			}
			else if ((global.File && msg.input instanceof File) || msg.input instanceof Object)	// thank you, Safari (see issue #106)
			{
				var results = Papa.parse(msg.input, msg.config);
				if (results)
					global.postMessage({
						workerId: Papa.WORKER_ID,
						results: results,
						finished: true
					});
			}
		}

		/** Makes a deep copy of an array or object (mostly) */
		function copy(obj)
		{
			if (typeof obj !== 'object')
				return obj;
			var cpy = obj instanceof Array ? [] : {};
			for (var key in obj)
				cpy[key] = copy(obj[key]);
			return cpy;
		}

		function bindFunction(f, self)
		{
			return function() { f.apply(self, arguments); };
		}

		function isFunction(func)
		{
			return typeof func === 'function';
		}

		return Papa;
	}));
	});
	var papaparse_1 = papaparse.parse;

	function parseObject(obj) {
	    for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) {
	        var key = _a[_i];
	        if (obj[key] === '' || obj[key] === null || obj[key] === undefined) {
	            // delete null key
	            delete obj[key];
	        }
	        else if ((obj[key] + '').toLowerCase() === 'true') {
	            // boolean TRUE
	            obj[key] = true;
	        }
	        else if ((obj[key] + '').toLowerCase() === 'false') {
	            // boolean FALSE
	            obj[key] = false;
	        }
	        else if (!isNaN(obj[key])) {
	            // number
	            obj[key] = Number(obj[key]);
	        }
	        else if ((obj[key].substr(0, 1) === '{' && obj[key].substr(-1) === '}') ||
	            (obj[key].substr(0, 1) === '[' && obj[key].substr(-1) === ']')) {
	            // JSON
	            try {
	                obj[key] = JSON.parse(obj[key]);
	            }
	            catch (e) {
	                // continue
	            }
	        }
	    }
	    return obj;
	}

	var __assign = (undefined && undefined.__assign) || function () {
	    __assign = Object.assign || function(t) {
	        for (var s, i = 1, n = arguments.length; i < n; i++) {
	            s = arguments[i];
	            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
	                t[p] = s[p];
	        }
	        return t;
	    };
	    return __assign.apply(this, arguments);
	};
	var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
	    return new (P || (P = Promise))(function (resolve, reject) {
	        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
	        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
	        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
	        step((generator = generator.apply(thisArg, _arguments || [])).next());
	    });
	};
	var __generator = (undefined && undefined.__generator) || function (thisArg, body) {
	    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
	    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
	    function verb(n) { return function (v) { return step([n, v]); }; }
	    function step(op) {
	        if (f) throw new TypeError("Generator is already executing.");
	        while (_) try {
	            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
	            if (y = 0, t) op = [op[0] & 2, t.value];
	            switch (op[0]) {
	                case 0: case 1: t = op; break;
	                case 4: _.label++; return { value: op[1], done: false };
	                case 5: _.label++; y = op[1]; op = [0]; continue;
	                case 7: op = _.ops.pop(); _.trys.pop(); continue;
	                default:
	                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
	                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
	                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
	                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
	                    if (t[2]) _.ops.pop();
	                    _.trys.pop(); continue;
	            }
	            op = body.call(thisArg, _);
	        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
	        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
	    }
	};
	var DatabaseDirectService = /** @class */ (function () {
	    function DatabaseDirectService(app) {
	        this.app = app;
	    }
	    DatabaseDirectService.prototype.all = function (sheet, cacheTime) {
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter(this, void 0, void 0, function () {
	            var _this = this;
	            return __generator(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.app.Cache.getRefresh('database_' + sheet, cacheTime, function () { return __awaiter(_this, void 0, void 0, function () {
	                            var csvText, items, i, l;
	                            return __generator(this, function (_a) {
	                                switch (_a.label) {
	                                    case 0: return [4 /*yield*/, this.app.Fetch.get(this.csvUrl(sheet), {}, { json: false })];
	                                    case 1:
	                                        csvText = _a.sent();
	                                        return [4 /*yield*/, this.parseCSV(csvText)];
	                                    case 2:
	                                        items = _a.sent();
	                                        // process items
	                                        for (i = 0, l = items.length; i < l; i++) {
	                                            items[i]['_row'] = i + 2;
	                                            parseObject(items[i]);
	                                        }
	                                        return [2 /*return*/, items];
	                                }
	                            });
	                        }); })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseDirectService.prototype.content = function (url, styles, cacheTime) {
	        if (styles === void 0) { styles = 'clean'; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter(this, void 0, void 0, function () {
	            var publishedId, content;
	            var _this = this;
	            return __generator(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        publishedId = url
	                            .replace('/pub', '')
	                            .split('/')
	                            .pop();
	                        return [4 /*yield*/, this.app.Cache.getRefresh('content_' + publishedId + '_' + styles, cacheTime, function () { return __awaiter(_this, void 0, void 0, function () {
	                                var html;
	                                return __generator(this, function (_a) {
	                                    switch (_a.label) {
	                                        case 0: return [4 /*yield*/, this.app.Fetch.get(url + '?embedded=true', {}, { json: false })];
	                                        case 1:
	                                            html = _a.sent();
	                                            return [4 /*yield*/, this.parseContent(html, styles)];
	                                        case 2: 
	                                        // parse
	                                        return [2 /*return*/, _a.sent()];
	                                    }
	                                });
	                            }); })];
	                    case 1:
	                        content = _a.sent();
	                        return [2 /*return*/, { content: content }];
	                }
	            });
	        });
	    };
	    DatabaseDirectService.prototype.csvUrl = function (sheet) {
	        var _a = this.app.options, databaseId = _a.databaseId, databaseGids = _a.databaseGids;
	        return "https://docs.google.com/spreadsheets/d/"
	            + databaseId +
	            "/pub?gid="
	            + databaseGids[sheet] +
	            "&single=true&output=csv";
	    };
	    DatabaseDirectService.prototype.parseCSV = function (csv) {
	        return new Promise(function (resolve, reject) {
	            papaparse_1(csv, {
	                header: true,
	                complete: function (result) { return !result.errors.length ? resolve(result.data) : reject(result.errors); },
	            });
	        });
	    };
	    DatabaseDirectService.prototype.getContentClassStyles = function (html) {
	        // copy class to inline
	        var classGroups = {};
	        var classes = {};
	        // extract classes
	        var classStrs = html.match(/class\=\"(.*?)\"/g);
	        if (!!classStrs) {
	            for (var i = 0, l = classStrs.length; i < l; i++) {
	                var classStr = classStrs[i].match(/class\=\"(.*?)\"/);
	                if (!!classStr) {
	                    var classNamesStr = classStr.pop();
	                    // add to classGroups
	                    if (classNamesStr.indexOf(' ') > -1) {
	                        classGroups[classNamesStr] = '';
	                    }
	                    // add to classes
	                    var classNames = classNamesStr.split(' ').filter(Boolean);
	                    for (var j = 0, lj = classNames.length; j < lj; j++) {
	                        classes[classNames[j]] = '';
	                    }
	                }
	            }
	        }
	        // get class styles
	        for (var _i = 0, _a = Object.keys(classes); _i < _a.length; _i++) {
	            var className = _a[_i];
	            var stylesMatch = html.match(new RegExp('.' + className + '{(.*?)}'));
	            // extract styles
	            if (!!stylesMatch) {
	                classes[className] = stylesMatch.pop().replace(/\"/g, '\'') + ';';
	            }
	        }
	        // get group styles
	        for (var _b = 0, _c = Object.keys(classGroups); _b < _c.length; _b++) {
	            var classGroup = _c[_b];
	            var groupStyles = '';
	            var classNames = classGroup.split(' ').filter(Boolean);
	            for (var i = 0, l = classNames.length; i < l; i++) {
	                groupStyles = groupStyles + classes[classNames[i]];
	            }
	            // save styles to group
	            classGroups[classGroup] = groupStyles;
	        }
	        return __assign({}, classGroups, classes);
	    };
	    DatabaseDirectService.prototype.parseContent = function (html, styles) {
	        if (styles === void 0) { styles = 'clean'; }
	        var content = html; // original
	        if (styles !== 'original') {
	            // extract content, between: </head></html>
	            content = html.match(/\<\/head\>(.*)\<\/html\>/).pop();
	            // clean up
	            content = content
	                .replace(/\<body(.*?)\>/, '') // replace: <body...>
	                .replace('</body>', '') // replace </body>
	                .replace(/\<script(.*?)\<\/script\>/g, '') // remove all script tag
	                .replace(/\<style(.*?)\<\/style\>/g, ''); // remove all style tag
	            // replace redirect links
	            var links = content.match(/\"https\:\/\/www\.google\.com\/url\?q\=(.*?)\"/g);
	            if (!!links) {
	                for (var i = 0, l = links.length; i < l; i++) {
	                    var link = links[i];
	                    var urlMatch = link.match(/\"https\:\/\/www\.google\.com\/url\?q\=(.*?)\&amp\;/);
	                    if (!!urlMatch) {
	                        var url = urlMatch.pop();
	                        content = content.replace(link, '"' + url + '"');
	                    }
	                }
	            }
	            // styles
	            if (styles === 'full') { // full
	                // move class styles to inline
	                var classStyles = this.getContentClassStyles(html);
	                for (var _i = 0, _a = Object.keys(classStyles); _i < _a.length; _i++) {
	                    var key = _a[_i];
	                    content = content.replace(new RegExp('class="' + key + '"', 'g'), 'style="' + classStyles[key] + '"');
	                }
	                // TODO: move tag styles to inline
	            }
	            else { //clean
	                // remove all attributes
	                var removeAttrs = ['style', 'id', 'class', 'width', 'height'];
	                for (var i = 0, l = removeAttrs.length; i < l; i++) {
	                    content = content.replace(new RegExp('\ ' + removeAttrs[i] + '\=\"(.*?)\"', 'g'), '');
	                }
	            }
	        }
	        return content;
	    };
	    return DatabaseDirectService;
	}());

	/*
	* Add integers, wrapping at 2^32. This uses 16-bit operations internally
	* to work around bugs in some JS interpreters.
	*/
	function safeAdd(x, y) {
	    var lsw = (x & 0xffff) + (y & 0xffff);
	    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
	    return (msw << 16) | (lsw & 0xffff);
	}
	/*
	* Bitwise rotate a 32-bit number to the left.
	*/
	function bitRotateLeft(num, cnt) {
	    return (num << cnt) | (num >>> (32 - cnt));
	}
	/*
	* These functions implement the four basic operations the algorithm uses.
	*/
	function md5cmn(q, a, b, x, s, t) {
	    return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
	}
	function md5ff(a, b, c, d, x, s, t) {
	    return md5cmn((b & c) | (~b & d), a, b, x, s, t);
	}
	function md5gg(a, b, c, d, x, s, t) {
	    return md5cmn((b & d) | (c & ~d), a, b, x, s, t);
	}
	function md5hh(a, b, c, d, x, s, t) {
	    return md5cmn(b ^ c ^ d, a, b, x, s, t);
	}
	function md5ii(a, b, c, d, x, s, t) {
	    return md5cmn(c ^ (b | ~d), a, b, x, s, t);
	}
	/*
	* Calculate the MD5 of an array of little-endian words, and a bit length.
	*/
	function binlMD5(x, len) {
	    /* append padding */
	    x[len >> 5] |= 0x80 << (len % 32);
	    x[((len + 64) >>> 9 << 4) + 14] = len;
	    var i;
	    var olda;
	    var oldb;
	    var oldc;
	    var oldd;
	    var a = 1732584193;
	    var b = -271733879;
	    var c = -1732584194;
	    var d = 271733878;
	    for (i = 0; i < x.length; i += 16) {
	        olda = a;
	        oldb = b;
	        oldc = c;
	        oldd = d;
	        a = md5ff(a, b, c, d, x[i], 7, -680876936);
	        d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
	        c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
	        b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
	        a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
	        d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
	        c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
	        b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
	        a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
	        d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
	        c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
	        b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
	        a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
	        d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
	        c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
	        b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
	        a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
	        d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
	        c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
	        b = md5gg(b, c, d, a, x[i], 20, -373897302);
	        a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
	        d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
	        c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
	        b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
	        a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
	        d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
	        c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
	        b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
	        a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
	        d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
	        c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
	        b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
	        a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
	        d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
	        c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
	        b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
	        a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
	        d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
	        c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
	        b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
	        a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
	        d = md5hh(d, a, b, c, x[i], 11, -358537222);
	        c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
	        b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
	        a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
	        d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
	        c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
	        b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
	        a = md5ii(a, b, c, d, x[i], 6, -198630844);
	        d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
	        c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
	        b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
	        a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
	        d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
	        c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
	        b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
	        a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
	        d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
	        c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
	        b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
	        a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
	        d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
	        c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
	        b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
	        a = safeAdd(a, olda);
	        b = safeAdd(b, oldb);
	        c = safeAdd(c, oldc);
	        d = safeAdd(d, oldd);
	    }
	    return [a, b, c, d];
	}
	/*
	* Convert an array of little-endian words to a string
	*/
	function binl2rstr(input) {
	    var i;
	    var output = '';
	    var length32 = input.length * 32;
	    for (i = 0; i < length32; i += 8) {
	        output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff);
	    }
	    return output;
	}
	/*
	* Convert a raw string to an array of little-endian words
	* Characters >255 have their high-byte silently ignored.
	*/
	function rstr2binl(input) {
	    var i;
	    var output = [];
	    output[(input.length >> 2) - 1] = undefined;
	    for (i = 0; i < output.length; i += 1) {
	        output[i] = 0;
	    }
	    var length8 = input.length * 8;
	    for (i = 0; i < length8; i += 8) {
	        output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32);
	    }
	    return output;
	}
	/*
	* Calculate the MD5 of a raw string
	*/
	function rstrMD5(s) {
	    return binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
	}
	/*
	* Calculate the HMAC-MD5, of a key and some data (raw strings)
	*/
	function rstrHMACMD5(key, data) {
	    var i;
	    var bkey = rstr2binl(key);
	    var ipad = [];
	    var opad = [];
	    var hash;
	    ipad[15] = opad[15] = undefined;
	    if (bkey.length > 16) {
	        bkey = binlMD5(bkey, key.length * 8);
	    }
	    for (i = 0; i < 16; i += 1) {
	        ipad[i] = bkey[i] ^ 0x36363636;
	        opad[i] = bkey[i] ^ 0x5c5c5c5c;
	    }
	    hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
	    return binl2rstr(binlMD5(opad.concat(hash), 512 + 128));
	}
	/*
	* Convert a raw string to a hex string
	*/
	function rstr2hex(input) {
	    var hexTab = '0123456789abcdef';
	    var output = '';
	    var x;
	    var i;
	    for (i = 0; i < input.length; i += 1) {
	        x = input.charCodeAt(i);
	        output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
	    }
	    return output;
	}
	/*
	* Encode a string as utf-8
	*/
	function str2rstrUTF8(input) {
	    return unescape(encodeURIComponent(input));
	}
	/*
	* Take string arguments and return either raw or hex encoded strings
	*/
	function rawMD5(s) {
	    return rstrMD5(str2rstrUTF8(s));
	}
	function hexMD5(s) {
	    return rstr2hex(rawMD5(s));
	}
	function rawHMACMD5(k, d) {
	    return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d));
	}
	function hexHMACMD5(k, d) {
	    return rstr2hex(rawHMACMD5(k, d));
	}
	function md5(str, key, raw) {
	    if (raw === void 0) { raw = false; }
	    if (!key) {
	        if (!raw) {
	            return hexMD5(str);
	        }
	        return rawMD5(str);
	    }
	    if (!raw) {
	        return hexHMACMD5(key, str);
	    }
	    return rawHMACMD5(key, str);
	}

	var __assign$1 = (undefined && undefined.__assign) || function () {
	    __assign$1 = Object.assign || function(t) {
	        for (var s, i = 1, n = arguments.length; i < n; i++) {
	            s = arguments[i];
	            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
	                t[p] = s[p];
	        }
	        return t;
	    };
	    return __assign$1.apply(this, arguments);
	};
	var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
	    return new (P || (P = Promise))(function (resolve, reject) {
	        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
	        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
	        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
	        step((generator = generator.apply(thisArg, _arguments || [])).next());
	    });
	};
	var __generator$1 = (undefined && undefined.__generator) || function (thisArg, body) {
	    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
	    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
	    function verb(n) { return function (v) { return step([n, v]); }; }
	    function step(op) {
	        if (f) throw new TypeError("Generator is already executing.");
	        while (_) try {
	            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
	            if (y = 0, t) op = [op[0] & 2, t.value];
	            switch (op[0]) {
	                case 0: case 1: t = op; break;
	                case 4: _.label++; return { value: op[1], done: false };
	                case 5: _.label++; y = op[1]; op = [0]; continue;
	                case 7: op = _.ops.pop(); _.trys.pop(); continue;
	                default:
	                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
	                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
	                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
	                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
	                    if (t[2]) _.ops.pop();
	                    _.trys.pop(); continue;
	            }
	            op = body.call(thisArg, _);
	        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
	        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
	    }
	};
	var DatabaseServerService = /** @class */ (function () {
	    function DatabaseServerService(app) {
	        this.app = app;
	        this.Api = this.app.Api
	            .extend()
	            .setEndpoint(this.app.options.authEndpoint || 'database');
	    }
	    DatabaseServerService.prototype.all = function (sheet, cacheTime) {
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$1(this, void 0, void 0, function () {
	            var _this = this;
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.app.Cache.getRefresh('database_' + sheet, cacheTime, function () { return __awaiter$1(_this, void 0, void 0, function () { return __generator$1(this, function (_a) {
	                            switch (_a.label) {
	                                case 0: return [4 /*yield*/, this.Api.get('/', { sheet: sheet })];
	                                case 1: return [2 /*return*/, _a.sent()];
	                            }
	                        }); }); })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.query = function (sheet, query, cacheTime) {
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$1(this, void 0, void 0, function () {
	            var _this = this;
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.app.Cache.getRefresh('database_' + sheet + '_query_' + md5(JSON.stringify(query)), cacheTime, function () { return __awaiter$1(_this, void 0, void 0, function () { return __generator$1(this, function (_a) {
	                            switch (_a.label) {
	                                case 0: return [4 /*yield*/, this.Api.get('/', __assign$1({}, query, { sheet: sheet }))];
	                                case 1: return [2 /*return*/, _a.sent()];
	                            }
	                        }); }); })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.item = function (sheet, key, cacheTime) {
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$1(this, void 0, void 0, function () {
	            var _this = this;
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.app.Cache.getRefresh('database_' + sheet + '_' + key, cacheTime, function () { return __awaiter$1(_this, void 0, void 0, function () { return __generator$1(this, function (_a) {
	                            switch (_a.label) {
	                                case 0: return [4 /*yield*/, this.Api.get('/', { sheet: sheet, key: key })];
	                                case 1: return [2 /*return*/, _a.sent()];
	                            }
	                        }); }); })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.content = function (urlOrDocId, styles, cacheTime) {
	        if (styles === void 0) { styles = 'clean'; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$1(this, void 0, void 0, function () {
	            var docId;
	            var _this = this;
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        docId = urlOrDocId
	                            .replace('https://docs.google.com/document/d/', '')
	                            .split('/')
	                            .shift();
	                        return [4 /*yield*/, this.app.Cache.getRefresh('content_' + docId + '_' + styles, cacheTime, function () { return __awaiter$1(_this, void 0, void 0, function () { return __generator$1(this, function (_a) {
	                                switch (_a.label) {
	                                    case 0: return [4 /*yield*/, this.Api.get('/content', { docId: docId, styles: styles })];
	                                    case 1: return [2 /*return*/, _a.sent()];
	                                }
	                            }); }); })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.set = function (sheet, key, data) {
	        return __awaiter$1(this, void 0, void 0, function () {
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.Api.post('/', {}, { sheet: sheet, key: key, data: data, clean: true })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.update = function (sheet, key, data) {
	        return __awaiter$1(this, void 0, void 0, function () {
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.Api.post('/', {}, { sheet: sheet, key: key, data: data })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.add = function (sheet, key, data) {
	        return __awaiter$1(this, void 0, void 0, function () {
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.update(sheet, key, data)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.remove = function (sheet, key) {
	        return __awaiter$1(this, void 0, void 0, function () {
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.update(sheet, key, null)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseServerService.prototype.increase = function (sheet, key, increasing) {
	        return __awaiter$1(this, void 0, void 0, function () {
	            return __generator$1(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.Api.post('/', {}, { sheet: sheet, key: key, increasing: increasing })];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    return DatabaseServerService;
	}());

	function buildQuery(filter) {
	    if (!filter['where']) { // shorthand query
	        var where = Object.keys(filter)[0];
	        var equal = filter[where];
	        delete filter[where]; // remove shorthand value from filter
	        filter['where'] = where;
	        filter['equal'] = equal;
	    }
	    return filter;
	}
	function buildAdvancedFilter(query) {
	    var advancedFilter;
	    // build advanced filter
	    var _a = query, where = _a.where, equal = _a.equal, exists = _a.exists, contains = _a.contains, lt = _a.lt, lte = _a.lte, gt = _a.gt, gte = _a.gte, childExists = _a.childExists, childEqual = _a.childEqual;
	    if (!!equal) { // where/equal
	        advancedFilter = function (item) { return (!!item[where] && item[where] === equal); };
	    }
	    else if (typeof exists === 'boolean') { // where/exists/not exists
	        advancedFilter = function (item) { return (!!exists ? !!item[where] : !item[where]); };
	    }
	    else if (!!contains) { // where/contains
	        advancedFilter = function (item) { return (!!item[where] &&
	            typeof item[where] === 'string' &&
	            item[where].indexOf(contains) > -1); };
	    }
	    else if (!!lt) { // where/less than
	        advancedFilter = function (item) { return (!!item[where] &&
	            typeof item[where] === 'number' &&
	            item[where] < lt); };
	    }
	    else if (!!lte) { // where/less than or equal
	        advancedFilter = function (item) { return (!!item[where] &&
	            typeof item[where] === 'number' &&
	            item[where] <= lte); };
	    }
	    else if (!!gt) { // where/greater than
	        advancedFilter = function (item) { return (!!item[where] &&
	            typeof item[where] === 'number' &&
	            item[where] > gt); };
	    }
	    else if (!!gte) { // where/greater than or equal
	        advancedFilter = function (item) { return (!!item[where] &&
	            typeof item[where] === 'number' &&
	            item[where] >= gte); };
	    }
	    else if (!!childExists) { // where/child exists, not exists
	        var notExists_1 = childExists.substr(0, 1) === '!';
	        var child_1 = notExists_1 ? childExists.replace('!', '') : childExists;
	        advancedFilter = function (item) {
	            if (!item[where] && notExists_1) {
	                return true; // child always not exists
	            }
	            else if (!!item[where]) {
	                if (item[where] instanceof Array) {
	                    return notExists_1 ?
	                        (item[where].indexOf(child_1) < 0) :
	                        (item[where].indexOf(child_1) > -1);
	                }
	                else if (item[where] instanceof Object) {
	                    return notExists_1 ? !item[where][child_1] : !!item[where][child_1];
	                }
	            }
	            return false;
	        };
	    }
	    else if (!!childEqual) { // where/child equal, not equal
	        var notEqual_1;
	        var childKey_1;
	        var childValue_1;
	        if (childEqual.indexOf('!=') > -1) {
	            notEqual_1 = true;
	            var keyValue = childEqual.split('!=').filter(Boolean);
	            childKey_1 = keyValue[0];
	            childValue_1 = keyValue[1];
	        }
	        else {
	            var keyValue = childEqual.split('=').filter(Boolean);
	            childKey_1 = keyValue[0];
	            childValue_1 = keyValue[1];
	        }
	        advancedFilter = function (item) {
	            if (!item[where] && notEqual_1) {
	                return true; // always not equal
	            }
	            else if (!!item[where]) {
	                return (item[where] instanceof Object &&
	                    (notEqual_1 ?
	                        (!item[where][childKey_1] || item[where][childKey_1] !== childValue_1) :
	                        (!!item[where][childKey_1] && item[where][childKey_1] === childValue_1)));
	            }
	            return false;
	        };
	    }
	    return advancedFilter;
	}

	var __assign$2 = (undefined && undefined.__assign) || function () {
	    __assign$2 = Object.assign || function(t) {
	        for (var s, i = 1, n = arguments.length; i < n; i++) {
	            s = arguments[i];
	            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
	                t[p] = s[p];
	        }
	        return t;
	    };
	    return __assign$2.apply(this, arguments);
	};
	var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
	    return new (P || (P = Promise))(function (resolve, reject) {
	        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
	        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
	        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
	        step((generator = generator.apply(thisArg, _arguments || [])).next());
	    });
	};
	var __generator$2 = (undefined && undefined.__generator) || function (thisArg, body) {
	    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
	    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
	    function verb(n) { return function (v) { return step([n, v]); }; }
	    function step(op) {
	        if (f) throw new TypeError("Generator is already executing.");
	        while (_) try {
	            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
	            if (y = 0, t) op = [op[0] & 2, t.value];
	            switch (op[0]) {
	                case 0: case 1: t = op; break;
	                case 4: _.label++; return { value: op[1], done: false };
	                case 5: _.label++; y = op[1]; op = [0]; continue;
	                case 7: op = _.ops.pop(); _.trys.pop(); continue;
	                default:
	                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
	                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
	                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
	                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
	                    if (t[2]) _.ops.pop();
	                    _.trys.pop(); continue;
	            }
	            op = body.call(thisArg, _);
	        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
	        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
	    }
	};
	var DatabaseService = /** @class */ (function () {
	    function DatabaseService(app) {
	        this.app = app;
	        this.DatabaseDirect = new DatabaseDirectService(this.app);
	        this.DatabaseServer = new DatabaseServerService(this.app);
	    }
	    DatabaseService.prototype.direct = function () {
	        return this.DatabaseDirect;
	    };
	    DatabaseService.prototype.server = function () {
	        return this.DatabaseServer;
	    };
	    DatabaseService.prototype.all = function (sheet, cacheTime) {
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$2(this, void 0, void 0, function () {
	            var items, error_1;
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        items = [];
	                        if (!this.isDirect(sheet)) return [3 /*break*/, 4];
	                        _a.label = 1;
	                    case 1:
	                        _a.trys.push([1, 3, , 4]);
	                        return [4 /*yield*/, this.direct().all(sheet, cacheTime)];
	                    case 2:
	                        items = _a.sent();
	                        return [3 /*break*/, 4];
	                    case 3:
	                        error_1 = _a.sent();
	                        return [3 /*break*/, 4];
	                    case 4:
	                        if (!!items) return [3 /*break*/, 6];
	                        return [4 /*yield*/, this.server().all(sheet, cacheTime)];
	                    case 5:
	                        items = _a.sent();
	                        _a.label = 6;
	                    case 6: return [2 /*return*/, items];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.query = function (sheet, filter, local, cacheTime) {
	        if (local === void 0) { local = true; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$2(this, void 0, void 0, function () {
	            var query, advancedFilter, localItems, items, i, length_1, item;
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        if (filter instanceof Function) {
	                            advancedFilter = filter;
	                        }
	                        else {
	                            query = buildQuery(filter);
	                        }
	                        if (!local) return [3 /*break*/, 2];
	                        // turn simple query into advanced query
	                        if (!advancedFilter) {
	                            advancedFilter = buildAdvancedFilter(query);
	                        }
	                        return [4 /*yield*/, this.all(sheet, cacheTime)];
	                    case 1:
	                        localItems = _a.sent();
	                        items = [];
	                        for (i = 0, length_1 = localItems.length; i < length_1; i++) {
	                            item = localItems[i];
	                            if (!!advancedFilter(item)) {
	                                items.push(item);
	                            }
	                        }
	                        return [2 /*return*/, items];
	                    case 2:
	                        if (!!advancedFilter) return [3 /*break*/, 4];
	                        return [4 /*yield*/, this.server().query(sheet, query, cacheTime)];
	                    case 3: return [2 /*return*/, _a.sent()];
	                    case 4: throw new Error('Can only apply advanced query with local data.');
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.items = function (sheet, filter, local, cacheTime) {
	        if (local === void 0) { local = true; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                return [2 /*return*/, !!filter ? this.query(sheet, filter, local, cacheTime) : this.all(sheet, cacheTime)];
	            });
	        });
	    };
	    DatabaseService.prototype.item = function (sheet, finder, local, cacheTime) {
	        if (local === void 0) { local = true; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$2(this, void 0, void 0, function () {
	            var item, items;
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        item = null;
	                        if (!(typeof finder === 'string' && !local)) return [3 /*break*/, 2];
	                        return [4 /*yield*/, this.server().item(sheet, finder, cacheTime)];
	                    case 1:
	                        item = _a.sent();
	                        return [3 /*break*/, 4];
	                    case 2:
	                        // turn string into finder
	                        if (typeof finder === 'string') {
	                            finder = { $key: finder };
	                        }
	                        return [4 /*yield*/, this.query(sheet, finder, local, cacheTime)];
	                    case 3:
	                        items = _a.sent();
	                        // extract item
	                        if ((items || []).length === 1) {
	                            item = items[0];
	                        }
	                        _a.label = 4;
	                    case 4: return [2 /*return*/, item];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.content = function (contentSource, // doc url | published url
	    styles, cacheTime) {
	        if (styles === void 0) { styles = 'clean'; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        if (!this.isDocsContentSourceNotDirectly(contentSource)) return [3 /*break*/, 2];
	                        return [4 /*yield*/, this.server().content(contentSource, styles, cacheTime)];
	                    case 1: // server
	                    return [2 /*return*/, _a.sent()];
	                    case 2: return [4 /*yield*/, this.direct().content(contentSource, styles, cacheTime)];
	                    case 3: // direct
	                    return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.itemAndContent = function (sheet, finder, item, // loaded item where not sure there is content or not
	    styles, local, cacheTime) {
	        if (styles === void 0) { styles = 'clean'; }
	        if (local === void 0) { local = true; }
	        if (cacheTime === void 0) { cacheTime = 0; }
	        return __awaiter$2(this, void 0, void 0, function () {
	            var contentData;
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        if (!!item) return [3 /*break*/, 2];
	                        return [4 /*yield*/, this.item(sheet, finder, local, cacheTime)];
	                    case 1:
	                        item = _a.sent();
	                        _a.label = 2;
	                    case 2:
	                        if (!(!!item &&
	                            !item['content'] &&
	                            !!item['contentSource'] &&
	                            this.isContentSourceFromDocs(item['contentSource']))) return [3 /*break*/, 4];
	                        return [4 /*yield*/, this.content(item['contentSource'], styles, cacheTime)];
	                    case 3:
	                        contentData = _a.sent();
	                        // merge content to item
	                        item = __assign$2({}, item, contentData);
	                        _a.label = 4;
	                    case 4: 
	                    // return final item
	                    return [2 /*return*/, item];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.set = function (sheet, key, data) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.server().set(sheet, key, data)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.update = function (sheet, key, data) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.server().update(sheet, key, data)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.add = function (sheet, key, data) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.update(sheet, key, data)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.remove = function (sheet, key) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.update(sheet, key, null)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.increase = function (sheet, key, increasing) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0: return [4 /*yield*/, this.server().increase(sheet, key, increasing)];
	                    case 1: return [2 /*return*/, _a.sent()];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.clearCacheAll = function (input) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            var i;
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0:
	                        // turn string to string[]
	                        if (typeof input === 'string') {
	                            input = [input];
	                        }
	                        i = 0;
	                        _a.label = 1;
	                    case 1:
	                        if (!(i < input.length)) return [3 /*break*/, 4];
	                        return [4 /*yield*/, this.app.Cache.removeByPrefix('database_' + input[i])];
	                    case 2:
	                        _a.sent();
	                        _a.label = 3;
	                    case 3:
	                        i++;
	                        return [3 /*break*/, 1];
	                    case 4: return [2 /*return*/];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.clearCacheItem = function (sheet, item) {
	        return __awaiter$2(this, void 0, void 0, function () {
	            var input, id;
	            return __generator$2(this, function (_a) {
	                switch (_a.label) {
	                    case 0: 
	                    // clear all associated data
	                    return [4 /*yield*/, this.clearCacheAll(sheet)];
	                    case 1:
	                        // clear all associated data
	                        _a.sent();
	                        input = item['contentSource'];
	                        if (!this.isContentSourceFromDocs(input)) return [3 /*break*/, 5];
	                        id = '';
	                        if (this.isDocsContentSourceNotDirectly(input)) {
	                            id = input.replace('https://docs.google.com/document/d/', '').split('/').shift();
	                        }
	                        else {
	                            id = input.replace('/pub', '').split('/').pop();
	                        }
	                        return [4 /*yield*/, this.app.Cache.removeByPrefix('content_' + id + '_clean')];
	                    case 2:
	                        _a.sent();
	                        return [4 /*yield*/, this.app.Cache.removeByPrefix('content_' + id + '_full')];
	                    case 3:
	                        _a.sent();
	                        return [4 /*yield*/, this.app.Cache.removeByPrefix('content_' + id + '_original')];
	                    case 4:
	                        _a.sent();
	                        _a.label = 5;
	                    case 5: return [2 /*return*/];
	                }
	            });
	        });
	    };
	    DatabaseService.prototype.isDirect = function (sheet) {
	        var _a = this.app.options, databaseId = _a.databaseId, databaseGids = _a.databaseGids;
	        return !!databaseId && !!databaseGids && !!databaseGids[sheet];
	    };
	    DatabaseService.prototype.isContentSourceFromDocs = function (input) {
	        // must be: not custom | doc url | published url
	        return (
	        // not intended custom source
	        input.indexOf('from:') < 0 &&
	            // doc url | published url
	            input.indexOf('https://docs.google.com/document/d/') > -1);
	    };
	    DatabaseService.prototype.isDocsContentSourceNotDirectly = function (input) {
	        return (input.indexOf('https://docs.google.com/document/d/e/') < 0 &&
	            input.indexOf('/pub') < 0);
	    };
	    return DatabaseService;
	}());

	function database(app) {
	    // get the default app
	    if (!app) {
	        if (!window['$$$SHEETBASE_APPS']) {
	            throw new Error('No app for database component.');
	        }
	        app = window['$$$SHEETBASE_APPS'].getApp();
	    }
	    // return the instance
	    if (!!app.Database) {
	        return app.Database;
	    }
	    else {
	        return new DatabaseService(app);
	    }
	}
	window['$$$SHEETBASE_COMPONENTS'].Database = DatabaseService;

	return database;

}));
//# sourceMappingURL=sheetbase-database.js.map
