Skip to content

[wrangler] Support volatile cache bindings in local dev - #14868

Draft
irvinebroque wants to merge 1 commit into
mainfrom
bib/volatile-cache-local-dev
Draft

[wrangler] Support volatile cache bindings in local dev#14868
irvinebroque wants to merge 1 commit into
mainfrom
bib/volatile-cache-local-dev

Conversation

@irvinebroque

@irvinebroque irvinebroque commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Important

This draft PR is not intended to be merged. It exists so CI can produce a PR prerelease of Wrangler that can configure MemoryCache in local workerd development.

Why this exists

workerd already has an experimental in-memory MemoryCache binding. Wrangler can carry the internal volatile_cache binding shape, but local development did not translate that configuration into workerd's memoryCache binding.

In plain language: the configuration described the cache, but wrangler dev did not connect that description to the local runtime. This PR adds the missing local-runtime connection through Wrangler and Miniflare. Production deployment behavior is unchanged.

Before and after

Before this change, an unsafe.bindings entry with type: "volatile_cache" was not available as a working MemoryCache binding in local development.

After this change, Wrangler passes the cache ID and limits to Miniflare, and Miniflare writes the corresponding memoryCache binding into workerd's configuration.

How to use the PR prerelease

Add the binding to wrangler.jsonc:

{
  "unsafe": {
    "bindings": [
      {
        "name": "AIG_VOLATILE_CACHE",
        "type": "volatile_cache",
        "cache_id": "ai-gateway-worker-staging",
        "max_keys": 10000,
        "max_value_size": 16384,
        "max_total_value_size": 33554432
      }
    ]
  }
}

The binding is then available on the Worker environment in local development:

export default {
  async fetch(_request, env) {
    const value = await env.AIG_VOLATILE_CACHE.read("example", async () => ({
      value: "computed value",
      expiration: Date.now() + 60_000,
    }));

    return new Response(value);
  },
};

read() returns an unexpired cached value when one exists. On a cache miss, it runs the callback; eligible values can then be retained subject to expiration and the configured limits.

The cache belongs to the local workerd process. It is not durable storage and should not be treated like KV, D1, or another persistent product.

Authoritative references:

Scope

  • Local runtime support through wrangler dev and Miniflare only.
  • Maps cache_id, max_keys, max_value_size, and max_total_value_size to workerd's MemoryCache configuration.
  • Does not add or change production deployment support.
  • Uses the existing experimental unsafe.bindings configuration surface.

@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bd9b599

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

This PR includes changesets to release 8 packages
Name Type
miniflare Minor
wrangler Minor
@cloudflare/deploy-helpers Patch
@cloudflare/pages-shared Patch
@cloudflare/remote-bindings Patch
@cloudflare/runtime-types Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers 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

✅ All changesets look good

@ask-bonk

ask-bonk Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

validateUnsafeBinding only requires name and type and preserves the rest (it doesn't strip unknown fields). So cache_id, max_keys, etc. flow through. The config validation is permissive here, which is fine for the unsafe surface.

I've now thoroughly reviewed the change. Let me summarize my findings:

Correctness verification:

  1. volatile_cache in unsafe.bindings → prefixed to unsafe_volatile_cache by binding-utils.ts:252
  2. extractBindingsOfType("unsafe_volatile_cache", ...) adds binding key (via getBindingKey) ✓
  3. Type Extract<Binding, {type: "unsafe_volatile_cache"}> resolves only to the explicit member ✓
  4. maxTotalValueSize: bigint matches capnp setUint64 requirement ✓ (was previously a latent bug — number would throw)
  5. JSON debug serializer bigint handling is correct/necessary ✓
  6. BINDING_LOCAL_SUPPORT Record type still excludes the new type via Exclude<..., \unsafe_${string}`>` ✓

Let me check one last potential real issue: the miniflare zod schema requires maxKeys, maxValueSize, maxTotalValueSize (non-optional), but the wrangler Binding type makes max_keys etc. required too. However, validateUnsafeBinding does NOT enforce these fields exist. If a user omits max_keys, wrangler passes maxKeys: undefined to miniflare, and the zod schema (z.number().int()..., non-optional) would throw. That's arguably acceptable validation behavior (fail with a schema error). Let me check whether workerd requires these limits or if they should be optional.

The workerd sample shows limits are part of the config. Since these are required in both the wrangler Binding type and the miniflare schema, this is internally consistent. A user omitting them gets a zod validation error rather than a graceful message, but that's a minor UX concern for an experimental unsafe binding, not a logic bug.

Let me double-check the id optionality concern — if cache_id is required in the wrangler type but workerd's id is optional, and multiple caches share the same id, they'd share storage. That's expected workerd behavior. No issue.

I've completed a thorough review. The implementation is correct: the data flow, type design, and the important bigint fix for maxTotalValueSize (which addresses a latent capnp serialization bug) are all sound. The tests cover both the miniflare and wrangler layers. I found no logic bugs, security issues, or backward-compatibility violations.

LGTM

github run

@pkg-pr-new

pkg-pr-new Bot commented Jul 26, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14868

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@14868

create-cloudflare

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

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14868

@cloudflare/kv-asset-handler

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

miniflare

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

@cloudflare/pages-shared

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

@cloudflare/unenv-preset

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

@cloudflare/vite-plugin

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

@cloudflare/vitest-pool-workers

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

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14868

@cloudflare/workers-editor-shared

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

@cloudflare/workers-utils

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

wrangler

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

commit: bd9b599

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

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants