@warlock.js/herald 4.0.100

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.
Files changed (82) hide show
  1. package/README.md +364 -0
  2. package/cjs/communicators/communicator-registry.d.ts +155 -0
  3. package/cjs/communicators/communicator-registry.d.ts.map +1 -0
  4. package/cjs/communicators/communicator-registry.js +206 -0
  5. package/cjs/communicators/communicator-registry.js.map +1 -0
  6. package/cjs/communicators/communicator.d.ts +90 -0
  7. package/cjs/communicators/communicator.d.ts.map +1 -0
  8. package/cjs/communicators/communicator.js +93 -0
  9. package/cjs/communicators/communicator.js.map +1 -0
  10. package/cjs/communicators/index.d.ts +3 -0
  11. package/cjs/communicators/index.d.ts.map +1 -0
  12. package/cjs/contracts/channel.contract.d.ts +175 -0
  13. package/cjs/contracts/channel.contract.d.ts.map +1 -0
  14. package/cjs/contracts/communicator-driver.contract.d.ts +168 -0
  15. package/cjs/contracts/communicator-driver.contract.d.ts.map +1 -0
  16. package/cjs/contracts/index.d.ts +3 -0
  17. package/cjs/contracts/index.d.ts.map +1 -0
  18. package/cjs/drivers/index.d.ts +2 -0
  19. package/cjs/drivers/index.d.ts.map +1 -0
  20. package/cjs/drivers/rabbitmq/index.d.ts +3 -0
  21. package/cjs/drivers/rabbitmq/index.d.ts.map +1 -0
  22. package/cjs/drivers/rabbitmq/rabbitmq-channel.d.ts +70 -0
  23. package/cjs/drivers/rabbitmq/rabbitmq-channel.d.ts.map +1 -0
  24. package/cjs/drivers/rabbitmq/rabbitmq-channel.js +400 -0
  25. package/cjs/drivers/rabbitmq/rabbitmq-channel.js.map +1 -0
  26. package/cjs/drivers/rabbitmq/rabbitmq-driver.d.ts +100 -0
  27. package/cjs/drivers/rabbitmq/rabbitmq-driver.d.ts.map +1 -0
  28. package/cjs/drivers/rabbitmq/rabbitmq-driver.js +299 -0
  29. package/cjs/drivers/rabbitmq/rabbitmq-driver.js.map +1 -0
  30. package/cjs/index.d.ts +45 -0
  31. package/cjs/index.d.ts.map +1 -0
  32. package/cjs/index.js +1 -0
  33. package/cjs/index.js.map +1 -0
  34. package/cjs/types.d.ts +396 -0
  35. package/cjs/types.d.ts.map +1 -0
  36. package/cjs/utils/connect-to-communicator.d.ts +86 -0
  37. package/cjs/utils/connect-to-communicator.d.ts.map +1 -0
  38. package/cjs/utils/connect-to-communicator.js +122 -0
  39. package/cjs/utils/connect-to-communicator.js.map +1 -0
  40. package/cjs/utils/index.d.ts +2 -0
  41. package/cjs/utils/index.d.ts.map +1 -0
  42. package/esm/communicators/communicator-registry.d.ts +155 -0
  43. package/esm/communicators/communicator-registry.d.ts.map +1 -0
  44. package/esm/communicators/communicator-registry.js +206 -0
  45. package/esm/communicators/communicator-registry.js.map +1 -0
  46. package/esm/communicators/communicator.d.ts +90 -0
  47. package/esm/communicators/communicator.d.ts.map +1 -0
  48. package/esm/communicators/communicator.js +93 -0
  49. package/esm/communicators/communicator.js.map +1 -0
  50. package/esm/communicators/index.d.ts +3 -0
  51. package/esm/communicators/index.d.ts.map +1 -0
  52. package/esm/contracts/channel.contract.d.ts +175 -0
  53. package/esm/contracts/channel.contract.d.ts.map +1 -0
  54. package/esm/contracts/communicator-driver.contract.d.ts +168 -0
  55. package/esm/contracts/communicator-driver.contract.d.ts.map +1 -0
  56. package/esm/contracts/index.d.ts +3 -0
  57. package/esm/contracts/index.d.ts.map +1 -0
  58. package/esm/drivers/index.d.ts +2 -0
  59. package/esm/drivers/index.d.ts.map +1 -0
  60. package/esm/drivers/rabbitmq/index.d.ts +3 -0
  61. package/esm/drivers/rabbitmq/index.d.ts.map +1 -0
  62. package/esm/drivers/rabbitmq/rabbitmq-channel.d.ts +70 -0
  63. package/esm/drivers/rabbitmq/rabbitmq-channel.d.ts.map +1 -0
  64. package/esm/drivers/rabbitmq/rabbitmq-channel.js +400 -0
  65. package/esm/drivers/rabbitmq/rabbitmq-channel.js.map +1 -0
  66. package/esm/drivers/rabbitmq/rabbitmq-driver.d.ts +100 -0
  67. package/esm/drivers/rabbitmq/rabbitmq-driver.d.ts.map +1 -0
  68. package/esm/drivers/rabbitmq/rabbitmq-driver.js +299 -0
  69. package/esm/drivers/rabbitmq/rabbitmq-driver.js.map +1 -0
  70. package/esm/index.d.ts +45 -0
  71. package/esm/index.d.ts.map +1 -0
  72. package/esm/index.js +1 -0
  73. package/esm/index.js.map +1 -0
  74. package/esm/types.d.ts +396 -0
  75. package/esm/types.d.ts.map +1 -0
  76. package/esm/utils/connect-to-communicator.d.ts +86 -0
  77. package/esm/utils/connect-to-communicator.d.ts.map +1 -0
  78. package/esm/utils/connect-to-communicator.js +122 -0
  79. package/esm/utils/connect-to-communicator.js.map +1 -0
  80. package/esm/utils/index.d.ts +2 -0
  81. package/esm/utils/index.d.ts.map +1 -0
  82. package/package.json +47 -0
