From d462ff4f69f96acaf2cefe7891afed5051af6727 Mon Sep 17 00:00:00 2001 From: saqut Date: Tue, 6 Feb 2024 14:29:42 +0300 Subject: [PATCH] IndexedDB.ts Ekle --- IndexedDB.ts | 463 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 IndexedDB.ts diff --git a/IndexedDB.ts b/IndexedDB.ts new file mode 100644 index 0000000..d9b0858 --- /dev/null +++ b/IndexedDB.ts @@ -0,0 +1,463 @@ +interface ModelIterface { + name:string; + version:number; + tables : TableInterface[] +} +interface TableInterface { + name:string; + key:string, + columns:string[] +} +/** + * Browser 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'] as IDBFactory + } + if('webkitIndexedDB' in window) + { + IndexedDB.Engine = window['webkitIndexedDB'] as IDBFactory + } + if('msIndexedDB' in window) + { + IndexedDB.Engine = window['msIndexedDB'] as IDBFactory + } + } +} +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); + } + } +} \ No newline at end of file