<commit_before>/*

Licensed under The MIT License
==============================

Copyright (c) 2010 Fireworks Technology Projects Inc.
[www.fireworksproject.com](http://www.fireworksproject.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

/*jslint
onevar: true,
undef: true,
nomen: true,
eqeqeq: true,
plusplus: true,
bitwise: true,
regexp: true,
strict: true,
newcap: false,
immed: true
*/

/*global
Components: false,
dump: false
*/

"use strict";

///////////////////////////////////////////////////////////////////////////////
// For Mozilla JavaScript modules system.
var EXPORTED_SYMBOLS = ["exports"];

var exports = {};
var module = {id: "dcube"};

///////////////////////////////////////////////////////////////////////////////

// Handy utility.
var setTimeout = (function () {
	var timer = Components.classes["@mozilla.org/timer;1"]
			.createInstance(Components.interfaces.nsITimer);

	return function(fn, time) {
		timer.initWithCallback({notify: fn}, time,
			Components.interfaces.nsITimer.TYPE_ONE_SHOT);
		return timer;
	};
}());


var DEBUG = false,
	DOMAIN = "http://localhost",
	DB,
	LOG,
	ENQ,
	SHA1,
	PROMISE,
	XHR,
	JSONRequest,
	REQ,
	CACHE,
	CXN,
	USER,
	$N = {}, $A = [], $F = function(){},
	isin, isObject, isArray, confirmObject, confirmArray, confirmFunc;

// Used throughout this module to inform the interface of exceptions that are
// not expected.  These exceptions should always be logged internally.
function unexpected_Exception(msg) {
	var self = new Error(msg || "unkown");
	self.name = "DCubeUnexpected";
	self.constructor = arguments.callee;
	return self;
}
exports.unexpectedException = unexpected_Exception;

// Used in the public API anywhere the network protocol is not working as
// expected.
function offline_Exception(msg) {
	var self = new Error(msg || "unexpected");
	self.name = "DCubeOffline";
	self.constructor = offline_Exception;
	return self;
}
exports.offlineException = offline_Exception;

// Used for string validation errors on username, passkey, and database names.
function string_Exception(type, msg) {
	var self = new Error(msg);
	self.name = type +"ValidationError";
	self.constructor = string_Exception;
	return self;
}
exports.stringException = string_Exception;

// Generic string name validation.
function validate_string(str, type, short, long, reg) {
	str = (typeof str === "string") ? str : "";
	var len = str.length;

	if (len < short) {
		throw string_Exception(type, "too short");
	}
	if (len > long) {
		throw string_Exception(type, "too long");
	}
	if (reg.test(str)) {
		throw string_Exception(type, "invalid characters");
	}
	return str;
}

/**
 * Validate a passkey.
 * Must be between 4 and 140 chars and must not contain /[\b\t\v\f\r\n]/.
 */
function validate_passkey(passkey) {
	return validate_string(passkey, "passkey", 4, 140, /[\b\t\v\f\r\n]/);
}
exports.validatePasskey = validate_passkey;

/**
 * Validate a username.
 * Must be between 1 and 70 chars and must not contain /\W/.
 */
function validate_username(username) {
	return validate_string(username, "username", 1, 70, /\W/);
}
exports.validateUsername = validate_username;

/**
 * Validate a database name.
 * Must be between 1 and 70 chars and must not contain /\W/.
 */
function validate_dbname(dbname) {
	return validate_string(dbname, "dbname", 1, 70, /\W/);
}
exports.validateDBName = validate_dbname;


/**
 * Takes an object cantaining logging methods
 * debug(), info(), warn(), error(), and/or critical().
 * Each if the default logging methods that are matched by a replacement method
 * will be replaced.
 */
exports.logger = function update_logger(x) {
	var m;
	for (m in x) {
		if (Object.prototype.hasOwnProperty.call(x, m)) {
			LOG[m] = x[m];
		}
	}
	return exports;
};

