Skip to content

[miniflare] Add purgeCache() method to clear cache entries programmatically#12462

Merged
petebacondarwin merged 2 commits intomainfrom
fix/4387-miniflare-purge-cache
Feb 17, 2026
Merged

[miniflare] Add purgeCache() method to clear cache entries programmatically#12462
petebacondarwin merged 2 commits intomainfrom
fix/4387-miniflare-purge-cache

Conversation

@petebacondarwin
Copy link
Copy Markdown
Contributor

@petebacondarwin petebacondarwin commented Feb 7, 2026

Fixes #4387.

Add Miniflare#purgeCache() method that allows developers to clear cached data during local development without restarting the Miniflare instance. This is useful when working with Workers Sites or any application that uses the Cache API.

Changes

  • KeyValueStorage: Added deleteAll() method to delete all entries and garbage collect associated blobs
  • CacheObject: Added @DELETE("/purge-all") route handler
  • Miniflare: Added purgeCache(cacheName?: string) method that returns the number of purged entries
  • CoreBindings: Added SERVICE_CACHE constant for the cache service binding
  • ProxyServer: Added cache service binding to enable purgeCache() to route through the cache-entry worker

Implementation Details

The purgeCache() method routes requests through the cache-entry worker service instead of calling the CacheObject Durable Object directly. This is necessary because:

  1. Node.js's Request constructor silently drops the non-standard cf property
  2. The proxy system's fetcherFetchCall creates a new Request in Node.js before forwarding
  3. MiniflareDurableObject#fetch() requires cf.miniflare.name and will crash if it's undefined

By routing through the cache-entry worker (which runs inside workerd), the cf.miniflare.name property is properly set before forwarding to the CacheObject DO. This follows the same path that normal Cache API operations use.

Usage

const mf = new Miniflare({ /* options */ });

// Purge the default cache
const count = await mf.purgeCache();

// Purge a specific named cache
await mf.purgeCache("my-named-cache");

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 7, 2026

🦋 Changeset detected

Latest commit: 1f01714

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

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

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Feb 7, 2026

Claude finished @petebacondarwin's task —— View job


Changeset Review

Reviewing changeset file(s) for this PR...

Tasks:

  • Read changeset guidelines
  • Read changeset file
  • Validate changeset quality
  • Check for cute animals
  • Post findings

All changesets look good

The changeset .changeset/add-miniflare-purge-cache.md follows all guidelines:

  • ✅ Version type: minor is correct for a new feature/method
  • ✅ Changelog quality: Clear title and descriptive body explaining the use case
  • ✅ Code example: Includes helpful TypeScript usage example
  • ✅ No markdown header issues (no h1/h2/h3 headers present)
  • ✅ Package coverage: Correctly lists only miniflare
  • ✅ Format: Follows the recommended structure with title and body

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Feb 7, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@12462

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@12462

miniflare

npm i https://pkg.pr.new/miniflare@12462

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@12462

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@12462

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@12462

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@12462

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@12462

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@12462

wrangler

npm i https://pkg.pr.new/wrangler@12462

commit: 1f01714

@petebacondarwin petebacondarwin marked this pull request as ready for review February 7, 2026 15:49
@petebacondarwin petebacondarwin requested a review from a team as a code owner February 7, 2026 15:49
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 6 additional findings.

Open in Devin Review

@petebacondarwin petebacondarwin force-pushed the fix/4387-miniflare-purge-cache branch 2 times, most recently from fb74c4b to 27575b9 Compare February 9, 2026 12:40
devin-ai-integration[bot]

This comment was marked as resolved.

@petebacondarwin petebacondarwin marked this pull request as draft February 9, 2026 14:19
@petebacondarwin petebacondarwin force-pushed the fix/4387-miniflare-purge-cache branch 2 times, most recently from 1cbcf63 to eb933ad Compare February 16, 2026 09:37
…ammatically

Add Miniflare#purgeCache() method that allows developers to clear
cached data during local development without restarting the Miniflare
instance. This is useful when working with Workers Sites or any
application that uses the Cache API.

The method:
- Purges the default cache when called without arguments
- Purges a specific named cache when called with a cache name
- Returns the number of entries deleted

Implementation includes:
- KeyValueStorage#deleteAll() method to delete all entries
- CacheObject#purgeAll route handler for DELETE /purge-all
- Tests for default cache, named cache, and empty cache scenarios

Fixes #4387
…ve cf property

The previous implementation called the CacheObject Durable Object directly
from Node.js, but the cf property was silently dropped by Node.js's Request
constructor in the proxy system. This caused MiniflareDurableObject#fetch()
to crash when asserting cf.miniflare.name.

This fix routes purgeCache() requests through the cache-entry worker service
instead, which runs inside workerd and properly sets cf.miniflare.name before
forwarding to the CacheObject DO. This follows the same path that normal
Cache API operations use.

Changes:
- Add SERVICE_CACHE constant to CoreBindings
- Add cache service binding to ProxyServer's env
- Rewrite purgeCache() to use the cache service Fetcher
@petebacondarwin petebacondarwin force-pushed the fix/4387-miniflare-purge-cache branch from eb933ad to 1f01714 Compare February 17, 2026 09:48
@petebacondarwin petebacondarwin marked this pull request as ready for review February 17, 2026 10:27
@github-project-automation github-project-automation bot moved this from Untriaged to Approved in workers-sdk Feb 17, 2026
@petebacondarwin
Copy link
Copy Markdown
Contributor Author

Tested manually with a Worker:

