The demo is a standalone TanStack Start app. Its Alchemy stack is separate from the Affinity API and dashboards.
Better Auth stores anonymous users and sessions in a demo-owned D1 database. Each browser has one Test practice, an HttpOnly session cookie, and a fixed 12-hour expiry. Returning in that browser resumes the workspace until expiry. There is no recovery or sharing of an anonymous session.
The UI uses typed TanStack Start server functions and TanStack Query.
Affinity from SDK 1.15.0 sends API version 2026-09-28.
A session-scoped forPractice() client handles patient, catalog, and order requests.
Resource IDs come first, request data follows, and persisted idempotency keys use the final options argument.
The server normalizes API JSON before returning it through TanStack Start. SDK calls stay on
the server. Practice IDs come from the session, not browser input.
Order reads, signing, and submission check order ownership before proceeding.
Patient requests use the session's practice. Inline patients and arbitrary prescribers
are not accepted by the demo. Live mode is rejected server-side, and no Live key is bound.
The demo uses a dedicated platform Test key. The browser receives only its own session's practice, never the platform's practice directory. Affinity Test orders route to simulators.
Limits are 5 starts per IP per UTC day, 100 starts globally per UTC day, 120 API requests per session per minute, and 30 order-creation attempts per session per UTC day. Retries count toward these abuse limits. Quotas use atomic Durable Object transactions. IPs are salted and hashed before becoming quota keys. Durable Object alarms remove expired session mappings and quota counters. Synthetic records in Affinity remain for audit history; session expiry is not clinical-record deletion.
Cross-origin and missing-Origin writes are rejected. Mutation keys are namespaced by session. A pending browser mutation retains its key until the result is known. The public deployment has no shared webhook inbox, prescriber-management endpoint, or authenticated image proxy. Unused proxy and webhook handlers are removed.
Use Bun. Vite+ runs Vite 8, formatting, linting, and TypeScript checks from
vite.config.ts.
bun install --frozen-lockfile
bun run check
bun test
bun run buildThe website requires its Worker D1 and Durable Object bindings. Run the local Worker through Alchemy with an isolated Test key and session secret supplied in the child environment:
doppler run --project affinity --config stg -- bun run devThe local Worker binds to loopback at port 1337. Do not publish a development server
or point this app at Live credentials. The deploy graph fixes the hosted Affinity API
to https://api.affinityrx.com.
On the Affinity Devbox, use the optional emr-demo service in the sibling Affinity
repository. It runs this checkout with hot reload at the protected URL
affinity.harbr.run/emr-demo/:
dt services start emr-demo --environment affinity
dt services logs emr-demo --environment affinityThis service sets VITE_BASE_PATH=/emr-demo/ and PORT=1337. Public builds retain /.
Saving source files updates development only; it does not deploy the public demo.
For standalone SDK experiments without a website, copy .env.example to the ignored
.env.dev, set your own Test key, and run bun run example.
Never put hosted secrets in environment files.
The canonical stack is affinity-emr-demo, stage demo, in this repository's
alchemy.run.ts. It owns Worker affinity-emr-demo, its Better Auth D1 database, ownership/quota Durable Object binding,
and demo-emr.affinityrx.com. Alchemy retains demo-emr.joinaffinityai.com as an alias;
the Worker redirects it to the new hostname with HTTP 308, preserving paths and queries.
Operator and runtime secrets come from Doppler affinity/stg for this independent demo:
CLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_EMR_DEMO_API_TOKEN: deployment only. The dedicated token needs Workers deployment, domain, and Account → D1 → Edit permissions.scripts/alchemy.tsmaps it to Alchemy's token variable only in the child process.AFFINITY_HARBOR_DEMO_API_KEY: dedicated Harbor platform Test key for this demo.AFFINITY_DEMO_SESSION_SECRET: Better Auth signing/encryption secret.
Alchemy binds only the last two secrets to the Worker, under
AFFINITY_API_KEY and DEMO_SESSION_SECRET. The demo key needs
catalog:read, practices:read, practices:write, patients:read,
patients:write, orders:read, orders:write, orders:sign, and team:write.
The provisioned key expires after 90 days and must be rotated before expiry.
Rotating the session secret invalidates existing cookies.
doppler run --project affinity --config stg -- bun run preview:demo
doppler run --project affinity --config stg -- bun run deploy
doppler run --project affinity --config stg -- bun run preview:demoReview the plan and require a no-op after deployment. Preserve this checkout's ignored
.alchemy state. Do not deploy from a second empty state or use Wrangler as a parallel
deployment path. This graph does not redeploy the Affinity API or dashboards.