Add a web worker for db operations
This commit is contained in:
parent
9c8c5fc24c
commit
66a27cb03c
381
src/workers/indexeddb.worker.ts
Normal file
381
src/workers/indexeddb.worker.ts
Normal file
@ -0,0 +1,381 @@
|
||||
/**
|
||||
* Database Web Worker - Handles all IndexedDB operations in background
|
||||
*/
|
||||
|
||||
import type {
|
||||
StoreDefinition,
|
||||
WorkerMessagePayload,
|
||||
WorkerMessageResponse,
|
||||
BatchWriteItem
|
||||
} from './worker.types';
|
||||
|
||||
const DB_NAME = '4nk';
|
||||
const DB_VERSION = 1;
|
||||
|
||||
// ============================================
|
||||
// STORE DEFINITIONS
|
||||
// ============================================
|
||||
|
||||
const STORE_DEFINITIONS: Record<string, StoreDefinition> = {
|
||||
AnkLabels: {
|
||||
name: 'labels',
|
||||
options: { keyPath: 'emoji' },
|
||||
indices: [],
|
||||
},
|
||||
AnkWallet: {
|
||||
name: 'wallet',
|
||||
options: { keyPath: 'pre_id' },
|
||||
indices: [],
|
||||
},
|
||||
AnkProcess: {
|
||||
name: 'processes',
|
||||
options: {},
|
||||
indices: [],
|
||||
},
|
||||
AnkSharedSecrets: {
|
||||
name: 'shared_secrets',
|
||||
options: {},
|
||||
indices: [],
|
||||
},
|
||||
AnkUnconfirmedSecrets: {
|
||||
name: 'unconfirmed_secrets',
|
||||
options: { autoIncrement: true },
|
||||
indices: [],
|
||||
},
|
||||
AnkPendingDiffs: {
|
||||
name: 'diffs',
|
||||
options: { keyPath: 'value_commitment' },
|
||||
indices: [
|
||||
{ name: 'byStateId', keyPath: 'state_id', options: { unique: false } },
|
||||
{ name: 'byNeedValidation', keyPath: 'need_validation', options: { unique: false } },
|
||||
{ name: 'byStatus', keyPath: 'validation_status', options: { unique: false } },
|
||||
],
|
||||
},
|
||||
AnkData: {
|
||||
name: 'data',
|
||||
options: {},
|
||||
indices: [],
|
||||
},
|
||||
};
|
||||
|
||||
let db: IDBDatabase | null = null;
|
||||
|
||||
// ============================================
|
||||
// DATABASE INITIALIZATION
|
||||
// ============================================
|
||||
|
||||
async function openDatabase(): Promise<IDBDatabase> {
|
||||
if (db) {
|
||||
return db;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
||||
const database = (event.target as IDBOpenDBRequest).result;
|
||||
|
||||
Object.values(STORE_DEFINITIONS).forEach(({ name, options, indices }) => {
|
||||
if (!database.objectStoreNames.contains(name)) {
|
||||
const store = database.createObjectStore(name, options);
|
||||
|
||||
indices.forEach(({ name: indexName, keyPath, options: indexOptions }) => {
|
||||
store.createIndex(indexName, keyPath, indexOptions);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
db = request.result;
|
||||
resolve(db);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// WRITE OPERATIONS
|
||||
// ============================================
|
||||
|
||||
async function addObject(storeName: string, object: any, key?: IDBValidKey): Promise<{ success: boolean }> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let request: IDBRequest;
|
||||
if (key !== null && key !== undefined) {
|
||||
request = store.put(object, key);
|
||||
} else {
|
||||
request = store.put(object);
|
||||
}
|
||||
|
||||
request.onsuccess = () => resolve({ success: true });
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function batchWriting(storeName: string, objects: BatchWriteItem[]): Promise<{ success: boolean }> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
for (const { key, object } of objects) {
|
||||
if (key !== null && key !== undefined) {
|
||||
store.put(object, key);
|
||||
} else {
|
||||
store.put(object);
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
tx.oncomplete = () => resolve({ success: true });
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// READ OPERATIONS
|
||||
// ============================================
|
||||
|
||||
async function getObject(storeName: string, key: IDBValidKey): Promise<any> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.get(key);
|
||||
request.onsuccess = () => resolve(request.result ?? null);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function dumpStore(storeName: string): Promise<Record<string, any>> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const result: Record<string, any> = {};
|
||||
const request = store.openCursor();
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
const cursor = (event.target as IDBRequest<IDBCursorWithValue | null>).result;
|
||||
if (cursor) {
|
||||
result[cursor.key as string] = cursor.value;
|
||||
cursor.continue();
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function getAllObjects(storeName: string): Promise<any[]> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.getAll();
|
||||
request.onsuccess = () => resolve(request.result || []);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function getMultipleObjects(storeName: string, keys: IDBValidKey[]): Promise<any[]> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
const requests = keys.map((key) => {
|
||||
return new Promise<any>((resolve) => {
|
||||
const request = store.get(key);
|
||||
request.onsuccess = () => resolve(request.result || null);
|
||||
request.onerror = () => {
|
||||
console.error(`Error fetching key ${key}:`, request.error);
|
||||
resolve(null);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const results = await Promise.all(requests);
|
||||
return results.filter(result => result !== null);
|
||||
}
|
||||
|
||||
async function getAllObjectsWithFilter(storeName: string, filterFn?: string): Promise<any[]> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.getAll();
|
||||
request.onsuccess = () => {
|
||||
const allItems = request.result || [];
|
||||
if (filterFn) {
|
||||
const filter = new Function('item', `return ${filterFn}`) as (item: any) => boolean;
|
||||
resolve(allItems.filter(filter));
|
||||
} else {
|
||||
resolve(allItems);
|
||||
}
|
||||
};
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// DELETE OPERATIONS
|
||||
// ============================================
|
||||
|
||||
async function deleteObject(storeName: string, key: IDBValidKey): Promise<{ success: boolean }> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.delete(key);
|
||||
request.onsuccess = () => resolve({ success: true });
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function clearStore(storeName: string): Promise<{ success: boolean }> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.clear();
|
||||
request.onsuccess = () => resolve({ success: true });
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INDEX OPERATIONS
|
||||
// ============================================
|
||||
|
||||
async function requestStoreByIndex(storeName: string, indexName: string, requestValue: IDBValidKey): Promise<any[]> {
|
||||
const database = await openDatabase();
|
||||
const tx = database.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
const index = store.index(indexName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = index.getAll(requestValue);
|
||||
request.onsuccess = () => {
|
||||
const allItems = request.result;
|
||||
const filtered = allItems.filter((item: any) => item.state_id === requestValue);
|
||||
resolve(filtered);
|
||||
};
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// UTILITY FUNCTIONS
|
||||
// ============================================
|
||||
|
||||
function getStoreList(): Record<string, string> {
|
||||
const storeList: Record<string, string> = {};
|
||||
Object.keys(STORE_DEFINITIONS).forEach((key) => {
|
||||
storeList[key] = STORE_DEFINITIONS[key].name;
|
||||
});
|
||||
return storeList;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MESSAGE HANDLER
|
||||
// ============================================
|
||||
|
||||
self.addEventListener('message', async (event: MessageEvent<WorkerMessagePayload>) => {
|
||||
const { type, payload, id } = event.data;
|
||||
|
||||
try {
|
||||
let result: any;
|
||||
|
||||
switch (type) {
|
||||
case 'INIT':
|
||||
await openDatabase();
|
||||
result = { success: true };
|
||||
break;
|
||||
|
||||
case 'ADD_OBJECT':
|
||||
result = await addObject(payload.storeName, payload.object, payload.key);
|
||||
break;
|
||||
|
||||
case 'BATCH_WRITING':
|
||||
result = await batchWriting(payload.storeName, payload.objects);
|
||||
break;
|
||||
|
||||
case 'GET_OBJECT':
|
||||
result = await getObject(payload.storeName, payload.key);
|
||||
break;
|
||||
|
||||
case 'DUMP_STORE':
|
||||
result = await dumpStore(payload.storeName);
|
||||
break;
|
||||
|
||||
case 'DELETE_OBJECT':
|
||||
result = await deleteObject(payload.storeName, payload.key);
|
||||
break;
|
||||
|
||||
case 'CLEAR_STORE':
|
||||
result = await clearStore(payload.storeName);
|
||||
break;
|
||||
|
||||
case 'REQUEST_STORE_BY_INDEX':
|
||||
result = await requestStoreByIndex(
|
||||
payload.storeName,
|
||||
payload.indexName,
|
||||
payload.request
|
||||
);
|
||||
break;
|
||||
|
||||
case 'GET_ALL_OBJECTS':
|
||||
result = await getAllObjects(payload.storeName);
|
||||
break;
|
||||
|
||||
case 'GET_MULTIPLE_OBJECTS':
|
||||
result = await getMultipleObjects(payload.storeName, payload.keys);
|
||||
break;
|
||||
|
||||
case 'GET_ALL_OBJECTS_WITH_FILTER':
|
||||
result = await getAllObjectsWithFilter(payload.storeName, payload.filterFn);
|
||||
break;
|
||||
|
||||
case 'GET_STORE_LIST':
|
||||
result = getStoreList();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown message type: ${type}`);
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
id,
|
||||
type: 'SUCCESS',
|
||||
result,
|
||||
} as WorkerMessageResponse);
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
id,
|
||||
type: 'ERROR',
|
||||
error: (error as Error).message || String(error),
|
||||
} as WorkerMessageResponse);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// INITIALIZATION
|
||||
// ============================================
|
||||
|
||||
openDatabase().catch((error) => {
|
||||
console.error('[Database Worker] Failed to initialize database:', error);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user