@zintrust/queue-rabbitmq 0.1.12
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 +21 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +102 -0
- package/dist/register.d.ts +5 -0
- package/dist/register.js +21 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @zintrust/queue-rabbitmq
|
|
2
|
+
|
|
3
|
+
RabbitMQ queue driver registration for Zintrust.
|
|
4
|
+
|
|
5
|
+
- Docs: https://zintrust.com/queue
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @zintrust/queue-rabbitmq
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import '@zintrust/queue-rabbitmq/register';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then set `QUEUE_DRIVER=rabbitmq` and configure:
|
|
20
|
+
|
|
21
|
+
- `RABBITMQ_URL` (e.g. `amqp://localhost`)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type QueueMessage<T = unknown> = {
|
|
2
|
+
id: string;
|
|
3
|
+
payload: T;
|
|
4
|
+
attempts: number;
|
|
5
|
+
};
|
|
6
|
+
export type RabbitMqQueueConfig = {
|
|
7
|
+
driver: 'rabbitmq';
|
|
8
|
+
url?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const RabbitMqQueue: Readonly<{
|
|
11
|
+
create(config?: RabbitMqQueueConfig): {
|
|
12
|
+
enqueue<T = unknown>(queue: string, payload: T): Promise<string>;
|
|
13
|
+
dequeue<T = unknown>(queue: string): Promise<QueueMessage<T> | undefined>;
|
|
14
|
+
ack(queue: string, id: string): Promise<void>;
|
|
15
|
+
length(queue: string): Promise<number>;
|
|
16
|
+
drain(queue: string): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
}>;
|
|
19
|
+
export default RabbitMqQueue;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ErrorFactory, generateUuid } from '@zintrust/core';
|
|
2
|
+
async function importAmqplib() {
|
|
3
|
+
// Avoid a string-literal import so TypeScript doesn't require the module at build time.
|
|
4
|
+
const specifier = 'amqplib';
|
|
5
|
+
return (await import(specifier));
|
|
6
|
+
}
|
|
7
|
+
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');
|
|
11
|
+
}
|
|
12
|
+
return url;
|
|
13
|
+
}
|
|
14
|
+
async function ensureChannel(state, config) {
|
|
15
|
+
if (state.channel !== undefined)
|
|
16
|
+
return state.channel;
|
|
17
|
+
const { connect } = await importAmqplib();
|
|
18
|
+
state.connection = await connect(resolveUrl(config));
|
|
19
|
+
state.channel = await state.connection.createChannel();
|
|
20
|
+
return state.channel;
|
|
21
|
+
}
|
|
22
|
+
async function ensureQueue(state, config, queue) {
|
|
23
|
+
const ch = await ensureChannel(state, config);
|
|
24
|
+
await ch.assertQueue(queue, { durable: true });
|
|
25
|
+
}
|
|
26
|
+
async function drainFallback(state, config, queue) {
|
|
27
|
+
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);
|
|
33
|
+
}
|
|
34
|
+
function createRabbitMqQueueDriver(config) {
|
|
35
|
+
const state = { inFlight: new Map() };
|
|
36
|
+
return {
|
|
37
|
+
async enqueue(queue, payload) {
|
|
38
|
+
const id = generateUuid();
|
|
39
|
+
await ensureQueue(state, config, queue);
|
|
40
|
+
const ch = await ensureChannel(state, config);
|
|
41
|
+
const msg = JSON.stringify({ id, payload, attempts: 0 });
|
|
42
|
+
ch.sendToQueue(queue, Buffer.from(msg), { persistent: true });
|
|
43
|
+
return id;
|
|
44
|
+
},
|
|
45
|
+
async dequeue(queue) {
|
|
46
|
+
await ensureQueue(state, config, queue);
|
|
47
|
+
const ch = await ensureChannel(state, config);
|
|
48
|
+
const msg = await ch.get(queue, { noAck: false });
|
|
49
|
+
if (msg === false)
|
|
50
|
+
return undefined;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(msg.content.toString('utf-8'));
|
|
53
|
+
if (typeof parsed?.id === 'string' && parsed.id.trim() !== '') {
|
|
54
|
+
state.inFlight.set(parsed.id, msg);
|
|
55
|
+
}
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// If we can't parse, ack it so we don't get stuck.
|
|
60
|
+
try {
|
|
61
|
+
ch.ack(msg);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// ignore
|
|
65
|
+
}
|
|
66
|
+
throw ErrorFactory.createTryCatchError('Failed to parse queue message', err);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async ack(_queue, id) {
|
|
70
|
+
const ch = await ensureChannel(state, config);
|
|
71
|
+
const msg = state.inFlight.get(id);
|
|
72
|
+
if (msg === undefined)
|
|
73
|
+
return;
|
|
74
|
+
state.inFlight.delete(id);
|
|
75
|
+
ch.ack(msg);
|
|
76
|
+
},
|
|
77
|
+
async length(queue) {
|
|
78
|
+
await ensureQueue(state, config, queue);
|
|
79
|
+
const ch = await ensureChannel(state, config);
|
|
80
|
+
if (typeof ch.checkQueue === 'function') {
|
|
81
|
+
const r = await ch.checkQueue(queue);
|
|
82
|
+
return Number(r.messageCount ?? 0);
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
},
|
|
86
|
+
async drain(queue) {
|
|
87
|
+
await ensureQueue(state, config, queue);
|
|
88
|
+
const ch = await ensureChannel(state, config);
|
|
89
|
+
if (typeof ch.purgeQueue === 'function') {
|
|
90
|
+
await ch.purgeQueue(queue);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
await drainFallback(state, config, queue);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export const RabbitMqQueue = Object.freeze({
|
|
98
|
+
create(config) {
|
|
99
|
+
return createRabbitMqQueueDriver(config);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
export default RabbitMqQueue;
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export async function registerRabbitMqQueueDriver(queue) {
|
|
2
|
+
const { RabbitMqQueue } = (await import('./index.js'));
|
|
3
|
+
queue.register('rabbitmq', RabbitMqQueue.create());
|
|
4
|
+
}
|
|
5
|
+
const importCore = async () => {
|
|
6
|
+
try {
|
|
7
|
+
return await import('@zintrust/core');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
try {
|
|
11
|
+
return await import('@zintrust/core');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const core = (await importCore());
|
|
19
|
+
if (core.Queue !== undefined) {
|
|
20
|
+
await registerRabbitMqQueueDriver(core.Queue);
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zintrust/queue-rabbitmq",
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./register": {
|
|
17
|
+
"types": "./dist/register.d.ts",
|
|
18
|
+
"default": "./dist/register.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20.0.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@zintrust/core": "^0.1.12"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
}
|
|
34
|
+
}
|