// Simple Cloudflare Worker that uses the Cache API
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const cache = caches.default;

    // PUT an item in the cache
    if (url.pathname === "/cache-put") {
      const key = url.searchParams.get("key");
      const value = url.searchParams.get("value");

      if (!key || !value) {
        return new Response("Missing key or value query param", { status: 400 });
      }

      // Create a cache key URL and response to store
      const cacheKey = new Request(`http://cache/${key}`);
      const cacheResponse = new Response(value, {
        headers: { "Cache-Control": "max-age=3600" },
      });

      await cache.put(cacheKey, cacheResponse);
      return new Response(`Cached "${key}" = "${value}"`);
    }

    // GET an item from the cache
    if (url.pathname === "/cache-get") {
      const key = url.searchParams.get("key");

      if (!key) {
        return new Response("Missing key query param", { status: 400 });
      }

      const cacheKey = new Request(`http://cache/${key}`);
      const cachedResponse = await cache.match(cacheKey);

      if (!cachedResponse) {
        return new Response(`Key "${key}" not found in cache`, { status: 404 });
      }

      const value = await cachedResponse.text();
      return new Response(`Found "${key}" = "${value}"`);
    }

    // Default response
    return new Response("Hello from Miniflare Cache Test Worker!");
  },
};

and the following test code:

import { Miniflare } from "miniflare";

async function main() {
  console.log("Creating Miniflare instance...\n");

  const mf = new Miniflare({
    modules: true,
    scriptPath: "./src/index.js",
    cache: true, // Enable cache (this is the default)
  });

  try {
    // Test 1: Add items to the cache
    console.log("=== Step 1: Adding items to cache ===");

    const put1 = await mf.dispatchFetch(
      "http://localhost/cache-put?key=greeting&value=hello"
    );
    console.log("PUT greeting:", await put1.text());

    const put2 = await mf.dispatchFetch(
      "http://localhost/cache-put?key=name&value=world"
    );
    console.log("PUT name:", await put2.text());

    const put3 = await mf.dispatchFetch(
      "http://localhost/cache-put?key=number&value=42"
    );
    console.log("PUT number:", await put3.text());

    // Test 2: Verify items are in the cache
    console.log("\n=== Step 2: Verifying items are cached ===");

    const get1 = await mf.dispatchFetch(
      "http://localhost/cache-get?key=greeting"
    );
    console.log("GET greeting:", await get1.text(), `(status: ${get1.status})`);

    const get2 = await mf.dispatchFetch("http://localhost/cache-get?key=name");
    console.log("GET name:", await get2.text(), `(status: ${get2.status})`);

    const get3 = await mf.dispatchFetch(
      "http://localhost/cache-get?key=number"
    );
    console.log("GET number:", await get3.text(), `(status: ${get3.status})`);

    // Test 3: Purge the cache
    console.log("\n=== Step 3: Calling mf.purgeCache() ===");

    const purgedCount = await mf.purgeCache();
    console.log(`purgeCache() returned: ${purgedCount} entries purged`);

    // Test 4: Verify cache is now empty
    console.log("\n=== Step 4: Verifying cache is empty after purge ===");

    const getAfter1 = await mf.dispatchFetch(
      "http://localhost/cache-get?key=greeting"
    );
    console.log(
      "GET greeting after purge:",
      await getAfter1.text(),
      `(status: ${getAfter1.status})`
    );

    const getAfter2 = await mf.dispatchFetch(
      "http://localhost/cache-get?key=name"
    );
    console.log(
      "GET name after purge:",
      await getAfter2.text(),
      `(status: ${getAfter2.status})`
    );

    const getAfter3 = await mf.dispatchFetch(
      "http://localhost/cache-get?key=number"
    );
    console.log(
      "GET number after purge:",
      await getAfter3.text(),
      `(status: ${getAfter3.status})`
    );

    // Summary
    console.log("\n=== Summary ===");
    if (
      getAfter1.status === 404 &&
      getAfter2.status === 404 &&
      getAfter3.status === 404
    ) {
      console.log("SUCCESS: All cache entries were purged!");
    } else {
      console.log("FAILURE: Some cache entries still exist after purge");
    }
  } finally {
    // Clean up
    console.log("\nDisposing Miniflare instance...");
    await mf.dispose();
  }
}

main().catch((err) => {
  console.error("Test failed:", err);
  process.exit(1);
});

Resulting in...

> node test.js

Creating Miniflare instance...

=== Step 1: Adding items to cache ===
PUT greeting: Cached "greeting" = "hello"
PUT name: Cached "name" = "world"
PUT number: Cached "number" = "42"

=== Step 2: Verifying items are cached ===
GET greeting: Found "greeting" = "hello" (status: 200)
GET name: Found "name" = "world" (status: 200)
GET number: Found "number" = "42" (status: 200)

=== Step 3: Calling mf.purgeCache() ===
purgeCache() returned: 3 entries purged

=== Step 4: Verifying cache is empty after purge ===
GET greeting after purge: Key "greeting" not found in cache (status: 404)
GET name after purge: Key "name" not found in cache (status: 404)
GET number after purge: Key "number" not found in cache (status: 404)

=== Summary ===
SUCCESS: All cache entries were purged!

Disposing Miniflare instance...

@petebacondarwin petebacondarwin merged commit f239077 into main Feb 17, 2026
39 of 40 checks passed
@petebacondarwin petebacondarwin deleted the fix/4387-miniflare-purge-cache branch February 17, 2026 13:42
@github-project-automation github-project-automation bot moved this from Approved to Done in workers-sdk Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Miniflare] Add Miniflare#purgeCache() method to reset cache when required

3 participants