| 1 | import type { Env } from "../env.js"; |
| 2 | |
| 3 | /** |
| 4 | * D1 read replication through the Sessions API. |
| 5 | * |
| 6 | * Every request runs its D1 queries in one session, so reads can come from a nearby |
| 7 | * replica while still seeing everything the same client wrote before: |
| 8 | * |
| 9 | * - Browsers carry the latest bookmark in a cookie. A request continues from it, so a |
| 10 | * page loaded after a form post sees that post's writes (sign-in, new repositories…). |
| 11 | * - Token clients (git, CLI, MCP, runners) don't keep cookies, so they read from the |
| 12 | * primary unless they send back the `x-d1-bookmark` header from an earlier response. |
| 13 | * - Anonymous first visits take the nearest copy ("first-unconstrained"). |
| 14 | * |
| 15 | * Durable Objects, queue consumers and cron use the binding directly, which always |
| 16 | * goes to the primary. |
| 17 | */ |
| 18 | export const BOOKMARK_COOKIE = "__Host-dfy_d1"; |
| 19 | export const BOOKMARK_HEADER = "x-d1-bookmark"; |
| 20 | |
| 21 | /** Bookmarks look like `00000085-0000024c-00004c6d-8e61117bf38d7adb71b934ebbf891683`. */ |
| 22 | export const isBookmark = (s: string | null | undefined): s is string => !!s && s.length <= 128 && /^[0-9a-f]+(-[0-9a-f]+){1,7}$/i.test(s); |
| 23 | |
| 24 | export type D1Session = { env: Env; bookmark(): string | null }; |
| 25 | |
| 26 | /** An env whose DB runs every query in one session that starts at `constraint`. */ |
| 27 | export function d1Session(env: Env, constraint: string): D1Session { |
| 28 | // Test doubles and old runtimes without sessions keep using the binding as is. |
| 29 | if (typeof env.DB.withSession !== "function") return { env, bookmark: () => null }; |
| 30 | const session = env.DB.withSession(constraint); |
| 31 | return { env: { ...env, DB: session as unknown as D1Database }, bookmark: () => session.getBookmark() }; |
| 32 | } |
| 33 | |
| 34 | /** Where a request's session starts. */ |
| 35 | export function sessionConstraint(o: { header?: string | null; cookie?: string | null; token: boolean }): string { |
| 36 | if (isBookmark(o.header)) return o.header; |
| 37 | if (!o.token && isBookmark(o.cookie)) return o.cookie; |
| 38 | return o.token ? "first-primary" : "first-unconstrained"; |
| 39 | } |