diff --git a/index.html b/index.html
index 7236d42..6d4f724 100644
--- a/index.html
+++ b/index.html
@@ -1,15 +1,29 @@
diff --git a/package.json b/package.json
index 9babd1c..7ba44db 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@ztimson/utils",
- "version": "0.25.2",
+ "version": "0.25.3",
"description": "Utility library",
"author": "Zak Timson",
"license": "MIT",
diff --git a/src/cache.ts b/src/cache.ts
index 15ad5c8..b6c0446 100644
--- a/src/cache.ts
+++ b/src/cache.ts
@@ -1,11 +1,11 @@
-import {Collection} from './collection.ts';
+import {Table} from './database.ts';
import {deepCopy, JSONSanitize} from './objects.ts';
export type CacheOptions = {
/** Delete keys automatically after x amount of seconds */
ttl?: number;
/** Storage to persist cache */
- storage?: Storage | Collection;
+ storage?: Storage | Table;
/** Key cache will be stored under */
storageKey?: string;
/** Keep or delete cached items once expired, defaults to delete */
@@ -33,10 +33,11 @@ export class Cache {
constructor(public readonly key?: keyof T, public readonly options: CacheOptions = {}) {
if(options.storageKey && !options.storage && typeof(Storage) !== 'undefined') options.storage = localStorage;
if(options.storage) {
- if(options.storage instanceof Collection) {
- (async () => {
- (await options.storage?.getAll()).forEach((v: any) => this.add(v));
- })()
+ if(options.storage instanceof Table) {
+ (async () => (await options.storage?.getAll()).forEach((v: any) => {
+ console.log(v);
+ this.add(v)
+ }))()
} else if(options.storageKey) {
const stored = options.storage?.getItem(options.storageKey);
if(stored != null) try { Object.assign(this.store, JSON.parse(stored)); } catch { }
@@ -62,7 +63,7 @@ export class Cache {
private save(key: K) {
if(this.options.storage) {
- if(this.options.storage instanceof Collection) {
+ if(this.options.storage instanceof Table) {
this.options.storage.put(key, this.store[key]);
} else if(this.options.storageKey) {
this.options.storage.setItem(this.options.storageKey, JSONSanitize(this.store));
@@ -138,14 +139,17 @@ export class Cache {
*/
expire(key: K): this {
this.complete = false;
- if(this.options.expiryPolicy == 'keep') (this.store[key])._expired = true;
- else this.delete(key);
+ if(this.options.expiryPolicy == 'keep') {
+ (this.store[key])._expired = true;
+ this.save(key);
+ } else this.delete(key);
return this;
}
/**
* Get item from the cache
* @param {K} key Key to lookup
+ * @param expired Include expired items
* @return {T} Cached item
*/
get(key: K, expired?: boolean): CachedValue | null {
diff --git a/src/collection.ts b/src/collection.ts
deleted file mode 100644
index c458417..0000000
--- a/src/collection.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-export class Collection {
- private database: Promise;
-
- constructor(public readonly db: string, public readonly collection: string, public readonly version?: number, private setup?: (db: IDBDatabase, event: IDBVersionChangeEvent) => void) {
- this.database = new Promise((resolve, reject) => {
- const req = indexedDB.open(this.db, this.version);
- req.onerror = () => reject(req.error);
- req.onsuccess = () => resolve(req.result);
- req.onupgradeneeded = (event) => {
- const db = req.result;
- if(!db.objectStoreNames.contains(collection)) db.createObjectStore(collection, {keyPath: undefined});
- if (this.setup) this.setup(db, event as IDBVersionChangeEvent);
- };
- });
- }
-
- async tx(collection: string, fn: ((store: IDBObjectStore) => IDBRequest), readonly?: boolean): Promise {
- const db = await this.database;
- const tx = db.transaction(collection, readonly ? 'readonly' : 'readwrite');
- const store = tx.objectStore(collection);
- return new Promise((resolve, reject) => {
- const request = fn(store);
- request.onsuccess = () => resolve(request.result);
- request.onerror = () => reject(request.error);
- });
- }
-
- add(value: T, key?: K): Promise {
- return this.tx(this.collection, store => store.add(value, key));
- }
-
- count(): Promise {
- return this.tx(this.collection, store => store.count(), true);
- }
-
- put(key: K, value: T): Promise {
- return this.tx(this.collection, store => store.put(value, key));
- }
-
- getAll(): Promise {
- return this.tx(this.collection, store => store.getAll(), true);
- }
-
- getAllKeys(): Promise {
- return this.tx(this.collection, store => store.getAllKeys(), true);
- }
-
- get(key: K): Promise {
- return this.tx(this.collection, store => store.get(key), true);
- }
-
- delete(key: K): Promise {
- return this.tx(this.collection, store => store.delete(key));
- }
-
- clear(): Promise {
- return this.tx(this.collection, store => store.clear());
- }
-}
diff --git a/src/database.ts b/src/database.ts
new file mode 100644
index 0000000..90020db
--- /dev/null
+++ b/src/database.ts
@@ -0,0 +1,94 @@
+export type TableOptions = {
+ name: string;
+ key?: string;
+};
+
+export class Database {
+ connection!: Promise;
+
+ constructor(public readonly database: string, public readonly tables: (string | TableOptions)[], public version?: number) {
+ this.connection = new Promise((resolve, reject) => {
+ const req = indexedDB.open(this.database, this.version);
+
+ req.onerror = () => reject(req.error);
+
+ req.onsuccess = () => {
+ const db = req.result;
+ if(tables.find(s => !db.objectStoreNames.contains(typeof s === 'string' ? s : s.name))) {
+ db.close();
+ Object.assign(this, new Database(this.database, this.tables, db.version + 1));
+ } else {
+ this.version = db.version;
+ resolve(db);
+ }
+ };
+
+ req.onupgradeneeded = () => {
+ const db = req.result;
+ Array.from(db.objectStoreNames)
+ .filter(s => !this.tables.find(t => typeof t === 'string' ? t : t.name == s))
+ .forEach(name => db.deleteObjectStore(name));
+ tables.filter(t => !db.objectStoreNames.contains(typeof t === 'string' ? t : t.name))
+ .forEach(t => {db.createObjectStore(typeof t === 'string' ? t : t.name, {
+ keyPath: typeof t === 'string' ? undefined : t.key
+ });
+ });
+ };
+ });
+ }
+
+ includes(name: string): boolean {
+ return this.tables.some(t => (typeof t === 'string' ? name === t : name === t.name));
+ }
+
+ table(name: string): Table {
+ return new Table(this, name);
+ }
+}
+
+export class Table {
+ constructor(private readonly database: Database, public readonly name: string) {}
+
+ async tx(schema: string, fn: (store: IDBObjectStore) => IDBRequest, readonly = false): Promise {
+ const db = await this.database.connection;
+ const tx = db.transaction(schema, readonly ? 'readonly' : 'readwrite');
+ const store = tx.objectStore(schema);
+ return new Promise((resolve, reject) => {
+ const request = fn(store);
+ request.onsuccess = () => resolve(request.result as R); // ✅ explicit cast
+ request.onerror = () => reject(request.error);
+ });
+ }
+
+ add(value: T, key?: K): Promise {
+ return this.tx(this.name, store => store.add(value, key));
+ }
+
+ 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));
+ }
+
+ getAll(): Promise {
+ return this.tx(this.name, store => store.getAll(), true);
+ }
+
+ getAllKeys(): Promise {
+ return this.tx(this.name, store => store.getAllKeys(), true);
+ }
+
+ get(key: K): Promise {
+ return this.tx(this.name, store => store.get(key), true);
+ }
+
+ delete(key: K): Promise {
+ return this.tx(this.name, store => store.delete(key));
+ }
+
+ clear(): Promise {
+ return this.tx(this.name, store => store.clear());
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index aeae0bf..4655739 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,7 +4,7 @@ export * from './aset';
export * from './cache';
export * from './color';
export * from './csv';
-export * from './collection';
+export * from './database';
export * from './files';
export * from './emitter';
export * from './errors';
diff --git a/tests/collection.spec.ts b/tests/collection.spec.ts
deleted file mode 100644
index b759274..0000000
--- a/tests/collection.spec.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import {Collection} from '../src';
-import 'fake-indexeddb/auto';
-
-describe('IndexedDb and Collection', () => {
- const data = {id: 1, name: 'Alice', age: 30, email: ''};
- const data2 = {id: 2, name: 'Bob', age: 39, email: ''};
- let collection = new Collection('database', 'collection');
-
- afterEach(() => collection.clear());
-
- test('can set and get an item', async () => {
- await collection.put(data.id, data);
- const result = await collection.get(data.id);
- expect(result).toEqual(data);
- });
-
- test('can remove an item', async () => {
- await collection.put(data.id, data);
- await collection.put(data2.id, data2);
- await collection.delete(data.id);
- const result = await collection.get(data.id);
- const result2 = await collection.get(data2.id);
- expect(result).toBeUndefined();
- expect(result2).toEqual(data2);
- });
-
- test('can clear all items', async () => {
- await collection.put(data.id, data);
- await collection.put(data2.id, data2);
- await collection.clear();
- const result = await collection.get(data.id);
- const result2 = await collection.get(data2.id);
- expect(result).toBeUndefined();
- expect(result2).toBeUndefined();
- });
-
- test('getItem on missing key returns undefined', async () => {
- const result = await collection.get(-1);
- expect(result).toBeUndefined();
- });
-
- test('count returns number of items', async () => {
- expect(await collection.count()).toBe(0);
- await collection.put(data.id, data);
- expect(await collection.count()).toBe(1);
- await collection.delete(data.id);
- expect(await collection.count()).toBe(0);
- });
-
- test('can get all items', async () => {
- await collection.put(data.id, data);
- await collection.put(data2.id, data2);
- const allItems = await collection.getAll();
- expect(allItems).toEqual(expect.arrayContaining([data, data2]));
- expect(allItems.length).toBe(2);
- });
-
- test('can get all keys', async () => {
- await collection.put(data.id, data);
- await collection.put(data2.id, data2);
- const keys = await collection.getAllKeys();
- expect(keys).toEqual(expect.arrayContaining([data.id, data2.id]));
- expect(keys.length).toBe(2);
- });
-});