Skip to content

Commit

Permalink
indexedDB driver for easier db access
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Sep 25, 2016
1 parent b97f30b commit 7bae8eb
Show file tree
Hide file tree
Showing 2 changed files with 360 additions and 0 deletions.
150 changes: 150 additions & 0 deletions modules/IndexedDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* @module IndexedDB
*/

import { Make } from '../util/make.js';
import IndexedQueryCompiler from './IndexedDB/IndexedQueryCompiler.js';

const IndexedStoreDefinition = {
name: '',
indexes: null,

_make: function(info) {
this.description = info;
this.indexes = [];
}
}

const IndexedDefinition = {
_version: 0,

/** @type {IndexedStoreDefinition} */
_currentStore: null,

/** @type {IndexedStoreDefinition[]} */
_allStores: null,

_make: function(version) {
this._version = version;
this._allStores = [];
},

store: function(info) {
if (this._currentStore) {
this._allStores.push(this._currentStore);
}

if (typeof info === 'string') {
info = { name: info };
}

this._currentStore = Make(IndexedStoreDefinition)(info);

return this;
},

index: function(name, members, options) {
this._currentStore.indexes.push({
name: name,
members: members,
options: options,
});

return this;
},

_execute: function(db, transaction) {
if (this._currentStore) {
this._allStores.push(this._currentStore);
}

this._allStores.forEach(storeDefinition => {
let store = null;

try {
store = transaction.objectStore(storeDefinition.name);
} catch (e) {
if (e.name === 'NotFoundError') {
store = db.createObjectStore(storeDefinition.description.name, storeDefinition.description);
} else {
console.error(e);
}
}

storeDefinition.indexes.forEach(index => {
try {
store.createIndex(index.name, index.members, index.options);
} catch (e) {
if (e.name === 'ConstraintError') {
store.deleteIndex(index.name);
store.createIndex(index.name, index.members, index.options);
} else {
console.error(e);
}
}
});
});
}
}

const IndexedDB = {
_name: '',

/**
* [_definitions description]
*
* @type {Function[]}
*/
_definitions: null,

_setup: function(success, failure) {
let request = window.indexedDB.open(this._name, this._definitions.length - 1);

request.onsuccess = (event) => success(event.target.result);
request.onerror = failure;
request.onupgradeneeded = ({ oldVersion: lastVersion, target: { result: db, transaction }}) => {
for (let i = lastVersion+1; i < this._definitions.length; i++) {
let setup = this._definitions[i];

setup._execute(db, transaction);
}
};
},

_make: function(name) {
this._name = name;
this._definitions = [];

this._promise = new Promise((success, failure) => {
setTimeout(this._setup.bind(this, success, failure), 0);
});
},

define: function(version) {
this._definitions[version] = Make(IndexedDefinition)(version);

return this._definitions[version];
},

read: function(storeName) {
return Make(IndexedQueryCompiler)(storeName, this._promise);
},

write: function(storeName, value) {
return this._promise.then(db => {
return new Promise((success, failure) => {
try {
let request = db.transaction([storeName], 'readwrite')
.objectStore(storeName).put(value);

request.onsuccess = event => success(event.target.result);
request.onerror = failure;
} catch (e) {
failure(e);
}
});
});
}
};

export default IndexedDB;
210 changes: 210 additions & 0 deletions modules/IndexedDB/IndexedQueryCompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { Make } from '../../util/make.js';
import ArrayUtil from '../../core/objects/ArrayUtil.js';

/**
* A query object for an indexedDB request.
*
* @lends module:IndexedDB.IndexedQuery#
*/
const IndexedQuery = {
/** @type {string} */
name: '',

/** @type {*} */
rangeStart: null,

/** @type {*} */
rangeEnd: null,
}

