From d8a53c0727a200fc25c20be4ec33e5ed3ea17aa1 Mon Sep 17 00:00:00 2001 From: scottbuscemi Date: Wed, 29 Jul 2026 17:01:56 -0500 Subject: [PATCH 1/2] fix: prefer uncached Hyperdrive after content writes After a content publish, anonymous public reads briefly use the primary Hyperdrive binding so edge/object caches are not reseeded from stale query-cache results (default 60s when cachedBinding is set). --- .../prefer-uncached-hyperdrive-after-write.md | 6 + docs/src/content/docs/deployment/database.mdx | 17 +- packages/cloudflare/src/db/hyperdrive.ts | 39 ++++- packages/cloudflare/src/index.ts | 40 +++-- .../tests/db/hyperdrive-routing.test.ts | 89 +++++++++++ .../tests/hyperdrive-config.test.ts | 19 +++ packages/core/src/astro/middleware.ts | 7 + packages/core/src/index.ts | 1 + packages/core/src/object-cache/index.ts | 150 +++++++++++++++++- packages/core/src/virtual-modules.d.ts | 6 + packages/core/tests/unit/object-cache.test.ts | 69 ++++++++ 11 files changed, 424 insertions(+), 19 deletions(-) create mode 100644 .changeset/prefer-uncached-hyperdrive-after-write.md diff --git a/.changeset/prefer-uncached-hyperdrive-after-write.md b/.changeset/prefer-uncached-hyperdrive-after-write.md new file mode 100644 index 0000000000..84e47ed6e4 --- /dev/null +++ b/.changeset/prefer-uncached-hyperdrive-after-write.md @@ -0,0 +1,6 @@ +--- +"emdash": patch +"@emdash-cms/cloudflare": patch +--- + +Fixes anonymous public pages reseeding edge/object caches with stale Hyperdrive query results right after content publishes. When `cachedBinding` is set, public reads prefer the uncached Hyperdrive binding for a short window after content writes (default 60s, overridable via `preferUncachedAfterWriteMs` to match your Hyperdrive max_age). diff --git a/docs/src/content/docs/deployment/database.mdx b/docs/src/content/docs/deployment/database.mdx index 0c6ff69052..eca8e89341 100644 --- a/docs/src/content/docs/deployment/database.mdx +++ b/docs/src/content/docs/deployment/database.mdx @@ -307,11 +307,14 @@ wrangler hyperdrive create emdash-db \ ### Configuration -| Option | Type | Default | Description | -| --------------- | -------- | ------------- | -------------------------------------------------------------------- | -| `binding` | `string` | `"HYPERDRIVE"` | Primary (caching-disabled) Hyperdrive binding name | -| `cachedBinding` | `string` | — | Optional caching-enabled binding for anonymous reads (see below) | -| `max` | `number` | `5` | Max size of the in-Worker connection pool to Hyperdrive | +| Option | Type | Default | Description | +| ------------------------------ | -------- | -------------- | ---------------------------------------------------------------------------------------------------------------- | +| `binding` | `string` | `"HYPERDRIVE"` | Primary (caching-disabled) Hyperdrive binding name | +| `cachedBinding` | `string` | — | Optional caching-enabled binding for anonymous reads (see below) | +| `preferUncachedAfterWriteMs` | `number` | `60000`\* | After a content publish, prefer `binding` for this many ms on anonymous public reads (match Hyperdrive `max_age`) | +| `max` | `number` | `5` | Max size of the in-Worker connection pool to Hyperdrive | + +\*Default `60000` applies only when `cachedBinding` is set; ignored otherwise. ### Serving anonymous reads from cache @@ -343,7 +346,7 @@ database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" This is the [two-configuration pattern](https://developers.cloudflare.com/hyperdrive/configuration/query-caching/#disable-caching) Cloudflare documents for caching. EmDash decides which binding to use per request: -- **Anonymous reads of public-site paths** (`GET`/`HEAD`, no session, not under `/_emdash`) → cache-enabled `cachedBinding`. +- **Anonymous reads of public-site paths** (`GET`/`HEAD`, no session, not under `/_emdash`) → cache-enabled `cachedBinding`, **except** for a short window after a content publish (default 60s; set `preferUncachedAfterWriteMs` to your Hyperdrive `max_age`) when EmDash prefers the uncached `binding` so a rebuild cannot reseed edge/object caches from still-stale Hyperdrive results. - **Authenticated requests** (editors, authors) → uncached `binding`. - **Writes** (`POST`, `PUT`, `DELETE`, including anonymous ones) → uncached `binding`. - **Any request under `/_emdash`** (admin, setup, auth, internal APIs), even an anonymous `GET` → uncached `binding`. @@ -357,7 +360,7 @@ This is the [two-configuration pattern](https://developers.cloudflare.com/hyperd