UNPKG

@eka-care/patient-ts-sdk

Version:

TypeScript SDK for Trinity Patient Profile Management System

309 lines (257 loc) 9.24 kB
/** * IndexedDB service for local patient data storage */ import { LocalMinifiedPatient } from '../types'; export class IndexedDBService { private dbName = 'TrinityProfilesDB'; private version = 1; private db: IDBDatabase | null = null; private workspaceId: string; constructor(workspaceId: string) { this.workspaceId = workspaceId; } /** * Initialize the IndexedDB connection */ async init(): Promise<void> { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, this.version + 1); request.onerror = () => reject(request.error); request.onsuccess = () => { this.db = request.result; resolve(); }; request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; // Create object store for patients with workspace-specific naming const storeName = this.getStoreName(); if (!db.objectStoreNames.contains(storeName)) { const store = db.createObjectStore(storeName, { keyPath: 'oid' }); // Create indexes for search fields store.createIndex('fln', 'fln', { unique: false }); store.createIndex('mobile', 'mobile', { unique: false }); store.createIndex('username', 'username', { unique: false }); store.createIndex('u_ate', 'u_ate', { unique: false }); } }; }); } /** * Get workspace-specific store name */ private getStoreName(): string { return `patients_${this.workspaceId}`; } /** * Store multiple patients in batch */ async batchStore(patients: LocalMinifiedPatient[]): Promise<void> { if (!this.db) { await this.init(); } const storeName = this.getStoreName(); if (!this.db || !this.db.objectStoreNames.contains(storeName)) { // Close current connection and reinitialize this.close(); await this.init(); // Check again after reinitialization if (!this.db?.objectStoreNames.contains(storeName)) { throw new Error(`Object store '${storeName}' still not found after reinitialization`); } } return new Promise((resolve, reject) => { try { const transaction = this.db!.transaction([this.getStoreName()], 'readwrite'); const store = transaction.objectStore(this.getStoreName()); transaction.oncomplete = () => { resolve(); }; transaction.onerror = () => { reject(transaction.error); }; patients?.forEach((patient) => { store.put(patient); }); } catch (error) { console.error('Error creating transaction:', error); reject(error); } }); } /** * Search patients by prefix with field-specific logic */ async searchByPrefix(prefix: string, limit: number = 50): Promise<LocalMinifiedPatient[]> { if (!this.db) throw new Error('Database not initialized'); const results: LocalMinifiedPatient[] = []; const isNumeric = /^\d+$/.test(prefix); const lowerPrefix = prefix.toLowerCase(); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readonly'); const store = transaction.objectStore(this.getStoreName()); const request = store.openCursor(); request.onsuccess = (event) => { const cursor = (event.target as IDBRequest).result; if (cursor && results.length < limit) { const patient = cursor.value as LocalMinifiedPatient; let match = false; if (isNumeric) { // Search in mobile and username fields for numeric prefix if ( patient.mobile?.startsWith(prefix) || patient.username?.toLowerCase().startsWith(lowerPrefix) ) { match = true; } } else { // Search in fln and username fields for alphabetic prefix if ( patient.fln?.toLowerCase().startsWith(lowerPrefix) || patient.username?.toLowerCase().startsWith(lowerPrefix) ) { match = true; } } if (match) { results.push(patient); } cursor.continue(); } else { resolve(results); } }; request.onerror = () => reject(request.error); }); } /** * Get patient by OID (primary key) */ async getByOid(oid: string): Promise<LocalMinifiedPatient | null> { if (!this.db) throw new Error('Database not initialized'); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readonly'); const store = transaction.objectStore(this.getStoreName()); const request = store.get(oid); request.onsuccess = () => resolve(request.result || null); request.onerror = () => reject(request.error); }); } /** * Update single patient by OID */ async updatePatient(patient: LocalMinifiedPatient): Promise<void> { if (!this.db) throw new Error('Database not initialized'); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readwrite'); const store = transaction.objectStore(this.getStoreName()); const request = store.put(patient); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } /** * Partially update patient by OID - only updates specified fields */ async partialUpdatePatient(oid: string, updates: Partial<LocalMinifiedPatient>): Promise<void> { if (!this.db) throw new Error('Database not initialized'); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readwrite'); const store = transaction.objectStore(this.getStoreName()); // First get the existing patient record const getRequest = store.get(oid); getRequest.onsuccess = () => { const existingPatient = getRequest.result; if (!existingPatient) { reject(new Error(`Patient with OID ${oid} not found`)); return; } // Merge existing data with updates const updatedPatient: LocalMinifiedPatient = { ...existingPatient, ...updates, oid, // Ensure OID is preserved }; // Update the record const putRequest = store.put(updatedPatient); putRequest.onsuccess = () => resolve(); putRequest.onerror = () => reject(putRequest.error); }; getRequest.onerror = () => reject(getRequest.error); }); } /** * Check if any data exists */ async hasData(): Promise<boolean> { if (!this.db) { await this.init(); } const storeName = this.getStoreName(); if (!this.db || !this.db.objectStoreNames.contains(storeName)) { return false; } return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readonly'); const store = transaction.objectStore(this.getStoreName()); const request = store.count(); request.onsuccess = () => resolve(request.result > 0); request.onerror = () => reject(request.error); }); } /** * Get all patients (for internal use) */ async getAllPatients(): Promise<LocalMinifiedPatient[]> { if (!this.db) throw new Error('Database not initialized'); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readonly'); const store = transaction.objectStore(this.getStoreName()); const request = store.getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } /** * Get the latest update timestamp */ async getLatestUpdateTime(): Promise<number> { if (!this.db) throw new Error('Database not initialized'); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readonly'); const store = transaction.objectStore(this.getStoreName()); const index = store.index('u_ate'); const request = index.openCursor(null, 'prev'); request.onsuccess = () => { const cursor = request.result; if (cursor) { resolve(cursor.value.u_ate); } else { resolve(0); } }; request.onerror = () => reject(request.error); }); } /** * Clear all data for the current workspace */ async clearData(): Promise<void> { if (!this.db) throw new Error('Database not initialized'); return new Promise((resolve, reject) => { const transaction = this.db!.transaction([this.getStoreName()], 'readwrite'); const store = transaction.objectStore(this.getStoreName()); const request = store.clear(); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } /** * Close the database connection */ close(): void { if (this.db) { this.db.close(); this.db = null; } } }