queen-mq 0.1.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.
Files changed (90) hide show
  1. package/API.md +1116 -0
  2. package/CACHE.md +519 -0
  3. package/DASHBOARD-V3.md +478 -0
  4. package/DASHBOARD.md +382 -0
  5. package/MOD_QUEUE.md +453 -0
  6. package/PARTITION_LOCKING_DESIGN.md +989 -0
  7. package/PLAN.md +707 -0
  8. package/QUERY_ANALSYS.md +72 -0
  9. package/QUEUE_BUS.md +334 -0
  10. package/README.md +1495 -0
  11. package/V2-PLAN.md +236 -0
  12. package/assets/dashboard.png +0 -0
  13. package/dashboard/.vscode/extensions.json +3 -0
  14. package/dashboard/README.md +5 -0
  15. package/dashboard/index.html +14 -0
  16. package/dashboard/package-lock.json +1458 -0
  17. package/dashboard/package.json +25 -0
  18. package/dashboard/public/vite.svg +1 -0
  19. package/dashboard/src/App.vue +29 -0
  20. package/dashboard/src/assets/styles/main.css +908 -0
  21. package/dashboard/src/assets/vue.svg +1 -0
  22. package/dashboard/src/components/cards/MetricCard.vue +298 -0
  23. package/dashboard/src/components/charts/QueueDepthChart.vue +276 -0
  24. package/dashboard/src/components/charts/QueueLagChart.vue +436 -0
  25. package/dashboard/src/components/charts/ThroughputChart.vue +302 -0
  26. package/dashboard/src/components/common/ActivityFeed.vue +251 -0
  27. package/dashboard/src/components/layout/AppHeader.vue +208 -0
  28. package/dashboard/src/components/layout/AppLayout.vue +88 -0
  29. package/dashboard/src/components/layout/AppSidebar.vue +261 -0
  30. package/dashboard/src/main.js +44 -0
  31. package/dashboard/src/router.js +54 -0
  32. package/dashboard/src/services/api.js +187 -0
  33. package/dashboard/src/services/websocket.js +167 -0
  34. package/dashboard/src/utils/constants.js +56 -0
  35. package/dashboard/src/utils/helpers.js +118 -0
  36. package/dashboard/src/views/Analytics.vue +912 -0
  37. package/dashboard/src/views/Dashboard.vue +906 -0
  38. package/dashboard/src/views/Messages.vue +437 -0
  39. package/dashboard/src/views/QueueDetail.vue +501 -0
  40. package/dashboard/src/views/Queues.vue +333 -0
  41. package/dashboard/vite.config.js +30 -0
  42. package/debug-namespace.js +110 -0
  43. package/docs/long-polling.md +159 -0
  44. package/docs/multi-server-cache-solutions.md +185 -0
  45. package/docs/performance-tuning.md +222 -0
  46. package/examples/bus-mode.js +239 -0
  47. package/examples/continuous-consumer-optimized.js +215 -0
  48. package/examples/continuous-consumer.js +159 -0
  49. package/examples/continuous-producer.js +343 -0
  50. package/examples/mixed-mode.js +277 -0
  51. package/examples/multi-server-test.js +305 -0
  52. package/examples/single.js +64 -0
  53. package/examples/smartchat-dealyed.js +42 -0
  54. package/examples/smartchat.js +52 -0
  55. package/examples/test-cache-invalidation.js +119 -0
  56. package/examples/test-cache-multi-server.js +245 -0
  57. package/examples/test-minimal-client.js +112 -0
  58. package/examples/test-queue-creation-policy.js +137 -0
  59. package/init-db.js +20 -0
  60. package/package.json +36 -0
  61. package/src/client/client.js +291 -0
  62. package/src/client/index.js +6 -0
  63. package/src/client/queenClient.js +513 -0
  64. package/src/client/utils/http.js +172 -0
  65. package/src/client/utils/loadBalancer.js +152 -0
  66. package/src/client/utils/retry.js +35 -0
  67. package/src/config.js +215 -0
  68. package/src/database/connection.js +103 -0
  69. package/src/database/poolManager.js +192 -0
  70. package/src/database/schema-v2.sql +214 -0
  71. package/src/managers/eventManager.js +59 -0
  72. package/src/managers/queueManagerOptimized.js +1512 -0
  73. package/src/managers/resourceCache.js +96 -0
  74. package/src/managers/systemEventManager.js +127 -0
  75. package/src/routes/ack.js +26 -0
  76. package/src/routes/analytics.js +812 -0
  77. package/src/routes/configure.js +46 -0
  78. package/src/routes/messages.js +298 -0
  79. package/src/routes/pop.js +85 -0
  80. package/src/routes/push.js +28 -0
  81. package/src/routes/resources.js +296 -0
  82. package/src/server.js +1286 -0
  83. package/src/services/encryptionService.js +82 -0
  84. package/src/services/evictionService.js +131 -0
  85. package/src/services/retentionService.js +129 -0
  86. package/src/services/startupSync.js +35 -0
  87. package/src/test/test.js +4521 -0
  88. package/src/utils/logger.js +44 -0
  89. package/src/utils/uuid.js +5 -0
  90. package/src/websocket/wsServer.js +221 -0
