Skip to content

feat: cache purge API (object cache + native Workers Caching) - #2297

Open
scottbuscemi wants to merge 9 commits into
mainfrom
feat/admin-cache-purge
Open

feat: cache purge API (object cache + native Workers Caching)#2297
scottbuscemi wants to merge 9 commits into
mainfrom
feat/admin-cache-purge

Conversation

@scottbuscemi

@scottbuscemi scottbuscemi commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds a first-party way for admins and sandboxed plugins to clear EmDash caches.

CMS object cache (KV / memory)

  • Admin API: GET / POST /_emdash/api/admin/cache/object (settings:manage)
    • GET reports whether an object-cache backend is configured
    • POST bumps epochs for fixed namespaces + content collections (or a selected subset)
  • Plugin: ctx.cache.getObjectCacheStatus() / ctx.cache.purgeObjectCache()

Workers Caching (native edge page cache)

  • Admin API: GET / POST /_emdash/api/admin/cache/workers (settings:manage)
    • GET reports whether native cache.purge is available
    • POST calls cache.purge({ purgeEverything: true }) or cache.purge({ pathPrefixes })
  • Plugin: ctx.cache.getWorkersCacheStatus() / ctx.cache.purgeWorkersCache({ pathPrefixes? })
  • Uses platform cache.purge via virtual:emdash/workers-cache (no zone ID / API token)
  • Status configured: true when cache.purge is a function (production Workers); local workerd may expose cache without purge

Shared

  • Capability cache:purge gates both in-process and through Cloudflare / workerd sandbox bridges
  • Block Kit buttons support optional disabled and title (tooltip)

Re-opened after #2275 was reverted in #2281 so this can go through normal review.

Closes #

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: n/a (EmDash maintainer)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: opencode + xAI grok-4.5

Screenshots / test output

Targeted tests: object-cache + workers-cache handlers/routes, virtual module generator, marketplace CAPABILITY_LABELS, blocks disabled+title.

