@awell-health/awell-extensions 2.1.203 → 2.1.205

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.
Files changed (48) hide show
  1. package/dist/extensions/markdown.json +3 -3
  2. package/dist/extensions/medplum/actions/createResource/config/dataPoints.d.ts +8 -8
  3. package/dist/extensions/medplum/actions/createResource/config/dataPoints.js +2 -16
  4. package/dist/extensions/medplum/actions/createResource/config/dataPoints.js.map +1 -1
  5. package/dist/extensions/medplum/actions/createResource/createResource.js +3 -52
  6. package/dist/extensions/medplum/actions/createResource/createResource.js.map +1 -1
  7. package/dist/extensions/medplum/actions/executeStoredBundle/config/dataPoints.d.ts +10 -0
  8. package/dist/extensions/medplum/actions/executeStoredBundle/config/dataPoints.js +6 -0
  9. package/dist/extensions/medplum/actions/executeStoredBundle/config/dataPoints.js.map +1 -0
  10. package/dist/extensions/medplum/actions/executeStoredBundle/config/fields.d.ts +14 -0
  11. package/dist/extensions/medplum/actions/executeStoredBundle/config/fields.js +18 -0
  12. package/dist/extensions/medplum/actions/executeStoredBundle/config/fields.js.map +1 -0
  13. package/dist/extensions/medplum/actions/executeStoredBundle/config/index.d.ts +2 -0
  14. package/dist/extensions/medplum/actions/executeStoredBundle/config/index.js +9 -0
  15. package/dist/extensions/medplum/actions/executeStoredBundle/config/index.js.map +1 -0
  16. package/dist/extensions/medplum/actions/executeStoredBundle/executeStoredBundle.d.ts +4 -0
  17. package/dist/extensions/medplum/actions/executeStoredBundle/executeStoredBundle.js +65 -0
  18. package/dist/extensions/medplum/actions/executeStoredBundle/executeStoredBundle.js.map +1 -0
  19. package/dist/extensions/medplum/actions/executeStoredBundle/index.d.ts +1 -0
  20. package/dist/extensions/medplum/actions/executeStoredBundle/index.js +6 -0
  21. package/dist/extensions/medplum/actions/executeStoredBundle/index.js.map +1 -0
  22. package/dist/extensions/medplum/actions/index.d.ts +1 -0
  23. package/dist/extensions/medplum/actions/index.js +3 -1
  24. package/dist/extensions/medplum/actions/index.js.map +1 -1
  25. package/dist/extensions/medplum/utils/index.d.ts +1 -0
  26. package/dist/extensions/medplum/utils/index.js +6 -1
  27. package/dist/extensions/medplum/utils/index.js.map +1 -1
  28. package/dist/extensions/medplum/utils/summarizeBatchResult.d.ts +50 -0
  29. package/dist/extensions/medplum/utils/summarizeBatchResult.js +88 -0
  30. package/dist/extensions/medplum/utils/summarizeBatchResult.js.map +1 -0
  31. package/dist/extensions/metriport/actions/index.d.ts +58 -0
  32. package/dist/extensions/metriport/actions/index.js +2 -0
  33. package/dist/extensions/metriport/actions/index.js.map +1 -1
  34. package/dist/extensions/metriport/actions/storeWebhookBundle/dataPoints.d.ts +23 -0
  35. package/dist/extensions/metriport/actions/storeWebhookBundle/dataPoints.js +27 -0
  36. package/dist/extensions/metriport/actions/storeWebhookBundle/dataPoints.js.map +1 -0
  37. package/dist/extensions/metriport/actions/storeWebhookBundle/storeWebhookBundle.d.ts +5 -0
  38. package/dist/extensions/metriport/actions/storeWebhookBundle/storeWebhookBundle.js +51 -0
  39. package/dist/extensions/metriport/actions/storeWebhookBundle/storeWebhookBundle.js.map +1 -0
  40. package/dist/extensions/metriport/actions/webhookBundle/fetchWebhookBundle.d.ts +18 -0
  41. package/dist/extensions/metriport/actions/webhookBundle/fetchWebhookBundle.js +29 -0
  42. package/dist/extensions/metriport/actions/webhookBundle/fetchWebhookBundle.js.map +1 -0
  43. package/dist/extensions/metriport/actions/webhookBundle/getWebhookBundle.js +3 -12
  44. package/dist/extensions/metriport/actions/webhookBundle/getWebhookBundle.js.map +1 -1
  45. package/dist/extensions/metriport/shared/fetchBundle.d.ts +8 -0
  46. package/dist/extensions/metriport/shared/fetchBundle.js +12 -3
  47. package/dist/extensions/metriport/shared/fetchBundle.js.map +1 -1
  48. package/package.json +3 -3
@@ -153,15 +153,15 @@
153
153
  },
154
154
  "medplum": {
155
155
  "readme": "---\ntitle: Medplum\ndescription: Medplum is the open source healthcare developer platform that helps you build, test, and deliver any healthcare product or service.\n---\n\n# Medplum\n\nMedplum is the open source healthcare developer platform that helps you build, test, and deliver any healthcare product or service.\n\n## Extension settings\n\nThe following settings are required to configure the Medplum extension:\n\n- **Client ID**: Used to authenticate with Medplum's API (required)\n- **Client Secret**: Used to authenticate with Medplum's API (required)\n- **Base URL**: Optional custom base URL for your Medplum server (e.g., `https://api.medplum.com/`). Leave empty to use the default Medplum server.\n",
156
- "changelog": "# Medplum changelog\n\n## Unreleased\n\n- Added `Fail if patient not found?` (`failIfNotFound`) field to the `searchPatient` action. When enabled, a search that returns no patient fails the action with a `SERVER_ERROR` instead of completing with an empty patient. Defaults to `false`, so existing care flows are unchanged.\n- Opted `searchPatient` in to automated retries (`supports_automated_retries`). Combined with `failIfNotFound`, this lets a care flow recover from the race where the search runs before the patient exists in Medplum.\n"
156
+ "changelog": "# Medplum changelog\n\n## Unreleased\n\n- Added the `Execute stored bundle` action: executes a FHIR Bundle (transaction or batch) in Medplum, reading it from a reference instead of from a field, so a bundle too large to pass between care flow steps, such as one stored by the Metriport `Store Webhook Bundle` action, can still be imported. The stored object must be a `transaction` or `batch` Bundle. It returns only `bundleId` and `bundleType`. `Find or create resource` also returns `resourceIds` and `resourcesCreated`, which grow with the number of entries, so a bundle too large to pass between steps could be answered by a result that is too large too.\n- Added `Fail if patient not found?` (`failIfNotFound`) field to the `searchPatient` action. When enabled, a search that returns no patient fails the action with a `SERVER_ERROR` instead of completing with an empty patient. Defaults to `false`, so existing care flows are unchanged.\n- Opted `searchPatient` in to automated retries (`supports_automated_retries`). Combined with `failIfNotFound`, this lets a care flow recover from the race where the search runs before the patient exists in Medplum.\n"
157
157
  },
158
158
  "messagebird": {
159
159
  "readme": "---\ntitle: MessageBird\ndescription: MessageBird is a cloud-based SMS marketing platform designed to help businesses engage with customers across a variety of channels including WhatsApp, voice, live chat and more.\n---\n# MessageBird\n\nMessageBird is a cloud-based SMS marketing platform designed to help businesses engage with customers across a variety of channels including WhatsApp, voice, live chat and more.\n\n## Extension settings\n\nIn order to set up this extension, you will need to provide a MessageBird [API key](https://developers.messagebird.com/quickstarts/voice-calling/test-credits-api-keys/#get-your-api-keys) and [report URL](https://support.messagebird.com/hc/en-us/articles/360001854898-Receiving-Delivery-Reports-DLRs-#:~:text=To%20configure%20the%20status%20report,need%20to%20be%20sent%20to.) (only required if you want to use WhatsApp messaging).\n\n## Custom Actions\n\n### Send SMS\n\nMessageBird's Programmable SMS API enables you to programmatically send SMS messages from within a care flow.\n\n[Read the official documentation](https://developers.messagebird.com/api/sms-messaging/#send-outbound-sms)\n\n### Send voice (text-to-speech) message\n\nMessageBird's Voice Messaging API enables you to transform text messages into voice messages to any country.\n\n[Read the official documentation](https://developers.messagebird.com/api/voice-messaging/#send-a-voice-message)\n\n### Send WhatsApp message\n\nSend a message to a recipient via WhatsApp. If an active conversation already exists for the recipient, the conversation will be resumed. In case there's no active conversation a new one is created.\n\n[Learn how to get started with MessageBird and WhatsApp](https://messagebird.com/omnichannel/whatsapp)",
160
160
  "changelog": "# MessageBird changelog"
161
161
  },