/**
 * Toggle debug mode on the fly.
 */
exports.debug = function toggle_debug(x) {
	DEBUG = !!x;
	return exports;
};

/**
 * Set the DCube domain to use on the fly.
 * The string given must NOT contain the protocol ("http://..." for example).
 * If the protocol is included, an error will be thrown.
 *
 * If .domain() is called with no arguments it simply returns the current
 * domain setting.
 */
exports.domain = function domain_accessor(x) {
	if (x && typeof x === "string") {
		if (/^[a-zA-Z]+:\/\//.test(x)) {
			throw string_Exception("URL", "exclude protocol");
		}

		var lastchar = x.length - 1;
		if(x.charAt(lastchar) === "/") {
			x = x.slice(0, lastchar);
		}
		DOMAIN = "http://"+ x;
		return exports;
	}
	return DOMAIN;
};

exports.user = (function () {
	var users = {};

	return function (username, passkey, force) {
		username = validate_username(username);

		if (!users[username] || force) {
			if (typeof passkey !== "function") {
				passkey = validate_passkey(passkey);
			}
			users[username] = USER(username, passkey);
		}
		return users[username];
	};
}());

exports.userExists = function (username) {
	username = validate_username(username);
	return PROMISE(function (fulfill, except, progress) {
		try {
			REQ({
					timeout: 7000,
					dir: "users",
					name: username
				},
				function (response) {
					if (response.head.status === 200 ||
							response.head.status === 401) {
						fulfill(true);
					}
					else if (response.head.status === 404) {
						fulfill(false);
					}
					else {
						LOG.warn(".userExists(); response status: "+
							response.head.status);
						except(offline_Exception());
					}
				},
				function (exception) {
					LOG.warn(".userExists(); "+ exception);
					except(offline_Exception());
				});
		} catch (e) {
			LOG.warn(".userExists(); "+ e);
			except(offline_Exception());
		}
	});
};

exports.connect = (function () {
	var connections = {};

	return function (dbname, username, passkey) {
		dbname = validate_dbname(dbname);
		username = validate_username(username);
		return PROMISE(function (fulfill, except, progress) {
			if (!connections[dbname +":"+ username]) {
				exports.user(username, passkey, true).connect(dbname)(
					function (cxn) {
						fulfill(connections[dbname +":"+ username] = cxn);
					}, except, progress);
			}
			else {
				fulfill(connections[dbname +":"+ username]);
			}
		});
	};
}());

exports.createUser = function pub_createUser(username, passkey) {
	username = validate_username(username);
	passkey = validate_passkey(passkey);

	return PROMISE(function (fulfill, except, progress) {
		progress("creating");
		try {
			REQ({
					timeout: 7000,
					dir: "users",
					name: username,
					method: "put"
				},
				function (response) {
					if (response.head.status === 201) {
						exports.user(username, passkey, true)
							.init(response.head.authorization[1],
								response.head.authorization[2])(
									function (user) {
										user.get(username)(fulfill, except, progress);
									});
					}
					else if (response.head.status === 401) {
						except(new Error("user exists"));
					}
					else {
						LOG.warn(".createUser(); status: "+ response.head.status);
						except(offline_Exception());
					}
				},
				function (exception) {
					LOG.warn(".createUser() "+ exception);
					except(offline_Exception());
				});
		} catch (e) {
			LOG.warn(".createUser() "+ e);
			except(offline_Exception());
		}
	});
};

