{"version":3,"file":"rabbitmq-driver.mjs","names":[],"sources":["../../../../../../../../@warlock.js/herald/src/drivers/rabbitmq/rabbitmq-driver.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\r\nimport type { BrokerDriverContract, ChannelContract } from \"../../contracts\";\r\nimport { EventMessage } from \"../../message-managers/event-message\";\r\nimport { EventConsumerClass } from \"../../message-managers/types\";\r\nimport type {\r\n  BrokerDriverType,\r\n  BrokerEvent,\r\n  BrokerEventListener,\r\n  ChannelOptions,\r\n  HealthCheckResult,\r\n  RabbitMQConnectionOptions,\r\n} from \"../../types\";\r\nimport { prepareConsumerSubscription } from \"./../../message-managers/prepare-consumer-subscription\";\r\nimport { RabbitMQChannel } from \"./rabbitmq-channel\";\r\n\r\n// ============================================================\r\n// Lazy-loaded amqplib Module\r\n// ============================================================\r\n\r\n/**\r\n * Cached amqplib module (loaded once, reused)\r\n */\r\nlet amqplibModule: typeof import(\"amqplib\");\r\n\r\n/**\r\n * Module availability flag\r\n */\r\nlet isModuleExists: boolean | null = null;\r\n\r\n/**\r\n * Installation instructions for amqplib\r\n */\r\nconst AMQPLIB_INSTALL_INSTRUCTIONS = `\r\nRabbitMQ driver requires the amqplib package.\r\nInstall it with:\r\n\r\n  npx warlock add herald --driver=rabbitmq\r\n\r\nOr manually:\r\n\r\n  npm install amqplib\r\n  pnpm add amqplib\r\n  yarn add amqplib\r\n`.trim();\r\n\r\n/**\r\n * Load amqplib module\r\n */\r\nasync function loadAmqplibModule() {\r\n  try {\r\n    amqplibModule = await import(\"amqplib\");\r\n    isModuleExists = true;\r\n  } catch {\r\n    isModuleExists = false;\r\n  }\r\n}\r\n\r\n// Kick off eager loading immediately\r\nloadAmqplibModule();\r\n\r\n// ============================================================\r\n// RabbitMQ Driver\r\n// ============================================================\r\n\r\n/**\r\n * RabbitMQ Driver\r\n *\r\n * Implementation of BrokerDriverContract for RabbitMQ/AMQP.\r\n *\r\n * **Important:** This driver requires the `amqplib` package to be installed.\r\n * Install it with: `npx warlock add herald --driver=rabbitmq` or `npm install amqplib`\r\n *\r\n * @example\r\n * ```typescript\r\n * const driver = new RabbitMQDriver({\r\n *   driver: \"rabbitmq\",\r\n *   host: \"localhost\",\r\n *   port: 5672,\r\n *   username: \"guest\",\r\n *   password: \"guest\",\r\n * });\r\n *\r\n * await driver.connect();\r\n * const channel = driver.channel(\"user.created\");\r\n * ```\r\n */\r\nexport class RabbitMQDriver implements BrokerDriverContract {\r\n  public readonly name = \"rabbitmq\" as const;\r\n\r\n  public readonly consumers: EventConsumerClass[] = [];\r\n\r\n  private readonly options: RabbitMQConnectionOptions;\r\n  private readonly events = new EventEmitter();\r\n  private readonly channels = new Map<string, ChannelContract<any>>();\r\n\r\n  private connection: any = null;\r\n  private amqpChannel: any = null;\r\n  private _isConnected = false;\r\n\r\n  /**\r\n   * Create a new RabbitMQ driver\r\n   *\r\n   * @param options - RabbitMQ connection options\r\n   */\r\n  public constructor(options: RabbitMQConnectionOptions) {\r\n    this.options = options;\r\n  }\r\n\r\n  /**\r\n   * Whether connected to RabbitMQ\r\n   */\r\n  public get isConnected(): boolean {\r\n    return this._isConnected;\r\n  }\r\n\r\n  /**\r\n   * Subscribe the given consumer class to the driver\r\n   *\r\n   * @param consumer - Consumer class to subscribe\r\n   *\r\n   * @example\r\n   * ```typescript\r\n   * driver.subscribe(UserUpdatedConsumer);\r\n   * ```\r\n   */\r\n  public subscribe(Consumer: EventConsumerClass) {\r\n    if (this.isConnected) {\r\n      this.channel(Consumer.eventName).subscribe(\r\n        prepareConsumerSubscription(Consumer, (error, eventName) => {\r\n          this.events.emit(\"error\", error, eventName);\r\n        }),\r\n        {\r\n          consumerId: Consumer.consumerId,\r\n        },\r\n      );\r\n    } else {\r\n      this.consumers.push(Consumer);\r\n    }\r\n\r\n    return () => {\r\n      this.unsubscribe(Consumer);\r\n    };\r\n  }\r\n\r\n  /**\r\n   * Unsubscribe the given consumer\r\n   */\r\n  public unsubscribe(Consumer: EventConsumerClass): void {\r\n    if (this.isConnected) {\r\n      this.channel(Consumer.eventName).unsubscribeById(Consumer.consumerId);\r\n    }\r\n    const index = this.consumers.indexOf(Consumer);\r\n    if (index > -1) {\r\n      this.consumers.splice(index, 1);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Publish the given event message.\r\n   * Auto-creates the channel if it hasn't been accessed before.\r\n   */\r\n  public publish<TPayload = Record<string, any>>(event: EventMessage<TPayload>): void {\r\n    this.channel(event.eventName).publish(event.serialize());\r\n  }\r\n\r\n  /**\r\n   * Connect to RabbitMQ\r\n   */\r\n  public async connect(): Promise<void> {\r\n    // Check if amqplib is installed\r\n    if (isModuleExists === false) {\r\n      throw new Error(`amqplib is not installed.\\n\\n${AMQPLIB_INSTALL_INSTRUCTIONS}`);\r\n    }\r\n\r\n    // Wait for module to load if still loading\r\n    if (isModuleExists === null) {\r\n      await loadAmqplibModule();\r\n      if (!isModuleExists) {\r\n        throw new Error(`amqplib is not installed.\\n\\n${AMQPLIB_INSTALL_INSTRUCTIONS}`);\r\n      }\r\n    }\r\n\r\n    try {\r\n      // Build connection URL\r\n      const url = this.buildConnectionUrl();\r\n\r\n      // Build connection options merging our options with native client options\r\n      const connectOptions = {\r\n        heartbeat: this.options.heartbeat ?? 60,\r\n        timeout: this.options.connectionTimeout,\r\n        // Merge native amqplib client options\r\n        ...this.options.clientOptions,\r\n      };\r\n\r\n      // Connect using cached module\r\n      this.connection = await amqplibModule.connect(url, connectOptions);\r\n\r\n      // Create channel\r\n      this.amqpChannel = await this.connection.createChannel();\r\n\r\n      // Set prefetch if specified\r\n      if (this.options.prefetch) {\r\n        await this.amqpChannel.prefetch(this.options.prefetch);\r\n      }\r\n\r\n      this._isConnected = true;\r\n      this.events.emit(\"connected\");\r\n\r\n      for (const consumer of this.consumers) {\r\n        this.subscribe(consumer);\r\n      }\r\n\r\n      this.consumers.length = 0;\r\n\r\n      // Handle connection close\r\n      this.connection.on(\"close\", () => {\r\n        this._isConnected = false;\r\n        this.events.emit(\"disconnected\");\r\n\r\n        if (this.options.reconnect !== false) {\r\n          this.handleReconnect();\r\n        }\r\n      });\r\n\r\n      // Handle errors\r\n      this.connection.on(\"error\", (error: Error) => {\r\n        this.events.emit(\"error\", error);\r\n      });\r\n    } catch (error) {\r\n      this._isConnected = false;\r\n      throw new Error(\r\n        `Failed to connect to RabbitMQ: ${error instanceof Error ? error.message : String(error)}`,\r\n      );\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Build connection URL from options\r\n   */\r\n  private buildConnectionUrl(): string {\r\n    if (this.options.uri) {\r\n      return this.options.uri;\r\n    }\r\n\r\n    const protocol = \"amqp\";\r\n    const host = this.options.host ?? \"localhost\";\r\n    const port = this.options.port ?? 5672;\r\n    const vhost = this.options.vhost ?? \"/\";\r\n    const username = this.options.username ?? \"guest\";\r\n    const password = this.options.password ?? \"guest\";\r\n\r\n    const encodedVhost = encodeURIComponent(vhost);\r\n\r\n    return `${protocol}://${username}:${password}@${host}:${port}/${encodedVhost}`;\r\n  }\r\n\r\n  /**\r\n   * Handle reconnection\r\n   */\r\n  private async handleReconnect(): Promise<void> {\r\n    const delay = this.options.reconnectDelay ?? 5000;\r\n    let attempt = 0;\r\n\r\n    const tryReconnect = async () => {\r\n      attempt++;\r\n      this.events.emit(\"reconnecting\", attempt);\r\n\r\n      try {\r\n        await this.connect();\r\n      } catch {\r\n        setTimeout(tryReconnect, delay);\r\n      }\r\n    };\r\n\r\n    setTimeout(tryReconnect, delay);\r\n  }\r\n\r\n  /**\r\n   * Disconnect from RabbitMQ\r\n   */\r\n  public async disconnect(): Promise<void> {\r\n    if (this.amqpChannel) {\r\n      try {\r\n        await this.amqpChannel.close();\r\n      } catch {\r\n        // Ignore close errors\r\n      }\r\n      this.amqpChannel = null;\r\n    }\r\n\r\n    if (this.connection) {\r\n      try {\r\n        await this.connection.close();\r\n      } catch {\r\n        // Ignore close errors\r\n      }\r\n      this.connection = null;\r\n    }\r\n\r\n    this._isConnected = false;\r\n    this.events.emit(\"disconnected\");\r\n  }\r\n\r\n  /**\r\n   * Register event listener\r\n   */\r\n  public on(event: BrokerEvent, listener: BrokerEventListener): void {\r\n    this.events.on(event, listener as any);\r\n  }\r\n\r\n  /**\r\n   * Remove event listener\r\n   */\r\n  public off(event: BrokerEvent, listener: BrokerEventListener): void {\r\n    this.events.off(event, listener as any);\r\n  }\r\n\r\n  /**\r\n   * Get or create a channel\r\n   */\r\n  public channel<TPayload = unknown>(\r\n    name: string,\r\n    options?: ChannelOptions<TPayload>,\r\n  ): ChannelContract<TPayload> {\r\n    // Check cache\r\n    const existing = this.channels.get(name);\r\n    if (existing) {\r\n      return existing as ChannelContract<TPayload>;\r\n    }\r\n\r\n    // Create new channel\r\n    const channel = new RabbitMQChannel<TPayload>(name, this.amqpChannel, options);\r\n\r\n    this.channels.set(name, channel);\r\n    return channel;\r\n  }\r\n\r\n  /**\r\n   * Start consuming messages\r\n   */\r\n  public async startConsuming(): Promise<void> {\r\n    // Channels start consuming when subscribe() is called\r\n    // This method is for batch start if needed\r\n  }\r\n\r\n  /**\r\n   * Stop consuming messages from all subscribed channels.\r\n   * Gracefully cancels all active consumers.\r\n   */\r\n  public async stopConsuming(): Promise<void> {\r\n    const stops = Array.from(this.channels.values()).map(channel =>\r\n      (channel as RabbitMQChannel<any>).stopConsuming(),\r\n    );\r\n    await Promise.all(stops);\r\n  }\r\n\r\n  /**\r\n   * Health check\r\n   */\r\n  public async healthCheck(): Promise<HealthCheckResult> {\r\n    if (!this._isConnected || !this.connection) {\r\n      return {\r\n        healthy: false,\r\n        error: \"Not connected to RabbitMQ\",\r\n      };\r\n    }\r\n\r\n    const start = Date.now();\r\n\r\n    try {\r\n      // Simple check - verify channel is open\r\n      await this.amqpChannel.checkQueue(\"amq.rabbitmq.reply-to\").catch(() => {\r\n        // Queue might not exist, but if we get here, connection is alive\r\n      });\r\n\r\n      return {\r\n        healthy: true,\r\n        latency: Date.now() - start,\r\n      };\r\n    } catch (error) {\r\n      return {\r\n        healthy: false,\r\n        error: error instanceof Error ? error.message : String(error),\r\n        latency: Date.now() - start,\r\n      };\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get all channel names\r\n   */\r\n  public getChannelNames(): string[] {\r\n    return Array.from(this.channels.keys());\r\n  }\r\n\r\n  /**\r\n   * Close a specific channel\r\n   */\r\n  public async closeChannel(name: string): Promise<void> {\r\n    const channel = this.channels.get(name);\r\n    if (channel) {\r\n      await channel.delete();\r\n      this.channels.delete(name);\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get the raw AMQP channel (for advanced use)\r\n   */\r\n  public getRawChannel(): any {\r\n    return this.amqpChannel;\r\n  }\r\n\r\n  /**\r\n   * Get the raw connection (for advanced use)\r\n   */\r\n  public getRawConnection(): any {\r\n    return this.connection;\r\n  }\r\n}\r\n"],"mappings":"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"}