162
162
  "metriport": {
163
- "readme": "---\ntitle: Metriport\ndescription: Metriport is Plaid for healthcare data.\n---\n# Metriport\n\nMetriport is Plaid for healthcare data. We help digital health companies access and manage patient health and medical data, through an open-source and universal API.\n\nThrough a single integration, our API enables modern health companies to get the comprehensive patient data they need from both HIEs and EHRs, as well as popular wearable devices.\n\nAs a developer-first interoperability solution, Metriport is powering the next wave of innovative companies, accelerating a revolution in digital health.\n\nTo learn more visit [https://www.metriport.com/](https://www.metriport.com/)\n# Extension settings\n\nIn order to set up this extension, **you will need to provide a Metriport API key**. You can obtain an API key via the Metriport dashboard by selecting the `Developers tab`. To learn more on how to get started with Metriport visit our [quick start docs](https://docs.metriport.com/medical-api/getting-started/quickstart) for our Medical API. Also, to better understand how our API keys work check out the [API Keys section](https://docs.metriport.com/home/api-info/api-keys) of our docs as well.\n\n# Ingestion\n\n## Metriport ADT notifications (`metriportAdt`)\n\nAn ingestion endpoint for Metriport's [real-time patient notifications](https://docs.metriport.com/medical-api/handling-data/realtime-patient-notifications). Point the webhook URL in the Metriport dashboard at it. Request verification (Metriport's HMAC-SHA256 `x-metriport-signature`, with the verification ping answered by `pong`) is implemented but switched off for the pre-release; it will be enabled, and the **Webhook Key** setting required, before general availability.\n\nHandled notification types are `patient.admit`, `patient.transfer`, `patient.discharge` and `patient.discharge-summary`. For each, the FHIR bundle behind the pre-signed `payload.url` is downloaded and the patient is resolved on `payload.externalId` (the id you gave Metriport when creating the patient, i.e. your own MRN). The endpoint saves one encounter keyed on the visit number (the patient itself is created by that resolution; demographics are not written yet), so all four notifications about one visit update the same encounter (`in-progress` until the discharge, `finished` after it, with the last transfer destination as its location). Any other notification type is acknowledged and produces nothing.\n\nEvents published, for care flows to trigger on: `patient.admitted`, `patient.transferred`, `patient.discharged` and `discharge.summary-received` (on a discharge summary). Every handled notification also publishes `fhir.bundle-received`, which FHIR data movement listens for; it carries no clinical data, the bundle is looked up by ingestion id.\n\n# Custom Actions\n\n**GENERAL NOTE: Make sure to create Organizations and Facilities in Metriport before using this extension. A Patient must be associated with a Facility by providing the facilityId when stated in the actions.**\n\n## Create Patient\n\nCreates a Patient in Metriport for the specified Facility where the Patient is receiving care. The Patient is not enrolled in real-time monitoring; use **Enroll in Monitoring** for that.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/create-patient) for more info.\n\n## Enroll in Monitoring\n\nCreates a Patient in Metriport and enrolls them in real-time monitoring in one step. It takes the same Patient details as **Create Patient**, but instead of a Facility ID it takes a **Cohort Name**. By convention a Cohort and the Facility its Patients receive care at share the same name, so the action looks both up by that name: the Patient is created in the matching Facility and added to the matching Cohort.\n\nNames are matched case-insensitively, ignoring surrounding whitespace, and must identify exactly one Cohort and exactly one Facility. Both lists are cached in memory for 24 hours; a name that is not in the cached list triggers an early refresh (at most once a minute) before the action fails, so a Cohort or Facility created recently is still found.\n\nNote that enrolling a Patient in real-time monitoring has downstream consequences: once you start receiving updates about the Patient, you are expected to contribute data back to Metriport.\n\nVisit the endpoint docs for [create patient](https://docs.metriport.com/medical-api/api-reference/patient/create-patient), [list facilities](https://docs.metriport.com/medical-api/api-reference/facility/list-facilities) and [list cohorts](https://docs.metriport.com/medical-api/api-reference/cohort/list-cohorts) for more info.\n\n## Update Patient\n\nUpdates the specified Patient.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/update-patient) for more info.\n\n## Get Patient\n\nRetrieves the specified Patient.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/get-patient) for more info.\n\n## Remove Patient\n\nRemoves a Patient at Metriport and at HIEs the Patient is linked to.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/delete-patient) for more info.\n\n## List Documents\n\nLists all Documents that can be retrieved for a Patient.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/document/list-documents) for more info.\n\n**NOTE: It also returns the status of querying Document references across HIEs, indicating whether there is an asynchronous query in progress (status processing) or not (status completed). If the query is in progress, you will also receive the total number of Documents to be queried as well as the ones that have already been completed.**\n\n## Query Documents\n\nTriggers a Document query for the specified Patient across HIEs.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/document/start-document-query) for more info.\n\n**NOTE: When executed, this endpoint triggers an asynchronous Document query with HIEs and immediately returns the status of Document query, processing .**\n\n## Get Document Url\n\nGets a presigned URL for downloading the specified Document.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/document/get-document) for more info.\n\n**NOTE: This endpoint returns a URL which you can use to download the specified Document using the file name provided from the List Documents endpoint.**\n\n## Get Facility by Name\n\nGets the Facility with the given name, so a care flow can resolve a human-readable Facility name into the `facilityId` that the Patient and Document actions require.\n\nMetriport has no endpoint to filter Facilities by name, so this action lists all Facilities in your Organization and matches in memory. The name is matched case-insensitively and ignoring surrounding whitespace, and must match **exactly one** Facility: the action errors if no Facility matches, and also if several do, rather than guessing which one was meant.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/facility/list-facilities) for more info.\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `facilityName` | string | The name of the Facility to look up. Matched case-insensitively, ignoring surrounding whitespace, and must match exactly one Facility in your Organization. |\n\nThe data points mirror the Facility object Metriport returns from [Get Facility](https://docs.metriport.com/medical-api/api-reference/facility/get-facility), one data point per response field:\n\n| Data point | Type | Metriport field | Description |\n| --- | --- | --- | --- |\n| `facilityId` | string | `id` | Metriport's UUID for the Facility. This is the `facilityId` the Patient and Document actions take. |\n| `facilityName` | string | `name` | The Facility name, as stored at Metriport — the canonical casing and spacing, not the name that was searched for. |\n| `npi` | string | `npi` | The Facility's National Provider Identifier. |\n| `tin` | string | `tin` | The Facility's Taxpayer Identification Number. Optional at Metriport; left unset when absent. |\n| `active` | boolean | `active` | Whether the Facility is active. Optional at Metriport; left unset when absent. |\n| `addressLine1` | string | `address.addressLine1` | Street address line 1. |\n| `addressLine2` | string | `address.addressLine2` | Street address line 2. Optional; left unset when absent. |\n| `city` | string | `address.city` | Address city. |\n| `state` | string | `address.state` | Two-letter US state or territory code. |\n| `zip` | string | `address.zip` | Address ZIP code. |\n| `country` | string | `address.country` | Address country — always `USA` at Metriport today. Left unset when absent. |\n\n## Remove Patient from Cohort\n\nRemoves the specified Patient from a cohort.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/cohort/remove-patients-from-cohort) for more info.\n\n## Get Webhook Bundle\n\nFetches the FHIR bundle from a Metriport webhook payload URL — e.g. the [Encounter Bundle](https://docs.metriport.com/medical-api/handling-data/patient-encounter-bundle) from an ADT notification, or a discharge summary. Pass the `bundleUrl` data point emitted by the **Realtime Update** webhook; the action downloads the bundle and returns it on the `bundle` data point.\n\nWhen the payload is a Patient Encounter Bundle, the action also rewrites it into an executable FHIR transaction and returns that on the `transactionBundle` data point, ready to hand to the Medplum **Find or create resource** action.\n\nThe Encounter's Metriport id is returned separately on the `encounterId` data point, so a later step can address the imported Encounter without re-parsing the bundle.\n\n**NOTE: Metriport pre-signed URLs are only valid for 10 minutes, so this action should run early in the care flow, shortly after the realtime update webhook fires.**\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `url` | string | The pre-signed payload URL to fetch (the webhook's `bundleUrl` data point). |\n| `eventType` | string (optional) | The Metriport notification type — wire this from the realtime update webhook's `eventType` data point. Recorded on the import Provenance so admit, transfer and discharge can be told apart. |\n| `provenanceReason` | text (optional) | Free-text reason recorded on the import Provenance, describing why the data was imported. |\n\n| Data point | Type | Description |\n| --- | --- | --- |\n| `bundle` | json | The FHIR bundle fetched from the URL, exactly as Metriport sent it. |\n| `transactionBundle` | json | The same data rewritten as an executable FHIR transaction. Omitted when the payload is not a Patient Encounter Bundle. |\n| `encounterId` | string | Metriport's UUID for the Encounter in the bundle. Resolve the imported Encounter in Medplum with `Encounter?identifier=https://metriport.com/fhir/encounter\\|<encounterId>`. Omitted when the bundle carries no Encounter. |\n\n### Building the transaction bundle\n\nMetriport delivers a bundle of `type: 'collection'`, which is **not executable**. Handing it straight to Medplum does nothing useful: no entry carries `request` metadata saying what to do with it, and every internal reference points at a Metriport UUID that means nothing in Medplum. The action therefore builds a second, executable bundle rather than passing the original through.\n\nThe transformation is a pure function with no Medplum access of its own — this extension holds Metriport credentials only. That rules out reading Medplum to reconcile against what is already there, so every lookup is expressed declaratively and resolved by the server when the transaction executes.\n\n**The Patient is never written.** Awell/Medplum is the source of truth for patient demographics, so the Patient entry is dropped entirely and every reference to it becomes a conditional reference:\n\n```\nPatient?identifier=https://awellhealth.com/patients|<awell patient id>\n```\n\nOmitting the entry guarantees structurally that a Metriport ADT feed can never overwrite the patient record.\n\n**Everything else is written idempotently.** Each Metriport resource is stamped with an identifier derived from its Metriport id and written with a conditional update:\n\n```\nidentifier: { system: 'https://metriport.com/fhir/encounter', value: '<metriport id>' }\nrequest: PUT Encounter?identifier=https://metriport.com/fhir/encounter|<metriport id>\n```\n\nA conditional update creates on zero matches and updates on one, so a redelivered notification updates in place instead of duplicating Conditions, Observations, Practitioners and Locations — and the admit and discharge notifications for one visit converge on a single Encounter. Existing identifiers are kept, so the Encounter keeps its HL7 `VN` visit number alongside ours.\n\n**References are rewritten to Metriport's own `fullUrl`s.** Metriport resolves its relationships correctly, but emits them in a form FHIR cannot match: entries carry `urn:uuid:` fullUrls while references to them are relative.\n\n```\nfullUrl: urn:uuid:3ca5e8d2-7c84-45ab-91e7-834f8becde12\nreference: Location/3ca5e8d2-7c84-45ab-91e7-834f8becde12\n```\n\nA transaction resolves an internal reference by matching it against `fullUrl` verbatim, and a relative reference does not match a `urn:uuid:` one — Medplum would read it as a reference to a *Medplum* Location with that id, which does not exist. Rewriting the reference to the entry's `fullUrl` closes the gap while keeping Metriport's identity. This is also why the result is a `transaction` and not a `batch`: `urn:uuid` resolution is a transaction feature.\n\n**`meta` is stripped from every resource.** Medplum's `meta.accounts` is inherited from the compartment of the Patient a resource references, but specifying `meta` at all on a create or update replaces those inherited accounts instead of adding to them. Metriport's Encounter arrives with a `meta`, so it is removed. `resource.id` is dropped too, for a separate reason: Medplum assigns identity, and the entry's `fullUrl` already carries the local identity the transaction needs.\n\n**Two entries are synthesised.** An `Organization` named *Metriport Realtime Monitoring*, created with `ifNoneExist` so an existing one is reused rather than overwritten; and a `Provenance` recording the import — what it created, when, which Metriport bundle it came from, and which ADT event triggered it (`patient.admit` → `A01`, `patient.transfer` → `A02`, `patient.discharge` → `A03`).\n\n`extensions/metriport/actions/webhookBundle/bundle/transform.test.ts` asserts the complete output for a real `patient.admit` bundle, if you want to see the whole before/after in one place.\n\n### Wiring it into a care flow\n\n1. **Realtime Update** webhook fires and emits `bundleUrl` and `eventType`.\n2. **Get Webhook Bundle** — pass `bundleUrl` to `url` and `eventType` to `eventType`. Run this early; the URL expires after 10 minutes.\n3. Medplum **Find or create resource** — pass the `transactionBundle` data point to its `resourceJson` field. No changes to that action are needed; it detects a Bundle and executes it.\n\n**PREREQUISITE: for imported resources to be tagged with the Metriport organization, the Medplum Patient must already carry it in `meta.accounts` before the notification arrives.** Resources created without a `meta` inherit their accounts from the compartment of the Patient they reference, so tagging the Patient once causes every subsequent import to inherit it automatically. Note that this only covers resources *in* the patient compartment — `Location` and `Practitioner` are shared directory resources and are intentionally created untagged, since scoping a hospital or a physician to one patient's tenant would be wrong.\n\n**NOTE: the conditional Patient reference only resolves if your Medplum Patients carry an identifier under the `https://awellhealth.com/patients` system with the Awell patient id as its value. A conditional reference that matches no Patient fails the whole transaction.**\n\n### When the transaction bundle is omitted\n\n`Get Webhook Bundle` serves several Metriport webhook types and only ADT notifications carry encounter bundles. For any payload that is not a `collection` bundle, `transactionBundle` is simply omitted and `bundle` is emitted on its own — the action still succeeds.\n\nA `collection` bundle that is missing its Patient or Encounter entry is treated differently: that is an encounter bundle which does not describe an encounter, so the action **fails** rather than silently emitting a partial result.\n\n# Webhooks\n\n## Realtime Update\n\nAn enrollment trigger that starts a care flow when Metriport sends a [real-time patient notification](https://docs.metriport.com/medical-api/handling-data/realtime-patient-notifications).\n\nMetriport POSTs every notification type to the same endpoint. Four of them share one payload shape — a `meta` envelope plus a `payload` pointing at a pre-signed bundle URL — and this webhook enrolls a patient on all four:\n\n- `patient.admit` (HL7 ADT^A01)\n- `patient.discharge` (HL7 ADT^A03)\n- `patient.transfer` (HL7 ADT^A02)\n- `patient.discharge-summary`\n\nThe `eventType` data point carries the raw Metriport webhook type, so a care flow can branch on it. The payload's `url` is a pre-signed link to the [FHIR Encounter Bundle](https://docs.metriport.com/medical-api/handling-data/patient-encounter-bundle).\n\nThe webhook validates the request, emits the data points (including the pre-signed bundle URL on `bundleUrl`), and replies immediately — it does **not** download the bundle. Fetch the bundle later in the care flow with the **Get Webhook Bundle** action, using the `bundleUrl` data point. Because the URL expires after 10 minutes, run that action early.\n\nAny other notification type Metriport sends — `medical.document-download`, `medical.consolidated-data`, and anything added in future — is acknowledged with a `200` and does not enroll a patient. A notification whose type *is* handled but whose payload is malformed fails loudly instead, so a genuine integration problem is not mistaken for an event we chose to ignore. Metriport [verification `ping` messages](https://docs.metriport.com/medical-api/getting-started/webhooks#the-ping-message) are answered with a `200` that echoes the ping value back as `pong: <value>`.\n\n### Data points\n\n| Data point | Type | Description |\n| --- | --- | --- |\n| `eventType` | string | The Metriport webhook type: `patient.admit`, `patient.discharge`, `patient.transfer` or `patient.discharge-summary` |\n| `metriportPatientId` | string | The Metriport patient ID (also used as the patient identifier for enrollment) |\n| `externalId` | string | Your external patient ID, if provided to Metriport |\n| `when` | date | When the event occurred — the admit time on an admit event, the discharge time on a discharge event |\n| `messageId` | string | The Metriport message ID for the notification |\n| `bundleUrl` | string | Pre-signed URL to the FHIR bundle; fetch it with the **Get Webhook Bundle** action (valid for 10 minutes) |\n\n### Verifying incoming requests\n\nOptionally set the **Webhook Key** setting to the webhook key from the Metriport dashboard (Settings/Developers tab). Metriport [authenticates each webhook](https://docs.metriport.com/medical-api/getting-started/webhooks#authentication) with an HMAC-SHA256 signature of the raw request body, keyed with your webhook key and sent in the `x-metriport-signature` header. When the setting is populated, the webhook recomputes the HMAC over the raw body and rejects any request whose signature is missing or does not match (`401`). When left empty, requests are not verified.\n\n## More Info\n\nFor more information on how to integrate with Metriport please visit our [Medical API docs](https://docs.metriport.com/medical-api/getting-started/quickstart)\n",
164
- "changelog": "# Metriport changelog\n\n## September 2026\n\n- Add the `metriportAdt` ingestion endpoint (`source: 'webhook'`): receives Metriport real-time notifications, handles `patient.admit`, `patient.transfer`, `patient.discharge` and `patient.discharge-summary`, downloads the FHIR bundle each points at, and saves one encounter per visit (the patient is created by identifier resolution; demographics are not written yet), so admit, transfer, discharge and the summary converge on the same encounter. Publishes `patient.admitted` / `patient.transferred` / `patient.discharged` / `discharge.summary-received` for care flows and `fhir.bundle-received` for FHIR data movement. Any other notification type is acknowledged without producing a record; HMAC-SHA256 signature verification (with the ping answered by `pong`) is implemented but switched off for the pre-release. The extension now declares `identifier.system` (`https://metriport.com`).\n- Add `Enroll in Monitoring` action: creates a Patient and enrolls them in real-time monitoring in one step. It takes the same Patient details as `Create Patient` but a **Cohort Name** instead of a Facility ID; by convention a Cohort and its Facility share a name, so both are resolved from that one input. Facility and Cohort lists are cached in memory for 24 hours per API credential, refreshing early (at most once a minute) when a name is not found.\n- **Breaking: remove the `Cohort` field from `Create Patient` and `Update Patient`.** Real-time monitoring enrollment now lives on `Enroll in Monitoring`; care flows that passed a cohort ID to `Create Patient` should switch to the new action.\n\n## August 2026\n\n- Rename the `enrollment` webhook to `realtimeUpdate`. The old name described what Awell does with the notification rather than what Metriport sends, which read confusingly next to the genuine enrolment concepts (the `Cohort` field, enrolling a patient into a care flow). Behaviour is unchanged. **Breaking: the webhook endpoint URL changes, so any webhook already configured in the Metriport dashboard must be repointed.**\n- `Date of Birth` on the `Create Patient` and `Update Patient` actions is now a date field rather than a free-text string, so care flows can map a date onto it directly instead of hand-formatting `YYYY-MM-DD`. The value is normalised to the date-only string Metriport expects, so care flows already passing `YYYY-MM-DD` are unaffected.\n\n## July 2026\n\n- Add `enrollment` webhook: an enrollment trigger for Metriport real-time patient notifications. The `eventType` data point carries the Metriport webhook type (`patient.admit` or `patient.discharge-summary`) so care flows can distinguish on it. The webhook validates and replies immediately, emitting the pre-signed FHIR bundle URL on the `bundleUrl` data point rather than downloading the bundle inline.\n- Add `Get Webhook Bundle` action: fetches the FHIR bundle from a Metriport webhook payload URL (the webhook's `bundleUrl`), for use later in the care flow.\n- Add optional `Webhook Key` setting used to verify incoming webhook requests via Metriport's HMAC-SHA256 signature (`x-metriport-signature` header), computed over the raw request body.\n"
163
+ "readme": "---\ntitle: Metriport\ndescription: Metriport is Plaid for healthcare data.\n---\n# Metriport\n\nMetriport is Plaid for healthcare data. We help digital health companies access and manage patient health and medical data, through an open-source and universal API.\n\nThrough a single integration, our API enables modern health companies to get the comprehensive patient data they need from both HIEs and EHRs, as well as popular wearable devices.\n\nAs a developer-first interoperability solution, Metriport is powering the next wave of innovative companies, accelerating a revolution in digital health.\n\nTo learn more visit [https://www.metriport.com/](https://www.metriport.com/)\n# Extension settings\n\nIn order to set up this extension, **you will need to provide a Metriport API key**. You can obtain an API key via the Metriport dashboard by selecting the `Developers tab`. To learn more on how to get started with Metriport visit our [quick start docs](https://docs.metriport.com/medical-api/getting-started/quickstart) for our Medical API. Also, to better understand how our API keys work check out the [API Keys section](https://docs.metriport.com/home/api-info/api-keys) of our docs as well.\n\n# Ingestion\n\n## Metriport ADT notifications (`metriportAdt`)\n\nAn ingestion endpoint for Metriport's [real-time patient notifications](https://docs.metriport.com/medical-api/handling-data/realtime-patient-notifications). Point the webhook URL in the Metriport dashboard at it. Request verification (Metriport's HMAC-SHA256 `x-metriport-signature`, with the verification ping answered by `pong`) is implemented but switched off for the pre-release; it will be enabled, and the **Webhook Key** setting required, before general availability.\n\nHandled notification types are `patient.admit`, `patient.transfer`, `patient.discharge` and `patient.discharge-summary`. For each, the FHIR bundle behind the pre-signed `payload.url` is downloaded and the patient is resolved on `payload.externalId` (the id you gave Metriport when creating the patient, i.e. your own MRN). The endpoint saves one encounter keyed on the visit number (the patient itself is created by that resolution; demographics are not written yet), so all four notifications about one visit update the same encounter (`in-progress` until the discharge, `finished` after it, with the last transfer destination as its location). Any other notification type is acknowledged and produces nothing.\n\nEvents published, for care flows to trigger on: `patient.admitted`, `patient.transferred`, `patient.discharged` and `discharge.summary-received` (on a discharge summary). Every handled notification also publishes `fhir.bundle-received`, which FHIR data movement listens for; it carries no clinical data, the bundle is looked up by ingestion id.\n\n# Custom Actions\n\n**GENERAL NOTE: Make sure to create Organizations and Facilities in Metriport before using this extension. A Patient must be associated with a Facility by providing the facilityId when stated in the actions.**\n\n## Create Patient\n\nCreates a Patient in Metriport for the specified Facility where the Patient is receiving care. The Patient is not enrolled in real-time monitoring; use **Enroll in Monitoring** for that.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/create-patient) for more info.\n\n## Enroll in Monitoring\n\nCreates a Patient in Metriport and enrolls them in real-time monitoring in one step. It takes the same Patient details as **Create Patient**, but instead of a Facility ID it takes a **Cohort Name**. By convention a Cohort and the Facility its Patients receive care at share the same name, so the action looks both up by that name: the Patient is created in the matching Facility and added to the matching Cohort.\n\nNames are matched case-insensitively, ignoring surrounding whitespace, and must identify exactly one Cohort and exactly one Facility. Both lists are cached in memory for 24 hours; a name that is not in the cached list triggers an early refresh (at most once a minute) before the action fails, so a Cohort or Facility created recently is still found.\n\nNote that enrolling a Patient in real-time monitoring has downstream consequences: once you start receiving updates about the Patient, you are expected to contribute data back to Metriport.\n\nVisit the endpoint docs for [create patient](https://docs.metriport.com/medical-api/api-reference/patient/create-patient), [list facilities](https://docs.metriport.com/medical-api/api-reference/facility/list-facilities) and [list cohorts](https://docs.metriport.com/medical-api/api-reference/cohort/list-cohorts) for more info.\n\n## Update Patient\n\nUpdates the specified Patient.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/update-patient) for more info.\n\n## Get Patient\n\nRetrieves the specified Patient.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/get-patient) for more info.\n\n## Remove Patient\n\nRemoves a Patient at Metriport and at HIEs the Patient is linked to.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/patient/delete-patient) for more info.\n\n## List Documents\n\nLists all Documents that can be retrieved for a Patient.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/document/list-documents) for more info.\n\n**NOTE: It also returns the status of querying Document references across HIEs, indicating whether there is an asynchronous query in progress (status processing) or not (status completed). If the query is in progress, you will also receive the total number of Documents to be queried as well as the ones that have already been completed.**\n\n## Query Documents\n\nTriggers a Document query for the specified Patient across HIEs.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/document/start-document-query) for more info.\n\n**NOTE: When executed, this endpoint triggers an asynchronous Document query with HIEs and immediately returns the status of Document query, processing .**\n\n## Get Document Url\n\nGets a presigned URL for downloading the specified Document.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/document/get-document) for more info.\n\n**NOTE: This endpoint returns a URL which you can use to download the specified Document using the file name provided from the List Documents endpoint.**\n\n## Get Facility by Name\n\nGets the Facility with the given name, so a care flow can resolve a human-readable Facility name into the `facilityId` that the Patient and Document actions require.\n\nMetriport has no endpoint to filter Facilities by name, so this action lists all Facilities in your Organization and matches in memory. The name is matched case-insensitively and ignoring surrounding whitespace, and must match **exactly one** Facility: the action errors if no Facility matches, and also if several do, rather than guessing which one was meant.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/facility/list-facilities) for more info.\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `facilityName` | string | The name of the Facility to look up. Matched case-insensitively, ignoring surrounding whitespace, and must match exactly one Facility in your Organization. |\n\nThe data points mirror the Facility object Metriport returns from [Get Facility](https://docs.metriport.com/medical-api/api-reference/facility/get-facility), one data point per response field:\n\n| Data point | Type | Metriport field | Description |\n| --- | --- | --- | --- |\n| `facilityId` | string | `id` | Metriport's UUID for the Facility. This is the `facilityId` the Patient and Document actions take. |\n| `facilityName` | string | `name` | The Facility name, as stored at Metriport — the canonical casing and spacing, not the name that was searched for. |\n| `npi` | string | `npi` | The Facility's National Provider Identifier. |\n| `tin` | string | `tin` | The Facility's Taxpayer Identification Number. Optional at Metriport; left unset when absent. |\n| `active` | boolean | `active` | Whether the Facility is active. Optional at Metriport; left unset when absent. |\n| `addressLine1` | string | `address.addressLine1` | Street address line 1. |\n| `addressLine2` | string | `address.addressLine2` | Street address line 2. Optional; left unset when absent. |\n| `city` | string | `address.city` | Address city. |\n| `state` | string | `address.state` | Two-letter US state or territory code. |\n| `zip` | string | `address.zip` | Address ZIP code. |\n| `country` | string | `address.country` | Address country — always `USA` at Metriport today. Left unset when absent. |\n\n## Remove Patient from Cohort\n\nRemoves the specified Patient from a cohort.\n\nVisit [endpoint docs](https://docs.metriport.com/medical-api/api-reference/cohort/remove-patients-from-cohort) for more info.\n\n## Get Webhook Bundle\n\nFetches the FHIR bundle from a Metriport webhook payload URL — e.g. the [Encounter Bundle](https://docs.metriport.com/medical-api/handling-data/patient-encounter-bundle) from an ADT notification, or a discharge summary. Pass the `bundleUrl` data point emitted by the **Realtime Update** webhook; the action downloads the bundle and returns it on the `bundle` data point.\n\nWhen the payload is a Patient Encounter Bundle, the action also rewrites it into an executable FHIR transaction and returns that on the `transactionBundle` data point, ready to hand to the Medplum **Find or create resource** action.\n\nThe Encounter's Metriport id is returned separately on the `encounterId` data point, so a later step can address the imported Encounter without re-parsing the bundle.\n\n**NOTE: Metriport pre-signed URLs are only valid for 10 minutes, so this action should run early in the care flow, shortly after the realtime update webhook fires.**\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `url` | string | The pre-signed payload URL to fetch (the webhook's `bundleUrl` data point). |\n| `eventType` | string (optional) | The Metriport notification type — wire this from the realtime update webhook's `eventType` data point. Recorded on the import Provenance so admit, transfer and discharge can be told apart. |\n| `provenanceReason` | text (optional) | Free-text reason recorded on the import Provenance, describing why the data was imported. |\n\n| Data point | Type | Description |\n| --- | --- | --- |\n| `bundle` | json | The FHIR bundle fetched from the URL, exactly as Metriport sent it. |\n| `transactionBundle` | json | The same data rewritten as an executable FHIR transaction. Omitted when the payload is not a Patient Encounter Bundle. |\n| `encounterId` | string | Metriport's UUID for the Encounter in the bundle. Resolve the imported Encounter in Medplum with `Encounter?identifier=https://metriport.com/fhir/encounter\\|<encounterId>`. Omitted when the bundle carries no Encounter. |\n\n### Building the transaction bundle\n\nMetriport delivers a bundle of `type: 'collection'`, which is **not executable**. Handing it straight to Medplum does nothing useful: no entry carries `request` metadata saying what to do with it, and every internal reference points at a Metriport UUID that means nothing in Medplum. The action therefore builds a second, executable bundle rather than passing the original through.\n\nThe transformation is a pure function with no Medplum access of its own — this extension holds Metriport credentials only. That rules out reading Medplum to reconcile against what is already there, so every lookup is expressed declaratively and resolved by the server when the transaction executes.\n\n**The Patient is never written.** Awell/Medplum is the source of truth for patient demographics, so the Patient entry is dropped entirely and every reference to it becomes a conditional reference:\n\n```\nPatient?identifier=https://awellhealth.com/patients|<awell patient id>\n```\n\nOmitting the entry guarantees structurally that a Metriport ADT feed can never overwrite the patient record.\n\n**Everything else is written idempotently.** Each Metriport resource is stamped with an identifier derived from its Metriport id and written with a conditional update:\n\n```\nidentifier: { system: 'https://metriport.com/fhir/encounter', value: '<metriport id>' }\nrequest: PUT Encounter?identifier=https://metriport.com/fhir/encounter|<metriport id>\n```\n\nA conditional update creates on zero matches and updates on one, so a redelivered notification updates in place instead of duplicating Conditions, Observations, Practitioners and Locations — and the admit and discharge notifications for one visit converge on a single Encounter. Existing identifiers are kept, so the Encounter keeps its HL7 `VN` visit number alongside ours.\n\n**References are rewritten to Metriport's own `fullUrl`s.** Metriport resolves its relationships correctly, but emits them in a form FHIR cannot match: entries carry `urn:uuid:` fullUrls while references to them are relative.\n\n```\nfullUrl: urn:uuid:3ca5e8d2-7c84-45ab-91e7-834f8becde12\nreference: Location/3ca5e8d2-7c84-45ab-91e7-834f8becde12\n```\n\nA transaction resolves an internal reference by matching it against `fullUrl` verbatim, and a relative reference does not match a `urn:uuid:` one — Medplum would read it as a reference to a *Medplum* Location with that id, which does not exist. Rewriting the reference to the entry's `fullUrl` closes the gap while keeping Metriport's identity. This is also why the result is a `transaction` and not a `batch`: `urn:uuid` resolution is a transaction feature.\n\n**`meta` is stripped from every resource.** Medplum's `meta.accounts` is inherited from the compartment of the Patient a resource references, but specifying `meta` at all on a create or update replaces those inherited accounts instead of adding to them. Metriport's Encounter arrives with a `meta`, so it is removed. `resource.id` is dropped too, for a separate reason: Medplum assigns identity, and the entry's `fullUrl` already carries the local identity the transaction needs.\n\n**Two entries are synthesised.** An `Organization` named *Metriport Realtime Monitoring*, created with `ifNoneExist` so an existing one is reused rather than overwritten; and a `Provenance` recording the import — what it created, when, which Metriport bundle it came from, and which ADT event triggered it (`patient.admit` → `A01`, `patient.transfer` → `A02`, `patient.discharge` → `A03`).\n\n`extensions/metriport/actions/webhookBundle/bundle/transform.test.ts` asserts the complete output for a real `patient.admit` bundle, if you want to see the whole before/after in one place.\n\n### Wiring it into a care flow\n\n1. **Realtime Update** webhook fires and emits `bundleUrl` and `eventType`.\n2. **Get Webhook Bundle** — pass `bundleUrl` to `url` and `eventType` to `eventType`. Run this early; the URL expires after 10 minutes.\n3. Medplum **Find or create resource** — pass the `transactionBundle` data point to its `resourceJson` field. No changes to that action are needed; it detects a Bundle and executes it.\n\n**PREREQUISITE: for imported resources to be tagged with the Metriport organization, the Medplum Patient must already carry it in `meta.accounts` before the notification arrives.** Resources created without a `meta` inherit their accounts from the compartment of the Patient they reference, so tagging the Patient once causes every subsequent import to inherit it automatically. Note that this only covers resources *in* the patient compartment — `Location` and `Practitioner` are shared directory resources and are intentionally created untagged, since scoping a hospital or a physician to one patient's tenant would be wrong.\n\n**NOTE: the conditional Patient reference only resolves if your Medplum Patients carry an identifier under the `https://awellhealth.com/patients` system with the Awell patient id as its value. A conditional reference that matches no Patient fails the whole transaction.**\n\n### When the transaction bundle is omitted\n\n`Get Webhook Bundle` serves several Metriport webhook types and only ADT notifications carry encounter bundles. For any payload that is not a `collection` bundle, `transactionBundle` is simply omitted and `bundle` is emitted on its own — the action still succeeds.\n\nA `collection` bundle that is missing its Patient or Encounter entry is treated differently: that is an encounter bundle which does not describe an encounter, so the action **fails** rather than silently emitting a partial result.\n\n## Store Webhook Bundle\n\nFetches the FHIR bundle from a Metriport webhook payload URL like **Get Webhook Bundle**, but stores it and returns references to it instead of the bundle. Use it when a bundle can be too large to pass between care flow steps: a discharge summary can be larger than the 5 MiB limit on messages inside Awell, which the `bundle` and `transactionBundle` data points of **Get Webhook Bundle** would then hit.\n\nIt takes the same fields as **Get Webhook Bundle**: `url`, `eventType` and `provenanceReason`.\n\n| Data point | Type | Description |\n| --- | --- | --- |\n| `bundleRef` | string | Reference to the stored bundle, exactly as Metriport sent it. |\n| `transactionBundleRef` | string | Reference to the stored executable FHIR transaction (see \"Building the transaction bundle\" above). Omitted when the payload is not a Patient Encounter Bundle. |\n| `encounterId` | string | Metriport's UUID for the Encounter in the bundle. Omitted when the bundle carries no Encounter. |\n\nA reference is opaque: pass it on, do not build or parse one. Stored bundles are named after the activity, so a retried action replaces what the failed attempt stored. The action cannot be previewed.\n\n**NOTE: Metriport pre-signed URLs are only valid for 10 minutes, so this action should run early in the care flow, shortly after the realtime update webhook fires.**\n\n### Wiring it into a care flow\n\n1. **Realtime Update** webhook fires and emits `bundleUrl` and `eventType`.\n2. **Store Webhook Bundle** — pass `bundleUrl` to `url` and `eventType` to `eventType`.\n3. Medplum **Execute stored bundle** — pass the `transactionBundleRef` data point to its `bundleRef` field.\n\n**If the Medplum bots that sync Tasks should link them to the Encounter**, map this action's `encounterId` data point to the care flow's baseline `encounter_id` data point. The bots read `encounter_id` from the baseline data points, and the mapping belongs to the action in the care flow, so a flow that moves from **Get Webhook Bundle** to this action needs it set again. `encounterId` itself is the same value in both.\n\n# Webhooks\n\n## Realtime Update\n\nAn enrollment trigger that starts a care flow when Metriport sends a [real-time patient notification](https://docs.metriport.com/medical-api/handling-data/realtime-patient-notifications).\n\nMetriport POSTs every notification type to the same endpoint. Four of them share one payload shape — a `meta` envelope plus a `payload` pointing at a pre-signed bundle URL — and this webhook enrolls a patient on all four:\n\n- `patient.admit` (HL7 ADT^A01)\n- `patient.discharge` (HL7 ADT^A03)\n- `patient.transfer` (HL7 ADT^A02)\n- `patient.discharge-summary`\n\nThe `eventType` data point carries the raw Metriport webhook type, so a care flow can branch on it. The payload's `url` is a pre-signed link to the [FHIR Encounter Bundle](https://docs.metriport.com/medical-api/handling-data/patient-encounter-bundle).\n\nThe webhook validates the request, emits the data points (including the pre-signed bundle URL on `bundleUrl`), and replies immediately — it does **not** download the bundle. Fetch the bundle later in the care flow with the **Get Webhook Bundle** action, using the `bundleUrl` data point. Because the URL expires after 10 minutes, run that action early.\n\nAny other notification type Metriport sends — `medical.document-download`, `medical.consolidated-data`, and anything added in future — is acknowledged with a `200` and does not enroll a patient. A notification whose type *is* handled but whose payload is malformed fails loudly instead, so a genuine integration problem is not mistaken for an event we chose to ignore. Metriport [verification `ping` messages](https://docs.metriport.com/medical-api/getting-started/webhooks#the-ping-message) are answered with a `200` that echoes the ping value back as `pong: <value>`.\n\n### Data points\n\n| Data point | Type | Description |\n| --- | --- | --- |\n| `eventType` | string | The Metriport webhook type: `patient.admit`, `patient.discharge`, `patient.transfer` or `patient.discharge-summary` |\n| `metriportPatientId` | string | The Metriport patient ID (also used as the patient identifier for enrollment) |\n| `externalId` | string | Your external patient ID, if provided to Metriport |\n| `when` | date | When the event occurred — the admit time on an admit event, the discharge time on a discharge event |\n| `messageId` | string | The Metriport message ID for the notification |\n| `bundleUrl` | string | Pre-signed URL to the FHIR bundle; fetch it with the **Get Webhook Bundle** action (valid for 10 minutes) |\n\n### Verifying incoming requests\n\nOptionally set the **Webhook Key** setting to the webhook key from the Metriport dashboard (Settings/Developers tab). Metriport [authenticates each webhook](https://docs.metriport.com/medical-api/getting-started/webhooks#authentication) with an HMAC-SHA256 signature of the raw request body, keyed with your webhook key and sent in the `x-metriport-signature` header. When the setting is populated, the webhook recomputes the HMAC over the raw body and rejects any request whose signature is missing or does not match (`401`). When left empty, requests are not verified.\n\n## More Info\n\nFor more information on how to integrate with Metriport please visit our [Medical API docs](https://docs.metriport.com/medical-api/getting-started/quickstart)\n",
164
+ "changelog": "# Metriport changelog\n\n## October 2026\n\n- Add `Store Webhook Bundle` action: fetches the FHIR bundle from a Metriport webhook payload URL like `Get Webhook Bundle`, but stores the bundle and its importable transaction rewrite and returns references to them (`bundleRef`, `transactionBundleRef`) instead of the bundles, so a bundle larger than the 5 MiB limit on messages inside Awell, such as a discharge summary, no longer blocks the care flow. Pair it with the Medplum `Execute stored bundle` action. `Get Webhook Bundle` is unchanged.\n- A bundle download is now limited to 100 MB and 60 seconds (it had neither limit), for `Get Webhook Bundle`, `Store Webhook Bundle` and the `metriportAdt` endpoint. A larger bundle now fails with a clear error instead of exhausting the memory of the pod that runs every tenant's jobs.\n\n## September 2026\n\n- Add the `metriportAdt` ingestion endpoint (`source: 'webhook'`): receives Metriport real-time notifications, handles `patient.admit`, `patient.transfer`, `patient.discharge` and `patient.discharge-summary`, downloads the FHIR bundle each points at, and saves one encounter per visit (the patient is created by identifier resolution; demographics are not written yet), so admit, transfer, discharge and the summary converge on the same encounter. Publishes `patient.admitted` / `patient.transferred` / `patient.discharged` / `discharge.summary-received` for care flows and `fhir.bundle-received` for FHIR data movement. Any other notification type is acknowledged without producing a record; HMAC-SHA256 signature verification (with the ping answered by `pong`) is implemented but switched off for the pre-release. The extension now declares `identifier.system` (`https://metriport.com`).\n- Add `Enroll in Monitoring` action: creates a Patient and enrolls them in real-time monitoring in one step. It takes the same Patient details as `Create Patient` but a **Cohort Name** instead of a Facility ID; by convention a Cohort and its Facility share a name, so both are resolved from that one input. Facility and Cohort lists are cached in memory for 24 hours per API credential, refreshing early (at most once a minute) when a name is not found.\n- **Breaking: remove the `Cohort` field from `Create Patient` and `Update Patient`.** Real-time monitoring enrollment now lives on `Enroll in Monitoring`; care flows that passed a cohort ID to `Create Patient` should switch to the new action.\n\n## August 2026\n\n- Rename the `enrollment` webhook to `realtimeUpdate`. The old name described what Awell does with the notification rather than what Metriport sends, which read confusingly next to the genuine enrolment concepts (the `Cohort` field, enrolling a patient into a care flow). Behaviour is unchanged. **Breaking: the webhook endpoint URL changes, so any webhook already configured in the Metriport dashboard must be repointed.**\n- `Date of Birth` on the `Create Patient` and `Update Patient` actions is now a date field rather than a free-text string, so care flows can map a date onto it directly instead of hand-formatting `YYYY-MM-DD`. The value is normalised to the date-only string Metriport expects, so care flows already passing `YYYY-MM-DD` are unaffected.\n\n## July 2026\n\n- Add `enrollment` webhook: an enrollment trigger for Metriport real-time patient notifications. The `eventType` data point carries the Metriport webhook type (`patient.admit` or `patient.discharge-summary`) so care flows can distinguish on it. The webhook validates and replies immediately, emitting the pre-signed FHIR bundle URL on the `bundleUrl` data point rather than downloading the bundle inline.\n- Add `Get Webhook Bundle` action: fetches the FHIR bundle from a Metriport webhook payload URL (the webhook's `bundleUrl`), for use later in the care flow.\n- Add optional `Webhook Key` setting used to verify incoming webhook requests via Metriport's HMAC-SHA256 signature (`x-metriport-signature` header), computed over the raw request body.\n"
165
165
  },