exports.query = function query_constructor() {
	var self = {}, q = [];

	self.put = function query_put(key, entity, indexes) {
		if (typeof key !== "string" && typeof key !== "number") {
			throw new Error("query.put(); key not a string or number.");
		}
		entity = JSON.stringify(confirmObject(entity));
		indexes = confirmObject(indexes);
		var stmts = [["key", "=", key], ["entity", "=", entity]], idx;
		
		for (idx in indexes) {
			if (indexes.hasOwnProperty(idx)) {
				if (typeof indexes[idx] !== "string" &&
						typeof indexes[idx] !== "number") {
					throw new Error("query.put(); index '"+
							idx +"' not a string or number.");
				}
				stmts.push([idx, "=", indexes[idx]]);
			}
		}

		q.push({action: "put", statements: stmts});
		return self;
	};

	self.query = function query_query() {
		var stmts = [];
		return {
			kind: function q_kind(kind) {
				if (typeof kind !== "string") {
					throw new Error("query.query.kind(); kind() takes a string.");
				}
				return this.eq('kind', kind);
			},

			eq: function q_eq(a, b) {
				if (typeof a !== "string" && typeof a !== "number") {
					throw new Error("query.query.eq(); "+
							"First param not a string or number.");
				}
				if (typeof b !== "string" && typeof b !== "number") {
					throw new Error("query.query.eq(); "+
							"Second param not a string or number.");
				}
				stmts.push([a,"=",b]);
				return this;
			},

			gt: function q_gt(a, b) {
				if (typeof a !== "string" && typeof a !== "number") {
					throw new Error("query.query.gt(); "+
							"First param not a string or number.");
				}
				if (typeof b !== "string" && typeof b !== "number") {
					throw new Error("query.query.gt(); "+
							"Second param not a string or number.");
				}
				stmts.push([a,">",b]);
				return this;
			},

			lt: function q_gt(a, b) {
				if (typeof a !== "string" && typeof a !== "number") {
					throw new Error("query.query.lt(); "+
							"First param not a string or number.");
				}
				if (typeof b !== "string" && typeof b !== "number") {
					throw new Error("query.query.lt(); "+
							"Second param not a string or number.");
				}
				stmts.push([a,"<",b]);
				return this;
			},

			append: function q_append() {
				q.push({action: "query", statements: stmts});
				return self;
			}
		};
	};

	self.get = function query_get(key) {
		if (typeof key !== "string" && typeof key !== "number") {
			throw new Error("query.get(); key not a string or number.");
		}
		q.push({action: "get", statements: [["key", "=", key]]});
		return self;
	};

	self.remove = function query_remove(key) {
		if (typeof key !== "string" && typeof key !== "number") {
			throw new Error("query.remove(); key not a string or number.");
		}
		q.push({action: "delete", statements: [["key", "=", key]]});
		return self;
	};

	self.resolve = function query_resolve() {
		return q;
	};

	return self;
};

