Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/volatile-cache-local-dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"miniflare": minor
"wrangler": minor
---

Support experimental Volatile Cache bindings in local development

`wrangler dev` now maps `volatile_cache` entries in `unsafe.bindings` to workerd's in-memory `MemoryCache` implementation, including the configured cache ID and size limits. Production deployment behavior is unchanged.
34 changes: 34 additions & 0 deletions packages/miniflare/src/plugins/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,20 @@ const CoreOptionsSchemaInput = z.intersection(
unsafeOverrideFetchWorker: z.string().optional(),

unsafeEvalBinding: z.string().optional(),
unsafeMemoryCaches: z
.record(
z.object({
id: z.string().optional(),
maxKeys: z.number().int().nonnegative().max(0xffffffff),
maxValueSize: z.number().int().nonnegative().max(0xffffffff),
maxTotalValueSize: z
.number()
.int()
.nonnegative()
.max(Number.MAX_SAFE_INTEGER),
})
)
.optional(),
unsafeUseModuleFallbackService: z.boolean().optional(),

/** Used to set the vitest pool worker SELF binding to point to the Router Worker if there are assets.
Expand Down Expand Up @@ -726,6 +740,26 @@ export const CORE_PLUGIN: Plugin<
unsafeEval: kVoid,
});
}
if (options.unsafeMemoryCaches !== undefined) {
bindings.push(
...Object.entries(options.unsafeMemoryCaches).map(
([
name,
{ id, maxKeys, maxValueSize, maxTotalValueSize },
]): Worker_Binding => ({
name,
memoryCache: {
id,
limits: {
maxKeys,
maxValueSize,
maxTotalValueSize: BigInt(maxTotalValueSize),
},
},
})
)
);
}

return Promise.all(bindings);
},
Expand Down
9 changes: 8 additions & 1 deletion packages/miniflare/src/runtime/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ function encodeCapnpStruct(obj: any, struct: Struct) {
export function serializeConfig(config: Config): Buffer {
const debugPath = process.env.MINIFLARE_WORKERD_CONFIG_DEBUG;
if (debugPath) {
writeFileSync(debugPath, JSON.stringify(config, null, 2));
writeFileSync(
debugPath,
JSON.stringify(
config,
(_key, value) => (typeof value === "bigint" ? value.toString() : value),
2
)
);
}
const message = new Message();
const struct = message.initRoot(CapnpConfig);
Expand Down
3 changes: 2 additions & 1 deletion packages/miniflare/src/runtime/config/workerd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export type Worker_Binding = {
| { analyticsEngine?: ServiceDesignator }
| { hyperdrive?: Worker_Binding_Hyperdrive }
| { unsafeEval?: Void }
| { memoryCache?: Worker_Binding_MemoryCache }
| { workerLoader?: Worker_Binding_WorkerLoader }
| { workerdDebugPort?: Void }
);
Expand Down Expand Up @@ -188,7 +189,7 @@ export interface Worker_Binding_MemoryCache {
export interface Worker_Binding_MemoryCacheLimits {
maxKeys?: number;
maxValueSize?: number;
maxTotalValueSize?: number;
maxTotalValueSize?: bigint;
}

export type Worker_DurableObjectNamespace = {
Expand Down
35 changes: 35 additions & 0 deletions packages/miniflare/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3205,6 +3205,41 @@ test("Miniflare: supports unsafe eval bindings", async ({ expect }) => {
expect(await response.text()).toBe("the computed value is 3");
});

test("Miniflare: supports memory cache bindings", async ({ expect }) => {
const mf = new Miniflare({
modules: true,
script: `export default {
async fetch(req, env) {
const first = await env.CACHE.read("key", async () => ({
value: "first",
expiration: Date.now() + 60_000,
}));
const second = await env.CACHE.read("key", async () => ({
value: "second",
expiration: Date.now() + 60_000,
}));
return Response.json({ first, second });
}
}`,
unsafeMemoryCaches: {
CACHE: {
id: "test-cache",
maxKeys: 10,
maxValueSize: 1024,
maxTotalValueSize: 1024,
},
},
});
useDispose(mf);

const response = await mf.dispatchFetch("http://localhost");
expect(response.ok).toBe(true);
expect(await response.json()).toEqual({
first: "first",
second: "first",
});
});

test("Miniflare: supports wrapped bindings", async ({ expect }) => {
const store = new Map<string, string>();
const mf = new Miniflare({
Expand Down
7 changes: 7 additions & 0 deletions packages/workers-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,13 @@ export type Binding =
| ({ type: "vpc_service" } & BindingOmit<CfVpcService>)
| ({ type: "vpc_network" } & BindingOmit<CfVpcNetwork>)
| ({ type: "media" } & BindingOmit<CfMediaBinding>)
| {
type: "unsafe_volatile_cache";
cache_id: string;
max_keys: number;
max_value_size: number;
max_total_value_size: number;
}
| ({ type: `unsafe_${string}` } & Omit<CfUnsafeBinding, "name" | "type">)
| { type: "assets" }
| { type: "inherit" };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,41 @@ describe("unstable_getMiniflareWorkerOptions", () => {
).toBeUndefined();
});
});

it("configures unsafe volatile cache bindings for local development", ({
expect,
}) => {
writeWranglerConfig(
{
name: "test-worker",
main: "./index.js",
compatibility_date: "2024-10-04",
unsafe: {
bindings: [
{
name: "AIG_VOLATILE_CACHE",
type: "volatile_cache",
cache_id: "ai-gateway-worker-staging",
max_keys: 10_000,
max_value_size: 16_384,
max_total_value_size: 33_554_432,
},
],
},
},
"./wrangler.json"
);

const { workerOptions } =
unstable_getMiniflareWorkerOptions("./wrangler.json");

expect(workerOptions.unsafeMemoryCaches).toEqual({
AIG_VOLATILE_CACHE: {
id: "ai-gateway-worker-staging",
maxKeys: 10_000,
maxValueSize: 16_384,
maxTotalValueSize: 33_554_432,
},
});
});
});
24 changes: 24 additions & 0 deletions packages/wrangler/src/dev/miniflare/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ type WorkerOptionsBindings = Pick<
| "flagship"
| "artifacts"
| "workerLoaders"
| "unsafeMemoryCaches"
| "unsafeBindings"
| "additionalUnboundDurableObjects"
| "media"
Expand Down Expand Up @@ -561,6 +562,10 @@ export function buildMiniflareBindingOptions(
const flagshipBindings = extractBindingsOfType("flagship", bindings);
const artifactsBindings = extractBindingsOfType("artifacts", bindings);
const workerLoaders = extractBindingsOfType("worker_loader", bindings);
const volatileCaches = extractBindingsOfType(
"unsafe_volatile_cache",
bindings
);
const sendEmailBindings = extractBindingsOfType("send_email", bindings);
// Extract both regular and unsafe ratelimit bindings
// Unsafe bindings have type "unsafe_ratelimit" (prefixed with "unsafe_")
Expand Down Expand Up @@ -811,6 +816,25 @@ export function buildMiniflareBindingOptions(
dataBlobBindings,
wasmBindings,
unsafeBindings,
unsafeMemoryCaches: Object.fromEntries(
volatileCaches.map(
({
binding,
cache_id,
max_keys,
max_value_size,
max_total_value_size,
}) => [
binding,
{
id: cache_id,
maxKeys: max_keys,
maxValueSize: max_value_size,
maxTotalValueSize: max_total_value_size,
},
]
)
),

ai:
aiBindings.length > 0
Expand Down
Loading