@venizia/ignis-docs 0.0.5 → 0.0.6-0
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/package.json +1 -1
- package/wiki/best-practices/architecture-decisions.md +0 -8
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/components.md +2 -2
- package/wiki/guides/core-concepts/dependency-injection.md +1 -1
- package/wiki/guides/core-concepts/services.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +1 -1
- package/wiki/guides/tutorials/ecommerce-api.md +2 -2
- package/wiki/guides/tutorials/realtime-chat.md +6 -6
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/bootstrapping.md +0 -2
- package/wiki/references/base/components.md +2 -2
- package/wiki/references/base/controllers.md +0 -1
- package/wiki/references/base/datasources.md +1 -1
- package/wiki/references/base/dependency-injection.md +1 -1
- package/wiki/references/base/filter-system/quick-reference.md +0 -14
- package/wiki/references/base/middlewares.md +0 -8
- package/wiki/references/base/providers.md +0 -9
- package/wiki/references/base/services.md +0 -1
- package/wiki/references/components/authentication/api.md +444 -0
- package/wiki/references/components/authentication/errors.md +177 -0
- package/wiki/references/components/authentication/index.md +571 -0
- package/wiki/references/components/authentication/usage.md +781 -0
- package/wiki/references/components/health-check.md +292 -103
- package/wiki/references/components/index.md +14 -12
- package/wiki/references/components/mail/api.md +505 -0
- package/wiki/references/components/mail/errors.md +176 -0
- package/wiki/references/components/mail/index.md +535 -0
- package/wiki/references/components/mail/usage.md +404 -0
- package/wiki/references/components/request-tracker.md +229 -25
- package/wiki/references/components/socket-io/api.md +1051 -0
- package/wiki/references/components/socket-io/errors.md +119 -0
- package/wiki/references/components/socket-io/index.md +410 -0
- package/wiki/references/components/socket-io/usage.md +322 -0
- package/wiki/references/components/static-asset/api.md +261 -0
- package/wiki/references/components/static-asset/errors.md +89 -0
- package/wiki/references/components/static-asset/index.md +617 -0
- package/wiki/references/components/static-asset/usage.md +364 -0
- package/wiki/references/components/swagger.md +390 -110
- package/wiki/references/components/template/api-page.md +125 -0
- package/wiki/references/components/template/errors-page.md +100 -0
- package/wiki/references/components/template/index.md +104 -0
- package/wiki/references/components/template/setup-page.md +134 -0
- package/wiki/references/components/template/single-page.md +132 -0
- package/wiki/references/components/template/usage-page.md +127 -0
- package/wiki/references/components/websocket/api.md +508 -0
- package/wiki/references/components/websocket/errors.md +123 -0
- package/wiki/references/components/websocket/index.md +453 -0
- package/wiki/references/components/websocket/usage.md +475 -0
- package/wiki/references/helpers/cron/index.md +224 -0
- package/wiki/references/helpers/crypto/index.md +537 -0
- package/wiki/references/helpers/env/index.md +214 -0
- package/wiki/references/helpers/error/index.md +232 -0
- package/wiki/references/helpers/index.md +16 -15
- package/wiki/references/helpers/inversion/index.md +608 -0
- package/wiki/references/helpers/logger/index.md +600 -0
- package/wiki/references/helpers/network/api.md +986 -0
- package/wiki/references/helpers/network/index.md +620 -0
- package/wiki/references/helpers/queue/index.md +589 -0
- package/wiki/references/helpers/redis/index.md +495 -0
- package/wiki/references/helpers/socket-io/api.md +497 -0
- package/wiki/references/helpers/socket-io/index.md +513 -0
- package/wiki/references/helpers/storage/api.md +705 -0
- package/wiki/references/helpers/storage/index.md +583 -0
- package/wiki/references/helpers/template/index.md +66 -0
- package/wiki/references/helpers/template/single-page.md +126 -0
- package/wiki/references/helpers/testing/index.md +510 -0
- package/wiki/references/helpers/types/index.md +512 -0
- package/wiki/references/helpers/uid/index.md +272 -0
- package/wiki/references/helpers/websocket/api.md +736 -0
- package/wiki/references/helpers/websocket/index.md +574 -0
- package/wiki/references/helpers/worker-thread/index.md +470 -0
- package/wiki/references/quick-reference.md +3 -18
- package/wiki/references/utilities/jsx.md +1 -8
- package/wiki/references/utilities/statuses.md +0 -7
- package/wiki/references/components/authentication.md +0 -476
- package/wiki/references/components/mail.md +0 -687
- package/wiki/references/components/socket-io.md +0 -562
- package/wiki/references/components/static-asset.md +0 -1277
- package/wiki/references/helpers/cron.md +0 -108
- package/wiki/references/helpers/crypto.md +0 -132
- package/wiki/references/helpers/env.md +0 -83
- package/wiki/references/helpers/error.md +0 -97
- package/wiki/references/helpers/inversion.md +0 -176
- package/wiki/references/helpers/logger.md +0 -296
- package/wiki/references/helpers/network.md +0 -396
- package/wiki/references/helpers/queue.md +0 -150
- package/wiki/references/helpers/redis.md +0 -142
- package/wiki/references/helpers/socket-io.md +0 -932
- package/wiki/references/helpers/storage.md +0 -665
- package/wiki/references/helpers/testing.md +0 -133
- package/wiki/references/helpers/types.md +0 -167
- package/wiki/references/helpers/uid.md +0 -167
- package/wiki/references/helpers/worker-thread.md +0 -178
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
# Queue
|
|
2
|
+
|
|
3
|
+
Message queuing and asynchronous task management with BullMQ, MQTT, and in-memory solutions.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Class | Extends | Peer Dependency | Use Case |
|
|
8
|
+
|-------|---------|-----------------|----------|
|
|
9
|
+
| **BullMQHelper** | `BaseHelper` | `bullmq` (^5.63.1) | Redis-backed job queue -- background processing, task scheduling |
|
|
10
|
+
| **MQTTClientHelper** | `BaseHelper` | `mqtt` (^5.14.1) | MQTT broker messaging -- real-time events, IoT |
|
|
11
|
+
| **QueueHelper** | `BaseHelper` | None | In-memory generator queue -- sequential tasks, single process |
|
|
12
|
+
|
|
13
|
+
#### Common Operations
|
|
14
|
+
|
|
15
|
+
| Helper | Subscribe / Consume | Publish / Produce |
|
|
16
|
+
|--------|---------------------|-------------------|
|
|
17
|
+
| **BullMQ** | Create with `role: 'worker'` | `queue.add(name, data)` via the exposed BullMQ `Queue` instance |
|
|
18
|
+
| **MQTT** | `subscribe({ topics })` | `publish({ topic, message })` |
|
|
19
|
+
| **In-Memory** | `new QueueHelper({ onMessage })` | `enqueue(payload)` |
|
|
20
|
+
|
|
21
|
+
#### Import Paths
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// In-memory queue (from base package)
|
|
25
|
+
import { QueueHelper, QueueStatuses } from '@venizia/ignis-helpers';
|
|
26
|
+
import type { TQueueStatus, TQueueElement } from '@venizia/ignis-helpers';
|
|
27
|
+
|
|
28
|
+
// BullMQ (separate export path)
|
|
29
|
+
import { BullMQHelper } from '@venizia/ignis-helpers/bullmq';
|
|
30
|
+
import type { TBullQueueRole } from '@venizia/ignis-helpers/bullmq';
|
|
31
|
+
|
|
32
|
+
// MQTT (separate export path)
|
|
33
|
+
import { MQTTClientHelper } from '@venizia/ignis-helpers/mqtt';
|
|
34
|
+
import type { IMQTTClientOptions } from '@venizia/ignis-helpers/mqtt';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Creating an Instance
|
|
38
|
+
|
|
39
|
+
All three queue helpers extend `BaseHelper`, providing scoped logging via `this.logger`.
|
|
40
|
+
|
|
41
|
+
### BullMQHelper
|
|
42
|
+
|
|
43
|
+
The `BullMQHelper` wraps the BullMQ library for Redis-backed job queuing. It operates in one of two roles: `'queue'` (producer) or `'worker'` (consumer). The role is set at construction time and determines which BullMQ primitives are initialized.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { DefaultRedisHelper } from '@venizia/ignis-helpers';
|
|
47
|
+
import { BullMQHelper } from '@venizia/ignis-helpers/bullmq';
|
|
48
|
+
|
|
49
|
+
const worker = new BullMQHelper({
|
|
50
|
+
queueName: 'email-queue',
|
|
51
|
+
identifier: 'email-worker',
|
|
52
|
+
role: 'worker',
|
|
53
|
+
redisConnection: redisHelper,
|
|
54
|
+
numberOfWorker: 3,
|
|
55
|
+
lockDuration: 90 * 60 * 1000,
|
|
56
|
+
onWorkerData: async (job) => {
|
|
57
|
+
console.log(`Processing job ${job.id}:`, job.data);
|
|
58
|
+
return { status: 'sent' };
|
|
59
|
+
},
|
|
60
|
+
onWorkerDataCompleted: async (job, result) => {
|
|
61
|
+
console.log(`Job ${job.id} completed:`, result);
|
|
62
|
+
},
|
|
63
|
+
onWorkerDataFail: async (job, error) => {
|
|
64
|
+
console.error(`Job ${job?.id} failed:`, error.message);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### IBullMQOptions
|
|
70
|
+
|
|
71
|
+
`IBullMQOptions<TQueueElement = any, TQueueResult = any>`
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default | Description |
|
|
74
|
+
|--------|------|---------|-------------|
|
|
75
|
+
| `queueName` | `string` | -- | Name of the BullMQ queue. Must be non-empty. |
|
|
76
|
+
| `identifier` | `string` | -- | Unique identifier used for scoped logging. |
|
|
77
|
+
| `role` | `TBullQueueRole` | -- | `'queue'` (producer) or `'worker'` (consumer). |
|
|
78
|
+
| `redisConnection` | `DefaultRedisHelper` | -- | Redis helper instance. The helper calls `getClient().duplicate()` internally. |
|
|
79
|
+
| `numberOfWorker` | `number` | `1` | Worker concurrency (number of jobs processed in parallel). |
|
|
80
|
+
| `lockDuration` | `number` | `5400000` | Job lock duration in milliseconds (default: 90 minutes). |
|
|
81
|
+
| `onWorkerData` | `(job: Job<TQueueElement, TQueueResult>) => Promise<any>` | `undefined` | Job processing callback. If omitted, the worker logs job details. |
|
|
82
|
+
| `onWorkerDataCompleted` | `(job: Job<TQueueElement, TQueueResult>, result: any) => Promise<void>` | `undefined` | Callback fired when a job completes successfully. |
|
|
83
|
+
| `onWorkerDataFail` | `(job: Job<TQueueElement, TQueueResult> \| undefined, error: Error) => Promise<void>` | `undefined` | Callback fired when a job fails. |
|
|
84
|
+
|
|
85
|
+
> [!IMPORTANT]
|
|
86
|
+
> Pass the `DefaultRedisHelper` instance to `redisConnection`, **not** the raw ioredis client. The helper internally calls `redisConnection.getClient().duplicate()` to create dedicated connections for the queue and worker.
|
|
87
|
+
|
|
88
|
+
### MQTTClientHelper
|
|
89
|
+
|
|
90
|
+
The `MQTTClientHelper` provides a pub/sub interface to an MQTT broker. The client connects automatically during construction.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { MQTTClientHelper } from '@venizia/ignis-helpers/mqtt';
|
|
94
|
+
|
|
95
|
+
const mqttClient = new MQTTClientHelper({
|
|
96
|
+
identifier: 'sensor-client',
|
|
97
|
+
url: 'mqtt://localhost:1883',
|
|
98
|
+
options: {
|
|
99
|
+
username: 'user',
|
|
100
|
+
password: 'password',
|
|
101
|
+
},
|
|
102
|
+
onMessage: ({ topic, message }) => {
|
|
103
|
+
console.log(`Received on ${topic}:`, message.toString());
|
|
104
|
+
},
|
|
105
|
+
onConnect: () => {
|
|
106
|
+
console.log('Connected to MQTT broker');
|
|
107
|
+
},
|
|
108
|
+
onDisconnect: () => {
|
|
109
|
+
console.log('Disconnected from MQTT broker');
|
|
110
|
+
},
|
|
111
|
+
onError: (error) => {
|
|
112
|
+
console.error('MQTT error:', error);
|
|
113
|
+
},
|
|
114
|
+
onClose: (error) => {
|
|
115
|
+
if (error) console.error('Connection closed with error:', error);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### IMQTTClientOptions
|
|
121
|
+
|
|
122
|
+
| Option | Type | Default | Description |
|
|
123
|
+
|--------|------|---------|-------------|
|
|
124
|
+
| `identifier` | `string` | -- | Unique identifier for scoped logging. |
|
|
125
|
+
| `url` | `string` | -- | MQTT broker URL (e.g., `mqtt://localhost:1883`). Must be non-empty. |
|
|
126
|
+
| `options` | `mqtt.IClientOptions` | -- | MQTT.js client options (username, password, keepalive, etc.). |
|
|
127
|
+
| `onMessage` | `(opts: { topic: string; message: Buffer }) => void` | -- | Message handler. Required. |
|
|
128
|
+
| `onConnect` | `() => void` | `undefined` | Callback fired when the client connects to the broker. |
|
|
129
|
+
| `onDisconnect` | `() => void` | `undefined` | Callback fired on disconnection. |
|
|
130
|
+
| `onError` | `(error: Error) => void` | `undefined` | Callback fired on client errors. |
|
|
131
|
+
| `onClose` | `(error?: Error) => void` | `undefined` | Callback fired when the connection is closed. |
|
|
132
|
+
|
|
133
|
+
### QueueHelper
|
|
134
|
+
|
|
135
|
+
The `QueueHelper` is a generator-based, in-memory queue with a built-in state machine. It processes enqueued items one at a time, making it suitable for sequential task processing within a single process.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { QueueHelper } from '@venizia/ignis-helpers';
|
|
139
|
+
|
|
140
|
+
const queue = new QueueHelper<string>({
|
|
141
|
+
identifier: 'task-queue',
|
|
142
|
+
autoDispatch: true,
|
|
143
|
+
onMessage: async ({ identifier, queueElement }) => {
|
|
144
|
+
console.log(`[${identifier}] Processing:`, queueElement.payload);
|
|
145
|
+
},
|
|
146
|
+
onDataEnqueue: async ({ identifier, queueElement }) => {
|
|
147
|
+
console.log(`[${identifier}] Enqueued:`, queueElement.payload);
|
|
148
|
+
},
|
|
149
|
+
onDataDequeue: async ({ identifier, queueElement }) => {
|
|
150
|
+
console.log(`[${identifier}] Dequeued:`, queueElement.payload);
|
|
151
|
+
},
|
|
152
|
+
onStateChange: async ({ identifier, from, to }) => {
|
|
153
|
+
console.log(`[${identifier}] State: ${from} -> ${to}`);
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### IQueueCallback
|
|
159
|
+
|
|
160
|
+
`IQueueCallback<TElementPayload>`
|
|
161
|
+
|
|
162
|
+
| Option | Type | Default | Description |
|
|
163
|
+
|--------|------|---------|-------------|
|
|
164
|
+
| `identifier` | `string` | -- | Unique identifier for scoped logging. |
|
|
165
|
+
| `autoDispatch` | `boolean` | `true` | If `true`, automatically triggers processing when an element is enqueued. |
|
|
166
|
+
| `onMessage` | `(opts: { identifier: string; queueElement: TQueueElement<T> }) => ValueOrPromise<void>` | `undefined` | Message processing callback. If omitted, the generator exits immediately. |
|
|
167
|
+
| `onDataEnqueue` | `(opts: { identifier: string; queueElement: TQueueElement<T> }) => ValueOrPromise<void>` | `undefined` | Callback fired after an element is added to the queue. |
|
|
168
|
+
| `onDataDequeue` | `(opts: { identifier: string; queueElement: TQueueElement<T> }) => ValueOrPromise<void>` | `undefined` | Callback fired after an element is removed from the queue. |
|
|
169
|
+
| `onStateChange` | `(opts: { identifier: string; from: TQueueStatus; to: TQueueStatus }) => ValueOrPromise<void>` | `undefined` | Callback fired on every state transition. |
|
|
170
|
+
|
|
171
|
+
#### TQueueElement
|
|
172
|
+
|
|
173
|
+
Each element in the queue is wrapped in a `TQueueElement`:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
type TQueueElement<T> = { isLocked: boolean; payload: T };
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Usage
|
|
180
|
+
|
|
181
|
+
### BullMQ -- Adding Jobs
|
|
182
|
+
|
|
183
|
+
When created with `role: 'queue'`, the helper exposes a `queue` property (a BullMQ `Queue` instance) for adding jobs.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const producer = new BullMQHelper({
|
|
187
|
+
queueName: 'email-queue',
|
|
188
|
+
identifier: 'email-producer',
|
|
189
|
+
role: 'queue',
|
|
190
|
+
redisConnection: redisHelper,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Add a job via the BullMQ Queue API
|
|
194
|
+
await producer.queue.add('send-welcome', { email: 'user@example.com', template: 'welcome' });
|
|
195
|
+
await producer.queue.add('send-reset', { email: 'user@example.com', token: 'abc123' });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
> [!TIP]
|
|
199
|
+
> You can also use the static factory method: `BullMQHelper.newInstance({ ... })` which is equivalent to `new BullMQHelper({ ... })`.
|
|
200
|
+
|
|
201
|
+
#### Default Job Options
|
|
202
|
+
|
|
203
|
+
Jobs are created with these defaults:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
defaultJobOptions: {
|
|
207
|
+
removeOnComplete: true,
|
|
208
|
+
removeOnFail: true,
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### BullMQ -- Processing Jobs
|
|
213
|
+
|
|
214
|
+
When created with `role: 'worker'`, the helper initializes a BullMQ `Worker` that listens for jobs on the specified queue.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const consumer = new BullMQHelper<{ email: string }, { status: string }>({
|
|
218
|
+
queueName: 'email-queue',
|
|
219
|
+
identifier: 'email-consumer',
|
|
220
|
+
role: 'worker',
|
|
221
|
+
redisConnection: redisHelper,
|
|
222
|
+
numberOfWorker: 3,
|
|
223
|
+
lockDuration: 10 * 60 * 1000, // 10 minutes
|
|
224
|
+
onWorkerData: async (job) => {
|
|
225
|
+
await sendEmail(job.data.email);
|
|
226
|
+
return { status: 'sent' };
|
|
227
|
+
},
|
|
228
|
+
onWorkerDataCompleted: async (job, result) => {
|
|
229
|
+
console.log(`Job ${job.id} done:`, result);
|
|
230
|
+
},
|
|
231
|
+
onWorkerDataFail: async (job, error) => {
|
|
232
|
+
console.error(`Job ${job?.id} failed:`, error.message);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If `onWorkerData` is not provided, the worker logs the job's `id`, `name`, and `data` at the info level.
|
|
238
|
+
|
|
239
|
+
### BullMQ -- Redis Cluster
|
|
240
|
+
|
|
241
|
+
When using Redis Cluster with BullMQ, you must set `maxRetriesPerRequest: null` on the cluster config -- this is **required** by BullMQ.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { Cluster } from 'ioredis';
|
|
245
|
+
import { DefaultRedisHelper } from '@venizia/ignis-helpers';
|
|
246
|
+
import { BullMQHelper } from '@venizia/ignis-helpers/bullmq';
|
|
247
|
+
|
|
248
|
+
const cluster = new Cluster(
|
|
249
|
+
[
|
|
250
|
+
{ host: 'node1.redis.example.com', port: 6379 },
|
|
251
|
+
{ host: 'node2.redis.example.com', port: 6379 },
|
|
252
|
+
{ host: 'node3.redis.example.com', port: 6379 },
|
|
253
|
+
],
|
|
254
|
+
{
|
|
255
|
+
maxRetriesPerRequest: null, // Required by BullMQ
|
|
256
|
+
enableReadyCheck: true,
|
|
257
|
+
scaleReads: 'slave',
|
|
258
|
+
redisOptions: {
|
|
259
|
+
password: 'your-password',
|
|
260
|
+
tls: {},
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const redisHelper = new DefaultRedisHelper({
|
|
266
|
+
scope: 'BullMQ',
|
|
267
|
+
identifier: 'cluster-redis',
|
|
268
|
+
client: cluster,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const worker = BullMQHelper.newInstance({
|
|
272
|
+
queueName: 'my-queue',
|
|
273
|
+
identifier: 'cluster-worker',
|
|
274
|
+
role: 'worker',
|
|
275
|
+
redisConnection: redisHelper,
|
|
276
|
+
onWorkerData: async (job) => {
|
|
277
|
+
// process job
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### BullMQ -- Graceful Shutdown
|
|
283
|
+
|
|
284
|
+
Call `close()` to gracefully shut down both the worker and queue connections.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
await producer.close();
|
|
288
|
+
await consumer.close();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`close()` calls `worker.close()` and `queue.close()` in sequence. If closing fails, it logs the error and re-throws.
|
|
292
|
+
|
|
293
|
+
### MQTT -- Subscribe and Publish
|
|
294
|
+
|
|
295
|
+
After the client connects to the broker, use `subscribe()` and `publish()` for topic-based messaging.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Subscribe to multiple topics
|
|
299
|
+
await mqttClient.subscribe({ topics: ['sensors/temperature', 'sensors/humidity'] });
|
|
300
|
+
|
|
301
|
+
// Publish a string message
|
|
302
|
+
await mqttClient.publish({ topic: 'sensors/temperature', message: '23.5' });
|
|
303
|
+
|
|
304
|
+
// Publish a Buffer message
|
|
305
|
+
await mqttClient.publish({ topic: 'sensors/raw', message: Buffer.from([0x01, 0x02]) });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
> [!NOTE]
|
|
309
|
+
> Both `subscribe()` and `publish()` reject with an `ApplicationError` (status 400) if the MQTT client is not connected. Ensure the connection is established before calling these methods.
|
|
310
|
+
|
|
311
|
+
### MQTT -- Event Handling
|
|
312
|
+
|
|
313
|
+
The `MQTTClientHelper` calls `configure()` automatically during construction. Once connected, the `onMessage` callback receives messages for all subscribed topics.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const client = new MQTTClientHelper({
|
|
317
|
+
identifier: 'iot-gateway',
|
|
318
|
+
url: 'mqtt://broker.example.com:1883',
|
|
319
|
+
options: { keepalive: 60 },
|
|
320
|
+
onConnect: () => {
|
|
321
|
+
// Subscribe once connected
|
|
322
|
+
client.subscribe({ topics: ['devices/+/status'] });
|
|
323
|
+
},
|
|
324
|
+
onMessage: ({ topic, message }) => {
|
|
325
|
+
const deviceId = topic.split('/')[1];
|
|
326
|
+
console.log(`Device ${deviceId}:`, message.toString());
|
|
327
|
+
},
|
|
328
|
+
onError: (error) => {
|
|
329
|
+
console.error('Connection error:', error.message);
|
|
330
|
+
},
|
|
331
|
+
onClose: () => {
|
|
332
|
+
console.log('Connection closed');
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### In-Memory Queue -- Enqueueing and Processing
|
|
338
|
+
|
|
339
|
+
With `autoDispatch: true` (default), elements are processed automatically as they are enqueued.
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { QueueHelper } from '@venizia/ignis-helpers';
|
|
343
|
+
|
|
344
|
+
const queue = new QueueHelper<{ task: string; priority: number }>({
|
|
345
|
+
identifier: 'task-processor',
|
|
346
|
+
onMessage: async ({ queueElement }) => {
|
|
347
|
+
console.log('Processing:', queueElement.payload.task);
|
|
348
|
+
await performTask(queueElement.payload);
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Elements are processed one at a time, in order
|
|
353
|
+
await queue.enqueue({ task: 'resize-image', priority: 1 });
|
|
354
|
+
await queue.enqueue({ task: 'send-notification', priority: 2 });
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Manual Dispatch
|
|
358
|
+
|
|
359
|
+
Set `autoDispatch: false` to control when processing begins. Call `nextMessage()` to trigger processing of the next element.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const queue = new QueueHelper<string>({
|
|
363
|
+
identifier: 'manual-queue',
|
|
364
|
+
autoDispatch: false,
|
|
365
|
+
onMessage: async ({ queueElement }) => {
|
|
366
|
+
console.log('Processing:', queueElement.payload);
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await queue.enqueue('item-1');
|
|
371
|
+
await queue.enqueue('item-2');
|
|
372
|
+
|
|
373
|
+
// Nothing processed yet -- trigger manually
|
|
374
|
+
queue.nextMessage(); // processes 'item-1'
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
> [!NOTE]
|
|
378
|
+
> `nextMessage()` only triggers processing when the queue state is `WAITING`. It logs a warning and returns if the queue is in any other state.
|
|
379
|
+
|
|
380
|
+
### In-Memory Queue -- State Machine
|
|
381
|
+
|
|
382
|
+
The `QueueHelper` uses a state machine to manage its lifecycle:
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
WAITING ──enqueue──> PROCESSING ──done──> WAITING
|
|
386
|
+
| |
|
|
387
|
+
└──lock()──> LOCKED <─┘
|
|
388
|
+
|
|
|
389
|
+
unlock()──> WAITING
|
|
390
|
+
|
|
|
391
|
+
settle()──> SETTLED (terminal)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
| State | Value | Description |
|
|
395
|
+
|-------|-------|-------------|
|
|
396
|
+
| `QueueStatuses.WAITING` | `'000_WAITING'` | Idle, ready to process the next element. |
|
|
397
|
+
| `QueueStatuses.PROCESSING` | `'100_PROCESSING'` | Currently handling a message via `onMessage`. |
|
|
398
|
+
| `QueueStatuses.LOCKED` | `'200_LOCKED'` | Paused. No new processing until `unlock()` is called. |
|
|
399
|
+
| `QueueStatuses.SETTLED` | `'300_SETTLED'` | Terminal state. No more elements accepted. |
|
|
400
|
+
|
|
401
|
+
You can validate a state string with `QueueStatuses.isValid(state)`.
|
|
402
|
+
|
|
403
|
+
### In-Memory Queue -- Lock and Unlock
|
|
404
|
+
|
|
405
|
+
Use `lock()` / `unlock()` to pause and resume processing without losing queued elements.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Pause the queue (e.g., during maintenance)
|
|
409
|
+
queue.lock();
|
|
410
|
+
|
|
411
|
+
// Elements can still be enqueued while locked,
|
|
412
|
+
// but they won't be processed until unlocked
|
|
413
|
+
await queue.enqueue('queued-while-locked');
|
|
414
|
+
|
|
415
|
+
// Resume processing
|
|
416
|
+
queue.unlock({ shouldProcessNextElement: true });
|
|
417
|
+
|
|
418
|
+
// Resume without processing the next element
|
|
419
|
+
queue.unlock({ shouldProcessNextElement: false });
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`lock()` logs an error and returns if the queue is already `LOCKED` or `SETTLED`.
|
|
423
|
+
|
|
424
|
+
`unlock()` logs an error and returns if the queue is `SETTLED` (past `LOCKED` state).
|
|
425
|
+
|
|
426
|
+
### In-Memory Queue -- Settling and Closing
|
|
427
|
+
|
|
428
|
+
Once settled, the queue rejects new elements and transitions to `SETTLED` after all in-flight work completes.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
// Signal that no more elements will be added
|
|
432
|
+
queue.settle();
|
|
433
|
+
|
|
434
|
+
// Check if the queue is settled and empty
|
|
435
|
+
if (queue.isSettled()) {
|
|
436
|
+
console.log('All work done, total events:', queue.getTotalEvent());
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Or close entirely (settle + terminate generator)
|
|
440
|
+
queue.close();
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
`settle()` sets `isSettleRequested` to `true`. If the queue is not currently processing, it immediately transitions to `SETTLED`. If processing, it transitions to `SETTLED` after the current message completes and the storage is empty.
|
|
444
|
+
|
|
445
|
+
`close()` calls `settle()` and then terminates the internal generator via `generator.return()`.
|
|
446
|
+
|
|
447
|
+
## API Summary
|
|
448
|
+
|
|
449
|
+
### BullMQHelper
|
|
450
|
+
|
|
451
|
+
| Method | Returns | Description |
|
|
452
|
+
|--------|---------|-------------|
|
|
453
|
+
| `static newInstance(opts)` | `BullMQHelper` | Factory method, equivalent to `new BullMQHelper(opts)`. |
|
|
454
|
+
| `configureQueue()` | `void` | Sets up the BullMQ `Queue` instance. Called automatically for `role: 'queue'`. |
|
|
455
|
+
| `configureWorker()` | `void` | Sets up the BullMQ `Worker` instance. Called automatically for `role: 'worker'`. |
|
|
456
|
+
| `configure()` | `void` | Delegates to `configureQueue()` or `configureWorker()` based on the `role`. |
|
|
457
|
+
| `close()` | `Promise<void>` | Gracefully closes the worker and queue connections. |
|
|
458
|
+
|
|
459
|
+
#### Properties
|
|
460
|
+
|
|
461
|
+
| Property | Type | Description |
|
|
462
|
+
|----------|------|-------------|
|
|
463
|
+
| `queue` | `Queue<TQueueElement, TQueueResult>` | BullMQ `Queue` instance (available when `role: 'queue'`). |
|
|
464
|
+
| `worker` | `Worker<TQueueElement, TQueueResult>` | BullMQ `Worker` instance (available when `role: 'worker'`). |
|
|
465
|
+
|
|
466
|
+
### MQTTClientHelper
|
|
467
|
+
|
|
468
|
+
| Method | Returns | Description |
|
|
469
|
+
|--------|---------|-------------|
|
|
470
|
+
| `configure()` | `void` | Connects to the MQTT broker. Called automatically by the constructor. |
|
|
471
|
+
| `subscribe(opts)` | `Promise<string[]>` | Subscribe to one or more topics. `opts: { topics: string[] }` |
|
|
472
|
+
| `publish(opts)` | `Promise<{ topic, message }>` | Publish a message to a topic. `opts: { topic: string; message: string \| Buffer }` |
|
|
473
|
+
|
|
474
|
+
### QueueHelper
|
|
475
|
+
|
|
476
|
+
| Method | Returns | Description |
|
|
477
|
+
|--------|---------|-------------|
|
|
478
|
+
| `enqueue(payload)` | `Promise<void>` | Add an element to the queue. Rejected if settled. |
|
|
479
|
+
| `dequeue()` | `TQueueElement<T> \| undefined` | Remove and return the first element. |
|
|
480
|
+
| `nextMessage()` | `void` | Manually trigger processing of the next element. Only works in `WAITING` state. |
|
|
481
|
+
| `lock()` | `void` | Pause processing. State becomes `LOCKED`. |
|
|
482
|
+
| `unlock(opts)` | `void` | Resume processing. `opts: { shouldProcessNextElement?: boolean }` (default: `true`). |
|
|
483
|
+
| `settle()` | `void` | Mark queue as settled. No new elements accepted after this. |
|
|
484
|
+
| `isSettled()` | `boolean` | Returns `true` if state is `SETTLED` and storage is empty. |
|
|
485
|
+
| `close()` | `void` | Settle the queue and terminate the internal generator. |
|
|
486
|
+
| `getElementAt(position)` | `TQueueElement<T>` | Peek at an element by index. |
|
|
487
|
+
| `getState()` | `TQueueStatus` | Returns the current queue state. |
|
|
488
|
+
| `getTotalEvent()` | `number` | Returns the total number of elements ever enqueued. |
|
|
489
|
+
| `getProcessingEvents()` | `Set<TQueueElement<T>>` | Returns the set of currently processing elements. |
|
|
490
|
+
|
|
491
|
+
## Troubleshooting
|
|
492
|
+
|
|
493
|
+
### "Invalid queue name"
|
|
494
|
+
|
|
495
|
+
**Cause:** The `queueName` option is empty or falsy when creating a BullMQ queue or worker.
|
|
496
|
+
|
|
497
|
+
**Fix:** Provide a non-empty `queueName`:
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
// Wrong
|
|
501
|
+
new BullMQHelper({ queueName: '', role: 'queue', ... });
|
|
502
|
+
|
|
503
|
+
// Correct
|
|
504
|
+
new BullMQHelper({ queueName: 'my-email-queue', role: 'queue', ... });
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### "Invalid client role to configure"
|
|
508
|
+
|
|
509
|
+
**Cause:** The `role` option is missing or not one of `'queue'` / `'worker'`.
|
|
510
|
+
|
|
511
|
+
**Fix:** Set `role` to either `'queue'` or `'worker'`:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// Wrong
|
|
515
|
+
new BullMQHelper({ role: undefined as any, ... });
|
|
516
|
+
|
|
517
|
+
// Correct
|
|
518
|
+
new BullMQHelper({ role: 'worker', ... });
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### "Invalid url to configure mqtt client!"
|
|
522
|
+
|
|
523
|
+
**Cause:** The `url` option is empty when constructing an `MQTTClientHelper`. Throws an `ApplicationError` with status 500.
|
|
524
|
+
|
|
525
|
+
**Fix:** Pass a valid MQTT broker URL:
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
// Wrong
|
|
529
|
+
new MQTTClientHelper({ url: '', ... });
|
|
530
|
+
|
|
531
|
+
// Correct
|
|
532
|
+
new MQTTClientHelper({ url: 'mqtt://localhost:1883', ... });
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### "MQTT Client is not available to subscribe topic!" / "MQTT Client is not available to publish message!"
|
|
536
|
+
|
|
537
|
+
**Cause:** `subscribe()` or `publish()` was called before the MQTT client finished connecting, or after the client disconnected. Throws an `ApplicationError` with status 400.
|
|
538
|
+
|
|
539
|
+
**Fix:** Wait for the `onConnect` callback before subscribing or publishing, or verify the client is connected:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
const client = new MQTTClientHelper({
|
|
543
|
+
identifier: 'my-client',
|
|
544
|
+
url: 'mqtt://localhost:1883',
|
|
545
|
+
options: {},
|
|
546
|
+
onConnect: () => {
|
|
547
|
+
// Safe to subscribe/publish here
|
|
548
|
+
client.subscribe({ topics: ['my/topic'] });
|
|
549
|
+
},
|
|
550
|
+
onMessage: ({ topic, message }) => { /* ... */ },
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Elements not processing in In-Memory Queue
|
|
555
|
+
|
|
556
|
+
**Cause:** Multiple possible reasons why `onMessage` is never called.
|
|
557
|
+
|
|
558
|
+
**Checklist:**
|
|
559
|
+
- Verify `onMessage` callback is provided -- the generator logs a warning and exits if missing
|
|
560
|
+
- Check if the queue is locked -- call `unlock({ shouldProcessNextElement: true })` to resume
|
|
561
|
+
- Check if `autoDispatch` is `false` -- call `nextMessage()` manually after each `enqueue()`
|
|
562
|
+
- Check if the queue is settled -- a settled queue rejects new elements; create a new `QueueHelper` instance
|
|
563
|
+
|
|
564
|
+
### "Queue was SETTLED | No more element acceptable"
|
|
565
|
+
|
|
566
|
+
**Cause:** `enqueue()` was called after `settle()` or `close()`.
|
|
567
|
+
|
|
568
|
+
**Fix:** Create a new `QueueHelper` instance if you need to continue processing:
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
queue.close();
|
|
572
|
+
|
|
573
|
+
// Start a new queue for further work
|
|
574
|
+
const newQueue = new QueueHelper<string>({
|
|
575
|
+
identifier: 'task-queue-v2',
|
|
576
|
+
onMessage: async ({ queueElement }) => { /* ... */ },
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## See Also
|
|
581
|
+
|
|
582
|
+
- **Other Helpers:**
|
|
583
|
+
- [Helpers Index](../index) -- All available helpers
|
|
584
|
+
- [Cron Helper](../cron/) -- Scheduled tasks with cron expressions
|
|
585
|
+
- [Redis Helper](../redis/) -- Redis connection management (required for BullMQ)
|
|
586
|
+
|
|
587
|
+
- **External Resources:**
|
|
588
|
+
- [BullMQ Documentation](https://docs.bullmq.io/) -- BullMQ queue library
|
|
589
|
+
- [MQTT.js](https://github.com/mqttjs/MQTT.js) -- MQTT client library
|