Summary
EmDash's Cloudflare D1 session adapter (@emdash-cms/cloudflare/src/db/d1.ts) routes reads to either first-primary, the bookmark from a prior write, or first-unconstrained based on opts.isAuthenticated. In the Astro middleware (src/astro/middleware.ts:282), isAuthenticated is set to !!sessionUser, and sessionUser is read only from the astro-session cookie:
const hasSessionCookie = cookies.get("astro-session") !== undefined;
const sessionUser = context.isPrerendered || !hasSessionCookie
? null
: await context.session?.get("user");
As a result, requests authenticated via Authorization: Bearer <PAT> (issued by _emdash/api/auth or directly to D1) are treated as anonymous by the D1 adapter:
- Writes still hit primary (correct —
isWrite=true forces first-primary).
- Reads go to
first-unconstrained (any replica), and the bookmark cookie is never persisted because commit() early-returns when !opts.isAuthenticated.
This means: a Bearer-authenticated client can POST data, get 200 back, and a subsequent GET (even seconds later) returns stale results from a replica that hasn't caught up.
Repro
A Python script using Authorization: Bearer ec_pat_… to call POST /content/posts/{slug}/terms/{tax} and then immediately GET the same path: POST returns the term in the response body, GET returns empty.
Workaround we adopted
Switched astro.config.mjs d1({ binding: "DB", session: "primary-first" }). This forces every read (anonymous or not) to start at primary — kills replica reads entirely. Tolerable on a small site, but defeats the purpose of read replicas on larger ones.
Suggested fix
Populate sessionUser (or a sibling flag like isApiAuthenticated) from the resolved user when Bearer auth succeeded. The auth resolver already runs upstream of createRequestScopedDb; passing user rather than re-reading the cookie would let API clients benefit from primary-first / bookmark-resumed reads.
Tested against emdash 0.9.0. Happy to discuss approach if helpful.
Summary
EmDash's Cloudflare D1 session adapter (
@emdash-cms/cloudflare/src/db/d1.ts) routes reads to eitherfirst-primary, the bookmark from a prior write, orfirst-unconstrainedbased onopts.isAuthenticated. In the Astro middleware (src/astro/middleware.ts:282),isAuthenticatedis set to!!sessionUser, andsessionUseris read only from theastro-sessioncookie:As a result, requests authenticated via
Authorization: Bearer <PAT>(issued by_emdash/api/author directly to D1) are treated as anonymous by the D1 adapter:isWrite=trueforcesfirst-primary).first-unconstrained(any replica), and the bookmark cookie is never persisted becausecommit()early-returns when!opts.isAuthenticated.This means: a Bearer-authenticated client can POST data, get 200 back, and a subsequent GET (even seconds later) returns stale results from a replica that hasn't caught up.
Repro
A Python script using
Authorization: Bearer ec_pat_…to callPOST /content/posts/{slug}/terms/{tax}and then immediatelyGETthe same path: POST returns the term in the response body, GET returns empty.Workaround we adopted
Switched
astro.config.mjsd1({ binding: "DB", session: "primary-first" }). This forces every read (anonymous or not) to start at primary — kills replica reads entirely. Tolerable on a small site, but defeats the purpose of read replicas on larger ones.Suggested fix
Populate
sessionUser(or a sibling flag likeisApiAuthenticated) from the resolved user when Bearer auth succeeded. The auth resolver already runs upstream ofcreateRequestScopedDb; passinguserrather than re-reading the cookie would let API clients benefit from primary-first / bookmark-resumed reads.Tested against emdash 0.9.0. Happy to discuss approach if helpful.