feat: cache purge API (object cache + native Workers Caching) - #2297
feat: cache purge API (object cache + native Workers Caching)#2297scottbuscemi wants to merge 9 commits into
Conversation
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 detectedLatest commit: 3f6795f The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
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 |
Scope checkThis 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. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 3f6795f | Jul 30 2026, 09:23 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 3f6795f | Jul 30 2026, 09:23 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | 3f6795f | Jul 30 2026, 09:24 PM |
There was a problem hiding this comment.
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:
- Orphaned JSDoc comment in
packages/core/src/plugins/context.ts: thecreateUserAccessdocstring is now separated from its function by the newly insertedcreateCacheAccess, making the comment misleading and leavingcreateUserAccessundocumented. - Absolute-URL scheme regex in
packages/core/src/api/handlers/workers-cache.tsexcludes digits (so schemes likes3://are mis-classified as relative paths) and includes a stray literal backslash. CacheAccesstype export gap: the type is defined and used byPluginContextbut not re-exported frompackages/core/src/plugins/index.tsor the mainemdashbarrel.- 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:955This JSDoc block documents
createUserAccess, butcreateCacheAccesswas inserted between the comment and the function it describes. The comment now precedes the wrong function, andcreateUserAccess(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:46This 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 tocache.purgeas 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:215CacheAccessis defined and exposed viaPluginContext.cache, but the type itself is not re-exported here or from the mainemdashbarrel. Plugin authors who want to type actx.cachereference (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
CacheAccessfrompackages/core/src/index.tsalongside the existing plugin-type exports. -
[suggestion]
packages/core/src/astro/integration/routes.ts:353This comment describes only the object-cache route, but the next
injectRouteregisters the Workers Cache purge route too. Update the comment to cover both.// Cache purge routes (object cache + native Workers Caching)
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.
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.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
What does this PR do?
Adds a first-party way for admins and sandboxed plugins to clear EmDash caches.
CMS object cache (KV / memory)
GET/POST/_emdash/api/admin/cache/object(settings:manage)ctx.cache.getObjectCacheStatus()/ctx.cache.purgeObjectCache()Workers Caching (native edge page cache)
GET/POST/_emdash/api/admin/cache/workers(settings:manage)cache.purgeis availablecache.purge({ purgeEverything: true })orcache.purge({ pathPrefixes })ctx.cache.getWorkersCacheStatus()/ctx.cache.purgeWorkersCache({ pathPrefixes? })cache.purgeviavirtual:emdash/workers-cache(no zone ID / API token)configured: truewhencache.purgeis a function (production Workers); local workerd may exposecachewithoutpurgeShared
cache:purgegates both in-process and through Cloudflare / workerd sandbox bridgesdisabledandtitle(tooltip)Re-opened after #2275 was reverted in #2281 so this can go through normal review.
Closes #
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
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):
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.