/**
* the query compiler for indexedDB requests.
*
* @lends module:IndexedDB.IndexedQueryCompiler#
*/
const IndexedQueryCompiler = {
/** @type {IndexedQuery} */
_currentQuery: null,

/** @type {IndexedQuery[]} */
_allQueries: null,
_store: '',
_db: null,
sortOrder: 'next',

_make: function(storeName, db) {
this._allQueries = [];
this._store = storeName;
this._db = db;
},

_transformExclude: function(value, exclude) {
if (Array.isArray(value)) {
value = value.map(item => {
return { value: item, exclude: exclude };
});
} else {
value = { value: value, exclude: exclude };
}

return value;
},

where: function(indexName) {
this._currentQuery = Make(IndexedQuery)();
this._currentQuery.name = indexName;

return this;
},

equals: function(value) {
this.from(value).to(value);

return this;
},

from: function(value, exclude=false) {
value = this._transformExclude(value, exclude);

if (Array.isArray(this._currentQuery.rangeStart)) {
this._currentQuery.rangeStart = ArrayUtil.assignEmpty(this._currentQuery.rangeStart, value);
} else {
this._currentQuery.rangeStart = value;
}

return this;
},

to: function(value, exclude=false) {
value = this._transformExclude(value, exclude);

if (Array.isArray(this._currentQuery.rangeEnd)) {
this._currentQuery.rangeEnd = ArrayUtil.assignEmpty(this._currentQuery.rangeEnd, value);
} else {
this._currentQuery.rangeEnd = value;
}

return this;
},

lowerThan: function(value) {
this.to(value, true);

return this;
},

higherThan: function(value) {
this.from(value, true);

return this;
},

or: function(indexName) {
this._allQueries.push(this._currentQuery);
this.where(indexName);

return this;
},

sort: function(direction) {
if (direction === 'ASC') {
this.sortOrder = 'next';
} else if (direction === 'DESC') {
this.sortOrder = 'prev';
}

return this;
},

get: function(...limit) {
if (limit.length === 1) {
limit.unshift(0);
}

this._allQueries.push(this._currentQuery);
this._currentQuery = null;

return this._execute(limit);
},

_execute: function(start, end) {
return this._db.then(db => {
let matches = [];
let results = [];

return Promise.all(this._allQueries.map(query => {
return new Promise((done, error) => {
let store = db.transaction([this._store], 'readonly').objectStore(this._store);
let range = null;
let rangeArray = Array.isArray(query.rangeStart) || Array.isArray(query.rangeEnd);

if (!rangeArray) {
if (query.rangeStart && query.rangeEnd) {
range = IDBKeyRange.bound(query.rangeStart.value, query.rangeEnd.value,
query.rangeStart.exclude, query.rangeEnd.exclude);
} else if (query.rangeStart) {
range = IDBKeyRange.lowerBound(query.rangeStart.value, query.rangeStart.exclude);
} else {
range = IDBKeyRange.upperBound(query.rangeEnd.value, query.rangeEnd.exclude);
}
}

let request = store.index(query.name).openCursor(range, this.sortOrder);

request.onsuccess = ({ target: { result: cursor } }) => {
if (!cursor) {
return done();
}

if (matches.indexOf(JSON.stringify(cursor.primaryKey)) < 0) {
let doesMatch = true;
let keyCount = query.rangeStart && query.rangeStart.length || 
query.rangeEnd && query.rangeEnd.length;

if (start > 0) {
start -= 1;
end -= 1;
return cursor.continue();
} else if (start === 0 && end > 0) {
end -= 1;
} else if (start === 0 && end === 0) {
return done();
}

if (rangeArray) {
for (let i = 0; i < keyCount; i++) {
if (query.rangeStart && query.rangeStart[i].value !== null) {
if (query.rangeStart[i].exclude) {
doesMatch = doesMatch && cursor.key[i] > query.rangeStart[i].value;
} else {
doesMatch = doesMatch && cursor.key[i] >= query.rangeStart[i].value;
}
}

if (query.rangeEnd && query.rangeEnd[i].value !== null) {
if (query.rangeEnd.exclude) {
doesMatch = doesMatch && cursor.key[i] < query.rangeEnd[i].value;
} else {
doesMatch = doesMatch && cursor.key[i] <= query.rangeEnd[i].value;
}
}
}
}

if (doesMatch) {
results.push(cursor.value);
matches.push(JSON.stringify(cursor.primaryKey));
}
}

return cursor.continue();
};

request.onerror = error;
});
})).then(() => results);
});
}
};

export default IndexedQueryCompiler;

0 comments on commit 7bae8eb

Please sign in to comment.