queues
Cloudflare Queues from one plugin: send from a request, consume with a handler declared in config.
What it does
queues gives a Worker both sides of a queue. send and sendBatch produce messages. consume is
what your queue export calls to deliver a batch to the instance's onMessage.
How it works
The consumer is config: each instance can declare onMessage. consume picks the instance in this
order:
- One instance. It always matches.
- By name. Its
nameequalsbatch.queue, orbatch.queuestarts withname-. That is the stage suffix:orders-devfindsorders. - The default. With no match, the default instance gets the batch. With no default,
consumethrows.
Then it walks the messages one at a time: it awaits onMessage(message, env), then emits
queue:message. An instance with no onMessage takes the batch and does nothing.
Config
pluginConfigs.queues, keyed by your logical name. The default is {}.
| Option | Type | Default | Description |
|---|---|---|---|
[key].name | string | required | The Cloudflare queue. A stage other than production suffixes it at deploy. |
[key].binding | string | required | The producer key on env. |
[key].onMessage | (message: Message, env: WorkerEnv) => Promise<void> | none | The consumer. With it, deploy registers this Worker as a consumer of the queue. |
[key].maxBatchTimeout | number | Cloudflare's | Seconds to wait to fill a batch, 0 to 60. Written as max_batch_timeout, so it needs onMessage. |
[key].default | boolean | none | Marks the default when there are several. |
API
Mounted at app.queues, reachable with require(queuesPlugin) in a handler.
| Method | Signature | Description |
|---|---|---|
send | (env, body: unknown) => Promise<void> | One message. |
sendBatch | (env, bodies: unknown[]) => Promise<void> | One message per element. |
use | (key: string) => QueueProducerApi | send and sendBatch on another queue. |
consume | (batch: MessageBatch, env, ctx: ExecutionContext) => Promise<void> | The delivery. Resolves after every message settled. |
deployManifest | () => { kind: "queue"; name: string; binding: string; consumer?: boolean; maxBatchTimeout?: number }[] | Build-time metadata. consumer is true with onMessage. |
Events
Emits
| Event | Payload | When |
|---|---|---|
queue:message | { queue: string; messageId: string } | After onMessage settled for a message. |
A hook on it needs depends: [queuesPlugin].
Listens to
None.
Depends on
- bindings: resolves the producer on every send.
Example
import type { WorkerEnv } from "@moku-labs/worker";
import { createApp, endpoint, kvPlugin, queuesPlugin } from "@moku-labs/worker";
type Order = { id: string };
export const app = createApp({
plugins: [queuesPlugin, kvPlugin],
pluginConfigs: {
kv: { orders: { name: "orders-state", binding: "ORDERS_STATE" } },
queues: {
orders: {
name: "orders",
binding: "ORDERS",
maxBatchTimeout: 5,
onMessage: async (message: { body: unknown }, env: WorkerEnv) => {
const order = message.body as Order;
await app.kv.put(env, order.id, "received");
}
}
},
server: {
endpoints: [
endpoint("/orders").post(async ({ request, env, require }) => {
await require(queuesPlugin).send(env, await request.json());
return new Response(null, { status: 202 });
})
]
}
}
});- A throw here rejects
consume.
import { app } from "./app";
export default {
fetch: (request: Request, env: Record<string, unknown>, ctx: ExecutionContext) =>
app.server.handle(request, env, ctx),
queue: (batch: MessageBatch, env: Record<string, unknown>, ctx: ExecutionContext) =>
app.queues.consume(batch, env, ctx)
} satisfies ExportedHandler;- Wire the
queueexport.onMessageruns only throughconsume. Thedeployplugin registers the consumer, your entry has to deliver. - A throw stops the batch.
consumerejects at the first failing message, and the rest of the loop does not run. Cloudflare then applies its retry policy to what was not acknowledged. Callmessage.ack()for finished work if a partial retry matters. - Messages run one by one. A slow
onMessageholds the batch. sendtakes the body only. There is nodelaySecondsorcontentType. Take the raw queue withapp.bindings.requirefor those.- A name that prefixes another. With
ordersandorders-euconfigured, the first declared match wins fororders-eu. Use names where neither is a prefix of the other.
Related
- deploy plugin: producers and consumers in the wrangler config.
- server plugin: the
fetchhalf of the entry.