queen-mq 0.2.0 → 0.2.11

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 (84) hide show
  1. package/README.md +165 -769
  2. package/{src → client-js}/benchmark/consumer.js +5 -3
  3. package/{src → client-js}/benchmark/producer.js +9 -4
  4. package/{src → client-js}/client/client.js +272 -6
  5. package/{src → client-js}/client/index.js +1 -4
  6. package/{src → client-js}/client/utils/http.js +3 -2
  7. package/{src → client-js}/client/utils/retry.js +7 -1
  8. package/{src → client-js}/test/core-tests.js +7 -2
  9. package/{src → client-js}/test/edge-case-tests.js +2 -1
  10. package/client-js/test/human.js +162 -0
  11. package/client-js/test/qos0-tests.js +334 -0
  12. package/{src → client-js}/test/test-new.js +42 -0
  13. package/package.json +8 -17
  14. package/init-db.js +0 -20
  15. package/src/cluster-server.js +0 -242
  16. package/src/config.js +0 -229
  17. package/src/database/connection.js +0 -129
  18. package/src/database/poolManager.js +0 -199
  19. package/src/database/schema-v2.sql +0 -278
  20. package/src/managers/eventManager.js +0 -59
  21. package/src/managers/queueManagerOptimized.js +0 -1634
  22. package/src/managers/resourceCache.js +0 -96
  23. package/src/managers/systemEventManager.js +0 -132
  24. package/src/routes/ack.js +0 -26
  25. package/src/routes/configure.js +0 -46
  26. package/src/routes/messages.js +0 -368
  27. package/src/routes/pop.js +0 -69
  28. package/src/routes/push.js +0 -28
  29. package/src/routes/resources.js +0 -330
  30. package/src/routes/status.js +0 -1037
  31. package/src/server.js +0 -1633
  32. package/src/test/MIGRATION_ISSUES.md +0 -174
  33. package/src/test/test.js +0 -4524
  34. package/src/utils/streaming.js +0 -231
  35. package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
  36. package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
  37. package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
  38. package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
  39. package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
  40. package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
  41. package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
  42. package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
  43. package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
  44. package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
  45. package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
  46. package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
  47. package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
  48. package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
  49. package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
  50. package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
  51. package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
  52. package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
  53. package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
  54. package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
  55. package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
  56. package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
  57. package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
  58. package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
  59. package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
  60. package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
  61. package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
  62. package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
  63. package/src/webapp-dist/assets/queen-logo.svg +0 -263
  64. package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
  65. package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
  66. package/src/webapp-dist/index.html +0 -15
  67. package/src/websocket/wsServer.js +0 -228
  68. /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
  69. /package/{src → client-js}/benchmark/producer_multi.js +0 -0
  70. /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
  71. /package/{src → client-js}/services/encryptionService.js +0 -0
  72. /package/{src → client-js}/services/evictionService.js +0 -0
  73. /package/{src → client-js}/services/retentionService.js +0 -0
  74. /package/{src → client-js}/services/startupSync.js +0 -0
  75. /package/{src → client-js}/test/README.md +0 -0
  76. /package/{src → client-js}/test/advanced-client-tests.js +0 -0
  77. /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
  78. /package/{src → client-js}/test/bus-mode-tests.js +0 -0
  79. /package/{src → client-js}/test/enterprise-tests.js +0 -0
  80. /package/{src → client-js}/test/partition-locking-tests.js +0 -0
  81. /package/{src → client-js}/test/utils.js +0 -0
  82. /package/{src → client-js}/test/window-buffer-test.js +0 -0
  83. /package/{src → client-js}/utils/logger.js +0 -0
  84. /package/{src → client-js}/utils/uuid.js +0 -0