Preferred site setup (see also #2277):

// wrangler.jsonc
{ "cache": { "enabled": true } }
// astro.config.mjs
import { cacheCloudflare } from "@astrojs/cloudflare/cache";
cache: { provider: cacheCloudflare() },

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks feat/admin-cache-purge. Updated automatically when the playground redeploys.

scottbuscemi and others added 7 commits July 30, 2026 16:06
Admins and sandboxed plugins can clear CMS object-cache namespaces
(KV/memory) via GET/POST /_emdash/api/admin/cache/object and
ctx.cache. Block Kit buttons gain optional disabled and title fields
for clearer troubleshooting UI.
Admins and plugins with cache:purge can clear edge-cached pages via
GET/POST /_emdash/api/admin/cache/workers and ctx.cache.purgeWorkersCache()
(Cloudflare purge_everything using CF_ZONE_ID + CF_CACHE_PURGE_TOKEN).
Replace zone REST purge (CF_ZONE_ID + token) with cloudflare:workers
cache.purge({ purgeEverything: true }). Status is configured when the
native API is available — no secrets required.
Dynamic import of cloudflare:workers from core failed under Vite.
Expose cache through virtual:emdash/workers-cache (same pattern as env
and waitUntil) so status/purge work on the Cloudflare adapter.
POST /admin/cache/workers and ctx.cache.purgeWorkersCache() accept
optional pathPrefixes (paths or full URLs, normalized). Empty input
still purges everything via cache.purge.
Move URL regex to module scope, drop redundant unknown union, rename
shadowed Tooltip render prop, and include cache:purge in
CAPABILITY_LABELS contract test.
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3f6795f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Minor
@emdash-cms/plugin-types Minor
@emdash-cms/plugin-cli Patch
@emdash-cms/blocks Minor
@emdash-cms/plugin-mcp-smoke Major
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Minor
@emdash-cms/registry-verification Patch
@emdash-cms/plugin-embeds Patch
@emdash-cms/auth Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 1,452 lines across 37 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jul 30, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2297

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2297

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2297

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2297

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2297

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2297

emdash

npm i https://pkg.pr.new/emdash@2297

create-emdash

npm i https://pkg.pr.new/create-emdash@2297

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2297

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2297

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2297

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2297

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2297

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2297

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2297

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2297

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2297

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2297

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2297

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2297

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2297

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2297

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2297

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2297

commit: 3f6795f

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 3f6795f Jul 30 2026, 09:23 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 3f6795f Jul 30 2026, 09:23 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 30, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 3f6795f Jul 30 2026, 09:24 PM

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a focused, well-scoped cache-purge capability that fits EmDash’s architecture: admin routes return ApiResult and use existing RBAC (settings:manage), the Workers Cache purge is isolated behind the same virtual:emdash/* adapter pattern used for waitUntil/env, and plugin access is gated by a new cache:purge capability propagated through all three plugin runtimes (in-process, Cloudflare sandbox, workerd sandbox). The Block Kit button disabled/title changes are small, additive, and use Kumo components.

I read the new handlers, routes, plugin context/wrappers, virtual-module wiring, type declarations, and tests. The implementation is largely clean and the tests cover the handler behavior and route authorization. There are a few items to address before merge:

  1. Orphaned JSDoc comment in packages/core/src/plugins/context.ts: the createUserAccess docstring is now separated from its function by the newly inserted createCacheAccess, making the comment misleading and leaving createUserAccess undocumented.
  2. Absolute-URL scheme regex in packages/core/src/api/handlers/workers-cache.ts excludes digits (so schemes like s3:// are mis-classified as relative paths) and includes a stray literal backslash.
  3. CacheAccess type export gap: the type is defined and used by PluginContext but not re-exported from packages/core/src/plugins/index.ts or the main emdash barrel.
  4. Minor comment accuracy in packages/core/src/astro/integration/routes.ts: the comment says "Object-cache purge" but the block also registers the Workers Cache purge route.

None of these are security or data-loss issues, but #1 and #2 are real correctness/convention items that should be fixed.


Findings

  • [needs fixing] packages/core/src/plugins/context.ts:955

    This JSDoc block documents createUserAccess, but createCacheAccess was inserted between the comment and the function it describes. The comment now precedes the wrong function, and createUserAccess (at line 998) is left with no docstring.

    Move the user-access JSDoc down so it sits immediately before export function createUserAccess.

    	/**
    	 * Create cache purge access for plugins with `cache:purge`.
    	 */
    	export function createCacheAccess(db: Kysely<Database>): CacheAccess {
    
  • [needs fixing] packages/core/src/api/handlers/workers-cache.ts:46

    This regex is meant to detect an absolute URL scheme, but the character class +\\-. excludes digits and includes a literal backslash. Valid schemes containing digits (e.g. s3://bucket/object) are therefore treated as relative paths and normalized to /s3:/bucket/object, which is then passed to cache.purge as a path prefix.

    Use an RFC 3986 scheme class that allows digits and removes the backslash:

    const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//;
    
  • [suggestion] packages/core/src/plugins/index.ts:215

    CacheAccess is defined and exposed via PluginContext.cache, but the type itself is not re-exported here or from the main emdash barrel. Plugin authors who want to type a ctx.cache reference (e.g. async function purge(cache: CacheAccess)) currently cannot import it directly.

    export type { CacheAccess, CurrentPluginCapability, DeprecatedPluginCapability } from "./types.js";
    

    For parity, consider also exporting CacheAccess from packages/core/src/index.ts alongside the existing plugin-type exports.

  • [suggestion] packages/core/src/astro/integration/routes.ts:353

    This comment describes only the object-cache route, but the next injectRoute registers the Workers Cache purge route too. Update the comment to cover both.

    	// Cache purge routes (object cache + native Workers Caching)
    

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 30, 2026
Add cache.purge to declaredAccessSchema in core and plugin-types so
reconcileManifestAccess does not strip cache:purge. Restore
createUserAccess JSDoc. Reject protocol-relative URLs in path normalize.
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 30, 2026
RFC 3986 absolute-URL scheme class (digits allowed), export CacheAccess
from the emdash barrel, and clarify cache purge route comment. JSDoc
orphan was already fixed.
@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant