Skip to content

Additional backends#2

Merged
hugobessa merged 3 commits intomainfrom
feat/additional-backends
Feb 27, 2026
Merged

Additional backends#2
hugobessa merged 3 commits intomainfrom
feat/additional-backends

Conversation

@hugobessa
Copy link
Contributor

@hugobessa hugobessa commented Feb 27, 2026

Summary by Sourcery

Support configurable Medplum backend identifiers and allow persisting notifications with predefined IDs.

New Features:

  • Allow MedplumNotificationBackend to accept an optional identifier and expose it via a getter.
  • Support persisting standard and one-off notifications using a caller-specified notification ID when provided.

Enhancements:

  • Update MedplumNotificationBackendFactory to pass optional backend options through to the backend constructor.
  • Extend template renderer mocks in adapter tests to include renderFromTemplateContent to match the interface.

Tests:

  • Add coverage to ensure notifications with predefined IDs are persisted with the specified ID.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 27, 2026

Reviewer's Guide

Extends the Medplum notification backend to support configurable backend identifiers, optional predefined notification IDs that are passed through to created FHIR Communication resources (including one-off notifications), updates the factory to accept backend options, and adjusts tests and mocks (including template renderer mocks) to cover the new behavior and API surface.

Sequence diagram for notification persistence with optional predefined ID

sequenceDiagram
  actor Caller
  participant Backend as MedplumNotificationBackend
  participant Medplum as MedplumClient

  Caller->>Backend: persistNotification(notification)
  alt notification has id
    Backend->>Backend: Build Communication with id
  else notification has no id
    Backend->>Backend: Build Communication without id
  end
  Backend->>Medplum: create(Communication)
  Medplum-->>Backend: Communication (with assigned or preserved id)
  Backend-->>Caller: DatabaseNotification
Loading

Class diagram for updated MedplumNotificationBackend and factory

classDiagram
  class MedplumNotificationBackendOptions {
    +string emailNotificationSubjectExtensionUrl
    +string identifier
  }

  class BaseNotificationTypeConfig {
  }

  class MedplumClient {
  }

  class BaseNotificationBackend~Config~ {
  }

  class MedplumNotificationBackend~Config~ {
    -BaseAttachmentManager attachmentManager
    -BaseLogger logger
    -string identifier
    +MedplumNotificationBackend(medplum MedplumClient, options MedplumNotificationBackendOptions)
    +getBackendIdentifier() string
    +persistNotification(notification NotificationWithoutIdOrWithOptionalId~Config~) DatabaseNotification~Config~
    +persistOneOffNotification(notification OneOffNotificationWithoutIdOrWithOptionalId~Config~) DatabaseOneOffNotification~Config~
  }

  class MedplumNotificationBackendFactory~Config~ {
    +create(medplum MedplumClient, options MedplumNotificationBackendOptions) MedplumNotificationBackend~Config~
  }

  class NotificationWithoutIdOrWithOptionalId~Config~ {
    +Config.NotificationIdType id
    +string title
    +Date sendAfter
  }

  class OneOffNotificationWithoutIdOrWithOptionalId~Config~ {
    +Config.NotificationIdType id
    +string title
    +Date sendAfter
  }

  class DatabaseNotification~Config~ {
  }

  class DatabaseOneOffNotification~Config~ {
  }

  class Communication {
    +string id
    +string resourceType
    +string status
    +string sent
    +string topic
  }

  MedplumNotificationBackend~Config~ ..|> BaseNotificationBackend~Config~
  MedplumNotificationBackend~Config~ o-- MedplumClient
  MedplumNotificationBackend~Config~ o-- MedplumNotificationBackendOptions
  MedplumNotificationBackendFactory~Config~ o-- MedplumNotificationBackend~Config~
  MedplumNotificationBackend~Config~ --> DatabaseNotification~Config~
  MedplumNotificationBackend~Config~ --> DatabaseOneOffNotification~Config~
  MedplumNotificationBackend~Config~ --> Communication
Loading

File-Level Changes

Change Details Files
Add configurable backend identifier support to MedplumNotificationBackend and expose it via a getter.
  • Extend MedplumNotificationBackendOptions to accept an optional identifier field.
  • Initialize a private identifier property in the backend constructor with a default value when none is provided.
  • Implement getBackendIdentifier to return the configured identifier value.
src/medplum-backend.ts
Allow callers to provide predefined notification IDs that are persisted on created Communication resources for both standard and one-off notifications.
  • Broaden persistNotification input type to accept an optional id while excluding the original id from Notification type.
  • Broaden persistOneOffNotification input type to accept an optional id while excluding the original id from OneOffNotificationInput type.
  • Conditionally set the Communication.id field from the provided notification.id when present for both persistNotification and persistOneOffNotification.
  • Add a test that verifies a predefined notification id is passed through to the created Communication and returned from persistNotification.
src/medplum-backend.ts
src/__tests__/medplum-backend.test.ts
Update backend factory and tests/mocks to support new options and template renderer API.
  • Update MedplumNotificationBackendFactory.create to accept an optional options argument and pass it through to the backend constructor.
  • Extend BaseEmailTemplateRenderer mocks in adapter tests to include renderFromTemplateContent to satisfy the updated interface or usage.
