@eka-care/patient-ts-sdk
Version:
TypeScript SDK for Trinity Patient Profile Management System
263 lines (262 loc) • 10.1 kB
JavaScript
/**
* IndexedDB service for local patient data storage
*/
export class IndexedDBService {
constructor(workspaceId) {
this.dbName = 'TrinityProfilesDB';
this.version = 1;
this.db = null;
this.workspaceId = workspaceId;
}
/**
* Initialize the IndexedDB connection
*/
async init() {
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.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
*/
getStoreName() {
return `patients_${this.workspaceId}`;
}
/**
* Store multiple patients in batch
*/
async batchStore(patients) {
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, limit = 50) {
if (!this.db)
throw new Error('Database not initialized');
const results = [];
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.result;
if (cursor && results.length < limit) {
const patient = cursor.value;
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) {
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) {
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, updates) {
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 = {
...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() {
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() {
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() {
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() {
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() {
if (this.db) {
this.db.close();
this.db = null;
}
}
}