Skip to content

Commit d2b62d8

Browse files
committed
Release vintasend-medplum@0.6.2
1 parent d755691 commit d2b62d8

3 files changed

Lines changed: 65 additions & 31 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vintasend-medplum",
3-
"version": "0.6.1",
3+
"version": "0.6.2",
44
"description": "",
55
"main": "dist/index.js",
66
"scripts": {
@@ -25,7 +25,7 @@
2525
"@medplum/fhirtypes": "^5.0.10",
2626
"pdfmake": "^0.2.23",
2727
"pug": "^3.0.3",
28-
"vintasend": "^0.6.1"
28+
"vintasend": "^0.6.2"
2929
},
3030
"devDependencies": {
3131
"@medplum/definitions": "^5.0.10",

src/__tests__/medplum-backend.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,13 @@ describe('MedplumNotificationBackend', () => {
507507

508508
const result = await backend.filterNotifications(filter, 1, 25);
509509

510-
expect(searchResourcesSpy).toHaveBeenCalledWith('Communication', {
511-
_tag: 'notification',
512-
'sent:ge': from.toISOString(),
513-
'sent:le': to.toISOString(),
514-
_count: '25',
515-
_offset: '25',
516-
});
510+
expect(searchResourcesSpy).toHaveBeenCalledWith('Communication', [
511+
["sent:ge", "2026-01-01T00:00:00.000Z"],
512+
["sent:le", "2026-01-31T23:59:59.999Z"],
513+
["_count", "25"],
514+
["_offset", "25"],
515+
["_tag", "notification"]
516+
]);
517517
expect(result).toEqual([]);
518518
});
519519

src/medplum-backend.ts

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -510,11 +510,12 @@ export class MedplumNotificationBackend<Config extends BaseNotificationTypeConfi
510510
async filterAllInAppUnreadNotifications(
511511
refenrenceString: Config['UserIdType']
512512
): Promise<DatabaseNotification<Config>[]> {
513-
const communications = await this.medplum.searchResources('Communication', {
514-
status: 'completed',
515-
_tag: 'notification,in-app',
516-
recipient: refenrenceString,
517-
});
513+
const communications = await this.medplum.searchResources('Communication', [
514+
['status', 'completed'],
515+
['_tag', 'notification'],
516+
['_tag', 'in-app'],
517+
['recipient', refenrenceString as string],
518+
]);
518519
return communications
519520
.map((comm) => this.mapToDatabaseNotification(comm))
520521
.filter((notif): notif is DatabaseNotification<Config> => 'userId' in notif && !notif.readAt);
@@ -525,13 +526,14 @@ export class MedplumNotificationBackend<Config extends BaseNotificationTypeConfi
525526
page: number,
526527
pageSize: number
527528
): Promise<DatabaseNotification<Config>[]> {
528-
const communications = await this.medplum.searchResources('Communication', {
529-
status: 'completed',
530-
_tag: 'notification,in-app',
531-
recipient: refenrenceString,
532-
_count: pageSize.toString(),
533-
_offset: (page * pageSize).toString(),
534-
});
529+
const communications = await this.medplum.searchResources('Communication', [
530+
['status', 'completed'],
531+
['_tag', 'notification'],
532+
['_tag', 'in-app'],
533+
['recipient', refenrenceString as string],
534+
['_count', pageSize.toString()],
535+
['_offset', (page * pageSize).toString()],
536+
]);
535537
return communications
536538
.map((comm) => this.mapToDatabaseNotification(comm))
537539
.filter((notif): notif is DatabaseNotification<Config> => 'userId' in notif && !notif.readAt);
@@ -692,21 +694,23 @@ export class MedplumNotificationBackend<Config extends BaseNotificationTypeConfi
692694
}
693695

694696
async getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]> {
695-
const communications = await this.medplum.searchResources('Communication', {
696-
_tag: 'notification,one-off',
697-
});
697+
const communications = await this.medplum.searchResources('Communication', [
698+
['_tag', 'notification'],
699+
['_tag', 'one-off'],
700+
]);
698701
return communications.map((comm) => this.mapToDatabaseNotification(comm) as DatabaseOneOffNotification<Config>);
699702
}
700703

701704
async getOneOffNotifications(
702705
page: number,
703706
pageSize: number,
704707
): Promise<DatabaseOneOffNotification<Config>[]> {
705-
const communications = await this.medplum.searchResources('Communication', {
706-
_tag: 'notification,one-off',
707-
_count: pageSize.toString(),
708-
_offset: (page * pageSize).toString(),
709-
});
708+
const communications = await this.medplum.searchResources('Communication', [
709+
['_tag', 'notification'],
710+
['_tag', 'one-off'],
711+
['_count', pageSize.toString()],
712+
['_offset', (page * pageSize).toString()],
713+
]);
710714
return communications.map((comm) => this.mapToDatabaseNotification(comm) as DatabaseOneOffNotification<Config>);
711715
}
712716

@@ -745,16 +749,20 @@ export class MedplumNotificationBackend<Config extends BaseNotificationTypeConfi
745749
}
746750

747751
const searchParams = this.buildFhirSearchParams(filter);
748-
this.ensureNotificationTag(searchParams);
749752
searchParams._count = pageSize.toString();
750753
searchParams._offset = (page * pageSize).toString();
751754

752-
const communications = await this.medplum.searchResources('Communication', searchParams);
755+
// Convert to string[][] so that _tag values become repeated AND parameters
756+
// (comma-separated _tag in a single param means OR in FHIR, which is wrong here)
757+
const searchTuples = this.paramsToSearchTuples(searchParams);
758+
759+
const communications = await this.medplum.searchResources('Communication', searchTuples);
753760
return communications.map((comm) => this.mapToDatabaseNotification(comm));
754761
}
755762

756763
/**
757764
* Ensure the `_tag` parameter always includes `notification`.
765+
* Mutates the params record in place.
758766
*/
759767
private ensureNotificationTag(params: Record<string, string>): void {
760768
if (params._tag) {
@@ -766,6 +774,32 @@ export class MedplumNotificationBackend<Config extends BaseNotificationTypeConfi
766774
}
767775
}
768776

777+
/**
778+
* Convert a `Record<string, string>` FHIR search params object into `string[][]` tuples.
779+
*
780+
* This is needed because the `_tag` parameter uses comma-separated values internally
781+
* to accumulate multiple tags (notificationType + contextName + 'notification'), but
782+
* **FHIR treats comma-separated token values as OR**. To get AND semantics we must
783+
* repeat the parameter: `_tag=notification&_tag=SMS` instead of `_tag=notification,SMS`.
784+
*
785+
* All other parameters are passed through as single key-value pairs.
786+
*/
787+
private paramsToSearchTuples(params: Record<string, string>): string[][] {
788+
this.ensureNotificationTag(params);
789+
const tuples: string[][] = [];
790+
for (const [key, value] of Object.entries(params)) {
791+
if (key === '_tag') {
792+
// Split into separate entries for AND semantics
793+
for (const tag of value.split(',')) {
794+
tuples.push(['_tag', tag.trim()]);
795+
}
796+
} else {
797+
tuples.push([key, value]);
798+
}
799+
}
800+
return tuples;
801+
}
802+
769803
/**
770804
* Recursively build FHIR search parameters from a NotificationFilter tree.
771805
* Handles field filters, AND, and NOT. OR is handled at the caller level.

0 commit comments

Comments
 (0)