@usermetrics/queuebit 1.0.0 → 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/EXAMPLES.md CHANGED
@@ -4,164 +4,120 @@ Practical examples for common use cases.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
+ - [Runnable Examples](#runnable-examples)
7
8
  - [Chat Application](#chat-application)
8
- - [Task Queue System](#task-queue-system)
9
+ - [Task Queue with Load Balancer](#task-queue-with-load-balancer)
9
10
  - [Real-time Analytics](#real-time-analytics)
10
11
  - [Microservices Communication](#microservices-communication)
11
12
  - [Event Sourcing](#event-sourcing)
12
13
 
13
14
  ---
14
15
 
15
- ## Chat Application
16
+ ## Runnable Examples
16
17
 
17
- Simple chat room using QueueBit.
18
+ | File | Description |
19
+ |------|-------------|
20
+ | [`examples/server2server.js`](../examples/server2server.js) | HTTP server using an external QueueBit server |
21
+ | [`examples/inprocessserver.js`](../examples/inprocessserver.js) | HTTP server with QueueBit running in-process |
22
+ | [`examples/queuegroup.js`](../examples/queuegroup.js) | Load balancer demo with 3 workers |
23
+ | [`examples/qpanel.html`](../examples/qpanel.html) | Browser dashboard with publish, subscribe, load balancer, and perf testing |
24
+ | [`test/test-harness.js`](../test/test-harness.js) | Full test suite |
18
25
 
19
- ```javascript
20
- const { QueueBitServer, QueueBitClient } = require('queuebit');
26
+ ---
21
27
 
22
- // Server
23
- const server = new QueueBitServer({ port: 3000 });
28
+ ## Chat Application
24
29
 
25
- // Client
30
+ ```javascript
31
+ const { QueueBitServer } = require('./src/server');
32
+ const { QueueBitClient } = require('./src/client-node');
33
+
34
+ const server = new QueueBitServer({ port: 3333 });
26
35
  const username = process.argv[2] || 'Anonymous';
27
- const client = new QueueBitClient('http://localhost:3000');
36
+ const client = new QueueBitClient('http://localhost:3333');
28
37
 
29
- // Receive messages
30
38
  await client.subscribe((message) => {
31
39
  const { user, text, timestamp } = message.data;
32
- const time = new Date(timestamp).toLocaleTimeString();
33
- console.log(`[${time}] ${user}: ${text}`);
40
+ console.log(`[${new Date(timestamp).toLocaleTimeString()}] ${user}: ${text}`);
34
41
  }, { subject: 'chat' });
35
42
 
36
- // Send messages
37
43
  const readline = require('readline');
38
- const rl = readline.createInterface({
39
- input: process.stdin,
40
- output: process.stdout
41
- });
44
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
42
45
 
43
46
  rl.on('line', async (text) => {
44
- await client.publish(
45
- { user: username, text, timestamp: new Date() },
46
- { subject: 'chat' }
47
- );
47
+ await client.publish({ user: username, text, timestamp: new Date() }, { subject: 'chat' });
48
48
  });
49
-
50
- console.log(`Joined chat as ${username}`);
51
49
  ```
52
50
 
53
51
  ---
54
52
 
55
- ## Task Queue System
53
+ ## Task Queue with Load Balancer
56
54
 
57
- Distribute tasks across multiple workers.
55
+ Messages are distributed round-robin — each message goes to exactly one worker.
58
56
 
59
57
  ```javascript
60
- // producer.js
61
- const { QueueBitClient } = require('queuebit');
62
- const client = new QueueBitClient('http://localhost:3000');
63
-
64
- async function produceTasks() {
65
- for (let i = 1; i <= 100; i++) {
66
- await client.publish(
67
- {
68
- taskId: i,
69
- type: 'process-image',
70
- imageUrl: `https://example.com/img${i}.jpg`,
71
- priority: i % 10 === 0 ? 'high' : 'normal'
72
- },
73
- { subject: 'tasks' }
74
- );
75
- console.log(`Task ${i} queued`);
76
- }
77
- }
58
+ const { QueueBitServer } = require('./src/server');
59
+ const { QueueBitClient } = require('./src/client-node');
78
60
 
79
- produceTasks();
61
+ const server = new QueueBitServer({ port: 3333 });
80
62
 
81
- // worker.js
82
- const { QueueBitClient } = require('queuebit');
83
- const workerId = process.argv[2] || '1';
84
- const client = new QueueBitClient('http://localhost:3000');
63
+ async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
85
64
 
86
- await client.subscribe(async (message) => {
87
- const { taskId, type, imageUrl, priority } = message.data;
88
-
89
- console.log(`Worker ${workerId} processing task ${taskId} (${priority})`);
90
-
91
- // Simulate work
92
- await new Promise(resolve => setTimeout(resolve, 1000));
93
-
94
- // Publish result
95
- await client.publish(
96
- { taskId, workerId, status: 'completed', timestamp: new Date() },
97
- { subject: 'results' }
98
- );
99
-
100
- console.log(`Worker ${workerId} completed task ${taskId}`);
101
- }, { subject: 'tasks', queue: 'workers' });
65
+ await sleep(500);
102
66
 
103
- console.log(`Worker ${workerId} ready`);
67
+ const worker1 = new QueueBitClient('http://localhost:3333');
68
+ const worker2 = new QueueBitClient('http://localhost:3333');
69
+ const worker3 = new QueueBitClient('http://localhost:3333');
70
+
71
+ await sleep(500);
72
+
73
+ // Each worker has a unique queue name → unique load balancer ID
74
+ await worker1.subscribe((msg) => {
75
+ console.log(`LB#${msg.loadBalancerId} Worker1:`, msg.data);
76
+ }, { subject: 'tasks', queue: 'worker-1' });
77
+
78
+ await worker2.subscribe((msg) => {
79
+ console.log(`LB#${msg.loadBalancerId} Worker2:`, msg.data);
80
+ }, { subject: 'tasks', queue: 'worker-2' });
81
+
82
+ await worker3.subscribe((msg) => {
83
+ console.log(`LB#${msg.loadBalancerId} Worker3:`, msg.data);
84
+ }, { subject: 'tasks', queue: 'worker-3' });
85
+
86
+ const publisher = new QueueBitClient('http://localhost:3333');
87
+ await sleep(500);
88
+
89
+ // Messages cycle: LB#1 → LB#2 → LB#3 → LB#1 → ...
90
+ for (let i = 1; i <= 9; i++) {
91
+ await publisher.publish({ taskId: i }, { subject: 'tasks' });
92
+ await sleep(100);
93
+ }
104
94
  ```
105
95
 
96
+ See [`examples/queuegroup.js`](../examples/queuegroup.js) for the runnable version.
97
+
106
98
  ---
107
99
 
108
100
  ## Real-time Analytics
109
101
 
110
- Collect and aggregate analytics events.
111
-
112
102
  ```javascript
113
- // event-collector.js
114
- const { QueueBitServer, QueueBitClient } = require('queuebit');
115
-
116
- const server = new QueueBitServer({ port: 3000 });
117
- const collector = new QueueBitClient('http://localhost:3000');
118
-
119
- const stats = {
120
- pageViews: 0,
121
- clicks: 0,
122
- purchases: 0
123
- };
103
+ const stats = { pageViews: 0, clicks: 0, purchases: 0 };
124
104
 
125
105
  await collector.subscribe((message) => {
126
- const { eventType, data } = message.data;
127
-
128
- switch(eventType) {
129
- case 'pageview':
130
- stats.pageViews++;
131
- break;
132
- case 'click':
133
- stats.clicks++;
134
- break;
135
- case 'purchase':
136
- stats.purchases++;
137
- break;
138
- }
139
-
140
- console.log('Stats:', stats);
106
+ const { eventType } = message.data;
107
+ if (eventType === 'pageview') stats.pageViews++;
108
+ if (eventType === 'click') stats.clicks++;
109
+ if (eventType === 'purchase') stats.purchases++;
141
110
  }, { subject: 'analytics' });
142
111
 
143
- // Display stats every 5 seconds
144
112
  setInterval(() => {
145
- console.log('\n=== Analytics Dashboard ===');
146
- console.log(`Page Views: ${stats.pageViews}`);
147
- console.log(`Clicks: ${stats.clicks}`);
148
- console.log(`Purchases: ${stats.purchases}`);
149
- console.log('===========================\n');
113
+ console.log('Stats:', stats);
150
114
  }, 5000);
151
115
 
152
- // event-generator.js (simulate events)
153
- const client = new QueueBitClient('http://localhost:3000');
154
-
116
+ // Simulate events
155
117
  setInterval(async () => {
156
118
  const events = ['pageview', 'click', 'purchase'];
157
- const eventType = events[Math.floor(Math.random() * events.length)];
158
-
159
119
  await client.publish(
160
- {
161
- eventType,
162
- data: { userId: Math.floor(Math.random() * 1000) },
163
- timestamp: new Date()
164
- },
120
+ { eventType: events[Math.floor(Math.random() * 3)] },
165
121
  { subject: 'analytics' }
166
122
  );
167
123
  }, 100);
@@ -171,65 +127,33 @@ setInterval(async () => {
171
127
 
172
128
  ## Microservices Communication
173
129
 
174
- Services communicate through QueueBit.
175
-
176
130
  ```javascript
177
131
  // user-service.js
178
- const { QueueBitClient } = require('queuebit');
179
- const client = new QueueBitClient('http://localhost:3000');
180
-
181
- // Listen for user creation requests
182
132
  await client.subscribe(async (message) => {
183
133
  const { requestId, username, email } = message.data;
184
-
185
- // Create user in database
186
134
  const userId = await createUser(username, email);
187
-
188
- // Publish user created event
189
- await client.publish(
190
- { userId, username, email },
191
- { subject: 'user.created' }
192
- );
193
-
194
- // Send response
195
- await client.publish(
196
- { requestId, success: true, userId },
197
- { subject: 'responses' }
198
- );
199
- }, { subject: 'user.create' });
200
135
 
201
- // order-service.js
202
- const client = new QueueBitClient('http://localhost:3000');
136
+ await client.publish({ requestId, success: true, userId }, { subject: 'responses' });
137
+ await client.publish({ userId, username, email }, { subject: 'user.created' });
138
+ }, { subject: 'user.create' });
203
139
 
204
- // Listen for user created events
140
+ // order-service.js - reacts to user creation events
205
141
  await client.subscribe(async (message) => {
206
- const { userId, username } = message.data;
207
-
208
- console.log(`New user ${username} (${userId}) - initializing order history`);
142
+ const { userId } = message.data;
209
143
  await initializeOrderHistory(userId);
210
144
  }, { subject: 'user.created' });
211
145
 
212
146
  // api-gateway.js
213
- const client = new QueueBitClient('http://localhost:3000');
214
-
215
147
  async function createUser(username, email) {
216
- const requestId = generateId();
217
-
218
- // Listen for response
148
+ const requestId = crypto.randomUUID();
149
+
219
150
  const responsePromise = new Promise((resolve) => {
220
- client.subscribe((message) => {
221
- if (message.data.requestId === requestId) {
222
- resolve(message.data);
223
- }
151
+ client.subscribe((msg) => {
152
+ if (msg.data.requestId === requestId) resolve(msg.data);
224
153
  }, { subject: 'responses' });
225
154
  });
226
-
227
- // Send request
228
- await client.publish(
229
- { requestId, username, email },
230
- { subject: 'user.create' }
231
- );
232
-
155
+
156
+ await client.publish({ requestId, username, email }, { subject: 'user.create' });
233
157
  return responsePromise;
234
158
  }
235
159
  ```
@@ -238,93 +162,38 @@ async function createUser(username, email) {
238
162
 
239
163
  ## Event Sourcing
240
164
 
241
- Store all events and rebuild state.
242
-
243
165
  ```javascript
244
- // event-store.js
245
- const { QueueBitClient } = require('queuebit');
246
- const client = new QueueBitClient('http://localhost:3000');
247
-
248
166
  const events = [];
249
167
 
250
168
  // Store all events
251
169
  await client.subscribe((message) => {
252
170
  events.push(message.data);
253
- console.log(`Event stored: ${message.data.type}`);
254
- }, { subject: 'events' });
171
+ }, { subject: 'account.events' });
255
172
 
256
173
  // Rebuild state from events
257
- function rebuildState() {
258
- const state = { balance: 0, transactions: [] };
259
-
260
- for (const event of events) {
261
- switch(event.type) {
262
- case 'deposit':
263
- state.balance += event.amount;
264
- state.transactions.push(event);
265
- break;
266
- case 'withdraw':
267
- state.balance -= event.amount;
268
- state.transactions.push(event);
269
- break;
270
- }
271
- }
272
-
273
- return state;
274
- }
275
-
276
- // bank-account.js
277
- const client = new QueueBitClient('http://localhost:3000');
278
-
279
- async function deposit(amount) {
280
- await client.publish(
281
- {
282
- type: 'deposit',
283
- amount,
284
- timestamp: new Date(),
285
- accountId: 'ACC123'
286
- },
287
- { subject: 'events' }
288
- );
289
- }
290
-
291
- async function withdraw(amount) {
292
- await client.publish(
293
- {
294
- type: 'withdraw',
295
- amount,
296
- timestamp: new Date(),
297
- accountId: 'ACC123'
298
- },
299
- { subject: 'events' }
300
- );
174
+ function getBalance() {
175
+ return events.reduce((bal, e) => {
176
+ if (e.type === 'deposit') return bal + e.amount;
177
+ if (e.type === 'withdraw') return bal - e.amount;
178
+ return bal;
179
+ }, 0);
301
180
  }
302
181
 
303
- // Usage
304
- await deposit(100);
305
- await withdraw(50);
306
- await deposit(75);
182
+ // Publish events
183
+ await client.publish({ type: 'deposit', amount: 100 }, { subject: 'account.events' });
184
+ await client.publish({ type: 'withdraw', amount: 30 }, { subject: 'account.events' });
185
+ await client.publish({ type: 'deposit', amount: 50 }, { subject: 'account.events' });
307
186
 
308
- // Rebuild state at any time
309
- const state = rebuildState();
310
- console.log('Current balance:', state.balance); // 125
187
+ console.log('Balance:', getBalance()); // 120
311
188
  ```
312
189
 
313
190
  ---
314
191
 
315
- ## More Examples
316
-
317
- See the [examples folder](../examples/) for:
318
- - Browser-based example with UI
319
- - Performance testing
320
- - Advanced patterns
321
-
322
- ---
323
-
324
192
  ## Tips
325
193
 
326
- 1. **Use subjects** to organize message types
327
- 2. **Queue groups** for scalable processing
328
- 3. **Store event IDs** to prevent duplicate processing
329
- 4. **Set expiry** for temporary messages
330
- 5. **Monitor performance** with the test harness
194
+ 1. Use **subjects** to organize message types (e.g. `orders.created`, `users.login`)
195
+ 2. Use **load balancers** with unique queue names per worker for scalable processing
196
+ 3. Use **`removeAfterRead: true`** for one-time notifications
197
+ 4. Use **`expiry`** to auto-clean temporary messages
198
+ 5. The **`loadBalancerId`** in received messages identifies which load balancer delivered it
199
+ 6. Load balancer messages are **consumed** — not replayed to late subscribers
@@ -2,121 +2,120 @@
2
2
 
3
3
  Get started with QueueBit in 5 minutes!
4
4
 
5
- ## Installation
6
-
7
- ```bash
8
- npm install queuebit
9
- ```
10
-
11
5
  ## Start the Server
12
6
 
13
7
  ```javascript
14
8
  // server.js
15
- const { QueueBitServer } = require('queuebit');
9
+ const { QueueBitServer } = require('./src/server');
16
10
 
17
- const server = new QueueBitServer({ port: 3000 });
18
- console.log('QueueBit server running on port 3000');
11
+ const server = new QueueBitServer({ port: 3333 });
12
+ // Starts listening immediately
19
13
  ```
20
14
 
21
- Run it:
22
-
23
15
  ```bash
24
16
  node server.js
25
17
  ```
26
18
 
27
- ## Publisher Example
19
+ ## Publisher
28
20
 
29
21
  ```javascript
30
- // publisher.js
31
- const { QueueBitClient } = require('queuebit');
22
+ const { QueueBitClient } = require('./src/client-node');
32
23
 
33
- const client = new QueueBitClient('http://localhost:3000');
24
+ const client = new QueueBitClient('http://localhost:3333');
34
25
 
35
- // Wait for connection
36
26
  setTimeout(async () => {
37
- // Publish a message
38
- await client.publish({
39
- message: 'Hello, QueueBit!',
40
- timestamp: new Date()
41
- });
42
-
27
+ await client.publish({ message: 'Hello, QueueBit!', timestamp: new Date() });
43
28
  console.log('Message published!');
44
29
  }, 1000);
45
30
  ```
46
31
 
47
- ## Subscriber Example
32
+ ## Subscriber
48
33
 
49
34
  ```javascript
50
- // subscriber.js
51
- const { QueueBitClient } = require('queuebit');
35
+ const { QueueBitClient } = require('./src/client-node');
52
36
 
53
- const client = new QueueBitClient('http://localhost:3000');
37
+ const client = new QueueBitClient('http://localhost:3333');
54
38
 
55
- // Subscribe to messages
56
39
  client.subscribe((message) => {
57
40
  console.log('Received:', message.data);
58
41
  });
59
-
60
- console.log('Waiting for messages...');
61
42
  ```
62
43
 
63
- ## Run the Examples
64
-
65
- Open three terminals:
44
+ ## In-Process (Server + Client Together)
66
45
 
67
- ```bash
68
- # Terminal 1: Start server
69
- node server.js
46
+ ```javascript
47
+ const { QueueBitServer } = require('./src/server');
48
+ const { QueueBitClient } = require('./src/client-node');
70
49
 
71
- # Terminal 2: Start subscriber
72
- node subscriber.js
50
+ const server = new QueueBitServer({ port: 3333 });
51
+ const client = new QueueBitClient('http://localhost:3333');
73
52
 
74
- # Terminal 3: Publish messages
75
- node publisher.js
53
+ setTimeout(async () => {
54
+ await client.subscribe((msg) => console.log('Got:', msg.data));
55
+ await client.publish({ hello: 'world' });
56
+ }, 500);
76
57
  ```
77
58
 
78
- ## Next Steps
79
-
80
- - Read the [API Documentation](./API.md)
81
- - Check out [Examples](./EXAMPLES.md)
82
- - See the [browser example](../examples/browser-example.html)
59
+ See [`examples/inprocessserver.js`](../examples/inprocessserver.js) for a full example.
83
60
 
84
61
  ## Common Patterns
85
62
 
86
- ### Work Queue
63
+ ### Regular Subscription (all subscribers receive every message)
87
64
 
88
65
  ```javascript
89
- // Multiple workers process tasks in parallel
90
- await worker.subscribe((message) => {
91
- processTask(message.data);
92
- }, { subject: 'tasks', queue: 'workers' });
66
+ await client.subscribe((message) => {
67
+ console.log('Event:', message.data);
68
+ }, { subject: 'events' });
93
69
  ```
94
70
 
95
- ### Pub/Sub
71
+ ### Load Balancer (round-robin, one subscriber per message)
96
72
 
97
73
  ```javascript
98
- // All subscribers receive every message
99
- await subscriber.subscribe((message) => {
100
- handleEvent(message.data);
101
- }, { subject: 'events' });
74
+ // Each worker gets a unique queue name → unique LB ID
75
+ await worker1.subscribe((msg) => {
76
+ console.log(`LB#${msg.loadBalancerId}:`, msg.data);
77
+ }, { subject: 'tasks', queue: 'worker-1' });
78
+
79
+ await worker2.subscribe((msg) => {
80
+ console.log(`LB#${msg.loadBalancerId}:`, msg.data);
81
+ }, { subject: 'tasks', queue: 'worker-2' });
102
82
  ```
103
83
 
104
- ### Request/Response
84
+ ### Ephemeral Messages (removed after first read)
105
85
 
106
86
  ```javascript
107
- // Send request and wait for response
108
- const requestId = generateId();
87
+ await client.publish(
88
+ { notification: 'One-time alert' },
89
+ { removeAfterRead: true, subject: 'alerts' }
90
+ );
91
+ ```
109
92
 
110
- await client.subscribe((msg) => {
111
- if (msg.data.requestId === requestId) {
112
- console.log('Response:', msg.data);
113
- }
114
- }, { subject: 'responses' });
93
+ ### Message Expiry
115
94
 
95
+ ```javascript
116
96
  await client.publish(
117
- { requestId, data: 'request' },
118
- { subject: 'requests' }
97
+ { code: 'ABC123' },
98
+ { expiry: new Date(Date.now() + 300000) } // expires in 5 minutes
119
99
  );
120
100
  ```
121
101
 
122
- Happy queuing! 🚀
102
+ ## Browser
103
+
104
+ ```html
105
+ <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
106
+ <script src="src/client-browser.js"></script>
107
+ <script>
108
+ const client = new QueueBitClient('http://localhost:3333');
109
+ client.subscribe((msg) => console.log(msg.data));
110
+ client.publish({ text: 'Hello!' });
111
+ </script>
112
+ ```
113
+
114
+ Open [`examples/qpanel.html`](../examples/qpanel.html) for the browser dashboard.
115
+
116
+ ## Next Steps
117
+
118
+ - [API Documentation](./API.md) - Full API reference
119
+ - [Examples](./EXAMPLES.md) - More use case examples
120
+ - [`examples/`](../examples/) folder - Runnable examples
121
+ - [`test/test-harness.js`](../test/test-harness.js) - Run all tests
@@ -0,0 +1,60 @@
1
+ /**
2
+ * In-process QueueBit demo.
3
+ * Starts the QueueBit server and an HTTP server in the same process.
4
+ * Use the qpanel.html dashboard to publish messages to the queue and see them received by the HTTP server.
5
+ * Run this example with /examples/start_node_inprocess.cmd
6
+ * No need to run a separate QueueBit server.
7
+ */
8
+ const http = require('http');
9
+ const { QueueBitServer } = require('../src/server');
10
+ const { QueueBitClient } = require('../src/client-node');
11
+
12
+ const webserverPORT = 3000;
13
+ const queuebitPORT = 3333;
14
+
15
+ // Start the QueueBit server in-process (constructor starts listening immediately)
16
+ const queuebitServer = new QueueBitServer({ port: queuebitPORT });
17
+
18
+ // Connect a client to the in-process QueueBit server
19
+ // this is just for testing. typically you would connect from another process/frontend/server
20
+ const messageQueue = new QueueBitClient(`http://localhost:${queuebitPORT}`);
21
+
22
+ messageQueue.subscribe((msg) => {
23
+ console.log('Received message from queue:', msg);
24
+ }, { subject: 'default' });
25
+
26
+ // Create an HTTP server
27
+ const server = http.createServer((req, res) => {
28
+ res.setHeader('Content-Type', 'application/json');
29
+
30
+ if (req.method === 'POST' && req.url === '/enqueue') {
31
+ let body = '';
32
+ req.on('data', chunk => { body += chunk.toString(); });
33
+ req.on('end', async () => {
34
+ try {
35
+ const message = JSON.parse(body);
36
+ const result = await messageQueue.publish(message);
37
+ res.writeHead(200);
38
+ res.end(JSON.stringify({ success: true, result }));
39
+ } catch (error) {
40
+ res.writeHead(400);
41
+ res.end(JSON.stringify({ error: error.message }));
42
+ }
43
+ });
44
+ } else if (req.method === 'GET' && req.url === '/messages') {
45
+ messageQueue.getMessages({ subject: 'default' }).then((result) => {
46
+ res.writeHead(200);
47
+ res.end(JSON.stringify(result));
48
+ });
49
+ } else {
50
+ res.writeHead(404);
51
+ res.end(JSON.stringify({ error: 'Not found' }));
52
+ }
53
+ });
54
+
55
+ server.listen(webserverPORT, () => {
56
+ console.log(`HTTP server running at http://localhost:${webserverPORT}`);
57
+ console.log('Endpoints:');
58
+ console.log(' POST /enqueue - Publish message to QueueBit');
59
+ console.log(' GET /messages - Get messages from QueueBit');
60
+ });