parent
6425ea48f0
commit
7f8e592f6a
|
@ -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';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue