@adatechnology/rabbitmq-provider 0.1.1
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/dist/index.d.ts +82 -0
- package/dist/index.js +410 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Options } from 'amqplib';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) 2026 Ada Technology. MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type RabbitMqConnectionConfig = string | Options.Connect;
|
|
8
|
+
type RabbitMqRoute = {
|
|
9
|
+
readonly exchange: string;
|
|
10
|
+
readonly queue: string;
|
|
11
|
+
readonly routingKey: string;
|
|
12
|
+
};
|
|
13
|
+
type RabbitMqRetryRoute = RabbitMqRoute & {
|
|
14
|
+
readonly delayMs: number;
|
|
15
|
+
readonly maxRetries: number;
|
|
16
|
+
};
|
|
17
|
+
type RabbitMqTopology = RabbitMqRoute & {
|
|
18
|
+
readonly retry: RabbitMqRetryRoute;
|
|
19
|
+
readonly deadLetter: RabbitMqRoute;
|
|
20
|
+
};
|
|
21
|
+
type RabbitMqProviderConfig = {
|
|
22
|
+
readonly connection: RabbitMqConnectionConfig;
|
|
23
|
+
readonly topology: RabbitMqTopology;
|
|
24
|
+
};
|
|
25
|
+
type RabbitMqDisposition = {
|
|
26
|
+
readonly type: 'ack';
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: 'retry';
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: 'dead-letter';
|
|
31
|
+
};
|
|
32
|
+
type RabbitMqConsumerMessage<TPayload> = {
|
|
33
|
+
readonly payload: TPayload;
|
|
34
|
+
readonly headers: Readonly<Record<string, unknown>>;
|
|
35
|
+
readonly messageId?: string;
|
|
36
|
+
readonly correlationId?: string;
|
|
37
|
+
readonly redelivered: boolean;
|
|
38
|
+
readonly retryCount: number;
|
|
39
|
+
};
|
|
40
|
+
type RabbitMqMessageHandler<TPayload> = (message: RabbitMqConsumerMessage<TPayload>) => RabbitMqDisposition | Promise<RabbitMqDisposition>;
|
|
41
|
+
type RabbitMqMessageDecoder<TPayload> = (value: unknown) => TPayload;
|
|
42
|
+
type RabbitMqConsumeParams<TPayload> = {
|
|
43
|
+
readonly decode: RabbitMqMessageDecoder<TPayload>;
|
|
44
|
+
readonly handler: RabbitMqMessageHandler<TPayload>;
|
|
45
|
+
readonly prefetch: number;
|
|
46
|
+
readonly consumerTag?: string;
|
|
47
|
+
};
|
|
48
|
+
type RabbitMqConsumer = {
|
|
49
|
+
readonly consumerTag: string;
|
|
50
|
+
cancel(): Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
type RabbitMqPublishOptions = {
|
|
53
|
+
readonly headers?: Readonly<Record<string, unknown>>;
|
|
54
|
+
readonly messageId?: string;
|
|
55
|
+
readonly correlationId?: string;
|
|
56
|
+
readonly type?: string;
|
|
57
|
+
};
|
|
58
|
+
type RabbitMqProviderHealth = {
|
|
59
|
+
readonly healthy: true;
|
|
60
|
+
};
|
|
61
|
+
type RabbitMqProvider = {
|
|
62
|
+
publish<TPayload>(payload: TPayload, options?: RabbitMqPublishOptions): Promise<void>;
|
|
63
|
+
consume<TPayload>(params: RabbitMqConsumeParams<TPayload>): Promise<RabbitMqConsumer>;
|
|
64
|
+
healthCheck(): Promise<RabbitMqProviderHealth>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
declare function createRabbitMqProvider(config: RabbitMqProviderConfig): Promise<RabbitMqProvider>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Copyright (c) 2026 Ada Technology. MIT License.
|
|
72
|
+
*/
|
|
73
|
+
declare class RabbitMqProviderClosedError extends Error {
|
|
74
|
+
readonly name = "RabbitMqProviderClosedError";
|
|
75
|
+
constructor();
|
|
76
|
+
}
|
|
77
|
+
declare class RabbitMqConfigurationError extends Error {
|
|
78
|
+
readonly name = "RabbitMqConfigurationError";
|
|
79
|
+
constructor(message: string);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { RabbitMqConfigurationError, type RabbitMqConnectionConfig, type RabbitMqConsumeParams, type RabbitMqConsumer, type RabbitMqConsumerMessage, type RabbitMqDisposition, type RabbitMqMessageDecoder, type RabbitMqMessageHandler, type RabbitMqProvider, RabbitMqProviderClosedError, type RabbitMqProviderConfig, type RabbitMqProviderHealth, type RabbitMqPublishOptions, type RabbitMqRetryRoute, type RabbitMqRoute, type RabbitMqTopology, createRabbitMqProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/rabbitmq-provider.factory.ts
|
|
5
|
+
import { connect } from "amqplib";
|
|
6
|
+
|
|
7
|
+
// src/rabbitmq-provider.error.ts
|
|
8
|
+
var RabbitMqProviderClosedError = class extends Error {
|
|
9
|
+
static {
|
|
10
|
+
__name(this, "RabbitMqProviderClosedError");
|
|
11
|
+
}
|
|
12
|
+
name = "RabbitMqProviderClosedError";
|
|
13
|
+
constructor() {
|
|
14
|
+
super("RabbitMQ provider is closing or already closed");
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var RabbitMqConfigurationError = class extends Error {
|
|
18
|
+
static {
|
|
19
|
+
__name(this, "RabbitMqConfigurationError");
|
|
20
|
+
}
|
|
21
|
+
name = "RabbitMqConfigurationError";
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/rabbitmq-provider.service.ts
|
|
28
|
+
var RabbitMqProviderService = class {
|
|
29
|
+
static {
|
|
30
|
+
__name(this, "RabbitMqProviderService");
|
|
31
|
+
}
|
|
32
|
+
connection;
|
|
33
|
+
publisherChannel;
|
|
34
|
+
topology;
|
|
35
|
+
consumers = /* @__PURE__ */ new Map();
|
|
36
|
+
openingConsumerChannels = /* @__PURE__ */ new Set();
|
|
37
|
+
closePromise;
|
|
38
|
+
closing = false;
|
|
39
|
+
constructor({ connection, publisherChannel, topology }) {
|
|
40
|
+
this.connection = connection;
|
|
41
|
+
this.publisherChannel = publisherChannel;
|
|
42
|
+
this.topology = topology;
|
|
43
|
+
}
|
|
44
|
+
async publish(payload, options = {}) {
|
|
45
|
+
this.assertOpen();
|
|
46
|
+
await this.publishConfirmed({
|
|
47
|
+
content: Buffer.from(JSON.stringify(payload)),
|
|
48
|
+
exchange: this.topology.exchange,
|
|
49
|
+
options: {
|
|
50
|
+
...options,
|
|
51
|
+
contentType: "application/json",
|
|
52
|
+
persistent: true
|
|
53
|
+
},
|
|
54
|
+
routingKey: this.topology.routingKey
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async consume({ decode, handler, prefetch, consumerTag }) {
|
|
58
|
+
this.assertOpen();
|
|
59
|
+
assertValidPrefetch(prefetch);
|
|
60
|
+
const channel = await this.connection.createChannel();
|
|
61
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
62
|
+
this.openingConsumerChannels.add(channel);
|
|
63
|
+
try {
|
|
64
|
+
this.assertOpen();
|
|
65
|
+
await channel.prefetch(prefetch);
|
|
66
|
+
this.assertOpen();
|
|
67
|
+
const reply = await channel.consume(this.topology.queue, (message) => {
|
|
68
|
+
if (!message) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const processing = this.processDelivery({
|
|
72
|
+
channel,
|
|
73
|
+
decode,
|
|
74
|
+
handler,
|
|
75
|
+
message
|
|
76
|
+
});
|
|
77
|
+
inFlight.add(processing);
|
|
78
|
+
void processing.then(() => inFlight.delete(processing), () => inFlight.delete(processing));
|
|
79
|
+
}, {
|
|
80
|
+
consumerTag,
|
|
81
|
+
noAck: false
|
|
82
|
+
});
|
|
83
|
+
this.assertOpen();
|
|
84
|
+
this.openingConsumerChannels.delete(channel);
|
|
85
|
+
this.consumers.set(reply.consumerTag, {
|
|
86
|
+
channel,
|
|
87
|
+
inFlight
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
consumerTag: reply.consumerTag,
|
|
91
|
+
cancel: /* @__PURE__ */ __name(() => this.cancelConsumer(reply.consumerTag), "cancel")
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.openingConsumerChannels.delete(channel);
|
|
95
|
+
await closeChannel(channel);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async healthCheck() {
|
|
100
|
+
this.assertOpen();
|
|
101
|
+
await this.publisherChannel.checkQueue(this.topology.queue);
|
|
102
|
+
return {
|
|
103
|
+
healthy: true
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
close() {
|
|
107
|
+
this.closePromise ??= this.shutdown();
|
|
108
|
+
return this.closePromise;
|
|
109
|
+
}
|
|
110
|
+
async processDelivery({ channel, decode, handler, message }) {
|
|
111
|
+
const decoded = decodeDelivery({
|
|
112
|
+
decode,
|
|
113
|
+
message,
|
|
114
|
+
retryQueue: this.topology.retry.queue
|
|
115
|
+
});
|
|
116
|
+
if (!decoded) {
|
|
117
|
+
channel.reject(message, false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const disposition = await handler(decoded);
|
|
122
|
+
await this.applyDisposition({
|
|
123
|
+
channel,
|
|
124
|
+
disposition,
|
|
125
|
+
message,
|
|
126
|
+
retryCount: decoded.retryCount
|
|
127
|
+
});
|
|
128
|
+
} catch {
|
|
129
|
+
await this.handleFailure({
|
|
130
|
+
channel,
|
|
131
|
+
message,
|
|
132
|
+
metadata: decoded
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async applyDisposition({ channel, disposition, message, retryCount: retryCount2 }) {
|
|
137
|
+
if (disposition.type === "ack") {
|
|
138
|
+
channel.ack(message);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (disposition.type === "dead-letter") {
|
|
142
|
+
channel.reject(message, false);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (retryCount2 >= this.topology.retry.maxRetries) {
|
|
146
|
+
channel.reject(message, false);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
await this.publishRetry({
|
|
150
|
+
channel,
|
|
151
|
+
message,
|
|
152
|
+
retryCount: retryCount2
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async handleFailure({ channel, message, metadata }) {
|
|
156
|
+
if (metadata.retryCount === 0 && !metadata.redelivered) {
|
|
157
|
+
channel.nack(message, false, true);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (metadata.retryCount >= this.topology.retry.maxRetries) {
|
|
161
|
+
channel.reject(message, false);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await this.publishRetry({
|
|
165
|
+
channel,
|
|
166
|
+
message,
|
|
167
|
+
retryCount: metadata.retryCount
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async publishRetry({ channel, message, retryCount: retryCount2 }) {
|
|
171
|
+
try {
|
|
172
|
+
await this.publishConfirmed({
|
|
173
|
+
content: message.content,
|
|
174
|
+
exchange: this.topology.retry.exchange,
|
|
175
|
+
options: retryPublishOptions({
|
|
176
|
+
message,
|
|
177
|
+
retryCount: retryCount2
|
|
178
|
+
}),
|
|
179
|
+
routingKey: this.topology.retry.routingKey
|
|
180
|
+
});
|
|
181
|
+
channel.ack(message);
|
|
182
|
+
} catch {
|
|
183
|
+
channel.nack(message, false, true);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async publishConfirmed({ content, exchange, options, routingKey }) {
|
|
187
|
+
const confirmation = createDeferred();
|
|
188
|
+
const canWrite = this.publisherChannel.publish(exchange, routingKey, content, options, (error) => {
|
|
189
|
+
if (error) {
|
|
190
|
+
confirmation.reject(toError(error));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
confirmation.resolve();
|
|
194
|
+
});
|
|
195
|
+
if (canWrite) {
|
|
196
|
+
await confirmation.promise;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await Promise.all([
|
|
200
|
+
confirmation.promise,
|
|
201
|
+
waitForDrain(this.publisherChannel)
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
cancelConsumer(consumerTag) {
|
|
205
|
+
const consumer = this.consumers.get(consumerTag);
|
|
206
|
+
if (!consumer) {
|
|
207
|
+
return Promise.resolve();
|
|
208
|
+
}
|
|
209
|
+
consumer.cancellation ??= this.cancelAndDrain({
|
|
210
|
+
consumer,
|
|
211
|
+
consumerTag
|
|
212
|
+
});
|
|
213
|
+
return consumer.cancellation;
|
|
214
|
+
}
|
|
215
|
+
async cancelAndDrain({ consumer, consumerTag }) {
|
|
216
|
+
await consumer.channel.cancel(consumerTag);
|
|
217
|
+
await Promise.all([
|
|
218
|
+
...consumer.inFlight
|
|
219
|
+
]);
|
|
220
|
+
await consumer.channel.close();
|
|
221
|
+
this.consumers.delete(consumerTag);
|
|
222
|
+
}
|
|
223
|
+
async shutdown() {
|
|
224
|
+
this.closing = true;
|
|
225
|
+
await Promise.all([
|
|
226
|
+
...this.consumers.keys()
|
|
227
|
+
].map((consumerTag) => this.cancelConsumer(consumerTag)));
|
|
228
|
+
await Promise.all([
|
|
229
|
+
...this.openingConsumerChannels
|
|
230
|
+
].map(closeChannel));
|
|
231
|
+
await this.publisherChannel.waitForConfirms();
|
|
232
|
+
await this.publisherChannel.close();
|
|
233
|
+
await this.connection.close();
|
|
234
|
+
}
|
|
235
|
+
assertOpen() {
|
|
236
|
+
if (this.closing) {
|
|
237
|
+
throw new RabbitMqProviderClosedError();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
function decodeDelivery({ decode, message, retryQueue }) {
|
|
242
|
+
try {
|
|
243
|
+
const headers = normalizeHeaders(message.properties.headers);
|
|
244
|
+
return {
|
|
245
|
+
payload: decode(JSON.parse(message.content.toString())),
|
|
246
|
+
headers,
|
|
247
|
+
messageId: stringProperty(message.properties.messageId),
|
|
248
|
+
correlationId: stringProperty(message.properties.correlationId),
|
|
249
|
+
redelivered: message.fields.redelivered,
|
|
250
|
+
retryCount: retryCount({
|
|
251
|
+
headers,
|
|
252
|
+
retryQueue
|
|
253
|
+
})
|
|
254
|
+
};
|
|
255
|
+
} catch {
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
__name(decodeDelivery, "decodeDelivery");
|
|
260
|
+
function normalizeHeaders(headers) {
|
|
261
|
+
return headers ?? {};
|
|
262
|
+
}
|
|
263
|
+
__name(normalizeHeaders, "normalizeHeaders");
|
|
264
|
+
function stringProperty(value) {
|
|
265
|
+
return typeof value === "string" ? value : void 0;
|
|
266
|
+
}
|
|
267
|
+
__name(stringProperty, "stringProperty");
|
|
268
|
+
function retryCount({ headers, retryQueue }) {
|
|
269
|
+
const providerRetryCount = headers["x-ada-retry-count"];
|
|
270
|
+
if (typeof providerRetryCount === "number" && Number.isInteger(providerRetryCount) && providerRetryCount >= 0) {
|
|
271
|
+
return providerRetryCount;
|
|
272
|
+
}
|
|
273
|
+
const deaths = headers["x-death"];
|
|
274
|
+
if (!Array.isArray(deaths)) {
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
return deaths.reduce((count, death) => {
|
|
278
|
+
if (!isRetryDeath(death, retryQueue)) {
|
|
279
|
+
return count;
|
|
280
|
+
}
|
|
281
|
+
return count + death.count;
|
|
282
|
+
}, 0);
|
|
283
|
+
}
|
|
284
|
+
__name(retryCount, "retryCount");
|
|
285
|
+
function isRetryDeath(death, retryQueue) {
|
|
286
|
+
if (!death || typeof death !== "object") {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return "count" in death && typeof death.count === "number" && "queue" in death && death.queue === retryQueue;
|
|
290
|
+
}
|
|
291
|
+
__name(isRetryDeath, "isRetryDeath");
|
|
292
|
+
function retryPublishOptions({ message, retryCount: retryCount2 }) {
|
|
293
|
+
return {
|
|
294
|
+
appId: stringProperty(message.properties.appId),
|
|
295
|
+
contentEncoding: stringProperty(message.properties.contentEncoding),
|
|
296
|
+
contentType: stringProperty(message.properties.contentType),
|
|
297
|
+
correlationId: stringProperty(message.properties.correlationId),
|
|
298
|
+
headers: {
|
|
299
|
+
...message.properties.headers,
|
|
300
|
+
"x-ada-retry-count": retryCount2 + 1
|
|
301
|
+
},
|
|
302
|
+
messageId: stringProperty(message.properties.messageId),
|
|
303
|
+
persistent: true,
|
|
304
|
+
type: stringProperty(message.properties.type)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
__name(retryPublishOptions, "retryPublishOptions");
|
|
308
|
+
function createDeferred() {
|
|
309
|
+
let resolve;
|
|
310
|
+
let reject;
|
|
311
|
+
const promise = new Promise((promiseResolve, promiseReject) => {
|
|
312
|
+
resolve = promiseResolve;
|
|
313
|
+
reject = promiseReject;
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
promise,
|
|
317
|
+
resolve,
|
|
318
|
+
reject
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
__name(createDeferred, "createDeferred");
|
|
322
|
+
function waitForDrain(channel) {
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
channel.once("drain", resolve);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
__name(waitForDrain, "waitForDrain");
|
|
328
|
+
function toError(error) {
|
|
329
|
+
return error instanceof Error ? error : new Error("RabbitMQ publish failed");
|
|
330
|
+
}
|
|
331
|
+
__name(toError, "toError");
|
|
332
|
+
function assertValidPrefetch(prefetch) {
|
|
333
|
+
if (!Number.isInteger(prefetch) || prefetch <= 0) {
|
|
334
|
+
throw new RabbitMqConfigurationError("RabbitMQ consumer prefetch must be a positive integer");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
__name(assertValidPrefetch, "assertValidPrefetch");
|
|
338
|
+
async function closeChannel(channel) {
|
|
339
|
+
try {
|
|
340
|
+
await channel.close();
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
__name(closeChannel, "closeChannel");
|
|
345
|
+
|
|
346
|
+
// src/rabbitmq-provider.factory.ts
|
|
347
|
+
async function createRabbitMqProvider(config) {
|
|
348
|
+
assertValidTopology(config.topology);
|
|
349
|
+
const connection = await connect(config.connection);
|
|
350
|
+
const publisherChannel = await connection.createConfirmChannel();
|
|
351
|
+
try {
|
|
352
|
+
await assertTopology({
|
|
353
|
+
channel: publisherChannel,
|
|
354
|
+
topology: config.topology
|
|
355
|
+
});
|
|
356
|
+
} catch (error) {
|
|
357
|
+
await publisherChannel.close();
|
|
358
|
+
await connection.close();
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
return new RabbitMqProviderService({
|
|
362
|
+
connection,
|
|
363
|
+
publisherChannel,
|
|
364
|
+
topology: config.topology
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
__name(createRabbitMqProvider, "createRabbitMqProvider");
|
|
368
|
+
async function assertTopology({ channel, topology }) {
|
|
369
|
+
await channel.assertExchange(topology.exchange, "direct", {
|
|
370
|
+
durable: true
|
|
371
|
+
});
|
|
372
|
+
await channel.assertExchange(topology.retry.exchange, "direct", {
|
|
373
|
+
durable: true
|
|
374
|
+
});
|
|
375
|
+
await channel.assertExchange(topology.deadLetter.exchange, "direct", {
|
|
376
|
+
durable: true
|
|
377
|
+
});
|
|
378
|
+
await channel.assertQueue(topology.queue, {
|
|
379
|
+
durable: true,
|
|
380
|
+
deadLetterExchange: topology.deadLetter.exchange,
|
|
381
|
+
deadLetterRoutingKey: topology.deadLetter.routingKey
|
|
382
|
+
});
|
|
383
|
+
await channel.bindQueue(topology.queue, topology.exchange, topology.routingKey);
|
|
384
|
+
await channel.assertQueue(topology.retry.queue, {
|
|
385
|
+
durable: true,
|
|
386
|
+
messageTtl: topology.retry.delayMs,
|
|
387
|
+
deadLetterExchange: topology.exchange,
|
|
388
|
+
deadLetterRoutingKey: topology.routingKey
|
|
389
|
+
});
|
|
390
|
+
await channel.bindQueue(topology.retry.queue, topology.retry.exchange, topology.retry.routingKey);
|
|
391
|
+
await channel.assertQueue(topology.deadLetter.queue, {
|
|
392
|
+
durable: true
|
|
393
|
+
});
|
|
394
|
+
await channel.bindQueue(topology.deadLetter.queue, topology.deadLetter.exchange, topology.deadLetter.routingKey);
|
|
395
|
+
}
|
|
396
|
+
__name(assertTopology, "assertTopology");
|
|
397
|
+
function assertValidTopology(topology) {
|
|
398
|
+
if (!Number.isInteger(topology.retry.delayMs) || topology.retry.delayMs <= 0) {
|
|
399
|
+
throw new RabbitMqConfigurationError("RabbitMQ retry delayMs must be a positive integer");
|
|
400
|
+
}
|
|
401
|
+
if (!Number.isInteger(topology.retry.maxRetries) || topology.retry.maxRetries < 0) {
|
|
402
|
+
throw new RabbitMqConfigurationError("RabbitMQ retry maxRetries must be a non-negative integer");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
__name(assertValidTopology, "assertValidTopology");
|
|
406
|
+
export {
|
|
407
|
+
RabbitMqConfigurationError,
|
|
408
|
+
RabbitMqProviderClosedError,
|
|
409
|
+
createRabbitMqProvider
|
|
410
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adatechnology/rabbitmq-provider",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Typed RabbitMQ provider for Bun workers and APIs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"engines": {
|
|
20
|
+
"bun": ">=1.3.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"amqp",
|
|
24
|
+
"bun",
|
|
25
|
+
"rabbitmq",
|
|
26
|
+
"worker"
|
|
27
|
+
],
|
|
28
|
+
"author": "Ada Technology",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"amqplib": "2.0.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "1.3.14",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"check": "tsc --noEmit",
|
|
44
|
+
"test": "bun test",
|
|
45
|
+
"test:integration": "bun test --preload ./src/require-test-rabbitmq.ts",
|
|
46
|
+
"format": "prettier --write .",
|
|
47
|
+
"format:check": "prettier --check ."
|
|
48
|
+
}
|
|
49
|
+
}
|