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,214 @@
1
+ -- Queen Message Queue Schema V2 with Bus/Consumer Group Support
2
+ -- Structure: Queues → Partitions → Messages → Status (per consumer group)
3
+
4
+ -- Create schema only if it doesn't exist (preserves existing data)
5
+ CREATE SCHEMA IF NOT EXISTS queen;
6
+
7
+ -- Queues table (top level, with all configuration options)
8
+ CREATE TABLE IF NOT EXISTS queen.queues (
9
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
10
+ name VARCHAR(255) UNIQUE NOT NULL,
11
+ namespace VARCHAR(255), -- optional grouping
12
+ task VARCHAR(255), -- optional grouping
13
+ priority INTEGER DEFAULT 0, -- Queue priority (higher = processed first)
14
+ -- Configuration options (all at queue level now)
15
+ lease_time INTEGER DEFAULT 300,
16
+ retry_limit INTEGER DEFAULT 3,
17
+ retry_delay INTEGER DEFAULT 1000,
18
+ max_size INTEGER DEFAULT 10000,
19
+ ttl INTEGER DEFAULT 3600,
20
+ dead_letter_queue BOOLEAN DEFAULT FALSE,
21
+ dlq_after_max_retries BOOLEAN DEFAULT FALSE,
22
+ delayed_processing INTEGER DEFAULT 0,
23
+ window_buffer INTEGER DEFAULT 0,
24
+ retention_seconds INTEGER DEFAULT 0,
25
+ completed_retention_seconds INTEGER DEFAULT 0,
26
+ retention_enabled BOOLEAN DEFAULT FALSE,
27
+ encryption_enabled BOOLEAN DEFAULT FALSE,
28
+ max_wait_time_seconds INTEGER DEFAULT 0,
29
+ max_queue_size INTEGER DEFAULT 0, -- 0 = unlimited queue size
30
+ created_at TIMESTAMPTZ DEFAULT NOW()
31
+ );
32
+
33
+ -- Partitions table (subdivisions of queues, where FIFO happens)
34
+ CREATE TABLE IF NOT EXISTS queen.partitions (
35
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
36
+ queue_id UUID REFERENCES queen.queues(id) ON DELETE CASCADE,
37
+ name VARCHAR(255) NOT NULL DEFAULT 'Default',
38
+ created_at TIMESTAMPTZ DEFAULT NOW(),
39
+ last_activity TIMESTAMPTZ DEFAULT NOW(),
40
+ UNIQUE(queue_id, name)
41
+ );
42
+
43
+ -- Messages table (immutable message data only)
44
+ CREATE TABLE IF NOT EXISTS queen.messages (
45
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
46
+ transaction_id VARCHAR(255) UNIQUE NOT NULL, -- Changed from UUID to string for flexibility
47
+ trace_id UUID DEFAULT gen_random_uuid(), -- For tracking multi-step pipelines
48
+ partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
49
+ payload JSONB NOT NULL,
50
+ created_at TIMESTAMPTZ DEFAULT NOW(),
51
+ is_encrypted BOOLEAN DEFAULT FALSE
52
+ -- Removed all status fields - moved to messages_status table
53
+ );
54
+
55
+ -- Messages status table (tracks consumption per consumer group)
56
+ CREATE TABLE IF NOT EXISTS queen.messages_status (
57
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
58
+ message_id UUID REFERENCES queen.messages(id) ON DELETE CASCADE,
59
+ consumer_group VARCHAR(255), -- NULL for queue mode (competing consumers)
60
+ status VARCHAR(20) DEFAULT 'pending',
61
+ worker_id VARCHAR(255),
62
+ locked_at TIMESTAMPTZ,
63
+ completed_at TIMESTAMPTZ,
64
+ failed_at TIMESTAMPTZ,
65
+ error_message TEXT,
66
+ retry_count INTEGER DEFAULT 0,
67
+ lease_expires_at TIMESTAMPTZ,
68
+ processing_at TIMESTAMPTZ,
69
+ created_at TIMESTAMPTZ DEFAULT NOW(),
70
+ UNIQUE(message_id, consumer_group) -- One status per message per consumer group
71
+ );
72
+
73
+ -- Consumer groups registry (tracks subscription preferences)
74
+ CREATE TABLE IF NOT EXISTS queen.consumer_groups (
75
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
76
+ queue_id UUID REFERENCES queen.queues(id) ON DELETE CASCADE,
77
+ name VARCHAR(255) NOT NULL,
78
+ created_at TIMESTAMPTZ DEFAULT NOW(),
79
+ subscription_start_from TIMESTAMPTZ, -- NULL = consume all, timestamp = consume from this point
80
+ active BOOLEAN DEFAULT true,
81
+ last_seen_at TIMESTAMPTZ DEFAULT NOW(),
82
+ UNIQUE(queue_id, name)
83
+ );
84
+
85
+ -- Partition leases table (tracks which partition is locked for which consumer group)
86
+ CREATE TABLE IF NOT EXISTS queen.partition_leases (
87
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
88
+ partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
89
+ consumer_group VARCHAR(255) DEFAULT '__QUEUE_MODE__', -- '__QUEUE_MODE__' for queue mode
90
+ lease_expires_at TIMESTAMPTZ NOT NULL,
91
+ message_batch JSONB, -- Store IDs of messages in this lease
92
+ created_at TIMESTAMPTZ DEFAULT NOW(),
93
+ released_at TIMESTAMPTZ, -- When lease was released (NULL if active)
94
+ UNIQUE(partition_id, consumer_group) -- One active lease per partition per consumer group
95
+ );
96
+
97
+ -- Indexes for queues
98
+ CREATE INDEX IF NOT EXISTS idx_queues_name ON queen.queues(name);
99
+ CREATE INDEX IF NOT EXISTS idx_queues_priority ON queen.queues(priority DESC);
100
+ CREATE INDEX IF NOT EXISTS idx_queues_namespace ON queen.queues(namespace) WHERE namespace IS NOT NULL;
101
+ CREATE INDEX IF NOT EXISTS idx_queues_task ON queen.queues(task) WHERE task IS NOT NULL;
102
+ CREATE INDEX IF NOT EXISTS idx_queues_namespace_task ON queen.queues(namespace, task) WHERE namespace IS NOT NULL AND task IS NOT NULL;
103
+ CREATE INDEX IF NOT EXISTS idx_queues_namespace_priority ON queen.queues(namespace, priority DESC) WHERE namespace IS NOT NULL;
104
+ CREATE INDEX IF NOT EXISTS idx_queues_task_priority ON queen.queues(task, priority DESC) WHERE task IS NOT NULL;
105
+ CREATE INDEX IF NOT EXISTS idx_queues_retention_enabled ON queen.queues(retention_enabled) WHERE retention_enabled = true;
106
+ CREATE INDEX IF NOT EXISTS idx_queues_lease_time ON queen.queues(lease_time);
107
+ CREATE INDEX IF NOT EXISTS idx_queues_ttl ON queen.queues(ttl);
108
+
109
+ -- Indexes for partitions
110
+ CREATE INDEX IF NOT EXISTS idx_partitions_queue_name ON queen.partitions(queue_id, name);
111
+
112
+ -- Indexes for messages (simplified - no status fields)
113
+ CREATE INDEX IF NOT EXISTS idx_messages_partition_created ON queen.messages(partition_id, created_at);
114
+ CREATE INDEX IF NOT EXISTS idx_messages_transaction_id ON queen.messages(transaction_id);
115
+ CREATE INDEX IF NOT EXISTS idx_messages_trace_id ON queen.messages(trace_id);
116
+ CREATE INDEX IF NOT EXISTS idx_messages_created_at ON queen.messages(created_at);
117
+
118
+ -- Indexes for messages_status (critical for performance)
119
+ -- Note: Removed the partial unique index idx_status_queue_mode as it conflicts with
120
+ -- the table-level UNIQUE(message_id, consumer_group) constraint
121
+
122
+ -- Bus mode index (consumer_group IS NOT NULL)
123
+ CREATE INDEX IF NOT EXISTS idx_status_bus_mode
124
+ ON queen.messages_status(message_id, consumer_group, status)
125
+ WHERE consumer_group IS NOT NULL;
126
+
127
+ -- Index for finding pending messages by consumer group
128
+ CREATE INDEX IF NOT EXISTS idx_status_pending
129
+ ON queen.messages_status(consumer_group, status, message_id)
130
+ WHERE status = 'pending';
131
+
132
+ -- Index for lease expiration checks
133
+ CREATE INDEX IF NOT EXISTS idx_status_lease_expires
134
+ ON queen.messages_status(lease_expires_at, status, consumer_group)
135
+ WHERE status = 'processing';
136
+
137
+ -- Index for fast queue depth checks
138
+ CREATE INDEX IF NOT EXISTS idx_messages_status_pending_processing
139
+ ON queen.messages_status(message_id, status)
140
+ WHERE status IN ('pending', 'processing') OR status IS NULL;
141
+
142
+ -- Index for efficient pop operations (find messages without status for a consumer group)
143
+ CREATE INDEX IF NOT EXISTS idx_status_message_consumer
144
+ ON queen.messages_status(message_id, consumer_group);
145
+
146
+ -- Indexes for consumer groups
147
+ CREATE INDEX IF NOT EXISTS idx_consumer_groups_queue_name
148
+ ON queen.consumer_groups(queue_id, name);
149
+
150
+ CREATE INDEX IF NOT EXISTS idx_consumer_groups_active
151
+ ON queen.consumer_groups(queue_id, active)
152
+ WHERE active = true;
153
+
154
+ -- Indexes for partition leases
155
+ CREATE INDEX IF NOT EXISTS idx_partition_leases_expires
156
+ ON queen.partition_leases(lease_expires_at)
157
+ WHERE released_at IS NULL;
158
+
159
+ CREATE INDEX IF NOT EXISTS idx_partition_leases_active
160
+ ON queen.partition_leases(partition_id, consumer_group)
161
+ WHERE released_at IS NULL;
162
+
163
+ CREATE INDEX IF NOT EXISTS idx_partition_leases_active_lookup
164
+ ON queen.partition_leases(partition_id, consumer_group, lease_expires_at)
165
+ WHERE released_at IS NULL;
166
+
167
+ -- Retention history table for tracking deletions
168
+ CREATE TABLE IF NOT EXISTS queen.retention_history (
169
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
170
+ partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
171
+ messages_deleted INTEGER DEFAULT 0,
172
+ retention_type VARCHAR(50), -- 'retention', 'completed', 'evicted'
173
+ executed_at TIMESTAMPTZ DEFAULT NOW()
174
+ );
175
+
176
+ CREATE INDEX IF NOT EXISTS idx_retention_history_partition
177
+ ON queen.retention_history(partition_id);
178
+
179
+ CREATE INDEX IF NOT EXISTS idx_retention_history_executed
180
+ ON queen.retention_history(executed_at);
181
+
182
+ -- Trigger to update partition last_activity
183
+ CREATE OR REPLACE FUNCTION update_partition_last_activity()
184
+ RETURNS TRIGGER AS $$
185
+ BEGIN
186
+ UPDATE queen.partitions
187
+ SET last_activity = NOW()
188
+ WHERE id = NEW.partition_id;
189
+ RETURN NEW;
190
+ END;
191
+ $$ LANGUAGE plpgsql;
192
+
193
+ DROP TRIGGER IF EXISTS trigger_update_partition_activity ON queen.messages;
194
+ CREATE TRIGGER trigger_update_partition_activity
195
+ AFTER INSERT ON queen.messages
196
+ FOR EACH ROW
197
+ EXECUTE FUNCTION update_partition_last_activity();
198
+
199
+ -- Function to clean up old completed statuses in bus mode
200
+ CREATE OR REPLACE FUNCTION cleanup_completed_statuses()
201
+ RETURNS void AS $$
202
+ BEGIN
203
+ -- Delete completed statuses older than retention period for bus mode
204
+ DELETE FROM queen.messages_status ms
205
+ USING queen.messages m, queen.partitions p, queen.queues q
206
+ WHERE ms.message_id = m.id
207
+ AND m.partition_id = p.id
208
+ AND p.queue_id = q.id
209
+ AND ms.status IN ('completed', 'failed')
210
+ AND ms.consumer_group IS NOT NULL -- Only for bus mode
211
+ AND q.completed_retention_seconds > 0
212
+ AND ms.completed_at < NOW() - INTERVAL '1 second' * q.completed_retention_seconds;
213
+ END;
214
+ $$ LANGUAGE plpgsql;
@@ -0,0 +1,59 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ export const createEventManager = () => {
4
+ const emitter = new EventEmitter();
5
+ emitter.setMaxListeners(1000); // Support many concurrent long polls
6
+
7
+ const emit = (event, data) => {
8
+ emitter.emit(event, data);
9
+ };
10
+
11
+ const on = (event, handler) => {
12
+ emitter.on(event, handler);
13
+ };
14
+
15
+ const once = (event, handler) => {
16
+ emitter.once(event, handler);
17
+ };
18
+
19
+ const removeListener = (event, handler) => {
20
+ emitter.removeListener(event, handler);
21
+ };
22
+
23
+ // Wait for a message with timeout
24
+ const waitForMessage = (queuePath, timeout = 30000) => {
25
+ return new Promise((resolve) => {
26
+ const timer = setTimeout(() => {
27
+ removeListener(`message:${queuePath}`, handler);
28
+ resolve(null);
29
+ }, timeout);
30
+
31
+ const handler = (data) => {
32
+ clearTimeout(timer);
33
+ resolve(data);
34
+ };
35
+
36
+ once(`message:${queuePath}`, handler);
37
+ });
38
+ };
39
+
40
+ // Notify when message is available
41
+ const notifyMessageAvailable = (queuePath) => {
42
+ emit(`message:${queuePath}`, { available: true });
43
+ // Also emit for parent queue if this is a partition path
44
+ const parts = queuePath.split('/');
45
+ if (parts.length === 2) {
46
+ // This is queue/partition, also notify queue level
47
+ emit(`message:${parts[0]}`, { available: true });
48
+ }
49
+ };
50
+
51
+ return {
52
+ emit,
53
+ on,
54
+ once,
55
+ removeListener,
56
+ waitForMessage,
57
+ notifyMessageAvailable
58
+ };
59
+ };