@@ -1,242 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Queen Message Queue - Clustered Server
5
- *
6
- * This module enables multi-core utilization using Node.js cluster module.
7
- * Each worker runs an independent instance of the Queen server, sharing the same port.
8
- *
9
- * Benefits:
10
- * - 10x throughput improvement (utilizes all CPU cores)
11
- * - Automatic load balancing across workers
12
- * - Fault tolerance (automatic worker restart on crash)
13
- * - Zero-downtime deploys (rolling worker restarts)
14
- *
15
- * Usage:
16
- * node src/cluster-server.js
17
- *
18
- * Environment Variables:
19
- * QUEEN_WORKERS=N - Number of workers (default: CPU count)
20
- * WORKER_ID=name - Base worker ID prefix (default: queen-worker)
21
- */
22
-
23
- import cluster from 'cluster';
24
- import os from 'os';
25
- import { log } from './utils/logger.js';
26
-
27
- // Configuration
28
- const numWorkers = parseInt(process.env.QUEEN_WORKERS) || os.cpus().length;
29
- const workerIdPrefix = process.env.WORKER_ID || 'queen-worker';
30
-
31
- if (cluster.isPrimary) {
32
- console.log('');
33
- console.log('═══════════════════════════════════════════════════════════');
34
- console.log('🚀 Queen Message Queue - Clustered Mode');
35
- console.log('═══════════════════════════════════════════════════════════');
36
- console.log(`📊 System: ${os.cpus()[0].model}`);
37
- console.log(`🔢 CPU Cores: ${os.cpus().length}`);
38
- console.log(`👷 Starting Workers: ${numWorkers}`);
39
- console.log(`🆔 Worker ID Prefix: ${workerIdPrefix}`);
40
- console.log('═══════════════════════════════════════════════════════════');
41
- console.log('');
42
-
43
- // Track worker statistics
44
- const workerStats = {
45
- startTime: Date.now(),
46
- totalRestarts: 0,
47
- workers: new Map()
48
- };
49
-
50
- // Graceful shutdown state (declared early for use in event handlers)
51
- let isShuttingDown = false;
52
- let forceExitTimeout = null;
53
- let statusInterval = null;
54
-
55
- // Fork workers
56
- for (let i = 0; i < numWorkers; i++) {
57
- forkWorker(i, workerStats);
58
- }
59
-
60
- // Handle worker exit
61
- cluster.on('exit', (worker, code, signal) => {
62
- const workerInfo = workerStats.workers.get(worker.id);
63
- const workerId = workerInfo ? workerInfo.workerId : `worker-${worker.id}`;
64
- const workerIndex = workerInfo ? workerInfo.index : 0;
65
-
66
- if (code !== 0 && !worker.exitedAfterDisconnect) {
67
- // Unexpected crash
68
- console.log('');
69
- console.log(`⚠️ Worker ${workerId} (PID: ${worker.process.pid}) crashed!`);
70
- console.log(` Exit code: ${code}, Signal: ${signal}`);
71
- console.log(` Restarting worker...`);
72
-
73
- workerStats.totalRestarts++;
74
-
75
- // Remove from stats
76
- workerStats.workers.delete(worker.id);
77
-
78
- // Restart with same index
79
- setTimeout(() => {
80
- forkWorker(workerIndex, workerStats);
81
- }, 1000); // Wait 1 second before restart
82
- } else {
83
- // Graceful shutdown
84
- console.log(`✅ Worker ${workerId} (PID: ${worker.process.pid}) exited gracefully`);
85
- workerStats.workers.delete(worker.id);
86
-
87
- // Check if all workers have exited during shutdown
88
- if (isShuttingDown && Object.keys(cluster.workers).length === 0) {
89
- console.log('✅ All workers exited, shutting down master process');
90
- clearInterval(statusInterval);
91
- clearTimeout(forceExitTimeout);
92
- process.exit(0);
93
- }
94
- }
95
- });
96
-
97
- // Handle worker online
98
- cluster.on('online', (worker) => {
99
- const workerInfo = workerStats.workers.get(worker.id);
100
- if (workerInfo) {
101
- // Update PID now that it's available
102
- workerInfo.pid = worker.process.pid;
103
- console.log(`✅ Worker ${workerInfo.workerId} (PID: ${worker.process.pid}) online and ready`);
104
- } else {
105
- console.log(`✅ Worker ${worker.id} (PID: ${worker.process.pid}) online`);
106
- }
107
- });
108
-
109
- // Handle worker listening
110
- cluster.on('listening', (worker, address) => {
111
- const workerInfo = workerStats.workers.get(worker.id);
112
- const workerId = workerInfo ? workerInfo.workerId : `worker-${worker.id}`;
113
- console.log(`🎧 Worker ${workerId} listening on ${address.address}:${address.port}`);
114
- });
115
-
116
- // Handle cluster errors
117
- cluster.on('error', (error) => {
118
- console.error('⚠️ Cluster error:', error.message);
119
- });
120
-
121
- // Handle uncaught exceptions in master
122
- process.on('uncaughtException', (error) => {
123
- console.error('❌ Uncaught exception in master process:', error);
124
- console.error(error.stack);
125
- // Don't exit - try to keep the cluster running
126
- });
127
-
128
- process.on('unhandledRejection', (reason, promise) => {
129
- console.error('❌ Unhandled rejection in master process:', reason);
130
- // Don't exit - try to keep the cluster running
131
- });
132
-
133
- // Graceful shutdown handling
134
- const gracefulShutdown = (signal) => {
135
- if (isShuttingDown) return;
136
- isShuttingDown = true;
137
-
138
- console.log('');
139
- console.log(`🛑 Received ${signal}, shutting down gracefully...`);
140
- console.log(`📊 Total worker restarts during uptime: ${workerStats.totalRestarts}`);
141
- console.log(`⏱️ Uptime: ${((Date.now() - workerStats.startTime) / 1000 / 60).toFixed(2)} minutes`);
142
-
143
- // Stop status reporting during shutdown
144
- clearInterval(statusInterval);
145
-
146
- // Check if we already have no workers
147
- if (Object.keys(cluster.workers).length === 0) {
148
- console.log('✅ No active workers, exiting immediately');
149
- process.exit(0);
150
- }
151
-
152
- // Disconnect all workers
153
- for (const id in cluster.workers) {
154
- cluster.workers[id].disconnect();
155
- }
156
-
157
- // Force exit if workers don't shut down in 10 seconds (reduced from 30)
158
- forceExitTimeout = setTimeout(() => {
159
- console.log('⚠️ Forcing shutdown after 10 second timeout');
160
- console.log(` Remaining workers: ${Object.keys(cluster.workers).length}`);
161
- process.exit(0);
162
- }, 10000);
163
- };
164
-
165
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
166
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
167
-
168
- // Status reporting every 60 seconds
169
- statusInterval = setInterval(() => {
170
- // Don't show status during shutdown
171
- if (isShuttingDown) return;
172
-
173
- const aliveWorkers = Object.keys(cluster.workers).length;
174
- const uptime = ((Date.now() - workerStats.startTime) / 1000 / 60).toFixed(2);
175
-
176
- console.log('');
177
- console.log(`📊 Cluster Status (${new Date().toISOString()})`);
178
- console.log(` Active Workers: ${aliveWorkers}/${numWorkers}`);
179
- console.log(` Total Restarts: ${workerStats.totalRestarts}`);
180
- console.log(` Uptime: ${uptime} minutes`);
181
- }, 60000);
182
-
183
- } else {
184
- // Worker process - run the normal server
185
- const workerId = process.env.WORKER_ID || `worker-${process.pid}`;
186
- const workerIndex = process.env.WORKER_INDEX || '0';
187
-
188
- console.log(`👷 Worker ${workerId} (index: ${workerIndex}, PID: ${process.pid}) starting...`);
189
-
190
- try {
191
- // Import and run the main server
192
- await import('./server.js');
193
-
194
- // Worker-specific metrics could be added here
195
- // e.g., track messages processed, errors, etc.
196
- } catch (error) {
197
- console.error(`❌ Worker ${workerId} failed to start:`, error.message);
198
- console.error(error.stack);
199
- process.exit(1);
200
- }
201
-
202
- // Handle uncaught exceptions in worker
203
- process.on('uncaughtException', (error) => {
204
- console.error(`❌ Uncaught exception in worker ${workerId}:`, error);
205
- process.exit(1);
206
- });
207
-
208
- process.on('unhandledRejection', (reason, promise) => {
209
- console.error(`❌ Unhandled rejection in worker ${workerId}:`, reason);
210
- process.exit(1);
211
- });
212
- }
213
-
214
- /**
215
- * Fork a new worker with proper environment setup
216
- */
217
- function forkWorker(index, stats) {
218
- const workerId = `${workerIdPrefix}-${index}`;
219
-
220
- try {
221
- const worker = cluster.fork({
222
- WORKER_ID: workerId,
223
- WORKER_INDEX: index.toString(),
224
- // Each worker gets a unique ID for system events
225
- SYSTEM_WORKER_ID: `${workerIdPrefix}-${index}-${Date.now()}`
226
- });
227
-
228
- // Store worker info immediately (PID will be updated in 'online' event)
229
- stats.workers.set(worker.id, {
230
- workerId,
231
- index,
232
- startTime: Date.now(),
233
- pid: null // Will be set when worker comes online
234
- });
235
-
236
- return worker;
237
- } catch (error) {
238
- console.error(`❌ Failed to fork worker ${workerId}:`, error.message);
239
- return null;
240
- }
241
- }
242
-
package/src/config.js DELETED
@@ -1,229 +0,0 @@
1
- /**
2
- * Centralized Configuration for Queen Message Queue System
3
- *
4
- * All configuration values can be overridden using environment variables.
5
- * This file provides default values and documentation for all settings.
6
- */
7
- import os from 'os';
8
-
9
- // Server Configuration
10
- export const SERVER = {
11
- PORT: parseInt(process.env.PORT) || 6632,
12
- HOST: process.env.HOST || '0.0.0.0',
13
- WORKER_ID: process.env.WORKER_ID || `worker-${os.hostname()}-${process.pid}`,
14
- APPLICATION_NAME: process.env.APP_NAME || 'queen-mq'
15
- };
16
-
17
- // Database Configuration
18
- export const DATABASE = {
19
- // Connection settings
20
- USER: process.env.PG_USER || 'postgres',
21
- HOST: process.env.PG_HOST || 'localhost',
22
- DATABASE: process.env.PG_DB || 'postgres',
23
- PASSWORD: process.env.PG_PASSWORD || 'postgres',
24
- PORT: parseInt(process.env.PG_PORT) || 5432,
25
-
26
- // SSL configuration
27
- USE_SSL: process.env.PG_USE_SSL === 'true',
28
- SSL_REJECT_UNAUTHORIZED: process.env.PG_SSL_REJECT_UNAUTHORIZED !== 'false',
29
-
30
- // Pool configuration
31
- POOL_SIZE: parseInt(process.env.DB_POOL_SIZE) || 150,
32
- IDLE_TIMEOUT: parseInt(process.env.DB_IDLE_TIMEOUT) || 30000, // 30 seconds
33
- CONNECTION_TIMEOUT: parseInt(process.env.DB_CONNECTION_TIMEOUT) || 2000, // 2 seconds
34
- STATEMENT_TIMEOUT: parseInt(process.env.DB_STATEMENT_TIMEOUT) || 30000, // 30 seconds
35
- QUERY_TIMEOUT: parseInt(process.env.DB_QUERY_TIMEOUT) || 30000, // 30 seconds
36
- LOCK_TIMEOUT: parseInt(process.env.DB_LOCK_TIMEOUT) || 10000, // 10 seconds
37
-
38
- // Pool manager settings
39
- MAX_RETRIES: parseInt(process.env.DB_MAX_RETRIES) || 3
40
- };
41
-
42
- // Queue Processing Configuration
43
- export const QUEUE = {
44
- // Pop operation defaults
45
- DEFAULT_TIMEOUT: parseInt(process.env.DEFAULT_TIMEOUT) || 30000, // 30 seconds
46
- MAX_TIMEOUT: parseInt(process.env.MAX_TIMEOUT) || 60000, // 60 seconds
47
- DEFAULT_BATCH_SIZE: parseInt(process.env.DEFAULT_BATCH_SIZE) || 1,
48
- BATCH_INSERT_SIZE: parseInt(process.env.BATCH_INSERT_SIZE) || 1000,
49
-
50
- // Long polling
51
- POLL_INTERVAL: parseInt(process.env.QUEUE_POLL_INTERVAL) || 100, // 100ms - initial poll interval
52
- POLL_INTERVAL_FILTERED: parseInt(process.env.QUEUE_POLL_INTERVAL_FILTERED) || 50, // 50ms for better distribution across consumers
53
- MAX_POLL_INTERVAL: parseInt(process.env.QUEUE_MAX_POLL_INTERVAL) || 2000, // 2000ms - max poll interval after backoff
54
- BACKOFF_THRESHOLD: parseInt(process.env.QUEUE_BACKOFF_THRESHOLD) || 5, // Number of empty polls before backoff starts
55
- BACKOFF_MULTIPLIER: parseFloat(process.env.QUEUE_BACKOFF_MULTIPLIER) || 2, // Exponential backoff multiplier
56
-
57
- // Partition selection for filtered pops (namespace/task)
58
- MAX_PARTITION_CANDIDATES: parseInt(process.env.MAX_PARTITION_CANDIDATES) || 100, // Number of candidate partitions to fetch for lease acquisition
59
-
60
- // Queue defaults
61
- DEFAULT_LEASE_TIME: parseInt(process.env.DEFAULT_LEASE_TIME) || 300, // 5 minutes
62
- DEFAULT_RETRY_LIMIT: parseInt(process.env.DEFAULT_RETRY_LIMIT) || 3,
63
- DEFAULT_RETRY_DELAY: parseInt(process.env.DEFAULT_RETRY_DELAY) || 1000, // 1 second
64
- DEFAULT_MAX_SIZE: parseInt(process.env.DEFAULT_MAX_SIZE) || 10000,
65
- DEFAULT_TTL: parseInt(process.env.DEFAULT_TTL) || 3600, // 1 hour
66
- DEFAULT_PRIORITY: parseInt(process.env.DEFAULT_PRIORITY) || 0,
67
- DEFAULT_DELAYED_PROCESSING: parseInt(process.env.DEFAULT_DELAYED_PROCESSING) || 0,
68
- DEFAULT_WINDOW_BUFFER: parseInt(process.env.DEFAULT_WINDOW_BUFFER) || 0,
69
-
70
- // Dead Letter Queue
71
- DEFAULT_DLQ_ENABLED: process.env.DEFAULT_DLQ_ENABLED === 'true' || false,
72
- DEFAULT_DLQ_AFTER_MAX_RETRIES: process.env.DEFAULT_DLQ_AFTER_MAX_RETRIES === 'true' || false,
73
-
74
- // Retention defaults
75
- DEFAULT_RETENTION_SECONDS: parseInt(process.env.DEFAULT_RETENTION_SECONDS) || 0,
76
- DEFAULT_COMPLETED_RETENTION_SECONDS: parseInt(process.env.DEFAULT_COMPLETED_RETENTION_SECONDS) || 0,
77
- DEFAULT_RETENTION_ENABLED: process.env.DEFAULT_RETENTION_ENABLED === 'true' || false,
78
-
79
- // Eviction
80
- DEFAULT_MAX_WAIT_TIME_SECONDS: parseInt(process.env.DEFAULT_MAX_WAIT_TIME_SECONDS) || 0
81
- };
82
-
83
- // System Events Configuration
84
- export const SYSTEM_EVENTS = {
85
- // Enable/disable system event propagation
86
- ENABLED: process.env.QUEEN_SYSTEM_EVENTS_ENABLED === 'true' || false,
87
-
88
- // Batching window for event publishing (milliseconds)
89
- BATCH_MS: parseInt(process.env.QUEEN_SYSTEM_EVENTS_BATCH_MS) || 10,
90
-
91
- // Timeout for startup synchronization (milliseconds)
92
- SYNC_TIMEOUT: parseInt(process.env.QUEEN_SYSTEM_EVENTS_SYNC_TIMEOUT) || 30000
93
- };
94
-
95
- // Background Jobs Configuration
96
- export const JOBS = {
97
- // Lease reclamation
98
- LEASE_RECLAIM_INTERVAL: parseInt(process.env.LEASE_RECLAIM_INTERVAL) || 5000, // 5 seconds
99
-
100
- // Retention service
101
- RETENTION_INTERVAL: parseInt(process.env.RETENTION_INTERVAL) || 300000, // 5 minutes
102
- RETENTION_BATCH_SIZE: parseInt(process.env.RETENTION_BATCH_SIZE) || 1000,
103
- PARTITION_CLEANUP_DAYS: parseInt(process.env.PARTITION_CLEANUP_DAYS) || 7,
104
-
105
- // Metrics retention (messages_consumed table)
106
- METRICS_RETENTION_DAYS: parseInt(process.env.METRICS_RETENTION_DAYS) || 90, // Keep 90 days of metrics by default
107
-
108
- // Eviction service
109
- EVICTION_INTERVAL: parseInt(process.env.EVICTION_INTERVAL) || 60000, // 1 minute
110
- EVICTION_BATCH_SIZE: parseInt(process.env.EVICTION_BATCH_SIZE) || 1000,
111
-
112
- // WebSocket updates
113
- QUEUE_DEPTH_UPDATE_INTERVAL: parseInt(process.env.QUEUE_DEPTH_UPDATE_INTERVAL) || 5000, // 5 seconds
114
- SYSTEM_STATS_UPDATE_INTERVAL: parseInt(process.env.SYSTEM_STATS_UPDATE_INTERVAL) || 10000 // 10 seconds
115
- };
116
-
117
- // WebSocket Configuration
118
- export const WEBSOCKET = {
119
- COMPRESSION: parseInt(process.env.WS_COMPRESSION) || 0,
120
- MAX_PAYLOAD_LENGTH: parseInt(process.env.WS_MAX_PAYLOAD_LENGTH) || 16384, // 16KB
121
- IDLE_TIMEOUT: parseInt(process.env.WS_IDLE_TIMEOUT) || 60, // 60 seconds
122
- MAX_CONNECTIONS: parseInt(process.env.WS_MAX_CONNECTIONS) || 1000,
123
- HEARTBEAT_INTERVAL: parseInt(process.env.WS_HEARTBEAT_INTERVAL) || 30000 // 30 seconds
124
- };
125
-
126
- // Encryption Configuration
127
- export const ENCRYPTION = {
128
- KEY_ENV_VAR: 'QUEEN_ENCRYPTION_KEY',
129
- ALGORITHM: 'aes-256-gcm',
130
- KEY_LENGTH: 32, // bytes
131
- IV_LENGTH: 16 // bytes
132
- };
133
-
134
- // Client SDK Configuration
135
- export const CLIENT = {
136
- DEFAULT_BASE_URL: process.env.QUEEN_BASE_URL || 'http://localhost:6632',
137
- DEFAULT_RETRY_ATTEMPTS: parseInt(process.env.CLIENT_RETRY_ATTEMPTS) || 3,
138
- DEFAULT_RETRY_DELAY: parseInt(process.env.CLIENT_RETRY_DELAY) || 1000, // 1 second
139
- DEFAULT_RETRY_BACKOFF: parseFloat(process.env.CLIENT_RETRY_BACKOFF) || 2,
140
- CONNECTION_POOL_SIZE: parseInt(process.env.CLIENT_POOL_SIZE) || 10,
141
- REQUEST_TIMEOUT: parseInt(process.env.CLIENT_REQUEST_TIMEOUT) || 30000 // 30 seconds
142
- };
143
-
144
- // API Configuration
145
- export const API = {
146
- MAX_BODY_SIZE: parseInt(process.env.MAX_BODY_SIZE) || 100 * 1024 * 1024, // 100MB max body size
147
- // Pagination
148
- DEFAULT_LIMIT: parseInt(process.env.API_DEFAULT_LIMIT) || 100,
149
- MAX_LIMIT: parseInt(process.env.API_MAX_LIMIT) || 1000,
150
- DEFAULT_OFFSET: parseInt(process.env.API_DEFAULT_OFFSET) || 0,
151
-
152
- // CORS
153
- CORS_MAX_AGE: parseInt(process.env.CORS_MAX_AGE) || 86400, // 24 hours
154
- CORS_ALLOWED_ORIGINS: process.env.CORS_ALLOWED_ORIGINS || '*',
155
- CORS_ALLOWED_METHODS: process.env.CORS_ALLOWED_METHODS || 'GET, POST, PUT, DELETE, OPTIONS',
156
- CORS_ALLOWED_HEADERS: process.env.CORS_ALLOWED_HEADERS || 'Content-Type, Authorization'
157
- };
158
-
159
- // Analytics Configuration
160
- export const ANALYTICS = {
161
- // Processing time calculations
162
- RECENT_COMPLETION_HOURS: parseInt(process.env.ANALYTICS_RECENT_HOURS) || 24,
163
- MIN_COMPLETED_FOR_STATS: parseInt(process.env.ANALYTICS_MIN_COMPLETED) || 5,
164
-
165
- // Time windows
166
- RECENT_MESSAGE_WINDOW: parseInt(process.env.RECENT_MESSAGE_WINDOW) || 60, // 1 minute in seconds
167
- RELATED_MESSAGE_WINDOW: parseInt(process.env.RELATED_MESSAGE_WINDOW) || 3600, // 1 hour in seconds
168
- MAX_RELATED_MESSAGES: parseInt(process.env.MAX_RELATED_MESSAGES) || 10
169
- };
170
-
171
- // Performance Monitoring
172
- export const MONITORING = {
173
- ENABLE_REQUEST_COUNTING: process.env.ENABLE_REQUEST_COUNTING !== 'false',
174
- ENABLE_MESSAGE_COUNTING: process.env.ENABLE_MESSAGE_COUNTING !== 'false',
175
- METRICS_ENDPOINT_ENABLED: process.env.METRICS_ENDPOINT_ENABLED !== 'false',
176
- HEALTH_CHECK_ENABLED: process.env.HEALTH_CHECK_ENABLED !== 'false'
177
- };
178
-
179
- // Logging Configuration
180
- export const LOGGING = {
181
- ENABLE_LOGGING: process.env.ENABLE_LOGGING !== 'false',
182
- LOG_LEVEL: process.env.LOG_LEVEL || 'info',
183
- LOG_FORMAT: process.env.LOG_FORMAT || 'json',
184
- LOG_TIMESTAMP: process.env.LOG_TIMESTAMP !== 'false'
185
- };
186
-
187
- // HTTP Response Status Codes
188
- export const HTTP_STATUS = {
189
- OK: 200,
190
- CREATED: 201,
191
- NO_CONTENT: 204,
192
- BAD_REQUEST: 400,
193
- NOT_FOUND: 404,
194
- INTERNAL_SERVER_ERROR: 500,
195
- SERVICE_UNAVAILABLE: 503
196
- };
197
-
198
- // Export a function to get the complete configuration
199
- export const getConfig = () => ({
200
- server: SERVER,
201
- database: DATABASE,
202
- queue: QUEUE,
203
- jobs: JOBS,
204
- websocket: WEBSOCKET,
205
- encryption: ENCRYPTION,
206
- client: CLIENT,
207
- api: API,
208
- analytics: ANALYTICS,
209
- monitoring: MONITORING,
210
- logging: LOGGING,
211
- httpStatus: HTTP_STATUS
212
- });
213
-
214
- // Export default configuration object
215
- export default {
216
- SERVER,
217
- DATABASE,
218
- QUEUE,
219
- SYSTEM_EVENTS,
220
- JOBS,
221
- WEBSOCKET,
222
- ENCRYPTION,
223
- CLIENT,
224
- API,
225
- ANALYTICS,
226
- MONITORING,
227
- LOGGING,
228
- HTTP_STATUS
229
- };
@@ -1,129 +0,0 @@
1
- import pg from 'pg';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import config from '../config.js';
6
-
7
- const { Pool } = pg;
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
-
10
- /**
11
- * Create a PostgreSQL connection pool with proper configuration
12
- * This is the single source of truth for pool creation across the application
13
- */
14
- export const createPool = () => {
15
- const poolConfig = {
16
- user: config.DATABASE.USER,
17
- host: config.DATABASE.HOST,
18
- database: config.DATABASE.DATABASE,
19
- password: config.DATABASE.PASSWORD,
20
- port: config.DATABASE.PORT,
21
- max: config.DATABASE.POOL_SIZE,
22
- idleTimeoutMillis: config.DATABASE.IDLE_TIMEOUT,
23
- connectionTimeoutMillis: config.DATABASE.CONNECTION_TIMEOUT,
24
- // Note: statement_timeout and query_timeout cannot be set as startup parameters
25
- // They should be set per-query or per-transaction using SET commands
26
- application_name: config.SERVER.APPLICATION_NAME,
27
- // PERFORMANCE NOTE: TCP_NODELAY for latency reduction
28
- // The node-postgres driver enables TCP_NODELAY by default on sockets
29
- // For server-side: Edit postgresql.conf and add: tcp_nodelay = on
30
- // Reference: https://stackoverflow.com/questions/60634455/how-does-one-configure-tcp-nodelay-for-libpq-and-postgres-server
31
- keepAlive: true,
32
- keepAliveInitialDelayMillis: 10000
33
- };
34
-
35
- // Add SSL configuration if enabled
36
- if (config.DATABASE.USE_SSL) {
37
- poolConfig.ssl = {
38
- rejectUnauthorized: config.DATABASE.SSL_REJECT_UNAUTHORIZED
39
- };
40
- }
41
-
42
- return new Pool(poolConfig);
43
- };
44
-
45
- // Initialize database schema
46
- export const initDatabase = async (pool) => {
47
- try {
48
- // Check if the queen schema already exists
49
- const schemaCheck = await pool.query(`
50
- SELECT schema_name
51
- FROM information_schema.schemata
52
- WHERE schema_name = 'queen'
53
- `);
54
-
55
- if (schemaCheck.rows.length > 0) {
56
- console.log('Database schema already exists, skipping initialization');
57
- return;
58
- }
59
-
60
- // Schema doesn't exist, create it
61
- const schemaPath = path.join(__dirname, 'schema-v2.sql');
62
- const schema = fs.readFileSync(schemaPath, 'utf8');
63
-
64
- await pool.query(schema);
65
- console.log('Database schema V2 initialized');
66
- } catch (error) {
67
- console.error('Failed to initialize database schema:', error);
68
- throw error;
69
- }
70
- };
71
-
72
- // Valid transaction isolation levels
73
- const VALID_ISOLATION_LEVELS = [
74
- 'READ COMMITTED',
75
- 'REPEATABLE READ',
76
- 'SERIALIZABLE'
77
- ];
78
-
79
- // Helper for transactions with retry logic
80
- export const withTransaction = async (pool, callback, isolationLevel = 'READ COMMITTED') => {
81
- // Validate isolation level
82
- if (isolationLevel && !VALID_ISOLATION_LEVELS.includes(isolationLevel)) {
83
- throw new Error(`Invalid isolation level: ${isolationLevel}`);
84
- }
85
-
86
- const maxAttempts = 3;
87
- let attempt = 1;
88
-
89
- while (attempt <= maxAttempts) {
90
- const client = await pool.connect();
91
- try {
92
- await client.query('BEGIN');
93
-
94
- if (isolationLevel) {
95
- await client.query(`SET TRANSACTION ISOLATION LEVEL ${isolationLevel}`);
96
- }
97
-
98
- // Set timeouts to prevent long blocks
99
- await client.query(`SET LOCAL statement_timeout = ${config.DATABASE.STATEMENT_TIMEOUT}`);
100
- await client.query(`SET LOCAL lock_timeout = ${config.DATABASE.LOCK_TIMEOUT}`);
101
-
102
- const result = await callback(client);
103
- await client.query('COMMIT');
104
- return result;
105
- } catch (error) {
106
- await client.query('ROLLBACK');
107
-
108
- // Check for retriable errors:
109
- // 40001: serialization_failure
110
- // 40P01: deadlock_detected
111
- // 55P03: lock_not_available (lock timeout)
112
- // 25P02: in_failed_sql_transaction (transaction already aborted - retry whole transaction)
113
- if ((error.code === '40001' || error.code === '40P01' || error.code === '55P03' || error.code === '25P02') && attempt < maxAttempts) {
114
- // Exponential backoff: 100ms, 200ms, 400ms
115
- const delay = Math.min(100 * Math.pow(2, attempt - 1), 1000);
116
- await new Promise(resolve => setTimeout(resolve, delay));
117
- attempt++;
118
- console.log(`Transaction retry attempt ${attempt}/${maxAttempts} after ${error.code} (${error.message})`);
119
- continue;
120
- }
121
-
122
- throw error;
123
- } finally {
124
- client.release();
125
- }
126
- }
127
-
128
- throw new Error('Max transaction retry attempts exceeded');
129
- };