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,187 @@
1
+ import { API_BASE_URL, API_VERSION } from '../utils/constants.js'
2
+
3
+ class ApiService {
4
+ constructor() {
5
+ this.baseUrl = `${API_BASE_URL}${API_VERSION}`
6
+ this.headers = {
7
+ 'Content-Type': 'application/json'
8
+ }
9
+ }
10
+
11
+ async request(endpoint, options = {}) {
12
+ const url = endpoint.startsWith('http') ? endpoint : `${this.baseUrl}${endpoint}`
13
+
14
+ try {
15
+ const response = await fetch(url, {
16
+ ...options,
17
+ headers: {
18
+ ...this.headers,
19
+ ...options.headers
20
+ }
21
+ })
22
+
23
+ if (response.status === 204) {
24
+ return null // No content
25
+ }
26
+
27
+ const data = await response.json()
28
+
29
+ if (!response.ok) {
30
+ throw new Error(data.error || `HTTP error! status: ${response.status}`)
31
+ }
32
+
33
+ return data
34
+ } catch (error) {
35
+ console.error('API request failed:', error)
36
+ throw error
37
+ }
38
+ }
39
+
40
+ // GET request
41
+ async get(endpoint, params = {}) {
42
+ const queryString = new URLSearchParams(params).toString()
43
+ const url = queryString ? `${endpoint}?${queryString}` : endpoint
44
+ return this.request(url, { method: 'GET' })
45
+ }
46
+
47
+ // POST request
48
+ async post(endpoint, data = {}) {
49
+ return this.request(endpoint, {
50
+ method: 'POST',
51
+ body: JSON.stringify(data)
52
+ })
53
+ }
54
+
55
+ // PUT request
56
+ async put(endpoint, data = {}) {
57
+ return this.request(endpoint, {
58
+ method: 'PUT',
59
+ body: JSON.stringify(data)
60
+ })
61
+ }
62
+
63
+ // DELETE request
64
+ async delete(endpoint) {
65
+ return this.request(endpoint, { method: 'DELETE' })
66
+ }
67
+
68
+ // Health & Metrics
69
+ async getHealth() {
70
+ return this.request(`${API_BASE_URL}/health`)
71
+ }
72
+
73
+ async getMetrics() {
74
+ return this.request(`${API_BASE_URL}/metrics`)
75
+ }
76
+
77
+ // Resources
78
+ async getSystemOverview() {
79
+ return this.get('/resources/overview')
80
+ }
81
+
82
+ async getQueues(params = {}) {
83
+ return this.get('/resources/queues', params)
84
+ }
85
+
86
+ async getQueueDetail(queueName) {
87
+ return this.get(`/resources/queues/${encodeURIComponent(queueName)}`)
88
+ }
89
+
90
+ async getPartitions(params = {}) {
91
+ return this.get('/resources/partitions', params)
92
+ }
93
+
94
+ async getNamespaces() {
95
+ return this.get('/resources/namespaces')
96
+ }
97
+
98
+ async getTasks() {
99
+ return this.get('/resources/tasks')
100
+ }
101
+
102
+ // Analytics
103
+ async getQueueAnalytics(queueName, params = {}) {
104
+ return this.get(`/analytics/queue/${encodeURIComponent(queueName)}`, params)
105
+ }
106
+
107
+ async getAllQueuesAnalytics(params = {}) {
108
+ return this.get('/analytics/queues', params)
109
+ }
110
+
111
+ async getQueueDepths() {
112
+ return this.get('/analytics/queue-depths')
113
+ }
114
+
115
+ async getThroughput() {
116
+ return this.get('/analytics/throughput')
117
+ }
118
+
119
+ async getQueueStats(params = {}) {
120
+ return this.get('/analytics/queue-stats', params)
121
+ }
122
+
123
+ async getQueueLag(params = {}) {
124
+ return this.get('/analytics/queue-lag', params)
125
+ }
126
+
127
+ // Messages
128
+ async getMessages(params = {}) {
129
+ return this.get('/messages', params)
130
+ }
131
+
132
+ async getMessage(transactionId) {
133
+ return this.get(`/messages/${transactionId}`)
134
+ }
135
+
136
+ async getRelatedMessages(transactionId) {
137
+ return this.get(`/messages/${transactionId}/related`)
138
+ }
139
+
140
+ async deleteMessage(transactionId) {
141
+ return this.delete(`/messages/${transactionId}`)
142
+ }
143
+
144
+ async retryMessage(transactionId) {
145
+ return this.post(`/messages/${transactionId}/retry`)
146
+ }
147
+
148
+ async moveMessageToDLQ(transactionId) {
149
+ return this.post(`/messages/${transactionId}/dlq`)
150
+ }
151
+
152
+ async clearQueue(queueName, partition = null) {
153
+ const params = partition ? { partition } : {}
154
+ return this.delete(`/queues/${encodeURIComponent(queueName)}/clear`, params)
155
+ }
156
+
157
+ // Push & Pop
158
+ async pushMessages(items) {
159
+ return this.post('/push', { items })
160
+ }
161
+
162
+ async popMessages(queue, partition = null, options = {}) {
163
+ const endpoint = partition
164
+ ? `/pop/queue/${encodeURIComponent(queue)}/partition/${encodeURIComponent(partition)}`
165
+ : `/pop/queue/${encodeURIComponent(queue)}`
166
+ return this.get(endpoint, options)
167
+ }
168
+
169
+ // Acknowledge
170
+ async acknowledgeMessage(transactionId, status, error = null) {
171
+ const data = { transactionId, status }
172
+ if (error) data.error = error
173
+ return this.post('/ack', data)
174
+ }
175
+
176
+ async acknowledgeBatch(acknowledgments) {
177
+ return this.post('/ack/batch', { acknowledgments })
178
+ }
179
+
180
+ // Configuration
181
+ async configurePartition(queue, partition = 'Default', options = {}) {
182
+ return this.post('/configure', { queue, partition, options })
183
+ }
184
+ }
185
+
186
+ // Export singleton instance
187
+ export default new ApiService()
@@ -0,0 +1,167 @@
1
+ import { WS_URL } from '../utils/constants.js'
2
+
3
+ class WebSocketService {
4
+ constructor() {
5
+ this.ws = null
6
+ this.reconnectAttempts = 0
7
+ this.maxReconnectAttempts = 5
8
+ this.reconnectDelay = 1000
9
+ this.listeners = new Map()
10
+ this.isConnected = false
11
+ this.pingInterval = null
12
+ }
13
+
14
+ connect() {
15
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
16
+ return Promise.resolve()
17
+ }
18
+
19
+ return new Promise((resolve, reject) => {
20
+ try {
21
+ this.ws = new WebSocket(WS_URL)
22
+
23
+ this.ws.onopen = () => {
24
+ console.log('WebSocket connected')
25
+ this.isConnected = true
26
+ this.reconnectAttempts = 0
27
+ this.startPing()
28
+ this.emit('connected')
29
+ resolve()
30
+ }
31
+
32
+ this.ws.onmessage = (event) => {
33
+ if (event.data === 'pong') {
34
+ return // Ignore pong messages
35
+ }
36
+
37
+ try {
38
+ const data = JSON.parse(event.data)
39
+ this.handleMessage(data)
40
+ } catch (error) {
41
+ console.error('Failed to parse WebSocket message:', error)
42
+ }
43
+ }
44
+
45
+ this.ws.onerror = (error) => {
46
+ console.error('WebSocket error:', error)
47
+ this.emit('error', error)
48
+ }
49
+
50
+ this.ws.onclose = () => {
51
+ console.log('WebSocket disconnected')
52
+ this.isConnected = false
53
+ this.stopPing()
54
+ this.emit('disconnected')
55
+ this.reconnect()
56
+ }
57
+ } catch (error) {
58
+ reject(error)
59
+ }
60
+ })
61
+ }
62
+
63
+ disconnect() {
64
+ this.stopPing()
65
+ if (this.ws) {
66
+ this.ws.close()
67
+ this.ws = null
68
+ }
69
+ this.isConnected = false
70
+ }
71
+
72
+ reconnect() {
73
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
74
+ console.error('Max reconnection attempts reached')
75
+ this.emit('reconnect_failed')
76
+ return
77
+ }
78
+
79
+ this.reconnectAttempts++
80
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
81
+
82
+ console.log(`Reconnecting in ${delay}ms... (attempt ${this.reconnectAttempts})`)
83
+
84
+ setTimeout(() => {
85
+ this.connect()
86
+ }, delay)
87
+ }
88
+
89
+ startPing() {
90
+ this.pingInterval = setInterval(() => {
91
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
92
+ this.ws.send('ping')
93
+ }
94
+ }, 30000) // Ping every 30 seconds
95
+ }
96
+
97
+ stopPing() {
98
+ if (this.pingInterval) {
99
+ clearInterval(this.pingInterval)
100
+ this.pingInterval = null
101
+ }
102
+ }
103
+
104
+ handleMessage(data) {
105
+ const { event, data: eventData, timestamp } = data
106
+
107
+ // Emit event to all listeners
108
+ this.emit(event, { ...eventData, timestamp })
109
+
110
+ // Also emit a general 'message' event
111
+ this.emit('message', data)
112
+ }
113
+
114
+ subscribe(queues = []) {
115
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
116
+ this.ws.send(JSON.stringify({
117
+ type: 'subscribe',
118
+ queues
119
+ }))
120
+ }
121
+ }
122
+
123
+ on(event, callback) {
124
+ if (!this.listeners.has(event)) {
125
+ this.listeners.set(event, [])
126
+ }
127
+ this.listeners.get(event).push(callback)
128
+
129
+ // Return unsubscribe function
130
+ return () => this.off(event, callback)
131
+ }
132
+
133
+ off(event, callback) {
134
+ if (!this.listeners.has(event)) return
135
+
136
+ const callbacks = this.listeners.get(event)
137
+ const index = callbacks.indexOf(callback)
138
+
139
+ if (index > -1) {
140
+ callbacks.splice(index, 1)
141
+ }
142
+
143
+ if (callbacks.length === 0) {
144
+ this.listeners.delete(event)
145
+ }
146
+ }
147
+
148
+ emit(event, data = {}) {
149
+ if (!this.listeners.has(event)) return
150
+
151
+ const callbacks = this.listeners.get(event)
152
+ callbacks.forEach(callback => {
153
+ try {
154
+ callback(data)
155
+ } catch (error) {
156
+ console.error(`Error in WebSocket listener for event ${event}:`, error)
157
+ }
158
+ })
159
+ }
160
+
161
+ getConnectionStatus() {
162
+ return this.isConnected
163
+ }
164
+ }
165
+
166
+ // Export singleton instance
167
+ export default new WebSocketService()
@@ -0,0 +1,56 @@
1
+ // API Configuration
2
+ export const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:6632'
3
+ export const API_VERSION = '/api/v1'
4
+ export const WS_URL = import.meta.env.VITE_WS_URL || 'ws://localhost:6632/ws/dashboard'
5
+
6
+ // Refresh intervals (in milliseconds)
7
+ export const REFRESH_INTERVALS = {
8
+ DASHBOARD: 5000, // 5 seconds
9
+ ANALYTICS: 30000, // 30 seconds
10
+ QUEUE_DEPTH: 5000, // 5 seconds
11
+ SYSTEM_STATS: 10000 // 10 seconds
12
+ }
13
+
14
+ // Message statuses
15
+ export const MESSAGE_STATUS = {
16
+ PENDING: 'pending',
17
+ PROCESSING: 'processing',
18
+ COMPLETED: 'completed',
19
+ FAILED: 'failed',
20
+ DEAD_LETTER: 'dead_letter'
21
+ }
22
+
23
+ // Status colors for UI
24
+ export const STATUS_COLORS = {
25
+ [MESSAGE_STATUS.PENDING]: '#f59e0b', // amber
26
+ [MESSAGE_STATUS.PROCESSING]: '#3b82f6', // blue
27
+ [MESSAGE_STATUS.COMPLETED]: '#10b981', // green
28
+ [MESSAGE_STATUS.FAILED]: '#ef4444', // red
29
+ [MESSAGE_STATUS.DEAD_LETTER]: '#6b7280' // gray
30
+ }
31
+
32
+ // Chart configuration
33
+ export const CHART_COLORS = [
34
+ '#3b82f6', // blue
35
+ '#10b981', // green
36
+ '#f59e0b', // amber
37
+ '#ef4444', // red
38
+ '#8b5cf6', // purple
39
+ '#ec4899', // pink
40
+ '#14b8a6', // teal
41
+ '#f97316', // orange
42
+ '#06b6d4', // cyan
43
+ '#84cc16' // lime
44
+ ]
45
+
46
+ // Pagination
47
+ export const DEFAULT_PAGE_SIZE = 20
48
+ export const PAGE_SIZE_OPTIONS = [10, 20, 50, 100]
49
+
50
+ // Date formats
51
+ export const DATE_FORMAT = {
52
+ SHORT: 'MMM DD, HH:mm',
53
+ LONG: 'MMM DD, YYYY HH:mm:ss',
54
+ TIME_ONLY: 'HH:mm:ss',
55
+ DATE_ONLY: 'MMM DD, YYYY'
56
+ }
@@ -0,0 +1,118 @@
1
+ // Format numbers with abbreviations (1.2K, 3.4M, etc.)
2
+ export function formatNumber(num) {
3
+ if (num === null || num === undefined) return '0'
4
+
5
+ const absNum = Math.abs(num)
6
+ if (absNum >= 1e9) return (num / 1e9).toFixed(1) + 'B'
7
+ if (absNum >= 1e6) return (num / 1e6).toFixed(1) + 'M'
8
+ if (absNum >= 1e3) return (num / 1e3).toFixed(1) + 'K'
9
+ return num.toString()
10
+ }
11
+
12
+ // Format bytes to human readable
13
+ export function formatBytes(bytes, decimals = 2) {
14
+ if (!bytes) return '0 Bytes'
15
+
16
+ const k = 1024
17
+ const dm = decimals < 0 ? 0 : decimals
18
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
19
+
20
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
21
+
22
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
23
+ }
24
+
25
+ // Format date to relative time (e.g., "2 hours ago")
26
+ export function formatRelativeTime(date) {
27
+ if (!date) return ''
28
+
29
+ const now = new Date()
30
+ const then = new Date(date)
31
+ const seconds = Math.floor((now - then) / 1000)
32
+
33
+ if (seconds < 60) return 'just now'
34
+ if (seconds < 3600) return `${Math.floor(seconds / 60)} min ago`
35
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`
36
+ if (seconds < 604800) return `${Math.floor(seconds / 86400)} days ago`
37
+
38
+ return then.toLocaleDateString()
39
+ }
40
+
41
+ // Format date to local string
42
+ export function formatDate(date, format = 'long') {
43
+ if (!date) return ''
44
+
45
+ const d = new Date(date)
46
+
47
+ switch (format) {
48
+ case 'short':
49
+ return d.toLocaleString('en-US', {
50
+ month: 'short',
51
+ day: 'numeric',
52
+ hour: '2-digit',
53
+ minute: '2-digit'
54
+ })
55
+ case 'time':
56
+ return d.toLocaleTimeString('en-US', {
57
+ hour: '2-digit',
58
+ minute: '2-digit',
59
+ second: '2-digit'
60
+ })
61
+ case 'date':
62
+ return d.toLocaleDateString('en-US', {
63
+ year: 'numeric',
64
+ month: 'short',
65
+ day: 'numeric'
66
+ })
67
+ default:
68
+ return d.toLocaleString('en-US', {
69
+ year: 'numeric',
70
+ month: 'short',
71
+ day: 'numeric',
72
+ hour: '2-digit',
73
+ minute: '2-digit',
74
+ second: '2-digit'
75
+ })
76
+ }
77
+ }
78
+
79
+ // Calculate percentage
80
+ export function calculatePercentage(value, total) {
81
+ if (!total || total === 0) return 0
82
+ return Math.round((value / total) * 100)
83
+ }
84
+
85
+ // Get status severity for PrimeVue components
86
+ export function getStatusSeverity(status) {
87
+ const severityMap = {
88
+ 'pending': 'warn',
89
+ 'processing': 'info',
90
+ 'completed': 'success',
91
+ 'failed': 'danger',
92
+ 'dead_letter': 'secondary'
93
+ }
94
+ return severityMap[status] || 'secondary'
95
+ }
96
+
97
+ // Debounce function for search inputs
98
+ export function debounce(func, wait) {
99
+ let timeout
100
+ return function executedFunction(...args) {
101
+ const later = () => {
102
+ clearTimeout(timeout)
103
+ func(...args)
104
+ }
105
+ clearTimeout(timeout)
106
+ timeout = setTimeout(later, wait)
107
+ }
108
+ }
109
+
110
+ // Deep clone object
111
+ export function deepClone(obj) {
112
+ return JSON.parse(JSON.stringify(obj))
113
+ }
114
+
115
+ // Generate unique ID
116
+ export function generateId() {
117
+ return Date.now().toString(36) + Math.random().toString(36).substr(2)
118
+ }