@zintrust/queue-rabbitmq 0.1.30 → 0.1.43

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.
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@zintrust/queue-rabbitmq",
3
3
  "version": "0.1.27",
4
- "buildDate": "2026-01-29T08:06:44.824Z",
4
+ "buildDate": "2026-02-19T05:36:49.100Z",
5
5
  "buildEnvironment": {
6
6
  "node": "v20.20.0",
7
7
  "platform": "linux",
8
8
  "arch": "x64"
9
9
  },
10
10
  "git": {
11
- "commit": "1555458",
11
+ "commit": "4e0a37b",
12
12
  "branch": "master"
13
13
  },
14
14
  "package": {
@@ -24,12 +24,12 @@
24
24
  },
25
25
  "files": {
26
26
  "index.d.ts": {
27
- "size": 862,
28
- "sha256": "c3514221f9c8279ce5dd5b3f2dcbca57f6180fe4159a9074825eb256372602a7"
27
+ "size": 1061,
28
+ "sha256": "f6fc93a659264f237824fb0d4c2a7c661b172b657f041739edb3ceb21a9690b2"
29
29
  },
30
30
  "index.js": {
31
- "size": 4185,
32
- "sha256": "9d2b64a708f9310ca8ec4043b7fd77c7799dc95095d720fbe8313560cc342423"
31
+ "size": 10030,
32
+ "sha256": "4be49e66997181badf62323369e8ba940585c6fbf9f0d3fca9ad7df6103501c9"
33
33
  },
34
34
  "register.d.ts": {
35
35
  "size": 172,
package/dist/index.d.ts CHANGED
@@ -6,6 +6,14 @@ export type QueueMessage<T = unknown> = {
6
6
  export type RabbitMqQueueConfig = {
7
7
  driver: 'rabbitmq';
8
8
  url?: string;
9
+ host?: string;
10
+ port?: number;
11
+ username?: string;
12
+ password?: string;
13
+ vhost?: string;
14
+ httpGatewayUrl?: string;
15
+ httpGatewayToken?: string;
16
+ httpGatewayTimeoutMs?: number;
9
17
  };
10
18
  export declare const RabbitMqQueue: Readonly<{
11
19
  create(config?: RabbitMqQueueConfig): {
package/dist/index.js CHANGED
@@ -1,16 +1,132 @@
1
- import { ErrorFactory, generateUuid } from '@zintrust/core';
1
+ import { Cloudflare, ErrorFactory, Logger, generateUuid } from '@zintrust/core';
2
2
  async function importAmqplib() {
3
3
  // Avoid a string-literal import so TypeScript doesn't require the module at build time.
4
4
  const specifier = 'amqplib';
5
5
  return (await import(specifier));
6
6
  }
7
+ const IN_FLIGHT_MAX_ENTRIES = 10000;
8
+ const IN_FLIGHT_TTL_MS = 15 * 60 * 1000;
9
+ const pruneInFlight = (state) => {
10
+ const now = Date.now();
11
+ for (const [id, value] of state.inFlight.entries()) {
12
+ if (now - value.seenAt > IN_FLIGHT_TTL_MS) {
13
+ state.inFlight.delete(id);
14
+ }
15
+ }
16
+ if (state.inFlight.size <= IN_FLIGHT_MAX_ENTRIES)
17
+ return;
18
+ const overflow = state.inFlight.size - IN_FLIGHT_MAX_ENTRIES;
19
+ let removed = 0;
20
+ for (const id of state.inFlight.keys()) {
21
+ state.inFlight.delete(id);
22
+ removed++;
23
+ if (removed >= overflow)
24
+ break;
25
+ }
26
+ Logger.warn('RabbitMQ in-flight map exceeded max entries; pruned oldest items', {
27
+ removed,
28
+ sizeAfter: state.inFlight.size,
29
+ });
30
+ };
31
+ /**
32
+ * Helper to resolve configuration value from config object or environment
33
+ */
34
+ const getConfigValue = (config, configKey, envKey, defaultValue) => {
35
+ return (config?.[configKey] ?? process.env[envKey] ?? defaultValue).toString().trim();
36
+ };
37
+ /**
38
+ * Build AMQP URL from components
39
+ */
40
+ const buildAmqpUrl = (host, port, username, password, vhost) => {
41
+ const auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
42
+ const vhostSegment = vhost === '/' ? '/' : `/${encodeURIComponent(vhost)}`;
43
+ const resolvedPort = Number.isFinite(port) ? port : 5672;
44
+ return `amqp://${auth}@${host}:${resolvedPort}${vhostSegment}`;
45
+ };
7
46
  function resolveUrl(config) {
8
- const url = (config?.url ?? process.env['RABBITMQ_URL'] ?? '').toString().trim();
9
- if (url === '') {
10
- throw ErrorFactory.createConfigError('RabbitMQ queue driver requires RABBITMQ_URL');
47
+ const url = getConfigValue(config, 'url', 'RABBITMQ_URL', '');
48
+ if (url !== '')
49
+ return url;
50
+ const host = getConfigValue(config, 'host', 'RABBITMQ_HOST', '');
51
+ if (host === '') {
52
+ throw ErrorFactory.createConfigError('RabbitMQ queue driver requires RABBITMQ_URL or host');
11
53
  }
12
- return url;
54
+ const port = Number(getConfigValue(config, 'port', 'RABBITMQ_PORT', '5672'));
55
+ const username = getConfigValue(config, 'username', 'RABBITMQ_USER', 'guest');
56
+ const password = getConfigValue(config, 'password', 'RABBITMQ_PASSWORD', 'guest');
57
+ const vhost = getConfigValue(config, 'vhost', 'RABBITMQ_VHOST', '/');
58
+ return buildAmqpUrl(host, port, username, password, vhost);
13
59
  }
60
+ const resolveGatewayUrl = (config) => {
61
+ return getConfigValue(config, 'httpGatewayUrl', 'RABBITMQ_HTTP_GATEWAY_URL', '');
62
+ };
63
+ const resolveGatewayToken = (config) => {
64
+ const token = getConfigValue(config, 'httpGatewayToken', 'RABBITMQ_HTTP_GATEWAY_TOKEN', '');
65
+ return token === '' ? null : token;
66
+ };
67
+ const resolveGatewayTimeoutMs = (config) => {
68
+ const raw = config?.httpGatewayTimeoutMs ?? process.env['RABBITMQ_HTTP_GATEWAY_TIMEOUT_MS'];
69
+ const parsed = Number(raw ?? 15000);
70
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 15000;
71
+ };
72
+ const shouldUseGateway = (config) => {
73
+ const gatewayUrl = resolveGatewayUrl(config);
74
+ if (gatewayUrl !== '')
75
+ return true;
76
+ return Cloudflare.getWorkersEnv() !== null;
77
+ };
78
+ const buildGatewayHeaders = (config) => {
79
+ const headers = {
80
+ 'content-type': 'application/json',
81
+ };
82
+ const token = resolveGatewayToken(config);
83
+ if (token) {
84
+ headers.authorization = `Bearer ${token}`;
85
+ }
86
+ return headers;
87
+ };
88
+ const fetchGateway = async (config, path, body) => {
89
+ const base = resolveGatewayUrl(config);
90
+ if (base === '') {
91
+ throw ErrorFactory.createConfigError('RabbitMQ HTTP gateway requires RABBITMQ_HTTP_GATEWAY_URL');
92
+ }
93
+ const timeoutMs = resolveGatewayTimeoutMs(config);
94
+ const hasAbortController = typeof AbortController === 'function';
95
+ const controller = hasAbortController ? new AbortController() : undefined;
96
+ const timeoutId = controller
97
+ ? globalThis.setTimeout(() => controller.abort(), timeoutMs)
98
+ : undefined;
99
+ try {
100
+ const res = await fetch(`${base.replace(/\/$/, '')}${path}`, {
101
+ method: 'POST',
102
+ headers: buildGatewayHeaders(config),
103
+ body: JSON.stringify(body ?? {}),
104
+ signal: controller?.signal,
105
+ });
106
+ // Clear timeout on successful fetch
107
+ if (timeoutId !== undefined)
108
+ clearTimeout(timeoutId);
109
+ if (!res.ok) {
110
+ const text = await res.text();
111
+ throw ErrorFactory.createConnectionError(`RabbitMQ gateway error (${res.status})`, {
112
+ status: res.status,
113
+ body: text,
114
+ });
115
+ }
116
+ return (await res.json());
117
+ }
118
+ catch (error) {
119
+ // Clear timeout on error
120
+ if (timeoutId !== undefined)
121
+ clearTimeout(timeoutId);
122
+ throw error;
123
+ }
124
+ finally {
125
+ // Final cleanup safeguard
126
+ if (timeoutId !== undefined)
127
+ clearTimeout(timeoutId);
128
+ }
129
+ };
14
130
  async function ensureChannel(state, config) {
15
131
  if (state.channel !== undefined)
16
132
  return state.channel;
@@ -25,13 +141,18 @@ async function ensureQueue(state, config, queue) {
25
141
  }
26
142
  async function drainFallback(state, config, queue) {
27
143
  const ch = await ensureChannel(state, config);
28
- const msg = await ch.get(queue, { noAck: false });
29
- if (msg === false)
30
- return;
31
- ch.ack(msg);
32
- await drainFallback(state, config, queue);
144
+ while (true) {
145
+ // eslint-disable-next-line no-await-in-loop
146
+ const msg = await ch.get(queue, { noAck: false });
147
+ if (msg === false)
148
+ return;
149
+ ch.ack(msg);
150
+ }
33
151
  }
34
152
  function createRabbitMqQueueDriver(config) {
153
+ if (shouldUseGateway(config)) {
154
+ return createRabbitMqHttpGatewayDriver(config);
155
+ }
35
156
  const state = { inFlight: new Map() };
36
157
  return {
37
158
  async enqueue(queue, payload) {
@@ -44,6 +165,7 @@ function createRabbitMqQueueDriver(config) {
44
165
  },
45
166
  async dequeue(queue) {
46
167
  await ensureQueue(state, config, queue);
168
+ pruneInFlight(state);
47
169
  const ch = await ensureChannel(state, config);
48
170
  const msg = await ch.get(queue, { noAck: false });
49
171
  if (msg === false)
@@ -51,7 +173,7 @@ function createRabbitMqQueueDriver(config) {
51
173
  try {
52
174
  const parsed = JSON.parse(msg.content.toString('utf-8'));
53
175
  if (typeof parsed?.id === 'string' && parsed.id.trim() !== '') {
54
- state.inFlight.set(parsed.id, msg);
176
+ state.inFlight.set(parsed.id, { message: msg, seenAt: Date.now() });
55
177
  }
56
178
  return parsed;
57
179
  }
@@ -67,12 +189,13 @@ function createRabbitMqQueueDriver(config) {
67
189
  }
68
190
  },
69
191
  async ack(_queue, id) {
192
+ pruneInFlight(state);
70
193
  const ch = await ensureChannel(state, config);
71
- const msg = state.inFlight.get(id);
72
- if (msg === undefined)
194
+ const inFlight = state.inFlight.get(id);
195
+ if (inFlight === undefined)
73
196
  return;
74
197
  state.inFlight.delete(id);
75
- ch.ack(msg);
198
+ ch.ack(inFlight.message);
76
199
  },
77
200
  async length(queue) {
78
201
  await ensureQueue(state, config, queue);
@@ -94,6 +217,37 @@ function createRabbitMqQueueDriver(config) {
94
217
  },
95
218
  };
96
219
  }
220
+ function createRabbitMqHttpGatewayDriver(config) {
221
+ return {
222
+ async enqueue(queue, payload) {
223
+ const response = await fetchGateway(config, '/enqueue', {
224
+ queue,
225
+ payload,
226
+ });
227
+ return typeof response?.id === 'string' && response.id.trim() !== ''
228
+ ? response.id
229
+ : generateUuid();
230
+ },
231
+ async dequeue(queue) {
232
+ const response = await fetchGateway(config, '/dequeue', { queue });
233
+ const msg = response?.message ?? null;
234
+ if (msg === null || msg === undefined)
235
+ return undefined;
236
+ return msg;
237
+ },
238
+ async ack(queue, id) {
239
+ await fetchGateway(config, '/ack', { queue, id });
240
+ },
241
+ async length(queue) {
242
+ const response = await fetchGateway(config, '/length', { queue });
243
+ const length = Number(response?.length ?? 0);
244
+ return Number.isFinite(length) ? length : 0;
245
+ },
246
+ async drain(queue) {
247
+ await fetchGateway(config, '/drain', { queue });
248
+ },
249
+ };
250
+ }
97
251
  export const RabbitMqQueue = Object.freeze({
98
252
  create(config) {
99
253
  return createRabbitMqQueueDriver(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/queue-rabbitmq",
3
- "version": "0.1.30",
3
+ "version": "0.1.43",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "amqplib": "^0.10.3"
26
26
  },
27
27
  "peerDependencies": {
28
- "@zintrust/core": "^0.1.30"
28
+ "@zintrust/core": "^0.1.43"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"