@openmrs/esm-patient-common-lib 13.0.2-pre.12803 → 13.0.2-pre.12809
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/visit/index.ts
CHANGED
|
@@ -1,41 +1,46 @@
|
|
|
1
|
-
import { vi, describe, it, expect
|
|
1
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
invalidateVisits,
|
|
4
4
|
invalidatePatientEncounters,
|
|
5
5
|
invalidateVisitAndEncounterData,
|
|
6
|
-
invalidateCurrentVisit,
|
|
7
6
|
invalidateVisitByUuid,
|
|
8
7
|
} from './revalidation-utils';
|
|
9
8
|
|
|
10
9
|
const mockMutate = vi.fn();
|
|
11
10
|
|
|
12
11
|
describe('revalidation-utils', () => {
|
|
13
|
-
describe('
|
|
14
|
-
it('should invalidate visit history
|
|
12
|
+
describe('invalidateVisits', () => {
|
|
13
|
+
it('should invalidate active visit, visit history and visit-by-uuid keys', () => {
|
|
15
14
|
const patientUuid = 'test-patient-123';
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
invalidateVisits(mockMutate, patientUuid);
|
|
18
17
|
|
|
19
18
|
expect(mockMutate).toHaveBeenCalledTimes(1);
|
|
20
19
|
expect(mockMutate).toHaveBeenCalledWith(expect.any(Function));
|
|
21
20
|
|
|
22
|
-
// Test the cache key matcher function
|
|
23
21
|
const matcherFn = mockMutate.mock.calls[0][0];
|
|
24
22
|
|
|
25
|
-
//
|
|
23
|
+
// Active visit keys
|
|
24
|
+
expect(matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&includeInactive=false')).toBe(true);
|
|
25
|
+
|
|
26
|
+
// Visit history keys (with pagination params)
|
|
26
27
|
expect(
|
|
27
28
|
matcherFn(
|
|
28
29
|
'/ws/rest/v1/visit?patient=test-patient-123&v=custom:(uuid,location)&limit=10&startIndex=0&totalCount=true',
|
|
29
30
|
),
|
|
30
31
|
).toBe(true);
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
+
// Visit history keys (without includeInactive)
|
|
33
34
|
expect(matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom:(uuid,location)')).toBe(true);
|
|
34
35
|
|
|
35
|
-
//
|
|
36
|
-
expect(matcherFn('/ws/rest/v1/visit
|
|
36
|
+
// Visits fetched by UUID (e.g. the visit context)
|
|
37
|
+
expect(matcherFn('/ws/rest/v1/visit/test-visit-123?v=custom:(uuid,display)')).toBe(true);
|
|
38
|
+
expect(matcherFn('/ws/rest/v1/visit/test-visit-123/attribute')).toBe(true);
|
|
39
|
+
|
|
40
|
+
// Past visit keys (includeInactive=true)
|
|
41
|
+
expect(matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&includeInactive=true')).toBe(true);
|
|
37
42
|
|
|
38
|
-
// Should not match other patient's
|
|
43
|
+
// Should not match other patient's visit lists
|
|
39
44
|
expect(matcherFn('/ws/rest/v1/visit?patient=other-patient&v=custom')).toBe(false);
|
|
40
45
|
|
|
41
46
|
// Should not match non-visit endpoints
|
|
@@ -77,7 +82,7 @@ describe('revalidation-utils', () => {
|
|
|
77
82
|
});
|
|
78
83
|
|
|
79
84
|
describe('invalidateVisitAndEncounterData', () => {
|
|
80
|
-
it('should call both visit
|
|
85
|
+
it('should call both visit and encounter invalidation functions', () => {
|
|
81
86
|
const patientUuid = 'test-patient-123';
|
|
82
87
|
|
|
83
88
|
invalidateVisitAndEncounterData(mockMutate, patientUuid);
|
|
@@ -93,38 +98,13 @@ describe('revalidation-utils', () => {
|
|
|
93
98
|
|
|
94
99
|
// Visit matcher should work
|
|
95
100
|
expect(visitMatcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&limit=10')).toBe(true);
|
|
101
|
+
expect(visitMatcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&includeInactive=false')).toBe(true);
|
|
96
102
|
|
|
97
103
|
// Encounter matcher should work
|
|
98
104
|
expect(encounterMatcherFn('/ws/rest/v1/encounter?patient=test-patient-123&v=custom')).toBe(true);
|
|
99
105
|
});
|
|
100
106
|
});
|
|
101
107
|
|
|
102
|
-
describe('invalidateCurrentVisit', () => {
|
|
103
|
-
it('should invalidate only current visit keys', () => {
|
|
104
|
-
const patientUuid = 'test-patient-123';
|
|
105
|
-
|
|
106
|
-
invalidateCurrentVisit(mockMutate, patientUuid);
|
|
107
|
-
|
|
108
|
-
expect(mockMutate).toHaveBeenCalledTimes(1);
|
|
109
|
-
expect(mockMutate).toHaveBeenCalledWith(expect.any(Function));
|
|
110
|
-
|
|
111
|
-
const matcherFn = mockMutate.mock.calls[0][0];
|
|
112
|
-
|
|
113
|
-
// Should match current visit key (includeInactive=false)
|
|
114
|
-
expect(matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&includeInactive=false')).toBe(true);
|
|
115
|
-
|
|
116
|
-
// Should not match other visit keys
|
|
117
|
-
expect(matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&includeInactive=true')).toBe(false);
|
|
118
|
-
expect(
|
|
119
|
-
matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&limit=10&startIndex=0&totalCount=true'),
|
|
120
|
-
).toBe(false);
|
|
121
|
-
expect(matcherFn('/ws/rest/v1/visit/test-patient-123')).toBe(false);
|
|
122
|
-
|
|
123
|
-
// Should not match encounter keys
|
|
124
|
-
expect(matcherFn('/ws/rest/v1/encounter?patient=test-patient-123&v=custom')).toBe(false);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
108
|
describe('invalidateVisitByUuid', () => {
|
|
129
109
|
it('should invalidate only keys for the specified visit', () => {
|
|
130
110
|
const visitUuid = 'test-visit-123';
|
|
@@ -2,59 +2,25 @@ import type { KeyedMutator } from 'swr';
|
|
|
2
2
|
import { restBaseUrl } from '@openmrs/esm-framework';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Invalidates visit
|
|
6
|
-
*
|
|
7
|
-
* This function provides surgical SWR cache invalidation for visit operations. It targets only
|
|
8
|
-
* the paginated visit data used by the visit history table, avoiding unnecessary revalidation
|
|
9
|
-
* of the 28+ components that use current visit data via the useVisit hook.
|
|
10
|
-
*
|
|
11
|
-
* The implementation uses URL pattern matching to discriminate between:
|
|
12
|
-
* - Current visit keys (includeInactive=false) - these are NOT invalidated
|
|
13
|
-
* - Visit history keys (no includeInactive param, or pagination params) - these ARE invalidated
|
|
5
|
+
* Invalidates all visit data for a patient: active and past visit lists (e.g. `useVisit`, the visit
|
|
6
|
+
* history table) as well as individual visits fetched by UUID (e.g. the visit context).
|
|
14
7
|
*
|
|
15
8
|
* Cache key patterns:
|
|
16
|
-
* -
|
|
9
|
+
* - Active visit: /visit?patient=123&v=custom&includeInactive=false
|
|
17
10
|
* - Visit history: /visit?patient=123&v=custom:(uuid,location...)&limit=10&startIndex=0&totalCount=true
|
|
11
|
+
* - Visit by UUID: /visit/<visit-uuid>?v=custom:(uuid,...)
|
|
18
12
|
*
|
|
19
|
-
*
|
|
20
|
-
* by global mutateVisit() calls, reducing network traffic and improving performance.
|
|
13
|
+
* Visit-by-UUID keys do not carry the patient UUID, so all of them are invalidated.
|
|
21
14
|
*
|
|
22
15
|
* @param mutate - SWR mutate function from useSWRConfig()
|
|
23
16
|
* @param patientUuid - Patient UUID to target visit data for
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* import { useSWRConfig } from 'swr';
|
|
28
|
-
* import { invalidateVisitHistory } from '@openmrs/esm-patient-common-lib';
|
|
29
|
-
*
|
|
30
|
-
* function MyComponent({ patientUuid }) {
|
|
31
|
-
* const { mutate } = useSWRConfig();
|
|
32
|
-
*
|
|
33
|
-
* const handleVisitUpdate = async () => {
|
|
34
|
-
* // Perform visit operation...
|
|
35
|
-
* await updateVisit(visitData);
|
|
36
|
-
*
|
|
37
|
-
* // Invalidate only visit history table, not current visit components
|
|
38
|
-
* invalidateVisitHistory(mutate, patientUuid);
|
|
39
|
-
* };
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
17
|
*/
|
|
43
|
-
export function
|
|
18
|
+
export function invalidateVisits(mutate: KeyedMutator<unknown>, patientUuid: string): void {
|
|
44
19
|
mutate((key) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// Visit history keys typically have pagination parameters or no includeInactive parameter
|
|
50
|
-
const hasHistoryParams = key.includes('limit=') || key.includes('startIndex=') || key.includes('totalCount=');
|
|
51
|
-
const hasNoIncludeInactive = !key.includes('includeInactive');
|
|
52
|
-
|
|
53
|
-
// Invalidate if it's clearly a history key OR if it doesn't have includeInactive=false
|
|
54
|
-
// This ensures we target visit history while preserving current visit cache
|
|
55
|
-
return !isCurrentVisitKey && (hasHistoryParams || hasNoIncludeInactive);
|
|
56
|
-
}
|
|
57
|
-
return false;
|
|
20
|
+
return (
|
|
21
|
+
typeof key === 'string' &&
|
|
22
|
+
(key.includes(`${restBaseUrl}/visit?patient=${patientUuid}`) || key.includes(`${restBaseUrl}/visit/`))
|
|
23
|
+
);
|
|
58
24
|
});
|
|
59
25
|
}
|
|
60
26
|
|
|
@@ -82,25 +48,7 @@ export function invalidatePatientEncounters(mutate: KeyedMutator<unknown>, patie
|
|
|
82
48
|
}
|
|
83
49
|
|
|
84
50
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* This refreshes components using useVisit without triggering the visit revalidation cascade.
|
|
88
|
-
*
|
|
89
|
-
* @param mutate - SWR mutate function from useSWRConfig()
|
|
90
|
-
* @param patientUuid - Patient UUID to target current visit data for
|
|
91
|
-
*/
|
|
92
|
-
export function invalidateCurrentVisit(mutate: KeyedMutator<unknown>, patientUuid: string): void {
|
|
93
|
-
mutate((key) => {
|
|
94
|
-
return (
|
|
95
|
-
typeof key === 'string' &&
|
|
96
|
-
key.includes(`${restBaseUrl}/visit?patient=${patientUuid}`) &&
|
|
97
|
-
key.includes('includeInactive=false')
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Combination utility that invalidates both visit history and encounter data.
|
|
51
|
+
* Combination utility that invalidates both visit (active and past) and encounter data.
|
|
104
52
|
*
|
|
105
53
|
* This is commonly needed when operations affect both visit structure and encounter content,
|
|
106
54
|
* such as form submissions, visit note creation, or encounter deletion.
|
|
@@ -115,7 +63,7 @@ export function invalidateCurrentVisit(mutate: KeyedMutator<unknown>, patientUui
|
|
|
115
63
|
* ```
|
|
116
64
|
*/
|
|
117
65
|
export function invalidateVisitAndEncounterData(mutate: KeyedMutator<unknown>, patientUuid: string): void {
|
|
118
|
-
|
|
66
|
+
invalidateVisits(mutate, patientUuid);
|
|
119
67
|
invalidatePatientEncounters(mutate, patientUuid);
|
|
120
68
|
}
|
|
121
69
|
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import { useSWRConfig } from 'swr';
|
|
3
|
-
import { useVisit, type Visit, restBaseUrl } from '@openmrs/esm-framework';
|
|
4
|
-
import { usePatientChartStore } from '../store/patient-chart-store';
|
|
5
|
-
|
|
6
|
-
export interface VisitMutationOptions {
|
|
7
|
-
encounters?: boolean;
|
|
8
|
-
notes?: boolean;
|
|
9
|
-
orders?: boolean;
|
|
10
|
-
observations?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Hook providing optimistic update utilities for visit-related data.
|
|
15
|
-
* This hook helps reduce the number of network requests by updating SWR caches directly
|
|
16
|
-
* instead of triggering full revalidations across all visit-related hooks.
|
|
17
|
-
*
|
|
18
|
-
* @param patientUuid The UUID of the patient
|
|
19
|
-
* @returns Object containing optimistic update functions
|
|
20
|
-
*/
|
|
21
|
-
export function useOptimisticVisitMutations(patientUuid: string) {
|
|
22
|
-
const { mutate } = useSWRConfig();
|
|
23
|
-
const { visitContext, mutateVisitContext } = usePatientChartStore(patientUuid);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Optimistically updates visit data in SWR caches without triggering network requests.
|
|
27
|
-
* Updates both the current visit cache and visit list caches.
|
|
28
|
-
*/
|
|
29
|
-
const updateVisitOptimistically = useCallback(
|
|
30
|
-
(visitUuid: string, updates: Partial<Visit>) => {
|
|
31
|
-
// Update current visit SWR cache if it matches
|
|
32
|
-
if (visitContext?.uuid === visitUuid) {
|
|
33
|
-
mutateVisitContext?.();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Update visit lists across all hooks using regex pattern matching
|
|
37
|
-
const visitListPattern = new RegExp(`${restBaseUrl}/visit\\?patient=${patientUuid}`);
|
|
38
|
-
|
|
39
|
-
mutate(
|
|
40
|
-
visitListPattern,
|
|
41
|
-
(current: any) => {
|
|
42
|
-
if (!current?.data?.results) return current;
|
|
43
|
-
return {
|
|
44
|
-
...current,
|
|
45
|
-
data: {
|
|
46
|
-
...current.data,
|
|
47
|
-
results: current.data.results.map((visit: Visit) =>
|
|
48
|
-
visit.uuid === visitUuid ? { ...visit, ...updates } : visit,
|
|
49
|
-
),
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
false, // Don't revalidate
|
|
54
|
-
);
|
|
55
|
-
},
|
|
56
|
-
[visitContext, mutateVisitContext, mutate, patientUuid],
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Optimistically removes a visit from SWR caches without triggering network requests.
|
|
61
|
-
* Removes from visit lists and revalidates current visit if the deleted visit was current.
|
|
62
|
-
*/
|
|
63
|
-
const removeVisitOptimistically = useCallback(
|
|
64
|
-
(visitUuid: string) => {
|
|
65
|
-
// Remove from all visit list caches
|
|
66
|
-
const visitListPattern = new RegExp(`${restBaseUrl}/visit\\?patient=${patientUuid}`);
|
|
67
|
-
|
|
68
|
-
mutate(
|
|
69
|
-
visitListPattern,
|
|
70
|
-
(current: any) => {
|
|
71
|
-
if (!current?.data?.results) return current;
|
|
72
|
-
return {
|
|
73
|
-
...current,
|
|
74
|
-
data: {
|
|
75
|
-
...current.data,
|
|
76
|
-
results: current.data.results.filter((visit: Visit) => visit.uuid !== visitUuid),
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
false, // Don't revalidate
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
// If deleted visit was current, revalidate current visit to get new state
|
|
84
|
-
if (visitContext?.uuid === visitUuid) {
|
|
85
|
-
mutateVisitContext?.();
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
[visitContext, mutateVisitContext, mutate, patientUuid],
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Optimistically adds a new visit to SWR caches without triggering network requests.
|
|
93
|
-
* Adds to visit lists and updates current visit cache if appropriate.
|
|
94
|
-
*/
|
|
95
|
-
const addVisitOptimistically = useCallback(
|
|
96
|
-
(newVisit: Visit) => {
|
|
97
|
-
// Add to visit list caches
|
|
98
|
-
const visitListPattern = new RegExp(`${restBaseUrl}/visit\\?patient=${patientUuid}`);
|
|
99
|
-
|
|
100
|
-
mutate(
|
|
101
|
-
visitListPattern,
|
|
102
|
-
(current: any) => {
|
|
103
|
-
if (!current?.data?.results) return current;
|
|
104
|
-
return {
|
|
105
|
-
...current,
|
|
106
|
-
data: {
|
|
107
|
-
...current.data,
|
|
108
|
-
results: [newVisit, ...current.data.results],
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
|
-
false, // Don't revalidate
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
// If this is an active visit (no stopDatetime), update current visit cache
|
|
116
|
-
if (!newVisit.stopDatetime) {
|
|
117
|
-
mutate(
|
|
118
|
-
`${restBaseUrl}/visit?patient=${patientUuid}&v=custom`,
|
|
119
|
-
(current: any) => ({
|
|
120
|
-
...current,
|
|
121
|
-
data: newVisit,
|
|
122
|
-
}),
|
|
123
|
-
false, // Don't revalidate
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
[mutate, patientUuid],
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Selectively invalidates only specific visit-related data that cannot be optimistically updated.
|
|
132
|
-
* This replaces the global mutateVisit() pattern with targeted revalidation.
|
|
133
|
-
*/
|
|
134
|
-
const invalidateVisitRelatedData = useCallback(
|
|
135
|
-
(options: VisitMutationOptions = {}) => {
|
|
136
|
-
const promises = [];
|
|
137
|
-
|
|
138
|
-
if (options.encounters) {
|
|
139
|
-
promises.push(mutate(new RegExp(`${restBaseUrl}/encounter\\?patient=${patientUuid}`)));
|
|
140
|
-
}
|
|
141
|
-
if (options.notes) {
|
|
142
|
-
promises.push(mutate(new RegExp(`${restBaseUrl}/encounter\\?patient=${patientUuid}.*obs=`)));
|
|
143
|
-
}
|
|
144
|
-
if (options.orders) {
|
|
145
|
-
promises.push(mutate(new RegExp(`${restBaseUrl}/order\\?patient=${patientUuid}`)));
|
|
146
|
-
}
|
|
147
|
-
if (options.observations) {
|
|
148
|
-
promises.push(mutate(new RegExp(`${restBaseUrl}/obs\\?patient=${patientUuid}`)));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return Promise.all(promises);
|
|
152
|
-
},
|
|
153
|
-
[mutate, patientUuid],
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Revalidates only the current visit data with a single targeted request.
|
|
158
|
-
* This confirms the optimistic update with fresh server data.
|
|
159
|
-
*/
|
|
160
|
-
const revalidateCurrentVisit = useCallback(() => {
|
|
161
|
-
// Single revalidation request for current visit only
|
|
162
|
-
return mutate(`${restBaseUrl}/visit?patient=${patientUuid}&v=custom`);
|
|
163
|
-
}, [mutate, patientUuid]);
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Fallback function that triggers full revalidation when optimistic updates fail.
|
|
167
|
-
* This ensures data consistency when errors occur.
|
|
168
|
-
*/
|
|
169
|
-
const revalidateAllVisitData = useCallback(() => {
|
|
170
|
-
// Revalidate current visit
|
|
171
|
-
mutate(`${restBaseUrl}/visit?patient=${patientUuid}&v=custom`);
|
|
172
|
-
|
|
173
|
-
// Revalidate visit lists
|
|
174
|
-
mutate(new RegExp(`${restBaseUrl}/visit\\?patient=${patientUuid}`));
|
|
175
|
-
|
|
176
|
-
// Revalidate related data
|
|
177
|
-
return invalidateVisitRelatedData({
|
|
178
|
-
encounters: true,
|
|
179
|
-
notes: true,
|
|
180
|
-
orders: true,
|
|
181
|
-
observations: true,
|
|
182
|
-
});
|
|
183
|
-
}, [mutate, patientUuid, invalidateVisitRelatedData]);
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
addVisitOptimistically,
|
|
187
|
-
invalidateVisitRelatedData,
|
|
188
|
-
removeVisitOptimistically,
|
|
189
|
-
revalidateAllVisitData,
|
|
190
|
-
revalidateCurrentVisit,
|
|
191
|
-
updateVisitOptimistically,
|
|
192
|
-
};
|
|
193
|
-
}
|