interface ModelIterface { name:string; version:number; tables : TableInterface[] } interface TableInterface { name:string; key:string, columns:string[] } /** * Browser ve nodejs uyumluluğu için yerel eventleri kullanmak yerine manual event sistemi yazılmıştır */ class EventTarget { /** * Olayları tutan nesne */ public _events : Map = new Map(); /** * Olay gerçekleştiğinde gerçekleşmesi için yeni işlev kaydı açar * @param {string} event olayın türü * @param {Function} callback olay gerçekleştiğinde çağrılacak fonksiyon */ public on(event:string, callback:Function) { if(this._events.has(event)) { this._events.get(event).push(callback) }else{ this._events.set(event,[callback]) } } /** * on ile aynı, eski tarayıcılar hala addEventListener kullanıyor */ public addEventListener(event:string, callback:Function) { this.on(event, callback) } /** * Browserlar için olay tetikleyicisi */ public dispatchEvent(event:Event) { this.emitEvent(event.type, event) } /** * * @param {string} type gerçekleşen olay türü * @param {...any[]} args olay gerçekleştiğinde çalışacak işlevler gönderilecek argümanlar */ public emitEvent(type:string, ...args) { let events = this._events.get(type); if(events) for (const callbacks of events) { callbacks(...args) } } } export class IndexedDB extends EventTarget { /** * Veritabanının hazır olup olmadığını tutar */ public IsReady : Boolean = false; /** * Migration için tabloların yapısı tutulan veri */ public Model : ModelIterface; /** * IndexedDB verisi */ private DBRequest : IDBOpenDBRequest; /** * IndexedDB veritabanı işleci */ private DB : IDBDatabase; /** * IndexedDB yeni örneğinde sistemin desteklediği API'yi yapılandırıyoruz */ public constructor() { super() if(!IndexedDB.Engine) { IndexedDB.PolyfillCompalibity() } } /** * Veritabanı açık ve işlemlere hazır olduğu anda çalıştırılır */ public scope(func: Function) { if(this.IsReady) { return func() } this.addEventListener("load",() => func()) } /** * Modeller hazır olduğunda bağlanmak için kullanılır */ public async connect() { this.IsReady = false; this.DBRequest = IndexedDB.Engine.open(this.Model.name, this.Model.version || 0); this.DBRequest.addEventListener("upgradeneeded", this.executeMigrate.bind(this)); new IEventLocate(this.DBRequest).sync().then(async db => { this.DB = db; this.IsReady = true; this.dispatchEvent(new Event("load")); }) } /** * IndexedDB'de yeni tablolar ve kolonlarını açmak için çalıştırılır */ public executeMigrate() { let db : IDBDatabase = this.DBRequest.result; for(let e of this.Model.tables) { let objectStore : IDBObjectStore; if(!db.objectStoreNames.contains(e.name)) { objectStore = db.createObjectStore(e.name, {keyPath:e.key}); }else{ objectStore = this.DBRequest.transaction.objectStore(e.name); } e.columns.forEach(name => { if(!objectStore.indexNames.contains(name)) { objectStore.createIndex(name, name) } }) let indexes = Array.from(objectStore.indexNames); for(let indexName of indexes) { if(!e.columns.includes(indexName)) { objectStore.deleteIndex(indexName) } } } // for (const objectStoreName of Array.from(db.objectStoreNames)) { let isExists = this.Model.tables.filter(e => e.name == objectStoreName).length != 0; if(!isExists) { db.deleteObjectStore(objectStoreName); } } this.dispatchEvent(new Event("migrate")); } /** * IndexedDB'de belirli bir yapıda tablo yapısı oluşturur ve değiştirir */ public migrate(mi:ModelIterface) { this.Model = mi; } /** * Tabloya veri ekler */ public async add(tablename:string, data:{[key:string]:any}) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).add(data); return await (new IEventLocate(store)).sync(); } /** * Tablodaki tüm verileri temizler */ public async clear(tablename:string) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).clear(); return await (new IEventLocate(store)).sync(); } /** * Tablodaki verilerin sayısını döner */ public async count(tablename:string) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).count(); return await (new IEventLocate(store)).sync(); } /** * Tablo bir kayıt siler */ public async delete(tablename:string, name:string) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).delete(name); return await (new IEventLocate (store)).sync(); } /** * Tablodaki bir kaydı okur */ public async get(tablename:string, index:string) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).get(index); return await (new IEventLocate <{[key:string]:any}> (store)).sync(); } /** * Tablodaki bir kaydı okur */ public async getFrom(tablename:string, column:string, index:string) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).index(column).get(index); return await (new IEventLocate <{[key:string]:any}> (store)).sync(); } /** * Tablodaki tüm kayıtları alır */ public async getAll(tablename:string) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).getAll(); return await (new IEventLocate <{[key:string]:any}[]> (store)).sync(); } /** * Tabloda veri yoksa oluşturur varsa günceller */ public async save(tablename:string, key:string, data:{[key:string]:any}) { if(!this.IsReady) return; let record = await this.get(tablename, key); if(record) { await this.put(tablename, data); }else{ await this.add(tablename, data); } } /** * Tabloda bir veriyi günceller */ public async put(tablename:string,data:{[key:string]:any}) { if(!this.IsReady) return; let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).put(data); return await (new IEventLocate (store)).sync(); } /** * Tablodaki tüm verileri tek tek verir */ public async each(tablename:string, callback:(value:Object,next:Function, stop:Function, primaryKey:any) => Promise) { let store = this.DB.transaction([tablename],"readonly").objectStore(tablename); let cursor = store.openCursor(); await new Promise(ok => { cursor.onsuccess = function(){ if(cursor.result !== null) { let data = cursor.result.value; try{ callback( data, () => cursor.result.continue(), () => ok(void 0), cursor.result.primaryKey ) }catch{ ok(void 0) } } else { ok(void 0) } }; }) } /** * Tablodaki tüm verileri tek tek gezer ve aktif menipülasyon sağlar */ public async subfilter( tablename:string, filterCallback: (value:Object, stop:Function) => Promise, mapCallback: (value:Object) => Promise, resultCallback?: (value:any[]) => any, ) : Promise { let pool : string[] = []; let mappedObj : any[] = []; await this.each(tablename, async (value, next, stop, id: string) => { if(await filterCallback( value, stop )){ pool.push(id); } next(); }); for (const item of pool) { let value = this.get(tablename, item); mappedObj.push( await mapCallback(value) ) } if(typeof resultCallback == "function") { resultCallback(mappedObj) } return mappedObj } /** * Tablodaki tüm verileri tek tek verir */ public async countFilter( tablename:string, filterCallback: (value:Object, stop:Function) => Promise, stopFindedOne : boolean = false ) : Promise { let pool : number = 0; await this.each(tablename, async (value, next, stop) => { if(await filterCallback( value, stop )){ pool++; if(stopFindedOne) { stop() } } next(); }); return pool } /** * IndexedDB bağlantısını sonlandırır */ public close() { if(!this.IsReady) return; this.dispatchEvent(new Event("close")); this.DB.close(); this.IsReady = false; } /** * IndexedDB okuma yazma için kullanılan api */ public static Engine : IDBFactory; /** * Farklı browserlarda uyumluluk için IndexedDB farklı isimlerde çağrılabilir */ public static PolyfillCompalibity() { if('indexedDB' in window) { IndexedDB.Engine = window['indexedDB'] } if('mozIndexedDB' in window) { IndexedDB.Engine = window['mozIndexedDB'] } if('webkitIndexedDB' in window) { IndexedDB.Engine = window['webkitIndexedDB'] } if('msIndexedDB' in window) { IndexedDB.Engine = window['msIndexedDB'] } } } interface ISheel{ onsuccess : (e:any) => any; onerror : (e:any) => any; } /** * Başarılı olduğunda onsuccess başarısız olduğunda onerror döndüren yapıları * javascript ES6 tarafında async/await yapısına dönüştürür böylece try-catch kullanılarak yakalanabilir */ class IEventLocate { private result : { then:any[], catch:any[], finally:any[] } = { then:[], catch:[], finally:[] }; public events : {[key:string]:Function[]}= { then: [], catch: [], finally: [] }; public constructor(sheel:ISheel) { sheel.onerror = (event) => { this.result.catch = [event.target, event]; this.events.catch.forEach(e => { e.call(event.target, event.target?.result); }) this.events.finally.forEach(e => { e.call(event.target, event.target?.result); }) } sheel.onsuccess = (event) => { this.result.then = [event.target, event]; this.events.then.forEach(e => { e.call(event.target, event.target?.result); }) } } public sync() : Promise { return new Promise((resolve, reject) => { this.then(resolve); this.catch(reject); }) } public then(e:any) { if(this.result.then.length) { e.call(this.result.then[0],this.result.then[1]) }else{ this.events.then.push(e); } } public catch(e:any) { if(this.result.catch.length) { e.call(this.result.catch[0],this.result.catch[1]) }else{ this.events.catch.push(e); } } public finally(e:any) { if(this.result.finally.length) { e.call(this.result.finally[0],this.result.finally[1]) }else{ this.events.finally.push(e); } } }