Repository navigation
Description
Version
void 1.7.0, and main at fecc1d4
Problem
Follow-up to #755, which asked for two things. fecc1d4 shipped the first, createDb(env.DB, { session: 'first-primary' }), and closed the issue. The second, a bookmark that carries across requests, isn't tracked anywhere, so here it is on its own.
Each request's db starts a new session on first-unconstrained. Within a request, reads see that request's writes. In the next request they may not: a write in one request followed by a read in the next can still reach a replica that hasn't caught up.
first-primary fixes this one read at a time, but only if the app knows which reads follow a write. Most of the time it doesn't:
- A form action saves and redirects, and the next page's loader shows the old data. Every loader that might run after any write would need
first-primary, which gives up replicas for those pages entirely. - A user pays and the client visits a page gated on that payment. Reading the user with
first-primarythere works, but the page is reached in normal navigation far more often than after a payment, and those visits would lose the replica too. The same goes for every other page that shows the paid state.
D1's own answer is the bookmark: save session.getBookmark() at the end of a request, and start the next request's session with withSession(bookmark), so it sees at least everything the user has written.
Void can't do this yet, and neither can an app:
createDb()'ssessionoption is aD1SessionConstraint, so it doesn't take a bookmark.- The request
dbdoesn't expose its session'sgetBookmark().
Proposal
An opt-in setting, such as database: { bookmark: 'cookie' } in void.config.ts. With it on, Void:
- starts each request's session from a bookmark cookie when there is one, and from
first-unconstrainedotherwise; - sets the cookie to the session's
getBookmark()when the response goes out, asHttpOnly,SameSite=Lax, with a shortMax-Age, since a bookmark older than replica lag adds nothing.
If that's more than Void wants to own, the building blocks would let apps do it in middleware:
- let
createDb()'ssessionoption take a bookmark string, aswithSession()does; - expose the request session's bookmark, such as
getBookmark()fromvoid/db; - let middleware set the request
db's starting constraint or bookmark before the page runs.
Prior art
Rails' DatabaseSelector reads from the primary for a while after a user writes, tracked in the session cookie. Laravel's sticky option does the same within a request. Cloudflare's read replication guide recommends passing the bookmark back to the client for exactly this case.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
kossnocorp commentedon Nov 11, 2026
Thank you! I'm on it.