adhara
Version:
foundation for any kind of website: microframework
416 lines (361 loc) • 13.4 kB
JavaScript
class AdharaDBStorage extends AdharaStorage{
constructor(){
super();
// promise based Async wrapper for indexedDB
function promisifyRequest(request) {
return new Promise(function(resolve, reject) {
request.onsuccess = function() {
resolve(request.result);
};
request.onerror = function() {
reject(request.error);
};
});
}
function promisifyRequestCall(obj, method, args) {
let request;
let p = new Promise((resolve, reject) => {
request = obj[method].apply(obj, args);
promisifyRequest(request).then(resolve, reject);
});
p.request = request;
return p;
}
function promisifyCursorRequestCall(obj, method, args) {
let p = promisifyRequestCall(obj, method, args);
return p.then(function(value) {
if (!value) {
return;
}
return new Cursor(value, p.request);
});
}
function proxyProperties(ProxyClass, targetProp, properties) {
properties.forEach(function(prop) {
Object.defineProperty(ProxyClass.prototype, prop, {
get: function() {
return this[targetProp][prop];
},
set: function(val) {
this[targetProp][prop] = val;
}
});
});
}
function proxyRequestMethods(ProxyClass, targetProp, Constructor, properties) {
properties.forEach(prop => {
if (!(prop in Constructor.prototype)) {
return;
}
ProxyClass.prototype[prop] = function(){
return promisifyRequestCall(this[targetProp], prop, arguments);
};
});
}
function proxyMethods(ProxyClass, targetProp, Constructor, properties) {
properties.forEach(prop => {
if (!(prop in Constructor.prototype)) {
return;
}
ProxyClass.prototype[prop] = function(){
return this[targetProp][prop].apply(this[targetProp], arguments);
};
});
}
function proxyCursorRequestMethods(ProxyClass, targetProp, Constructor, properties) {
properties.forEach(function(prop) {
if (!(prop in Constructor.prototype)) {
return;
}
ProxyClass.prototype[prop] = function() {
return promisifyCursorRequestCall(this[targetProp], prop, arguments);
};
});
}
function Index(index) {
this._index = index;
}
proxyProperties(Index, '_index', [
'name',
'keyPath',
'multiEntry',
'unique'
]);
proxyRequestMethods(Index, '_index', IDBIndex, [
'get',
'getKey',
'getAll',
'getAllKeys',
'count'
]);
proxyCursorRequestMethods(Index, '_index', IDBIndex, [
'openCursor',
'openKeyCursor'
]);
function Cursor(cursor, request) {
this._cursor = cursor;
this._request = request;
}
proxyProperties(Cursor, '_cursor', [
'direction',
'key',
'primaryKey',
'value'
]);
proxyRequestMethods(Cursor, '_cursor', IDBCursor, [
'update',
'delete'
]);
// proxy 'next' methods
['advance', 'continue', 'continuePrimaryKey'].forEach(function(methodName) {
if (!(methodName in IDBCursor.prototype)) {
return;
}
Cursor.prototype[methodName] = function() {
let cursor = this;
let args = arguments;
return Promise.resolve().then(function() {
cursor._cursor[methodName].apply(cursor._cursor, args);
return promisifyRequest(cursor._request).then(value => value ? (new Cursor(value, cursor._request)) : null);
});
};
});
function ObjectStore(store) {
this._store = store;
}
ObjectStore.prototype.createIndex = function() {
return new Index(this._store.createIndex.apply(this._store, arguments));
};
ObjectStore.prototype.index = function() {
return new Index(this._store.index.apply(this._store, arguments));
};
proxyProperties(ObjectStore, '_store', [
'name',
'keyPath',
'indexNames',
'autoIncrement'
]);
proxyRequestMethods(ObjectStore, '_store', IDBObjectStore, [
'put',
'add',
'delete',
'clear',
'get',
'getAll',
'getKey',
'getAllKeys',
'count'
]);
proxyCursorRequestMethods(ObjectStore, '_store', IDBObjectStore, [
'openCursor',
'openKeyCursor'
]);
proxyMethods(ObjectStore, '_store', IDBObjectStore, [
'deleteIndex'
]);
function Transaction(idbTransaction) {
this._tx = idbTransaction;
this.complete = new Promise(function(resolve, reject) {
idbTransaction.oncomplete = function() {
resolve();
};
idbTransaction.onerror = function() {
reject(idbTransaction.error);
};
idbTransaction.onabort = function() {
reject(idbTransaction.error);
};
});
}
Transaction.prototype.objectStore = function() {
return new ObjectStore(this._tx.objectStore.apply(this._tx, arguments));
};
proxyProperties(Transaction, '_tx', [
'objectStoreNames',
'mode'
]);
proxyMethods(Transaction, '_tx', IDBTransaction, [
'abort'
]);
function UpgradeDB(db, oldVersion, transaction) {
this._db = db;
this.oldVersion = oldVersion;
this.transaction = new Transaction(transaction);
}
UpgradeDB.prototype.createObjectStore = function() {
return new ObjectStore(this._db.createObjectStore.apply(this._db, arguments));
};
proxyProperties(UpgradeDB, '_db', [
'name',
'version',
'objectStoreNames'
]);
proxyMethods(UpgradeDB, '_db', IDBDatabase, [
'deleteObjectStore',
'close'
]);
function DB(db) {
this._db = db;
}
DB.prototype.transaction = function() {
return new Transaction(this._db.transaction.apply(this._db, arguments));
};
proxyProperties(DB, '_db', [
'name',
'version',
'objectStoreNames'
]);
proxyMethods(DB, '_db', IDBDatabase, [
'close'
]);
// Add cursor iterators
['openCursor', 'openKeyCursor'].forEach(function(funcName) {
[ObjectStore, Index].forEach(function(Constructor) {
Constructor.prototype[funcName.replace('open', 'iterate')] = function() {
let args = Array.prototype.slice.call(arguments);
let callback = args[args.length - 1];
let nativeObject = this._store || this._index;
let request = nativeObject[funcName].apply(nativeObject, args.slice(0, -1));
request.onsuccess = function() {
callback(request.result);
};
};
});
});
// polyfill getAll
[Index, ObjectStore].forEach(function(Constructor) {
if (Constructor.prototype.getAll) {
return;
}
Constructor.prototype.getAll = function(query, count) {
let instance = this;
let items = [];
return new Promise(function(resolve) {
instance.iterateCursor(query, function(cursor) {
if (!cursor) {
resolve(items);
return;
}
items.push(cursor.value);
if (count !== undefined && items.length === count) {
resolve(items);
return;
}
cursor["continue"]();
});
});
};
});
let idb = {
open: (name, version, upgradeCallback)=>{
let p = promisifyRequestCall(indexedDB, 'open', [name, version]);
let request = p.request;
request.onupgradeneeded = function(event) {
if (upgradeCallback) {
upgradeCallback(new UpgradeDB(request.result, event.oldVersion, request.transaction));
}
};
return p.then((db)=>{
return new DB(db);
});
},
del: (name)=>{
return promisifyRequestCall(indexedDB, 'deleteDatabase', [name]);
}
};
this.dbs = {};
for(let db_config of Adhara.app.DBConfig){
this.dbs[db_config.id] = idb.open(db_config.name, db_config.version, upgradeDb => this.createObjectStores(upgradeDb, db_config));
}
}
createObjectStores(upgradeDb, db_config){
try{
let db_schema = db_config.schema;
for(let table_name in db_schema){
if(db_schema.hasOwnProperty(table_name)){
if(!upgradeDb.objectStoreNames.contains(table_name)){ //If table doesn't exist already
let os = upgradeDb.createObjectStore(table_name, db_schema[table_name]);
//indexes = [
// // See for more details https://developers.google.com/web/ilt/pwa/working-with-indexeddb#defining_indexes
// {
// name: "title-index",
// property: "title",
// options: {
// unique: true
// }
// }
// {
// name: "gender-index",
// property: "gender",
// options: {
// unique: false
// }
// }
// ]
for(let index of db_schema[table_name].indexes){
os.createIndex(index.name, index.property, index.options);
}
}
}
}
return true;
} catch(e){
return false;
}
}
select(database_name, dataset_name){
return new DBStorageOperator(dataset_name, this.dbs[database_name]);
}
}
class DBStorageOperator extends AdharaStorageOperator{
constructor(dataset, db){
super(dataset);
this.db = db;
}
retrieve(key) {
return this.db.then(db => {
return db.transaction(this.dataset)
.objectStore(this.dataset).get(key);
});
}
store(key, val) {
if(!val){
val = key;
key = undefined;
}
return this.db.then(db => {
const tx = db.transaction(this.dataset, 'readwrite');
tx.objectStore(this.dataset).put(val, key);
return tx.complete;
});
}
remove(key) {
return this.db.then(db => {
const tx = db.transaction(this.dataset, 'readwrite');
tx.objectStore(this.dataset).delete(key);
return tx.complete;
});
}
removeAll() {
return this.db.then(db => {
const tx = db.transaction(this.dataset, 'readwrite');
tx.objectStore(this.dataset).clear();
return tx.complete;
});
}
keys() {
return this.db.then(db => {
const tx = db.transaction(this.dataset);
const keys = [];
const store = tx.objectStore(this.dataset);
// This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
// openKeyCursor isn't supported by Safari, so we fall back
(store.iterateKeyCursor || store.iterateCursor).call(store, cursor => {
if (!cursor) return;
keys.push(cursor.key);
cursor.continue();
});
return tx.complete.then(() => keys);
});
}
}