@@ -0,0 +1,96 @@
1
+ import { EventTypes } from './systemEventManager.js';
2
+
3
+ // In-memory cache for resource existence to avoid repeated DB lookups
4
+ export const createResourceCache = () => {
5
+ const cache = new Map();
6
+ // Make TTL configurable - set to 0 to disable caching in multi-server setups
7
+ const TTL = process.env.QUEEN_CACHE_TTL ? parseInt(process.env.QUEEN_CACHE_TTL) : 60000; // Default 1 minute
8
+
9
+ const getCacheKey = (queue, partition) => `${queue}:${partition || 'Default'}`;
10
+
11
+ const checkResource = (queue, partition) => {
12
+ // If TTL is 0, caching is disabled
13
+ if (TTL === 0) return null;
14
+
15
+ const key = getCacheKey(queue, partition);
16
+ const cached = cache.get(key);
17
+
18
+ if (cached && Date.now() - cached.timestamp < TTL) {
19
+ return cached.data;
20
+ }
21
+
22
+ return null; // Not in cache
23
+ };
24
+
25
+ const cacheResource = (queue, partition, data) => {
26
+ // If TTL is 0, caching is disabled
27
+ if (TTL === 0) return;
28
+
29
+ const key = getCacheKey(queue, partition);
30
+ cache.set(key, { data, timestamp: Date.now() });
31
+ };
32
+
33
+ const invalidate = (queue, partition) => {
34
+ if (queue && partition) {
35
+ cache.delete(getCacheKey(queue, partition));
36
+ } else if (queue) {
37
+ // Clear all partitions for this queue
38
+ for (const key of cache.keys()) {
39
+ if (key.startsWith(`${queue}:`)) {
40
+ cache.delete(key);
41
+ }
42
+ }
43
+ } else {
44
+ cache.clear(); // Clear all if no specific resource
45
+ }
46
+ };
47
+
48
+ const invalidateQueue = (queue) => {
49
+ // Clear all partitions for this queue when queue config changes
50
+ for (const key of cache.keys()) {
51
+ if (key.startsWith(`${queue}:`)) {
52
+ cache.delete(key);
53
+ }
54
+ }
55
+ };
56
+
57
+ // Cleanup old entries periodically (only if caching is enabled)
58
+ if (TTL > 0) {
59
+ setInterval(() => {
60
+ const now = Date.now();
61
+ for (const [key, value] of cache.entries()) {
62
+ if (now - value.timestamp > TTL) {
63
+ cache.delete(key);
64
+ }
65
+ }
66
+ }, Math.min(TTL, 60000)); // Check at most once per minute
67
+ }
68
+
69
+ // Register event handlers for cache invalidation
70
+ const registerEventHandlers = (eventManager) => {
71
+ // Invalidate cache on queue changes
72
+ eventManager.on(EventTypes.QUEUE_UPDATED, (event) => {
73
+ invalidateQueue(event.entityId);
74
+ });
75
+
76
+ eventManager.on(EventTypes.QUEUE_DELETED, (event) => {
77
+ invalidateQueue(event.entityId);
78
+ });
79
+
80
+ eventManager.on(EventTypes.PARTITION_CREATED, (event) => {
81
+ invalidate(event.queueName, event.entityId);
82
+ });
83
+
84
+ eventManager.on(EventTypes.PARTITION_DELETED, (event) => {
85
+ invalidate(event.queueName, event.entityId);
86
+ });
87
+ };
88
+
89
+ return {
90
+ checkResource,
91
+ cacheResource,
92
+ invalidate,
93
+ invalidateQueue,
94
+ registerEventHandlers
95
+ };
96
+ };
@@ -0,0 +1,127 @@
1
+ import { generateUUID } from '../utils/uuid.js';
2
+
3
+ export const SYSTEM_QUEUE = '__system_events__';
4
+
5
+ export const EventTypes = {
6
+ // Queue events
7
+ QUEUE_CREATED: 'queue.created',
8
+ QUEUE_UPDATED: 'queue.updated',
9
+ QUEUE_DELETED: 'queue.deleted',
10
+
11
+ // Partition events
12
+ PARTITION_CREATED: 'partition.created',
13
+ PARTITION_DELETED: 'partition.deleted',
14
+
15
+ // Future events
16
+ CONSUMER_GROUP_CREATED: 'consumer_group.created',
17
+ CONSUMER_GROUP_UPDATED: 'consumer_group.updated'
18
+ };
19
+
20
+ export class SystemEventManager {
21
+ constructor(pool, serverInstanceId) {
22
+ this.pool = pool;
23
+ this.serverInstanceId = serverInstanceId;
24
+ this.handlers = new Map();
25
+ this.eventQueue = [];
26
+ this.batchTimer = null;
27
+ }
28
+
29
+ // Register event handlers
30
+ on(eventType, handler) {
31
+ if (!this.handlers.has(eventType)) {
32
+ this.handlers.set(eventType, []);
33
+ }
34
+ this.handlers.get(eventType).push(handler);
35
+ }
36
+
37
+ // Emit event locally and to system queue
38
+ async emit(eventType, data) {
39
+ const event = {
40
+ id: generateUUID(),
41
+ eventType,
42
+ ...data,
43
+ timestamp: Date.now(),
44
+ sourceServer: this.serverInstanceId,
45
+ version: 1
46
+ };
47
+
48
+ // Handle locally first (immediate consistency for originating server)
49
+ await this.handleEventLocally(event);
50
+
51
+ // Queue for batch publishing
52
+ this.eventQueue.push(event);
53
+ this.scheduleBatch();
54
+ }
55
+
56
+ // Process incoming event from system queue
57
+ async processSystemEvent(event) {
58
+ // Skip own events (already processed locally)
59
+ if (event.sourceServer === this.serverInstanceId) {
60
+ return;
61
+ }
62
+
63
+ await this.handleEventLocally(event);
64
+ }
65
+
66
+ async handleEventLocally(event) {
67
+ const handlers = this.handlers.get(event.eventType) || [];
68
+ for (const handler of handlers) {
69
+ try {
70
+ await handler(event);
71
+ } catch (error) {
72
+ console.error(`Handler failed for ${event.eventType}:`, error);
73
+ }
74
+ }
75
+ }
76
+
77
+ scheduleBatch() {
78
+ if (this.batchTimer) return;
79
+
80
+ this.batchTimer = setTimeout(() => {
81
+ this.publishBatch();
82
+ }, 10); // 10ms batching
83
+ }
84
+
85
+ async publishBatch() {
86
+ if (this.eventQueue.length === 0) return;
87
+
88
+ const events = [...this.eventQueue];
89
+ this.eventQueue = [];
90
+ this.batchTimer = null;
91
+
92
+ try {
93
+ // Direct database insert to avoid circular dependency
94
+ const client = await this.pool.connect();
95
+ try {
96
+ for (const event of events) {
97
+ await this.insertSystemEvent(client, event);
98
+ }
99
+ } finally {
100
+ client.release();
101
+ }
102
+ } catch (error) {
103
+ console.error('Failed to publish system events:', error);
104
+ }
105
+ }
106
+
107
+ async insertSystemEvent(client, event) {
108
+ // Direct insert bypassing queue manager to avoid circular dependency
109
+ await client.query(`
110
+ INSERT INTO queen.messages (
111
+ id, transaction_id, partition_id, payload,
112
+ created_at, trace_id
113
+ )
114
+ SELECT
115
+ $1, $2, p.id, $3, NOW(), $4
116
+ FROM queen.partitions p
117
+ JOIN queen.queues q ON p.queue_id = q.id
118
+ WHERE q.name = $5 AND p.name = 'Default'
119
+ `, [
120
+ generateUUID(),
121
+ generateUUID(),
122
+ JSON.stringify(event),
123
+ event.id,
124
+ SYSTEM_QUEUE
125
+ ]);
126
+ }
127
+ }
@@ -0,0 +1,26 @@
1
+ export const createAckRoute = (queueManager) => {
2
+ return async (body) => {
3
+ const { transactionId, status, error, consumerGroup } = body;
4
+
5
+ if (!transactionId) {
6
+ throw new Error('transactionId is required');
7
+ }
8
+
9
+ if (!status || !['completed', 'failed'].includes(status)) {
10
+ throw new Error('status must be "completed" or "failed"');
11
+ }
12
+
13
+ const result = await queueManager.acknowledgeMessage(transactionId, status, error, consumerGroup);
14
+
15
+ if (!result) {
16
+ throw new Error('Message not found or not in processing state');
17
+ }
18
+
19
+ return {
20
+ transactionId: result.transaction_id,
21
+ status: result.status,
22
+ consumerGroup: consumerGroup || null,
23
+ acknowledgedAt: new Date().toISOString()
24
+ };
25
+ };
26
+ };