@amqp-contract/core 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/index.cjs +26 -37
- package/dist/index.d.cts +19 -34
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +19 -34
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +26 -37
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +113 -117
- package/package.json +6 -6
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/connection-manager.ts","../src/errors.ts","../src/setup.ts","../src/amqp-client.ts","../src/telemetry.ts"],"sourcesContent":["import amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\n\n/**\n * Connection manager singleton for sharing AMQP connections across clients.\n *\n * This singleton implements connection pooling to avoid creating multiple connections\n * to the same broker, which is a RabbitMQ best practice. Connections are identified\n * by their URLs and connection options, and reference counting ensures connections\n * are only closed when all clients have released them.\n *\n * @example\n * ```typescript\n * const manager = ConnectionManagerSingleton.getInstance();\n * const connection = manager.getConnection(['amqp://localhost']);\n * // ... use connection ...\n * await manager.releaseConnection(['amqp://localhost']);\n * ```\n */\nexport class ConnectionManagerSingleton {\n private static instance: ConnectionManagerSingleton;\n private connections: Map<string, AmqpConnectionManager> = new Map();\n private refCounts: Map<string, number> = new Map();\n\n private constructor() {}\n\n /**\n * Get the singleton instance of the connection manager.\n *\n * @returns The singleton instance\n */\n static getInstance(): ConnectionManagerSingleton {\n if (!ConnectionManagerSingleton.instance) {\n ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();\n }\n return ConnectionManagerSingleton.instance;\n }\n\n /**\n * Get or create a connection for the given URLs and options.\n *\n * If a connection already exists with the same URLs and options, it is reused\n * and its reference count is incremented. Otherwise, a new connection is created.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns The AMQP connection manager instance\n */\n getConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): AmqpConnectionManager {\n // Create a key based on URLs and connection options\n const key = this.createConnectionKey(urls, connectionOptions);\n\n if (!this.connections.has(key)) {\n const connection = amqp.connect(urls, connectionOptions);\n this.connections.set(key, connection);\n this.refCounts.set(key, 0);\n }\n\n // Increment reference count\n this.refCounts.set(key, (this.refCounts.get(key) ?? 0) + 1);\n\n return this.connections.get(key)!;\n }\n\n /**\n * Release a connection reference.\n *\n * Decrements the reference count for the connection. If the count reaches zero,\n * the connection is closed and removed from the pool.\n *\n * @param urls - AMQP broker URL(s) used to identify the connection\n * @param connectionOptions - Optional connection configuration used to identify the connection\n * @returns A promise that resolves when the connection is released (and closed if necessary)\n */\n async releaseConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): Promise<void> {\n const key = this.createConnectionKey(urls, connectionOptions);\n const refCount = this.refCounts.get(key) ?? 0;\n\n if (refCount <= 1) {\n // Last reference - close and remove connection\n const connection = this.connections.get(key);\n if (connection) {\n await connection.close();\n this.connections.delete(key);\n this.refCounts.delete(key);\n }\n } else {\n // Decrement reference count\n this.refCounts.set(key, refCount - 1);\n }\n }\n\n /**\n * Create a unique key for a connection based on URLs and options.\n *\n * The key is deterministic: same URLs and options always produce the same key,\n * enabling connection reuse.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns A unique string key identifying the connection\n */\n private createConnectionKey(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): string {\n // Create a deterministic key from URLs and options\n // Use JSON.stringify for URLs to avoid ambiguity (e.g., ['a,b'] vs ['a', 'b'])\n const urlsStr = JSON.stringify(urls);\n // Sort object keys for deterministic serialization of connection options\n const optsStr = connectionOptions ? this.serializeOptions(connectionOptions) : \"\";\n return `${urlsStr}::${optsStr}`;\n }\n\n /**\n * Serialize connection options to a deterministic string.\n *\n * @param options - Connection options to serialize\n * @returns A JSON string with sorted keys for deterministic comparison\n */\n private serializeOptions(options: AmqpConnectionManagerOptions): string {\n // Create a deterministic string representation by deeply sorting all object keys\n const sorted = this.deepSort(options);\n return JSON.stringify(sorted);\n }\n\n /**\n * Deep sort an object's keys for deterministic serialization.\n *\n * @param value - The value to deep sort (can be object, array, or primitive)\n * @returns The value with all object keys sorted alphabetically\n */\n private deepSort(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => this.deepSort(item));\n }\n\n if (value !== null && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const sortedKeys = Object.keys(obj).sort();\n const result: Record<string, unknown> = {};\n\n for (const key of sortedKeys) {\n result[key] = this.deepSort(obj[key]);\n }\n\n return result;\n }\n\n return value;\n }\n\n /**\n * Get the number of active pooled connections.\n *\n * @internal\n */\n _getConnectionCountForTesting(): number {\n return this.connections.size;\n }\n\n /**\n * Reset all cached connections (for testing purposes)\n * @internal\n */\n async _resetForTesting(): Promise<void> {\n // Close all connections before clearing\n const closePromises = Array.from(this.connections.values()).map((conn) => conn.close());\n await Promise.all(closePromises);\n this.connections.clear();\n this.refCounts.clear();\n }\n}\n\n/**\n * Number of active pooled connections. Test-only helper — exposed in lieu of\n * the underlying singleton, which is intentionally not part of the public API\n * (mutating it from outside the library can break in-flight clients sharing a\n * connection).\n *\n * @internal\n */\nexport function _getConnectionCountForTesting(): number {\n return ConnectionManagerSingleton.getInstance()._getConnectionCountForTesting();\n}\n\n/**\n * Close every pooled connection and clear ref-counts. Test-only helper.\n *\n * @internal\n */\nexport function _resetConnectionsForTesting(): Promise<void> {\n return ConnectionManagerSingleton.getInstance()._resetForTesting();\n}\n","/**\n * Error for technical/runtime failures that cannot be prevented by TypeScript.\n *\n * This includes AMQP connection failures, channel issues, validation failures,\n * and other runtime errors. This error is shared across core, worker, and client packages.\n */\nexport class TechnicalError extends Error {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error thrown when message validation fails (payload or headers).\n *\n * Used by both the client (publish-time payload validation) and the worker\n * (consume-time payload and headers validation).\n *\n * @param source - The name of the publisher or consumer that triggered the validation\n * @param issues - The validation issues from the Standard Schema validation\n */\nexport class MessageValidationError extends Error {\n constructor(\n public readonly source: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for \"${source}\"`);\n this.name = \"MessageValidationError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n","import type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { extractQueue } from \"@amqp-contract/contract\";\nimport type { Channel } from \"amqplib\";\nimport { TechnicalError } from \"./errors.js\";\n\n/**\n * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.\n *\n * This function sets up the complete AMQP topology in the correct order:\n * 1. Assert all exchanges defined in the contract\n * 2. Validate dead letter exchanges are declared before referencing them\n * 3. Assert all queues with their configurations (including dead letter settings)\n * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)\n *\n * @param channel - The AMQP channel to use for topology setup\n * @param contract - The contract definition containing the topology specification\n * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created\n * @throws {TechnicalError} If a queue references a dead letter exchange not declared in the contract\n *\n * @example\n * ```typescript\n * const channel = await connection.createChannel();\n * await setupAmqpTopology(channel, contract);\n * ```\n */\nexport async function setupAmqpTopology(\n channel: Channel,\n contract: ContractDefinition,\n): Promise<void> {\n // Setup exchanges. The AMQP default exchange (name \"\") is implicit; RabbitMQ\n // does not allow asserting it, so we skip empty-named exchange entries.\n const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== \"\");\n const exchangeResults = await Promise.allSettled(\n exchanges.map((exchange) =>\n channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n }),\n ),\n );\n const exchangeErrors = exchangeResults\n .map((result, i) => ({ result, name: exchanges[i]!.name }))\n .filter(\n (entry): entry is { result: PromiseRejectedResult; name: string } =>\n entry.result.status === \"rejected\",\n );\n if (exchangeErrors.length > 0) {\n const names = exchangeErrors.map((e) => e.name).join(\", \");\n throw new AggregateError(\n exchangeErrors.map(({ result }) => result.reason),\n `Failed to setup exchanges: ${names}`,\n );\n }\n\n // Validate dead letter exchanges before setting up queues\n for (const queueEntry of Object.values(contract.queues ?? {})) {\n const queue = extractQueue(queueEntry);\n if (queue.deadLetter) {\n const dlxName = queue.deadLetter.exchange.name;\n const exchangeExists = Object.values(contract.exchanges ?? {}).some(\n (exchange) => exchange.name === dlxName,\n );\n\n if (!exchangeExists) {\n throw new TechnicalError(\n `Queue \"${queue.name}\" references dead letter exchange \"${dlxName}\" which is not declared in the contract. ` +\n `Add the exchange to contract.exchanges to ensure it is created before the queue.`,\n );\n }\n }\n }\n\n // Setup queues\n const queueEntries = Object.values(contract.queues ?? {});\n const queueResults = await Promise.allSettled(\n queueEntries.map((queueEntry) => {\n const queue = extractQueue(queueEntry);\n // Build queue arguments, merging dead letter configuration and queue type\n const queueArguments: Record<string, unknown> = { ...queue.arguments };\n\n // Set queue type\n queueArguments[\"x-queue-type\"] = queue.type;\n\n if (queue.deadLetter) {\n queueArguments[\"x-dead-letter-exchange\"] = queue.deadLetter.exchange.name;\n if (queue.deadLetter.routingKey) {\n queueArguments[\"x-dead-letter-routing-key\"] = queue.deadLetter.routingKey;\n }\n }\n\n // Handle type-specific properties using discriminated union\n if (queue.type === \"quorum\") {\n return channel.assertQueue(queue.name, {\n durable: true, // Quorum queues are always durable\n arguments: queueArguments,\n });\n }\n\n if (queue.maxPriority !== undefined) {\n queueArguments[\"x-max-priority\"] = queue.maxPriority;\n }\n\n // Classic queue\n return channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queueArguments,\n });\n }),\n );\n const queueErrors = queueResults\n .map((result, i) => ({ result, name: extractQueue(queueEntries[i]!).name }))\n .filter(\n (entry): entry is { result: PromiseRejectedResult; name: string } =>\n entry.result.status === \"rejected\",\n );\n if (queueErrors.length > 0) {\n const names = queueErrors.map((e) => e.name).join(\", \");\n throw new AggregateError(\n queueErrors.map(({ result }) => result.reason),\n `Failed to setup queues: ${names}`,\n );\n }\n\n // Setup bindings\n const bindings = Object.values(contract.bindings ?? {});\n const bindingResults = await Promise.allSettled(\n bindings.map((binding) => {\n if (binding.type === \"queue\") {\n return channel.bindQueue(\n binding.queue.name,\n binding.exchange.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n\n return channel.bindExchange(\n binding.destination.name,\n binding.source.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }),\n );\n const bindingErrors = bindingResults\n .map((result, i) => {\n const binding = bindings[i]!;\n const name =\n binding.type === \"queue\"\n ? `${binding.exchange.name} -> ${binding.queue.name}`\n : `${binding.source.name} -> ${binding.destination.name}`;\n return { result, name };\n })\n .filter(\n (entry): entry is { result: PromiseRejectedResult; name: string } =>\n entry.result.status === \"rejected\",\n );\n if (bindingErrors.length > 0) {\n const names = bindingErrors.map((e) => e.name).join(\", \");\n throw new AggregateError(\n bindingErrors.map(({ result }) => result.reason),\n `Failed to setup bindings: ${names}`,\n );\n }\n}\n","import type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport type {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ChannelWrapper,\n ConnectionUrl,\n CreateChannelOpts,\n} from \"amqp-connection-manager\";\nimport type { Channel, ConsumeMessage, Options } from \"amqplib\";\nimport { ConnectionManagerSingleton } from \"./connection-manager.js\";\nimport { TechnicalError } from \"./errors.js\";\nimport { setupAmqpTopology } from \"./setup.js\";\n\n/**\n * Invoke a SetupFunc, handling both callback-based and promise-based signatures.\n * Uses Function.length to distinguish (same approach as promise-breaker).\n * @internal\n */\nfunction callSetupFunc(\n setup: NonNullable<CreateChannelOpts[\"setup\"]>,\n channel: Channel,\n): Promise<void> {\n if (setup.length >= 2) {\n return new Promise<void>((resolve, reject) => {\n (setup as (channel: Channel, callback: (error?: Error) => void) => void)(\n channel,\n (error?: Error) => {\n if (error) reject(error);\n else resolve();\n },\n );\n });\n }\n return (setup as (channel: Channel) => Promise<void>)(channel);\n}\n\n/**\n * Default time `waitForConnect` will wait for the broker before erroring out.\n * Defaulting to a finite value (rather than waiting forever) means a fail-fast\n * developer experience: a misconfigured URL, a down broker, or wrong\n * credentials surface as a Result.Error within 30 seconds. Pass `null`\n * explicitly to disable the timeout — `Infinity` and other non-finite values\n * are also coerced to \"no timeout\" because Node's `setTimeout` clamps large\n * delays to ~24.8 days and silently fires near-immediately on `Infinity`.\n */\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\n\n/**\n * Normalise the user-supplied connect timeout to either a positive finite\n * number of milliseconds, or `null` (no timeout). `Infinity`, `NaN`, and\n * non-positive values all map to `null` rather than being passed to\n * `setTimeout` — see {@link DEFAULT_CONNECT_TIMEOUT_MS}.\n */\nfunction resolveConnectTimeoutMs(input: number | null | undefined): number | null {\n if (input === null) return null;\n if (input === undefined) return DEFAULT_CONNECT_TIMEOUT_MS;\n if (!Number.isFinite(input) || input <= 0) return null;\n return input;\n}\n\n/**\n * Options for creating an AMQP client.\n *\n * @property urls - AMQP broker URL(s). Multiple URLs provide failover support.\n * @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).\n * @property channelOptions - Optional channel configuration options.\n * @property connectTimeoutMs - Maximum time in ms to wait for the channel to\n * become ready in `waitForConnect`. Defaults to {@link DEFAULT_CONNECT_TIMEOUT_MS}.\n * Pass `null` to disable the timeout entirely (amqp-connection-manager will\n * retry indefinitely).\n */\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n channelOptions?: Partial<CreateChannelOpts> | undefined;\n connectTimeoutMs?: number | null | undefined;\n};\n\n/**\n * Callback type for consuming messages.\n */\nexport type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;\n\n/**\n * Publish options that extend amqplib's Options.Publish with optional timeout support.\n */\nexport type PublishOptions = Options.Publish & {\n /** Message will be rejected after timeout ms */\n timeout?: number;\n};\n\n/**\n * Consume options that extend amqplib's Options.Consume with optional prefetch support.\n */\nexport type ConsumerOptions = Options.Consume & {\n /** Number of messages to prefetch */\n prefetch?: number;\n};\n\n/**\n * AMQP client that manages connections and channels with automatic topology setup.\n *\n * This class handles:\n * - Connection management with automatic reconnection via amqp-connection-manager\n * - Connection pooling and sharing across instances with the same URLs\n * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract\n * - Channel creation with JSON serialization enabled by default\n *\n * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.\n *\n * @example\n * ```typescript\n * const client = new AmqpClient(contract, {\n * urls: ['amqp://localhost'],\n * connectionOptions: { heartbeatIntervalInSeconds: 30 }\n * });\n *\n * // Wait for connection\n * await client.waitForConnect().resultToPromise();\n *\n * // Publish a message\n * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();\n *\n * // Close when done\n * await client.close().resultToPromise();\n * ```\n */\nexport class AmqpClient {\n private readonly connection: AmqpConnectionManager;\n private readonly channelWrapper: ChannelWrapper;\n private readonly urls: ConnectionUrl[];\n private readonly connectionOptions?: AmqpConnectionManagerOptions;\n /** Resolved timeout in ms; `null` means \"wait forever\". */\n private readonly connectTimeoutMs: number | null;\n\n /**\n * Create a new AMQP client instance.\n *\n * The client will automatically:\n * - Get or create a shared connection using the singleton pattern\n * - Set up AMQP topology (exchanges, queues, bindings) from the contract\n * - Create a channel with JSON serialization enabled\n *\n * @param contract - The contract definition specifying the AMQP topology\n * @param options - Client configuration options\n */\n constructor(\n private readonly contract: ContractDefinition,\n options: AmqpClientOptions,\n ) {\n // Store for cleanup\n this.urls = options.urls;\n if (options.connectionOptions !== undefined) {\n this.connectionOptions = options.connectionOptions;\n }\n // Resolve connect timeout: explicit null disables it; undefined (the common\n // case) gets the fail-fast default. Finite positive numbers pass through;\n // any other numeric value (Infinity, NaN, ≤ 0) is coerced to null because\n // Node's `setTimeout` clamps large delays to ~24.8 days and silently fires\n // near-immediately on Infinity — neither is what a caller asking for \"no\n // timeout\" expects.\n this.connectTimeoutMs = resolveConnectTimeoutMs(options.connectTimeoutMs);\n\n // Always use singleton to get/create connection\n const singleton = ConnectionManagerSingleton.getInstance();\n this.connection = singleton.getConnection(options.urls, options.connectionOptions);\n\n // Create default setup function that calls setupAmqpTopology\n const defaultSetup = (channel: Channel) => setupAmqpTopology(channel, this.contract);\n\n // Destructure setup from channelOptions to handle it separately\n const { setup: userSetup, ...otherChannelOptions } = options.channelOptions ?? {};\n\n // Merge user-provided channel options with defaults\n const channelOpts: CreateChannelOpts = {\n confirm: true,\n json: true,\n setup: defaultSetup,\n ...otherChannelOptions,\n };\n\n // If user provided a custom setup, wrap it to call both\n if (userSetup) {\n channelOpts.setup = async (channel: Channel) => {\n await defaultSetup(channel);\n await callSetupFunc(userSetup, channel);\n };\n }\n\n this.channelWrapper = this.connection.createChannel(channelOpts);\n }\n\n /**\n * Get the underlying connection manager\n *\n * This method exposes the AmqpConnectionManager instance that this client uses.\n * The connection is automatically shared across all AmqpClient instances that\n * use the same URLs and connection options.\n *\n * @returns The AmqpConnectionManager instance used by this client\n */\n getConnection(): AmqpConnectionManager {\n return this.connection;\n }\n\n /**\n * Wait for the channel to be connected and ready.\n *\n * If `connectTimeoutMs` was provided in the constructor options, the returned\n * Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.\n * Without a timeout, this waits forever — amqp-connection-manager retries\n * connections indefinitely and never errors on its own.\n *\n * NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /\n * `TypedAmqpWorker`), the constructor has already incremented the pooled\n * connection's reference count. Callers must invoke `close()` on the error\n * path to release the connection — `waitForConnect` does not do this\n * automatically. The typed factories handle this cleanup for you.\n *\n * @returns A Future resolving to `Result.Ok(void)` on connect, or\n * `Result.Error(TechnicalError)` on timeout / connection failure.\n */\n waitForConnect(): Future<Result<void, TechnicalError>> {\n const connectPromise = this.channelWrapper.waitForConnect();\n const timeoutMs = this.connectTimeoutMs;\n\n const racedPromise =\n timeoutMs === null\n ? connectPromise\n : new Promise<void>((resolve, reject) => {\n const handle = setTimeout(() => {\n reject(new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));\n }, timeoutMs);\n connectPromise.then(\n () => {\n clearTimeout(handle);\n resolve();\n },\n (error: unknown) => {\n clearTimeout(handle);\n reject(error);\n },\n );\n });\n\n return Future.fromPromise(racedPromise).mapError(\n (error: unknown) => new TechnicalError(\"Failed to connect to AMQP broker\", error),\n );\n }\n\n /**\n * Publish a message to an exchange.\n *\n * @param exchange - The exchange name\n * @param routingKey - The routing key\n * @param content - The message content (will be JSON serialized if json: true)\n * @param options - Optional publish options\n * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full\n */\n publish(\n exchange: string,\n routingKey: string,\n content: Buffer | unknown,\n options?: PublishOptions,\n ): Future<Result<boolean, TechnicalError>> {\n return Future.fromPromise(\n this.channelWrapper.publish(exchange, routingKey, content, options),\n ).mapError((error: unknown) => new TechnicalError(\"Failed to publish message\", error));\n }\n\n /**\n * Publish a message directly to a queue.\n *\n * @param queue - The queue name\n * @param content - The message content (will be JSON serialized if json: true)\n * @param options - Optional publish options\n * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full\n */\n sendToQueue(\n queue: string,\n content: Buffer | unknown,\n options?: PublishOptions,\n ): Future<Result<boolean, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.sendToQueue(queue, content, options)).mapError(\n (error: unknown) => new TechnicalError(\"Failed to publish message to queue\", error),\n );\n }\n\n /**\n * Start consuming messages from a queue.\n *\n * @param queue - The queue name\n * @param callback - The callback to invoke for each message\n * @param options - Optional consume options\n * @returns A Future with `Result<string>` - the consumer tag\n */\n consume(\n queue: string,\n callback: ConsumeCallback,\n options?: ConsumerOptions,\n ): Future<Result<string, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.consume(queue, callback, options))\n .mapError((error: unknown) => new TechnicalError(\"Failed to start consuming messages\", error))\n .mapOk((reply: { consumerTag: string }) => reply.consumerTag);\n }\n\n /**\n * Cancel a consumer by its consumer tag.\n *\n * @param consumerTag - The consumer tag to cancel\n * @returns A Future that resolves when the consumer is cancelled\n */\n cancel(consumerTag: string): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.cancel(consumerTag))\n .mapError((error: unknown) => new TechnicalError(\"Failed to cancel consumer\", error))\n .mapOk(() => undefined);\n }\n\n /**\n * Acknowledge a message.\n *\n * @param msg - The message to acknowledge\n * @param allUpTo - If true, acknowledge all messages up to and including this one\n */\n ack(msg: ConsumeMessage, allUpTo = false): void {\n this.channelWrapper.ack(msg, allUpTo);\n }\n\n /**\n * Negative acknowledge a message.\n *\n * @param msg - The message to nack\n * @param allUpTo - If true, nack all messages up to and including this one\n * @param requeue - If true, requeue the message(s)\n */\n nack(msg: ConsumeMessage, allUpTo = false, requeue = true): void {\n this.channelWrapper.nack(msg, allUpTo, requeue);\n }\n\n /**\n * Add a setup function to be called when the channel is created or reconnected.\n *\n * This is useful for setting up channel-level configuration like prefetch.\n *\n * @param setup - The setup function to add\n */\n addSetup(setup: (channel: Channel) => void | Promise<void>): void {\n this.channelWrapper.addSetup(setup);\n }\n\n /**\n * Register an event listener on the channel wrapper.\n *\n * Available events:\n * - 'connect': Emitted when the channel is (re)connected\n * - 'close': Emitted when the channel is closed\n * - 'error': Emitted when an error occurs\n *\n * @param event - The event name\n * @param listener - The event listener\n */\n on(event: string, listener: (...args: unknown[]) => void): void {\n this.channelWrapper.on(event, listener);\n }\n\n /**\n * Close the channel and release the connection reference.\n *\n * This will:\n * - Close the channel wrapper\n * - Decrease the reference count on the shared connection\n * - Close the connection if this was the last client using it\n *\n * @returns A Future that resolves when the channel and connection are closed\n */\n close(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.close())\n .mapError((error: unknown) => new TechnicalError(\"Failed to close channel\", error))\n .flatMap((channelResult) =>\n Future.fromPromise(\n ConnectionManagerSingleton.getInstance().releaseConnection(\n this.urls,\n this.connectionOptions,\n ),\n )\n .mapError((error: unknown) => new TechnicalError(\"Failed to release connection\", error))\n .map((releaseResult) => {\n if (channelResult.isError() && releaseResult.isError()) {\n return Result.Error(\n new TechnicalError(\n \"Failed to close channel and release connection\",\n new AggregateError(\n [channelResult.error, releaseResult.error],\n \"Failed to close channel and release connection\",\n ),\n ),\n );\n }\n\n return channelResult.isError() ? channelResult : releaseResult;\n }),\n );\n }\n\n /**\n * Reset connection singleton cache (for testing only)\n * @internal\n */\n static async _resetConnectionCacheForTesting(): Promise<void> {\n await ConnectionManagerSingleton.getInstance()._resetForTesting();\n }\n}\n","import { createRequire } from \"node:module\";\nimport {\n type Attributes,\n type Counter,\n type Histogram,\n type Span,\n type Tracer,\n} from \"@opentelemetry/api\";\n\n/**\n * SpanKind values from OpenTelemetry.\n * Defined as constants to avoid runtime dependency when types are used.\n * @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind\n */\nconst SpanKind = {\n /** Producer span represents a message producer */\n PRODUCER: 3,\n /** Consumer span represents a message consumer */\n CONSUMER: 4,\n} as const;\n\n/**\n * Semantic conventions for AMQP messaging following OpenTelemetry standards.\n * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/\n */\nexport const MessagingSemanticConventions = {\n // Messaging attributes\n MESSAGING_SYSTEM: \"messaging.system\",\n MESSAGING_DESTINATION: \"messaging.destination.name\",\n MESSAGING_DESTINATION_KIND: \"messaging.destination.kind\",\n MESSAGING_OPERATION: \"messaging.operation\",\n\n // AMQP/RabbitMQ specific attributes\n MESSAGING_RABBITMQ_ROUTING_KEY: \"messaging.rabbitmq.destination.routing_key\",\n MESSAGING_RABBITMQ_MESSAGE_DELIVERY_TAG: \"messaging.rabbitmq.message.delivery_tag\",\n AMQP_PUBLISHER_NAME: \"amqp.publisher.name\",\n AMQP_CONSUMER_NAME: \"amqp.consumer.name\",\n\n // Error attributes\n ERROR_TYPE: \"error.type\",\n\n // Values\n MESSAGING_SYSTEM_RABBITMQ: \"rabbitmq\",\n MESSAGING_DESTINATION_KIND_EXCHANGE: \"exchange\",\n MESSAGING_DESTINATION_KIND_QUEUE: \"queue\",\n MESSAGING_OPERATION_PUBLISH: \"publish\",\n MESSAGING_OPERATION_PROCESS: \"process\",\n} as const;\n\n/**\n * Telemetry provider for AMQP operations.\n * Uses lazy loading to gracefully handle cases where OpenTelemetry is not installed.\n */\nexport type TelemetryProvider = {\n /**\n * Get a tracer instance for creating spans.\n * Returns undefined if OpenTelemetry is not available.\n */\n getTracer: () => Tracer | undefined;\n\n /**\n * Get a counter for messages published.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishCounter: () => Counter | undefined;\n\n /**\n * Get a counter for messages consumed.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeCounter: () => Counter | undefined;\n\n /**\n * Get a histogram for publish latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishLatencyHistogram: () => Histogram | undefined;\n\n /**\n * Get a histogram for consume/process latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeLatencyHistogram: () => Histogram | undefined;\n\n /**\n * Get a counter for RPC replies that arrive after the caller has gone away\n * (timeout, cancellation, or unknown correlationId). Returns undefined if\n * OpenTelemetry is not available.\n */\n getLateRpcReplyCounter: () => Counter | undefined;\n};\n\n/**\n * Instrumentation scope name for amqp-contract.\n */\nconst INSTRUMENTATION_SCOPE_NAME = \"@amqp-contract\";\n\n/**\n * Instrumentation scope version, sourced from this package's package.json so\n * the OTel meter version always tracks the released library version. We use\n * `createRequire` rather than a JSON import attribute so the same source builds\n * to ESM, CJS, and runs under bundlers that don't yet understand\n * `import … with { type: \"json\" }`.\n */\nconst INSTRUMENTATION_SCOPE_VERSION: string = (() => {\n try {\n const localRequire = createRequire(import.meta.url);\n const pkg = localRequire(\"../package.json\") as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n})();\n\n// Cache for OpenTelemetry API module and instruments\nlet otelApi: typeof import(\"@opentelemetry/api\") | null | undefined;\nlet cachedTracer: Tracer | undefined;\nlet cachedPublishCounter: Counter | undefined;\nlet cachedConsumeCounter: Counter | undefined;\nlet cachedPublishLatencyHistogram: Histogram | undefined;\nlet cachedConsumeLatencyHistogram: Histogram | undefined;\nlet cachedLateRpcReplyCounter: Counter | undefined;\n\n/**\n * Try to load the OpenTelemetry API module.\n * Returns null if the module is not available.\n */\nfunction tryLoadOpenTelemetryApi(): typeof import(\"@opentelemetry/api\") | null {\n if (otelApi === undefined) {\n try {\n // Dynamic import using require to avoid bundler issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n otelApi = require(\"@opentelemetry/api\") as typeof import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n }\n return otelApi;\n}\n\n/**\n * Get or create a tracer instance.\n */\nfunction getTracer(): Tracer | undefined {\n if (cachedTracer !== undefined) {\n return cachedTracer;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return undefined;\n }\n\n cachedTracer = api.trace.getTracer(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n return cachedTracer;\n}\n\n/**\n * Get or create a meter and its instruments.\n */\nfunction getMeterInstruments(): {\n publishCounter: Counter | undefined;\n consumeCounter: Counter | undefined;\n publishLatencyHistogram: Histogram | undefined;\n consumeLatencyHistogram: Histogram | undefined;\n lateRpcReplyCounter: Counter | undefined;\n} {\n if (cachedPublishCounter !== undefined) {\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n lateRpcReplyCounter: cachedLateRpcReplyCounter,\n };\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return {\n publishCounter: undefined,\n consumeCounter: undefined,\n publishLatencyHistogram: undefined,\n consumeLatencyHistogram: undefined,\n lateRpcReplyCounter: undefined,\n };\n }\n\n const meter = api.metrics.getMeter(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n\n cachedPublishCounter = meter.createCounter(\"amqp.client.messages.published\", {\n description: \"Number of messages published to AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedConsumeCounter = meter.createCounter(\"amqp.worker.messages.consumed\", {\n description: \"Number of messages consumed from AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedPublishLatencyHistogram = meter.createHistogram(\"amqp.client.publish.duration\", {\n description: \"Duration of message publish operations\",\n unit: \"ms\",\n });\n\n cachedConsumeLatencyHistogram = meter.createHistogram(\"amqp.worker.process.duration\", {\n description: \"Duration of message processing operations\",\n unit: \"ms\",\n });\n\n cachedLateRpcReplyCounter = meter.createCounter(\"amqp.client.rpc.late_reply\", {\n description:\n \"RPC replies received after the caller stopped waiting (timeout, cancellation, or unknown correlationId)\",\n unit: \"{message}\",\n });\n\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n lateRpcReplyCounter: cachedLateRpcReplyCounter,\n };\n}\n\n/**\n * Default telemetry provider that uses OpenTelemetry API if available.\n */\nexport const defaultTelemetryProvider: TelemetryProvider = {\n getTracer,\n getPublishCounter: () => getMeterInstruments().publishCounter,\n getConsumeCounter: () => getMeterInstruments().consumeCounter,\n getPublishLatencyHistogram: () => getMeterInstruments().publishLatencyHistogram,\n getConsumeLatencyHistogram: () => getMeterInstruments().consumeLatencyHistogram,\n getLateRpcReplyCounter: () => getMeterInstruments().lateRpcReplyCounter,\n};\n\n/**\n * Create a span for a publish operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startPublishSpan(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${exchangeName} publish`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.PRODUCER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_EXCHANGE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PUBLISH,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n ...attributes,\n },\n });\n}\n\n/**\n * Create a span for a consume/process operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startConsumeSpan(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${queueName} process`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.CONSUMER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_QUEUE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PROCESS,\n [MessagingSemanticConventions.AMQP_CONSUMER_NAME]: consumerName,\n ...attributes,\n },\n });\n}\n\n/**\n * End a span with success status.\n */\nexport function endSpanSuccess(span: Span | undefined): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.OK });\n }\n span.end();\n}\n\n/**\n * End a span with error status.\n */\nexport function endSpanError(span: Span | undefined, error: Error): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });\n span.recordException(error);\n span.setAttribute(MessagingSemanticConventions.ERROR_TYPE, error.name);\n }\n span.end();\n}\n\n/**\n * Record a publish metric.\n */\nexport function recordPublishMetric(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n success: boolean,\n durationMs: number,\n): void {\n const publishCounter = provider.getPublishCounter();\n const publishLatencyHistogram = provider.getPublishLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n success: success,\n };\n\n publishCounter?.add(1, attributes);\n publishLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Record a consume metric.\n */\nexport function recordConsumeMetric(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n success: boolean,\n durationMs: number,\n): void {\n const consumeCounter = provider.getConsumeCounter();\n const consumeLatencyHistogram = provider.getConsumeLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n [MessagingSemanticConventions.AMQP_CONSUMER_NAME]: consumerName,\n success: success,\n };\n\n consumeCounter?.add(1, attributes);\n consumeLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Record an RPC reply that arrived after the caller stopped waiting.\n *\n * @param reason - Why the reply was orphaned. `\"unknown-correlation-id\"` is\n * the typical \"caller already timed out\" case; `\"missing-correlation-id\"`\n * means the broker delivered a reply with no correlationId at all (a\n * protocol violation by the responder).\n */\nexport function recordLateRpcReply(\n provider: TelemetryProvider,\n reason: \"unknown-correlation-id\" | \"missing-correlation-id\",\n): void {\n const counter = provider.getLateRpcReplyCounter();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n reason,\n };\n\n counter?.add(1, attributes);\n}\n\n/**\n * Reset the cached OpenTelemetry API module and instruments.\n * For testing purposes only.\n * @internal\n */\nexport function _resetTelemetryCacheForTesting(): void {\n otelApi = undefined;\n cachedTracer = undefined;\n cachedPublishCounter = undefined;\n cachedConsumeCounter = undefined;\n cachedPublishLatencyHistogram = undefined;\n cachedConsumeLatencyHistogram = undefined;\n cachedLateRpcReplyCounter = undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/connection-manager.ts","../src/errors.ts","../src/setup.ts","../src/amqp-client.ts","../src/telemetry.ts"],"sourcesContent":["import amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\n\n/**\n * Connection manager singleton for sharing AMQP connections across clients.\n *\n * This singleton implements connection pooling to avoid creating multiple connections\n * to the same broker, which is a RabbitMQ best practice. Connections are identified\n * by their URLs and connection options, and reference counting ensures connections\n * are only closed when all clients have released them.\n *\n * @example\n * ```typescript\n * const manager = ConnectionManagerSingleton.getInstance();\n * const connection = manager.getConnection(['amqp://localhost']);\n * // ... use connection ...\n * await manager.releaseConnection(['amqp://localhost']);\n * ```\n */\nexport class ConnectionManagerSingleton {\n private static instance: ConnectionManagerSingleton;\n private connections: Map<string, AmqpConnectionManager> = new Map();\n private refCounts: Map<string, number> = new Map();\n\n private constructor() {}\n\n /**\n * Get the singleton instance of the connection manager.\n *\n * @returns The singleton instance\n */\n static getInstance(): ConnectionManagerSingleton {\n if (!ConnectionManagerSingleton.instance) {\n ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();\n }\n return ConnectionManagerSingleton.instance;\n }\n\n /**\n * Get or create a connection for the given URLs and options.\n *\n * If a connection already exists with the same URLs and options, it is reused\n * and its reference count is incremented. Otherwise, a new connection is created.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns The AMQP connection manager instance\n */\n getConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): AmqpConnectionManager {\n // Create a key based on URLs and connection options\n const key = this.createConnectionKey(urls, connectionOptions);\n\n if (!this.connections.has(key)) {\n const connection = amqp.connect(urls, connectionOptions);\n this.connections.set(key, connection);\n this.refCounts.set(key, 0);\n }\n\n // Increment reference count\n this.refCounts.set(key, (this.refCounts.get(key) ?? 0) + 1);\n\n return this.connections.get(key)!;\n }\n\n /**\n * Release a connection reference.\n *\n * Decrements the reference count for the connection. If the count reaches zero,\n * the connection is closed and removed from the pool.\n *\n * @param urls - AMQP broker URL(s) used to identify the connection\n * @param connectionOptions - Optional connection configuration used to identify the connection\n * @returns A promise that resolves when the connection is released (and closed if necessary)\n */\n async releaseConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): Promise<void> {\n const key = this.createConnectionKey(urls, connectionOptions);\n const refCount = this.refCounts.get(key) ?? 0;\n\n if (refCount <= 1) {\n // Last reference - close and remove connection\n const connection = this.connections.get(key);\n if (connection) {\n await connection.close();\n this.connections.delete(key);\n this.refCounts.delete(key);\n }\n } else {\n // Decrement reference count\n this.refCounts.set(key, refCount - 1);\n }\n }\n\n /**\n * Create a unique key for a connection based on URLs and options.\n *\n * The key is deterministic: same URLs and options always produce the same key,\n * enabling connection reuse.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns A unique string key identifying the connection\n */\n private createConnectionKey(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): string {\n // Create a deterministic key from URLs and options\n // Use JSON.stringify for URLs to avoid ambiguity (e.g., ['a,b'] vs ['a', 'b'])\n const urlsStr = JSON.stringify(urls);\n // Sort object keys for deterministic serialization of connection options\n const optsStr = connectionOptions ? this.serializeOptions(connectionOptions) : \"\";\n return `${urlsStr}::${optsStr}`;\n }\n\n /**\n * Serialize connection options to a deterministic string.\n *\n * @param options - Connection options to serialize\n * @returns A JSON string with sorted keys for deterministic comparison\n */\n private serializeOptions(options: AmqpConnectionManagerOptions): string {\n // Create a deterministic string representation by deeply sorting all object keys\n const sorted = this.deepSort(options);\n return JSON.stringify(sorted);\n }\n\n /**\n * Deep sort an object's keys for deterministic serialization.\n *\n * @param value - The value to deep sort (can be object, array, or primitive)\n * @returns The value with all object keys sorted alphabetically\n */\n private deepSort(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => this.deepSort(item));\n }\n\n if (value !== null && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const sortedKeys = Object.keys(obj).sort();\n const result: Record<string, unknown> = {};\n\n for (const key of sortedKeys) {\n result[key] = this.deepSort(obj[key]);\n }\n\n return result;\n }\n\n return value;\n }\n\n /**\n * Get the number of active pooled connections.\n *\n * @internal\n */\n _getConnectionCountForTesting(): number {\n return this.connections.size;\n }\n\n /**\n * Reset all cached connections (for testing purposes)\n * @internal\n */\n async _resetForTesting(): Promise<void> {\n // Close all connections before clearing\n const closePromises = Array.from(this.connections.values()).map((conn) => conn.close());\n await Promise.all(closePromises);\n this.connections.clear();\n this.refCounts.clear();\n }\n}\n\n/**\n * Number of active pooled connections. Test-only helper — exposed in lieu of\n * the underlying singleton, which is intentionally not part of the public API\n * (mutating it from outside the library can break in-flight clients sharing a\n * connection).\n *\n * @internal\n */\nexport function _getConnectionCountForTesting(): number {\n return ConnectionManagerSingleton.getInstance()._getConnectionCountForTesting();\n}\n\n/**\n * Close every pooled connection and clear ref-counts. Test-only helper.\n *\n * @internal\n */\nexport function _resetConnectionsForTesting(): Promise<void> {\n return ConnectionManagerSingleton.getInstance()._resetForTesting();\n}\n","/**\n * Error for technical/runtime failures that cannot be prevented by TypeScript.\n *\n * This includes AMQP connection failures, channel issues, validation failures,\n * and other runtime errors. This error is shared across core, worker, and client packages.\n */\nexport class TechnicalError extends Error {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error thrown when message validation fails (payload or headers).\n *\n * Used by both the client (publish-time payload validation) and the worker\n * (consume-time payload and headers validation).\n *\n * @param source - The name of the publisher or consumer that triggered the validation\n * @param issues - The validation issues from the Standard Schema validation\n */\nexport class MessageValidationError extends Error {\n constructor(\n public readonly source: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for \"${source}\"`);\n this.name = \"MessageValidationError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n","import type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { extractQueue } from \"@amqp-contract/contract\";\nimport type { Channel } from \"amqplib\";\nimport { TechnicalError } from \"./errors.js\";\n\n/**\n * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.\n *\n * This function sets up the complete AMQP topology in the correct order:\n * 1. Assert all exchanges defined in the contract\n * 2. Validate dead letter exchanges are declared before referencing them\n * 3. Assert all queues with their configurations (including dead letter settings)\n * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)\n *\n * @param channel - The AMQP channel to use for topology setup\n * @param contract - The contract definition containing the topology specification\n * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created\n * @throws {TechnicalError} If a queue references a dead letter exchange not declared in the contract\n *\n * @example\n * ```typescript\n * const channel = await connection.createChannel();\n * await setupAmqpTopology(channel, contract);\n * ```\n */\nexport async function setupAmqpTopology(\n channel: Channel,\n contract: ContractDefinition,\n): Promise<void> {\n // Setup exchanges. The AMQP default exchange (name \"\") is implicit; RabbitMQ\n // does not allow asserting it, so we skip empty-named exchange entries.\n const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== \"\");\n const exchangeResults = await Promise.allSettled(\n exchanges.map((exchange) =>\n channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n }),\n ),\n );\n const exchangeErrors = exchangeResults\n .map((result, i) => ({ result, name: exchanges[i]!.name }))\n .filter(\n (entry): entry is { result: PromiseRejectedResult; name: string } =>\n entry.result.status === \"rejected\",\n );\n if (exchangeErrors.length > 0) {\n const names = exchangeErrors.map((e) => e.name).join(\", \");\n throw new AggregateError(\n exchangeErrors.map(({ result }) => result.reason),\n `Failed to setup exchanges: ${names}`,\n );\n }\n\n // Validate dead letter exchanges before setting up queues\n for (const queueEntry of Object.values(contract.queues ?? {})) {\n const queue = extractQueue(queueEntry);\n if (queue.deadLetter) {\n const dlxName = queue.deadLetter.exchange.name;\n const exchangeExists = Object.values(contract.exchanges ?? {}).some(\n (exchange) => exchange.name === dlxName,\n );\n\n if (!exchangeExists) {\n throw new TechnicalError(\n `Queue \"${queue.name}\" references dead letter exchange \"${dlxName}\" which is not declared in the contract. ` +\n `Add the exchange to contract.exchanges to ensure it is created before the queue.`,\n );\n }\n }\n }\n\n // Setup queues\n const queueEntries = Object.values(contract.queues ?? {});\n const queueResults = await Promise.allSettled(\n queueEntries.map((queueEntry) => {\n const queue = extractQueue(queueEntry);\n // Build queue arguments, merging dead letter configuration and queue type\n const queueArguments: Record<string, unknown> = { ...queue.arguments };\n\n // Set queue type\n queueArguments[\"x-queue-type\"] = queue.type;\n\n if (queue.deadLetter) {\n queueArguments[\"x-dead-letter-exchange\"] = queue.deadLetter.exchange.name;\n if (queue.deadLetter.routingKey) {\n queueArguments[\"x-dead-letter-routing-key\"] = queue.deadLetter.routingKey;\n }\n }\n\n // Handle type-specific properties using discriminated union\n if (queue.type === \"quorum\") {\n return channel.assertQueue(queue.name, {\n durable: true, // Quorum queues are always durable\n arguments: queueArguments,\n });\n }\n\n if (queue.maxPriority !== undefined) {\n queueArguments[\"x-max-priority\"] = queue.maxPriority;\n }\n\n // Classic queue\n return channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queueArguments,\n });\n }),\n );\n const queueErrors = queueResults\n .map((result, i) => ({ result, name: extractQueue(queueEntries[i]!).name }))\n .filter(\n (entry): entry is { result: PromiseRejectedResult; name: string } =>\n entry.result.status === \"rejected\",\n );\n if (queueErrors.length > 0) {\n const names = queueErrors.map((e) => e.name).join(\", \");\n throw new AggregateError(\n queueErrors.map(({ result }) => result.reason),\n `Failed to setup queues: ${names}`,\n );\n }\n\n // Setup bindings\n const bindings = Object.values(contract.bindings ?? {});\n const bindingResults = await Promise.allSettled(\n bindings.map((binding) => {\n if (binding.type === \"queue\") {\n return channel.bindQueue(\n binding.queue.name,\n binding.exchange.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n\n return channel.bindExchange(\n binding.destination.name,\n binding.source.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }),\n );\n const bindingErrors = bindingResults\n .map((result, i) => {\n const binding = bindings[i]!;\n const name =\n binding.type === \"queue\"\n ? `${binding.exchange.name} -> ${binding.queue.name}`\n : `${binding.source.name} -> ${binding.destination.name}`;\n return { result, name };\n })\n .filter(\n (entry): entry is { result: PromiseRejectedResult; name: string } =>\n entry.result.status === \"rejected\",\n );\n if (bindingErrors.length > 0) {\n const names = bindingErrors.map((e) => e.name).join(\", \");\n throw new AggregateError(\n bindingErrors.map(({ result }) => result.reason),\n `Failed to setup bindings: ${names}`,\n );\n }\n}\n","import type { ContractDefinition } from \"@amqp-contract/contract\";\nimport type {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ChannelWrapper,\n ConnectionUrl,\n CreateChannelOpts,\n} from \"amqp-connection-manager\";\nimport type { Channel, ConsumeMessage, Options } from \"amqplib\";\nimport { err, ok, Result, ResultAsync } from \"neverthrow\";\nimport { ConnectionManagerSingleton } from \"./connection-manager.js\";\nimport { TechnicalError } from \"./errors.js\";\nimport { setupAmqpTopology } from \"./setup.js\";\n\n/**\n * Invoke a SetupFunc, handling both callback-based and promise-based signatures.\n * Uses Function.length to distinguish (same approach as promise-breaker).\n * @internal\n */\nfunction callSetupFunc(\n setup: NonNullable<CreateChannelOpts[\"setup\"]>,\n channel: Channel,\n): Promise<void> {\n if (setup.length >= 2) {\n return new Promise<void>((resolve, reject) => {\n (setup as (channel: Channel, callback: (error?: Error) => void) => void)(\n channel,\n (error?: Error) => {\n if (error) reject(error);\n else resolve();\n },\n );\n });\n }\n return (setup as (channel: Channel) => Promise<void>)(channel);\n}\n\n/**\n * Default time `waitForConnect` will wait for the broker before erroring out.\n * Defaulting to a finite value (rather than waiting forever) means a fail-fast\n * developer experience: a misconfigured URL, a down broker, or wrong\n * credentials surface as an `err` within 30 seconds. Pass `null`\n * explicitly to disable the timeout — `Infinity` and other non-finite values\n * are also coerced to \"no timeout\" because Node's `setTimeout` clamps large\n * delays to ~24.8 days and silently fires near-immediately on `Infinity`.\n */\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\n\n/**\n * Normalise the user-supplied connect timeout to either a positive finite\n * number of milliseconds, or `null` (no timeout). `Infinity`, `NaN`, and\n * non-positive values all map to `null` rather than being passed to\n * `setTimeout` — see {@link DEFAULT_CONNECT_TIMEOUT_MS}.\n */\nfunction resolveConnectTimeoutMs(input: number | null | undefined): number | null {\n if (input === null) return null;\n if (input === undefined) return DEFAULT_CONNECT_TIMEOUT_MS;\n if (!Number.isFinite(input) || input <= 0) return null;\n return input;\n}\n\n/**\n * Options for creating an AMQP client.\n *\n * @property urls - AMQP broker URL(s). Multiple URLs provide failover support.\n * @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).\n * @property channelOptions - Optional channel configuration options.\n * @property connectTimeoutMs - Maximum time in ms to wait for the channel to\n * become ready in `waitForConnect`. Defaults to {@link DEFAULT_CONNECT_TIMEOUT_MS}.\n * Pass `null` to disable the timeout entirely (amqp-connection-manager will\n * retry indefinitely).\n */\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n channelOptions?: Partial<CreateChannelOpts> | undefined;\n connectTimeoutMs?: number | null | undefined;\n};\n\n/**\n * Callback type for consuming messages.\n */\nexport type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;\n\n/**\n * Publish options that extend amqplib's Options.Publish with optional timeout support.\n */\nexport type PublishOptions = Options.Publish & {\n /** Message will be rejected after timeout ms */\n timeout?: number;\n};\n\n/**\n * Consume options that extend amqplib's Options.Consume with optional prefetch support.\n */\nexport type ConsumerOptions = Options.Consume & {\n /** Number of messages to prefetch */\n prefetch?: number;\n};\n\n/**\n * AMQP client that manages connections and channels with automatic topology setup.\n *\n * This class handles:\n * - Connection management with automatic reconnection via amqp-connection-manager\n * - Connection pooling and sharing across instances with the same URLs\n * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract\n * - Channel creation with JSON serialization enabled by default\n *\n * All operations return `ResultAsync<T, TechnicalError>` for consistent error handling.\n *\n * @example\n * ```typescript\n * const client = new AmqpClient(contract, {\n * urls: ['amqp://localhost'],\n * connectionOptions: { heartbeatIntervalInSeconds: 30 }\n * });\n *\n * // Wait for connection (ResultAsync is thenable)\n * await client.waitForConnect();\n *\n * // Publish a message\n * const result = await client.publish('exchange', 'routingKey', { data: 'value' });\n *\n * // Close when done\n * await client.close();\n * ```\n */\nexport class AmqpClient {\n private readonly connection: AmqpConnectionManager;\n private readonly channelWrapper: ChannelWrapper;\n private readonly urls: ConnectionUrl[];\n private readonly connectionOptions?: AmqpConnectionManagerOptions;\n /** Resolved timeout in ms; `null` means \"wait forever\". */\n private readonly connectTimeoutMs: number | null;\n\n /**\n * Create a new AMQP client instance.\n *\n * The client will automatically:\n * - Get or create a shared connection using the singleton pattern\n * - Set up AMQP topology (exchanges, queues, bindings) from the contract\n * - Create a channel with JSON serialization enabled\n *\n * @param contract - The contract definition specifying the AMQP topology\n * @param options - Client configuration options\n */\n constructor(\n private readonly contract: ContractDefinition,\n options: AmqpClientOptions,\n ) {\n // Store for cleanup\n this.urls = options.urls;\n if (options.connectionOptions !== undefined) {\n this.connectionOptions = options.connectionOptions;\n }\n // Resolve connect timeout: explicit null disables it; undefined (the common\n // case) gets the fail-fast default. Finite positive numbers pass through;\n // any other numeric value (Infinity, NaN, ≤ 0) is coerced to null because\n // Node's `setTimeout` clamps large delays to ~24.8 days and silently fires\n // near-immediately on Infinity — neither is what a caller asking for \"no\n // timeout\" expects.\n this.connectTimeoutMs = resolveConnectTimeoutMs(options.connectTimeoutMs);\n\n // Always use singleton to get/create connection\n const singleton = ConnectionManagerSingleton.getInstance();\n this.connection = singleton.getConnection(options.urls, options.connectionOptions);\n\n // Create default setup function that calls setupAmqpTopology\n const defaultSetup = (channel: Channel) => setupAmqpTopology(channel, this.contract);\n\n // Destructure setup from channelOptions to handle it separately\n const { setup: userSetup, ...otherChannelOptions } = options.channelOptions ?? {};\n\n // Merge user-provided channel options with defaults\n const channelOpts: CreateChannelOpts = {\n confirm: true,\n json: true,\n setup: defaultSetup,\n ...otherChannelOptions,\n };\n\n // If user provided a custom setup, wrap it to call both\n if (userSetup) {\n channelOpts.setup = async (channel: Channel) => {\n await defaultSetup(channel);\n await callSetupFunc(userSetup, channel);\n };\n }\n\n this.channelWrapper = this.connection.createChannel(channelOpts);\n }\n\n /**\n * Get the underlying connection manager\n *\n * This method exposes the AmqpConnectionManager instance that this client uses.\n * The connection is automatically shared across all AmqpClient instances that\n * use the same URLs and connection options.\n *\n * @returns The AmqpConnectionManager instance used by this client\n */\n getConnection(): AmqpConnectionManager {\n return this.connection;\n }\n\n /**\n * Wait for the channel to be connected and ready.\n *\n * If `connectTimeoutMs` was provided in the constructor options, the returned\n * ResultAsync resolves to `err(TechnicalError)` once the timeout elapses.\n * Without a timeout, this waits forever — amqp-connection-manager retries\n * connections indefinitely and never errors on its own.\n *\n * NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /\n * `TypedAmqpWorker`), the constructor has already incremented the pooled\n * connection's reference count. Callers must invoke `close()` on the error\n * path to release the connection — `waitForConnect` does not do this\n * automatically. The typed factories handle this cleanup for you.\n */\n waitForConnect(): ResultAsync<void, TechnicalError> {\n const connectPromise = this.channelWrapper.waitForConnect();\n const timeoutMs = this.connectTimeoutMs;\n\n const racedPromise =\n timeoutMs === null\n ? connectPromise\n : new Promise<void>((resolve, reject) => {\n const handle = setTimeout(() => {\n reject(new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));\n }, timeoutMs);\n connectPromise.then(\n () => {\n clearTimeout(handle);\n resolve();\n },\n (error: unknown) => {\n clearTimeout(handle);\n reject(error);\n },\n );\n });\n\n return ResultAsync.fromPromise(\n racedPromise,\n (error: unknown) => new TechnicalError(\"Failed to connect to AMQP broker\", error),\n );\n }\n\n /**\n * Publish a message to an exchange.\n *\n * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.\n */\n publish(\n exchange: string,\n routingKey: string,\n content: Buffer | unknown,\n options?: PublishOptions,\n ): ResultAsync<boolean, TechnicalError> {\n return ResultAsync.fromPromise(\n this.channelWrapper.publish(exchange, routingKey, content, options),\n (error: unknown) => new TechnicalError(\"Failed to publish message\", error),\n );\n }\n\n /**\n * Publish a message directly to a queue.\n *\n * @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.\n */\n sendToQueue(\n queue: string,\n content: Buffer | unknown,\n options?: PublishOptions,\n ): ResultAsync<boolean, TechnicalError> {\n return ResultAsync.fromPromise(\n this.channelWrapper.sendToQueue(queue, content, options),\n (error: unknown) => new TechnicalError(\"Failed to publish message to queue\", error),\n );\n }\n\n /**\n * Start consuming messages from a queue.\n *\n * @returns ResultAsync resolving to the consumer tag.\n */\n consume(\n queue: string,\n callback: ConsumeCallback,\n options?: ConsumerOptions,\n ): ResultAsync<string, TechnicalError> {\n return ResultAsync.fromPromise(\n this.channelWrapper.consume(queue, callback, options),\n (error: unknown) => new TechnicalError(\"Failed to start consuming messages\", error),\n ).map((reply: { consumerTag: string }) => reply.consumerTag);\n }\n\n /**\n * Cancel a consumer by its consumer tag.\n */\n cancel(consumerTag: string): ResultAsync<void, TechnicalError> {\n return ResultAsync.fromPromise(\n this.channelWrapper.cancel(consumerTag),\n (error: unknown) => new TechnicalError(\"Failed to cancel consumer\", error),\n ).map(() => undefined);\n }\n\n /**\n * Acknowledge a message.\n *\n * @param msg - The message to acknowledge\n * @param allUpTo - If true, acknowledge all messages up to and including this one\n */\n ack(msg: ConsumeMessage, allUpTo = false): void {\n this.channelWrapper.ack(msg, allUpTo);\n }\n\n /**\n * Negative acknowledge a message.\n *\n * @param msg - The message to nack\n * @param allUpTo - If true, nack all messages up to and including this one\n * @param requeue - If true, requeue the message(s)\n */\n nack(msg: ConsumeMessage, allUpTo = false, requeue = true): void {\n this.channelWrapper.nack(msg, allUpTo, requeue);\n }\n\n /**\n * Add a setup function to be called when the channel is created or reconnected.\n *\n * This is useful for setting up channel-level configuration like prefetch.\n *\n * @param setup - The setup function to add\n */\n addSetup(setup: (channel: Channel) => void | Promise<void>): void {\n this.channelWrapper.addSetup(setup);\n }\n\n /**\n * Register an event listener on the channel wrapper.\n *\n * Available events:\n * - 'connect': Emitted when the channel is (re)connected\n * - 'close': Emitted when the channel is closed\n * - 'error': Emitted when an error occurs\n *\n * @param event - The event name\n * @param listener - The event listener\n */\n on(event: string, listener: (...args: unknown[]) => void): void {\n this.channelWrapper.on(event, listener);\n }\n\n /**\n * Close the channel and release the connection reference.\n *\n * This will:\n * - Close the channel wrapper\n * - Decrease the reference count on the shared connection\n * - Close the connection if this was the last client using it\n *\n * Both steps run regardless of each other's outcome; if both fail, the\n * errors are wrapped in an AggregateError.\n */\n close(): ResultAsync<void, TechnicalError> {\n const inner = (async (): Promise<Result<void, TechnicalError>> => {\n const channelResult = await ResultAsync.fromPromise(\n this.channelWrapper.close(),\n (error: unknown) => new TechnicalError(\"Failed to close channel\", error),\n );\n const releaseResult = await ResultAsync.fromPromise(\n ConnectionManagerSingleton.getInstance().releaseConnection(\n this.urls,\n this.connectionOptions,\n ),\n (error: unknown) => new TechnicalError(\"Failed to release connection\", error),\n );\n\n if (channelResult.isErr() && releaseResult.isErr()) {\n return err(\n new TechnicalError(\n \"Failed to close channel and release connection\",\n new AggregateError(\n [channelResult.error, releaseResult.error],\n \"Failed to close channel and release connection\",\n ),\n ),\n );\n }\n\n if (channelResult.isErr()) return channelResult;\n if (releaseResult.isErr()) return releaseResult;\n return ok(undefined);\n })();\n\n return new ResultAsync(inner);\n }\n\n /**\n * Reset connection singleton cache (for testing only)\n * @internal\n */\n static async _resetConnectionCacheForTesting(): Promise<void> {\n await ConnectionManagerSingleton.getInstance()._resetForTesting();\n }\n}\n","import { createRequire } from \"node:module\";\nimport {\n type Attributes,\n type Counter,\n type Histogram,\n type Span,\n type Tracer,\n} from \"@opentelemetry/api\";\n\n/**\n * SpanKind values from OpenTelemetry.\n * Defined as constants to avoid runtime dependency when types are used.\n * @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind\n */\nconst SpanKind = {\n /** Producer span represents a message producer */\n PRODUCER: 3,\n /** Consumer span represents a message consumer */\n CONSUMER: 4,\n} as const;\n\n/**\n * Semantic conventions for AMQP messaging following OpenTelemetry standards.\n * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/\n */\nexport const MessagingSemanticConventions = {\n // Messaging attributes\n MESSAGING_SYSTEM: \"messaging.system\",\n MESSAGING_DESTINATION: \"messaging.destination.name\",\n MESSAGING_DESTINATION_KIND: \"messaging.destination.kind\",\n MESSAGING_OPERATION: \"messaging.operation\",\n\n // AMQP/RabbitMQ specific attributes\n MESSAGING_RABBITMQ_ROUTING_KEY: \"messaging.rabbitmq.destination.routing_key\",\n MESSAGING_RABBITMQ_MESSAGE_DELIVERY_TAG: \"messaging.rabbitmq.message.delivery_tag\",\n AMQP_PUBLISHER_NAME: \"amqp.publisher.name\",\n AMQP_CONSUMER_NAME: \"amqp.consumer.name\",\n\n // Error attributes\n ERROR_TYPE: \"error.type\",\n\n // Values\n MESSAGING_SYSTEM_RABBITMQ: \"rabbitmq\",\n MESSAGING_DESTINATION_KIND_EXCHANGE: \"exchange\",\n MESSAGING_DESTINATION_KIND_QUEUE: \"queue\",\n MESSAGING_OPERATION_PUBLISH: \"publish\",\n MESSAGING_OPERATION_PROCESS: \"process\",\n} as const;\n\n/**\n * Telemetry provider for AMQP operations.\n * Uses lazy loading to gracefully handle cases where OpenTelemetry is not installed.\n */\nexport type TelemetryProvider = {\n /**\n * Get a tracer instance for creating spans.\n * Returns undefined if OpenTelemetry is not available.\n */\n getTracer: () => Tracer | undefined;\n\n /**\n * Get a counter for messages published.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishCounter: () => Counter | undefined;\n\n /**\n * Get a counter for messages consumed.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeCounter: () => Counter | undefined;\n\n /**\n * Get a histogram for publish latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishLatencyHistogram: () => Histogram | undefined;\n\n /**\n * Get a histogram for consume/process latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeLatencyHistogram: () => Histogram | undefined;\n\n /**\n * Get a counter for RPC replies that arrive after the caller has gone away\n * (timeout, cancellation, or unknown correlationId). Returns undefined if\n * OpenTelemetry is not available.\n */\n getLateRpcReplyCounter: () => Counter | undefined;\n};\n\n/**\n * Instrumentation scope name for amqp-contract.\n */\nconst INSTRUMENTATION_SCOPE_NAME = \"@amqp-contract\";\n\n/**\n * Instrumentation scope version, sourced from this package's package.json so\n * the OTel meter version always tracks the released library version. We use\n * `createRequire` rather than a JSON import attribute so the same source builds\n * to ESM, CJS, and runs under bundlers that don't yet understand\n * `import … with { type: \"json\" }`.\n */\nconst INSTRUMENTATION_SCOPE_VERSION: string = (() => {\n try {\n const localRequire = createRequire(import.meta.url);\n const pkg = localRequire(\"../package.json\") as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n})();\n\n// Cache for OpenTelemetry API module and instruments\nlet otelApi: typeof import(\"@opentelemetry/api\") | null | undefined;\nlet cachedTracer: Tracer | undefined;\nlet cachedPublishCounter: Counter | undefined;\nlet cachedConsumeCounter: Counter | undefined;\nlet cachedPublishLatencyHistogram: Histogram | undefined;\nlet cachedConsumeLatencyHistogram: Histogram | undefined;\nlet cachedLateRpcReplyCounter: Counter | undefined;\n\n/**\n * Try to load the OpenTelemetry API module.\n * Returns null if the module is not available.\n */\nfunction tryLoadOpenTelemetryApi(): typeof import(\"@opentelemetry/api\") | null {\n if (otelApi === undefined) {\n try {\n // Dynamic import using require to avoid bundler issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n otelApi = require(\"@opentelemetry/api\") as typeof import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n }\n return otelApi;\n}\n\n/**\n * Get or create a tracer instance.\n */\nfunction getTracer(): Tracer | undefined {\n if (cachedTracer !== undefined) {\n return cachedTracer;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return undefined;\n }\n\n cachedTracer = api.trace.getTracer(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n return cachedTracer;\n}\n\n/**\n * Get or create a meter and its instruments.\n */\nfunction getMeterInstruments(): {\n publishCounter: Counter | undefined;\n consumeCounter: Counter | undefined;\n publishLatencyHistogram: Histogram | undefined;\n consumeLatencyHistogram: Histogram | undefined;\n lateRpcReplyCounter: Counter | undefined;\n} {\n if (cachedPublishCounter !== undefined) {\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n lateRpcReplyCounter: cachedLateRpcReplyCounter,\n };\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return {\n publishCounter: undefined,\n consumeCounter: undefined,\n publishLatencyHistogram: undefined,\n consumeLatencyHistogram: undefined,\n lateRpcReplyCounter: undefined,\n };\n }\n\n const meter = api.metrics.getMeter(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n\n cachedPublishCounter = meter.createCounter(\"amqp.client.messages.published\", {\n description: \"Number of messages published to AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedConsumeCounter = meter.createCounter(\"amqp.worker.messages.consumed\", {\n description: \"Number of messages consumed from AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedPublishLatencyHistogram = meter.createHistogram(\"amqp.client.publish.duration\", {\n description: \"Duration of message publish operations\",\n unit: \"ms\",\n });\n\n cachedConsumeLatencyHistogram = meter.createHistogram(\"amqp.worker.process.duration\", {\n description: \"Duration of message processing operations\",\n unit: \"ms\",\n });\n\n cachedLateRpcReplyCounter = meter.createCounter(\"amqp.client.rpc.late_reply\", {\n description:\n \"RPC replies received after the caller stopped waiting (timeout, cancellation, or unknown correlationId)\",\n unit: \"{message}\",\n });\n\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n lateRpcReplyCounter: cachedLateRpcReplyCounter,\n };\n}\n\n/**\n * Default telemetry provider that uses OpenTelemetry API if available.\n */\nexport const defaultTelemetryProvider: TelemetryProvider = {\n getTracer,\n getPublishCounter: () => getMeterInstruments().publishCounter,\n getConsumeCounter: () => getMeterInstruments().consumeCounter,\n getPublishLatencyHistogram: () => getMeterInstruments().publishLatencyHistogram,\n getConsumeLatencyHistogram: () => getMeterInstruments().consumeLatencyHistogram,\n getLateRpcReplyCounter: () => getMeterInstruments().lateRpcReplyCounter,\n};\n\n/**\n * Create a span for a publish operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startPublishSpan(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${exchangeName} publish`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.PRODUCER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_EXCHANGE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PUBLISH,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n ...attributes,\n },\n });\n}\n\n/**\n * Create a span for a consume/process operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startConsumeSpan(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${queueName} process`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.CONSUMER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_QUEUE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PROCESS,\n [MessagingSemanticConventions.AMQP_CONSUMER_NAME]: consumerName,\n ...attributes,\n },\n });\n}\n\n/**\n * End a span with success status.\n */\nexport function endSpanSuccess(span: Span | undefined): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.OK });\n }\n span.end();\n}\n\n/**\n * End a span with error status.\n */\nexport function endSpanError(span: Span | undefined, error: Error): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });\n span.recordException(error);\n span.setAttribute(MessagingSemanticConventions.ERROR_TYPE, error.name);\n }\n span.end();\n}\n\n/**\n * Record a publish metric.\n */\nexport function recordPublishMetric(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n success: boolean,\n durationMs: number,\n): void {\n const publishCounter = provider.getPublishCounter();\n const publishLatencyHistogram = provider.getPublishLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n success: success,\n };\n\n publishCounter?.add(1, attributes);\n publishLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Record a consume metric.\n */\nexport function recordConsumeMetric(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n success: boolean,\n durationMs: number,\n): void {\n const consumeCounter = provider.getConsumeCounter();\n const consumeLatencyHistogram = provider.getConsumeLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n [MessagingSemanticConventions.AMQP_CONSUMER_NAME]: consumerName,\n success: success,\n };\n\n consumeCounter?.add(1, attributes);\n consumeLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Record an RPC reply that arrived after the caller stopped waiting.\n *\n * @param reason - Why the reply was orphaned. `\"unknown-correlation-id\"` is\n * the typical \"caller already timed out\" case; `\"missing-correlation-id\"`\n * means the broker delivered a reply with no correlationId at all (a\n * protocol violation by the responder).\n */\nexport function recordLateRpcReply(\n provider: TelemetryProvider,\n reason: \"unknown-correlation-id\" | \"missing-correlation-id\",\n): void {\n const counter = provider.getLateRpcReplyCounter();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n reason,\n };\n\n counter?.add(1, attributes);\n}\n\n/**\n * Reset the cached OpenTelemetry API module and instruments.\n * For testing purposes only.\n * @internal\n */\nexport function _resetTelemetryCacheForTesting(): void {\n otelApi = undefined;\n cachedTracer = undefined;\n cachedPublishCounter = undefined;\n cachedConsumeCounter = undefined;\n cachedPublishLatencyHistogram = undefined;\n cachedConsumeLatencyHistogram = undefined;\n cachedLateRpcReplyCounter = undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAa,6BAAb,MAAa,2BAA2B;CACtC,OAAe;CACf,8BAA0D,IAAI,KAAK;CACnE,4BAAyC,IAAI,KAAK;CAElD,cAAsB;;;;;;CAOtB,OAAO,cAA0C;AAC/C,MAAI,CAAC,2BAA2B,SAC9B,4BAA2B,WAAW,IAAI,4BAA4B;AAExE,SAAO,2BAA2B;;;;;;;;;;;;CAapC,cACE,MACA,mBACuB;EAEvB,MAAM,MAAM,KAAK,oBAAoB,MAAM,kBAAkB;AAE7D,MAAI,CAAC,KAAK,YAAY,IAAI,IAAI,EAAE;GAC9B,MAAM,aAAa,KAAK,QAAQ,MAAM,kBAAkB;AACxD,QAAK,YAAY,IAAI,KAAK,WAAW;AACrC,QAAK,UAAU,IAAI,KAAK,EAAE;;AAI5B,OAAK,UAAU,IAAI,MAAM,KAAK,UAAU,IAAI,IAAI,IAAI,KAAK,EAAE;AAE3D,SAAO,KAAK,YAAY,IAAI,IAAI;;;;;;;;;;;;CAalC,MAAM,kBACJ,MACA,mBACe;EACf,MAAM,MAAM,KAAK,oBAAoB,MAAM,kBAAkB;EAC7D,MAAM,WAAW,KAAK,UAAU,IAAI,IAAI,IAAI;AAE5C,MAAI,YAAY,GAAG;GAEjB,MAAM,aAAa,KAAK,YAAY,IAAI,IAAI;AAC5C,OAAI,YAAY;AACd,UAAM,WAAW,OAAO;AACxB,SAAK,YAAY,OAAO,IAAI;AAC5B,SAAK,UAAU,OAAO,IAAI;;QAI5B,MAAK,UAAU,IAAI,KAAK,WAAW,EAAE;;;;;;;;;;;;CAczC,oBACE,MACA,mBACQ;AAMR,SAAO,GAHS,KAAK,UAAU,KAGd,CAAC,IADF,oBAAoB,KAAK,iBAAiB,kBAAkB,GAAG;;;;;;;;CAUjF,iBAAyB,SAA+C;EAEtE,MAAM,SAAS,KAAK,SAAS,QAAQ;AACrC,SAAO,KAAK,UAAU,OAAO;;;;;;;;CAS/B,SAAiB,OAAyB;AACxC,MAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,SAAS,KAAK,SAAS,KAAK,CAAC;AAGjD,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;GAC/C,MAAM,MAAM;GACZ,MAAM,aAAa,OAAO,KAAK,IAAI,CAAC,MAAM;GAC1C,MAAM,SAAkC,EAAE;AAE1C,QAAK,MAAM,OAAO,WAChB,QAAO,OAAO,KAAK,SAAS,IAAI,KAAK;AAGvC,UAAO;;AAGT,SAAO;;;;;;;CAQT,gCAAwC;AACtC,SAAO,KAAK,YAAY;;;;;;CAO1B,MAAM,mBAAkC;EAEtC,MAAM,gBAAgB,MAAM,KAAK,KAAK,YAAY,QAAQ,CAAC,CAAC,KAAK,SAAS,KAAK,OAAO,CAAC;AACvF,QAAM,QAAQ,IAAI,cAAc;AAChC,OAAK,YAAY,OAAO;AACxB,OAAK,UAAU,OAAO;;;;;;;;;;;AAY1B,SAAgB,gCAAwC;AACtD,QAAO,2BAA2B,aAAa,CAAC,+BAA+B;;;;;;;AAQjF,SAAgB,8BAA6C;AAC3D,QAAO,2BAA2B,aAAa,CAAC,kBAAkB;;;;;;;;;;ACnMpE,IAAa,iBAAb,cAAoC,MAAM;CACxC,YACE,SACA,OACA;AACA,QAAM,QAAQ;AAFW,OAAA,QAAA;AAGzB,OAAK,OAAO;EAEZ,MAAM,mBAAmB;AAGzB,MAAI,OAAO,iBAAiB,sBAAsB,WAChD,kBAAiB,kBAAkB,MAAM,KAAK,YAAY;;;;;;;;;;;;AAchE,IAAa,yBAAb,cAA4C,MAAM;CAChD,YACE,QACA,QACA;AACA,QAAM,kCAAkC,OAAO,GAAG;AAHlC,OAAA,SAAA;AACA,OAAA,SAAA;AAGhB,OAAK,OAAO;EAEZ,MAAM,mBAAmB;AAGzB,MAAI,OAAO,iBAAiB,sBAAsB,WAChD,kBAAiB,kBAAkB,MAAM,KAAK,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;ACnBhE,eAAsB,kBACpB,SACA,UACe;CAGf,MAAM,YAAY,OAAO,OAAO,SAAS,aAAa,EAAE,CAAC,CAAC,QAAQ,MAAM,EAAE,SAAS,GAAG;CAWtF,MAAM,kBAAiB,MAVO,QAAQ,WACpC,UAAU,KAAK,aACb,QAAQ,eAAe,SAAS,MAAM,SAAS,MAAM;EACnD,SAAS,SAAS;EAClB,YAAY,SAAS;EACrB,UAAU,SAAS;EACnB,WAAW,SAAS;EACrB,CAAC,CACH,CACF,EAEE,KAAK,QAAQ,OAAO;EAAE;EAAQ,MAAM,UAAU,GAAI;EAAM,EAAE,CAC1D,QACE,UACC,MAAM,OAAO,WAAW,WAC3B;AACH,KAAI,eAAe,SAAS,GAAG;EAC7B,MAAM,QAAQ,eAAe,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;AAC1D,QAAM,IAAI,eACR,eAAe,KAAK,EAAE,aAAa,OAAO,OAAO,EACjD,8BAA8B,QAC/B;;AAIH,MAAK,MAAM,cAAc,OAAO,OAAO,SAAS,UAAU,EAAE,CAAC,EAAE;EAC7D,MAAM,QAAQ,aAAa,WAAW;AACtC,MAAI,MAAM,YAAY;GACpB,MAAM,UAAU,MAAM,WAAW,SAAS;AAK1C,OAAI,CAJmB,OAAO,OAAO,SAAS,aAAa,EAAE,CAAC,CAAC,MAC5D,aAAa,SAAS,SAAS,QAGf,CACjB,OAAM,IAAI,eACR,UAAU,MAAM,KAAK,qCAAqC,QAAQ,2HAEnE;;;CAMP,MAAM,eAAe,OAAO,OAAO,SAAS,UAAU,EAAE,CAAC;CAsCzD,MAAM,eAAc,MArCO,QAAQ,WACjC,aAAa,KAAK,eAAe;EAC/B,MAAM,QAAQ,aAAa,WAAW;EAEtC,MAAM,iBAA0C,EAAE,GAAG,MAAM,WAAW;AAGtE,iBAAe,kBAAkB,MAAM;AAEvC,MAAI,MAAM,YAAY;AACpB,kBAAe,4BAA4B,MAAM,WAAW,SAAS;AACrE,OAAI,MAAM,WAAW,WACnB,gBAAe,+BAA+B,MAAM,WAAW;;AAKnE,MAAI,MAAM,SAAS,SACjB,QAAO,QAAQ,YAAY,MAAM,MAAM;GACrC,SAAS;GACT,WAAW;GACZ,CAAC;AAGJ,MAAI,MAAM,gBAAgB,KAAA,EACxB,gBAAe,oBAAoB,MAAM;AAI3C,SAAO,QAAQ,YAAY,MAAM,MAAM;GACrC,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,WAAW;GACZ,CAAC;GACF,CACH,EAEE,KAAK,QAAQ,OAAO;EAAE;EAAQ,MAAM,aAAa,aAAa,GAAI,CAAC;EAAM,EAAE,CAC3E,QACE,UACC,MAAM,OAAO,WAAW,WAC3B;AACH,KAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,QAAQ,YAAY,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;AACvD,QAAM,IAAI,eACR,YAAY,KAAK,EAAE,aAAa,OAAO,OAAO,EAC9C,2BAA2B,QAC5B;;CAIH,MAAM,WAAW,OAAO,OAAO,SAAS,YAAY,EAAE,CAAC;CAoBvD,MAAM,iBAAgB,MAnBO,QAAQ,WACnC,SAAS,KAAK,YAAY;AACxB,MAAI,QAAQ,SAAS,QACnB,QAAO,QAAQ,UACb,QAAQ,MAAM,MACd,QAAQ,SAAS,MACjB,QAAQ,cAAc,IACtB,QAAQ,UACT;AAGH,SAAO,QAAQ,aACb,QAAQ,YAAY,MACpB,QAAQ,OAAO,MACf,QAAQ,cAAc,IACtB,QAAQ,UACT;GACD,CACH,EAEE,KAAK,QAAQ,MAAM;EAClB,MAAM,UAAU,SAAS;AAKzB,SAAO;GAAE;GAAQ,MAHf,QAAQ,SAAS,UACb,GAAG,QAAQ,SAAS,KAAK,MAAM,QAAQ,MAAM,SAC7C,GAAG,QAAQ,OAAO,KAAK,MAAM,QAAQ,YAAY;GAChC;GACvB,CACD,QACE,UACC,MAAM,OAAO,WAAW,WAC3B;AACH,KAAI,cAAc,SAAS,GAAG;EAC5B,MAAM,QAAQ,cAAc,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;AACzD,QAAM,IAAI,eACR,cAAc,KAAK,EAAE,aAAa,OAAO,OAAO,EAChD,6BAA6B,QAC9B;;;;;;;;;;ACnJL,SAAS,cACP,OACA,SACe;AACf,KAAI,MAAM,UAAU,EAClB,QAAO,IAAI,SAAe,SAAS,WAAW;AAC3C,QACC,UACC,UAAkB;AACjB,OAAI,MAAO,QAAO,MAAM;OACnB,UAAS;IAEjB;GACD;AAEJ,QAAQ,MAA8C,QAAQ;;;;;;;;;;;AAYhE,MAAa,6BAA6B;;;;;;;AAQ1C,SAAS,wBAAwB,OAAiD;AAChF,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,UAAU,KAAA,EAAW,QAAO;AAChC,KAAI,CAAC,OAAO,SAAS,MAAM,IAAI,SAAS,EAAG,QAAO;AAClD,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsET,IAAa,aAAb,MAAwB;CACtB;CACA;CACA;CACA;;CAEA;;;;;;;;;;;;CAaA,YACE,UACA,SACA;AAFiB,OAAA,WAAA;AAIjB,OAAK,OAAO,QAAQ;AACpB,MAAI,QAAQ,sBAAsB,KAAA,EAChC,MAAK,oBAAoB,QAAQ;AAQnC,OAAK,mBAAmB,wBAAwB,QAAQ,iBAAiB;EAGzE,MAAM,YAAY,2BAA2B,aAAa;AAC1D,OAAK,aAAa,UAAU,cAAc,QAAQ,MAAM,QAAQ,kBAAkB;EAGlF,MAAM,gBAAgB,YAAqB,kBAAkB,SAAS,KAAK,SAAS;EAGpF,MAAM,EAAE,OAAO,WAAW,GAAG,wBAAwB,QAAQ,kBAAkB,EAAE;EAGjF,MAAM,cAAiC;GACrC,SAAS;GACT,MAAM;GACN,OAAO;GACP,GAAG;GACJ;AAGD,MAAI,UACF,aAAY,QAAQ,OAAO,YAAqB;AAC9C,SAAM,aAAa,QAAQ;AAC3B,SAAM,cAAc,WAAW,QAAQ;;AAI3C,OAAK,iBAAiB,KAAK,WAAW,cAAc,YAAY;;;;;;;;;;;CAYlE,gBAAuC;AACrC,SAAO,KAAK;;;;;;;;;;;;;;;;CAiBd,iBAAoD;EAClD,MAAM,iBAAiB,KAAK,eAAe,gBAAgB;EAC3D,MAAM,YAAY,KAAK;EAEvB,MAAM,eACJ,cAAc,OACV,iBACA,IAAI,SAAe,SAAS,WAAW;GACrC,MAAM,SAAS,iBAAiB;AAC9B,2BAAO,IAAI,MAAM,+CAA+C,UAAU,IAAI,CAAC;MAC9E,UAAU;AACb,kBAAe,WACP;AACJ,iBAAa,OAAO;AACpB,aAAS;OAEV,UAAmB;AAClB,iBAAa,OAAO;AACpB,WAAO,MAAM;KAEhB;IACD;AAER,SAAO,YAAY,YACjB,eACC,UAAmB,IAAI,eAAe,oCAAoC,MAAM,CAClF;;;;;;;CAQH,QACE,UACA,YACA,SACA,SACsC;AACtC,SAAO,YAAY,YACjB,KAAK,eAAe,QAAQ,UAAU,YAAY,SAAS,QAAQ,GAClE,UAAmB,IAAI,eAAe,6BAA6B,MAAM,CAC3E;;;;;;;CAQH,YACE,OACA,SACA,SACsC;AACtC,SAAO,YAAY,YACjB,KAAK,eAAe,YAAY,OAAO,SAAS,QAAQ,GACvD,UAAmB,IAAI,eAAe,sCAAsC,MAAM,CACpF;;;;;;;CAQH,QACE,OACA,UACA,SACqC;AACrC,SAAO,YAAY,YACjB,KAAK,eAAe,QAAQ,OAAO,UAAU,QAAQ,GACpD,UAAmB,IAAI,eAAe,sCAAsC,MAAM,CACpF,CAAC,KAAK,UAAmC,MAAM,YAAY;;;;;CAM9D,OAAO,aAAwD;AAC7D,SAAO,YAAY,YACjB,KAAK,eAAe,OAAO,YAAY,GACtC,UAAmB,IAAI,eAAe,6BAA6B,MAAM,CAC3E,CAAC,UAAU,KAAA,EAAU;;;;;;;;CASxB,IAAI,KAAqB,UAAU,OAAa;AAC9C,OAAK,eAAe,IAAI,KAAK,QAAQ;;;;;;;;;CAUvC,KAAK,KAAqB,UAAU,OAAO,UAAU,MAAY;AAC/D,OAAK,eAAe,KAAK,KAAK,SAAS,QAAQ;;;;;;;;;CAUjD,SAAS,OAAyD;AAChE,OAAK,eAAe,SAAS,MAAM;;;;;;;;;;;;;CAcrC,GAAG,OAAe,UAA8C;AAC9D,OAAK,eAAe,GAAG,OAAO,SAAS;;;;;;;;;;;;;CAczC,QAA2C;AA+BzC,SAAO,IAAI,aA9BI,YAAmD;GAChE,MAAM,gBAAgB,MAAM,YAAY,YACtC,KAAK,eAAe,OAAO,GAC1B,UAAmB,IAAI,eAAe,2BAA2B,MAAM,CACzE;GACD,MAAM,gBAAgB,MAAM,YAAY,YACtC,2BAA2B,aAAa,CAAC,kBACvC,KAAK,MACL,KAAK,kBACN,GACA,UAAmB,IAAI,eAAe,gCAAgC,MAAM,CAC9E;AAED,OAAI,cAAc,OAAO,IAAI,cAAc,OAAO,CAChD,QAAO,IACL,IAAI,eACF,kDACA,IAAI,eACF,CAAC,cAAc,OAAO,cAAc,MAAM,EAC1C,iDACD,CACF,CACF;AAGH,OAAI,cAAc,OAAO,CAAE,QAAO;AAClC,OAAI,cAAc,OAAO,CAAE,QAAO;AAClC,UAAO,GAAG,KAAA,EAAU;MAGM,CAAC;;;;;;CAO/B,aAAa,kCAAiD;AAC5D,QAAM,2BAA2B,aAAa,CAAC,kBAAkB;;;;;;;;;;ACvYrE,MAAM,WAAW;;CAEf,UAAU;;CAEV,UAAU;CACX;;;;;AAMD,MAAa,+BAA+B;CAE1C,kBAAkB;CAClB,uBAAuB;CACvB,4BAA4B;CAC5B,qBAAqB;CAGrB,gCAAgC;CAChC,yCAAyC;CACzC,qBAAqB;CACrB,oBAAoB;CAGpB,YAAY;CAGZ,2BAA2B;CAC3B,qCAAqC;CACrC,kCAAkC;CAClC,6BAA6B;CAC7B,6BAA6B;CAC9B;;;;AAgDD,MAAM,6BAA6B;;;;;;;;AASnC,MAAM,uCAA+C;AACnD,KAAI;AAGF,SAFqB,cAAc,OAAO,KAAK,IACvB,CAAC,kBACf,CAAC,WAAW;SAChB;AACN,SAAO;;IAEP;AAGJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;;;;;AAMJ,SAAS,0BAAsE;AAC7E,KAAI,YAAY,KAAA,EACd,KAAI;AAGF,YAAA,UAAkB,qBAAqB;SACjC;AACN,YAAU;;AAGd,QAAO;;;;;AAMT,SAAS,YAAgC;AACvC,KAAI,iBAAiB,KAAA,EACnB,QAAO;CAGT,MAAM,MAAM,yBAAyB;AACrC,KAAI,CAAC,IACH;AAGF,gBAAe,IAAI,MAAM,UAAU,4BAA4B,8BAA8B;AAC7F,QAAO;;;;;AAMT,SAAS,sBAMP;AACA,KAAI,yBAAyB,KAAA,EAC3B,QAAO;EACL,gBAAgB;EAChB,gBAAgB;EAChB,yBAAyB;EACzB,yBAAyB;EACzB,qBAAqB;EACtB;CAGH,MAAM,MAAM,yBAAyB;AACrC,KAAI,CAAC,IACH,QAAO;EACL,gBAAgB,KAAA;EAChB,gBAAgB,KAAA;EAChB,yBAAyB,KAAA;EACzB,yBAAyB,KAAA;EACzB,qBAAqB,KAAA;EACtB;CAGH,MAAM,QAAQ,IAAI,QAAQ,SAAS,4BAA4B,8BAA8B;AAE7F,wBAAuB,MAAM,cAAc,kCAAkC;EAC3E,aAAa;EACb,MAAM;EACP,CAAC;AAEF,wBAAuB,MAAM,cAAc,iCAAiC;EAC1E,aAAa;EACb,MAAM;EACP,CAAC;AAEF,iCAAgC,MAAM,gBAAgB,gCAAgC;EACpF,aAAa;EACb,MAAM;EACP,CAAC;AAEF,iCAAgC,MAAM,gBAAgB,gCAAgC;EACpF,aAAa;EACb,MAAM;EACP,CAAC;AAEF,6BAA4B,MAAM,cAAc,8BAA8B;EAC5E,aACE;EACF,MAAM;EACP,CAAC;AAEF,QAAO;EACL,gBAAgB;EAChB,gBAAgB;EAChB,yBAAyB;EACzB,yBAAyB;EACzB,qBAAqB;EACtB;;;;;AAMH,MAAa,2BAA8C;CACzD;CACA,yBAAyB,qBAAqB,CAAC;CAC/C,yBAAyB,qBAAqB,CAAC;CAC/C,kCAAkC,qBAAqB,CAAC;CACxD,kCAAkC,qBAAqB,CAAC;CACxD,8BAA8B,qBAAqB,CAAC;CACrD;;;;;AAMD,SAAgB,iBACd,UACA,cACA,YACA,YACkB;CAClB,MAAM,SAAS,SAAS,WAAW;AACnC,KAAI,CAAC,OACH;CAGF,MAAM,WAAW,GAAG,aAAa;AAEjC,QAAO,OAAO,UAAU,UAAU;EAChC,MAAM,SAAS;EACf,YAAY;IACT,6BAA6B,mBAC5B,6BAA6B;IAC9B,6BAA6B,wBAAwB;IACrD,6BAA6B,6BAC5B,6BAA6B;IAC9B,6BAA6B,sBAC5B,6BAA6B;GAC/B,GAAI,aACA,GAAG,6BAA6B,iCAAiC,YAAY,GAC7E,EAAE;GACN,GAAG;GACJ;EACF,CAAC;;;;;;AAOJ,SAAgB,iBACd,UACA,WACA,cACA,YACkB;CAClB,MAAM,SAAS,SAAS,WAAW;AACnC,KAAI,CAAC,OACH;CAGF,MAAM,WAAW,GAAG,UAAU;AAE9B,QAAO,OAAO,UAAU,UAAU;EAChC,MAAM,SAAS;EACf,YAAY;IACT,6BAA6B,mBAC5B,6BAA6B;IAC9B,6BAA6B,wBAAwB;IACrD,6BAA6B,6BAC5B,6BAA6B;IAC9B,6BAA6B,sBAC5B,6BAA6B;IAC9B,6BAA6B,qBAAqB;GACnD,GAAG;GACJ;EACF,CAAC;;;;;AAMJ,SAAgB,eAAe,MAA8B;AAC3D,KAAI,CAAC,KACH;CAGF,MAAM,MAAM,yBAAyB;AACrC,KAAI,IACF,MAAK,UAAU,EAAE,MAAM,IAAI,eAAe,IAAI,CAAC;AAEjD,MAAK,KAAK;;;;;AAMZ,SAAgB,aAAa,MAAwB,OAAoB;AACvE,KAAI,CAAC,KACH;CAGF,MAAM,MAAM,yBAAyB;AACrC,KAAI,KAAK;AACP,OAAK,UAAU;GAAE,MAAM,IAAI,eAAe;GAAO,SAAS,MAAM;GAAS,CAAC;AAC1E,OAAK,gBAAgB,MAAM;AAC3B,OAAK,aAAa,6BAA6B,YAAY,MAAM,KAAK;;AAExE,MAAK,KAAK;;;;;AAMZ,SAAgB,oBACd,UACA,cACA,YACA,SACA,YACM;CACN,MAAM,iBAAiB,SAAS,mBAAmB;CACnD,MAAM,0BAA0B,SAAS,4BAA4B;CAErE,MAAM,aAAyB;GAC5B,6BAA6B,mBAC5B,6BAA6B;GAC9B,6BAA6B,wBAAwB;EACtD,GAAI,aACA,GAAG,6BAA6B,iCAAiC,YAAY,GAC7E,EAAE;EACG;EACV;AAED,iBAAgB,IAAI,GAAG,WAAW;AAClC,0BAAyB,OAAO,YAAY,WAAW;;;;;AAMzD,SAAgB,oBACd,UACA,WACA,cACA,SACA,YACM;CACN,MAAM,iBAAiB,SAAS,mBAAmB;CACnD,MAAM,0BAA0B,SAAS,4BAA4B;CAErE,MAAM,aAAyB;GAC5B,6BAA6B,mBAC5B,6BAA6B;GAC9B,6BAA6B,wBAAwB;GACrD,6BAA6B,qBAAqB;EAC1C;EACV;AAED,iBAAgB,IAAI,GAAG,WAAW;AAClC,0BAAyB,OAAO,YAAY,WAAW;;;;;;;;;;AAWzD,SAAgB,mBACd,UACA,QACM;CACN,MAAM,UAAU,SAAS,wBAAwB;CAEjD,MAAM,aAAyB;GAC5B,6BAA6B,mBAC5B,6BAA6B;EAC/B;EACD;AAED,UAAS,IAAI,GAAG,WAAW;;;;;;;AAQ7B,SAAgB,iCAAuC;AACrD,WAAU,KAAA;AACV,gBAAe,KAAA;AACf,wBAAuB,KAAA;AACvB,wBAAuB,KAAA;AACvB,iCAAgC,KAAA;AAChC,iCAAgC,KAAA;AAChC,6BAA4B,KAAA"}
|