@eka-care/patient-ts-sdk
Version:
TypeScript SDK for Trinity Patient Profile Management System
383 lines (382 loc) • 12.1 kB
JavaScript
/**
* Trinity Profiles SDK
*
* A TypeScript SDK for interacting with the Trinity Patient Profile Management System.
* This SDK provides a type-safe, easy-to-use interface for managing patient profiles
* through the Trinity API.
*
* @example
* ```typescript
* import { TrinityProfilesSDK } from './trinity-profiles-sdk';
*
* const sdk = new TrinityProfilesSDK({
* baseUrl: 'https://api.trinity.example.com',
* accessToken: 'your-access-token'
* });
*
* // Create a patient
* const patient = await sdk.patients.create({
* oid: 'unique-patient-id',
* gen: 'M',
* dob: '1990-01-01',
* fn: 'John',
* ln: 'Doe'
* });
* ```
*/
import { HttpClient } from './client';
import { PatientMethods } from './methods/patients';
import { SearchMethods } from './methods/search';
import { UtilsMethods } from './methods/utils';
/**
* Main SDK class
*/
export class TrinityProfilesSDK {
/**
* Initialize the Trinity Profiles SDK
*
* @param config SDK configuration
*
* @example
* ```typescript
* const sdk = new TrinityProfilesSDK({
* baseUrl: 'https://api.trinity.example.com',
* accessToken: 'your-jwt-token',
* workspaceId: 'your-workspace-id',
* enableLocalSearch: true, // optional, enables local search functionality
* timeout: 30000 // optional, defaults to 30s
* });
* ```
*/
constructor(config) {
// Add worker property
this.syncWorker = null;
// Validate configuration
if (!config.baseUrl) {
throw new Error('baseUrl is required');
}
if (!config.workspaceId) {
throw new Error('workspaceId is required');
}
// Initialize HTTP client
this.client = new HttpClient(config);
// Initialize method groups
this.patients = new PatientMethods(this.client);
this.search = new SearchMethods(this.client, config);
this.utils = new UtilsMethods(this.client);
// Set up IndexedDB update callback for patients
if (config.workspaceId) {
this.patients.setIndexedDBUpdateCallback(async (patient) => {
const indexedDB = this.search.getDataLoader()?.getIndexedDB();
if (indexedDB) {
await indexedDB.updatePatient(patient);
}
});
}
}
/**
* Static method to get the singleton instance with optional initialization
*
* @param config Optional SDK configuration for initialization
* @returns Singleton instance of TrinityProfilesSDK
*
* @example
* ```typescript
* // Get instance without initialization
* const sdk = TrinityProfilesSDK.getInstance();
*
* // Get instance with initialization
* const sdk = TrinityProfilesSDK.getInstance({
* baseUrl: 'https://api.trinity.example.com',
* accessToken: 'your-jwt-token',
* workspaceId: 'your-workspace-id'
* });
* ```
*/
static getInstance(config) {
if (!TrinityProfilesSDK.instance) {
if (!config) {
throw new Error('Configuration is required for first initialization. Please provide SdkConfig.');
}
TrinityProfilesSDK.instance = new TrinityProfilesSDK(config);
}
return TrinityProfilesSDK.instance;
}
/**
* Reset the singleton instance (useful for testing or re-initialization)
*
* @example
* ```typescript
* TrinityProfilesSDK.resetInstance();
* ```
*/
static resetInstance() {
if (TrinityProfilesSDK.instance) {
TrinityProfilesSDK.instance.destroy();
TrinityProfilesSDK.instance = null;
}
}
/**
* Update the access token (for token refresh scenarios)
*
* @param newToken New access token
*
* @example
* ```typescript
* // After token refresh
* sdk.updateAccessToken(newAccessToken);
* ```
*/
updateAccessToken(newToken) {
if (!newToken) {
throw new Error('Access token cannot be empty');
}
// Get current config to preserve settings
const currentConfig = this.client.getConfig();
const newConfig = {
baseUrl: currentConfig.baseUrl,
accessToken: newToken,
workspaceId: currentConfig.workspaceId || '',
timeout: currentConfig.timeout,
};
// Create new client with updated token
this.client = new HttpClient(newConfig);
// Reinitialize method groups with new client
this.patients = new PatientMethods(this.client);
this.search = new SearchMethods(this.client, newConfig);
this.utils = new UtilsMethods(this.client);
// Re-establish IndexedDB update callback for patients
if (newConfig.workspaceId) {
this.patients.setIndexedDBUpdateCallback(async (patient) => {
const indexedDB = this.search.getDataLoader()?.getIndexedDB();
if (indexedDB) {
await indexedDB.updatePatient(patient);
}
});
}
}
/**
* Test the SDK connection
*
* @returns Promise that resolves if connection is successful
*
* @example
* ```typescript
* try {
* await sdk.testConnection();
* console.log('SDK is connected successfully');
* } catch (error) {
* console.error('Connection failed:', error.message);
* }
* ```
*/
async testConnection() {
try {
// Try a simple search request to test connectivity
await this.search.bulkGet([]);
}
catch (error) {
throw new Error(`SDK connection test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Initialize local search functionality
*
* @example
* ```typescript
* await sdk.initializeLocalSearch();
* ```
*/
// TO LOAD DATA
async initializeLocalSearch() {
await this.search.initializeLocalSearch();
// Start sync if enabled and Worker is supported
if (this.isLocalSearchEnabled()) {
await this.startSyncWithoutWorker();
}
// TODO
// if (this.shouldUseWorker()) {
// console.log("worker")
// await this.startSyncWithWorker();
// } else if (this.isLocalSearchEnabled()) {
// console.log("without worker")
// await this.startSyncWithoutWorker();
// }
}
// TO SEARCH
async searchPatientByPrefix(prefix, limit = 50, select) {
return await this.search.searchByPrefix(prefix, limit, select);
}
// TO CREATE PATIENT
async createPatient(patient) {
return await this.patients.create(patient);
}
// TO UPDATE PATIENT
/**
* Start background data synchronization
*
* @param callbacks Optional callbacks for sync progress
*
* @example
* ```typescript
* await sdk.startLocalSync({
* onProgress: (progress) => console.log(`Synced ${progress.progress} records`),
* onComplete: () => console.log('Sync complete'),
* onError: (error) => console.error('Sync error:', error)
* });
* ```
*/
async startLocalSync(callbacks) {
if (this.shouldUseWorker()) {
await this.startSyncWithWorker(callbacks);
}
else if (this.isLocalSearchEnabled()) {
await this.startSyncWithoutWorker(callbacks);
}
}
/**
* Check if local search data is available
*
* @returns Promise that resolves to true if local data exists
*
* @example
* ```typescript
* const hasData = await sdk.hasLocalSearchData();
* if (!hasData) {
* await sdk.startLocalSync();
* }
* ```
*/
async hasLocalSearchData() {
return await this.search.hasLocalSearchData();
}
/**
* Check if sync is complete
*
* @returns True if sync is complete
*/
isSyncComplete() {
return this.search.isSyncCompleted();
}
/**
* Clear all local search data
*
* @example
* ```typescript
* await sdk.clearLocalSearchData();
* ```
*/
async clearLocalSearchData() {
await this.search.clearLocalSearchData();
}
/**
* Cleanup all resources including local search
* Call this when done with the SDK to prevent memory leaks
*
* @example
* ```typescript
* sdk.destroy();
* ```
*/
destroy() {
this.search.destroy();
if (this.syncWorker) {
this.syncWorker.terminate();
this.syncWorker = null;
}
}
/**
* Check if local search is enabled
*/
isLocalSearchEnabled() {
const config = this.client.getConfig();
return Boolean(config.workspaceId);
}
/**
* Check if we should use Web Worker
*/
shouldUseWorker() {
return this.isLocalSearchEnabled() && typeof Worker !== 'undefined';
}
/**
* Start sync with Web Worker
*/
async startSyncWithWorker(callbacks) {
if (this.syncWorker) {
this.syncWorker.terminate();
}
try {
// Create worker from the built file
this.syncWorker = new Worker('./dist/workers/sync-worker.js');
this.syncWorker.onmessage = (event) => {
const { type, payload } = event.data;
switch (type) {
case 'progress':
if (callbacks?.onProgress && payload?.progress) {
callbacks.onProgress(payload.progress);
}
break;
case 'complete':
this.search.setSyncComplete(true);
if (callbacks?.onComplete) {
callbacks.onComplete();
}
break;
case 'error':
if (callbacks?.onError && payload?.error) {
callbacks.onError(payload.error);
}
break;
}
};
this.syncWorker.onerror = (error) => {
if (callbacks?.onError) {
callbacks.onError(`Worker error: ${error.message}`);
}
};
// Start sync
this.syncWorker.postMessage({
type: 'start',
payload: { config: this.client.getConfig() }
});
}
catch (error) {
console.warn('Failed to start Web Worker, falling back to direct sync:', error);
await this.startSyncWithoutWorker(callbacks);
}
}
/**
* Start sync without Web Worker (direct)
*/
async startSyncWithoutWorker(callbacks) {
const dataLoader = this.search.getDataLoader();
if (!dataLoader) {
throw new Error('Local search not enabled');
}
try {
await dataLoader.loadAllData({
onProgress: callbacks?.onProgress,
onComplete: () => {
this.search.setSyncComplete(true);
if (callbacks?.onComplete) {
callbacks.onComplete();
}
},
onError: callbacks?.onError
});
}
catch (error) {
if (callbacks?.onError) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
callbacks.onError(errorMessage);
}
throw error;
}
}
}
TrinityProfilesSDK.instance = null;
// Export all types and errors for external use
export * from './errors';
export * from './types';
// Default export
export const getTrinitySDKInstance = (config) => TrinityProfilesSDK.getInstance(config);