Skip to content

void/db can't carry a D1 bookmark across requests #783

Open

Description

@karthickeyan-dev

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-primary there 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()'s session option is a D1SessionConstraint, so it doesn't take a bookmark.
  • The request db doesn't expose its session's getBookmark().

Proposal

An opt-in setting, such as database: { bookmark: 'cookie' } in void.config.ts. With it on, Void:

  1. starts each request's session from a bookmark cookie when there is one, and from first-unconstrained otherwise;
  2. sets the cookie to the session's getBookmark() when the response goes out, as HttpOnly, SameSite=Lax, with a short Max-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()'s session option take a bookmark string, as withSession() does;
  • expose the request session's bookmark, such as getBookmark() from void/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.

Activity

  1. self-assigned this
    on Nov 11, 2026
  2. kossnocorp commented on Nov 11, 2026

    @kossnocorp
    Contributor

    Thank you! I'm on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions