@openmrs/esm-patient-common-lib 13.0.2-pre.12803 → 13.0.2-pre.12805

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-patient-common-lib",
3
- "version": "13.0.2-pre.12803",
3
+ "version": "13.0.2-pre.12805",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Library for common patient chart components",
6
6
  "browser": "dist/openmrs-esm-patient-common-lib.js",
@@ -1,2 +1 @@
1
1
  export * from './revalidation-utils';
2
- export * from './visit-mutations';
@@ -1,41 +1,46 @@
1
- import { vi, describe, it, expect, test } from 'vitest';
1
+ import { vi, describe, it, expect } from 'vitest';
2
2
  import {
3
- invalidateVisitHistory,
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('invalidateVisitHistory', () => {
14
- it('should invalidate visit history keys but not current visit keys', () => {
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
- invalidateVisitHistory(mockMutate, patientUuid);
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
- // Should invalidate visit history keys (with pagination params)
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
- // Should invalidate visit history keys (without includeInactive)
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
- // Should NOT invalidate current visit keys (with includeInactive=false)
36
- expect(matcherFn('/ws/rest/v1/visit?patient=test-patient-123&v=custom&includeInactive=false')).toBe(false);
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 keys
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 history and encounter invalidation functions', () => {
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 history table data without triggering global visit revalidation cascade.
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
- * - Current visit: /visit?patient=123&v=custom&includeInactive=false
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
- * This approach eliminates the 13+ cascade revalidation requests that were previously triggered
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 invalidateVisitHistory(mutate: KeyedMutator<unknown>, patientUuid: string): void {
18
+ export function invalidateVisits(mutate: KeyedMutator<unknown>, patientUuid: string): void {
44
19
  mutate((key) => {
45
- if (typeof key === 'string' && key.includes(`${restBaseUrl}/visit?patient=${patientUuid}`)) {
46
- // Current visit keys have includeInactive=false
47
- const isCurrentVisitKey = key.includes('includeInactive=false');
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
- * Invalidates only the current (active) visit cache for a specific patient.
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
- invalidateVisitHistory(mutate, patientUuid);
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
- }