diff --git a/index.html b/index.html index 4ba8121..32ec86a 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,17 @@ diff --git a/package.json b/package.json index ca5be19..cb29e6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/utils", - "version": "0.25.8", + "version": "0.25.9", "description": "Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/database.ts b/src/database.ts index b2fe710..9734690 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,47 +1,85 @@ +import {findByProp} from './array.ts'; import {ASet} from './aset.ts'; export type TableOptions = { name: string; key?: string; + autoIncrement?: boolean; }; export class Database { connection!: Promise; + ready = false; tables!: TableOptions[]; - constructor(public readonly database: string, tables: (string | TableOptions)[], public version?: number) { + constructor(public readonly database: string, tables?: (string | TableOptions)[], public version?: number) { this.connection = new Promise((resolve, reject) => { const req = indexedDB.open(this.database, this.version); - this.tables = tables.map(t => { + this.tables = !tables ? [] : tables.map(t => { t = typeof t == 'object' ? t : {name: t}; return {...t, name: t.name.toString()}; }); - const tableNames = new ASet(this.tables.map(t => t.name)); req.onerror = () => reject(req.error); req.onsuccess = () => { const db = req.result; - if(tableNames.symmetricDifference(new ASet(Array.from(db.objectStoreNames))).length) { + const existing = Array.from(db.objectStoreNames); + if(!tables) this.tables = existing.map(t => { + const tx = db.transaction(t, 'readonly', ) + const store = tx.objectStore(t); + return {name: t, key: store.keyPath}; + }); + const desired = new ASet((tables || []).map(t => typeof t == 'string' ? t : t.name)); + if(tables && desired.symmetricDifference(new ASet(existing)).length) { db.close(); Object.assign(this, new Database(this.database, this.tables, db.version + 1)); + this.connection.then(resolve); } else { this.version = db.version; resolve(db); } + this.ready = true; }; req.onupgradeneeded = () => { const db = req.result; const existingTables = new ASet(Array.from(db.objectStoreNames)); - existingTables.difference(tableNames).forEach(name => db.deleteObjectStore(name)); - tableNames.difference(existingTables).forEach(name => db.createObjectStore(name)); + if(tables) { + const desired = new ASet((tables || []).map(t => typeof t == 'string' ? t : t.name)); + existingTables.difference(desired).forEach(name => db.deleteObjectStore(name)); + desired.difference(existingTables).forEach(name => { + const t = this.tables.find(findByProp('name', name)); + db.createObjectStore(name, { + keyPath: t?.key, + autoIncrement: t?.autoIncrement || !t?.key + }); + }); + } }; }); } + async createTable(table: string | TableOptions): Promise> { + if(typeof table == 'string') table = {name: table}; + const conn = await this.connection; + if(!this.includes(table.name)) { + conn.close(); + Object.assign(this, new Database(this.database, [...this.tables, table], (this.version ?? 0) + 1)); + } + return this.table(table.name); + } + + async deleteTable(table: string | TableOptions): Promise { + if(typeof table == 'string') table = {name: table}; + if(!this.includes(table.name)) return; + const conn = await this.connection; + conn.close(); + Object.assign(this, new Database(this.database, this.tables.filter(t => t.name != table.name), (this.version ?? 0) + 1)); + } + includes(name: any): boolean { - return !!this.tables.find(t => t.name == name.toString()); + return !!this.tables.find(t => t.name == (typeof name == 'object' ? name.name : name.toString())); } table(name: any): Table { @@ -50,7 +88,12 @@ export class Database { } export class Table { - constructor(private readonly database: Database, public readonly name: string) {} + constructor(private readonly database: Database, public readonly name: string, public readonly key: keyof T | string = 'id') { + this.database.connection.then(() => { + const exists = !!this.database.tables.find(findByProp('name', this.name)); + if(!exists) this.database.createTable(this.name); + }); + } async tx(table: string, fn: (store: IDBObjectStore) => IDBRequest, readonly = false): Promise { const db = await this.database.connection; @@ -67,12 +110,24 @@ export class Table { return this.tx(this.name, store => store.add(value, key)); } + all = this.getAll; + + clear(): Promise { + return this.tx(this.name, store => store.clear()); + } + count(): Promise { return this.tx(this.name, store => store.count(), true); } - put(key: K, value: T): Promise { - return this.tx(this.name, store => store.put(value, key)); + create = this.add; + + delete(key: K): Promise { + return this.tx(this.name, store => store.delete(key)); + } + + get(key: K): Promise { + return this.tx(this.name, store => store.get(key), true); } getAll(): Promise { @@ -83,15 +138,20 @@ export class Table { return this.tx(this.name, store => store.getAllKeys(), true); } - get(key: K): Promise { - return this.tx(this.name, store => store.get(key), true); + put(key: K, value: T): Promise { + return this.tx(this.name, store => store.put(value, key)); } - delete(key: K): Promise { - return this.tx(this.name, store => store.delete(key)); + read(): Promise; + read(key: K): Promise; + read(key?: K): Promise { + return key ? this.get(key) : this.getAll(); } - clear(): Promise { - return this.tx(this.name, store => store.clear()); + set(value: T, key?: K): Promise { + if(!key && !(value)[this.key]) return this.add(value); + return this.put(key || (value)[this.key], value); } + + update = this.set; }