DB = (function () {
	var cache = {}, models = {}, uid;

	function entity(spec, mapper) {
		var key = spec.key,
			struct = JSON.parse(JSON.stringify(spec.data)),
			index = JSON.parse(JSON.stringify(spec.indexes)),
			update;

		function update_array(a, b) {
			a = isArray(a) ? a : [];
			var i = 0, len = b.length;
			for (; i < len; i += 1) {
				a[i] = update(a[i], b[i]);
			}
			return a;
		}

		function update_object(a, b) {
			a = isObject(a) ? a : {};
			var p;
			for (p in b) {
				if (isin(b, p)) {
					a[p] = update(a[p], b[p]);
				}
			}
			return a;
		}

		update = function (a, b) {
			if (isArray(b)) {
				return update_array(a, b);
			}
			if (isObject(b)) {
				return update_object(a, b);
			}
			return (a = b);
		};

		return function (method) {
			switch (method) {

			case 'key':
				return key;

			case 'entity':
				// Return a cheap copy.
				return JSON.parse(JSON.stringify(struct));

			case 'indexes':
				// Return a cheap copy.
				return JSON.parse(JSON.stringify(index));

			case 'update':
				struct = mapper(update(struct, arguments[1]), index);
				return JSON.parse(JSON.stringify(struct));

			case 'delete':
				struct = index = null;
				return true;

			default:
				throw new Error('Invalid entity method: '+ method);
			}
		};
	}

	function db(connection) {
		var self = {},
		promised_results = [],
		fulfilled_results = [],
		request = connection.request();

		function create(kind) {
			var ent = models[kind]();
			return (cache[ent('key')] = ent);
		}

		function get(key, cb) {
			if (cache[key]) {
				fulfilled_results.push(function () {
					cb(cache[key]);
				});
			}
			else {
				request.get(key);
				promised_results.push(cb);
			}
			return self;
		}

		function put(ent, cb) {
			request.put(ent('key'), ent('entity'), ent('indexes'));
			promised_results.push(cb);
			return self;
		}

		function del(key, cb) {
			cache[key]('delete');
			delete cache[key];
			request.remove(key);
			promised_results.push(cb);
			return self;
		}

		function query() {
			var q = request.query(), append = q.append;
			q.append = function (cb) {
				append();
				promised_results.push(cb);
				return self;
			};
			return q;
		}

		function update_entity(item, cb) {
			var ent, parsed_entity = JSON.parse(item.entity);

			try {
				if (!cache[item.key]) {
					ent = models[item.indexes.kind](
						item.key, parsed_entity, item.indexes);
					cache[item.key] = ent;
				}
				else {
					cache[item.key]('update', parsed_entity);
				}
			} catch (e) {
				LOG.warn('Unable to handle DCube results for '+
					JSON.stringify(item));
				LOG.error(e);
			}
			cb(cache[item.key] || null);
		}

		function go(errback) {
			var this_results = promised_results,
				this_fulfilled_results = fulfilled_results;

			function make_push_result(results) {
				return function (r) {
					results.push(r);
				};
			}
<commit_msg>Integrate DCube bug fixes.<commit_after>/*

Licensed under The MIT License
==============================

Copyright (c) 2010 Fireworks Technology Projects Inc.
[www.fireworksproject.com](http://www.fireworksproject.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

/*jslint
onevar: true,
undef: true,
nomen: true,
eqeqeq: true,
plusplus: true,
bitwise: true,
regexp: true,
strict: true,
newcap: false,
immed: true
*/

/*global
Components: false,
dump: false
*/

"use strict";

///////////////////////////////////////////////////////////////////////////////
// For Mozilla JavaScript modules system.
var EXPORTED_SYMBOLS = ["exports"];

var exports = {};
var module = {id: "dcube"};

///////////////////////////////////////////////////////////////////////////////

// Handy utility.
var setTimeout = (function () {
	var timer = Components.classes["@mozilla.org/timer;1"]
			.createInstance(Components.interfaces.nsITimer);

	return function(fn, time) {
		timer.initWithCallback({notify: fn}, time,
			Components.interfaces.nsITimer.TYPE_ONE_SHOT);
		return timer;
	};
}());


var DEBUG = false,
	DOMAIN = "http://localhost",
	DB,
	LOG,
	ENQ,
	SHA1,
	PROMISE,
	XHR,
	JSONRequest,
	REQ,
	CACHE,
	CXN,
	USER,
	$N = {}, $A = [], $F = function(){},
	isin, isObject, isArray, confirmObject, confirmArray, confirmFunc;

// Used throughout this module to inform the interface of exceptions that are
// not expected.  These exceptions should always be logged internally.
function unexpected_Exception(msg) {
	var self = new Error(msg || "unkown");
	self.name = "DCubeUnexpected";
	self.constructor = arguments.callee;
	return self;
}
exports.unexpectedException = unexpected_Exception;

// Used in the public API anywhere the network protocol is not working as
// expected.
function offline_Exception(msg) {
	var self = new Error(msg || "unexpected");
	self.name = "DCubeOffline";
	self.constructor = offline_Exception;
	return self;
}
exports.offlineException = offline_Exception;

// Used for string validation errors on username, passkey, and database names.
function string_Exception(type, msg) {
	var self = new Error(msg);
	self.name = type +"ValidationError";
	self.constructor = string_Exception;
	return self;
}
exports.stringException = string_Exception;

// Generic string name validation.
function validate_string(str, type, short, long, reg) {
	str = (typeof str === "string") ? str : "";
	var len = str.length;

	if (len < short) {
		throw string_Exception(type, "too short");
	}
	if (len > long) {
		throw string_Exception(type, "too long");
	}
	if (reg.test(str)) {
		throw string_Exception(type, "invalid characters");
	}
	return str;
}

/**
 * Validate a passkey.
 * Must be between 4 and 140 chars and must not contain /[\b\t\v\f\r\n]/.
 */
function validate_passkey(passkey) {
	return validate_string(passkey, "passkey", 4, 140, /[\b\t\v\f\r\n]/);
}
exports.validatePasskey = validate_passkey;

/**
 * Validate a username.
 * Must be between 1 and 70 chars and must not contain /\W/.
 */
function validate_username(username) {
	return validate_string(username, "username", 1, 70, /\W/);
}
exports.validateUsername = validate_username;

/**
 * Validate a database name.
 * Must be between 1 and 70 chars and must not contain /\W/.
 */
function validate_dbname(dbname) {
	return validate_string(dbname, "dbname", 1, 70, /\W/);
}
exports.validateDBName = validate_dbname;


/**
 * Takes an object cantaining logging methods
 * debug(), info(), warn(), error(), and/or critical().
 * Each if the default logging methods that are matched by a replacement method
 * will be replaced.
 */
exports.logger = function update_logger(x) {
	var m;
	for (m in x) {
		if (Object.prototype.hasOwnProperty.call(x, m)) {
			LOG[m] = x[m];
		}
	}
	return exports;
};

/**
 * Toggle debug mode on the fly.
 */
exports.debug = function toggle_debug(x) {
	DEBUG = !!x;
	return exports;
};

/**
 * Set the DCube domain to use on the fly.
 * The string given must NOT contain the protocol ("http://..." for example).
 * If the protocol is included, an error will be thrown.
 *
 * If .domain() is called with no arguments it simply returns the current
 * domain setting.
 */
exports.domain = function domain_accessor(x) {
	if (x && typeof x === "string") {
		if (/^[a-zA-Z]+:\/\//.test(x)) {
			throw string_Exception("URL", "exclude protocol");
		}

		var lastchar = x.length - 1;
		if(x.charAt(lastchar) === "/") {
			x = x.slice(0, lastchar);
		}
		DOMAIN = "http://"+ x;
		return exports;
	}
	return DOMAIN;
};

exports.user = (function () {
	var users = {};

	return function (username, passkey, force) {
		username = validate_username(username);

		if (!users[username] || force) {
			if (typeof passkey !== "function") {
				passkey = validate_passkey(passkey);
			}
			users[username] = USER(username, passkey);
		}
		return users[username];
	};
}());

exports.userExists = function (username) {
	username = validate_username(username);
	return PROMISE(function (fulfill, except, progress) {
		try {
			REQ({
					timeout: 7000,
					dir: "users",
					name: username
				},
				function (response) {
					if (response.head.status === 200 ||
							response.head.status === 401) {
						fulfill(true);
					}
					else if (response.head.status === 404) {
						fulfill(false);
					}
					else {
						LOG.warn(".userExists(); response status: "+
							response.head.status);
						except(offline_Exception());
					}
				},
				function (exception) {
					LOG.warn(".userExists(); "+ exception);
					except(offline_Exception());
				});
		} catch (e) {
			LOG.warn(".userExists(); "+ e);
			except(offline_Exception());
		}
	});
};

exports.connect = (function () {
	var connections = {};

	return function (dbname, username, passkey) {
		dbname = validate_dbname(dbname);
		username = validate_username(username);
		return PROMISE(function (fulfill, except, progress) {
			if (!connections[dbname +":"+ username]) {
				exports.user(username, passkey, true).connect(dbname)(
					function (cxn) {
						fulfill(connections[dbname +":"+ username] = cxn);
					}, except, progress);
			}
			else {
				fulfill(connections[dbname +":"+ username]);
			}
		});
	};
}());

exports.createUser = function pub_createUser(username, passkey) {
	username = validate_username(username);
	passkey = validate_passkey(passkey);

	return PROMISE(function (fulfill, except, progress) {
		progress("creating");
		try {
			REQ({
					timeout: 7000,
					dir: "users",
					name: username,
					method: "put"
				},
				function (response) {
					if (response.head.status === 201) {
						exports.user(username, passkey, true)
							.init(response.head.authorization[1],
								response.head.authorization[2])(
									function (user) {
										user.get(username)(fulfill, except, progress);
									});
					}
					else if (response.head.status === 401) {
						except(new Error("user exists"));
					}
					else {
						LOG.warn(".createUser(); status: "+ response.head.status);
						except(offline_Exception());
					}
				},
				function (exception) {
					LOG.warn(".createUser() "+ exception);
					except(offline_Exception());
				});
		} catch (e) {
			LOG.warn(".createUser() "+ e);
			except(offline_Exception());
		}
	});
};

exports.query = function query_constructor() {
	var self = {}, q = [];

	self.put = function query_put(key, entity, indexes) {
		if (typeof key !== "string" && typeof key !== "number") {
			throw new Error("query.put(); key not a string or number.");
		}
		entity = JSON.stringify(confirmObject(entity));
		indexes = confirmObject(indexes);
		var stmts = [["key", "=", key], ["entity", "=", entity]], idx;
		
		for (idx in indexes) {
			if (indexes.hasOwnProperty(idx)) {
				if (typeof indexes[idx] !== "string" &&
						typeof indexes[idx] !== "number") {
					throw new Error("query.put(); index '"+
							idx +"' not a string or number.");
				}
				stmts.push([idx, "=", indexes[idx]]);
			}
		}

		q.push({action: "put", statements: stmts});
		return self;
	};

	self.query = function query_query() {
		var stmts = [];
		return {
			kind: function q_kind(kind) {
				if (typeof kind !== "string") {
					throw new Error("query.query.kind(); kind() takes a string.");
				}
				return this.eq('kind', kind);
			},

			eq: function q_eq(a, b) {
				if (typeof a !== "string" && typeof a !== "number") {
					throw new Error("query.query.eq(); "+
							"First param not a string or number.");
				}
				if (typeof b !== "string" && typeof b !== "number") {
					throw new Error("query.query.eq(); "+
							"Second param not a string or number.");
				}
				stmts.push([a,"=",b]);
				return this;
			},

			gt: function q_gt(a, b) {
				if (typeof a !== "string" && typeof a !== "number") {
					throw new Error("query.query.gt(); "+
							"First param not a string or number.");
				}
				if (typeof b !== "string" && typeof b !== "number") {
					throw new Error("query.query.gt(); "+
							"Second param not a string or number.");
				}
				stmts.push([a,">",b]);
				return this;
			},

			lt: function q_gt(a, b) {
				if (typeof a !== "string" && typeof a !== "number") {
					throw new Error("query.query.lt(); "+
							"First param not a string or number.");
				}
				if (typeof b !== "string" && typeof b !== "number") {
					throw new Error("query.query.lt(); "+
							"Second param not a string or number.");
				}
				stmts.push([a,"<",b]);
				return this;
			},

			append: function q_append() {
				q.push({action: "query", statements: stmts});
				return self;
			}
		};
	};

	self.get = function query_get(key) {
		if (typeof key !== "string" && typeof key !== "number") {
			throw new Error("query.get(); key not a string or number.");
		}
		q.push({action: "get", statements: [["key", "=", key]]});
		return self;
	};

	self.remove = function query_remove(key) {
		if (typeof key !== "string" && typeof key !== "number") {
			throw new Error("query.remove(); key not a string or number.");
		}
		q.push({action: "delete", statements: [["key", "=", key]]});
		return self;
	};

	self.resolve = function query_resolve() {
		return q;
	};

	return self;
};

DB = (function () {
	var cache = {}, models = {}, uid;

	function entity(spec, mapper) {
		var key = spec.key,
			struct = JSON.parse(JSON.stringify(spec.data)),
			index = JSON.parse(JSON.stringify(spec.indexes)),
			update;

		function update_array(a, b) {
			a = isArray(a) ? a : [];
			var i = 0, len = b.length;
			for (; i < len; i += 1) {
				a[i] = update(a[i], b[i]);
			}
			return a;
		}

		function update_object(a, b) {
			a = isObject(a) ? a : {};
			var p;
			for (p in b) {
				if (isin(b, p)) {
					a[p] = update(a[p], b[p]);
				}
			}
			return a;
		}

		update = function (a, b) {
			if (isArray(b)) {
				return update_array(a, b);
			}
			if (isObject(b)) {
				return update_object(a, b);
			}
			return (a = b);
		};

		return function (method) {
			switch (method) {

			case 'key':
				return key;

			case 'entity':
				// Return a cheap copy.
				return JSON.parse(JSON.stringify(struct));

			case 'indexes':
				// Return a cheap copy.
				return JSON.parse(JSON.stringify(index));

			case 'update':
				struct = JSON.parse(JSON.stringify(
							mapper(update(struct, arguments[1]), index)));
				return JSON.parse(JSON.stringify(struct));

			case 'delete':
				struct = index = null;
				return true;

			default:
				throw new Error('Invalid entity method: '+ method);
			}
		};
	}

	function db(connection) {
		var self = {},
		promised_results = [],
		fulfilled_results = [],
		request = connection.request();

		function create(kind) {
			var ent = models[kind]();
			return (cache[ent('key')] = ent);
		}

		function get(key, cb) {
			if (cache[key]) {
				fulfilled_results.push(function () {
					cb(cache[key]);
				});
			}
			else {
				request.get(key);
				promised_results.push(cb);
			}
			return self;
		}

		function put(ent, cb) {
			request.put(ent('key'), ent('entity'), ent('indexes'));
			promised_results.push(cb);
			return self;
		}

		function del(key, cb) {
			cache[key]('delete');
			delete cache[key];
			request.remove(key);
			promised_results.push(cb);
			return self;
		}

		function query() {
			var q = request.query(), append = q.append;
			q.append = function (cb) {
				append();
				promised_results.push(cb);
				return self;
			};
			return q;
		}

		function update_entity(item, cb) {
			var ent, parsed_entity = JSON.parse(item.entity);

			try {
				if (!cache[item.key]) {
					ent = models[item.indexes.kind](
						item.key, parsed_entity, item.indexes);
					cache[item.key] = ent;
				}
				else {
					cache[item.key]('update', parsed_entity);
				}
			} catch (e) {
				LOG.warn('Unable to handle DCube results for '+
					JSON.stringify(item));
				LOG.error(e);
			}
			cb(cache[item.key] || null);
		}

		function go(errback) {
			var this_results = promised_results,
				this_fulfilled_results = fulfilled_results,
				i = 0;

			if (!promised_results.length) {
				setTimeout(function () {
					for (; i < this_fulfilled_results.length; i += 1) {
						this_fulfilled_results[i]();
					}
				}, 0);
				fulfilled_results = [];
				return;
			}

			function make_push_result(results) {
				return function (r) {
					results.push(r);
				};
			}

{'fraction_non_alphanumeric': 0.16692695392915455, 'fraction_numerical': 0.0040817775253780084, 'mean_word_length': 3.0839252065516742, 'pattern_counts': {'":': 6, '<': 10, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2849288', 'n_tokens_mistral': 10783, 'n_tokens_neox': 8934, 'n_words': 2578}