@zintrust/queue-rabbitmq 0.1.30 → 0.1.41
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/build-manifest.json +6 -6
- package/dist/index.d.ts +8 -0
- package/dist/index.js +168 -14
- package/package.json +2 -2
package/dist/build-manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/queue-rabbitmq",
|
|
3
3
|
"version": "0.1.27",
|
|
4
|
-
"buildDate": "2026-
|
|
4
|
+
"buildDate": "2026-02-14T22:45:58.578Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v20.20.0",
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"arch": "x64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "e20c137",
|
|
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":
|
|
28
|
-
"sha256": "
|
|
27
|
+
"size": 1061,
|
|
28
|
+
"sha256": "f6fc93a659264f237824fb0d4c2a7c661b172b657f041739edb3ceb21a9690b2"
|
|
29
29
|
},
|
|
30
30
|
"index.js": {
|
|
31
|
-
"size":
|
|
32
|
-
"sha256": "
|
|
31
|
+
"size": 10030,
|
|
32
|
+
"sha256": "7fd38bf82a8d73ad911945cddab98f63d140119bfe089911a232a77133b33579"
|
|
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
|
|
9
|
-
if (url
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
72
|
-
if (
|
|
194
|
+
const inFlight = state.inFlight.get(id);
|
|
195
|
+
if (inFlight === undefined)
|
|
73
196
|
return;
|
|
74
197
|
state.inFlight.delete(id);
|
|
75
|
-
ch.ack(
|
|
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.
|
|
3
|
+
"version": "0.1.41",
|
|
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.
|
|
28
|
+
"@zintrust/core": "^0.1.41"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|