Common app reads are fresh on commit — the moment a write lands, not 15–30s later. No polling, no manual cache invalidation. Heavy work runs async and tells you exactly where it stands.
The sync / async split
FHIR servers treat every change the same way: write it, then wait for the index to catch up. That works when records crawl between distrustful hospitals. It falls apart when one team is building one product and a clinician just signed a note. bonfireDB splits the difference — the reads your UI needs every second are fresh on commit, and the expensive work happens off the commit path with a status you can watch.
Write the resource, then wait for eventual consistency before search reflects it. AWS HealthLake, for one, documents a default eventual-consistency window of ~15–30s between a write and when it's searchable. Realtime is best-effort: rest-hook delivery can silently drop, events arrive out of commit order, bulk $import emits zero events, and the channel can fall days behind.
Postgres is the source of truth. Operational read models — notesByPatient, timeline, latestScores — are kept fresh on commit, inside the same transaction, so common reads reflect the write the instant it lands. A reactive query cache pushes the new value to the client. Heavy work runs async with reported status.
The freshness lifecycle
A write doesn't return a bare 200. It returns a freshness object: the commit status, which operational views are already fresh, and which heavy indexes are still catching up. You decide what to render now and what to wait on — without guessing.
const result = await clinical.notes.create({ patientId, encounterId, text }) // result is the freshness lifecycle object { status: "committed", views: { notesByPatient: "fresh", timeline: "fresh" }, indexes: { semanticSearch: "pending", agentContext: "pending" } }
Operational views are fresh on commit. Heavy indexes (semantic search, agent context, complex FHIR search) run async and report pending until they catch up — so you always know the truth instead of assuming it.
How it works — Postgres-first
Freshness isn't a separate cache or message bus you stand up alongside the database. It's the database. bonfireDB is TypeScript + Postgres + pgvector, with FHIR R4 generated underneath — and Postgres alone carries commit-ordered freshness. No Redis. No second system to deploy, secure under a BAA, keep consistent, and page someone about at 2am.
Operational read models are updated in the same transaction as the write. When the commit lands, the view is already fresh — there's no window where the write succeeded but the read is stale.
Postgres LISTEN/NOTIFY plus the WAL via logical replication drive the reactive query cache — so clients are notified in commit order, not best-effort and out of sequence like rest-hooks.
Heavy work (embeddings, semantic index, agent context) drains from a Postgres work queue with FOR UPDATE SKIP LOCKED — durable, concurrent, and idempotent, with no extra broker to operate.
Postgres-first by default: LISTEN/NOTIFY + WAL/logical-replication for commit-ordered freshness, a SKIP LOCKED queue for the async lane. We add a separate cache or bus only when a measured hot path forces it — not on day one, not by reflex.
Subscribe once. When a write updates an operational view, Postgres LISTEN/NOTIFY drives the reactive query cache and the new value is pushed to the client. No refetch, no polling, no invalidation logic to maintain.
// reactive, fresh on commit — no refetch / poll / invalidation const notes = useClinicalQuery( api.notes.listByPatient, { patientId } ) // when clinical.notes.create commits, // this view is fresh and re-renders automatically
Heavy work, off the commit path
Semantic search, embeddings, agent context assembly, and complex FHIR search are too heavy to block a write. They run asynchronously off the commit path and report pending → fresh through the same freshness lifecycle. Your write stays fast; your indexes never silently fall behind.
Operational read models like notesByPatient, timeline, and latestScores are kept fresh as part of the write.
Semantic search and embeddings rebuild off the commit path, surfacing pending until they're caught up.
Semantic search →Cited, permission-aware context for agents is assembled async — never blocking the clinician's write.
Custom MCP builder →FHIR R4 is generated underneath for export and interop, on demand — not on the hot path of every read.
FHIR underneath →The same freshness model powers the agent layer. Session prep pulls a cited, permission-aware window of the record — assembled async, off the write path, so the moment a note commits the clinician keeps working while heavier context catches up.
// cited, permission-aware — assembled async const ctx = await clinical.agent.sessionPrep({ patientId, windowDays: 90, include: ["recentNotes", "assessments", "tasks"] }) // clean FHIR R4 Bundle on demand, not on the hot path await clinical.fhir.export(patientId)
Common reads fresh on commit, heavy work async with honest status, FHIR generated underneath. Stop operating a federation protocol — start shipping your app.
bonfireDB keeps operational read models like notesByPatient, timeline, and latestScores fresh inside the same transaction as the write, so common reads reflect the change the instant the commit lands — no 15–30s eventual-consistency window. This is a design goal of the early-access product, not a benchmarked claim.
As of 2026 (verify current), AWS HealthLake documents a default eventual-consistency window of roughly 15–30 seconds between a write and when it becomes searchable, because FHIR servers rebuild the search index after the write. bonfireDB instead keeps common app reads fresh on commit and runs only heavy indexes async. It's an app backend that generates FHIR underneath (not a FHIR server), not another FHIR server.
It uses Postgres LISTEN/NOTIFY plus the WAL via logical replication to push new values to a reactive query cache in commit order. There's no Redis or message bus to deploy — bonfireDB is designed as TypeScript + Postgres + pgvector, so Postgres alone carries commit-ordered freshness.
Heavy work like semantic search, embeddings, and agent context runs async off the commit path and reports pending → fresh through a freshness lifecycle object returned by each write. Indexes report their status honestly instead of silently falling behind, so you always know what's caught up.
Yes — useClinicalQuery subscribes once and re-renders when the underlying operational view changes. When a write commits, Postgres LISTEN/NOTIFY drives the reactive cache and the new value is pushed to the client, with no refetch, polling, or manual cache invalidation to maintain.