Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 47 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,22 @@ When we make a request to a server with any of library's methods, we'll probably
<summary>Simplified example</summary>

```TypeScript
import React from 'react';
import { Patient } from 'fhir/r4b';
import { getFHIRResource } from 'fhir-react/lib/services/fhir';
import { isFailure, isSuccess } from 'fhir-react/lib/libs/remoteData';
import { initServices, makeReference } from '@beda.software/fhir-react';
import { isFailure, isSuccess } from '@beda.software/remote-data';

const { getFHIRResource } = initServices('<FHIR-server-base-URL>');

async function loadPatientGender() {
const patientResponse = await getFHIRResource<Patient>({
resourceType: 'Patient',
id: 'patient-id',
});
const patientResponse = await getFHIRResource<Patient>(makeReference('Patient', 'patient-id'));
if (isSuccess(patientResponse)) {
return `Patient name is ${patientResponse.data.gender ?? 'unknown'}`;
return `Patient gender is ${patientResponse.data.gender ?? 'unknown'}`;
}
if (isFailure(patientResponse)) {
return `
Failed to request patient,
status: ${patientResponse.status},
error : ${patientResponse.error}
error: ${patientResponse.error}
`;
}
}
Expand Down Expand Up @@ -136,6 +134,42 @@ if (isSuccess(qrBundleResponse)) {
}
```

#### InactiveMapping

```typescript
import { initServices, makeReference } from '@beda.software/fhir-react';

const inactiveMapping: InactiveMapping = {
DocumentReference: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
Observation: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
Location: {
searchField: 'status',
statusField: 'status',
value: 'inactive',
},
Patient: {
searchField: 'active',
statusField: 'active',
value: false,
},
};
// With this init, `getFHIRResources` / `getAllFHIRResources` automatically
// exclude inactive patients (and other mapped types), and
// `deleteFHIRResource(reference)` marks the resource as inactive instead of removing it.

const { getFHIRResource } = initServices('<FHIR-server-base-URL>', inactiveMapping);

// The request will be: GET /Patient?active:not=false
```

### getAllFHIRResources

