2024-02-06 14:29:42 +03:00
|
|
|
|
interface ModelIterface {
|
|
|
|
|
name:string;
|
|
|
|
|
version:number;
|
|
|
|
|
tables : TableInterface[]
|
|
|
|
|
}
|
|
|
|
|
interface TableInterface {
|
|
|
|
|
name:string;
|
|
|
|
|
key:string,
|
|
|
|
|
columns:string[]
|
|
|
|
|
}
|
|
|
|
|
/**
|
2024-02-08 21:59:29 +03:00
|
|
|
|
* Browser ve nodejs uyumluluğu için yerel eventleri kullanmak yerine manual event sistemi yazılmıştır
|
2024-02-06 14:29:42 +03:00
|
|
|
|
*/
|
|
|
|
|
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))
|
|
|
|
|
{
|
2024-03-27 09:08:10 +03:00
|
|
|
|
this._events.get(event)?.push(callback)
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}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ü
|
2024-03-27 09:08:10 +03:00
|
|
|
|
* @param {...any[]} args olay gerexçekleştiğinde çalışacak işlevler gönderilecek argümanlar
|
2024-02-06 14:29:42 +03:00
|
|
|
|
*/
|
|
|
|
|
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));
|
2024-03-27 09:08:10 +03:00
|
|
|
|
new IEventLocate<IDBDatabase,IDBRequest>(this.DBRequest).sync().then(async db => {
|
2024-02-06 14:29:42 +03:00
|
|
|
|
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{
|
2024-03-27 09:08:10 +03:00
|
|
|
|
objectStore = (this.DBRequest.transaction as IDBTransaction).objectStore(e.name);
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate<IDBValidKey,IDBRequest>(store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Tablodaki tüm verileri temizler
|
|
|
|
|
*/
|
|
|
|
|
public async clear(tablename:string)
|
|
|
|
|
{
|
|
|
|
|
if(!this.IsReady) return;
|
|
|
|
|
let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).clear();
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate<undefined,IDBRequest>(store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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();
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate<number,IDBRequest>(store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate <undefined,IDBRequest> (store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
2024-03-27 09:08:10 +03:00
|
|
|
|
* Tablodaki bir kaydı okur (primary key ile)
|
2024-02-06 14:29:42 +03:00
|
|
|
|
*/
|
|
|
|
|
public async get(tablename:string, index:string)
|
|
|
|
|
{
|
|
|
|
|
if(!this.IsReady) return;
|
|
|
|
|
let store = this.DB.transaction([tablename], "readwrite").objectStore(tablename).get(index);
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate <any,IDBRequest> (store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
2024-03-27 09:08:10 +03:00
|
|
|
|
* Tablodaki bir kaydı okur (belirtilen index kolonuyla arar)
|
2024-02-06 14:29:42 +03:00
|
|
|
|
*/
|
|
|
|
|
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);
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate <any,IDBRequest> (store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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();
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate <any[], IDBRequest> (store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
2024-03-27 09:08:10 +03:00
|
|
|
|
return await (new IEventLocate <IDBValidKey,IDBRequest> (store)).sync();
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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,
|
2024-03-27 09:08:10 +03:00
|
|
|
|
() => cursor.result?.continue(),
|
2024-02-06 14:29:42 +03:00
|
|
|
|
() => 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)
|
|
|
|
|
{
|
2024-03-27 09:08:10 +03:00
|
|
|
|
IndexedDB.Engine = window['mozIndexedDB'] as IDBFactory
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
if('webkitIndexedDB' in window)
|
|
|
|
|
{
|
2024-03-27 09:08:10 +03:00
|
|
|
|
IndexedDB.Engine = window['webkitIndexedDB'] as IDBFactory
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
if('msIndexedDB' in window)
|
|
|
|
|
{
|
2024-03-27 09:08:10 +03:00
|
|
|
|
IndexedDB.Engine = window['msIndexedDB'] as IDBFactory
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-27 09:08:10 +03:00
|
|
|
|
interface ISheel<Type>{
|
|
|
|
|
onsuccess : ((this: Type, ev: Event) => any) | null;
|
|
|
|
|
onerror : ((this: Type, ev: Event) => any) | null;
|
2024-02-06 14:29:42 +03:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2024-03-27 09:08:10 +03:00
|
|
|
|
class IEventLocate<Type,Shell>
|
2024-02-06 14:29:42 +03:00
|
|
|
|
{
|
|
|
|
|
private result : {
|
|
|
|
|
then:any[],
|
|
|
|
|
catch:any[],
|
|
|
|
|
finally:any[]
|
|
|
|
|
} = {
|
|
|
|
|
then:[],
|
|
|
|
|
catch:[],
|
|
|
|
|
finally:[]
|
|
|
|
|
};
|
|
|
|
|
public events : {[key:string]:Function[]}= {
|
|
|
|
|
then: [],
|
|
|
|
|
catch: [],
|
|
|
|
|
finally: []
|
|
|
|
|
};
|
2024-03-27 09:08:10 +03:00
|
|
|
|
public constructor(sheel:ISheel<Shell>)
|
2024-02-06 14:29:42 +03:00
|
|
|
|
{
|
2024-03-27 09:08:10 +03:00
|
|
|
|
sheel.onerror = (event:any) => {
|
2024-02-06 14:29:42 +03:00
|
|
|
|
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);
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-03-27 09:08:10 +03:00
|
|
|
|
sheel.onsuccess = (event:any) => {
|
2024-02-06 14:29:42 +03:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|