0

Publish message

by
Published today

Publish one message to an AMQP 0-9-1 exchange (RabbitMQ) and wait for the broker's confirm. Leave exchange empty to send straight to the queue named by routing_key. A string payload is sent as-is, anything else as JSON. routed is false when no queue received it.

Script amqp Verified

The script

Submitted by hugo989 Bun
Verified 8 hours ago
1
import amqp from "amqplib"
2

3
/**
4
 * Publish message
5
 * Publish one message to an AMQP 0-9-1 exchange (RabbitMQ) and wait for the broker's confirm. Leave exchange empty to send straight to the queue named by routing_key. A string payload is sent as-is, anything else as JSON. routed is false when no queue received it.
6
 */
7
export async function main(
8
  auth: RT.Amqp,
9
  routing_key: string,
10
  payload: any,
11
  exchange: string | undefined,
12
  headers: { [key: string]: string } | undefined,
13
  persistent: boolean = false
14
) {
15
  if (payload === undefined) throw new Error("payload is required")
16
  const anonymous = !auth.username && !auth.password
17
  const conn = await amqp.connect({
18
    protocol: auth.tls ? "amqps" : "amqp",
19
    hostname: auth.host,
20
    port: auth.port || (auth.tls ? 5671 : 5672),
21
    // guest/guest is amqplib's default, but only for a resource with no credentials at all.
22
    username: anonymous ? "guest" : (auth.username ?? ""),
23
    password: anonymous ? "guest" : (auth.password ?? ""),
24
    // amqplib unescapes the vhost: encode it, like Windmill's AMQP trigger does.
25
    vhost: encodeURIComponent(auth.vhost || "/"),
26
  })
27
  try {
28
    // amqplib rethrows an 'error' event nobody listens to, and doesn't settle pending
29
    // confirms when that happens: race the confirm against the connection's and the
30
    // channel's errors (unknown exchange, access refused, broker closing the connection).
31
    const failed = new Promise<never>((_, reject) => conn.once("error", reject))
32
    failed.catch(() => {})
33
    const channel = await Promise.race([conn.createConfirmChannel(), failed])
34
    const closed = new Promise<never>((_, reject) =>
35
      channel.once("error", reject)
36
    )
37
    closed.catch(() => {})
38
    // mandatory: the broker returns a message no queue took, before its confirm.
39
    let routed = true
40
    channel.on("return", () => (routed = false))
41
    const text = typeof payload === "string"
42
    channel.publish(
43
      exchange || "",
44
      routing_key,
45
      Buffer.from(text ? payload : JSON.stringify(payload)),
46
      {
47
        mandatory: true,
48
        persistent,
49
        contentType: text ? "text/plain" : "application/json",
50
        headers: Object.fromEntries(
51
          Object.entries(headers ?? {}).map(([k, v]) => [
52
            k,
53
            typeof v === "string" ? v : JSON.stringify(v),
54
          ])
55
        ),
56
      }
57
    )
58
    await Promise.race([channel.waitForConfirms(), failed, closed])
59
    return { exchange: exchange || "", routing_key, routed }
60
  } finally {
61
    // Closing an already-closed connection throws, which would hide the real error.
62
    await conn.close().catch(() => {})
63
  }
64
}
65