vintasend-medplum 0.5.1 → 0.6.0
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MedplumClient } from '@medplum/core';
|
|
2
2
|
import type { BaseAttachmentManager } from 'vintasend/dist/services/attachment-manager/base-attachment-manager';
|
|
3
3
|
import type { BaseLogger } from 'vintasend/dist/services/loggers/base-logger';
|
|
4
|
-
import type { BaseNotificationBackend } from 'vintasend/dist/services/notification-backends/base-notification-backend';
|
|
4
|
+
import type { BaseNotificationBackend, NotificationFilter } from 'vintasend/dist/services/notification-backends/base-notification-backend';
|
|
5
5
|
import type { AttachmentFileRecord, StoredAttachment } from 'vintasend/dist/types/attachment';
|
|
6
6
|
import type { InputJsonValue } from 'vintasend/dist/types/json-values';
|
|
7
7
|
import type { AnyDatabaseNotification, DatabaseNotification, NotificationInput } from 'vintasend/dist/types/notification';
|
|
@@ -24,6 +24,29 @@ export declare class MedplumNotificationBackend<Config extends BaseNotificationT
|
|
|
24
24
|
* Inject logger for debugging and monitoring
|
|
25
25
|
*/
|
|
26
26
|
injectLogger(logger: BaseLogger): void;
|
|
27
|
+
/**
|
|
28
|
+
* Get the filter capabilities supported by Medplum backend.
|
|
29
|
+
* Medplum has limitations with OR operators and negation on date ranges.
|
|
30
|
+
*/
|
|
31
|
+
getFilterCapabilities(): {
|
|
32
|
+
'logical.and': boolean;
|
|
33
|
+
'logical.or': boolean;
|
|
34
|
+
'logical.not': boolean;
|
|
35
|
+
'logical.notNested': boolean;
|
|
36
|
+
'fields.status': boolean;
|
|
37
|
+
'fields.notificationType': boolean;
|
|
38
|
+
'fields.adapterUsed': boolean;
|
|
39
|
+
'fields.userId': boolean;
|
|
40
|
+
'fields.bodyTemplate': boolean;
|
|
41
|
+
'fields.subjectTemplate': boolean;
|
|
42
|
+
'fields.contextName': boolean;
|
|
43
|
+
'fields.sendAfterRange': boolean;
|
|
44
|
+
'fields.createdAtRange': boolean;
|
|
45
|
+
'fields.sentAtRange': boolean;
|
|
46
|
+
'negation.sendAfterRange': boolean;
|
|
47
|
+
'negation.createdAtRange': boolean;
|
|
48
|
+
'negation.sentAtRange': boolean;
|
|
49
|
+
};
|
|
27
50
|
/**
|
|
28
51
|
* Get attachment manager with null check
|
|
29
52
|
*/
|
|
@@ -37,6 +60,11 @@ export declare class MedplumNotificationBackend<Config extends BaseNotificationT
|
|
|
37
60
|
getFutureNotificationsFromUser(referenceString: Config['UserIdType'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
|
|
38
61
|
persistNotification(notification: NotificationInput<Config>): Promise<DatabaseNotification<Config>>;
|
|
39
62
|
persistNotificationUpdate(notificationId: Config['NotificationIdType'], notification: Partial<Omit<DatabaseNotification<Config>, 'id'>>): Promise<DatabaseNotification<Config>>;
|
|
63
|
+
/**
|
|
64
|
+
* Update FHIR identifiers from a partial notification update.
|
|
65
|
+
* Replaces identifier values for known systems when new values are provided.
|
|
66
|
+
*/
|
|
67
|
+
private updateIdentifiers;
|
|
40
68
|
getAllNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
|
|
41
69
|
getNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
42
70
|
bulkPersistNotifications(notifications: Omit<NotificationInput<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
|
|
@@ -48,12 +76,65 @@ export declare class MedplumNotificationBackend<Config extends BaseNotificationT
|
|
|
48
76
|
filterAllInAppUnreadNotifications(refenrenceString: Config['UserIdType']): Promise<DatabaseNotification<Config>[]>;
|
|
49
77
|
filterInAppUnreadNotifications(refenrenceString: Config['UserIdType'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
|
|
50
78
|
getUserEmailFromNotification(notificationId: Config['NotificationIdType']): Promise<string | undefined>;
|
|
51
|
-
|
|
79
|
+
storeAdapterAndContextUsed(notificationId: Config['NotificationIdType'], adapterKey: string, context: InputJsonValue): Promise<void>;
|
|
52
80
|
persistOneOffNotification(notification: Omit<OneOffNotificationInput<Config>, 'id'>): Promise<DatabaseOneOffNotification<Config>>;
|
|
53
81
|
persistOneOffNotificationUpdate(notificationId: Config['NotificationIdType'], notification: Partial<Omit<DatabaseOneOffNotification<Config>, 'id'>>): Promise<DatabaseOneOffNotification<Config>>;
|
|
54
82
|
getOneOffNotification(notificationId: Config['NotificationIdType'], forUpdate: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
|
|
55
83
|
getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
|
|
56
84
|
getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Filter notifications using composable query filters.
|
|
87
|
+
* All filtering is delegated to the FHIR server via search parameters.
|
|
88
|
+
*
|
|
89
|
+
* **Supported filter fields** (translated to FHIR search parameters):
|
|
90
|
+
* - `status` → `Communication.status`
|
|
91
|
+
* - `notificationType` → `_tag`
|
|
92
|
+
* - `contextName` → `_tag`
|
|
93
|
+
* - `userId` → `recipient`
|
|
94
|
+
* - `adapterUsed` → `identifier` (system: vintasend adapter-used)
|
|
95
|
+
* - `bodyTemplate` → `identifier` (system: vintasend body-template)
|
|
96
|
+
* - `subjectTemplate` → `identifier` (system: vintasend subject-template)
|
|
97
|
+
* - `sendAfterRange` → `sent` (date comparators)
|
|
98
|
+
* - `sentAtRange` → `sent` (date comparators)
|
|
99
|
+
* - `createdAtRange` → `_lastUpdated` (date comparators)
|
|
100
|
+
*
|
|
101
|
+
* **Logical operators:**
|
|
102
|
+
* - `AND` — merges FHIR search parameters from sub-filters
|
|
103
|
+
* - `OR` — not supported by FHIR search (throws)
|
|
104
|
+
* - `NOT` — uses FHIR `:not` modifier (status, notificationType, userId only)
|
|
105
|
+
*/
|
|
106
|
+
filterNotifications(filter: NotificationFilter<Config>, page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Ensure the `_tag` parameter always includes `notification`.
|
|
109
|
+
*/
|
|
110
|
+
private ensureNotificationTag;
|
|
111
|
+
/**
|
|
112
|
+
* Recursively build FHIR search parameters from a NotificationFilter tree.
|
|
113
|
+
* Handles field filters, AND, and NOT. OR is handled at the caller level.
|
|
114
|
+
*/
|
|
115
|
+
private buildFhirSearchParams;
|
|
116
|
+
/**
|
|
117
|
+
* Merge FHIR search parameters from multiple AND sub-filters.
|
|
118
|
+
*/
|
|
119
|
+
private mergeAndFilters;
|
|
120
|
+
/**
|
|
121
|
+
* Convert a leaf field filter to FHIR search parameters.
|
|
122
|
+
* Throws for fields that are not queryable via FHIR search.
|
|
123
|
+
*/
|
|
124
|
+
private fieldFilterToFhirParams;
|
|
125
|
+
/**
|
|
126
|
+
* Negate a simple field filter using the FHIR `:not` modifier.
|
|
127
|
+
* Only supports `status`, `notificationType`, and `userId`.
|
|
128
|
+
*/
|
|
129
|
+
private negateFilter;
|
|
130
|
+
/**
|
|
131
|
+
* Throw if the filter uses fields not queryable via FHIR search.
|
|
132
|
+
*/
|
|
133
|
+
private throwIfUnsupportedField;
|
|
134
|
+
/**
|
|
135
|
+
* Map VintaSend NotificationStatus to FHIR Communication status.
|
|
136
|
+
*/
|
|
137
|
+
private mapStatusToFhirStatus;
|
|
57
138
|
/**
|
|
58
139
|
* Store an attachment file record in the backend's database (Media resource).
|
|
59
140
|
* This is called after the AttachmentManager uploads a file and returns storageIdentifiers.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"medplum-backend.d.ts","sourceRoot":"","sources":["../src/medplum-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oEAAoE,CAAC;AAChH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAC9E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;
|
|
1
|
+
{"version":3,"file":"medplum-backend.d.ts","sourceRoot":"","sources":["../src/medplum-backend.ts"],"names":[],"mappings":"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"}
|
package/dist/medplum-backend.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { isFieldFilter } from 'vintasend/dist/services/notification-backends/base-notification-backend';
|
|
2
|
+
/** FHIR identifier systems used by MedplumNotificationBackend for searchable fields. */
|
|
3
|
+
const IDENTIFIER_SYSTEMS = {
|
|
4
|
+
bodyTemplate: 'http://vintasend.com/fhir/body-template',
|
|
5
|
+
subjectTemplate: 'http://vintasend.com/fhir/subject-template',
|
|
6
|
+
adapterUsed: 'http://vintasend.com/fhir/adapter-used',
|
|
7
|
+
};
|
|
1
8
|
export class MedplumNotificationBackend {
|
|
2
9
|
constructor(medplum, options = {
|
|
3
10
|
emailNotificationSubjectExtensionUrl: 'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
|
|
@@ -17,6 +24,31 @@ export class MedplumNotificationBackend {
|
|
|
17
24
|
injectLogger(logger) {
|
|
18
25
|
this.logger = logger;
|
|
19
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the filter capabilities supported by Medplum backend.
|
|
29
|
+
* Medplum has limitations with OR operators and negation on date ranges.
|
|
30
|
+
*/
|
|
31
|
+
getFilterCapabilities() {
|
|
32
|
+
return {
|
|
33
|
+
'logical.and': true,
|
|
34
|
+
'logical.or': false,
|
|
35
|
+
'logical.not': true,
|
|
36
|
+
'logical.notNested': false,
|
|
37
|
+
'fields.status': true,
|
|
38
|
+
'fields.notificationType': true,
|
|
39
|
+
'fields.adapterUsed': true,
|
|
40
|
+
'fields.userId': true,
|
|
41
|
+
'fields.bodyTemplate': true,
|
|
42
|
+
'fields.subjectTemplate': true,
|
|
43
|
+
'fields.contextName': true,
|
|
44
|
+
'fields.sendAfterRange': true,
|
|
45
|
+
'fields.createdAtRange': true,
|
|
46
|
+
'fields.sentAtRange': true,
|
|
47
|
+
'negation.sendAfterRange': false,
|
|
48
|
+
'negation.createdAtRange': false,
|
|
49
|
+
'negation.sentAtRange': false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
20
52
|
/**
|
|
21
53
|
* Get attachment manager with null check
|
|
22
54
|
*/
|
|
@@ -39,6 +71,15 @@ export class MedplumNotificationBackend {
|
|
|
39
71
|
const emailOrPhone = emailOrPhoneExtension?.valueString;
|
|
40
72
|
const firstName = firstNameExtension?.valueString;
|
|
41
73
|
const lastName = lastNameExtension?.valueString;
|
|
74
|
+
// Extract contextUsed from extension
|
|
75
|
+
const contextUsedExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/contextUsed');
|
|
76
|
+
const contextUsed = contextUsedExtension?.valueString
|
|
77
|
+
? JSON.parse(contextUsedExtension.valueString)
|
|
78
|
+
: null;
|
|
79
|
+
// Extract searchable fields from identifiers
|
|
80
|
+
const bodyTemplate = communication.identifier?.find((id) => id.system === IDENTIFIER_SYSTEMS.bodyTemplate)?.value || communication.payload?.[0]?.contentString || '';
|
|
81
|
+
const subjectTemplateFromId = communication.identifier?.find((id) => id.system === IDENTIFIER_SYSTEMS.subjectTemplate)?.value || subjectTemplate;
|
|
82
|
+
const adapterUsed = communication.identifier?.find((id) => id.system === IDENTIFIER_SYSTEMS.adapterUsed)?.value || null;
|
|
42
83
|
const baseNotification = {
|
|
43
84
|
id: notificationId,
|
|
44
85
|
notificationType: communication.meta?.tag?.[2]?.code,
|
|
@@ -46,12 +87,12 @@ export class MedplumNotificationBackend {
|
|
|
46
87
|
contextName: communication.meta?.tag?.[1]?.code,
|
|
47
88
|
contextParameters: JSON.parse(communication.note?.[0]?.text || '{}'),
|
|
48
89
|
sendAfter: communication.sent ? new Date(communication.sent) : null,
|
|
49
|
-
bodyTemplate
|
|
50
|
-
subjectTemplate:
|
|
90
|
+
bodyTemplate,
|
|
91
|
+
subjectTemplate: subjectTemplateFromId,
|
|
51
92
|
extraParams: {},
|
|
52
93
|
status: communication.status === 'completed' ? 'SENT' : communication.status === 'stopped' ? 'FAILED' : 'PENDING_SEND',
|
|
53
|
-
contextUsed
|
|
54
|
-
adapterUsed
|
|
94
|
+
contextUsed,
|
|
95
|
+
adapterUsed,
|
|
55
96
|
sentAt: communication.status === 'completed' ? new Date(communication.sent || new Date()) : null,
|
|
56
97
|
readAt: null,
|
|
57
98
|
createdAt: new Date(communication.meta?.lastUpdated || new Date()),
|
|
@@ -157,12 +198,19 @@ export class MedplumNotificationBackend {
|
|
|
157
198
|
payload.push(...attachmentPayload);
|
|
158
199
|
}
|
|
159
200
|
}
|
|
201
|
+
const identifiers = [
|
|
202
|
+
{ system: IDENTIFIER_SYSTEMS.bodyTemplate, value: notification.bodyTemplate },
|
|
203
|
+
];
|
|
204
|
+
if (notification.subjectTemplate) {
|
|
205
|
+
identifiers.push({ system: IDENTIFIER_SYSTEMS.subjectTemplate, value: notification.subjectTemplate });
|
|
206
|
+
}
|
|
160
207
|
const communication = {
|
|
161
208
|
resourceType: 'Communication',
|
|
162
209
|
status: 'in-progress',
|
|
163
210
|
sent: notification.sendAfter?.toISOString(),
|
|
164
211
|
topic: { text: notification.title || undefined },
|
|
165
212
|
payload,
|
|
213
|
+
identifier: identifiers,
|
|
166
214
|
recipient: [{ reference: notification.userId }],
|
|
167
215
|
note: [{ text: JSON.stringify(notification.contextParameters) }],
|
|
168
216
|
meta: {
|
|
@@ -185,10 +233,14 @@ export class MedplumNotificationBackend {
|
|
|
185
233
|
}
|
|
186
234
|
async persistNotificationUpdate(notificationId, notification) {
|
|
187
235
|
const existing = await this.medplum.readResource('Communication', notificationId);
|
|
236
|
+
// Update identifiers for searchable fields when they change
|
|
237
|
+
const existingIdentifiers = existing.identifier || [];
|
|
238
|
+
const updatedIdentifiers = this.updateIdentifiers(existingIdentifiers, notification);
|
|
188
239
|
const updated = {
|
|
189
240
|
...existing,
|
|
190
241
|
...(notification.status === 'SENT' && { status: 'completed' }),
|
|
191
242
|
...(notification.status === 'FAILED' && { status: 'stopped' }),
|
|
243
|
+
identifier: updatedIdentifiers,
|
|
192
244
|
meta: {
|
|
193
245
|
...existing.meta,
|
|
194
246
|
lastUpdated: new Date().toISOString(),
|
|
@@ -197,6 +249,32 @@ export class MedplumNotificationBackend {
|
|
|
197
249
|
const result = await this.medplum.updateResource(updated);
|
|
198
250
|
return this.mapToDatabaseNotification(result);
|
|
199
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Update FHIR identifiers from a partial notification update.
|
|
254
|
+
* Replaces identifier values for known systems when new values are provided.
|
|
255
|
+
*/
|
|
256
|
+
updateIdentifiers(existing, notification) {
|
|
257
|
+
const updated = [...existing];
|
|
258
|
+
const upsert = (system, value) => {
|
|
259
|
+
if (value === undefined)
|
|
260
|
+
return;
|
|
261
|
+
const idx = updated.findIndex((id) => id.system === system);
|
|
262
|
+
if (value === null) {
|
|
263
|
+
if (idx >= 0)
|
|
264
|
+
updated.splice(idx, 1);
|
|
265
|
+
}
|
|
266
|
+
else if (idx >= 0) {
|
|
267
|
+
updated[idx] = { ...updated[idx], system, value };
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
updated.push({ system, value });
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
upsert(IDENTIFIER_SYSTEMS.bodyTemplate, notification.bodyTemplate);
|
|
274
|
+
upsert(IDENTIFIER_SYSTEMS.subjectTemplate, notification.subjectTemplate);
|
|
275
|
+
upsert(IDENTIFIER_SYSTEMS.adapterUsed, notification.adapterUsed);
|
|
276
|
+
return updated;
|
|
277
|
+
}
|
|
200
278
|
async getAllNotifications() {
|
|
201
279
|
const communications = await this.medplum.searchResources('Communication', {
|
|
202
280
|
_tag: 'notification',
|
|
@@ -214,6 +292,12 @@ export class MedplumNotificationBackend {
|
|
|
214
292
|
async bulkPersistNotifications(notifications) {
|
|
215
293
|
const ids = [];
|
|
216
294
|
for (const notification of notifications) {
|
|
295
|
+
const bulkIdentifiers = [
|
|
296
|
+
{ system: IDENTIFIER_SYSTEMS.bodyTemplate, value: notification.bodyTemplate },
|
|
297
|
+
];
|
|
298
|
+
if (notification.subjectTemplate) {
|
|
299
|
+
bulkIdentifiers.push({ system: IDENTIFIER_SYSTEMS.subjectTemplate, value: notification.subjectTemplate });
|
|
300
|
+
}
|
|
217
301
|
const communication = {
|
|
218
302
|
resourceType: 'Communication',
|
|
219
303
|
status: 'in-progress',
|
|
@@ -229,6 +313,7 @@ export class MedplumNotificationBackend {
|
|
|
229
313
|
},
|
|
230
314
|
]
|
|
231
315
|
: [],
|
|
316
|
+
identifier: bulkIdentifiers,
|
|
232
317
|
recipient: [{ reference: notification.userId }],
|
|
233
318
|
note: [{ text: JSON.stringify(notification.contextParameters) }],
|
|
234
319
|
meta: {
|
|
@@ -349,11 +434,23 @@ export class MedplumNotificationBackend {
|
|
|
349
434
|
return undefined;
|
|
350
435
|
}
|
|
351
436
|
}
|
|
352
|
-
async
|
|
437
|
+
async storeAdapterAndContextUsed(notificationId, adapterKey, context) {
|
|
353
438
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
439
|
+
const contextUsedExtensionUrl = 'http://vintasend.com/fhir/StructureDefinition/contextUsed';
|
|
440
|
+
// Remove any existing contextUsed extension, then add the new one
|
|
441
|
+
const existingExtensions = (communication.extension || []).filter((ext) => ext.url !== contextUsedExtensionUrl);
|
|
442
|
+
// Upsert the adapterUsed identifier
|
|
443
|
+
const existingIdentifiers = (communication.identifier || []).filter((id) => id.system !== IDENTIFIER_SYSTEMS.adapterUsed);
|
|
354
444
|
const updated = {
|
|
355
445
|
...communication,
|
|
356
|
-
|
|
446
|
+
identifier: [
|
|
447
|
+
...existingIdentifiers,
|
|
448
|
+
{ system: IDENTIFIER_SYSTEMS.adapterUsed, value: adapterKey },
|
|
449
|
+
],
|
|
450
|
+
extension: [
|
|
451
|
+
...existingExtensions,
|
|
452
|
+
{ url: contextUsedExtensionUrl, valueString: JSON.stringify(context) },
|
|
453
|
+
],
|
|
357
454
|
};
|
|
358
455
|
await this.medplum.updateResource(updated);
|
|
359
456
|
}
|
|
@@ -377,12 +474,19 @@ export class MedplumNotificationBackend {
|
|
|
377
474
|
payload.push(...attachmentPayload);
|
|
378
475
|
}
|
|
379
476
|
}
|
|
477
|
+
const oneOffIdentifiers = [
|
|
478
|
+
{ system: IDENTIFIER_SYSTEMS.bodyTemplate, value: notification.bodyTemplate },
|
|
479
|
+
];
|
|
480
|
+
if (notification.subjectTemplate) {
|
|
481
|
+
oneOffIdentifiers.push({ system: IDENTIFIER_SYSTEMS.subjectTemplate, value: notification.subjectTemplate });
|
|
482
|
+
}
|
|
380
483
|
const communication = {
|
|
381
484
|
resourceType: 'Communication',
|
|
382
485
|
status: 'in-progress',
|
|
383
486
|
sent: notification.sendAfter?.toISOString(),
|
|
384
487
|
topic: { text: notification.title || undefined },
|
|
385
488
|
payload,
|
|
489
|
+
identifier: oneOffIdentifiers,
|
|
386
490
|
note: [{ text: JSON.stringify(notification.contextParameters) }],
|
|
387
491
|
extension: [
|
|
388
492
|
{ url: 'http://vintasend.com/fhir/StructureDefinition/emailOrPhone', valueString: notification.emailOrPhone },
|
|
@@ -408,10 +512,13 @@ export class MedplumNotificationBackend {
|
|
|
408
512
|
}
|
|
409
513
|
async persistOneOffNotificationUpdate(notificationId, notification) {
|
|
410
514
|
const existing = await this.medplum.readResource('Communication', notificationId);
|
|
515
|
+
const existingIdentifiers = existing.identifier || [];
|
|
516
|
+
const updatedIdentifiers = this.updateIdentifiers(existingIdentifiers, notification);
|
|
411
517
|
const updated = {
|
|
412
518
|
...existing,
|
|
413
519
|
...(notification.status === 'SENT' && { status: 'completed' }),
|
|
414
520
|
...(notification.status === 'FAILED' && { status: 'stopped' }),
|
|
521
|
+
identifier: updatedIdentifiers,
|
|
415
522
|
meta: {
|
|
416
523
|
...existing.meta,
|
|
417
524
|
lastUpdated: new Date().toISOString(),
|
|
@@ -444,6 +551,261 @@ export class MedplumNotificationBackend {
|
|
|
444
551
|
});
|
|
445
552
|
return communications.map((comm) => this.mapToDatabaseNotification(comm));
|
|
446
553
|
}
|
|
554
|
+
/* Notification filtering methods */
|
|
555
|
+
/**
|
|
556
|
+
* Filter notifications using composable query filters.
|
|
557
|
+
* All filtering is delegated to the FHIR server via search parameters.
|
|
558
|
+
*
|
|
559
|
+
* **Supported filter fields** (translated to FHIR search parameters):
|
|
560
|
+
* - `status` → `Communication.status`
|
|
561
|
+
* - `notificationType` → `_tag`
|
|
562
|
+
* - `contextName` → `_tag`
|
|
563
|
+
* - `userId` → `recipient`
|
|
564
|
+
* - `adapterUsed` → `identifier` (system: vintasend adapter-used)
|
|
565
|
+
* - `bodyTemplate` → `identifier` (system: vintasend body-template)
|
|
566
|
+
* - `subjectTemplate` → `identifier` (system: vintasend subject-template)
|
|
567
|
+
* - `sendAfterRange` → `sent` (date comparators)
|
|
568
|
+
* - `sentAtRange` → `sent` (date comparators)
|
|
569
|
+
* - `createdAtRange` → `_lastUpdated` (date comparators)
|
|
570
|
+
*
|
|
571
|
+
* **Logical operators:**
|
|
572
|
+
* - `AND` — merges FHIR search parameters from sub-filters
|
|
573
|
+
* - `OR` — not supported by FHIR search (throws)
|
|
574
|
+
* - `NOT` — uses FHIR `:not` modifier (status, notificationType, userId only)
|
|
575
|
+
*/
|
|
576
|
+
async filterNotifications(filter, page, pageSize) {
|
|
577
|
+
if ('or' in filter) {
|
|
578
|
+
throw new Error('OR filters are not supported by MedplumNotificationBackend (FHIR search does not support OR logic).');
|
|
579
|
+
}
|
|
580
|
+
const searchParams = this.buildFhirSearchParams(filter);
|
|
581
|
+
this.ensureNotificationTag(searchParams);
|
|
582
|
+
searchParams._count = pageSize.toString();
|
|
583
|
+
searchParams._offset = (page * pageSize).toString();
|
|
584
|
+
const communications = await this.medplum.searchResources('Communication', searchParams);
|
|
585
|
+
return communications.map((comm) => this.mapToDatabaseNotification(comm));
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Ensure the `_tag` parameter always includes `notification`.
|
|
589
|
+
*/
|
|
590
|
+
ensureNotificationTag(params) {
|
|
591
|
+
if (params._tag) {
|
|
592
|
+
if (!params._tag.split(',').includes('notification')) {
|
|
593
|
+
params._tag = `notification,${params._tag}`;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
params._tag = 'notification';
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Recursively build FHIR search parameters from a NotificationFilter tree.
|
|
602
|
+
* Handles field filters, AND, and NOT. OR is handled at the caller level.
|
|
603
|
+
*/
|
|
604
|
+
buildFhirSearchParams(filter) {
|
|
605
|
+
if ('or' in filter) {
|
|
606
|
+
throw new Error('OR filters are not supported by MedplumNotificationBackend (FHIR search does not support OR logic).');
|
|
607
|
+
}
|
|
608
|
+
if ('and' in filter) {
|
|
609
|
+
return this.mergeAndFilters(filter.and);
|
|
610
|
+
}
|
|
611
|
+
if ('not' in filter) {
|
|
612
|
+
return this.negateFilter(filter.not);
|
|
613
|
+
}
|
|
614
|
+
// Leaf field filter
|
|
615
|
+
return this.fieldFilterToFhirParams(filter);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Merge FHIR search parameters from multiple AND sub-filters.
|
|
619
|
+
*/
|
|
620
|
+
mergeAndFilters(subFilters) {
|
|
621
|
+
const merged = {};
|
|
622
|
+
for (const sub of subFilters) {
|
|
623
|
+
const params = this.buildFhirSearchParams(sub);
|
|
624
|
+
for (const [key, value] of Object.entries(params)) {
|
|
625
|
+
if (key in merged && merged[key] !== value) {
|
|
626
|
+
throw new Error(`Conflicting values for FHIR search parameter '${key}' in AND filter: ` +
|
|
627
|
+
`'${merged[key]}' vs '${value}'. ` +
|
|
628
|
+
'AND sub-filters must not set different values for the same search parameter.');
|
|
629
|
+
}
|
|
630
|
+
merged[key] = value;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return merged;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Convert a leaf field filter to FHIR search parameters.
|
|
637
|
+
* Throws for fields that are not queryable via FHIR search.
|
|
638
|
+
*/
|
|
639
|
+
fieldFilterToFhirParams(filter) {
|
|
640
|
+
const params = {};
|
|
641
|
+
// Status → Communication.status (comma-separated = OR)
|
|
642
|
+
if (filter.status !== undefined) {
|
|
643
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
644
|
+
const fhirStatuses = statuses
|
|
645
|
+
.map((s) => this.mapStatusToFhirStatus(s))
|
|
646
|
+
.filter((s) => s !== null);
|
|
647
|
+
if (fhirStatuses.length > 0) {
|
|
648
|
+
params.status = fhirStatuses.join(',');
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Notification type → _tag (comma-separated = AND in FHIR tags)
|
|
652
|
+
if (filter.notificationType !== undefined) {
|
|
653
|
+
const types = Array.isArray(filter.notificationType)
|
|
654
|
+
? filter.notificationType
|
|
655
|
+
: [filter.notificationType];
|
|
656
|
+
params._tag = types.join(',');
|
|
657
|
+
}
|
|
658
|
+
// contextName → _tag (appended to existing _tag)
|
|
659
|
+
if (filter.contextName !== undefined) {
|
|
660
|
+
params._tag = params._tag
|
|
661
|
+
? `${params._tag},${filter.contextName}`
|
|
662
|
+
: filter.contextName;
|
|
663
|
+
}
|
|
664
|
+
// UserId → recipient
|
|
665
|
+
if (filter.userId !== undefined) {
|
|
666
|
+
params.recipient = filter.userId;
|
|
667
|
+
}
|
|
668
|
+
// adapterUsed → identifier search with system
|
|
669
|
+
if (filter.adapterUsed !== undefined) {
|
|
670
|
+
const adapters = Array.isArray(filter.adapterUsed)
|
|
671
|
+
? filter.adapterUsed
|
|
672
|
+
: [filter.adapterUsed];
|
|
673
|
+
params.identifier = adapters
|
|
674
|
+
.map((a) => `${IDENTIFIER_SYSTEMS.adapterUsed}|${a}`)
|
|
675
|
+
.join(',');
|
|
676
|
+
}
|
|
677
|
+
// bodyTemplate → identifier search with system
|
|
678
|
+
if (filter.bodyTemplate !== undefined) {
|
|
679
|
+
params.identifier = params.identifier
|
|
680
|
+
? `${params.identifier},${IDENTIFIER_SYSTEMS.bodyTemplate}|${filter.bodyTemplate}`
|
|
681
|
+
: `${IDENTIFIER_SYSTEMS.bodyTemplate}|${filter.bodyTemplate}`;
|
|
682
|
+
}
|
|
683
|
+
// subjectTemplate → identifier search with system
|
|
684
|
+
if (filter.subjectTemplate !== undefined) {
|
|
685
|
+
params.identifier = params.identifier
|
|
686
|
+
? `${params.identifier},${IDENTIFIER_SYSTEMS.subjectTemplate}|${filter.subjectTemplate}`
|
|
687
|
+
: `${IDENTIFIER_SYSTEMS.subjectTemplate}|${filter.subjectTemplate}`;
|
|
688
|
+
}
|
|
689
|
+
// sendAfterRange → sent date comparators
|
|
690
|
+
if (filter.sendAfterRange) {
|
|
691
|
+
if (filter.sendAfterRange.from) {
|
|
692
|
+
params['sent:ge'] = filter.sendAfterRange.from.toISOString();
|
|
693
|
+
}
|
|
694
|
+
if (filter.sendAfterRange.to) {
|
|
695
|
+
params['sent:le'] = filter.sendAfterRange.to.toISOString();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// sentAtRange → sent date comparators
|
|
699
|
+
if (filter.sentAtRange) {
|
|
700
|
+
if (filter.sentAtRange.from) {
|
|
701
|
+
params['sent:ge'] = filter.sentAtRange.from.toISOString();
|
|
702
|
+
}
|
|
703
|
+
if (filter.sentAtRange.to) {
|
|
704
|
+
params['sent:le'] = filter.sentAtRange.to.toISOString();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// createdAtRange → _lastUpdated (best approximation in FHIR)
|
|
708
|
+
if (filter.createdAtRange) {
|
|
709
|
+
if (filter.createdAtRange.from) {
|
|
710
|
+
params['_lastUpdated:ge'] = filter.createdAtRange.from.toISOString();
|
|
711
|
+
}
|
|
712
|
+
if (filter.createdAtRange.to) {
|
|
713
|
+
params['_lastUpdated:le'] = filter.createdAtRange.to.toISOString();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return params;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Negate a simple field filter using the FHIR `:not` modifier.
|
|
720
|
+
* Only supports `status`, `notificationType`, and `userId`.
|
|
721
|
+
*/
|
|
722
|
+
negateFilter(inner) {
|
|
723
|
+
if (!isFieldFilter(inner)) {
|
|
724
|
+
throw new Error('NOT filters are only supported for simple field filters in MedplumNotificationBackend.');
|
|
725
|
+
}
|
|
726
|
+
this.throwIfUnsupportedField(inner);
|
|
727
|
+
const params = {};
|
|
728
|
+
const filter = inner;
|
|
729
|
+
if (filter.status !== undefined) {
|
|
730
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
731
|
+
const fhirStatuses = statuses
|
|
732
|
+
.map((s) => this.mapStatusToFhirStatus(s))
|
|
733
|
+
.filter((s) => s !== null);
|
|
734
|
+
if (fhirStatuses.length > 0) {
|
|
735
|
+
params['status:not'] = fhirStatuses.join(',');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (filter.notificationType !== undefined) {
|
|
739
|
+
const types = Array.isArray(filter.notificationType)
|
|
740
|
+
? filter.notificationType
|
|
741
|
+
: [filter.notificationType];
|
|
742
|
+
params['_tag:not'] = types.join(',');
|
|
743
|
+
}
|
|
744
|
+
if (filter.contextName !== undefined) {
|
|
745
|
+
params['_tag:not'] = params['_tag:not']
|
|
746
|
+
? `${params['_tag:not']},${filter.contextName}`
|
|
747
|
+
: filter.contextName;
|
|
748
|
+
}
|
|
749
|
+
if (filter.userId !== undefined) {
|
|
750
|
+
params['recipient:not'] = filter.userId;
|
|
751
|
+
}
|
|
752
|
+
if (filter.adapterUsed !== undefined) {
|
|
753
|
+
const adapters = Array.isArray(filter.adapterUsed)
|
|
754
|
+
? filter.adapterUsed
|
|
755
|
+
: [filter.adapterUsed];
|
|
756
|
+
params['identifier:not'] = adapters
|
|
757
|
+
.map((a) => `${IDENTIFIER_SYSTEMS.adapterUsed}|${a}`)
|
|
758
|
+
.join(',');
|
|
759
|
+
}
|
|
760
|
+
if (filter.bodyTemplate !== undefined) {
|
|
761
|
+
params['identifier:not'] = params['identifier:not']
|
|
762
|
+
? `${params['identifier:not']},${IDENTIFIER_SYSTEMS.bodyTemplate}|${filter.bodyTemplate}`
|
|
763
|
+
: `${IDENTIFIER_SYSTEMS.bodyTemplate}|${filter.bodyTemplate}`;
|
|
764
|
+
}
|
|
765
|
+
if (filter.subjectTemplate !== undefined) {
|
|
766
|
+
params['identifier:not'] = params['identifier:not']
|
|
767
|
+
? `${params['identifier:not']},${IDENTIFIER_SYSTEMS.subjectTemplate}|${filter.subjectTemplate}`
|
|
768
|
+
: `${IDENTIFIER_SYSTEMS.subjectTemplate}|${filter.subjectTemplate}`;
|
|
769
|
+
}
|
|
770
|
+
if (filter.sentAtRange) {
|
|
771
|
+
throw new Error('NOT filter on sentAtRange is not supported by MedplumNotificationBackend.');
|
|
772
|
+
}
|
|
773
|
+
if (filter.sendAfterRange) {
|
|
774
|
+
throw new Error('NOT filter on sendAfterRange is not supported by MedplumNotificationBackend.');
|
|
775
|
+
}
|
|
776
|
+
if (filter.createdAtRange) {
|
|
777
|
+
throw new Error('NOT filter on createdAtRange is not supported by MedplumNotificationBackend.');
|
|
778
|
+
}
|
|
779
|
+
if (Object.keys(params).length === 0) {
|
|
780
|
+
throw new Error('NOT filter must contain at least one supported negatable field (status, notificationType, contextName, userId, adapterUsed, bodyTemplate, subjectTemplate).');
|
|
781
|
+
}
|
|
782
|
+
return params;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Throw if the filter uses fields not queryable via FHIR search.
|
|
786
|
+
*/
|
|
787
|
+
throwIfUnsupportedField(_filter) {
|
|
788
|
+
// All fields in the current NotificationFilterFields are supported.
|
|
789
|
+
// This method is kept as a guard for future additions.
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Map VintaSend NotificationStatus to FHIR Communication status.
|
|
793
|
+
*/
|
|
794
|
+
mapStatusToFhirStatus(status) {
|
|
795
|
+
switch (status) {
|
|
796
|
+
case 'PENDING_SEND':
|
|
797
|
+
return 'in-progress';
|
|
798
|
+
case 'SENT':
|
|
799
|
+
case 'READ':
|
|
800
|
+
return 'completed';
|
|
801
|
+
case 'FAILED':
|
|
802
|
+
return 'stopped';
|
|
803
|
+
case 'CANCELLED':
|
|
804
|
+
return 'not-done';
|
|
805
|
+
default:
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
447
809
|
/* Attachment management methods */
|
|
448
810
|
/**
|
|
449
811
|
* Store an attachment file record in the backend's database (Media resource).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vintasend-medplum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@medplum/fhirtypes": "^5.0.10",
|
|
26
26
|
"pdfmake": "^0.2.23",
|
|
27
27
|
"pug": "^3.0.3",
|
|
28
|
-
"vintasend": "^0.
|
|
28
|
+
"vintasend": "^0.6.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@medplum/definitions": "^5.0.10",
|