@@ -0,0 +1,168 @@
1
+ import type { ChannelContract } from "./channel.contract";
2
+ import type { ChannelOptions, CommunicatorEvent, CommunicatorEventListener, CommunicatorDriverType, HealthCheckResult } from "../types";
3
+ /**
4
+ * Communicator Driver Contract
5
+ *
6
+ * Base contract for all message bus drivers (RabbitMQ, Kafka, etc.)
7
+ * Similar to DriverContract in @warlock.js/cascade
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Driver implementation
12
+ * class RabbitMQDriver implements CommunicatorDriverContract {
13
+ * readonly name = "rabbitmq";
14
+ * // ...
15
+ * }
16
+ * ```
17
+ */
18
+ export interface CommunicatorDriverContract {
19
+ /**
20
+ * Driver name identifier
21
+ *
22
+ * @example "rabbitmq", "kafka", "redis-streams"
23
+ */
24
+ readonly name: CommunicatorDriverType;
25
+ /**
26
+ * Whether currently connected to the message broker
27
+ */
28
+ readonly isConnected: boolean;
29
+ /**
30
+ * Establish connection to the message broker
31
+ *
32
+ * @throws Error if connection fails
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * await driver.connect();
37
+ * console.log("Connected to RabbitMQ");
38
+ * ```
39
+ */
40
+ connect(): Promise<void>;
41
+ /**
42
+ * Close connection gracefully
43
+ *
44
+ * Ensures all pending operations complete before disconnecting.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * await driver.disconnect();
49
+ * ```
50
+ */
51
+ disconnect(): Promise<void>;
52
+ /**
53
+ * Register event listeners for driver lifecycle events
54
+ *
55
+ * @param event - Event name to listen for
56
+ * @param listener - Callback function
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * driver.on("connected", () => {
61
+ * console.log("Connected to broker");
62
+ * });
63
+ *
64
+ * driver.on("disconnected", () => {
65
+ * console.log("Disconnected from broker");
66
+ * });
67
+ *
68
+ * driver.on("error", (error) => {
69
+ * console.error("Driver error:", error);
70
+ * });
71
+ *
72
+ * driver.on("reconnecting", (attempt) => {
73
+ * console.log(`Reconnection attempt ${attempt}`);
74
+ * });
75
+ * ```
76
+ */
77
+ on(event: CommunicatorEvent, listener: CommunicatorEventListener): void;
78
+ /**
79
+ * Remove an event listener
80
+ *
81
+ * @param event - Event name
82
+ * @param listener - Callback to remove
83
+ */
84
+ off(event: CommunicatorEvent, listener: CommunicatorEventListener): void;
85
+ /**
86
+ * Get or create a channel
87
+ *
88
+ * Channels are lazy-created and cached for reuse.
89
+ *
90
+ * @param name - Channel/queue/topic name
91
+ * @param options - Channel configuration
92
+ * @returns Channel instance
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // Simple channel
97
+ * const channel = driver.channel("user.created");
98
+ *
99
+ * // With options
100
+ * const orderChannel = driver.channel("orders", {
101
+ * durable: true,
102
+ * deadLetter: { channel: "orders.failed" },
103
+ * });
104
+ *
105
+ * // Typed channel
106
+ * const typedChannel = driver.channel<OrderPayload>("orders", {
107
+ * schema: OrderSchema,
108
+ * });
109
+ * ```
110
+ */
111
+ channel<TPayload = unknown>(name: string, options?: ChannelOptions<TPayload>): ChannelContract<TPayload>;
112
+ /**
113
+ * Start consuming messages from all subscribed channels
114
+ *
115
+ * Call this after setting up all subscriptions to begin processing.
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * // Set up subscriptions
120
+ * channel1.subscribe(handler1);
121
+ * channel2.subscribe(handler2);
122
+ *
123
+ * // Start consuming
124
+ * await driver.startConsuming();
125
+ * ```
126
+ */
127
+ startConsuming(): Promise<void>;
128
+ /**
129
+ * Stop consuming messages gracefully
130
+ *
131
+ * Waits for currently processing messages to complete.
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * await driver.stopConsuming();
136
+ * ```
137
+ */
138
+ stopConsuming(): Promise<void>;
139
+ /**
140
+ * Perform a health check on the connection
141
+ *
142
+ * @returns Health check result with status and optional latency
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const health = await driver.healthCheck();
147
+ * if (health.healthy) {
148
+ * console.log(`Healthy, latency: ${health.latency}ms`);
149
+ * } else {
150
+ * console.error(`Unhealthy: ${health.error}`);
151
+ * }
152
+ * ```
153
+ */
154
+ healthCheck(): Promise<HealthCheckResult>;
155
+ /**
156
+ * Get list of all channels managed by this driver
157
+ *
158
+ * @returns Array of channel names
159
+ */
160
+ getChannelNames(): string[];
161
+ /**
162
+ * Close and remove a specific channel
163
+ *
164
+ * @param name - Channel name to close
165
+ */
166
+ closeChannel(name: string): Promise<void>;
167
+ }
168
+ //# sourceMappingURL=communicator-driver.contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"communicator-driver.contract.d.ts","sourceRoot":"","sources":["../../src/contracts/communicator-driver.contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAEtC;;OAEG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B;;;;;;;;;;OAUG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB;;;;;;;;;OASG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAExE;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAEzE;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,QAAQ,GAAG,OAAO,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,GACjC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAE7B;;;;;;;;;;;;;;OAcG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;;;;;;;;OASG;IACH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/B;;;;;;;;;;;;;;OAcG;IACH,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAE1C;;;;OAIG;IACH,eAAe,IAAI,MAAM,EAAE,CAAC;IAE5B;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C"}
@@ -0,0 +1,3 @@
1
+ export * from "./channel.contract";
2
+ export * from "./communicator-driver.contract";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./rabbitmq";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/drivers/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./rabbitmq-driver";
2
+ export * from "./rabbitmq-channel";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/drivers/rabbitmq/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,70 @@
1
+ import type { ChannelContract } from "../../contracts";
2
+ import type { ChannelOptions, ChannelStats, MessageHandler, PublishOptions, RequestOptions, ResponseHandler, SubscribeOptions, Subscription } from "../../types";
3
+ /**
4
+ * RabbitMQ Channel Implementation
5
+ *
6
+ * Wraps a RabbitMQ queue/exchange with a unified API.
7
+ *
8
+ * @template TPayload - The typed payload
9
+ */
10
+ export declare class RabbitMQChannel<TPayload = unknown> implements ChannelContract<TPayload> {
11
+ readonly name: string;
12
+ readonly options: ChannelOptions<TPayload>;
13
+ private readonly amqpChannel;
14
+ private readonly subscriptions;
15
+ private asserted;
16
+ /**
17
+ * Create a new RabbitMQ channel
18
+ */
19
+ constructor(name: string, amqpChannel: any, options?: ChannelOptions<TPayload>);
20
+ /**
21
+ * Assert the queue exists
22
+ */
23
+ assert(): Promise<void>;
24
+ /**
25
+ * Publish a message
26
+ */
27
+ publish(payload: TPayload, options?: PublishOptions): Promise<void>;
28
+ /**
29
+ * Publish multiple messages
30
+ */
31
+ publishBatch(messages: TPayload[], options?: PublishOptions): Promise<void>;
32
+ /**
33
+ * Subscribe to messages
34
+ *
35
+ * Smart auto-ack behavior (when autoAck is not true):
36
+ * - If handler completes successfully without explicit ack/nack/reject → auto-ack
37
+ * - If handler throws an error → auto-nack (with retry if configured)
38
+ * - If handler explicitly calls ack/nack/reject → respects that call
39
+ */
40
+ subscribe(handler: MessageHandler<TPayload>, options?: SubscribeOptions): Promise<Subscription>;
41
+ /**
42
+ * Send message to dead-letter queue
43
+ */
44
+ private sendToDeadLetter;
45
+ /**
46
+ * Request-response pattern
47
+ */
48
+ request<TResponse = unknown>(payload: TPayload, options?: RequestOptions): Promise<TResponse>;
49
+ /**
50
+ * Register response handler for RPC
51
+ */
52
+ respond<TResponse = unknown>(handler: ResponseHandler<TPayload, TResponse>): Promise<Subscription>;
53
+ /**
54
+ * Get queue statistics
55
+ */
56
+ stats(): Promise<ChannelStats>;
57
+ /**
58
+ * Purge all messages
59
+ */
60
+ purge(): Promise<number>;
61
+ /**
62
+ * Check if queue exists
63
+ */
64
+ exists(): Promise<boolean>;
65
+ /**
66
+ * Delete the queue
67
+ */
68
+ delete(): Promise<void>;
69
+ }
70
+ //# sourceMappingURL=rabbitmq-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rabbitmq-channel.d.ts","sourceRoot":"","sources":["../../../src/drivers/rabbitmq/rabbitmq-channel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EAGZ,cAAc,EAEd,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,YAAY,EACb,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,qBAAa,eAAe,CAAC,QAAQ,GAAG,OAAO,CAAE,YAAW,eAAe,CAAC,QAAQ,CAAC;IACnF,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IAElD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAM;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2C;IACzE,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;gBACgB,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC;IAMrF;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBpC;;OAEG;IACU,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDhF;;OAEG;IACU,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxF;;;;;;;OAOG;IACU,SAAS,CACpB,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC,EACjC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,YAAY,CAAC;IAmLxB;;OAEG;YACW,gBAAgB;IAe9B;;OAEG;IACU,OAAO,CAAC,SAAS,GAAG,OAAO,EACtC,OAAO,EAAE,QAAQ,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,SAAS,CAAC;IAiDrB;;OAEG;IACU,OAAO,CAAC,SAAS,GAAG,OAAO,EACtC,OAAO,EAAE,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,GAC5C,OAAO,CAAC,YAAY,CAAC;IAQxB;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC;IAY3C;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAOrC;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IASvC;;OAEG;IACU,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAerC"}
@@ -0,0 +1,400 @@
1
+ 'use strict';var seal=require('@warlock.js/seal'),node_crypto=require('node:crypto');/**
2
+ * RabbitMQ Channel Implementation
3
+ *
4
+ * Wraps a RabbitMQ queue/exchange with a unified API.
5
+ *
6
+ * @template TPayload - The typed payload
7
+ */
8
+ class RabbitMQChannel {
9
+ name;
10
+ options;
11
+ amqpChannel;
12
+ subscriptions = new Map();
13
+ asserted = false;
14
+ /**
15
+ * Create a new RabbitMQ channel
16
+ */
17
+ constructor(name, amqpChannel, options) {
18
+ this.name = name;
19
+ this.amqpChannel = amqpChannel;
20
+ this.options = options ?? {};
21
+ }
22
+ /**
23
+ * Assert the queue exists
24
+ */
25
+ async assert() {
26
+ if (this.asserted)
27
+ return;
28
+ const queueOptions = {
29
+ durable: this.options.durable ?? true,
30
+ autoDelete: this.options.autoDelete ?? false,
31
+ exclusive: this.options.exclusive ?? false,
32
+ messageTtl: this.options.messageTtl,
33
+ maxLength: this.options.maxLength,
34
+ deadLetterExchange: this.options.deadLetter?.channel ? "" : undefined,
35
+ deadLetterRoutingKey: this.options.deadLetter?.channel,
36
+ };
37
+ await this.amqpChannel.assertQueue(this.name, queueOptions);
38
+ this.asserted = true;
39
+ }
40
+ /**
41
+ * Publish a message
42
+ */
43
+ async publish(payload, options) {
44
+ await this.assert();
45
+ // Validate with schema if provided
46
+ if (this.options.schema) {
47
+ const context = {
48
+ allValues: payload,
49
+ value: payload,
50
+ };
51
+ const result = await seal.v.validate(this.options.schema, payload, { context });
52
+ if (!result.isValid) {
53
+ throw new Error(`Message validation failed: ${JSON.stringify(result.errors)}`);
54
+ }
55
+ payload = result.data;
56
+ }
57
+ const messageId = node_crypto.randomUUID();
58
+ const timestamp = new Date();
59
+ const messageContent = JSON.stringify({
60
+ payload,
61
+ metadata: {
62
+ messageId,
63
+ timestamp: timestamp.toISOString(),
64
+ correlationId: options?.correlationId,
65
+ headers: options?.headers,
66
+ },
67
+ });
68
+ const publishOptions = {
69
+ persistent: options?.persistent ?? true,
70
+ messageId,
71
+ timestamp: timestamp.getTime(),
72
+ correlationId: options?.correlationId,
73
+ expiration: options?.expiration?.toString(),
74
+ priority: options?.priority,
75
+ headers: options?.headers,
76
+ };
77
+ // Handle delayed messages (requires rabbitmq-delayed-message-exchange plugin)
78
+ if (options?.delay) {
79
+ publishOptions.headers = {
80
+ ...publishOptions.headers,
81
+ "x-delay": options.delay,
82
+ };
83
+ }
84
+ this.amqpChannel.sendToQueue(this.name, Buffer.from(messageContent), publishOptions);
85
+ }
86
+ /**
87
+ * Publish multiple messages
88
+ */
89
+ async publishBatch(messages, options) {
90
+ for (const payload of messages) {
91
+ await this.publish(payload, options);
92
+ }
93
+ }
94
+ /**
95
+ * Subscribe to messages
96
+ *
97
+ * Smart auto-ack behavior (when autoAck is not true):
98
+ * - If handler completes successfully without explicit ack/nack/reject → auto-ack
99
+ * - If handler throws an error → auto-nack (with retry if configured)
100
+ * - If handler explicitly calls ack/nack/reject → respects that call
101
+ */
102
+ async subscribe(handler, options) {
103
+ await this.assert();
104
+ const subscriptionId = node_crypto.randomUUID();
105
+ // Set prefetch if specified
106
+ if (options?.prefetch) {
107
+ await this.amqpChannel.prefetch(options.prefetch);
108
+ }
109
+ // If autoAck is true, RabbitMQ handles ack immediately (fire-and-forget)
110
+ const isFireAndForget = options?.autoAck === true;
111
+ const consumerOptions = {
112
+ noAck: isFireAndForget,
113
+ exclusive: options?.exclusive ?? false,
114
+ consumerTag: options?.group ?? subscriptionId,
115
+ };
116
+ const { consumerTag } = await this.amqpChannel.consume(this.name, async (msg) => {
117
+ if (!msg)
118
+ return;
119
+ // Track if acknowledgment was handled explicitly
120
+ let ackHandled = isFireAndForget;
121
+ try {
122
+ const content = JSON.parse(msg.content.toString());
123
+ let payload = content.payload;
124
+ // Validate with schema if provided
125
+ if (this.options.schema) {
126
+ const schemaContext = {
127
+ allValues: payload,
128
+ parent: null,
129
+ value: payload,
130
+ key: "",
131
+ path: "",
132
+ translateRule: (t) => t.rule?.defaultErrorMessage ?? "Validation failed",
133
+ translateAttribute: (t) => t.attribute ?? "",
134
+ };
135
+ const result = await this.options.schema.validate(payload, schemaContext);
136
+ if (!result.isValid) {
137
+ // Reject invalid messages
138
+ this.amqpChannel.nack(msg, false, false);
139
+ return;
140
+ }
141
+ payload = result.data;
142
+ }
143
+ const metadata = {
144
+ messageId: msg.properties.messageId || content.metadata?.messageId || node_crypto.randomUUID(),
145
+ timestamp: new Date(msg.properties.timestamp || content.metadata?.timestamp),
146
+ correlationId: msg.properties.correlationId || content.metadata?.correlationId,
147
+ replyTo: msg.properties.replyTo,
148
+ priority: msg.properties.priority,
149
+ headers: msg.properties.headers,
150
+ retryCount: msg.properties.headers?.["x-retry-count"] || 0,
151
+ originalChannel: this.name,
152
+ };
153
+ const message = {
154
+ metadata,
155
+ payload,
156
+ raw: msg,
157
+ };
158
+ const context = {
159
+ ack: async () => {
160
+ if (!ackHandled) {
161
+ ackHandled = true;
162
+ this.amqpChannel.ack(msg);
163
+ }
164
+ },
165
+ nack: async (requeue = true) => {
166
+ if (!ackHandled) {
167
+ ackHandled = true;
168
+ this.amqpChannel.nack(msg, false, requeue);
169
+ }
170
+ },
171
+ reject: async () => {
172
+ if (!ackHandled) {
173
+ ackHandled = true;
174
+ this.amqpChannel.reject(msg, false);
175
+ }
176
+ },
177
+ reply: async (replyPayload) => {
178
+ if (msg.properties.replyTo) {
179
+ const replyContent = JSON.stringify({
180
+ payload: replyPayload,
181
+ metadata: {
182
+ messageId: node_crypto.randomUUID(),
183
+ timestamp: new Date().toISOString(),
184
+ correlationId: msg.properties.correlationId,
185
+ },
186
+ });
187
+ this.amqpChannel.sendToQueue(msg.properties.replyTo, Buffer.from(replyContent), {
188
+ correlationId: msg.properties.correlationId,
189
+ });
190
+ }
191
+ },
192
+ retry: async (delay) => {
193
+ if (ackHandled)
194
+ return;
195
+ ackHandled = true;
196
+ const retryCount = (metadata.retryCount || 0) + 1;
197
+ const maxRetries = options?.retry?.maxRetries ?? 3;
198
+ if (retryCount > maxRetries) {
199
+ // Send to dead-letter if configured
200
+ if (options?.deadLetter) {
201
+ await this.sendToDeadLetter(message, options.deadLetter.channel);
202
+ }
203
+ this.amqpChannel.ack(msg);
204
+ return;
205
+ }
206
+ // Republish with retry count
207
+ const headers = {
208
+ ...msg.properties.headers,
209
+ "x-retry-count": retryCount,
210
+ };
211
+ if (delay) {
212
+ headers["x-delay"] = delay;
213
+ }
214
+ this.amqpChannel.sendToQueue(this.name, msg.content, { ...msg.properties, headers });
215
+ this.amqpChannel.ack(msg);
216
+ },
217
+ };
218
+ // Execute handler
219
+ await handler(message, context);
220
+ // Smart auto-ack: if handler succeeded and didn't explicitly handle ack
221
+ if (!ackHandled) {
222
+ this.amqpChannel.ack(msg);
223
+ }
224
+ }
225
+ catch (error) {
226
+ // Smart auto-nack: if handler threw and didn't explicitly handle ack
227
+ if (ackHandled)
228
+ return;
229
+ // Handle errors - nack and potentially retry
230
+ if (options?.retry) {
231
+ const retryCount = msg.properties.headers?.["x-retry-count"] || 0;
232
+ if (retryCount < options.retry.maxRetries) {
233
+ // Requeue for retry
234
+ this.amqpChannel.nack(msg, false, true);
235
+ }
236
+ else if (options.deadLetter) {
237
+ // Send to dead-letter
238
+ this.amqpChannel.nack(msg, false, false);
239
+ }
240
+ else {
241
+ this.amqpChannel.reject(msg, false);
242
+ }
243
+ }
244
+ else {
245
+ // No retry configured - reject without requeue
246
+ this.amqpChannel.nack(msg, false, false);
247
+ }
248
+ }
249
+ }, consumerOptions);
250
+ const subscription = new RabbitMQSubscription(subscriptionId, this.name, consumerTag, this.amqpChannel);
251
+ this.subscriptions.set(subscriptionId, subscription);
252
+ return subscription;
253
+ }
254
+ /**
255
+ * Send message to dead-letter queue
256
+ */
257
+ async sendToDeadLetter(message, deadLetterChannel) {
258
+ const content = JSON.stringify({
259
+ payload: message.payload,
260
+ metadata: {
261
+ ...message.metadata,
262
+ originalChannel: this.name,
263
+ },
264
+ });
265
+ this.amqpChannel.sendToQueue(deadLetterChannel, Buffer.from(content), { persistent: true });
266
+ }
267
+ /**
268
+ * Request-response pattern
269
+ */
270
+ async request(payload, options) {
271
+ await this.assert();
272
+ const correlationId = node_crypto.randomUUID();
273
+ const timeout = options?.timeout ?? 30000;
274
+ // Create exclusive reply queue
275
+ const { queue: replyQueue } = await this.amqpChannel.assertQueue("", {
276
+ exclusive: true,
277
+ autoDelete: true,
278
+ });
279
+ return new Promise((resolve, reject) => {
280
+ const timeoutId = setTimeout(() => {
281
+ reject(new Error(`Request timeout after ${timeout}ms`));
282
+ }, timeout);
283
+ // Consume reply
284
+ this.amqpChannel.consume(replyQueue, (msg) => {
285
+ if (msg?.properties.correlationId === correlationId) {
286
+ clearTimeout(timeoutId);
287
+ const content = JSON.parse(msg.content.toString());
288
+ resolve(content.payload);
289
+ }
290
+ }, { noAck: true });
291
+ // Send request
292
+ const messageContent = JSON.stringify({
293
+ payload,
294
+ metadata: {
295
+ messageId: node_crypto.randomUUID(),
296
+ timestamp: new Date().toISOString(),
297
+ correlationId,
298
+ },
299
+ });
300
+ this.amqpChannel.sendToQueue(this.name, Buffer.from(messageContent), {
301
+ correlationId,
302
+ replyTo: replyQueue,
303
+ expiration: timeout.toString(),
304
+ ...options,
305
+ });
306
+ });
307
+ }
308
+ /**
309
+ * Register response handler for RPC
310
+ */
311
+ async respond(handler) {
312
+ return this.subscribe(async (message, ctx) => {
313
+ const response = await handler(message, ctx);
314
+ await ctx.reply(response);
315
+ await ctx.ack();
316
+ });
317
+ }
318
+ /**
319
+ * Get queue statistics
320
+ */
321
+ async stats() {
322
+ await this.assert();
323
+ const queueInfo = await this.amqpChannel.checkQueue(this.name);
324
+ return {
325
+ name: this.name,
326
+ messageCount: queueInfo.messageCount,
327
+ consumerCount: queueInfo.consumerCount,
328
+ };
329
+ }
330
+ /**
331
+ * Purge all messages
332
+ */
333
+ async purge() {
334
+ await this.assert();
335
+ const result = await this.amqpChannel.purgeQueue(this.name);
336
+ return result.messageCount;
337
+ }
338
+ /**
339
+ * Check if queue exists
340
+ */
341
+ async exists() {
342
+ try {
343
+ await this.amqpChannel.checkQueue(this.name);
344
+ return true;
345
+ }
346
+ catch {
347
+ return false;
348
+ }
349
+ }
350
+ /**
351
+ * Delete the queue
352
+ */
353
+ async delete() {
354
+ // Cancel all subscriptions
355
+ for (const subscription of this.subscriptions.values()) {
356
+ await subscription.unsubscribe();
357
+ }
358
+ this.subscriptions.clear();
359
+ try {
360
+ await this.amqpChannel.deleteQueue(this.name);
361
+ }
362
+ catch {
363
+ // Ignore if queue doesn't exist
364
+ }
365
+ this.asserted = false;
366
+ }
367
+ }
368
+ /**
369
+ * RabbitMQ Subscription Implementation
370
+ */
371
+ class RabbitMQSubscription {
372
+ id;
373
+ channel;
374
+ consumerTag;
375
+ amqpChannel;
376
+ _isActive = true;
377
+ constructor(id, channel, consumerTag, amqpChannel) {
378
+ this.id = id;
379
+ this.channel = channel;
380
+ this.consumerTag = consumerTag;
381
+ this.amqpChannel = amqpChannel;
382
+ }
383
+ async unsubscribe() {
384
+ if (!this._isActive)
385
+ return;
386
+ await this.amqpChannel.cancel(this.consumerTag);
387
+ this._isActive = false;
388
+ }
389
+ async pause() {
390
+ // RabbitMQ doesn't have native pause, cancel consumer
391
+ await this.amqpChannel.cancel(this.consumerTag);
392
+ }
393
+ async resume() {
394
+ // Would need to re-subscribe - not directly supported
395
+ throw new Error("Resume is not supported for RabbitMQ. Please create a new subscription.");
396
+ }
397
+ isActive() {
398
+ return this._isActive;
399
+ }
400
+ }exports.RabbitMQChannel=RabbitMQChannel;//# sourceMappingURL=rabbitmq-channel.js.map