Get all found resources from all pages.
Expand Down Expand Up @@ -260,12 +294,14 @@ const bundleResponse = await saveFHIRResources([
], 'transaction');
```

### deleteFHIRResource(resources)
### deleteFHIRResource(resource, inactiveMapping?)

Actually it doesn't delete a resource, just mark it as deleted by altering its status (see `inactiveMapping` list in `fhir.ts`).
Does not remove the resource; marks it as deleted by updating its status (see `inactiveMapping` in `fhir.ts`). `inactiveMapping` is required: pass it as the second argument or when initializing services (`initServices(baseURL, inactiveMapping)` / `initServicesFromService(service, inactiveMapping)`). If omitted in both places, the method throws.

```TypeScript
await deleteFHIRResource(makeReference('Patient', 'patient-id'));
// or with per-call mapping:
await deleteFHIRResource(makeReference('Location', 'loc-1'), { Location: { searchField: 'status', statusField: 'status', value: 'inactive' } });
```

### forceDeleteFHIRResource(resource)
Expand Down
16 changes: 10 additions & 6 deletions src/services/fhir/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RemoteDataResult, RequestService, failure, isFailure, success } from '@

import { WithId, extractBundleResources } from '.';
import {
InactiveMapping,
NullableRecursivePartial,
create,
forceDelete,
Expand Down Expand Up @@ -58,18 +59,20 @@ export async function getFHIRResources<R extends Resource>(
service: RequestService,
resourceType: R['resourceType'],
searchParams: SearchParams,
extraPath?: string
extraPath?: string,
inactiveMapping?: InactiveMapping
): Promise<RemoteDataResult<Bundle<WithId<R>>>> {
return service(list(resourceType, searchParams, extraPath));
return service(list(resourceType, searchParams, extraPath, inactiveMapping));
}

export async function getAllFHIRResources<R extends Resource>(
service: RequestService,
resourceType: string,
params: SearchParams,
extraPath?: string
extraPath?: string,
inactiveMapping?: InactiveMapping
): Promise<RemoteDataResult<Bundle<WithId<R>>>> {
const resultBundleResponse = await getFHIRResources<R>(service, resourceType, params, extraPath);
const resultBundleResponse = await getFHIRResources<R>(service, resourceType, params, extraPath, inactiveMapping);

if (isFailure(resultBundleResponse)) {
return resultBundleResponse;
Expand Down Expand Up @@ -185,9 +188,10 @@ export async function patchFHIRResource<R extends Resource>(

export async function deleteFHIRResource<R extends Resource>(
service: RequestService,
resource: Reference
resource: Reference,
inactiveMapping: InactiveMapping
): Promise<RemoteDataResult<WithId<R>>> {
return service(markAsDeleted(resource));
return service(markAsDeleted(resource, inactiveMapping));
}

export async function forceDeleteFHIRResource<R extends Resource>(
Expand Down
69 changes: 10 additions & 59 deletions src/services/fhir/apiConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,74 +7,21 @@ import { buildQueryParams } from '@beda.software/remote-data';

import { parseFHIRReference } from '../../utils/fhir';

interface InactiveMappingItem {
export interface InactiveMappingItem {
searchField: string;
statusField: string;
value: any;
}

interface InactiveMapping {
export interface InactiveMapping {
[resourceType: string]: InactiveMappingItem;
}

const inactiveMapping: InactiveMapping = {
DocumentReference: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
Observation: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
Location: {
searchField: 'status',
statusField: 'status',
value: 'inactive',
},
Schedule: {
searchField: 'active',
statusField: 'active',
value: false,
},
Slot: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
Practitioner: {
searchField: 'active',
statusField: 'active',
value: false,
},
Patient: {
searchField: 'active',
statusField: 'active',
value: false,
},
User: {
searchField: 'active',
statusField: 'active',
value: false,
},
Note: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
EpisodeOfCare: {
searchField: 'status',
statusField: 'status',
value: 'entered-in-error',
},
};

export function isObject(value: any): boolean {
return typeof value === 'object' && value !== null;
}

function getInactiveSearchParam(resourceType: string) {
function getInactiveSearchParam(resourceType: string, inactiveMapping: InactiveMapping) {
const item = inactiveMapping[resourceType];

if (item) {
Expand Down Expand Up @@ -133,12 +80,16 @@ export function get(reference: Reference) {
export function list<R extends Resource>(
resourceType: R['resourceType'],
searchParams: SearchParams,
extraPath?: string
extraPath?: string,
inactiveMapping?: InactiveMapping
) {
return {
method: 'GET',
url: extraPath ? `/${resourceType}/${extraPath}` : `/${resourceType}`,
params: { ...searchParams, ...getInactiveSearchParam(resourceType) },
params: {
...searchParams,
...(inactiveMapping ? getInactiveSearchParam(resourceType, inactiveMapping) : {}),
},
};
}

Expand Down Expand Up @@ -174,7 +125,7 @@ export function patch<R extends Resource>(resource: NullableRecursivePartial<R>,
throw new Error('Resourse id and search parameters are not specified');
}

export function markAsDeleted(reference: Reference) {
export function markAsDeleted(reference: Reference, inactiveMapping: InactiveMapping) {
const { resourceType, id } = parseFHIRReference(reference)!;

if (!resourceType) {
Expand Down
27 changes: 20 additions & 7 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
saveFHIRResources,
updateFHIRResource,
} from './fhir/api';
import { InactiveMapping } from './fhir/apiConfigs';
import { SearchParams } from './search';

export * from './fhir';
Expand All @@ -29,8 +30,11 @@ export * from './search';
// This function is low-level alternative to initServices
// it's useful when you already have service initiated and don't want to create new axios instance
export function initServicesFromService(
service: <S = any, F = any>(config: AxiosRequestConfig) => Promise<RemoteDataResult<S, F>>
service: <S = any, F = any>(config: AxiosRequestConfig) => Promise<RemoteDataResult<S, F>>,
inactiveMapping?: InactiveMapping
) {
const initInactiveMapping = inactiveMapping;

return {
createFHIRResource: async <R extends Resource>(
resource: R,
Expand All @@ -52,14 +56,14 @@ export function initServicesFromService(
searchParams: SearchParams,
extraPath?: string
): Promise<RemoteDataResult<Bundle<WithId<R>>>> => {
return await getFHIRResources<R>(service, resourceType, searchParams, extraPath);
return await getFHIRResources<R>(service, resourceType, searchParams, extraPath, initInactiveMapping);
},
getAllFHIRResources: async <R extends Resource>(
resourceType: string,
searchParams: SearchParams,
extraPath?: string
): Promise<RemoteDataResult<Bundle<WithId<R>>>> => {
return await getAllFHIRResources<R>(service, resourceType, searchParams, extraPath);
return await getAllFHIRResources<R>(service, resourceType, searchParams, extraPath, initInactiveMapping);
},
findFHIRResource: async <R extends Resource>(
resourceType: R['resourceType'],
Expand All @@ -83,8 +87,17 @@ export function initServicesFromService(
): Promise<RemoteDataResult<WithId<R>>> => {
return await patchFHIRResource<R>(service, resource, searchParams);
},
deleteFHIRResource: async <R extends Resource>(resource: Reference): Promise<RemoteDataResult<WithId<R>>> => {
return await deleteFHIRResource<R>(service, resource);
deleteFHIRResource: async <R extends Resource>(
resource: Reference,
inactiveMapping?: InactiveMapping
): Promise<RemoteDataResult<WithId<R>>> => {
const mapping = inactiveMapping ?? initInactiveMapping;
if (mapping === undefined) {
throw new Error(
'inactiveMapping is required for deleteFHIRResource. Provide it as the second argument or when initializing services.'
);
}
return await deleteFHIRResource<R>(service, resource, mapping);
},
forceDeleteFHIRResource: async <R extends Resource>(
resource: Reference
Expand All @@ -109,8 +122,8 @@ export function initServicesFromService(
};
}

export function initServices(baseURL?: string) {
export function initServices(baseURL?: string, inactiveMapping?: InactiveMapping) {
const { service, ...rest } = remoteDataInit(baseURL);

return { ...initServicesFromService(service), ...rest };
return { ...initServicesFromService(service, inactiveMapping), ...rest };
}
Loading
Loading