59 lines2,461 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1/**
2 * Reading a service's D1 database near the caller with D1's Sessions API,
3 * and saying how long an RPC took: the TypeScript side of
4 * crates/kit/src/d1.rs, with the same rules.
5 *
6 * The caller chooses, per request, with the `x-d1-bookmark` header:
7 *
8 * | Header | Reads go to |
9 * | --- | --- |
10 * | absent | the primary, with no session (as always) |
11 * | `first-primary` | the primary first, then any copy at least as new |
12 * | `first-unconstrained` | the nearest copy |
13 * | a bookmark | any copy at least as new as the bookmark |
14 *
15 * Writes always go to the primary. The session's latest bookmark comes
16 * back in the response's `x-d1-bookmark`. docs/PERFORMANCE.md says who
17 * sends what.
18 */
19
20/** The header that carries a session's constraint or bookmark, both ways. */
21export const BOOKMARK_HEADER = "x-d1-bookmark";
22
23const MAX_BOOKMARK = 256;
24
25/**
26 * What an `x-d1-bookmark` header asks for: null for no session, otherwise
27 * what `withSession` is given. Anything malformed starts on the primary.
28 */
29export function sessionConstraint(header: string | null | undefined): string | null {
30 const value = header?.trim();
31 if (!value) return null;
32 if (value === "first-primary" || value === "first-unconstrained") return value;
33 return value.length <= MAX_BOOKMARK && /^[0-9A-Za-z-]+$/.test(value) ? value : "first-primary";
34}
35
36/** A D1 binding, as far as sessions need it. */
37type SessionCapable = { withSession(constraintOrBookmark?: string): { getBookmark(): string | null } };
38
39/**
40 * The database for an RPC `request`: a session, seen as the binding, when
41 * the caller asked for one (it answers `prepare` and `batch`, all a request
42 * path uses), the binding itself otherwise. `finish` adds the bookmark and
43 * the time taken to the answer.
44 */
45export function openD1<D extends SessionCapable>(db: D, request: Request): { db: D; finish(response: Response): Response } {
46 const started = Date.now();
47 const asked = sessionConstraint(request.headers.get(BOOKMARK_HEADER));
48 const session = asked ? db.withSession(asked) : null;
49 return {
50 db: (session ?? db) as D,
51 finish(response) {
52 const answered = new Response(response.body, response);
53 answered.headers.append("server-timing", `svc;dur=${Date.now() - started};desc="${session ? "session" : "primary"}"`);
54 const bookmark = session?.getBookmark();
55 if (bookmark) answered.headers.set(BOOKMARK_HEADER, bookmark);
56 return answered;
57 },
58 };
59}