Skip to content

Architecture

EstimateIQ runs the whole application — API, UI, and data — from a single Cloudflare Worker, deployed to esb.guru. The design goal is a synchronous, transactional data layer at the edge with no round-trips to a remote database on the hot path, and a zero-flash server-rendered UI.

Stack

LayerTechnology
RuntimeBun (local dev + build); the Cloudflare Workers runtime in production
APIHono — one app (src/app.ts) that mounts every /api route
UIAstro with output: 'server' SSR via @astrojs/cloudflare, embedded in the Worker
DataA Durable Object wrapping synchronous SQLite — the source of truth
ORMA single Drizzle schema (src/db/schema.ts), shared by the Worker, the DO, and D1
Edge servicesD1, R2, KV, Queues, Workflows, Vectorize, Workers AI, AI Gateway
InfraTerraform (data plane) + Ansible (data load) — see Infrastructure as code

The Worker owns everything

The Worker (cloudflare/worker/index.ts) is the front door. On every request it decides where to go:

  1. POST /api/admin/* — handled at the edge, before the Access gate, guarded by a Bearer secret (REVSRC_SECRET_KEY). This is how automation loads data and reindexes without a browser session.
  2. Cloudflare Access — in production, every non-public path must carry a valid Access JWT (/healthz is public).
  3. /api/ai-brief/:id — reads/writes the persisted next-best-action brief in R2.
  4. /api/searchWorkers AI embeds the query, Vectorize returns matches, KV caches them.
  5. Any other /api/* or /healthz — forwarded to the data Durable Object, which runs the shared Hono app over synchronous SQLite.
  6. Static assets (/_astro/*, /brand/*, /docs/*, favicon) — served from Assets.
  7. Everything else — falls through to Astro SSR, rendered server-side from live DO data.

The Worker also exports the Durable Objects and the Workflow so wrangler can bind them, and it implements two background handlers: queue() (inbound carrier mail) and scheduled() (the hourly SLA sweep).

Why a Durable Object for data

The primary store is the EstimateIQ Durable Object with an embedded SQLite database accessed synchronously. This gives EstimateIQ:

  • Single-writer consistency — one DO instance (primary) owns the estimate state machine, so status transitions and ball-in-court updates are serialized and race-free.
  • Transactional reads and writes with no network latency between the app and its data.
  • Automatic co-location of compute and state at the edge.

Because DO SQLite is synchronous, the exact same synchronous repo/actions code runs both in the Bun dev server and inside the Worker — setDb() just points the shared runtime at whichever database is active. Tables are created from the shared DDL on first run inside blockConcurrencyWhile, so init always completes before any request is served.

The DO starts empty — nothing is baked into the image and there is no auto-seed. Data is loaded explicitly through the API (see Data loading).

A second Durable Object, SlaTimerDO, holds one alarm per estimate: when the ball is with us and a response is due, it arms an alarm that — on fire — enqueues an escalation nudge so a silent estimate never slips.

Server-side rendering, embedded in the Worker

The UI is Astro with output: 'server', built by @astrojs/cloudflare into a _worker.js bundle that is imported directly into the front-door Worker. Pages render server-side on every request with a zero-flash first paint — no client-side loading spinner and no stale build-time data.

Because the SSR handler runs in the same Worker as the DO, a page reads live data by calling the DO stub through Astro.locals.runtime.env — no network hop. Any dynamic route (any :id — an estimate, a carrier, an estimator) is rendered on demand against the DO’s current state. In local astro dev there is no runtime binding, so the same data helper falls back to an HTTP fetch against the Hono API.

Cloudflare services in use

ServiceBindingPurpose
Durable ObjectsESTIMATEIQ, SLA_TIMERSThe synchronous-SQLite data layer + per-estimate SLA alarms.
D1DBAppend-only event/audit mirror for cross-instance reporting.
R2ARTIFACTSAI briefs (ai/brief/:id.json) + raw .eml correspondence + Xactimate artifacts.
KVCACHESemantic-search result cache + last-reindex marker.
VectorizeVECTORIZEVector index (estimateiq-notes, 768-dim) for semantic search.
Workers AIAIbge-base-en-v1.5 embeddings for search indexing + queries.
QueuesMAIL_INGEST (+ DLQ)Inbound carrier-mail ingest, consumed by Worker.queue().
WorkflowsREVISION_WFRevisionWorkflow — the durable revision lifecycle.
AI GatewayAuthenticated gateway that routes model calls to Claude (see AI).
CronHourly SLA sweep (0 * * * *) that re-arms SlaTimerDO alarms from the worklist.
AssetsASSETSThe built Astro static bundle, with run_worker_first for /api/* + /healthz.

Everything ships as one Worker to a single origin, https://esb.guru, on the derekethandavis Cloudflare account.