🏠 

LocalStorageDB

LocalStorageDB Script

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/14976/94706/LocalStorageDB.js

/*
Kailash Nadh (http://nadh.in)
localStorageDB v 2.3.1
A simple database layer for localStorage
v 2.3.1 Mar 2015
v 2.3 Feb 2014 Contribution: Christian Kellner (http://orange-coding.net)
v 2.2 Jan 2014 Contribution: Andy Hawkins (http://a904guy.com)
v 2.1 Nov 2013
v 2.0 June 2013
v 1.9 Nov 2012
License	:	MIT License
*/
!(function (_global, undefined) {
function localStorageDB(db_name, engine) {
var db_prefix = 'db_',
db_id = db_prefix + db_name,
db_new = false,	// this flag determines whether a new database was created during an object initialisation
db = null;
try {
var storage = (engine == sessionStorage ? sessionStorage: localStorage);
} catch(e) { // ie8 hack
var storage = engine;
}
// if the database doesn't exist, create it
db = storage[ db_id ];
if( !( db && (db = JSON.parse(db)) && db.tables && db.data ) ) {
if(!validateName(db_name)) {
error("The name '" + db_name + "' contains invalid characters");
} else {
db = {tables: {}, data: {}};
commit();
db_new = true;
}
}
// ______________________ private methods
// _________ database functions
// drop the database
function drop() {
if(storage.hasOwnProperty(db_id)) {
delete storage[db_id];
}
db = null;
}
// number of tables in the database
function tableCount() {
var count = 0;
for(var table in db.tables) {
if( db.tables.hasOwnProperty(table) ) {
count++;
}
}
return count;
}
// _________ table functions
// returns all fields in a table.
function tableFields(table_name) {
return db.tables[table_name].fields;
}
// check whether a table exists
function tableExists(table_name) {
return db.tables[table_name] ? true : false;
}
// check whether a table exists, and if not, throw an error
function tableExistsWarn(table_name) {
if(!tableExists(table_name)) {
error("The table '" + table_name + "' does not exist");
}
}
// check whether a table column exists
function columnExists(table_name, field_name) {
var exists = false;
var table_fields = db.tables[table_name].fields;
for(var field in table_fields){
if(table_fields[field] == field_name)
{
exists = true;
break;
}
}
return exists;
}
// create a table
function createTable(table_name, fields) {
db.tables[table_name] = {fields: fields, auto_increment: 1};
db.data[table_name] = {};
}
// drop a table
function dropTable(table_name) {
delete db.tables[table_name];
delete db.data[table_name];
}
// empty a table
function truncate(table_name) {
db.tables[table_name].auto_increment = 1;
db.data[table_name] = {};
}
//alter a table
function alterTable(table_name, new_fields, default_values){
db.tables[table_name].fields = db.tables[table_name].fields.concat(new_fields);
// insert default values in existing table
if(typeof default_values != "undefined") {
// loop through all the records in the table
for(var ID in db.data[table_name]) {
if( !db.data[table_name].hasOwnProperty(ID) ) {
continue;
}
for(var field in new_fields) {
if(typeof default_values == "object") {
db.data[table_name][ID][new_fields[field]] = default_values[new_fields[field]];
} else {
db.data[table_name][ID][new_fields[field]] = default_values;
}
}
}
}
}
// number of rows in a table
function rowCount(table_name) {
var count = 0;
for(var ID in db.data[table_name]) {
if( db.data[table_name].hasOwnProperty(ID) ) {
count++;
}
}
return count;
}
// insert a new row
function insert(table_name, data) {
data.ID = db.tables[table_name].auto_increment;
db.data[table_name][ db.tables[table_name].auto_increment ] = data;
db.tables[table_name].auto_increment++;
return data.ID;
}
// select rows, given a list of IDs of rows in a table
function select(table_name, ids, start, limit, sort, distinct) {
var ID = null, results = [], row = null;
for(var i=0; i<ids.length; i++) {
ID = ids[i];
row = db.data[table_name][ID];
results.push( clone(row) );
}
// there are sorting params
if(sort && sort instanceof Array) {
for(var i=0; i<sort.length; i++) {
results.sort(sort_results(sort[i][0], sort[i].length > 1 ? sort[i][1] : null));
}
}
// distinct params
if(distinct && distinct instanceof Array) {
for(var j=0; j<distinct.length; j++) {
var seen = {}, d = distinct[j];
for(var i=0; i<results.length; i++) {
if(results[i] === undefined) {
continue;
}
if(results[i].hasOwnProperty(d) && seen.hasOwnProperty(results[i][d])) {
delete(results[i]);
} else {
seen[results[i][d]] = 1;
}
}
}
// can't use .filter(ie8)
var new_results = [];
for(var i=0; i<results.length; i++) {
if(results[i] !== undefined) {
new_results.push(results[i]);
}
}
results = new_results;
}
// limit and offset
start = start && typeof start === "number" ? start : null;
limit = limit && typeof limit === "number" ? limit : null;
if(start && limit) {
results = results.slice(start, start+limit);
} else if(start) {
results = results.slice(start);
} else if(limit) {
results = results.slice(start, limit);
}
return results;
}
// sort a result set
function sort_results(field, order) {
return function(x, y) {
// case insensitive comparison for string values
var v1 = typeof(x[field]) === "string" ? x[field].toLowerCase() : x[field],
v2 = typeof(y[field]) === "string" ? y[field].toLowerCase() : y[field];
if(order === "DESC") {
return v1 == v2 ? 0 : (v1 < v2 ? 1 : -1);
} else {
return v1 == v2 ? 0 : (v1 > v2 ? 1 : -1);
}
};
}
// select rows in a table by field-value pairs, returns the IDs of matches
function queryByValues(table_name, data) {
var result_ids = [],
exists = false,
row = null;
// loop through all the records in the table, looking for matches
for(var ID in db.data[table_name]) {
if( !db.data[table_name].hasOwnProperty(ID) ) {
continue;
}
row = db.data[table_name][ID];
exists = true;
for(var field in data) {
if( !data.hasOwnProperty(field) ) {
continue;
}
if(typeof data[field] == 'string') {	// if the field is a string, do a case insensitive comparison
if( row[field] === null || row[field].toString().toLowerCase() != data[field].toString().toLowerCase() ) {
exists = false;
break;
}
} else {
if(row[field] != data[field]) {
exists = false;
break;
}
}
}
if(exists) {
result_ids.push(ID);
}
}
return result_ids;
}
// select rows in a table by a function, returns the IDs of matches
function queryByFunction(table_name, query_function) {
var result_ids = [],
exists = false,
row = null;
// loop through all the records in the table, looking for matches
for(var ID in db.data[table_name]) {
if( !db.data[table_name].hasOwnProperty(ID) ) {
continue;
}
row = db.data[table_name][ID];
if( query_function( clone(row) ) == true ) {	// it's a match if the supplied conditional function is satisfied
result_ids.push(ID);
}
}
return result_ids;
}
// return all the IDs in a table
function getIDs(table_name) {
var result_ids = [];
for(var ID in db.data[table_name]) {
if( db.data[table_name].hasOwnProperty(ID) ) {
result_ids.push(ID);
}
}
return result_ids;
}
// delete rows, given a list of their IDs in a table
function deleteRows(table_name, ids) {
for(var i=0; i<ids.length; i++) {
if( db.data[table_name].hasOwnProperty(ids[i]) ) {
delete db.data[table_name][ ids[i] ];
}
}
return ids.length;
}
// update rows
function update(table_name, ids, update_function) {
var ID = '', num = 0;
for(var i=0; i<ids.length; i++) {
ID = ids[i];
var updated_data = update_function( clone(db.data[table_name][ID]) );
if(updated_data) {
delete updated_data['ID']; // no updates possible to ID
var new_data = db.data[table_name][ID];
// merge updated data with existing data
for(var field in updated_data) {
if( updated_data.hasOwnProperty(field) ) {
new_data[field] = updated_data[field];
}
}
db.data[table_name][ID] = validFields(table_name, new_data);
num++;
}
}
return num;
}
// commit the database to localStorage
function commit() {
try {
storage.setItem(db_id, JSON.stringify(db));
return true;
} catch(e) {
return false;
}
}
// serialize the database
function serialize() {
return JSON.stringify(db);
}
// throw an error
function error(msg) {
throw new Error(msg);
}
// clone an object
function clone(obj) {
var new_obj = {};
for(var key in obj) {
if( obj.hasOwnProperty(key) ) {
new_obj[key] = obj[key];
}
}
return new_obj;
}
// validate db, table, field names (alpha-numeric only)
function validateName(name) {
return name.toString().match(/[^a-z_0-9]/ig) ? false : true;
}
// given a data list, only retain valid fields in a table
function validFields(table_name, data) {
var field = '', new_data = {};
for(var i=0; i<db.tables[table_name].fields.length; i++) {
field = db.tables[table_name].fields[i];
if (data[field] !== undefined) {
new_data[field] = data[field];
}
}
return new_data;
}
// given a data list, populate with valid field names of a table
function validateData(table_name, data) {
var field = '', new_data = {};
for(var i=0; i<db.tables[table_name].fields.length; i++) {
field = db.tables[table_name].fields[i];
new_data[field] = (data[field] === null || data[field] === undefined) ? null : data[field];
}
return new_data;
}
// ______________________ public methods
return {
// commit the database to localStorage
commit: function() {
return commit();
},
// is this instance a newly created database?
isNew: function() {
return db_new;
},
// delete the database
drop: function() {
drop();
},
// serialize the database
serialize: function() {
return serialize();
},
// check whether a table exists
tableExists: function(table_name) {
return tableExists(table_name);
},
// list of keys in a table
tableFields: function(table_name) {
return tableFields(table_name);
},
// number of tables in the database
tableCount: function() {
return tableCount();
},
columnExists: function(table_name, field_name){
return columnExists(table_name, field_name);
},
// create a table
createTable: function(table_name, fields) {
var result = false;
if(!validateName(table_name)) {
error("The database name '" + table_name + "' contains invalid characters.");
} else if(this.tableExists(table_name)) {
error("The table name '" + table_name + "' already exists.");
} else {
// make sure field names are valid
var is_valid = true;
for(var i=0; i<fields.length; i++) {
if(!validateName(fields[i])) {
is_valid = false;
break;
}
}
if(is_valid) {
// cannot use indexOf due to <IE9 incompatibility
// de-duplicate the field list
var fields_literal = {};
for(var i=0; i<fields.length; i++) {
fields_literal[ fields[i] ] = true;
}
delete fields_literal['ID']; // ID is a reserved field name
fields = ['ID'];
for(var field in fields_literal) {
if( fields_literal.hasOwnProperty(field) ) {
fields.push(field);
}
}
createTable(table_name, fields);
result = true;
} else {
error("One or more field names in the table definition contains invalid characters");
}
}
return result;
},
// Create a table using array of Objects @ [{k:v,k:v},{k:v,k:v},etc]
createTableWithData: function(table_name, data) {
if(typeof data !== 'object' || !data.length || data.length < 1) {
error("Data supplied isn't in object form. Example: [{k:v,k:v},{k:v,k:v} ..]");
}
var fields = Object.keys(data[0]);
// create the table
if( this.createTable(table_name, fields) ) {
this.commit();
// populate
for (var i=0; i<data.length; i++) {
if( !insert(table_name, data[i]) ) {
error("Failed to insert record: [" + JSON.stringify(data[i]) + "]");
}
}
this.commit();
}
return true;
},
// drop a table
dropTable: function(table_name) {
tableExistsWarn(table_name);
dropTable(table_name);
},
// empty a table
truncate: function(table_name) {
tableExistsWarn(table_name);
truncate(table_name);
},
// alter a table
alterTable: function(table_name, new_fields, default_values) {
var result = false;
if(!validateName(table_name)) {
error("The database name '" + table_name + "' contains invalid characters");
} else {
if(typeof new_fields == "object") {
// make sure field names are valid
var is_valid = true;
for(var i=0; i<new_fields.length; i++) {
if(!validateName(new_fields[i])) {
is_valid = false;
break;
}
}
if(is_valid) {
// cannot use indexOf due to <IE9 incompatibility
// de-duplicate the field list
var fields_literal = {};
for(var i=0; i<new_fields.length; i++) {
fields_literal[ new_fields[i] ] = true;
}
delete fields_literal['ID']; // ID is a reserved field name
new_fields = [];
for(var field in fields_literal) {
if( fields_literal.hasOwnProperty(field) ) {
new_fields.push(field);
}
}
alterTable(table_name, new_fields, default_values);
result = true;
} else {
error("One or more field names in the table definition contains invalid characters");
}
} else if(typeof new_fields == "string") {
if(validateName(new_fields)) {
var new_fields_array = [];
new_fields_array.push(new_fields);
alterTable(table_name, new_fields_array, default_values);
result = true;
} else {
error("One or more field names in the table definition contains invalid characters");
}
}
}
return result;
},
// number of rows in a table
rowCount: function(table_name) {
tableExistsWarn(table_name);
return rowCount(table_name);
},
// insert a row
insert: function(table_name, data) {
tableExistsWarn(table_name);
return insert(table_name, validateData(table_name, data) );
},
// insert or update based on a given condition
insertOrUpdate: function(table_name, query, data) {
tableExistsWarn(table_name);
var result_ids = [];
if(!query) {
result_ids = getIDs(table_name);				// there is no query. applies to all records
} else if(typeof query == 'object') {				// the query has key-value pairs provided
result_ids = queryByValues(table_name, validFields(table_name, query));
} else if(typeof query == 'function') {				// the query has a conditional map function provided
result_ids = queryByFunction(table_name, query);
}
// no existing records matched, so insert a new row
if(result_ids.length == 0) {
return insert(table_name, validateData(table_name, data) );
} else {
var ids = [];
for(var n=0; n<result_ids.length; n++) {
update(table_name, result_ids, function(o) {
ids.push(o.ID);
return data;
});
}
return ids;
}
},
// update rows
update: function(table_name, query, update_function) {
tableExistsWarn(table_name);
var result_ids = [];
if(!query) {
result_ids = getIDs(table_name);				// there is no query. applies to all records
} else if(typeof query == 'object') {				// the query has key-value pairs provided
result_ids = queryByValues(table_name, validFields(table_name, query));
} else if(typeof query == 'function') {				// the query has a conditional map function provided
result_ids = queryByFunction(table_name, query);
}
return update(table_name, result_ids, update_function);
},
// select rows
query: function(table_name, query, limit, start, sort, distinct) {
tableExistsWarn(table_name);
var result_ids = [];
if(!query) {
result_ids = getIDs(table_name, limit, start); // no conditions given, return all records
} else if(typeof query == 'object') {			// the query has key-value pairs provided
result_ids = queryByValues(table_name, validFields(table_name, query), limit, start);
} else if(typeof query == 'function') {		// the query has a conditional map function provided
result_ids = queryByFunction(table_name, query, limit, start);
}
return select(table_name, result_ids, start, limit, sort, distinct);
},
// alias for query() that takes a dict of params instead of positional arrguments
queryAll: function(table_name, params) {
if(!params) {
return this.query(table_name)
} else {
return this.query(table_name,
params.hasOwnProperty('query') ? params.query : null,
params.hasOwnProperty('limit') ? params.limit : null,
params.hasOwnProperty('start') ? params.start : null,
params.hasOwnProperty('sort') ? params.sort : null,
params.hasOwnProperty('distinct') ? params.distinct : null
);
}
},
// delete rows
deleteRows: function(table_name, query) {
tableExistsWarn(table_name);
var result_ids = [];
if(!query) {
result_ids = getIDs(table_name);
} else if(typeof query == 'object') {
result_ids = queryByValues(table_name, validFields(table_name, query));
} else if(typeof query == 'function') {
result_ids = queryByFunction(table_name, query);
}
return deleteRows(table_name, result_ids);
}
}
}
// make amd compatible
if(typeof define === 'function' && define.amd) {
define(function() {
return localStorageDB;
});
} else {
_global['localStorageDB'] = localStorageDB;
}
}(window));