src/medplum-backend.ts
src/__tests__/medplum-adapter-attachments.test.ts
src/__tests__/medplum-adapter-one-off.test.ts
src/__tests__/medplum-adapter.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The constructor now directly assigns the passed options, which means emailNotificationSubjectExtensionUrl will be undefined if the caller supplies only identifier; consider shallow-merging with the defaults so you don’t regress the default URL behavior.
  • In both persistNotification and persistOneOffNotification, notification.id is cast to string when constructing the Communication; if Config['NotificationIdType'] can be non-string, consider tightening the generic constraint or normalizing the value before passing it to Medplum.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The constructor now directly assigns the passed `options`, which means `emailNotificationSubjectExtensionUrl` will be `undefined` if the caller supplies only `identifier`; consider shallow-merging with the defaults so you don’t regress the default URL behavior.
- In both `persistNotification` and `persistOneOffNotification`, `notification.id` is cast to `string` when constructing the `Communication`; if `Config['NotificationIdType']` can be non-string, consider tightening the generic constraint or normalizing the value before passing it to Medplum.

## Individual Comments

### Comment 1
<location path="src/medplum-backend.ts" line_range="63-67" />
<code_context>
   private logger?: BaseLogger;
+  private identifier: string;

   constructor(private medplum: MedplumClient, private options: MedplumNotificationBackendOptions = {
     emailNotificationSubjectExtensionUrl: 'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
-  }) {}
+    identifier: 'default-medplum',
+  }) {
+    this.identifier = options.identifier || 'default-medplum';
+  }
+
</code_context>
<issue_to_address>
**suggestion:** Avoid duplicating the default value for `identifier` in both the parameter default and the constructor body.

`'default-medplum'` is defined both in the `options` default and again in `this.identifier = options.identifier || 'default-medplum';`, which risks them diverging if the default changes. Derive `this.identifier` from `this.options.identifier` with a single source of truth for the default (e.g., a shared constant or by defaulting in only one place).

Suggested implementation:

```typescript
const DEFAULT_MEDPLUM_IDENTIFIER = 'default-medplum';

type MedplumNotificationBackendOptions = {
  emailNotificationSubjectExtensionUrl?: string;
  identifier?: string;
};

```

```typescript
  private attachmentManager?: BaseAttachmentManager;
  private logger?: BaseLogger;
  private identifier: string;

  constructor(
    private medplum: MedplumClient,
    private options: MedplumNotificationBackendOptions = {
      emailNotificationSubjectExtensionUrl:
        'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
      identifier: DEFAULT_MEDPLUM_IDENTIFIER,
    }
  ) {
    this.identifier = this.options.identifier ?? DEFAULT_MEDPLUM_IDENTIFIER;
  }

```
</issue_to_address>

### Comment 2
<location path="src/medplum-backend.ts" line_range="316-317" />
<code_context>
   }

-  async persistNotification(notification: NotificationInput<Config>): Promise<DatabaseNotification<Config>> {
+  async persistNotification(
+    notification: Omit<Notification<Config>, 'id'> & { id?: Config['NotificationIdType'] },
+  ): Promise<DatabaseNotification<Config>> {
     const notificationWithOptionalGitCommitSha = notification as NotificationInput<Config> & {
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `persistNotification` input type may drift from `NotificationInput` but is still cast to `NotificationInput` internally.

The parameter is now `Omit<Notification<Config>, 'id'> & { id?: ... }`, but you still assert it to `NotificationInput<Config>` to access `gitCommitSha`. If `Notification<Config>` and `NotificationInput<Config>` ever differ, this assertion can mask type errors and cause runtime bugs. Consider either using `NotificationInput<Config> & { id?: ... }` as the parameter type, or extending `Notification<Config>` to include `gitCommitSha` so the assertion isn’t needed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 63 to +67
constructor(private medplum: MedplumClient, private options: MedplumNotificationBackendOptions = {
emailNotificationSubjectExtensionUrl: 'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
}) {}
identifier: 'default-medplum',
}) {
this.identifier = options.identifier || 'default-medplum';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Avoid duplicating the default value for identifier in both the parameter default and the constructor body.

'default-medplum' is defined both in the options default and again in this.identifier = options.identifier || 'default-medplum';, which risks them diverging if the default changes. Derive this.identifier from this.options.identifier with a single source of truth for the default (e.g., a shared constant or by defaulting in only one place).

Suggested implementation:

const DEFAULT_MEDPLUM_IDENTIFIER = 'default-medplum';

type MedplumNotificationBackendOptions = {
  emailNotificationSubjectExtensionUrl?: string;
  identifier?: string;
};
  private attachmentManager?: BaseAttachmentManager;
  private logger?: BaseLogger;
  private identifier: string;

  constructor(
    private medplum: MedplumClient,
    private options: MedplumNotificationBackendOptions = {
      emailNotificationSubjectExtensionUrl:
        'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
      identifier: DEFAULT_MEDPLUM_IDENTIFIER,
    }
  ) {
    this.identifier = this.options.identifier ?? DEFAULT_MEDPLUM_IDENTIFIER;
  }

Comment on lines +316 to +317
async persistNotification(
notification: Omit<Notification<Config>, 'id'> & { id?: Config['NotificationIdType'] },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The new persistNotification input type may drift from NotificationInput but is still cast to NotificationInput internally.

The parameter is now Omit<Notification<Config>, 'id'> & { id?: ... }, but you still assert it to NotificationInput<Config> to access gitCommitSha. If Notification<Config> and NotificationInput<Config> ever differ, this assertion can mask type errors and cause runtime bugs. Consider either using NotificationInput<Config> & { id?: ... } as the parameter type, or extending Notification<Config> to include gitCommitSha so the assertion isn’t needed.

@hugobessa hugobessa merged commit 73becae into main Feb 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant