@usermetrics/queuebit 1.0.1 → 1.0.5
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 +92 -118
- package/docs/API.md +116 -376
- package/docs/EXAMPLES.md +94 -225
- package/docs/QUICKSTART.md +64 -65
- package/package.json +4 -5
- package/src/client.js +0 -3
package/docs/API.md
CHANGED
|
@@ -7,6 +7,7 @@ QueueBit is a high-performance, socket-based message queue system with guarantee
|
|
|
7
7
|
- [Server API](#server-api)
|
|
8
8
|
- [Client API](#client-api)
|
|
9
9
|
- [Message Format](#message-format)
|
|
10
|
+
- [Load Balancer](#load-balancer)
|
|
10
11
|
- [Examples](#examples)
|
|
11
12
|
|
|
12
13
|
---
|
|
@@ -15,15 +16,8 @@ QueueBit is a high-performance, socket-based message queue system with guarantee
|
|
|
15
16
|
|
|
16
17
|
### QueueBitServer
|
|
17
18
|
|
|
18
|
-
The server class that handles message queuing and delivery.
|
|
19
|
-
|
|
20
|
-
#### Constructor
|
|
21
|
-
|
|
22
|
-
Creates a new QueueBit server instance.
|
|
23
|
-
|
|
24
19
|
```javascript
|
|
25
|
-
const { QueueBitServer } = require('queuebit');
|
|
26
|
-
|
|
20
|
+
const { QueueBitServer } = require('queuebit/src/server');
|
|
27
21
|
const server = new QueueBitServer(options);
|
|
28
22
|
```
|
|
29
23
|
|
|
@@ -38,7 +32,7 @@ const server = new QueueBitServer(options);
|
|
|
38
32
|
|
|
39
33
|
```javascript
|
|
40
34
|
const server = new QueueBitServer({
|
|
41
|
-
port:
|
|
35
|
+
port: 3333,
|
|
42
36
|
maxQueueSize: 50000
|
|
43
37
|
});
|
|
44
38
|
```
|
|
@@ -51,51 +45,28 @@ Shuts down the server and closes all connections.
|
|
|
51
45
|
server.close();
|
|
52
46
|
```
|
|
53
47
|
|
|
54
|
-
**Example:**
|
|
55
|
-
|
|
56
|
-
```javascript
|
|
57
|
-
// Graceful shutdown
|
|
58
|
-
process.on('SIGINT', () => {
|
|
59
|
-
console.log('Shutting down...');
|
|
60
|
-
server.close();
|
|
61
|
-
process.exit(0);
|
|
62
|
-
});
|
|
63
|
-
```
|
|
64
|
-
|
|
65
48
|
---
|
|
66
49
|
|
|
67
50
|
## Client API
|
|
68
51
|
|
|
69
52
|
### QueueBitClient
|
|
70
53
|
|
|
71
|
-
The client class for connecting to a QueueBit server.
|
|
72
|
-
|
|
73
|
-
#### Constructor
|
|
74
|
-
|
|
75
|
-
Creates a new client connection to the server.
|
|
76
|
-
|
|
77
54
|
```javascript
|
|
78
|
-
|
|
55
|
+
// Node.js
|
|
56
|
+
const { QueueBitClient } = require('queuebit/src/client-node');
|
|
57
|
+
const client = new QueueBitClient('http://localhost:3333');
|
|
79
58
|
|
|
80
|
-
|
|
59
|
+
// Browser - include socket.io first
|
|
60
|
+
// <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
|
61
|
+
// <script src="src/client-browser.js"></script>
|
|
62
|
+
const client = new QueueBitClient('http://localhost:3333');
|
|
81
63
|
```
|
|
82
64
|
|
|
83
65
|
**Parameters:**
|
|
84
66
|
|
|
85
67
|
| Parameter | Type | Default | Description |
|
|
86
68
|
|-----------|------|---------|-------------|
|
|
87
|
-
| `url` | string | 'http://localhost:
|
|
88
|
-
|
|
89
|
-
**Example:**
|
|
90
|
-
|
|
91
|
-
```javascript
|
|
92
|
-
// Node.js
|
|
93
|
-
const { QueueBitClient } = require('queuebit');
|
|
94
|
-
const client = new QueueBitClient('http://localhost:3000');
|
|
95
|
-
|
|
96
|
-
// Browser
|
|
97
|
-
const client = new QueueBitClient('http://localhost:3000');
|
|
98
|
-
```
|
|
69
|
+
| `url` | string | 'http://localhost:3333' | Server URL |
|
|
99
70
|
|
|
100
71
|
---
|
|
101
72
|
|
|
@@ -103,13 +74,6 @@ const client = new QueueBitClient('http://localhost:3000');
|
|
|
103
74
|
|
|
104
75
|
Publishes a message to the queue.
|
|
105
76
|
|
|
106
|
-
**Parameters:**
|
|
107
|
-
|
|
108
|
-
| Parameter | Type | Required | Description |
|
|
109
|
-
|-----------|------|----------|-------------|
|
|
110
|
-
| `message` | object | Yes | The message data (must be a JSON object) |
|
|
111
|
-
| `options` | object | No | Publishing options |
|
|
112
|
-
|
|
113
77
|
**Options:**
|
|
114
78
|
|
|
115
79
|
| Option | Type | Description |
|
|
@@ -118,37 +82,22 @@ Publishes a message to the queue.
|
|
|
118
82
|
| `expiry` | Date | Expiration date for the message |
|
|
119
83
|
| `removeAfterRead` | boolean | Remove message after first delivery (default: false) |
|
|
120
84
|
|
|
121
|
-
**Returns:** Promise<{ success: boolean, messageId?: string, error?: string }
|
|
122
|
-
|
|
123
|
-
**Examples:**
|
|
85
|
+
**Returns:** `Promise<{ success: boolean, messageId?: string, error?: string }>`
|
|
124
86
|
|
|
125
87
|
```javascript
|
|
126
88
|
// Basic publish
|
|
127
|
-
|
|
128
|
-
text: 'Hello, World!'
|
|
129
|
-
});
|
|
130
|
-
console.log(result); // { success: true, messageId: 'uuid-here' }
|
|
89
|
+
await client.publish({ text: 'Hello, World!' });
|
|
131
90
|
|
|
132
|
-
// Publish to
|
|
133
|
-
await client.publish(
|
|
134
|
-
{ orderId: 12345, status: 'pending' },
|
|
135
|
-
{ subject: 'orders' }
|
|
136
|
-
);
|
|
91
|
+
// Publish to subject
|
|
92
|
+
await client.publish({ orderId: 123 }, { subject: 'orders' });
|
|
137
93
|
|
|
138
|
-
// Ephemeral
|
|
139
|
-
await client.publish(
|
|
140
|
-
{ notification: 'System update' },
|
|
141
|
-
{ removeAfterRead: true }
|
|
142
|
-
);
|
|
94
|
+
// Ephemeral (removed after first read)
|
|
95
|
+
await client.publish({ code: 'ABC' }, { removeAfterRead: true });
|
|
143
96
|
|
|
144
|
-
//
|
|
145
|
-
const expiryDate = new Date(Date.now() + 3600000);
|
|
97
|
+
// With expiry
|
|
146
98
|
await client.publish(
|
|
147
|
-
{
|
|
148
|
-
{
|
|
149
|
-
subject: 'temp',
|
|
150
|
-
expiry: expiryDate
|
|
151
|
-
}
|
|
99
|
+
{ data: 'temp' },
|
|
100
|
+
{ expiry: new Date(Date.now() + 3600000) }
|
|
152
101
|
);
|
|
153
102
|
```
|
|
154
103
|
|
|
@@ -156,157 +105,72 @@ await client.publish(
|
|
|
156
105
|
|
|
157
106
|
### subscribe(callback, options)
|
|
158
107
|
|
|
159
|
-
Subscribes to messages
|
|
160
|
-
|
|
161
|
-
**Parameters:**
|
|
162
|
-
|
|
163
|
-
| Parameter | Type | Required | Description |
|
|
164
|
-
|-----------|------|----------|-------------|
|
|
165
|
-
| `callback` | function | Yes | Function called when message is received |
|
|
166
|
-
| `options` | object | No | Subscription options |
|
|
108
|
+
Subscribes to messages. All existing non-ephemeral messages are replayed to new regular subscribers.
|
|
167
109
|
|
|
168
110
|
**Options:**
|
|
169
111
|
|
|
170
112
|
| Option | Type | Description |
|
|
171
113
|
|--------|------|-------------|
|
|
172
114
|
| `subject` | string | Subscribe to specific subject (default: 'default') |
|
|
173
|
-
| `queue` | string | Join a
|
|
174
|
-
|
|
175
|
-
**Callback Signature:**
|
|
115
|
+
| `queue` | string | Join a load balancer group for round-robin delivery |
|
|
176
116
|
|
|
177
|
-
|
|
178
|
-
(message: QueueMessage) => void
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**Returns:** Promise<{ success: boolean, subject?: string, queue?: string }>
|
|
182
|
-
|
|
183
|
-
**Examples:**
|
|
117
|
+
**Returns:** `Promise<{ success: boolean, subject: string, loadBalancer?: string, loadBalancerId?: number }>`
|
|
184
118
|
|
|
185
119
|
```javascript
|
|
186
|
-
//
|
|
120
|
+
// Regular subscription (all subscribers receive every message)
|
|
187
121
|
await client.subscribe((message) => {
|
|
188
122
|
console.log('Received:', message.data);
|
|
189
|
-
});
|
|
123
|
+
}, { subject: 'events' });
|
|
190
124
|
|
|
191
|
-
//
|
|
192
|
-
await client.subscribe(
|
|
193
|
-
(message)
|
|
194
|
-
|
|
195
|
-
},
|
|
196
|
-
{ subject: 'orders' }
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// Queue group (load-balanced across multiple subscribers)
|
|
200
|
-
await client.subscribe(
|
|
201
|
-
(message) => {
|
|
202
|
-
console.log('Processing task:', message.data);
|
|
203
|
-
// Only one subscriber in the group receives this message
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
subject: 'tasks',
|
|
207
|
-
queue: 'workers'
|
|
208
|
-
}
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// Multiple subjects
|
|
212
|
-
await client.subscribe(
|
|
213
|
-
(message) => console.log('High priority:', message.data),
|
|
214
|
-
{ subject: 'priority.high' }
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
await client.subscribe(
|
|
218
|
-
(message) => console.log('Low priority:', message.data),
|
|
219
|
-
{ subject: 'priority.low' }
|
|
220
|
-
);
|
|
125
|
+
// Load balancer (only one subscriber receives each message, round-robin)
|
|
126
|
+
await client.subscribe((message) => {
|
|
127
|
+
console.log('Processing:', message.data);
|
|
128
|
+
}, { subject: 'tasks', queue: 'my-workers' });
|
|
221
129
|
```
|
|
222
130
|
|
|
131
|
+
> **Note:** Load balancer subscriptions do **not** receive existing messages on subscribe, only new ones.
|
|
132
|
+
|
|
223
133
|
---
|
|
224
134
|
|
|
225
135
|
### unsubscribe(options)
|
|
226
136
|
|
|
227
137
|
Unsubscribes from messages.
|
|
228
138
|
|
|
229
|
-
**Parameters:**
|
|
230
|
-
|
|
231
|
-
| Parameter | Type | Required | Description |
|
|
232
|
-
|-----------|------|----------|-------------|
|
|
233
|
-
| `options` | object | No | Unsubscription options |
|
|
234
|
-
|
|
235
139
|
**Options:**
|
|
236
140
|
|
|
237
141
|
| Option | Type | Description |
|
|
238
142
|
|--------|------|-------------|
|
|
239
143
|
| `subject` | string | Subject to unsubscribe from (default: 'default') |
|
|
240
|
-
| `queue` | string |
|
|
241
|
-
|
|
242
|
-
**Returns:** Promise<{ success: boolean }>
|
|
144
|
+
| `queue` | string | Load balancer group to leave |
|
|
243
145
|
|
|
244
|
-
**
|
|
146
|
+
**Returns:** `Promise<{ success: boolean }>`
|
|
245
147
|
|
|
246
148
|
```javascript
|
|
247
|
-
// Unsubscribe from default subject
|
|
248
|
-
await client.unsubscribe();
|
|
249
|
-
|
|
250
|
-
// Unsubscribe from specific subject
|
|
251
149
|
await client.unsubscribe({ subject: 'orders' });
|
|
252
|
-
|
|
253
|
-
// Leave queue group
|
|
254
|
-
await client.unsubscribe({
|
|
255
|
-
subject: 'tasks',
|
|
256
|
-
queue: 'workers'
|
|
257
|
-
});
|
|
150
|
+
await client.unsubscribe({ subject: 'tasks', queue: 'my-workers' });
|
|
258
151
|
```
|
|
259
152
|
|
|
260
153
|
---
|
|
261
154
|
|
|
262
155
|
### getMessages(options)
|
|
263
156
|
|
|
264
|
-
Retrieves all messages currently
|
|
265
|
-
|
|
266
|
-
**Parameters:**
|
|
157
|
+
Retrieves all messages currently stored for a subject.
|
|
267
158
|
|
|
268
|
-
|
|
269
|
-
|-----------|------|----------|-------------|
|
|
270
|
-
| `options` | object | No | Query options |
|
|
271
|
-
|
|
272
|
-
**Options:**
|
|
273
|
-
|
|
274
|
-
| Option | Type | Description |
|
|
275
|
-
|--------|------|-------------|
|
|
276
|
-
| `subject` | string | Subject to query (default: 'default') |
|
|
277
|
-
|
|
278
|
-
**Returns:** Promise<{ success: boolean, messages: QueueMessage[], count: number }>
|
|
279
|
-
|
|
280
|
-
**Examples:**
|
|
159
|
+
**Returns:** `Promise<{ success: boolean, messages: QueueMessage[], count: number }>`
|
|
281
160
|
|
|
282
161
|
```javascript
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
console.log(
|
|
286
|
-
result.messages.forEach(msg => {
|
|
287
|
-
console.log(msg.data);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Get messages from specific subject
|
|
291
|
-
const orders = await client.getMessages({ subject: 'orders' });
|
|
292
|
-
console.log(`${orders.count} pending orders`);
|
|
162
|
+
const result = await client.getMessages({ subject: 'orders' });
|
|
163
|
+
console.log(`${result.count} messages`);
|
|
164
|
+
result.messages.forEach(msg => console.log(msg.data));
|
|
293
165
|
```
|
|
294
166
|
|
|
295
167
|
---
|
|
296
168
|
|
|
297
169
|
### disconnect()
|
|
298
170
|
|
|
299
|
-
Disconnects from the server.
|
|
300
|
-
|
|
301
|
-
```javascript
|
|
302
|
-
client.disconnect();
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
**Example:**
|
|
171
|
+
Disconnects the client from the server.
|
|
306
172
|
|
|
307
173
|
```javascript
|
|
308
|
-
// Clean disconnect
|
|
309
|
-
await client.unsubscribe();
|
|
310
174
|
client.disconnect();
|
|
311
175
|
```
|
|
312
176
|
|
|
@@ -314,32 +178,45 @@ client.disconnect();
|
|
|
314
178
|
|
|
315
179
|
## Message Format
|
|
316
180
|
|
|
317
|
-
### QueueMessage
|
|
318
|
-
|
|
319
|
-
Every message in QueueBit has the following structure:
|
|
320
|
-
|
|
321
181
|
```typescript
|
|
322
182
|
{
|
|
323
|
-
id: string, // Unique
|
|
183
|
+
id: string, // Unique UUID
|
|
324
184
|
data: object, // Your message payload
|
|
325
185
|
subject: string, // Message subject/topic
|
|
326
|
-
timestamp: Date, // When
|
|
327
|
-
expiry?: Date, // Optional expiration
|
|
328
|
-
removeAfterRead: boolean
|
|
186
|
+
timestamp: Date, // When published
|
|
187
|
+
expiry?: Date, // Optional expiration
|
|
188
|
+
removeAfterRead: boolean,// Ephemeral flag
|
|
189
|
+
loadBalancerId?: number, // Set when delivered via load balancer
|
|
190
|
+
queueName?: string // Load balancer group name (internal routing)
|
|
329
191
|
}
|
|
330
192
|
```
|
|
331
193
|
|
|
332
|
-
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Load Balancer
|
|
197
|
+
|
|
198
|
+
Load balancers provide round-robin delivery across multiple subscribers. Each message is delivered to exactly one subscriber.
|
|
199
|
+
|
|
200
|
+
- Each call to `subscribe` with a unique `queue` name creates a new load balancer with a unique numeric ID
|
|
201
|
+
- Messages are distributed round-robin across all registered load balancers for a subject
|
|
202
|
+
- Load balancer messages are **consumed** (not stored) after delivery
|
|
203
|
+
- The `loadBalancerId` is included in delivered messages for identification
|
|
333
204
|
|
|
334
205
|
```javascript
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
206
|
+
// Worker 1 - gets its own load balancer (e.g. LB#1)
|
|
207
|
+
await worker1.subscribe((msg) => {
|
|
208
|
+
console.log(`LB#${msg.loadBalancerId} got:`, msg.data);
|
|
209
|
+
}, { subject: 'jobs', queue: 'worker-1' });
|
|
210
|
+
|
|
211
|
+
// Worker 2 - gets its own load balancer (e.g. LB#2)
|
|
212
|
+
await worker2.subscribe((msg) => {
|
|
213
|
+
console.log(`LB#${msg.loadBalancerId} got:`, msg.data);
|
|
214
|
+
}, { subject: 'jobs', queue: 'worker-2' });
|
|
215
|
+
|
|
216
|
+
// Messages alternate: LB#1, LB#2, LB#1, LB#2, ...
|
|
217
|
+
await publisher.publish({ job: 1 }, { subject: 'jobs' }); // → LB#1
|
|
218
|
+
await publisher.publish({ job: 2 }, { subject: 'jobs' }); // → LB#2
|
|
219
|
+
await publisher.publish({ job: 3 }, { subject: 'jobs' }); // → LB#1
|
|
343
220
|
```
|
|
344
221
|
|
|
345
222
|
---
|
|
@@ -349,229 +226,92 @@ Every message in QueueBit has the following structure:
|
|
|
349
226
|
### Basic Pub/Sub
|
|
350
227
|
|
|
351
228
|
```javascript
|
|
352
|
-
const { QueueBitServer
|
|
353
|
-
|
|
354
|
-
// Start server
|
|
355
|
-
const server = new QueueBitServer({ port: 3000 });
|
|
229
|
+
const { QueueBitServer } = require('./src/server');
|
|
230
|
+
const { QueueBitClient } = require('./src/client-node');
|
|
356
231
|
|
|
357
|
-
|
|
358
|
-
const publisher = new QueueBitClient('http://localhost:
|
|
359
|
-
const subscriber = new QueueBitClient('http://localhost:
|
|
232
|
+
const server = new QueueBitServer({ port: 3333 });
|
|
233
|
+
const publisher = new QueueBitClient('http://localhost:3333');
|
|
234
|
+
const subscriber = new QueueBitClient('http://localhost:3333');
|
|
360
235
|
|
|
361
|
-
// Subscribe
|
|
362
236
|
await subscriber.subscribe((message) => {
|
|
363
237
|
console.log('Received:', message.data);
|
|
364
238
|
});
|
|
365
239
|
|
|
366
|
-
// Publish
|
|
367
240
|
await publisher.publish({ text: 'Hello, World!' });
|
|
368
241
|
```
|
|
369
242
|
|
|
370
|
-
###
|
|
243
|
+
### In-Process Server
|
|
371
244
|
|
|
372
245
|
```javascript
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const { requestId, question } = message.data;
|
|
376
|
-
|
|
377
|
-
// Process request
|
|
378
|
-
const answer = processQuestion(question);
|
|
379
|
-
|
|
380
|
-
// Send response
|
|
381
|
-
await responder.publish(
|
|
382
|
-
{ requestId, answer },
|
|
383
|
-
{ subject: 'responses' }
|
|
384
|
-
);
|
|
385
|
-
}, { subject: 'requests' });
|
|
386
|
-
|
|
387
|
-
// Requester
|
|
388
|
-
const requestId = generateId();
|
|
389
|
-
|
|
390
|
-
// Listen for response
|
|
391
|
-
await requester.subscribe((message) => {
|
|
392
|
-
if (message.data.requestId === requestId) {
|
|
393
|
-
console.log('Answer:', message.data.answer);
|
|
394
|
-
}
|
|
395
|
-
}, { subject: 'responses' });
|
|
396
|
-
|
|
397
|
-
// Send request
|
|
398
|
-
await requester.publish(
|
|
399
|
-
{ requestId, question: 'What is 2+2?' },
|
|
400
|
-
{ subject: 'requests' }
|
|
401
|
-
);
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Work Queue Pattern
|
|
405
|
-
|
|
406
|
-
```javascript
|
|
407
|
-
// Producer
|
|
408
|
-
for (let i = 0; i < 100; i++) {
|
|
409
|
-
await producer.publish(
|
|
410
|
-
{ taskId: i, work: `Task ${i}` },
|
|
411
|
-
{ subject: 'tasks' }
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Worker 1
|
|
416
|
-
await worker1.subscribe((message) => {
|
|
417
|
-
console.log('Worker 1 processing:', message.data.taskId);
|
|
418
|
-
}, { subject: 'tasks', queue: 'workers' });
|
|
419
|
-
|
|
420
|
-
// Worker 2
|
|
421
|
-
await worker2.subscribe((message) => {
|
|
422
|
-
console.log('Worker 2 processing:', message.data.taskId);
|
|
423
|
-
}, { subject: 'tasks', queue: 'workers' });
|
|
424
|
-
|
|
425
|
-
// Tasks are distributed between Worker 1 and Worker 2
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Expiring Messages
|
|
429
|
-
|
|
430
|
-
```javascript
|
|
431
|
-
// Publish message that expires in 5 minutes
|
|
432
|
-
const expiryDate = new Date(Date.now() + 300000);
|
|
433
|
-
|
|
434
|
-
await client.publish(
|
|
435
|
-
{
|
|
436
|
-
code: 'ABC123',
|
|
437
|
-
description: 'Temporary access code'
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
subject: 'temp-codes',
|
|
441
|
-
expiry: expiryDate
|
|
442
|
-
}
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
// Message is automatically removed after expiry
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### One-Time Notifications
|
|
449
|
-
|
|
450
|
-
```javascript
|
|
451
|
-
// Publisher sends ephemeral notification
|
|
452
|
-
await publisher.publish(
|
|
453
|
-
{ alert: 'Server restarting in 5 minutes' },
|
|
454
|
-
{
|
|
455
|
-
subject: 'alerts',
|
|
456
|
-
removeAfterRead: true
|
|
457
|
-
}
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
// First subscriber gets the message
|
|
461
|
-
await subscriber1.subscribe((message) => {
|
|
462
|
-
console.log('Alert:', message.data.alert); // Receives message
|
|
463
|
-
}, { subject: 'alerts' });
|
|
464
|
-
|
|
465
|
-
// Second subscriber connects later
|
|
466
|
-
await subscriber2.subscribe((message) => {
|
|
467
|
-
console.log('Alert:', message.data.alert); // Won't receive it
|
|
468
|
-
}, { subject: 'alerts' });
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### Multi-Topic Subscription
|
|
472
|
-
|
|
473
|
-
```javascript
|
|
474
|
-
const client = new QueueBitClient('http://localhost:3000');
|
|
475
|
-
|
|
476
|
-
// Subscribe to multiple topics
|
|
477
|
-
await client.subscribe((msg) => {
|
|
478
|
-
console.log('User event:', msg.data);
|
|
479
|
-
}, { subject: 'users' });
|
|
246
|
+
const { QueueBitServer } = require('./src/server');
|
|
247
|
+
const { QueueBitClient } = require('./src/client-node');
|
|
480
248
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
249
|
+
// Start server and client in the same process
|
|
250
|
+
const server = new QueueBitServer({ port: 3333 });
|
|
251
|
+
const client = new QueueBitClient('http://localhost:3333');
|
|
484
252
|
|
|
485
253
|
await client.subscribe((msg) => {
|
|
486
|
-
console.log('
|
|
487
|
-
}
|
|
254
|
+
console.log('Got:', msg.data);
|
|
255
|
+
});
|
|
488
256
|
|
|
489
|
-
|
|
490
|
-
await client.publish({ action: 'login' }, { subject: 'users' });
|
|
491
|
-
await client.publish({ orderId: 123 }, { subject: 'orders' });
|
|
492
|
-
await client.publish({ amount: 50 }, { subject: 'payments' });
|
|
257
|
+
await client.publish({ hello: 'world' });
|
|
493
258
|
```
|
|
494
259
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
## Performance Tips
|
|
498
|
-
|
|
499
|
-
1. **Use subjects** for routing instead of filtering in callbacks
|
|
500
|
-
2. **Queue groups** for load balancing across multiple consumers
|
|
501
|
-
3. **Batch publishing** when sending many messages
|
|
502
|
-
4. **Remove old messages** using expiry to prevent memory issues
|
|
503
|
-
5. **WebSocket transport** is faster than long-polling (default)
|
|
504
|
-
|
|
505
|
-
---
|
|
506
|
-
|
|
507
|
-
## Error Handling
|
|
260
|
+
### Work Queue
|
|
508
261
|
|
|
509
262
|
```javascript
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
console.error('Publish failed:', result.error);
|
|
514
|
-
}
|
|
515
|
-
} catch (error) {
|
|
516
|
-
console.error('Connection error:', error);
|
|
263
|
+
// Producer
|
|
264
|
+
for (let i = 0; i < 10; i++) {
|
|
265
|
+
await producer.publish({ taskId: i }, { subject: 'tasks' });
|
|
517
266
|
}
|
|
518
267
|
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
console.log('
|
|
522
|
-
|
|
523
|
-
});
|
|
268
|
+
// Worker 1
|
|
269
|
+
await worker1.subscribe((msg) => {
|
|
270
|
+
console.log('Worker 1:', msg.data.taskId);
|
|
271
|
+
}, { subject: 'tasks', queue: 'worker-1' });
|
|
524
272
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
273
|
+
// Worker 2
|
|
274
|
+
await worker2.subscribe((msg) => {
|
|
275
|
+
console.log('Worker 2:', msg.data.taskId);
|
|
276
|
+
}, { subject: 'tasks', queue: 'worker-2' });
|
|
277
|
+
// Tasks alternate between workers
|
|
528
278
|
```
|
|
529
279
|
|
|
530
280
|
---
|
|
531
281
|
|
|
532
282
|
## Browser Usage
|
|
533
283
|
|
|
534
|
-
Include Socket.IO and QueueBit client in your HTML:
|
|
535
|
-
|
|
536
284
|
```html
|
|
537
285
|
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
|
538
|
-
<script src="
|
|
539
|
-
|
|
286
|
+
<script src="src/client-browser.js"></script>
|
|
540
287
|
<script>
|
|
541
|
-
const client = new QueueBitClient('http://localhost:
|
|
288
|
+
const client = new QueueBitClient('http://localhost:3333');
|
|
542
289
|
|
|
543
290
|
client.subscribe((message) => {
|
|
544
291
|
console.log('Received:', message.data);
|
|
545
|
-
});
|
|
292
|
+
}, { subject: 'events' });
|
|
546
293
|
|
|
547
|
-
client.publish({ text: 'Hello from browser!' });
|
|
294
|
+
client.publish({ text: 'Hello from browser!' }, { subject: 'events' });
|
|
548
295
|
</script>
|
|
549
296
|
```
|
|
550
297
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
## NATS Compatibility
|
|
554
|
-
|
|
555
|
-
QueueBit implements NATS-like patterns:
|
|
556
|
-
|
|
557
|
-
- **Subjects**: Topic-based routing
|
|
558
|
-
- **Queue Groups**: Load-balanced delivery
|
|
559
|
-
- **At-least-once delivery**: Messages persisted until delivered
|
|
560
|
-
- **Wildcards**: Not currently supported (planned)
|
|
298
|
+
See [`examples/qpanel.html`](../examples/qpanel.html) for a full browser dashboard with publish, subscribe, load balancer, and performance testing.
|
|
561
299
|
|
|
562
300
|
---
|
|
563
301
|
|
|
564
|
-
##
|
|
565
|
-
|
|
566
|
-
1. **Use meaningful subject names**: `orders.created`, `users.login`, etc.
|
|
567
|
-
2. **Set appropriate expiry times** for temporary data
|
|
568
|
-
3. **Clean up subscriptions** when no longer needed
|
|
569
|
-
4. **Monitor queue sizes** to prevent memory issues
|
|
570
|
-
5. **Use queue groups** for scalable message processing
|
|
571
|
-
6. **Handle reconnection** in production applications
|
|
572
|
-
|
|
573
|
-
---
|
|
302
|
+
## Error Handling
|
|
574
303
|
|
|
575
|
-
|
|
304
|
+
```javascript
|
|
305
|
+
try {
|
|
306
|
+
const result = await client.publish({ data: 'test' });
|
|
307
|
+
if (!result.success) {
|
|
308
|
+
console.error('Publish failed:', result.error);
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// Thrown on timeout (5 second default)
|
|
312
|
+
console.error('Publish error:', error.message);
|
|
313
|
+
}
|
|
576
314
|
|
|
577
|
-
|
|
315
|
+
client.socket.on('disconnect', () => console.log('Disconnected'));
|
|
316
|
+
client.socket.on('connect_error', (err) => console.error('Connection error:', err));
|
|
317
|
+
```
|