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,302 @@
1
+ <template>
2
+ <div class="chart-container">
3
+ <div class="chart-header">
4
+ <h3 class="chart-title">{{ title || 'Message Throughput' }}</h3>
5
+ <div class="chart-actions">
6
+ <Button
7
+ icon="pi pi-refresh"
8
+ class="p-button-text p-button-sm"
9
+ @click="$emit('refresh')"
10
+ v-tooltip="'Refresh'"
11
+ />
12
+ </div>
13
+ </div>
14
+
15
+ <div class="throughput-chart">
16
+ <Chart
17
+ v-if="!loading && chartDataWithTheme && chartDataWithTheme.labels && chartDataWithTheme.labels.length > 0"
18
+ type="bar"
19
+ :data="chartDataWithTheme"
20
+ :options="chartOptionsWithTheme"
21
+ class="chart"
22
+ />
23
+ <div v-else-if="loading" class="chart-loading">
24
+ <ProgressSpinner />
25
+ <span>Loading chart data...</span>
26
+ </div>
27
+ <div v-else class="chart-empty">
28
+ <i class="pi pi-chart-bar empty-icon"></i>
29
+ <span>No data available</span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { computed } from 'vue'
37
+ import Chart from 'primevue/chart'
38
+ import Button from 'primevue/button'
39
+ import ProgressSpinner from 'primevue/progressspinner'
40
+
41
+ // Import and register Chart.js components
42
+ import {
43
+ Chart as ChartJS,
44
+ CategoryScale,
45
+ LinearScale,
46
+ BarElement,
47
+ Title,
48
+ Tooltip,
49
+ Legend
50
+ } from 'chart.js'
51
+
52
+ ChartJS.register(
53
+ CategoryScale,
54
+ LinearScale,
55
+ BarElement,
56
+ Title,
57
+ Tooltip,
58
+ Legend
59
+ )
60
+
61
+ const props = defineProps({
62
+ title: {
63
+ type: String,
64
+ default: null
65
+ },
66
+ data: {
67
+ type: Object,
68
+ default: null
69
+ },
70
+ loading: {
71
+ type: Boolean,
72
+ default: false
73
+ },
74
+ stacked: {
75
+ type: Boolean,
76
+ default: true
77
+ }
78
+ })
79
+
80
+ const emit = defineEmits(['refresh'])
81
+
82
+ // Apply dark theme colors to chart data
83
+ const chartDataWithTheme = computed(() => {
84
+ if (!props.data) return null
85
+
86
+ return {
87
+ labels: props.data.labels,
88
+ datasets: [
89
+ {
90
+ label: 'Incoming Messages',
91
+ backgroundColor: '#ec4899',
92
+ borderColor: '#ec4899',
93
+ borderWidth: 0,
94
+ borderRadius: 4,
95
+ data: props.data.datasets?.[0]?.data || []
96
+ },
97
+ {
98
+ label: 'Completed Messages',
99
+ backgroundColor: 'rgba(236, 72, 153, 0.5)',
100
+ borderColor: 'rgba(236, 72, 153, 0.5)',
101
+ borderWidth: 0,
102
+ borderRadius: 4,
103
+ data: props.data.datasets?.[1]?.data || []
104
+ },
105
+ {
106
+ label: 'Failed Messages',
107
+ backgroundColor: 'rgba(236, 72, 153, 0.2)',
108
+ borderColor: 'rgba(236, 72, 153, 0.2)',
109
+ borderWidth: 0,
110
+ borderRadius: 4,
111
+ data: props.data.datasets?.[2]?.data || []
112
+ }
113
+ ]
114
+ }
115
+ })
116
+
117
+ // Dark theme chart options
118
+ const chartOptionsWithTheme = computed(() => ({
119
+ responsive: true,
120
+ maintainAspectRatio: false,
121
+ plugins: {
122
+ legend: {
123
+ position: 'bottom',
124
+ labels: {
125
+ padding: 15,
126
+ usePointStyle: true,
127
+ font: {
128
+ size: 11,
129
+ family: 'Inter'
130
+ },
131
+ color: '#cbd5e1'
132
+ }
133
+ },
134
+ tooltip: {
135
+ backgroundColor: 'rgba(30, 41, 59, 0.95)',
136
+ titleColor: '#f1f5f9',
137
+ bodyColor: '#cbd5e1',
138
+ borderColor: 'rgba(236, 72, 153, 0.3)',
139
+ borderWidth: 1,
140
+ cornerRadius: 8,
141
+ padding: 12,
142
+ displayColors: true,
143
+ mode: 'index',
144
+ intersect: false,
145
+ callbacks: {
146
+ label: (context) => {
147
+ return `${context.dataset.label}: ${context.parsed.y.toLocaleString()} messages`
148
+ }
149
+ }
150
+ }
151
+ },
152
+ scales: {
153
+ x: {
154
+ stacked: props.stacked,
155
+ grid: {
156
+ display: false
157
+ },
158
+ ticks: {
159
+ color: '#94a3b8',
160
+ maxRotation: 0,
161
+ autoSkipPadding: 20,
162
+ font: {
163
+ size: 11
164
+ }
165
+ },
166
+ border: {
167
+ display: false
168
+ }
169
+ },
170
+ y: {
171
+ stacked: props.stacked,
172
+ beginAtZero: true,
173
+ grid: {
174
+ color: 'rgba(255, 255, 255, 0.03)',
175
+ drawBorder: false
176
+ },
177
+ ticks: {
178
+ color: '#94a3b8',
179
+ callback: (value) => {
180
+ if (value >= 1000000) {
181
+ return (value / 1000000).toFixed(1) + 'M'
182
+ } else if (value >= 1000) {
183
+ return (value / 1000).toFixed(1) + 'K'
184
+ }
185
+ return value
186
+ },
187
+ font: {
188
+ size: 11
189
+ }
190
+ },
191
+ border: {
192
+ display: false
193
+ }
194
+ }
195
+ },
196
+ interaction: {
197
+ mode: 'index',
198
+ intersect: false
199
+ },
200
+ animation: {
201
+ duration: 300,
202
+ easing: 'easeInOutQuart',
203
+ // Smooth animations for real-time updates
204
+ onComplete: null,
205
+ onProgress: null
206
+ },
207
+ // Enable smooth transitions for data updates
208
+ transitions: {
209
+ active: {
210
+ animation: {
211
+ duration: 200
212
+ }
213
+ }
214
+ }
215
+ }))
216
+ </script>
217
+
218
+ <style scoped>
219
+ .chart-container {
220
+ background: transparent;
221
+ border: 1px solid rgba(255, 255, 255, 0.1);
222
+ border-radius: 16px;
223
+ padding: 1.5rem;
224
+ height: 100%;
225
+ display: flex;
226
+ flex-direction: column;
227
+ }
228
+
229
+ .chart-header {
230
+ display: flex;
231
+ justify-content: space-between;
232
+ align-items: center;
233
+ margin-bottom: 1.5rem;
234
+ }
235
+
236
+ .chart-title {
237
+ font-size: 1.125rem;
238
+ font-weight: 600;
239
+ color: var(--surface-700);
240
+ margin: 0;
241
+ }
242
+
243
+ .chart-actions {
244
+ display: flex;
245
+ gap: 0.5rem;
246
+ }
247
+
248
+ .throughput-chart {
249
+ flex: 1;
250
+ min-height: 300px;
251
+ position: relative;
252
+ }
253
+
254
+ .chart {
255
+ height: 100%;
256
+ }
257
+
258
+ .chart-loading,
259
+ .chart-empty {
260
+ height: 100%;
261
+ display: flex;
262
+ flex-direction: column;
263
+ align-items: center;
264
+ justify-content: center;
265
+ gap: 1rem;
266
+ color: var(--surface-400);
267
+ }
268
+
269
+ .empty-icon {
270
+ font-size: 3rem;
271
+ color: var(--surface-300);
272
+ }
273
+
274
+ :deep(.p-button-sm) {
275
+ padding: 0.5rem;
276
+ font-size: 0.875rem;
277
+ }
278
+
279
+ :deep(.p-button-text) {
280
+ color: var(--surface-500);
281
+ }
282
+
283
+ :deep(.p-button-text:hover) {
284
+ background: rgba(236, 72, 153, 0.1);
285
+ color: var(--primary-500);
286
+ }
287
+
288
+ /* Responsive */
289
+ @media (max-width: 768px) {
290
+ .chart-container {
291
+ padding: 1rem;
292
+ }
293
+
294
+ .chart-title {
295
+ font-size: 1rem;
296
+ }
297
+
298
+ .throughput-chart {
299
+ min-height: 250px;
300
+ }
301
+ }
302
+ </style>
@@ -0,0 +1,251 @@
1
+ <template>
2
+ <div class="activity-feed">
3
+ <ScrollPanel class="feed-container">
4
+ <div v-if="events.length > 0" class="feed-items">
5
+ <div
6
+ v-for="event in events"
7
+ :key="event.id"
8
+ class="feed-item fade-in"
9
+ >
10
+ <div class="feed-icon" :class="getEventClass(event.event)">
11
+ <i :class="getEventIcon(event.event)"></i>
12
+ </div>
13
+
14
+ <div class="feed-content">
15
+ <div class="feed-title">{{ getEventTitle(event.event) }}</div>
16
+ <div class="feed-details">
17
+ <span v-if="event.data && event.data.queue" class="feed-queue">
18
+ {{ event.data.queue }}
19
+ <span v-if="event.data.partition && event.data.partition !== 'Default'">
20
+ /{{ event.data.partition }}
21
+ </span>
22
+ </span>
23
+ <span v-if="event.data && event.data.transactionId" class="feed-id">
24
+ {{ event.data.transactionId.substring(0, 8) }}...
25
+ </span>
26
+ </div>
27
+ <div class="feed-time">{{ formatTime(event.timestamp) }}</div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <div v-else class="feed-empty">
33
+ <i class="pi pi-inbox empty-icon"></i>
34
+ <span>No activity yet</span>
35
+ <span class="empty-subtitle">Events will appear here in real-time</span>
36
+ </div>
37
+ </ScrollPanel>
38
+ </div>
39
+ </template>
40
+
41
+ <script setup>
42
+ import { computed } from 'vue'
43
+ import ScrollPanel from 'primevue/scrollpanel'
44
+ import { formatRelativeTime } from '../../utils/helpers.js'
45
+
46
+ const props = defineProps({
47
+ events: {
48
+ type: Array,
49
+ default: () => []
50
+ }
51
+ })
52
+
53
+ // Get event icon
54
+ const getEventIcon = (eventType) => {
55
+ if (!eventType || typeof eventType !== 'string') return 'pi pi-info-circle'
56
+ const icons = {
57
+ 'message.pushed': 'pi pi-plus',
58
+ 'message.processing': 'pi pi-spin pi-spinner',
59
+ 'message.completed': 'pi pi-check',
60
+ 'message.failed': 'pi pi-times',
61
+ 'queue.created': 'pi pi-inbox',
62
+ 'queue.depth': 'pi pi-chart-bar',
63
+ 'system.stats': 'pi pi-chart-line',
64
+ 'client.connected': 'pi pi-sign-in',
65
+ 'client.disconnected': 'pi pi-sign-out'
66
+ }
67
+ return icons[eventType] || 'pi pi-info-circle'
68
+ }
69
+
70
+ // Get event class for styling
71
+ const getEventClass = (eventType) => {
72
+ if (!eventType || typeof eventType !== 'string') return 'event-default'
73
+ if (eventType.includes('completed')) return 'event-success'
74
+ if (eventType.includes('failed')) return 'event-danger'
75
+ if (eventType.includes('processing')) return 'event-info'
76
+ if (eventType.includes('pushed') || eventType.includes('created')) return 'event-primary'
77
+ return 'event-default'
78
+ }
79
+
80
+ // Get event title
81
+ const getEventTitle = (eventType) => {
82
+ if (!eventType || typeof eventType !== 'string') return 'Unknown Event'
83
+ const titles = {
84
+ 'message.pushed': 'Message Pushed',
85
+ 'message.processing': 'Processing Message',
86
+ 'message.completed': 'Message Completed',
87
+ 'message.failed': 'Message Failed',
88
+ 'queue.created': 'Queue Created',
89
+ 'queue.depth': 'Queue Depth Update',
90
+ 'system.stats': 'System Stats Update',
91
+ 'client.connected': 'Client Connected',
92
+ 'client.disconnected': 'Client Disconnected'
93
+ }
94
+ return titles[eventType] || eventType
95
+ }
96
+
97
+ // Format time
98
+ const formatTime = (timestamp) => {
99
+ return formatRelativeTime(timestamp)
100
+ }
101
+ </script>
102
+
103
+ <style scoped>
104
+ .activity-feed {
105
+ height: 100%;
106
+ }
107
+
108
+ .feed-container {
109
+ height: 350px;
110
+ }
111
+
112
+ :deep(.p-scrollpanel-content) {
113
+ padding: 0.5rem;
114
+ }
115
+
116
+ .feed-items {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: 0.5rem;
120
+ }
121
+
122
+ .feed-item {
123
+ display: flex;
124
+ align-items: flex-start;
125
+ gap: 1rem;
126
+ padding: 0.875rem;
127
+ border-radius: 8px;
128
+ background: var(--surface-0);
129
+ border: 1px solid rgba(255, 255, 255, 0.03);
130
+ transition: all 0.2s ease;
131
+ }
132
+
133
+ .feed-item:hover {
134
+ background: rgba(236, 72, 153, 0.05);
135
+ border-color: rgba(236, 72, 153, 0.1);
136
+ }
137
+
138
+ .feed-icon {
139
+ width: 36px;
140
+ height: 36px;
141
+ border-radius: 8px;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ flex-shrink: 0;
146
+ font-size: 0.875rem;
147
+ }
148
+
149
+ .event-primary {
150
+ background: rgba(236, 72, 153, 0.15);
151
+ color: var(--primary-500);
152
+ }
153
+
154
+ .event-success {
155
+ background: rgba(16, 185, 129, 0.15);
156
+ color: var(--success-color);
157
+ }
158
+
159
+ .event-info {
160
+ background: rgba(6, 182, 212, 0.15);
161
+ color: var(--info-color);
162
+ }
163
+
164
+ .event-danger {
165
+ background: rgba(239, 68, 68, 0.15);
166
+ color: var(--danger-color);
167
+ }
168
+
169
+ .event-default {
170
+ background: rgba(148, 163, 184, 0.15);
171
+ color: var(--surface-400);
172
+ }
173
+
174
+ .feed-content {
175
+ flex: 1;
176
+ min-width: 0;
177
+ }
178
+
179
+ .feed-title {
180
+ font-size: 0.875rem;
181
+ font-weight: 500;
182
+ color: var(--surface-600);
183
+ margin-bottom: 0.125rem;
184
+ }
185
+
186
+ .feed-details {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 0.5rem;
190
+ font-size: 0.75rem;
191
+ color: var(--surface-400);
192
+ margin-bottom: 0.25rem;
193
+ }
194
+
195
+ .feed-queue {
196
+ background: rgba(236, 72, 153, 0.1);
197
+ color: var(--primary-500);
198
+ padding: 0.125rem 0.5rem;
199
+ border-radius: 4px;
200
+ font-weight: 500;
201
+ }
202
+
203
+ .feed-id {
204
+ font-family: 'Courier New', monospace;
205
+ font-size: 0.75rem;
206
+ color: var(--surface-400);
207
+ }
208
+
209
+ .feed-time {
210
+ font-size: 0.75rem;
211
+ color: var(--surface-300);
212
+ }
213
+
214
+ .feed-empty {
215
+ height: 100%;
216
+ display: flex;
217
+ flex-direction: column;
218
+ align-items: center;
219
+ justify-content: center;
220
+ gap: 0.5rem;
221
+ color: var(--surface-400);
222
+ padding: 2rem;
223
+ text-align: center;
224
+ }
225
+
226
+ .empty-icon {
227
+ font-size: 3rem;
228
+ color: var(--surface-300);
229
+ margin-bottom: 0.5rem;
230
+ }
231
+
232
+ .empty-subtitle {
233
+ font-size: 0.875rem;
234
+ color: var(--surface-300);
235
+ }
236
+
237
+ .fade-in {
238
+ animation: fadeIn 0.3s ease;
239
+ }
240
+
241
+ @keyframes fadeIn {
242
+ from {
243
+ opacity: 0;
244
+ transform: translateY(-10px);
245
+ }
246
+ to {
247
+ opacity: 1;
248
+ transform: translateY(0);
249
+ }
250
+ }
251
+ </style>