Контейнер для группы FHIR-ресурсов. Используется широко: API-операции (создать несколько ресурсов одним запросом), search responses (сервер возвращает множество ресурсов на query), document export (clinical document как root + referenced resources), async messaging.
Структура Bundle
Resource Bundle (FHIR R4) имеет следующие поля1:
| Поле | Cardinality | Когда используется |
|---|---|---|
type | 1..1 (code) | Всегда — определяет семантику (см. таблицу ниже) |
entry[] | 0..* (BackboneElement) | Сами ресурсы / операции; разворачивается ниже |
identifier | 0..1 (Identifier) | Идентифицировать сам Bundle (transaction id, document id) |
timestamp | 0..1 (instant) | Когда Bundle был assembled |
total | 0..1 (unsignedInt) | Для searchset — общее число matching resources (для пагинации) |
link[] | 0..* (BackboneElement) | Pagination links (self, next, prev) — для searchset с paging |
signature | 0..1 (Signature) | Digital signature — особенно для document Bundle |
Каждый entry сам — BackboneElement с несколькими полями:
fullUrl(uri) — URI ресурса. Для нового ресурса в transaction —urn:uuid:<UUID>placeholder; для existing — absolute URL.resource(Resource) — сам FHIR-ресурс целиком.request(для transaction/batch) —method(POST/PUT/DELETE/GET),url, плюс conditional headers:ifNoneMatch,ifMatch,ifNoneExist,ifModifiedSince.response(от сервера) —status(201 Created,200 OK, etc.),location(assigned URL),etag,lastModified,outcome(если были warnings/errors).search(для searchset) —mode(match/include/outcome),score(для ranked results).link[]— links специфичные для entry.
Типы Bundle (FHIR R4)
Bundle.type — обязательное поле, определяет семантику обработки:
| Type | Что это |
|---|---|
transaction | Атомарный batch CRUD — сервер применяет всё или ничего (ACID). Resolve внутренних urn:uuid: placeholder’ов. |
batch | Неатомарный batch CRUD — каждый entry независим, failures одного не блокируют другие. |
document | Документ-снимок: первый entry — Composition, дальше referenced ресурсы. Семантически законченный clinical document. Используется в ips-standard (Patient/$summary возвращает Bundle type=document). |
searchset | Ответ сервера на FHIR search query (GET Observation?subject=Patient/123 → matching ресурсы + included). |
collection | Bag of resources без специфической семантики — просто группа. |
history | Ответ сервера на history-операцию — versions ресурса. |
message | Async messaging — первый entry это MessageHeader. |
Transaction Bundle
В нашем стеке — основной кейс. Pipeline-шаг save-fhir-resources собирает Bundle с Composition + Observations + ServiceRequest + CarePlan + Provenance + Organization и POST-ит в google-healthcare-api одним запросом. Атомарность критична: частичный fail хуже чем full rollback (получится half-saved analysis без Composition или без observations).
Пример Bundle (Patient + Observation которая ссылается на ещё-не-созданный Patient):
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:patient-1",
"resource": { "resourceType": "Patient", ... },
"request": { "method": "POST", "url": "Patient" }
},
{
"fullUrl": "urn:uuid:obs-1",
"resource": {
"resourceType": "Observation",
"subject": { "reference": "urn:uuid:patient-1" }
},
"request": { "method": "POST", "url": "Observation" }
}
]
}UUID resolution
Ключевой механизм: одна и та же строка urn:uuid:patient-1 живёт в двух местах — как fullUrl нового ресурса (декларация placeholder’а) и как Reference.reference из другого entry (ссылка на него). Сервер matches их по строке, присваивает real id, переписывает ссылки.

Источник Mermaid — attachments/bundle-uuid-resolution.mmd.
Четыре прохода на сервере2:
- Parse — собрать все
urn:uuid:*placeholder’ы изentry[].fullUrl - Assign real IDs — для каждого POST entry сгенерировать server-assigned id (
urn:uuid:patient-1 → Patient/p-9001) - Rewrite references — найти все
Reference.referenceсо значениемurn:uuid:patient-1и заменить наPatient/p-9001 - Apply atomically — записать всё за одну transaction. Любой fail → rollback всех entries (ACID).
Альтернативные форматы fullUrl:
urn:uuid:<UUID>— placeholder для нового ресурса (POST). Resolve’ится в server-assigned id.- Absolute URL (
https://server/Patient/123) — для existing ресурса (PUT/PATCH/DELETE/GET). Не resolve’ится. - Conditional через
ifNoneExist— см. ниже.
Conditional create — ifNoneExist
Паттерн для shared ресурсов, которые должны существовать в FHIR store ровно одной копией (Organization-as-author, ранее fhir-device). В entry.request ресурса добавляется условие — сервер создаст ресурс только если такого ещё нет:
{
"resource": { "resourceType": "Organization", "id": "bloodgpt-com", ... },
"request": {
"method": "POST",
"url": "Organization",
"ifNoneExist": "identifier=bloodgpt-com"
}
}Идемпотентно — Bundle можно повторно отправить без дублей. Применяется для refs между Bundle’ами: один раз создан Organization/bloodgpt, далее каждый analysis Bundle ссылается на него по identifier через ifNoneExist.
Можно ли генерировать кодом? Codegen FHIR-инструменты (fhir-code-generation) разделяются на два слоя:
- Type generation — типы / билдеры из StructureDefinition.
Bundle.entry.request.ifNoneExistпоявляется как typed string field; задать значение через builder — генерится автоматически. Тривиально. - Workflow / pattern generation — «когда применять
ifNoneExist, на какой Identifier, с какой URL’ой» — не выводится из спеки. Это application decision: знание «Organization — singleton, всегда ifNoneExist по identifier» не часть FHIR profile, а часть нашей domain logic.
В save-fhir-resources.ts ifNoneExist для Organization пишется руками. Если бы хотели автоматизировать — сделали бы helper createSharedResource(organization) который сам бы emit’ил entry с правильным ifNoneExist. Это уровень custom infrastructure, а не FHIR codegen.
Использование в BloodGPT
Один transaction Bundle = один analysis run. Реализация — packages/analysis-core/src/lib/save-fhir-resources.ts. Document / searchset / batch / message не используем.
Связано
- fhir-composition, fhir-careplan, fhir-observation — типичный контент внутри transaction Bundle
- fhir-resource-elements — общая механика resource elements (cardinality, choice types, Reference) на примере US Core Lab Observation
- ips-standard — Bundle type=document
- fhir-code-generation — что genenrators умеют (типы) и не умеют (паттерны типа
ifNoneExist-by-rule) - google-healthcare-api — наш FHIR backend, принимает transaction Bundle через POST
- phi-in-fhir-not-sql — Bundle как pipeline transport между Inngest шагами
- inngest —
save-fhir-resourcesшаг собирает и отправляет Bundle
Сноски
-
HL7 R4 spec — Bundle, accessed 2026-05-20, https://hl7.org/fhir/R4/bundle.html. ↩
-
HL7 R4 spec — Bundle transaction processing, accessed 2026-05-20, https://hl7.org/fhir/R4/bundle.html#transaction. ↩