PowerOfIndexedDB/IndexedDB.ts

463 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string,Function[]> = 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ıı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<IDBDatabase>(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<IDBValidKey>(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<undefined>(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<number>(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 <undefined> (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 <IDBValidKey> (store)).sync();
}
/**
* Tablodaki tüm verileri tek tek verir
*/
public async each(tablename:string, callback:(value:Object,next:Function, stop:Function, primaryKey:any) => Promise<any>)
{
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<Boolean>,
mapCallback: (value:Object) => Promise<any>,
resultCallback?: (value:any[]) => any,
) : Promise<any[]>
{
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<Boolean>,
stopFindedOne : boolean = false
) : Promise<number>
{
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<Type>
{
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<Type>
{
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);
}
}
}