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 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
@@ -0,0 +1,298 @@
1
+ <template>
2
+ <div class="metric-card-v3" :class="`metric-${color}`">
3
+ <div class="metric-icon-wrapper">
4
+ <i :class="icon"></i>
5
+ </div>
6
+
7
+ <div class="metric-content">
8
+ <p class="metric-label">{{ title }}</p>
9
+
10
+ <h3 class="metric-value" v-if="!loading">
11
+ {{ formattedValue }}{{ suffix }}
12
+ </h3>
13
+ <Skeleton v-else height="2rem" width="80%" />
14
+
15
+ <p class="metric-change" :class="changeClass" v-if="change !== null && !loading">
16
+ <i :class="changeIcon"></i>
17
+ <span>{{ Math.abs(change) }}%</span>
18
+ <span class="metric-period">{{ period }}</span>
19
+ </p>
20
+ </div>
21
+
22
+ <div class="metric-sparkline" v-if="sparklineData && sparklineData.length > 0">
23
+ <svg viewBox="0 0 80 30" preserveAspectRatio="none">
24
+ <polyline
25
+ :points="sparklinePoints"
26
+ fill="none"
27
+ :stroke="sparklineColor"
28
+ stroke-width="2"
29
+ stroke-linecap="round"
30
+ stroke-linejoin="round"
31
+ />
32
+ </svg>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { computed } from 'vue'
39
+ import Skeleton from 'primevue/skeleton'
40
+
41
+ const props = defineProps({
42
+ title: {
43
+ type: String,
44
+ required: true
45
+ },
46
+ value: {
47
+ type: [String, Number],
48
+ required: true
49
+ },
50
+ icon: {
51
+ type: String,
52
+ default: 'pi pi-chart-line'
53
+ },
54
+ color: {
55
+ type: String,
56
+ default: 'primary',
57
+ validator: (value) => ['primary', 'success', 'warning', 'danger', 'info'].includes(value)
58
+ },
59
+ change: {
60
+ type: Number,
61
+ default: null
62
+ },
63
+ period: {
64
+ type: String,
65
+ default: 'vs last hour'
66
+ },
67
+ suffix: {
68
+ type: String,
69
+ default: ''
70
+ },
71
+ loading: {
72
+ type: Boolean,
73
+ default: false
74
+ },
75
+ sparklineData: {
76
+ type: Array,
77
+ default: () => []
78
+ }
79
+ })
80
+
81
+ // Format large numbers
82
+ const formattedValue = computed(() => {
83
+ const val = typeof props.value === 'string' ? parseFloat(props.value) : props.value
84
+ if (isNaN(val)) return props.value
85
+
86
+ if (val >= 1000000) {
87
+ return (val / 1000000).toFixed(1) + 'M'
88
+ } else if (val >= 1000) {
89
+ return (val / 1000).toFixed(1) + 'K'
90
+ }
91
+ return val.toLocaleString()
92
+ })
93
+
94
+ // Change indicator
95
+ const changeClass = computed(() => ({
96
+ positive: props.change > 0,
97
+ negative: props.change < 0,
98
+ neutral: props.change === 0
99
+ }))
100
+
101
+ const changeIcon = computed(() => {
102
+ if (props.change > 0) return 'pi pi-arrow-up'
103
+ if (props.change < 0) return 'pi pi-arrow-down'
104
+ return 'pi pi-minus'
105
+ })
106
+
107
+ // Sparkline calculation
108
+ const sparklinePoints = computed(() => {
109
+ if (!props.sparklineData || props.sparklineData.length === 0) return ''
110
+
111
+ const data = props.sparklineData
112
+ const max = Math.max(...data)
113
+ const min = Math.min(...data)
114
+ const range = max - min || 1
115
+
116
+ return data.map((value, index) => {
117
+ const x = (index / (data.length - 1)) * 80
118
+ const y = 30 - ((value - min) / range) * 25
119
+ return `${x},${y}`
120
+ }).join(' ')
121
+ })
122
+
123
+ const sparklineColor = computed(() => {
124
+ const colors = {
125
+ primary: '#ec4899',
126
+ success: '#10b981',
127
+ warning: '#f59e0b',
128
+ danger: '#ef4444',
129
+ info: '#06b6d4'
130
+ }
131
+ return colors[props.color] || colors.primary
132
+ })
133
+ </script>
134
+
135
+ <style scoped>
136
+ .metric-card-v3 {
137
+ background: transparent;
138
+ border: 1px solid rgba(255, 255, 255, 0.08);
139
+ border-radius: 12px;
140
+ padding: 1.25rem;
141
+ display: flex;
142
+ align-items: flex-start;
143
+ gap: 1rem;
144
+ position: relative;
145
+ overflow: hidden;
146
+ transition: all var(--transition-base);
147
+ min-height: auto;
148
+ }
149
+
150
+ /* Removed top gradient line */
151
+
152
+ .metric-card-v3:hover {
153
+ border-color: rgba(236, 72, 153, 0.15);
154
+ }
155
+
156
+ .metric-icon-wrapper {
157
+ width: 56px;
158
+ height: 56px;
159
+ border-radius: 14px;
160
+ background: var(--gradient-primary);
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ flex-shrink: 0;
165
+ }
166
+
167
+ .metric-success .metric-icon-wrapper {
168
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
169
+ }
170
+
171
+ .metric-warning .metric-icon-wrapper {
172
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
173
+ }
174
+
175
+ .metric-danger .metric-icon-wrapper {
176
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
177
+ }
178
+
179
+ .metric-info .metric-icon-wrapper {
180
+ background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%);
181
+ }
182
+
183
+ .metric-icon-wrapper i {
184
+ font-size: 1.375rem;
185
+ color: white;
186
+ }
187
+
188
+ .metric-content {
189
+ flex: 1;
190
+ min-width: 0;
191
+ }
192
+
193
+ .metric-label {
194
+ font-size: 0.625rem;
195
+ color: #78716c;
196
+ text-transform: uppercase;
197
+ letter-spacing: 0.1em;
198
+ margin: 0 0 0.5rem 0;
199
+ font-weight: 500;
200
+ opacity: 0.8;
201
+ }
202
+
203
+ .metric-value {
204
+ font-size: 2rem;
205
+ font-weight: 700;
206
+ color: #fafaf9;
207
+ line-height: 1;
208
+ margin: 0;
209
+ }
210
+
211
+ .metric-change {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ gap: 0.25rem;
215
+ font-size: 0.875rem;
216
+ font-weight: 500;
217
+ margin: 0;
218
+ }
219
+
220
+ .metric-change.positive {
221
+ color: var(--success-color);
222
+ }
223
+
224
+ .metric-change.negative {
225
+ color: var(--danger-color);
226
+ }
227
+
228
+ .metric-change.neutral {
229
+ color: var(--surface-400);
230
+ }
231
+
232
+ .metric-change i {
233
+ font-size: 0.75rem;
234
+ }
235
+
236
+ .metric-period {
237
+ color: var(--surface-400);
238
+ font-weight: 400;
239
+ margin-left: 0.25rem;
240
+ }
241
+
242
+ .metric-sparkline {
243
+ position: absolute;
244
+ right: 1.25rem;
245
+ bottom: 1.25rem;
246
+ width: 100px;
247
+ height: 40px;
248
+ opacity: 0.4;
249
+ }
250
+
251
+ .metric-card-v3:hover .metric-sparkline {
252
+ opacity: 0.6;
253
+ }
254
+
255
+ /* Responsive */
256
+ @media (max-width: 768px) {
257
+ .metric-card-v3 {
258
+ padding: 1rem;
259
+ min-height: 100px;
260
+ }
261
+
262
+ .metric-icon-wrapper {
263
+ width: 40px;
264
+ height: 40px;
265
+ }
266
+
267
+ .metric-icon-wrapper i {
268
+ font-size: 1rem;
269
+ }
270
+
271
+ .metric-value {
272
+ font-size: 1.5rem;
273
+ }
274
+
275
+ .metric-label {
276
+ font-size: 0.7rem;
277
+ }
278
+
279
+ .metric-change {
280
+ font-size: 0.75rem;
281
+ }
282
+
283
+ .metric-sparkline {
284
+ width: 60px;
285
+ height: 20px;
286
+ }
287
+ }
288
+
289
+ @media (max-width: 480px) {
290
+ .metric-card-v3 {
291
+ padding: 0.875rem;
292
+ }
293
+
294
+ .metric-value {
295
+ font-size: 1.25rem;
296
+ }
297
+ }
298
+ </style>
@@ -0,0 +1,276 @@
1
+ <template>
2
+ <div class="chart-container">
3
+ <div class="chart-header">
4
+ <h3 class="chart-title">{{ title || 'Queue Depth' }}</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="queue-depth-chart">
16
+ <Chart
17
+ v-if="!loading && chartDataWithTheme && chartDataWithTheme.labels && chartDataWithTheme.labels.length > 0"
18
+ :key="`queue-depth-${chartDataWithTheme.labels.length}`"
19
+ type="bar"
20
+ :data="chartDataWithTheme"
21
+ :options="chartOptionsWithTheme"
22
+ class="chart"
23
+ />
24
+ <div v-else-if="loading" class="chart-loading">
25
+ <ProgressSpinner />
26
+ <span>Loading chart data...</span>
27
+ </div>
28
+ <div v-else class="chart-empty">
29
+ <i class="pi pi-chart-bar empty-icon"></i>
30
+ <span>No queues with messages</span>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup>
37
+ import { computed } from 'vue'
38
+ import Chart from 'primevue/chart'
39
+ import Button from 'primevue/button'
40
+ import ProgressSpinner from 'primevue/progressspinner'
41
+
42
+ // Import and register Chart.js components
43
+ import {
44
+ Chart as ChartJS,
45
+ CategoryScale,
46
+ LinearScale,
47
+ BarElement,
48
+ Title,
49
+ Tooltip,
50
+ Legend
51
+ } from 'chart.js'
52
+
53
+ ChartJS.register(
54
+ CategoryScale,
55
+ LinearScale,
56
+ BarElement,
57
+ Title,
58
+ Tooltip,
59
+ Legend
60
+ )
61
+
62
+ const props = defineProps({
63
+ title: {
64
+ type: String,
65
+ default: null
66
+ },
67
+ data: {
68
+ type: Object,
69
+ default: null
70
+ },
71
+ loading: {
72
+ type: Boolean,
73
+ default: false
74
+ }
75
+ })
76
+
77
+ const emit = defineEmits(['refresh'])
78
+
79
+ // Apply dark theme colors to chart data
80
+ const chartDataWithTheme = computed(() => {
81
+ if (!props.data) return null
82
+
83
+ // Create gradient colors for each dataset
84
+ const colors = {
85
+ pending: '#f59e0b',
86
+ processing: '#06b6d4',
87
+ completed: '#10b981',
88
+ failed: '#ef4444',
89
+ dead_letter: '#64748b'
90
+ }
91
+
92
+ return {
93
+ labels: props.data.labels,
94
+ datasets: props.data.datasets?.map(dataset => ({
95
+ ...dataset,
96
+ backgroundColor: colors[dataset.label?.toLowerCase().replace(' ', '_')] || '#ec4899',
97
+ borderColor: 'transparent',
98
+ borderWidth: 0,
99
+ borderRadius: 4
100
+ })) || []
101
+ }
102
+ })
103
+
104
+ // Dark theme chart options
105
+ const chartOptionsWithTheme = computed(() => ({
106
+ responsive: true,
107
+ maintainAspectRatio: false,
108
+ plugins: {
109
+ legend: {
110
+ position: 'bottom',
111
+ labels: {
112
+ padding: 15,
113
+ usePointStyle: true,
114
+ font: {
115
+ size: 11,
116
+ family: 'Inter'
117
+ },
118
+ color: '#cbd5e1'
119
+ }
120
+ },
121
+ tooltip: {
122
+ backgroundColor: 'rgba(30, 41, 59, 0.95)',
123
+ titleColor: '#f1f5f9',
124
+ bodyColor: '#cbd5e1',
125
+ borderColor: 'rgba(236, 72, 153, 0.3)',
126
+ borderWidth: 1,
127
+ cornerRadius: 8,
128
+ padding: 12,
129
+ displayColors: true,
130
+ callbacks: {
131
+ label: (context) => {
132
+ return `${context.dataset.label}: ${context.parsed.y.toLocaleString()} messages`
133
+ }
134
+ }
135
+ }
136
+ },
137
+ scales: {
138
+ x: {
139
+ stacked: true,
140
+ grid: {
141
+ display: false
142
+ },
143
+ ticks: {
144
+ color: '#94a3b8',
145
+ maxRotation: 45,
146
+ minRotation: 0,
147
+ font: {
148
+ size: 11
149
+ },
150
+ callback: function(value, index) {
151
+ const label = this.getLabelForValue(value)
152
+ return label && label.length > 15 ? label.substr(0, 15) + '...' : (label || '')
153
+ }
154
+ },
155
+ border: {
156
+ display: false
157
+ }
158
+ },
159
+ y: {
160
+ stacked: true,
161
+ beginAtZero: true,
162
+ grid: {
163
+ color: 'rgba(255, 255, 255, 0.03)',
164
+ drawBorder: false
165
+ },
166
+ ticks: {
167
+ color: '#94a3b8',
168
+ callback: (value) => {
169
+ if (value >= 1000000) {
170
+ return (value / 1000000).toFixed(1) + 'M'
171
+ } else if (value >= 1000) {
172
+ return (value / 1000).toFixed(1) + 'K'
173
+ }
174
+ return value
175
+ },
176
+ font: {
177
+ size: 11
178
+ }
179
+ },
180
+ border: {
181
+ display: false
182
+ }
183
+ }
184
+ },
185
+ animation: {
186
+ duration: 750,
187
+ easing: 'easeInOutQuart'
188
+ }
189
+ }))
190
+ </script>
191
+
192
+ <style scoped>
193
+ .chart-container {
194
+ background: transparent;
195
+ border: 1px solid rgba(255, 255, 255, 0.1);
196
+ border-radius: 16px;
197
+ padding: 1.5rem;
198
+ height: 100%;
199
+ display: flex;
200
+ flex-direction: column;
201
+ }
202
+
203
+ .chart-header {
204
+ display: flex;
205
+ justify-content: space-between;
206
+ align-items: center;
207
+ margin-bottom: 1.5rem;
208
+ }
209
+
210
+ .chart-title {
211
+ font-size: 1.125rem;
212
+ font-weight: 600;
213
+ color: var(--surface-700);
214
+ margin: 0;
215
+ }
216
+
217
+ .chart-actions {
218
+ display: flex;
219
+ gap: 0.5rem;
220
+ }
221
+
222
+ .queue-depth-chart {
223
+ flex: 1;
224
+ min-height: 300px;
225
+ position: relative;
226
+ }
227
+
228
+ .chart {
229
+ height: 100%;
230
+ }
231
+
232
+ .chart-loading,
233
+ .chart-empty {
234
+ height: 100%;
235
+ display: flex;
236
+ flex-direction: column;
237
+ align-items: center;
238
+ justify-content: center;
239
+ gap: 1rem;
240
+ color: var(--surface-400);
241
+ }
242
+
243
+ .empty-icon {
244
+ font-size: 3rem;
245
+ color: var(--surface-300);
246
+ }
247
+
248
+ :deep(.p-button-sm) {
249
+ padding: 0.5rem;
250
+ font-size: 0.875rem;
251
+ }
252
+
253
+ :deep(.p-button-text) {
254
+ color: var(--surface-500);
255
+ }
256
+
257
+ :deep(.p-button-text:hover) {
258
+ background: rgba(236, 72, 153, 0.1);
259
+ color: var(--primary-500);
260
+ }
261
+
262
+ /* Responsive */
263
+ @media (max-width: 768px) {
264
+ .chart-container {
265
+ padding: 1rem;
266
+ }
267
+
268
+ .chart-title {
269
+ font-size: 1rem;
270
+ }
271
+
272
+ .queue-depth-chart {
273
+ min-height: 250px;
274
+ }
275
+ }
276
+ </style>