adding all FHIR client methods.

Adding documentation.
This commit is contained in:
Jason Kulatunga 2022-10-05 23:15:11 -07:00
parent 6425ea48f0
commit 7f8e592f6a
5 changed files with 158 additions and 18 deletions

View File

@ -1,6 +1,9 @@
import {BaseClient} from './base_client'; import {BaseClient} from './base_client';
import {Source} from '../../../models/database/source'; import {Source} from '../../../models/database/source';
// @ts-ignore
import * as BaseClient_GetRequest from './fixtures/BaseClient_GetRequest.json'; import * as BaseClient_GetRequest from './fixtures/BaseClient_GetRequest.json';
// @ts-ignore
import * as BaseClient_GetFhirVersion from './fixtures/BaseClient_GetFhirVersion.json'; import * as BaseClient_GetFhirVersion from './fixtures/BaseClient_GetFhirVersion.json';

View File

@ -26,6 +26,11 @@ export abstract class BaseClient {
} }
} }
/**
* This function gets the FhirVersion as specified by the api CapabilityStatement endpoint (metadata)
* https://build.fhir.org/capabilitystatement.html
* @constructor
*/
public async GetFhirVersion(): Promise<any> { public async GetFhirVersion(): Promise<any> {
return this.GetRequest("metadata") return this.GetRequest("metadata")
.then((resp) => { .then((resp) => {
@ -33,6 +38,12 @@ export abstract class BaseClient {
}) })
} }
/**
* This function will make an authenticated request against an OAuth protected resource. If the AccessToken used has expired, it will attempt
* to use a refresh token (if present) to get a new AccessToken.
* @param resourceSubpathOrNext
* @constructor
*/
public async GetRequest(resourceSubpathOrNext: string): Promise<any> { public async GetRequest(resourceSubpathOrNext: string): Promise<any> {
//check if the url is absolute //check if the url is absolute
@ -63,13 +74,6 @@ export abstract class BaseClient {
// Protected methods // Protected methods
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
protected GetPatientBundle(patientId: string): Promise<any> {
return this.GetRequest(`Patient/${patientId}/$everything`)
}
protected GetPatient(patientId: string): Promise<IResourceRaw> {
return this.GetRequest(`Patient/${patientId}`)
}
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Private methods // Private methods

View File

@ -1,11 +1,13 @@
import {FHIR401Client} from './fhir401_r4_client'; import {FHIR401Client} from './fhir401_r4_client';
import {Source} from '../../../models/database/source'; import {Source} from '../../../models/database/source';
import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json';
import {IResourceBundleRaw} from '../../interface'; import {IResourceBundleRaw} from '../../interface';
import {ResourceFhir} from '../../../models/database/resource_fhir'; import {ResourceFhir} from '../../../models/database/resource_fhir';
import {NewRepositiory} from '../../../database/pouchdb_repository'; import {NewRepositiory} from '../../../database/pouchdb_repository';
import {Base64} from '../../../utils/base64'; import {Base64} from '../../../utils/base64';
// @ts-ignore
import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json';
class TestClient extends FHIR401Client { class TestClient extends FHIR401Client {
constructor(source: Source) { constructor(source: Source) {
super(source); super(source);

View File

@ -13,28 +13,106 @@ export class FHIR401Client extends BaseClient implements IClient {
super(source); super(source);
} }
/**
* This function attempts to retrieve a Patient Bundle and sync all resources to the database
* @param db
* @constructor
*/
public async SyncAll(db: IDatabaseRepository): Promise<string[]> { public async SyncAll(db: IDatabaseRepository): Promise<string[]> {
const bundle = await this.GetPatientBundle(this.source.patient) const bundle = await this.GetPatientBundle(this.source.patient)
const wrappedResourceModels = await this.ProcessBundle(bundle) const wrappedResourceModels = await this.ProcessBundle(bundle)
//todo, create the resources in dependency order //todo, create the resources in dependency order
//TODO bulk insert
// for(let dbModel of wrappedResourceModels){
// db.CreateResource(dbModel)
// }
console.log(wrappedResourceModels)
return db.CreateResources(wrappedResourceModels) return db.CreateResources(wrappedResourceModels)
} }
public async SyncAllBundle(db: IDatabaseRepository, bundleFile: any): Promise<any> { /**
return Promise.resolve(undefined); * If Patient-everything api endpoint is unavailable (SyncAll) this function can be used to search for each resource associated with a Patient
* and sync them to the database.
* @param db
* @param resourceNames
* @constructor
*/
public async SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<string[]>{
//Store the Patient
const patientResource = await this.GetPatient(this.source.patient)
const patientResourceFhir = new ResourceFhir()
patientResourceFhir.source_id = this.source._id
patientResourceFhir.source_resource_type = patientResource.resourceType
patientResourceFhir.source_resource_id = patientResource.id
patientResourceFhir.resource_raw = patientResource
await db.CreateResource(patientResourceFhir)
//error map storage.
let syncErrors = {}
//Store all other resources.
for(let resourceType of resourceNames) {
try {
let bundle = await this.GetResourceBundlePaginated(`${resourceType}?patient=${this.source.patient}`)
let wrappedResourceModels = await this.ProcessBundle(bundle)
for(let apiModel of wrappedResourceModels){
await db.CreateResource(apiModel)
}
}
catch (e) {
console.error(`An error occurred while processing ${resourceType} bundle ${this.source.patient}`)
syncErrors[resourceType] = e
continue
}
}
//TODO: correctly return newly inserted documents
return []
}
/**
* This function is used to sync all resources from a Bundle file. Not applicable to this Client
* @param db
* @param bundleFile
* @constructor
*/
public async SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<any> {
return Promise.reject(new Error("not implemented"));
} }
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Protected methods // Protected methods
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
/**
* This function attempts to retrieve the Patient Bundle using the Patient-everything api endpoint (if available)
* This response may be paginated
* https://hl7.org/fhir/operation-patient-everything.html
* @param patientId
* @constructor
* @protected
*/
protected GetPatientBundle(patientId: string): Promise<any> {
return this.GetResourceBundlePaginated(`Patient/${patientId}/$everything`)
}
/**
* This function retrieves a patient resource
* @param patientId
* @constructor
* @protected
*/
protected GetPatient(patientId: string): Promise<IResourceRaw> {
return this.GetRequest(`Patient/${patientId}`)
}
/**
* This function parses a FHIR Bundle and wraps each BundleEntry resource in a ResourceFhir object which can be stored in the DB.
* @param bundle
* @constructor
* @protected
*/
protected async ProcessBundle(bundle: IResourceBundleRaw): Promise<ResourceFhir[]> { protected async ProcessBundle(bundle: IResourceBundleRaw): Promise<ResourceFhir[]> {
// console.log(bundle) // console.log(bundle)
// process each entry in bundle // process each entry in bundle
@ -52,11 +130,55 @@ export class FHIR401Client extends BaseClient implements IClient {
// wrappedResourceModel.updated_at = bundleEntry.resource.meta?.lastUpdated // wrappedResourceModel.updated_at = bundleEntry.resource.meta?.lastUpdated
return wrappedResourceModel return wrappedResourceModel
}) })
} }
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Private methods // Private methods
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
/**
* Retrieve a resource bundle. While "next" link is present in response, continue to request urls and append BundleEntries
* @param relativeResourcePath
* @constructor
* @private
*/
private async GetResourceBundlePaginated(relativeResourcePath: string): Promise<IResourceBundleRaw> {
// https://www.hl7.org/fhir/patient-operation-everything.html
const bundle = await this.GetRequest(relativeResourcePath) as IResourceBundleRaw
let next: string
let prev: string
let self: string
for(let link of bundle.link || []){
if(link.relation == "next"){
next = link.url
} else if(link.relation == "self"){
self = link.url
} else if(link.relation == "previous"){
prev = link.url
}
}
while(next && next != self && next != prev){
console.debug(`Paginated request => ${next}`)
let nextBundle = await this.GetRequest(next) as IResourceBundleRaw
bundle.entry = bundle.entry.concat(nextBundle.entry)
next = "" //reset the pointers
self = ""
prev = ""
for(let link of nextBundle.link){
if(link.relation == "next"){
next = link.url
} else if(link.relation == "self"){
self = link.url
} else if(link.relation == "previous"){
prev = link.url
}
}
}
return bundle
}
} }

View File

@ -4,10 +4,19 @@ export interface IClient {
fhirVersion: string fhirVersion: string
GetRequest(resourceSubpath: string): Promise<any> GetRequest(resourceSubpath: string): Promise<any>
GetFhirVersion(): Promise<any> GetFhirVersion(): Promise<any>
/**
* This function attempts to retrieve a Patient Bundle and sync all resources to the database
* @param db
* @constructor
*/
SyncAll(db: IDatabaseRepository): Promise<any> SyncAll(db: IDatabaseRepository): Promise<any>
SyncAllByResourceName(db: IDatabaseRepository, resourceNames: string[]): Promise<string[]>
//Manual client ONLY functions //Manual client ONLY functions
SyncAllBundle(db: IDatabaseRepository, bundleFile: any): Promise<any> SyncAllFromBundleFile(db: IDatabaseRepository, bundleFile: any): Promise<any>
} }
//This is the "raw" Fhir resource //This is the "raw" Fhir resource