166
166
  "nexuzhealth": {
167
167
  "readme": "---\ntitle: nexuzhealth\ndescription: Demo extension for nexuzhealth\n---\n\nThis is an extension that can only be used for demo purposes.",
@@ -7,13 +7,9 @@ export declare const dataPoints: {
7
7
  key: string;
8
8
  valueType: "string";
9
9
  };
10
- bundleId: {
11
- key: string;
12
- valueType: "string";
13
- };
14
- bundleType: {
10
+ wasResourceFound: {
15
11
  key: string;
16
- valueType: "string";
12
+ valueType: "boolean";
17
13
  };
18
14
  resourceIds: {
19
15
  key: string;
@@ -23,8 +19,12 @@ export declare const dataPoints: {
23
19
  key: string;
24
20
  valueType: "json";
25
21
  };
26
- wasResourceFound: {
22
+ bundleId: {
27
23
  key: string;
28
- valueType: "boolean";
24
+ valueType: "string";
25
+ };
26
+ bundleType: {
27
+ key: string;
28
+ valueType: "string";
29
29
  };
30
30
  };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dataPoints = void 0;
4
+ const utils_1 = require("../../../utils");
4
5
  exports.dataPoints = {
6
+ ...utils_1.batchResultDataPoints,
5
7
  resourceId: {
6
8
  key: 'resourceId',
7
9
  valueType: 'string',
@@ -10,22 +12,6 @@ exports.dataPoints = {
10
12
  key: 'resourceType',
11
13
  valueType: 'string',
12
14
  },
13
- bundleId: {
14
- key: 'bundleId',
15
- valueType: 'string',
16
- },
17
- bundleType: {
18
- key: 'bundleType',
19
- valueType: 'string',
20
- },
21
- resourceIds: {
22
- key: 'resourceIds',
23
- valueType: 'string',
24
- },
25
- resourcesCreated: {
26
- key: 'resourcesCreated',
27
- valueType: 'json',
28
- },
29
15
  wasResourceFound: {
30
16
  key: 'wasResourceFound',
31
17
  valueType: 'boolean',
@@ -1 +1 @@
1
- {"version":3,"file":"dataPoints.js","sourceRoot":"","sources":["../../../../../../extensions/medplum/actions/createResource/config/dataPoints.ts"],"names":[],"mappings":";;;AAEa,QAAA,UAAU,GAAG;IACxB,UAAU,EAAE;QACV,GAAG,EAAE,YAAY;QACjB,SAAS,EAAE,QAAQ;KACpB;IACD,YAAY,EAAE;QACZ,GAAG,EAAE,cAAc;QACnB,SAAS,EAAE,QAAQ;KACpB;IACD,QAAQ,EAAE;QACR,GAAG,EAAE,UAAU;QACf,SAAS,EAAE,QAAQ;KACpB;IACD,UAAU,EAAE;QACV,GAAG,EAAE,YAAY;QACjB,SAAS,EAAE,QAAQ;KACpB;IACD,WAAW,EAAE;QACX,GAAG,EAAE,aAAa;QAClB,SAAS,EAAE,QAAQ;KACpB;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,kBAAkB;QACvB,SAAS,EAAE,MAAM;KAClB;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,kBAAkB;QACvB,SAAS,EAAE,SAAS;KACrB;CAC4C,CAAA"}
1
+ {"version":3,"file":"dataPoints.js","sourceRoot":"","sources":["../../../../../../extensions/medplum/actions/createResource/config/dataPoints.ts"],"names":[],"mappings":";;;AACA,0CAAsD;AAEzC,QAAA,UAAU,GAAG;IACxB,GAAG,6BAAqB;IACxB,UAAU,EAAE;QACV,GAAG,EAAE,YAAY;QACjB,SAAS,EAAE,QAAQ;KACpB;IACD,YAAY,EAAE;QACZ,GAAG,EAAE,cAAc;QACnB,SAAS,EAAE,QAAQ;KACpB;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,kBAAkB;QACvB,SAAS,EAAE,SAAS;KACrB;CAC4C,CAAA"}
@@ -13,7 +13,7 @@ exports.createResource = {
13
13
  previewable: false,
14
14
  dataPoints: config_1.dataPoints,
15
15
  onEvent: async ({ payload, onComplete, onError, helpers }) => {
16
- var _a, _b, _c, _d, _e, _f, _g, _h;
16
+ var _a, _b;
17
17
  const { fields: input, medplumSdk } = await (0, utils_1.validateAndCreateSdkClient)({
18
18
  fieldsSchema: config_1.FieldsValidationSchema,
19
19
  payload,
@@ -46,58 +46,9 @@ exports.createResource = {
46
46
  helpers.log({ resourceData }, '[createResource] Executing Medplum bundle');
47
47
  const result = await medplumSdk.executeBatch(resourceData);
48
48
  helpers.log({ batchResult: result }, '[Medplum extension] Medplum batch result');
49
- const resourceIds = (_c = (_b = result.entry) === null || _b === void 0 ? void 0 : _b.map((entry) => {
50
- var _a, _b;
51
- if (((_a = entry.response) === null || _a === void 0 ? void 0 : _a.location) !== undefined &&
52
- entry.response.location !== '') {
53
- const match = entry.response.location.match(/(?:^|\/)([^/]+)\/([^/]+)(?:\/|$)/);
54
- return match !== null ? match[2] : undefined;
55
- }
56
- return (_b = entry.resource) === null || _b === void 0 ? void 0 : _b.id;
57
- }).filter((id) => id !== undefined && id !== '').join(',')) !== null && _c !== void 0 ? _c : '';
58
- const resourcesCreated = (_e = (_d = result.entry) === null || _d === void 0 ? void 0 : _d.map((entry) => {
59
- var _a, _b, _c, _d, _e;
60
- let id;
61
- let resourceType;
62
- let location;
63
- if (((_a = entry.response) === null || _a === void 0 ? void 0 : _a.location) !== undefined &&
64
- entry.response.location !== '') {
65
- const match = entry.response.location.match(/(?:^|\/)([^/]+)\/([^/]+)(?:\/|$)/);
66
- if (match !== null) {
67
- resourceType = match[1];
68
- id = match[2];
69
- location = `${resourceType}/${id}`;
70
- }
71
- }
72
- if ((id === undefined || id === '') &&
73
- ((_b = entry.resource) === null || _b === void 0 ? void 0 : _b.id) !== undefined &&
74
- entry.resource.id !== '') {
75
- id = entry.resource.id;
76
- }
77
- if ((resourceType === undefined || resourceType === '') &&
78
- ((_c = entry.resource) === null || _c === void 0 ? void 0 : _c.resourceType) !== undefined) {
79
- resourceType = entry.resource.resourceType;
80
- }
81
- if ((location === undefined || location === '') &&
82
- resourceType !== undefined &&
83
- resourceType !== '' &&
84
- id !== undefined &&
85
- id !== '') {
86
- location = `${resourceType}/${id}`;
87
- }
88
- return {
89
- id: id !== null && id !== void 0 ? id : '',
90
- resourceType: resourceType !== null && resourceType !== void 0 ? resourceType : '',
91
- status: (_e = (_d = entry.response) === null || _d === void 0 ? void 0 : _d.status) !== null && _e !== void 0 ? _e : '',
92
- location: location !== null && location !== void 0 ? location : '',
93
- };
94
- }).filter((resource) => resource.id !== '')) !== null && _e !== void 0 ? _e : [];
95
49
  await onComplete({
96
50
  data_points: {
97
- bundleId: (_f = result.id) !== null && _f !== void 0 ? _f : '',
98
- resourceIds,
99
- bundleType: (_g = result.type) !== null && _g !== void 0 ? _g : '',
100
- resourcesCreated: JSON.stringify(resourcesCreated),
51
+ ...(0, utils_1.summarizeBatchResult)(result),
101
52
  wasResourceFound: 'false',
102
53
  },
103
54
  });
@@ -107,7 +58,7 @@ exports.createResource = {
107
58
  const result = await medplumSdk.createResource(resourceData);
108
59
  await onComplete({
109
60
  data_points: {
110
- resourceId: (_h = result.id) !== null && _h !== void 0 ? _h : '',
61
+ resourceId: (_b = result.id) !== null && _b !== void 0 ? _b : '',
111
62
  resourceType: result.resourceType,
112
63
  wasResourceFound: 'false',
113
64
  },
@@ -1 +1 @@
1
- {"version":3,"file":"createResource.js","sourceRoot":"","sources":["../../../../../extensions/medplum/actions/createResource/createResource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createResource.js","sourceRoot":"","sources":["../../../../../extensions/medplum/actions/createResource/createResource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare const dataPoints: {
2
+ bundleId: {
3
+ key: string;
4
+ valueType: "string";
5
+ };
6
+ bundleType: {
7
+ key: string;
8
+ valueType: "string";
9
+ };
10
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataPoints = void 0;
4
+ const utils_1 = require("../../../utils");
5
+ exports.dataPoints = utils_1.bundleResultDataPoints;
6
+ //# sourceMappingURL=dataPoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataPoints.js","sourceRoot":"","sources":["../../../../../../extensions/medplum/actions/executeStoredBundle/config/dataPoints.ts"],"names":[],"mappings":";;;AAAA,0CAAuD;AAE1C,QAAA,UAAU,GAAG,8BAAsB,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { FieldType } from '@awell-health/extensions-core';
2
+ import { z } from 'zod';
3
+ export declare const fields: {
4
+ bundleRef: {
5
+ id: string;
6
+ label: string;
7
+ description: string;
8
+ type: FieldType.STRING;
9
+ required: true;
10
+ };
11
+ };
12
+ export declare const FieldsValidationSchema: z.ZodObject<{
13
+ bundleRef: z.ZodString;
14
+ }, z.core.$strip>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FieldsValidationSchema = exports.fields = void 0;
4
+ const extensions_core_1 = require("@awell-health/extensions-core");
5
+ const zod_1 = require("zod");
6
+ exports.fields = {
7
+ bundleRef: {
8
+ id: 'bundleRef',
9
+ label: 'Bundle reference',
10
+ description: 'Reference to a stored FHIR Bundle (transaction or batch) to execute in Medplum, as returned by an action that stores one, such as the Metriport "Store Webhook Bundle" action.',
11
+ type: extensions_core_1.FieldType.STRING,
12
+ required: true,
13
+ },
14
+ };
15
+ exports.FieldsValidationSchema = zod_1.z.object({
16
+ bundleRef: zod_1.z.string().trim().min(1),
17
+ });
18
+ //# sourceMappingURL=fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../../../../../../extensions/medplum/actions/executeStoredBundle/config/fields.ts"],"names":[],"mappings":";;;AAAA,mEAAqE;AACrE,6BAAqC;AAExB,QAAA,MAAM,GAAG;IACpB,SAAS,EAAE;QACT,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,gLAAgL;QAClL,IAAI,EAAE,2BAAS,CAAC,MAAM;QACtB,QAAQ,EAAE,IAAI;KACf;CAC8B,CAAA;AAEpB,QAAA,sBAAsB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC7C,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACW,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { fields, FieldsValidationSchema } from './fields';
2
+ export { dataPoints } from './dataPoints';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataPoints = exports.FieldsValidationSchema = exports.fields = void 0;
4
+ var fields_1 = require("./fields");
5
+ Object.defineProperty(exports, "fields", { enumerable: true, get: function () { return fields_1.fields; } });
6
+ Object.defineProperty(exports, "FieldsValidationSchema", { enumerable: true, get: function () { return fields_1.FieldsValidationSchema; } });
7
+ var dataPoints_1 = require("./dataPoints");
8
+ Object.defineProperty(exports, "dataPoints", { enumerable: true, get: function () { return dataPoints_1.dataPoints; } });
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../extensions/medplum/actions/executeStoredBundle/config/index.ts"],"names":[],"mappings":";;;AAAA,mCAAyD;AAAhD,gGAAA,MAAM,OAAA;AAAE,gHAAA,sBAAsB,OAAA;AACvC,2CAAyC;AAAhC,wGAAA,UAAU,OAAA"}
@@ -0,0 +1,4 @@
1
+ import { type Action } from '@awell-health/extensions-core';
2
+ import { type settings } from '../../settings';
3
+ import { fields, dataPoints } from './config';
4
+ export declare const executeStoredBundle: Action<typeof fields, typeof settings, keyof typeof dataPoints>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeStoredBundle = void 0;
4
+ const extensions_core_1 = require("@awell-health/extensions-core");
5
+ const utils_1 = require("../../utils");
6
+ const config_1 = require("./config");
7
+ exports.executeStoredBundle = {
8
+ key: 'executeStoredBundle',
9
+ category: extensions_core_1.Category.EHR_INTEGRATIONS,
10
+ title: 'Execute stored bundle',
11
+ description: 'Executes a FHIR Bundle (transaction or batch) in Medplum, reading it from a reference instead of from a field. Use it in place of "Find or create resource" when the bundle is too large to pass between care flow steps, such as one stored by the Metriport "Store Webhook Bundle" action.',
12
+ fields: config_1.fields,
13
+ previewable: false,
14
+ dataPoints: config_1.dataPoints,
15
+ onEvent: async ({ payload, onComplete, onError, helpers }) => {
16
+ var _a, _b;
17
+ const { fields: input, medplumSdk } = await (0, utils_1.validateAndCreateSdkClient)({
18
+ fieldsSchema: config_1.FieldsValidationSchema,
19
+ payload,
20
+ });
21
+ try {
22
+ const stored = await helpers.objectStore.get(input.bundleRef);
23
+ // The bundle is PHI: an error that quotes a malformed one would put it in
24
+ // an activity event, so say that it is not JSON and nothing more.
25
+ let bundle;
26
+ try {
27
+ bundle = JSON.parse(stored);
28
+ }
29
+ catch (_c) {
30
+ throw new Error(`The object at ${input.bundleRef} is not valid JSON`);
31
+ }
32
+ if ((bundle === null || bundle === void 0 ? void 0 : bundle.resourceType) !== 'Bundle') {
33
+ throw new Error(`The object at ${input.bundleRef} is not a FHIR Bundle`);
34
+ }
35
+ // Medplum rejects any other type, but only after being sent it: and the
36
+ // raw Metriport `bundleRef` is a collection, an easy one to wire in by mistake.
37
+ if (bundle.type !== 'transaction' && bundle.type !== 'batch') {
38
+ throw new Error(`The object at ${input.bundleRef} is not a transaction or batch Bundle`);
39
+ }
40
+ // The bundle can be megabytes of PHI: log what it is, not what is in it.
41
+ helpers.log({
42
+ bundleRef: input.bundleRef,
43
+ bundleType: bundle.type,
44
+ entries: (_b = (_a = bundle.entry) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
45
+ }, '[executeStoredBundle] Executing Medplum bundle');
46
+ const result = await medplumSdk.executeBatch(bundle);
47
+ // Not what Medplum did with each resource: that grows with the bundle, and
48
+ // a bundle too large to pass between steps can be answered by one too large.
49
+ await onComplete({ data_points: (0, utils_1.summarizeBundleResult)(result) });
50
+ }
51
+ catch (error) {
52
+ const message = `Failed to execute bundle: ${error.message}`;
53
+ await onError({
54
+ events: [
55
+ {
56
+ date: new Date().toISOString(),
57
+ text: { en: message },
58
+ error: { category: 'SERVER_ERROR', message },
59
+ },
60
+ ],
61
+ });
62
+ }
63
+ },
64
+ };
65
+ //# sourceMappingURL=executeStoredBundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executeStoredBundle.js","sourceRoot":"","sources":["../../../../../extensions/medplum/actions/executeStoredBundle/executeStoredBundle.ts"],"names":[],"mappings":";;;AAAA,mEAAqE;AAErE,uCAA+E;AAC/E,qCAAqE;AAExD,QAAA,mBAAmB,GAI5B;IACF,GAAG,EAAE,qBAAqB;IAC1B,QAAQ,EAAE,0BAAQ,CAAC,gBAAgB;IACnC,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EACT,8RAA8R;IAChS,MAAM,EAAN,eAAM;IACN,WAAW,EAAE,KAAK;IAClB,UAAU,EAAV,mBAAU;IACV,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAiB,EAAE;;QAC1E,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,kCAA0B,EAAC;YACrE,YAAY,EAAE,+BAAsB;YACpC,OAAO;SACR,CAAC,CAAA;QAEF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YAE7D,0EAA0E;YAC1E,kEAAkE;YAClE,IAAI,MAAM,CAAA;YACV,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YAC7B,CAAC;YAAC,WAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,SAAS,oBAAoB,CAAC,CAAA;YACvE,CAAC;YACD,IAAI,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,YAAY,MAAK,QAAQ,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,SAAS,uBAAuB,CAAC,CAAA;YAC1E,CAAC;YACD,wEAAwE;YACxE,gFAAgF;YAChF,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC7D,MAAM,IAAI,KAAK,CACb,iBAAiB,KAAK,CAAC,SAAS,uCAAuC,CACxE,CAAA;YACH,CAAC;YAED,yEAAyE;YACzE,OAAO,CAAC,GAAG,CACT;gBACE,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,UAAU,EAAE,MAAM,CAAC,IAAI;gBACvB,OAAO,EAAE,MAAA,MAAA,MAAM,CAAC,KAAK,0CAAE,MAAM,mCAAI,CAAC;aACnC,EACD,gDAAgD,CACjD,CAAA;YAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;YAEpD,2EAA2E;YAC3E,6EAA6E;YAC7E,MAAM,UAAU,CAAC,EAAE,WAAW,EAAE,IAAA,6BAAqB,EAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,6BAA8B,KAAe,CAAC,OAAO,EAAE,CAAA;YACvE,MAAM,OAAO,CAAC;gBACZ,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;wBAC9B,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;wBACrB,KAAK,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE;qBAC7C;iBACF;aACF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;CACF,CAAA"}
@@ -0,0 +1 @@
1
+ export { executeStoredBundle } from './executeStoredBundle';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeStoredBundle = void 0;
4
+ var executeStoredBundle_1 = require("./executeStoredBundle");
5
+ Object.defineProperty(exports, "executeStoredBundle", { enumerable: true, get: function () { return executeStoredBundle_1.executeStoredBundle; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../extensions/medplum/actions/executeStoredBundle/index.ts"],"names":[],"mappings":";;;AAAA,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA"}
@@ -11,3 +11,4 @@ export { getServiceRequest } from './getServiceRequest';
11
11
  export { getMedicationRequest } from './getMedicationRequest';
12
12
  export { searchPatient } from './searchPatient';
13
13
  export { createResource } from './createResource';
14
+ export { executeStoredBundle } from './executeStoredBundle';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createResource = exports.searchPatient = exports.getMedicationRequest = exports.getServiceRequest = exports.addSideEffect = exports.createCalculationObservation = exports.submitQuestionnaireResponse = exports.createServiceRequest = exports.createTask = exports.executeBot = exports.getAppointment = exports.createPatient = exports.getPatient = void 0;
3
+ exports.executeStoredBundle = exports.createResource = exports.searchPatient = exports.getMedicationRequest = exports.getServiceRequest = exports.addSideEffect = exports.createCalculationObservation = exports.submitQuestionnaireResponse = exports.createServiceRequest = exports.createTask = exports.executeBot = exports.getAppointment = exports.createPatient = exports.getPatient = void 0;
4
4
  var getPatient_1 = require("./getPatient");
5
5
  Object.defineProperty(exports, "getPatient", { enumerable: true, get: function () { return getPatient_1.getPatient; } });
6
6
  var createPatient_1 = require("./createPatient");
@@ -27,4 +27,6 @@ var searchPatient_1 = require("./searchPatient");
27
27
  Object.defineProperty(exports, "searchPatient", { enumerable: true, get: function () { return searchPatient_1.searchPatient; } });
28
28
  var createResource_1 = require("./createResource");
29
29
  Object.defineProperty(exports, "createResource", { enumerable: true, get: function () { return createResource_1.createResource; } });
30
+ var executeStoredBundle_1 = require("./executeStoredBundle");
31
+ Object.defineProperty(exports, "executeStoredBundle", { enumerable: true, get: function () { return executeStoredBundle_1.executeStoredBundle; } });
30
32
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../extensions/medplum/actions/index.ts"],"names":[],"mappings":";;;AAAA,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,+DAA6D;AAApD,4HAAA,oBAAoB,OAAA;AAC7B,6EAA2E;AAAlE,0IAAA,2BAA2B,OAAA;AACpC,+EAA6E;AAApE,4IAAA,4BAA4B,OAAA;AACrC,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,yDAAuD;AAA9C,sHAAA,iBAAiB,OAAA;AAC1B,+DAA6D;AAApD,4HAAA,oBAAoB,OAAA;AAC7B,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../extensions/medplum/actions/index.ts"],"names":[],"mappings":";;;AAAA,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,+DAA6D;AAApD,4HAAA,oBAAoB,OAAA;AAC7B,6EAA2E;AAAlE,0IAAA,2BAA2B,OAAA;AACpC,+EAA6E;AAApE,4IAAA,4BAA4B,OAAA;AACrC,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,yDAAuD;AAA9C,sHAAA,iBAAiB,OAAA;AAC1B,+DAA6D;AAApD,4HAAA,oBAAoB,OAAA;AAC7B,iDAA+C;AAAtC,8GAAA,aAAa,OAAA;AACtB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA"}
@@ -1,2 +1,3 @@
1
1
  export { validateAndCreateSdkClient } from './validateAndCreateSdkClient';
2
+ export { summarizeBatchResult, summarizeBundleResult, batchResultDataPoints, bundleResultDataPoints, } from './summarizeBatchResult';
2
3
  export declare const AWELL_IDENTIFIER_SYSTEM = "https://www.awellhealth.com/";
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AWELL_IDENTIFIER_SYSTEM = exports.validateAndCreateSdkClient = void 0;
3
+ exports.AWELL_IDENTIFIER_SYSTEM = exports.bundleResultDataPoints = exports.batchResultDataPoints = exports.summarizeBundleResult = exports.summarizeBatchResult = exports.validateAndCreateSdkClient = void 0;
4
4
  var validateAndCreateSdkClient_1 = require("./validateAndCreateSdkClient");
5
5
  Object.defineProperty(exports, "validateAndCreateSdkClient", { enumerable: true, get: function () { return validateAndCreateSdkClient_1.validateAndCreateSdkClient; } });
6
+ var summarizeBatchResult_1 = require("./summarizeBatchResult");
7
+ Object.defineProperty(exports, "summarizeBatchResult", { enumerable: true, get: function () { return summarizeBatchResult_1.summarizeBatchResult; } });
8
+ Object.defineProperty(exports, "summarizeBundleResult", { enumerable: true, get: function () { return summarizeBatchResult_1.summarizeBundleResult; } });
9
+ Object.defineProperty(exports, "batchResultDataPoints", { enumerable: true, get: function () { return summarizeBatchResult_1.batchResultDataPoints; } });
10
+ Object.defineProperty(exports, "bundleResultDataPoints", { enumerable: true, get: function () { return summarizeBatchResult_1.bundleResultDataPoints; } });
6
11
  exports.AWELL_IDENTIFIER_SYSTEM = 'https://www.awellhealth.com/';
7
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../extensions/medplum/utils/index.ts"],"names":[],"mappings":";;;AAAA,2EAAyE;AAAhE,wIAAA,0BAA0B,OAAA;AAEtB,QAAA,uBAAuB,GAAG,8BAA8B,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../extensions/medplum/utils/index.ts"],"names":[],"mappings":";;;AAAA,2EAAyE;AAAhE,wIAAA,0BAA0B,OAAA;AACnC,+DAK+B;AAJ7B,4HAAA,oBAAoB,OAAA;AACpB,6HAAA,qBAAqB,OAAA;AACrB,6HAAA,qBAAqB,OAAA;AACrB,8HAAA,sBAAsB,OAAA;AAGX,QAAA,uBAAuB,GAAG,8BAA8B,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { type Bundle } from '@medplum/fhirtypes';
2
+ /** The data points `summarizeBundleResult` fills: what the result is, not what is in it. */
3
+ export declare const bundleResultDataPoints: {
4
+ bundleId: {
5
+ key: string;
6
+ valueType: "string";
7
+ };
8
+ bundleType: {
9
+ key: string;
10
+ valueType: "string";
11
+ };
12
+ };
13
+ /**
14
+ * The data points `summarizeBatchResult` fills. The last two grow with the
15
+ * number of entries, so an action whose bundle can be large leaves them out and
16
+ * returns only `bundleResultDataPoints`.
17
+ */
18
+ export declare const batchResultDataPoints: {
19
+ resourceIds: {
20
+ key: string;
21
+ valueType: "string";
22
+ };
23
+ resourcesCreated: {
24
+ key: string;
25
+ valueType: "json";
26
+ };
27
+ bundleId: {
28
+ key: string;
29
+ valueType: "string";
30
+ };
31
+ bundleType: {
32
+ key: string;
33
+ valueType: "string";
34
+ };
35
+ };
36
+ /** The id and type of an executed bundle's result. */
37
+ export declare const summarizeBundleResult: (result: Bundle) => {
38
+ bundleId: string;
39
+ bundleType: string;
40
+ };
41
+ /**
42
+ * The data points that describe what Medplum did with an executed bundle: the
43
+ * id of every resource it touched, and for each its type, status and location.
44
+ */
45
+ export declare const summarizeBatchResult: (result: Bundle) => {
46
+ bundleId: string;
47
+ resourceIds: string;
48
+ bundleType: string;
49
+ resourcesCreated: string;
50
+ };
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarizeBatchResult = exports.summarizeBundleResult = exports.batchResultDataPoints = exports.bundleResultDataPoints = void 0;
4
+ const LOCATION = /(?:^|\/)([^/]+)\/([^/]+)(?:\/|$)/;
5
+ /** The data points `summarizeBundleResult` fills: what the result is, not what is in it. */
6
+ exports.bundleResultDataPoints = {
7
+ bundleId: { key: 'bundleId', valueType: 'string' },
8
+ bundleType: { key: 'bundleType', valueType: 'string' },
9
+ };
10
+ /**
11
+ * The data points `summarizeBatchResult` fills. The last two grow with the
12
+ * number of entries, so an action whose bundle can be large leaves them out and
13
+ * returns only `bundleResultDataPoints`.
14
+ */
15
+ exports.batchResultDataPoints = {
16
+ ...exports.bundleResultDataPoints,
17
+ resourceIds: { key: 'resourceIds', valueType: 'string' },
18
+ resourcesCreated: { key: 'resourcesCreated', valueType: 'json' },
19
+ };
20
+ /** The id and type of an executed bundle's result. */
21
+ const summarizeBundleResult = (result) => {
22
+ var _a, _b;
23
+ return ({
24
+ bundleId: (_a = result.id) !== null && _a !== void 0 ? _a : '',
25
+ bundleType: (_b = result.type) !== null && _b !== void 0 ? _b : '',
26
+ });
27
+ };
28
+ exports.summarizeBundleResult = summarizeBundleResult;
29
+ /**
30
+ * The data points that describe what Medplum did with an executed bundle: the
31
+ * id of every resource it touched, and for each its type, status and location.
32
+ */
33
+ const summarizeBatchResult = (result) => {
34
+ var _a, _b, _c, _d;
35
+ const resourceIds = (_b = (_a = result.entry) === null || _a === void 0 ? void 0 : _a.map((entry) => {
36
+ var _a, _b;
37
+ if (((_a = entry.response) === null || _a === void 0 ? void 0 : _a.location) !== undefined &&
38
+ entry.response.location !== '') {
39
+ const match = entry.response.location.match(LOCATION);
40
+ return match !== null ? match[2] : undefined;
41
+ }
42
+ return (_b = entry.resource) === null || _b === void 0 ? void 0 : _b.id;
43
+ }).filter((id) => id !== undefined && id !== '').join(',')) !== null && _b !== void 0 ? _b : '';
44
+ const resourcesCreated = (_d = (_c = result.entry) === null || _c === void 0 ? void 0 : _c.map((entry) => {
45
+ var _a, _b, _c, _d, _e;
46
+ let id;
47
+ let resourceType;
48
+ let location;
49
+ if (((_a = entry.response) === null || _a === void 0 ? void 0 : _a.location) !== undefined &&
50
+ entry.response.location !== '') {
51
+ const match = entry.response.location.match(LOCATION);
52
+ if (match !== null) {
53
+ resourceType = match[1];
54
+ id = match[2];
55
+ location = `${resourceType}/${id}`;
56
+ }
57
+ }
58
+ if ((id === undefined || id === '') &&
59
+ ((_b = entry.resource) === null || _b === void 0 ? void 0 : _b.id) !== undefined &&
60
+ entry.resource.id !== '') {
61
+ id = entry.resource.id;
62
+ }
63
+ if ((resourceType === undefined || resourceType === '') &&
64
+ ((_c = entry.resource) === null || _c === void 0 ? void 0 : _c.resourceType) !== undefined) {
65
+ resourceType = entry.resource.resourceType;
66
+ }
67
+ if ((location === undefined || location === '') &&
68
+ resourceType !== undefined &&
69
+ resourceType !== '' &&
70
+ id !== undefined &&
71
+ id !== '') {
72
+ location = `${resourceType}/${id}`;
73
+ }
74
+ return {
75
+ id: id !== null && id !== void 0 ? id : '',
76
+ resourceType: resourceType !== null && resourceType !== void 0 ? resourceType : '',
77
+ status: (_e = (_d = entry.response) === null || _d === void 0 ? void 0 : _d.status) !== null && _e !== void 0 ? _e : '',
78
+ location: location !== null && location !== void 0 ? location : '',
79
+ };
80
+ }).filter((resource) => resource.id !== '')) !== null && _d !== void 0 ? _d : [];
81
+ return {
82
+ ...(0, exports.summarizeBundleResult)(result),
83
+ resourceIds,
84
+ resourcesCreated: JSON.stringify(resourcesCreated),
85
+ };
86
+ };
87
+ exports.summarizeBatchResult = summarizeBatchResult;
88
+ //# sourceMappingURL=summarizeBatchResult.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarizeBatchResult.js","sourceRoot":"","sources":["../../../../extensions/medplum/utils/summarizeBatchResult.ts"],"names":[],"mappings":";;;AAGA,MAAM,QAAQ,GAAG,kCAAkC,CAAA;AAEnD,4FAA4F;AAC/E,QAAA,sBAAsB,GAAG;IACpC,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE;IAClD,UAAU,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE;CACT,CAAA;AAE/C;;;;GAIG;AACU,QAAA,qBAAqB,GAAG;IACnC,GAAG,8BAAsB;IACzB,WAAW,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE;IACxD,gBAAgB,EAAE,EAAE,GAAG,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,EAAE;CACnB,CAAA;AAE/C,sDAAsD;AAC/C,MAAM,qBAAqB,GAAG,CACnC,MAAc,EAC4B,EAAE;;IAAC,OAAA,CAAC;QAC9C,QAAQ,EAAE,MAAA,MAAM,CAAC,EAAE,mCAAI,EAAE;QACzB,UAAU,EAAE,MAAA,MAAM,CAAC,IAAI,mCAAI,EAAE;KAC9B,CAAC,CAAA;CAAA,CAAA;AALW,QAAA,qBAAqB,yBAKhC;AAEF;;;GAGG;AACI,MAAM,oBAAoB,GAAG,CAClC,MAAc,EAMd,EAAE;;IACF,MAAM,WAAW,GACf,MAAA,MAAA,MAAM,CAAC,KAAK,0CACR,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;;QACd,IACE,CAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,QAAQ,MAAK,SAAS;YACtC,KAAK,CAAC,QAAQ,CAAC,QAAQ,KAAK,EAAE,EAC9B,CAAC;YACD,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YACrD,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC9C,CAAC;QACD,OAAO,MAAA,KAAK,CAAC,QAAQ,0CAAE,EAAE,CAAA;IAC3B,CAAC,EACA,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,EAC1D,IAAI,CAAC,GAAG,CAAC,mCAAI,EAAE,CAAA;IAEpB,MAAM,gBAAgB,GACpB,MAAA,MAAA,MAAM,CAAC,KAAK,0CACR,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;;QACd,IAAI,EAAsB,CAAA;QAC1B,IAAI,YAAgC,CAAA;QACpC,IAAI,QAA4B,CAAA;QAEhC,IACE,CAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,QAAQ,MAAK,SAAS;YACtC,KAAK,CAAC,QAAQ,CAAC,QAAQ,KAAK,EAAE,EAC9B,CAAC;YACD,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YACrD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;gBACvB,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;gBACb,QAAQ,GAAG,GAAG,YAAY,IAAI,EAAE,EAAE,CAAA;YACpC,CAAC;QACH,CAAC;QAED,IACE,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC;YAC/B,CAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,EAAE,MAAK,SAAS;YAChC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,EACxB,CAAC;YACD,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAA;QACxB,CAAC;QACD,IACE,CAAC,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,EAAE,CAAC;YACnD,CAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,YAAY,MAAK,SAAS,EAC1C,CAAC;YACD,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAA;QAC5C,CAAC;QACD,IACE,CAAC,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,CAAC;YAC3C,YAAY,KAAK,SAAS;YAC1B,YAAY,KAAK,EAAE;YACnB,EAAE,KAAK,SAAS;YAChB,EAAE,KAAK,EAAE,EACT,CAAC;YACD,QAAQ,GAAG,GAAG,YAAY,IAAI,EAAE,EAAE,CAAA;QACpC,CAAC;QAED,OAAO;YACL,EAAE,EAAE,EAAE,aAAF,EAAE,cAAF,EAAE,GAAI,EAAE;YACZ,YAAY,EAAE,YAAY,aAAZ,YAAY,cAAZ,YAAY,GAAI,EAAE;YAChC,MAAM,EAAE,MAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,MAAM,mCAAI,EAAE;YACpC,QAAQ,EAAE,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,EAAE;SACzB,CAAA;IACH,CAAC,EACA,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,mCAAI,EAAE,CAAA;IAEnD,OAAO;QACL,GAAG,IAAA,6BAAqB,EAAC,MAAM,CAAC;QAChC,WAAW;QACX,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;KACnD,CAAA;AACH,CAAC,CAAA;AA/EY,QAAA,oBAAoB,wBA+EhC"}
@@ -667,6 +667,64 @@ export declare const actions: {
667
667
  required: false;
668
668
  };
669
669
  }, "bundle" | "transactionBundle" | "encounterId">;
670
+ storeWebhookBundle: import("@awell-health/extensions-core").Action<{
671
+ url: {
672
+ id: string;
673
+ label: string;
674
+ description: string;
675
+ type: import("@awell-health/extensions-core").FieldType.STRING;
676
+ required: true;
677
+ };
678
+ eventType: {
679
+ id: string;
680
+ label: string;
681
+ description: string;
682
+ type: import("@awell-health/extensions-core").FieldType.STRING;
683
+ required: false;
684
+ options: {
685
+ dropdownOptions: {
686
+ label: string;
687
+ value: import("../webhooks/types").MetriportWebhookType;
688
+ }[];
689
+ };
690
+ };
691
+ provenanceReason: {
692
+ id: string;
693
+ label: string;
694
+ description: string;
695
+ type: import("@awell-health/extensions-core").FieldType.TEXT;
696
+ required: false;
697
+ };
698
+ }, {
699
+ apiKey: {
700
+ key: string;
701
+ label: string;
702
+ obfuscated: true;
703
+ description: string;
704
+ required: true;
705
+ };
706
+ baseUrl: {
707
+ key: string;
708
+ label: string;
709
+ obfuscated: false;
710
+ description: string;
711
+ required: false;
712
+ };
713
+ webhookKey: {
714
+ key: string;
715
+ label: string;
716
+ obfuscated: true;
717
+ description: string;
718
+ required: false;
719
+ };
720
+ rateLimitDuration: {
721
+ key: string;
722
+ label: string;
723
+ obfuscated: false;
724
+ description: string;
725
+ required: false;
726
+ };
727
+ }, "bundleRef" | "encounterId" | "transactionBundleRef">;
670
728
  removePatientFromCohort: import("@awell-health/extensions-core").Action<{
671
729
  cohortId: {
672
730
  id: string;
@@ -12,6 +12,7 @@ const startNetworkQuery_1 = require("./network/startNetworkQuery");
12
12
  const startConsolidatedQuery_1 = require("./consolidated/startConsolidatedQuery");
13
13
  const getConsolidatedQueryStatus_1 = require("./consolidated/getConsolidatedQueryStatus");
14
14
  const getWebhookBundle_1 = require("./webhookBundle/getWebhookBundle");
15
+ const storeWebhookBundle_1 = require("./storeWebhookBundle/storeWebhookBundle");
15
16
  const removePatientFromCohort_1 = require("./cohort/removePatientFromCohort");
16
17
  const getFacilityByName_1 = require("./facility/getFacilityByName");
17
18
  const enrollInMonitoring_1 = require("./monitoring/enrollInMonitoring");
@@ -27,6 +28,7 @@ exports.actions = {
27
28
  startConsolidatedQuery: startConsolidatedQuery_1.startConsolidatedQuery,
28
29
  getConsolidatedQueryStatus: getConsolidatedQueryStatus_1.getConsolidatedQueryStatus,
29
30
  getWebhookBundle: getWebhookBundle_1.getWebhookBundle,
31
+ storeWebhookBundle: storeWebhookBundle_1.storeWebhookBundle,
30
32
  removePatientFromCohort: removePatientFromCohort_1.removePatientFromCohort,
31
33
  getFacilityByName: getFacilityByName_1.getFacilityByName,
32
34
  enrollInMonitoring: enrollInMonitoring_1.enrollInMonitoring,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../extensions/metriport/actions/index.ts"],"names":[],"mappings":";;;AAAA,uCAA0C;AAC1C,6CAAgD;AAChD,6CAAgD;AAChD,6CAAgD;AAChD,0CAA+C;AAC/C,4CAA4C;AAC5C,8CAA0C;AAC1C,mEAA+D;AAC/D,kFAA8E;AAC9E,0FAAsF;AACtF,uEAAmE;AACnE,8EAA0E;AAC1E,oEAAgE;AAChE,wEAAoE;AAEvD,QAAA,OAAO,GAAG;IACrB,UAAU,EAAV,gBAAU;IACV,aAAa,EAAb,sBAAa;IACb,aAAa,EAAb,sBAAa;IACb,aAAa,EAAb,sBAAa;IACb,aAAa,EAAb,oBAAa;IACb,SAAS,EAAT,iBAAS;IACT,MAAM,EAAN,eAAM;IACN,iBAAiB,EAAjB,qCAAiB;IACjB,sBAAsB,EAAtB,+CAAsB;IACtB,0BAA0B,EAA1B,uDAA0B;IAC1B,gBAAgB,EAAhB,mCAAgB;IAChB,uBAAuB,EAAvB,iDAAuB;IACvB,iBAAiB,EAAjB,qCAAiB;IACjB,kBAAkB,EAAlB,uCAAkB;CACnB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../extensions/metriport/actions/index.ts"],"names":[],"mappings":";;;AAAA,uCAA0C;AAC1C,6CAAgD;AAChD,6CAAgD;AAChD,6CAAgD;AAChD,0CAA+C;AAC/C,4CAA4C;AAC5C,8CAA0C;AAC1C,mEAA+D;AAC/D,kFAA8E;AAC9E,0FAAsF;AACtF,uEAAmE;AACnE,gFAA4E;AAC5E,8EAA0E;AAC1E,oEAAgE;AAChE,wEAAoE;AAEvD,QAAA,OAAO,GAAG;IACrB,UAAU,EAAV,gBAAU;IACV,aAAa,EAAb,sBAAa;IACb,aAAa,EAAb,sBAAa;IACb,aAAa,EAAb,sBAAa;IACb,aAAa,EAAb,oBAAa;IACb,SAAS,EAAT,iBAAS;IACT,MAAM,EAAN,eAAM;IACN,iBAAiB,EAAjB,qCAAiB;IACjB,sBAAsB,EAAtB,+CAAsB;IACtB,0BAA0B,EAA1B,uDAA0B;IAC1B,gBAAgB,EAAhB,mCAAgB;IAChB,kBAAkB,EAAlB,uCAAkB;IAClB,uBAAuB,EAAvB,iDAAuB;IACvB,iBAAiB,EAAjB,qCAAiB;IACjB,kBAAkB,EAAlB,uCAAkB;CACnB,CAAA"}
@@ -0,0 +1,23 @@
1
+ export declare const dataPoints: {
2
+ /** Reference to the stored bundle, exactly as Metriport sent it. */
3
+ bundleRef: {
4
+ key: string;
5
+ valueType: "string";
6
+ };
7
+ /**
8
+ * Reference to the stored `bundle` rewritten as an executable FHIR
9
+ * transaction. Omitted when the payload is not a Patient Encounter Bundle.
10
+ */
11
+ transactionBundleRef: {
12
+ key: string;
13
+ valueType: "string";
14
+ };
15
+ /**
16
+ * Metriport's own UUID for the Encounter in the bundle — not a Medplum
17
+ * resource id. Omitted when the bundle carries no Encounter.
18
+ */
19
+ encounterId: {
20
+ key: string;
21
+ valueType: "string";
22
+ };
23
+ };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataPoints = void 0;
4
+ exports.dataPoints = {
5
+ /** Reference to the stored bundle, exactly as Metriport sent it. */
6
+ bundleRef: {
7
+ key: 'bundleRef',
8
+ valueType: 'string',
9
+ },
10
+ /**
11
+ * Reference to the stored `bundle` rewritten as an executable FHIR
12
+ * transaction. Omitted when the payload is not a Patient Encounter Bundle.
13
+ */
14
+ transactionBundleRef: {
15
+ key: 'transactionBundleRef',
16
+ valueType: 'string',
17
+ },
18
+ /**
19
+ * Metriport's own UUID for the Encounter in the bundle — not a Medplum
20
+ * resource id. Omitted when the bundle carries no Encounter.
21
+ */
22
+ encounterId: {
23
+ key: 'encounterId',
24
+ valueType: 'string',
25
+ },
26
+ };
27
+ //# sourceMappingURL=dataPoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataPoints.js","sourceRoot":"","sources":["../../../../../extensions/metriport/actions/storeWebhookBundle/dataPoints.ts"],"names":[],"mappings":";;;AAEa,QAAA,UAAU,GAAG;IACxB,oEAAoE;IACpE,SAAS,EAAE;QACT,GAAG,EAAE,WAAW;QAChB,SAAS,EAAE,QAAQ;KACpB;IACD;;;OAGG;IACH,oBAAoB,EAAE;QACpB,GAAG,EAAE,sBAAsB;QAC3B,SAAS,EAAE,QAAQ;KACpB;IACD;;;OAGG;IACH,WAAW,EAAE;QACX,GAAG,EAAE,aAAa;QAClB,SAAS,EAAE,QAAQ;KACpB;CAC4C,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { type Action } from '@awell-health/extensions-core';
2
+ import { type settings } from '../../settings';
3
+ import { fields } from '../webhookBundle/fields';
4
+ import { dataPoints } from './dataPoints';
5
+ export declare const storeWebhookBundle: Action<typeof fields, typeof settings, keyof typeof dataPoints>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storeWebhookBundle = void 0;
4
+ const extensions_core_1 = require("@awell-health/extensions-core");
5
+ const errorHandler_1 = require("../../shared/errorHandler");
6
+ const fields_1 = require("../webhookBundle/fields");
7
+ const fetchWebhookBundle_1 = require("../webhookBundle/fetchWebhookBundle");
8
+ const validation_1 = require("../webhookBundle/validation");
9
+ const dataPoints_1 = require("./dataPoints");
10
+ exports.storeWebhookBundle = {
11
+ key: 'storeWebhookBundle',
12
+ category: extensions_core_1.Category.EHR_INTEGRATIONS,
13
+ title: 'Store Webhook Bundle',
14
+ description: 'Fetches the FHIR bundle from a Metriport webhook payload URL and stores it, returning a reference to it instead of the bundle. Use it in place of "Get Webhook Bundle" when bundles can be too large to pass between care flow steps, such as a discharge summary. The URL is provided by the realtime update webhook on the `bundleUrl` data point and is only valid for 10 minutes.',
15
+ fields: fields_1.fields,
16
+ // A preview has no care flow to hand the references to.
17
+ previewable: false,
18
+ // Safe to retry: the objects are named after the activity, so a retry
19
+ // replaces what the failed attempt stored.
20
+ supports_automated_retries: true,
21
+ dataPoints: dataPoints_1.dataPoints,
22
+ onEvent: async ({ payload, onComplete, onError, helpers }) => {
23
+ try {
24
+ const { url, eventType, provenanceReason } = validation_1.getWebhookBundleSchema.parse(payload.fields);
25
+ // No `log`: it would write every resource's identifiers (MRNs and the
26
+ // like) to the logs, and a bundle can hold tens of thousands of resources.
27
+ const { bundle, transactionBundle, encounterId } = await (0, fetchWebhookBundle_1.fetchWebhookBundle)({
28
+ url,
29
+ awellPatientId: payload.patient.id,
30
+ eventType,
31
+ reason: provenanceReason,
32
+ });
33
+ const objectName = (name) => `metriport/${payload.activity.id}/${name}.json`;
34
+ const bundleRef = await helpers.objectStore.put(objectName('bundle'), JSON.stringify(bundle));
35
+ const transactionBundleRef = transactionBundle === undefined
36
+ ? undefined
37
+ : await helpers.objectStore.put(objectName('transaction-bundle'), JSON.stringify(transactionBundle));
38
+ await onComplete({
39
+ data_points: {
40
+ bundleRef,
41
+ ...(transactionBundleRef !== undefined ? { transactionBundleRef } : {}),
42
+ ...(encounterId !== undefined ? { encounterId } : {}),
43
+ },
44
+ });
45
+ }
46
+ catch (err) {
47
+ await (0, errorHandler_1.handleErrorMessage)(err, onError);
48
+ }
49
+ },
50
+ };
51
+ //# sourceMappingURL=storeWebhookBundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storeWebhookBundle.js","sourceRoot":"","sources":["../../../../../extensions/metriport/actions/storeWebhookBundle/storeWebhookBundle.ts"],"names":[],"mappings":";;;AAAA,mEAAqE;AAErE,4DAA8D;AAC9D,oDAAgD;AAChD,4EAAwE;AACxE,4DAAoE;AACpE,6CAAyC;AAE5B,QAAA,kBAAkB,GAI3B;IACF,GAAG,EAAE,oBAAoB;IACzB,QAAQ,EAAE,0BAAQ,CAAC,gBAAgB;IACnC,KAAK,EAAE,sBAAsB;IAC7B,WAAW,EACT,uXAAuX;IACzX,MAAM,EAAN,eAAM;IACN,wDAAwD;IACxD,WAAW,EAAE,KAAK;IAClB,sEAAsE;IACtE,2CAA2C;IAC3C,0BAA0B,EAAE,IAAI;IAChC,UAAU,EAAV,uBAAU;IACV,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAiB,EAAE;QAC1E,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,mCAAsB,CAAC,KAAK,CACvE,OAAO,CAAC,MAAM,CACf,CAAA;YAED,sEAAsE;YACtE,2EAA2E;YAC3E,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,WAAW,EAAE,GAC9C,MAAM,IAAA,uCAAkB,EAAC;gBACvB,GAAG;gBACH,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;gBAClC,SAAS;gBACT,MAAM,EAAE,gBAAgB;aACzB,CAAC,CAAA;YAEJ,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAC1C,aAAa,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,IAAI,OAAO,CAAA;YAEjD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,CAC7C,UAAU,CAAC,QAAQ,CAAC,EACpB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAA;YACD,MAAM,oBAAoB,GACxB,iBAAiB,KAAK,SAAS;gBAC7B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,GAAG,CAC3B,UAAU,CAAC,oBAAoB,CAAC,EAChC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAClC,CAAA;YAEP,MAAM,UAAU,CAAC;gBACf,WAAW,EAAE;oBACX,SAAS;oBACT,GAAG,CAAC,oBAAoB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvE,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACtD;aACF,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAA,iCAAkB,EAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;CACF,CAAA"}
@@ -0,0 +1,18 @@
1
+ import { type Bundle } from '@medplum/fhirtypes';
2
+ import { type Helpers } from '@awell-health/extensions-core';
3
+ /**
4
+ * Downloads a Metriport webhook bundle and derives what both bundle actions
5
+ * hand on: the importable transaction rewrite and the Encounter's id. They
6
+ * differ only in how they publish the result.
7
+ */
8
+ export declare const fetchWebhookBundle: ({ url, awellPatientId, eventType, reason, log, }: {
9
+ url: string;
10
+ awellPatientId: string;
11
+ eventType?: string;
12
+ reason?: string;
13
+ log?: Helpers["log"];
14
+ }) => Promise<{
15
+ bundle: Bundle;
16
+ transactionBundle: Bundle | undefined;
17
+ encounterId: string | undefined;
18
+ }>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchWebhookBundle = void 0;
4
+ const fetchBundle_1 = require("../../shared/fetchBundle");
5
+ const bundle_1 = require("./bundle");
6
+ const encounter_1 = require("./bundle/encounter");
7
+ /**
8
+ * Downloads a Metriport webhook bundle and derives what both bundle actions
9
+ * hand on: the importable transaction rewrite and the Encounter's id. They
10
+ * differ only in how they publish the result.
11
+ */
12
+ const fetchWebhookBundle = async ({ url, awellPatientId, eventType, reason, log, }) => {
13
+ const bundle = await (0, fetchBundle_1.fetchBundle)(url);
14
+ // Only ADT notifications carry Patient Encounter Bundles; for the other
15
+ // webhook types the transaction bundle is undefined and simply omitted. A
16
+ // collection bundle missing its Patient or Encounter throws instead: it
17
+ // claims to be an encounter bundle but cannot be imported, so failing the
18
+ // activity is better than silently emitting the raw bundle alone.
19
+ const transactionBundle = (0, bundle_1.buildTransactionBundle)({
20
+ bundle,
21
+ awellPatientId,
22
+ eventType,
23
+ reason,
24
+ log,
25
+ });
26
+ return { bundle, transactionBundle, encounterId: (0, encounter_1.findEncounterId)(bundle) };
27
+ };
28
+ exports.fetchWebhookBundle = fetchWebhookBundle;
29
+ //# sourceMappingURL=fetchWebhookBundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchWebhookBundle.js","sourceRoot":"","sources":["../../../../../extensions/metriport/actions/webhookBundle/fetchWebhookBundle.ts"],"names":[],"mappings":";;;AAEA,0DAAsD;AACtD,qCAAiD;AACjD,kDAAoD;AAEpD;;;;GAIG;AACI,MAAM,kBAAkB,GAAG,KAAK,EAAE,EACvC,GAAG,EACH,cAAc,EACd,SAAS,EACT,MAAM,EACN,GAAG,GAOJ,EAIE,EAAE;IACH,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAW,EAAC,GAAG,CAAC,CAAA;IAErC,wEAAwE;IACxE,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,kEAAkE;IAClE,MAAM,iBAAiB,GAAG,IAAA,+BAAsB,EAAC;QAC/C,MAAM;QACN,cAAc;QACd,SAAS;QACT,MAAM;QACN,GAAG;KACJ,CAAC,CAAA;IAEF,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAA,2BAAe,EAAC,MAAM,CAAC,EAAE,CAAA;AAC5E,CAAC,CAAA;AAjCY,QAAA,kBAAkB,sBAiC9B"}
@@ -6,9 +6,7 @@ const errorHandler_1 = require("../../shared/errorHandler");
6
6
  const fields_1 = require("./fields");
7
7
  const validation_1 = require("./validation");
8
8
  const dataPoints_1 = require("./dataPoints");
9
- const fetchBundle_1 = require("../../shared/fetchBundle");
10
- const bundle_1 = require("./bundle");
11
- const encounter_1 = require("./bundle/encounter");
9
+ const fetchWebhookBundle_1 = require("./fetchWebhookBundle");
12
10
  exports.getWebhookBundle = {
13
11
  key: 'getWebhookBundle',
14
12
  category: extensions_core_1.Category.EHR_INTEGRATIONS,
@@ -21,20 +19,13 @@ exports.getWebhookBundle = {
21
19
  onEvent: async ({ payload, onComplete, onError, helpers }) => {
22
20
  try {
23
21
  const { url, eventType, provenanceReason } = validation_1.getWebhookBundleSchema.parse(payload.fields);
24
- const bundle = await (0, fetchBundle_1.fetchBundle)(url);
25
- // Only ADT notifications carry Patient Encounter Bundles; for the other
26
- // webhook types this is undefined and the data point is simply omitted.
27
- // A collection bundle missing its Patient or Encounter throws instead —
28
- // it claims to be an encounter bundle but cannot be imported, so failing
29
- // the activity is better than silently emitting the raw bundle alone.
30
- const transactionBundle = (0, bundle_1.buildTransactionBundle)({
31
- bundle,
22
+ const { bundle, transactionBundle, encounterId } = await (0, fetchWebhookBundle_1.fetchWebhookBundle)({
23
+ url,
32
24
  awellPatientId: payload.patient.id,
33
25
  eventType,
34
26
  reason: provenanceReason,
35
27
  log: helpers.log,
36
28
  });
37
- const encounterId = (0, encounter_1.findEncounterId)(bundle);
38
29
  await onComplete({
39
30
  data_points: {
40
31
  bundle: JSON.stringify(bundle),
@@ -1 +1 @@
1
- {"version":3,"file":"getWebhookBundle.js","sourceRoot":"","sources":["../../../../../extensions/metriport/actions/webhookBundle/getWebhookBundle.ts"],"names":[],"mappings":";;;AAAA,mEAAqE;AAErE,4DAA8D;AAC9D,qCAAiC;AACjC,6CAAqD;AACrD,6CAAyC;AACzC,0DAAsD;AACtD,qCAAiD;AACjD,kDAAoD;AAEvC,QAAA,gBAAgB,GAIzB;IACF,GAAG,EAAE,kBAAkB;IACvB,QAAQ,EAAE,0BAAQ,CAAC,gBAAgB;IACnC,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EACT,+PAA+P;IACjQ,MAAM,EAAN,eAAM;IACN,WAAW,EAAE,IAAI;IACjB,0BAA0B,EAAE,IAAI;IAChC,UAAU,EAAV,uBAAU;IACV,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAiB,EAAE;QAC1E,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,mCAAsB,CAAC,KAAK,CACvE,OAAO,CAAC,MAAM,CACf,CAAA;YAED,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAW,EAAC,GAAG,CAAC,CAAA;YAErC,wEAAwE;YACxE,wEAAwE;YACxE,wEAAwE;YACxE,yEAAyE;YACzE,sEAAsE;YACtE,MAAM,iBAAiB,GAAG,IAAA,+BAAsB,EAAC;gBAC/C,MAAM;gBACN,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;gBAClC,SAAS;gBACT,MAAM,EAAE,gBAAgB;gBACxB,GAAG,EAAE,OAAO,CAAC,GAAG;aACjB,CAAC,CAAA;YAEF,MAAM,WAAW,GAAG,IAAA,2BAAe,EAAC,MAAM,CAAC,CAAA;YAE3C,MAAM,UAAU,CAAC;gBACf,WAAW,EAAE;oBACX,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;oBAC9B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrD,GAAG,CAAC,iBAAiB,KAAK,SAAS;wBACjC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE;wBAC1D,CAAC,CAAC,EAAE,CAAC;iBACR;aACF,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAA,iCAAkB,EAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;CACF,CAAA"}
1
+ {"version":3,"file":"getWebhookBundle.js","sourceRoot":"","sources":["../../../../../extensions/metriport/actions/webhookBundle/getWebhookBundle.ts"],"names":[],"mappings":";;;AAAA,mEAAqE;AAErE,4DAA8D;AAC9D,qCAAiC;AACjC,6CAAqD;AACrD,6CAAyC;AACzC,6DAAyD;AAE5C,QAAA,gBAAgB,GAIzB;IACF,GAAG,EAAE,kBAAkB;IACvB,QAAQ,EAAE,0BAAQ,CAAC,gBAAgB;IACnC,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EACT,+PAA+P;IACjQ,MAAM,EAAN,eAAM;IACN,WAAW,EAAE,IAAI;IACjB,0BAA0B,EAAE,IAAI;IAChC,UAAU,EAAV,uBAAU;IACV,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAiB,EAAE;QAC1E,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,mCAAsB,CAAC,KAAK,CACvE,OAAO,CAAC,MAAM,CACf,CAAA;YAED,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,WAAW,EAAE,GAC9C,MAAM,IAAA,uCAAkB,EAAC;gBACvB,GAAG;gBACH,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;gBAClC,SAAS;gBACT,MAAM,EAAE,gBAAgB;gBACxB,GAAG,EAAE,OAAO,CAAC,GAAG;aACjB,CAAC,CAAA;YAEJ,MAAM,UAAU,CAAC;gBACf,WAAW,EAAE;oBACX,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;oBAC9B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrD,GAAG,CAAC,iBAAiB,KAAK,SAAS;wBACjC,CAAC,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE;wBAC1D,CAAC,CAAC,EAAE,CAAC;iBACR;aACF,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAA,iCAAkB,EAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;CACF,CAAA"}
@@ -1,4 +1,12 @@
1
1
  import { type Bundle } from '@medplum/fhirtypes';
2
+ /**
3
+ * A bundle costs several times its size in memory while it is parsed,
4
+ * rewritten and stored, and the extension server runs jobs side by side in a
5
+ * pod with a heap of 768 MB. Past this a download fails with a clear error
6
+ * instead of taking every job on the pod down with it.
7
+ */
8
+ export declare const MAX_BUNDLE_BYTES: number;
9
+ export declare const FETCH_BUNDLE_TIMEOUT_MS = 60000;
2
10
  /**
3
11
  * Downloads the FHIR bundle from a Metriport webhook payload URL. The URL is
4
12
  * pre-signed and only valid for 10 minutes.
@@ -3,9 +3,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.fetchBundle = void 0;
6
+ exports.fetchBundle = exports.FETCH_BUNDLE_TIMEOUT_MS = exports.MAX_BUNDLE_BYTES = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const request_filtering_agent_1 = require("request-filtering-agent");
9
+ /**
10
+ * A bundle costs several times its size in memory while it is parsed,
11
+ * rewritten and stored, and the extension server runs jobs side by side in a
12
+ * pod with a heap of 768 MB. Past this a download fails with a clear error
13
+ * instead of taking every job on the pod down with it.
14
+ */
15
+ exports.MAX_BUNDLE_BYTES = 100 * 1024 * 1024;
16
+ exports.FETCH_BUNDLE_TIMEOUT_MS = 60000;
9
17
  /**
10
18
  * Downloads the FHIR bundle from a Metriport webhook payload URL. The URL is
11
19
  * pre-signed and only valid for 10 minutes.
@@ -19,10 +27,11 @@ const fetchBundle = async (url) => {
19
27
  const { data } = await axios_1.default.get(url, {
20
28
  httpAgent: (0, request_filtering_agent_1.useAgent)(url),
21
29
  httpsAgent: (0, request_filtering_agent_1.useAgent)(url),
22
- // The bundle can be large; give it room and expect JSON back.
30
+ // The bundle can be large; give it room, up to a limit, and expect JSON back.
23
31
  responseType: 'json',
24
- maxContentLength: Infinity,
32
+ maxContentLength: exports.MAX_BUNDLE_BYTES,
25
33
  maxBodyLength: Infinity,
34
+ timeout: exports.FETCH_BUNDLE_TIMEOUT_MS,
26
35
  });
27
36
  return data;
28
37
  };
@@ -1 +1 @@
1
- {"version":3,"file":"fetchBundle.js","sourceRoot":"","sources":["../../../../extensions/metriport/shared/fetchBundle.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAyB;AACzB,qEAAkD;AAGlD;;;;GAIG;AACI,MAAM,WAAW,GAAG,KAAK,EAAE,GAAW,EAAmB,EAAE;IAChE,0FAA0F;IAC1F,gGAAgG;IAChG,sDAAsD;IACtD,yBAAyB;IACzB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,eAAK,CAAC,GAAG,CAAS,GAAG,EAAE;QAC5C,SAAS,EAAE,IAAA,kCAAQ,EAAC,GAAG,CAAC;QACxB,UAAU,EAAE,IAAA,kCAAQ,EAAC,GAAG,CAAC;QACzB,8DAA8D;QAC9D,YAAY,EAAE,MAAM;QACpB,gBAAgB,EAAE,QAAQ;QAC1B,aAAa,EAAE,QAAQ;KACxB,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAdY,QAAA,WAAW,eAcvB"}
1
+ {"version":3,"file":"fetchBundle.js","sourceRoot":"","sources":["../../../../extensions/metriport/shared/fetchBundle.ts"],"names":[],"mappings":";;;;;;AAAA,kDAAyB;AACzB,qEAAkD;AAGlD;;;;;GAKG;AACU,QAAA,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAA;AACpC,QAAA,uBAAuB,GAAG,KAAM,CAAA;AAE7C;;;;GAIG;AACI,MAAM,WAAW,GAAG,KAAK,EAAE,GAAW,EAAmB,EAAE;IAChE,0FAA0F;IAC1F,gGAAgG;IAChG,sDAAsD;IACtD,yBAAyB;IACzB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,eAAK,CAAC,GAAG,CAAS,GAAG,EAAE;QAC5C,SAAS,EAAE,IAAA,kCAAQ,EAAC,GAAG,CAAC;QACxB,UAAU,EAAE,IAAA,kCAAQ,EAAC,GAAG,CAAC;QACzB,8EAA8E;QAC9E,YAAY,EAAE,MAAM;QACpB,gBAAgB,EAAE,wBAAgB;QAClC,aAAa,EAAE,QAAQ;QACvB,OAAO,EAAE,+BAAuB;KACjC,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAfY,QAAA,WAAW,eAevB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awell-health/awell-extensions",
3
- "version": "2.1.203",
3
+ "version": "2.1.205",
4
4
  "packageManager": "yarn@4.5.3",
5
5
  "main": "dist/src/index.js",
6
6
  "repository": {
@@ -48,10 +48,10 @@
48
48
  "!**/__mocks__/**"
49
49
  ],
50
50
  "peerDependencies": {
51
- "@awell-health/extensions-core": ">=1.3.1 <2"
51
+ "@awell-health/extensions-core": ">=1.3.8 <2"
52
52
  },
53
53
  "devDependencies": {
54
- "@awell-health/extensions-core": "1.3.3",
54
+ "@awell-health/extensions-core": "1.3.8",
55
55
  "@faker-js/faker": "^8.4.1",
56
56
  "@graphql-codegen/cli": "^3.3.1",
57
57
  "@medplum/fhirtypes": "^3.1.4",