Connect a FHIR warehouse
Wire the patient and prescription ports to a real FHIR R4 warehouse using the
bundled @posos/fhir plugin, instead of an in-memory or demo adapter. This assumes
you already have an instance manifest — see
Your first instance if you don't, and
Configure ports and adapters for how port/adapter
selection works in general.
Before you start
- A reachable FHIR R4 server.
@posos/fhiris tested against samply/blaze and against Synthea-generated bundles loaded into it (external-plugins/fhir/plugin.yaml,external-plugins/fhir/adapters/patient.ts). Other FHIR R4 servers may work but are untested. @posos/fhirpresent in your plugin set. It is a core plugin shipped in the API image (external-plugins/fhir) and is loaded like any other plugin (aPLUGINS_PATHroot); nothing below is registry-specific.
1. Stand up a test warehouse (optional)
If you don't have a FHIR store yet, run Blaze locally and load Synthea data:
docker run -d --name blaze -p 8090:8080 -e STORAGE=in-memory samply/blaze:latest
node external-plugins/fhir/scripts/load-synthea.mjs # loads Synthea bundles into Blaze
The docker-compose.yml stack profile also runs a persistent Blaze
(samply/blaze:1.10.1, RocksDB storage) on the same port mapping (8090:8080); use
that instead for anything longer-lived than a one-off test.
2. Declare the connection to the warehouse
@posos/fhir contributes the connection type fhir-r4; its two adapters and its
fhir-poll source require a connection of that type. Declare it in the instance
manifest and make it the default of the type:
connections:
entrepot:
type: fhir-r4
label: Entrepôt FHIR local
baseUrl: http://localhost:8090/fhir
connectionDefaults:
fhir-r4: entrepot
A protected warehouse takes an authentication scheme among those the type accepts
(none, basic, header, bearer, oauth2-client-credentials), its secret written
env:NAME:
auth: { scheme: bearer, token: env:FHIR_TOKEN }
The connection can also be created in the console (Connections), with a credential of the store. See Manage connections.
3. Point the ports at the warehouse
Set the patient and prescription ports to the fhir adapter. Without
connection, each uses the default connection of type fhir-r4:
ports:
patient: { adapter: fhir }
prescription: { adapter: fhir }
instance.example.yaml at the repo root wires both ports this way against the local
Blaze from step 1. To use another connection for one port, name it:
prescription: { adapter: fhir, connection: autre-entrepot }.
The connection is resolved at each call: a connection modified in the console applies
to the next request. Without a usable connection, the call fails with
Aucune connexion par défaut pour le type « fhir-r4 ». … or
Connexion « <id> » inutilisable : ….
The previous configuration — config.baseUrl on the port, or the FHIR_BASE_URL
variable — is still read for one version, with the warning
[fhir] config.baseUrl est déprécié : déclarer une connexion de type fhir-r4 et la désigner par connection. (or FHIR_BASE_URL est déprécié …).
Note that FHIR has no single "prescription" resource: the prescription port
adapter treats the id you pass it as a patient id and aggregates that patient's
MedicationRequest resources (external-plugins/fhir/adapters/prescription.ts). For
the full resource mapping behind both ports (Patient, MedicationRequest, Condition,
AllergyIntolerance, Observation, Communication, Task…), see
FHIR resource mapping (apps/ehr-lab/docs/resources/reference/fhir-mapping.md) and the
port contracts reference.
4. Declare the plugin's own dependencies
@posos/fhir's manifest lists dependsOn: { "@posos/clinical": "^0.2.0", "@posos/common": "^0.4.0" } (external-plugins/fhir/plugin.yaml): its default event bindings project raw
FHIR resources onto events declared (contributes.events) by those two plugins (e.g.
fhir-creatinine → BioResultReceived, fhir-communication-received →
CommunicationReceived), and are checked against those contracts at load. If you're assembling your instance from plugins pulled off
a registry rather than using the bundled set as-is, make sure @posos/clinical and
@posos/common are declared alongside @posos/fhir, in versions those ranges accept:
the host does not load @posos/fhir next to a version outside them (« dépendance
signalée ») — see
Declare and install plugins and
Set up a plugin registry.
Connecting the warehouse as an event source (polling FHIR for new resources, e.g.
new lab results) is a separate concern from the port wiring above: declare an inbound
connection of type fhir-poll that names the fhir-r4 connection and the bindings
to enable:
connections:
fhir-poll:
type: fhir-poll
connection: entrepot # absent: default connection of type fhir-r4
intervalSeconds: 15
bindings: [fhir-creatinine, fhir-validation-decided] # absent: every binding of fhir-poll
The console creates the same connection in Connexions → Connexions entrantes,
format « Interrogation FHIR ». See Connect an event source.
After each restart of the API, fhir-poll re-reads the last startOverlapSeconds
(default 600 s) of each feed: resources modified during a shorter stop start their
runs, and resources already received are duplicates. Notes and conditions written to the record
by workflows come back through these feeds as events — see
Meridian's own writes come back as events. Each poll follows the pages of the
search, up to maxPages pages of count resources per feed (10 pages of 50 by
default), and the next poll continues right away where it stopped — see
Restarts and the overlap window
and Pages and cursor of a feed.
The next links of a Blaze result carry its BASE_URL; the source requests those
pages on the base URL of its connection. Each request is limited to
requestTimeoutSeconds (30 s by default).
Each resource read is stored in the reception inbox before its binding projects it.
A resource whose processing fails (Restate unreachable, for instance) does not block
its feed: the reception delivers it again until it passes — see
Records that fail to process.
5. Verify the connection
Smoke-test the store directly:
curl http://localhost:8090/fhir/Patient?_count=1should return aBundle.Run the adapter's own integration check, which reads and writes through the real
patientport against Blaze:FHIR_BASE_URL=http://localhost:8090/fhir pnpm run test:fhirThis requires at least one adult
Patientalready loaded (the Synthea bundles from step 1 provide this) — seeapps/api/tests/test-fhir.ts.Boot the endpoint against the manifest that wires FHIR (
INSTANCE_CONFIG=instance.example.yaml pnpm run endpoint:fhir, orpnpm run dev:fhirfor the full endpoint+proxy+UI stack) and confirm a workflow can read a patient snapshot end to end.
Validation decisions written in the warehouse
When a run suspends on a human validation and the patient port is on the fhir
adapter, the adapter writes a Task (status: requested, code
urn:ehr-lab:validation|decision) with two identifiers:
urn:ehr-lab:validation-run (the run id) and urn:ehr-lab:validation-pending
(the validation, pending.id of the run). The fhir-validation-decided binding
resumes the run when that Task moves to completed (accepted) or rejected,
and applies the decision only to the validation the Task names.
A run can wait on several validations in turn, each with its own Task under the
same run identifier. The system that records the decision must update the Task
still requested — for instance the most recent one of the run with
Task?identifier=urn:ehr-lab:validation-run|<runId>&status=requested&_sort=-_lastUpdated
— and keep both identifiers. A decision written on the Task of an earlier
validation is not applied to the current one.
Concurrent updates to a Patient
patient.update-demographics reads the Patient, applies its patch, then writes it
back with If-Match: W/"<versionId>", the version read by the GET
(external-plugins/fhir/adapters/patient.ts, updateDemographics). When another
writer changed the resource in between, the server answers 412 Precondition Failed;
the adapter reads the resource again and reapplies the patch, up to three times after
the first attempt, then fails the node with
HTTP 412 : ressource modifiée en parallèle, échec après 4 essais. Two runs updating
the same patient at the same time therefore keep both changes.
The warehouse must support versioned updates (meta.versionId on reads, If-Match
on PUT). A resource read without meta.versionId is written back unconditionally.
Related
- Configure ports and adapters
- Implement a port adapter — to write your own adapter for a different warehouse
- Connect an event source
- FHIR resource mapping (
apps/ehr-lab/docs/resources/reference/fhir-mapping.md) - Instance manifest reference