@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/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: 3000,
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
- const { QueueBitClient } = require('queuebit');
55
+ // Node.js
56
+ const { QueueBitClient } = require('queuebit/src/client-node');
57
+ const client = new QueueBitClient('http://localhost:3333');
79
58
 
80
- const client = new QueueBitClient(url);
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:3000' | Server URL |
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
- const result = await client.publish({
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 specific subject
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 message (removed after first read)
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
- // Message with expiry (expires in 1 hour)
145
- const expiryDate = new Date(Date.now() + 3600000);
97
+ // With expiry
146
98
  await client.publish(
147
- { tempData: 'expires soon' },
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 from the queue.
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 queue group for load-balanced delivery |
174
-
175
- **Callback Signature:**
115
+ | `queue` | string | Join a load balancer group for round-robin delivery |
176
116
 
177
- ```javascript
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
- // Basic subscription
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
- // Subscribe to specific subject
192
- await client.subscribe(
193
- (message) => {
194
- console.log('Order received:', message.data);
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 | Queue group to leave |
241
-
242
- **Returns:** Promise<{ success: boolean }>
144
+ | `queue` | string | Load balancer group to leave |
243
145
 
244
- **Examples:**
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 in the queue for a subject.
265
-
266
- **Parameters:**
157
+ Retrieves all messages currently stored for a subject.
267
158
 
268
- | Parameter | Type | Required | Description |
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
- // Get all messages from default subject
284
- const result = await client.getMessages();
285
- console.log(`Found ${result.count} messages`);
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 message identifier (UUID)
183
+ id: string, // Unique UUID
324
184
  data: object, // Your message payload
325
185
  subject: string, // Message subject/topic
326
- timestamp: Date, // When message was published
327
- expiry?: Date, // Optional expiration date
328
- removeAfterRead: boolean // Whether to remove after first read
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
- **Example:**
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
- id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
337
- data: { orderId: 12345, amount: 99.99 },
338
- subject: 'orders',
339
- timestamp: '2024-01-15T10:30:00.000Z',
340
- expiry: null,
341
- removeAfterRead: false
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, QueueBitClient } = require('queuebit');
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
- // Create clients
358
- const publisher = new QueueBitClient('http://localhost:3000');
359
- const subscriber = new QueueBitClient('http://localhost:3000');
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
- ### Request-Response Pattern
243
+ ### In-Process Server
371
244
 
372
245
  ```javascript
373
- // Responder
374
- await responder.subscribe(async (message) => {
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
- await client.subscribe((msg) => {
482
- console.log('Order event:', msg.data);
483
- }, { subject: 'orders' });
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('Payment event:', msg.data);
487
- }, { subject: 'payments' });
254
+ console.log('Got:', msg.data);
255
+ });
488
256
 
489
- // Publish to different topics
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
- try {
511
- const result = await client.publish({ data: 'test' });
512
- if (!result.success) {
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
- // Handle disconnection
520
- client.socket.on('disconnect', () => {
521
- console.log('Disconnected from server');
522
- // Implement reconnection logic
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
- client.socket.on('connect_error', (error) => {
526
- console.error('Connection error:', error);
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="node_modules/queuebit/src/client-browser.js"></script>
539
-
286
+ <script src="src/client-browser.js"></script>
540
287
  <script>
541
- const client = new QueueBitClient('http://localhost:3000');
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
- ## Best Practices
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
- ## License
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
- MIT License - See LICENSE file for details
315
+ client.socket.on('disconnect', () => console.log('Disconnected'));
316
+ client.socket.on('connect_error', (err) => console.error('Connection error:', err));
317
+ ```