bonfireDB puts Postgres at the center, keeps your common app reads fresh on commit, and generates FHIR R4 underneath for export and interop. One store, no Redis. You write clinical functions; the data layer behaves like a product backend, not a federation protocol.
FHIR was built to move records between distrustful organizations. When you build one product, that model leaks into everything. bonfire flips it: Postgres is the source of truth, FHIR is a generated projection on the way out.
Every clinical write lands in Postgres (with pgvector). Referential integrity, transactions, and write semantics work the way an app team expects — not the way a stranger-to-stranger exchange assumes.
Common app reads — notesByPatient, timeline, latestScores — are maintained on commit. No eventual-consistency lag, no rebuild-the-projection cron. The reads your list screens need are already fresh.
Clients subscribe with useClinicalQuery. When a write commits and a read model updates, the change is pushed to the client. The screen reflects the database — you don't wire up invalidation by hand.
FHIR R4 is produced from the same source of truth for export and interop, on demand. You get a clean Bundle when you need to leave or integrate — not a federation protocol you have to operate to ship a feature.
Not every read can be free on commit. bonfire draws a hard line between the cheap reads your UI needs synchronously and the heavy work that can't be — and tells you exactly where each one stands.
The operational read models that power list screens, timelines, and dashboards update inside the same commit as the write. By the time the write returns, these views are fresh.
Embeddings, semantic search, agent-context assembly, and complex FHIR search run asynchronously. They don't block your write — but they don't silently rot either. Every write tells you they're pending and when they catch up.
Every write returns a freshness object. You never have to guess whether a read is current or whether a downstream index has caught up — the write tells you.
{ status: "committed", views: { notesByPatient: "fresh", timeline: "fresh" }, indexes: { semanticSearch: "pending", agentContext: "pending" } }
Call a typed function. Get a committed result, fresh operational views, and an honest status on the heavy indexes — in one response.
await clinical.notes.create({ patientId, encounterId, text }) // returns the freshness object above const notes = useClinicalQuery(api.notes.listByPatient, { patientId }) // reactive, fresh on write
The fastest way to get a stale read is to keep the same fact in two systems. Most stacks bolt a cache or a separate event bus next to the database, then spend the rest of their lives keeping the two honest. bonfire keeps one source of truth and gets freshness and reactivity from Postgres itself — no Redis in the default path.
Clinical writes commit to Postgres (with pgvector). One transaction, one place a fact lives. There is no second copy to invalidate, so there is no window where the cache and the database disagree.
The views your list screens read are maintained inside the same transaction as the write. When the commit returns, the read model is already current. That's the fast-read job a cache usually does — done with the database's own commit ordering, so it can't lag behind the write that produced it.
When a write commits, Postgres NOTIFY (and, for ordered streams, logical replication of the WAL) pushes the change outward in commit order. Subscribed clients update from the same timeline the database committed — no polling, no separate broker to keep in sync with the DB's state.
Heavy work — embeddings, semantic indexing, agent-context assembly — drains from a Postgres queue with FOR UPDATE SKIP LOCKED. Workers pull jobs concurrently without a separate queue service, and FHIR R4 is generated from the same source of truth for export and interop. The whole pipeline is one database you already operate.
Honest trade-off: Postgres-first isn't infinitely scalable on its own. The point isn't "never add a cache" — it's "don't start with one." When a measured hot path actually demands a cache or a dedicated bus, you add it deliberately, against real numbers, instead of inheriting a sync problem on day one.
Same code path the whole way. Start on your laptop, deploy into your own cloud, and write clinical functions in TypeScript.
Spin up the full stack — Postgres, pgvector, read models, reactive cache — in one command.
# local stack, one command — early access
Ship the same stack into your own AWS account. In the OSS tier it runs entirely in your infrastructure.
# deploy to your AWS — early access
Import the typed client and call clinical functions. The freshness object comes back with every write.
import { clinical } from "bonfire"
Notes, assessments, observations — typed clinical functions, not raw resources.
Explore →Committed read models and a reactive cache keep your UI in sync with the database.
Explore →pgvector under the hood, status-reported, scoped to the patient and tenant.
Explore →Clean FHIR R4 Bundles generated on demand for export and interop.
Explore →FHIR has nowhere to put drafts, UI state, or workflow status — so teams stand up a second database and inherit a sync problem. And because you can't query FHIR by arbitrary fields, joins, or aggregates, even a list screen needs a denormalized projection. bonfire ends both.
Because FHIR is generated from the source of truth, leaving — or integrating — is one call away. You get a clean R4 Bundle on demand.
await clinical.fhir.export(patientId) // clean FHIR R4 Bundle on demand
OSS tier (Apache-2.0): bonfire runs entirely in your AWS account. The data never touches our infrastructure, so we are not a Business Associate — there's nothing for us to sign, because we never handle your PHI.
Managed tier: we host it for you and sign a BAA, backed by our own AWS BAA. Same code path, same freshness model, same FHIR-underneath — you just don't operate the infrastructure.
bonfireDB is early-stage; this page describes product design and positioning. "FHIR-native" / "FHIR-compatible" are used descriptively. Choose the tier that matches who you want holding the PHI.
Postgres source of truth, fresh-on-commit reads, FHIR generated underneath. Start in one command.
Use Postgres as your source of truth and treat FHIR as an export format. FHIR was designed to move records between distrustful organizations, so it has no home for drafts, UI, or workflow state, and you can't query it by arbitrary fields, joins, or aggregates. bonfireDB writes to Postgres and generates FHIR R4 underneath on demand, so you get app-style querying plus clean interop.
No. There's no Redis in the default path. bonfireDB maintains committed operational read models inside the same transaction as the write and uses Postgres LISTEN/NOTIFY (plus WAL for ordered streams) for reactivity, so freshness comes from the database itself. You add a cache later only if a measured hot path forces it.
Common app reads like notesByPatient, timeline, and latestScores are maintained inside the same commit as the write, so by the time the write returns those views are already current. bonfireDB is designed so there's no eventual-consistency lag and no projection-rebuild cron for the operational reads your list screens need.
Every write is designed to return a freshness object: a committed status, which operational views are fresh, and the status of heavy indexes like semanticSearch and agentContext. Sync read models come back fresh; async work (embeddings, semantic search, agent context) reports pending and tells you when it catches up, so nothing silently rots.
bonfireDB is the app backend that generates FHIR underneath (not a FHIR server), not another FHIR server. Instead of operating a federation protocol to ship a feature, you write typed clinical functions over Postgres and get FHIR R4 Bundles generated on demand for export and interop. It's early access (Apache-2.0 OSS tier runs entirely in your own AWS account).