Cloudflare Runtime Mapping
Recipe-only code sketch for mapping Cloudflare Queues, Durable Objects, D1, and alarms to Runtime Engine concepts.
Cloudflare is a recipe in v1, not a bundled first-party Crux runtime adapter. The examples below show the shape an adapter author can build: Crux still owns target identity, payload JSON, idempotency, and wake handling; Cloudflare provides queue delivery and storage primitives.
Runtime config sketch
Use genericQueue() when your Cloudflare Queue worker forwards wake messages to a deployed Crux runtime endpoint. The store below is a placeholder for a future Cloudflare/D1 or Durable Object store adapter.
import { config } from "@use-crux/core";
import { genericQueue, serverless } from "@use-crux/core/runtime";
import { cloudflareStore } from "./cloudflare-runtime-store";
export default config({
runtime: serverless({
store: cloudflareStore({
d1Binding: "CRUX_RUNTIME_DB",
namespace: process.env.CRUX_RUNTIME_NAMESPACE,
}),
wake: genericQueue({
secret: process.env.CRUX_RUNTIME_WAKE_SECRET!,
maxDelayMs: 15 * 60 * 1000,
enqueue: async (message) => {
await env.CRUX_WAKE_QUEUE.send(message, {
delaySeconds:
Math.min(message.envelope.notBeforeDelayMs ?? 0, 900) / 1000,
});
},
}),
publicUrl: process.env.CRUX_PUBLIC_URL,
}),
});Queue consumer
The worker receives Crux wake messages from Cloudflare Queues and forwards the signed body and headers exactly as produced by genericQueue().
Production namespace
Cloudflare is not a Vercel host, so serverless() cannot infer a production namespace. Set CRUX_RUNTIME_NAMESPACE (or pass namespace directly) for every production deployment. Otherwise composition throws NAMESPACE_AMBIGUOUS instead of falling back to local.
export default {
async queue(batch: MessageBatch<RuntimeWakeMessage>, env: Env) {
for (const message of batch.messages) {
const response = await fetch(message.body.url, {
method: "POST",
headers: message.body.headers,
body: message.body.body,
});
if (response.status >= 500 || response.status === 409) {
message.retry();
continue;
}
message.ack();
}
},
};
interface RuntimeWakeMessage {
id: string;
url: string;
body: string;
headers: Record<string, string>;
envelope: {
idempotencyKey: string;
notBeforeDelayMs?: number;
};
}Store boundary sketch
A real adapter would implement RuntimeStoreAdapter against D1 tables or Durable Object state, then run the shared conformance suite.
import type { RuntimeStoreAdapter } from "@use-crux/core/runtime";
export function cloudflareStore(options: {
d1Binding: string;
namespace?: string;
}): RuntimeStoreAdapter {
return {
id: "cloudflare-d1",
capabilities: {
transactions: "serializable",
},
async transact(fn) {
// Use D1 transactions or Durable Object serialization here.
// The callback receives state/events/waiters/timers/outbox/leases ports.
return d1Transaction(options.d1Binding, fn);
},
setup: {
check: async () => checkD1RuntimeSchema(options.d1Binding),
apply: async () => applyD1RuntimeSchema(options.d1Binding),
},
};
}Mapping notes
| Runtime concern | Cloudflare substrate |
|---|---|
| Wake/task delivery | Cloudflare Queues |
| State/events/leases | Durable Objects or D1 |
| Timers | Durable Object alarms, Queue delays, or store-backed scans |
| Live updates | Durable Object WebSockets or SSE |
Queue delivery is at least once. Keep Crux idempotency keys as the durable identity and acknowledge only after the runtime endpoint accepts the wake.
Related
- Guide: Runtime Adapter Authoring
- Recipe: Generic Queue and BullMQ