queen-mq 0.2.0 → 0.2.22

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 (85) hide show
  1. package/README.md +186 -763
  2. package/{src → client-js}/benchmark/consumer.js +6 -4
  3. package/{src → client-js}/benchmark/producer.js +9 -4
  4. package/{src → client-js}/client/client.js +277 -7
  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/README.md +30 -9
  9. package/{src → client-js}/test/core-tests.js +7 -2
  10. package/{src → client-js}/test/edge-case-tests.js +2 -1
  11. package/client-js/test/human.js +162 -0
  12. package/client-js/test/partition-transaction-tests.js +482 -0
  13. package/client-js/test/qos0-tests.js +334 -0
  14. package/{src → client-js}/test/test-new.js +55 -0
  15. package/package.json +8 -17
  16. package/init-db.js +0 -20
  17. package/src/cluster-server.js +0 -242
  18. package/src/config.js +0 -229
  19. package/src/database/connection.js +0 -129
  20. package/src/database/poolManager.js +0 -199
  21. package/src/database/schema-v2.sql +0 -278
  22. package/src/managers/eventManager.js +0 -59
  23. package/src/managers/queueManagerOptimized.js +0 -1634
  24. package/src/managers/resourceCache.js +0 -96
  25. package/src/managers/systemEventManager.js +0 -132
  26. package/src/routes/ack.js +0 -26
  27. package/src/routes/configure.js +0 -46
  28. package/src/routes/messages.js +0 -368
  29. package/src/routes/pop.js +0 -69
  30. package/src/routes/push.js +0 -28
  31. package/src/routes/resources.js +0 -330
  32. package/src/routes/status.js +0 -1037
  33. package/src/server.js +0 -1633
  34. package/src/test/MIGRATION_ISSUES.md +0 -174
  35. package/src/test/test.js +0 -4524
  36. package/src/utils/streaming.js +0 -231
  37. package/src/webapp-dist/assets/Analytics-DJrE4Rz2.css +0 -1
  38. package/src/webapp-dist/assets/Analytics-HEMXKmfJ.js +0 -1
  39. package/src/webapp-dist/assets/ConfirmDialog-YEoCdE7x.js +0 -1
  40. package/src/webapp-dist/assets/ConsumerGroups-CC5VJywp.css +0 -1
  41. package/src/webapp-dist/assets/ConsumerGroups-D8Z20HhZ.js +0 -1
  42. package/src/webapp-dist/assets/Dashboard-BRFOz6it.css +0 -1
  43. package/src/webapp-dist/assets/Dashboard-Bpx9I32h.js +0 -1
  44. package/src/webapp-dist/assets/LoadingSpinner-BPttXcbs.js +0 -1
  45. package/src/webapp-dist/assets/Messages-BOfVHKma.css +0 -1
  46. package/src/webapp-dist/assets/Messages-EKURCbLl.js +0 -1
  47. package/src/webapp-dist/assets/QueueDetail-BXXA7xoZ.css +0 -1
  48. package/src/webapp-dist/assets/QueueDetail-Bmxg0KiA.js +0 -1
  49. package/src/webapp-dist/assets/Queues-CvrJkrZT.css +0 -1
  50. package/src/webapp-dist/assets/Queues-yUx9_3-9.js +0 -1
  51. package/src/webapp-dist/assets/StatusBadge-C1bBtXHh.css +0 -1
  52. package/src/webapp-dist/assets/StatusBadge-C6N84XmO.js +0 -1
  53. package/src/webapp-dist/assets/analytics-BapTekLM.js +0 -1
  54. package/src/webapp-dist/assets/colors-5z6Q_kUr.js +0 -18
  55. package/src/webapp-dist/assets/index-B7w2bKT6.css +0 -1
  56. package/src/webapp-dist/assets/index-CUSnwYTH.js +0 -31
  57. package/src/webapp-dist/assets/messages-CRV6E-xU.js +0 -1
  58. package/src/webapp-dist/assets/queen-logo-blue.svg +0 -210
  59. package/src/webapp-dist/assets/queen-logo-cyan.svg +0 -210
  60. package/src/webapp-dist/assets/queen-logo-indigo.svg +0 -210
  61. package/src/webapp-dist/assets/queen-logo-orange.svg +0 -210
  62. package/src/webapp-dist/assets/queen-logo-pink.svg +0 -210
  63. package/src/webapp-dist/assets/queen-logo-purple.svg +0 -210
  64. package/src/webapp-dist/assets/queen-logo-rose.svg +0 -239
  65. package/src/webapp-dist/assets/queen-logo.svg +0 -263
  66. package/src/webapp-dist/assets/queues-BdUbxfBC.js +0 -1
  67. package/src/webapp-dist/assets/resources-Bs9U-ceF.js +0 -1
  68. package/src/webapp-dist/index.html +0 -15
  69. package/src/websocket/wsServer.js +0 -228
  70. /package/{src → client-js}/benchmark/consumer_multi.js +0 -0
  71. /package/{src → client-js}/benchmark/producer_multi.js +0 -0
  72. /package/{src → client-js}/client/utils/loadBalancer.js +0 -0
  73. /package/{src → client-js}/services/encryptionService.js +0 -0
  74. /package/{src → client-js}/services/evictionService.js +0 -0
  75. /package/{src → client-js}/services/retentionService.js +0 -0
  76. /package/{src → client-js}/services/startupSync.js +0 -0
  77. /package/{src → client-js}/test/advanced-client-tests.js +0 -0
  78. /package/{src → client-js}/test/advanced-pattern-tests.js +0 -0
  79. /package/{src → client-js}/test/bus-mode-tests.js +0 -0
  80. /package/{src → client-js}/test/enterprise-tests.js +0 -0
  81. /package/{src → client-js}/test/partition-locking-tests.js +0 -0
  82. /package/{src → client-js}/test/utils.js +0 -0
  83. /package/{src → client-js}/test/window-buffer-test.js +0 -0
  84. /package/{src → client-js}/utils/logger.js +0 -0
  85. /package/{src → client-js}/utils/uuid.js +0 -0
@@ -1,199 +0,0 @@
1
- /**
2
- * Connection Pool Manager for High Concurrency
3
- *
4
- * This module provides optimized connection pooling for high-throughput scenarios
5
- */
6
-
7
- import pg from 'pg';
8
- import appConfig from '../config.js';
9
-
10
- const { Pool } = pg;
11
-
12
- export class PoolManager {
13
- constructor(config) {
14
- this.config = {
15
- user: appConfig.DATABASE.USER,
16
- host: appConfig.DATABASE.HOST,
17
- database: appConfig.DATABASE.DATABASE,
18
- password: appConfig.DATABASE.PASSWORD,
19
- port: appConfig.DATABASE.PORT,
20
- max: appConfig.DATABASE.POOL_SIZE,
21
- idleTimeoutMillis: appConfig.DATABASE.IDLE_TIMEOUT,
22
- connectionTimeoutMillis: appConfig.DATABASE.CONNECTION_TIMEOUT,
23
- ...config
24
- };
25
-
26
- // Add SSL configuration if enabled
27
- if (appConfig.DATABASE.USE_SSL) {
28
- this.config.ssl = {
29
- rejectUnauthorized: appConfig.DATABASE.SSL_REJECT_UNAUTHORIZED
30
- };
31
- }
32
-
33
- this.pool = new Pool(this.config);
34
- this.waitingQueue = [];
35
- this.activeConnections = 0;
36
- this.maxRetries = appConfig.DATABASE.MAX_RETRIES;
37
-
38
- // Monitor pool events
39
- this.pool.on('connect', () => {
40
- this.activeConnections++;
41
- });
42
-
43
- this.pool.on('remove', () => {
44
- this.activeConnections--;
45
- });
46
-
47
- this.pool.on('error', (err) => {
48
- console.error('Unexpected pool error:', err);
49
- });
50
- }
51
-
52
- /**
53
- * Execute a query with automatic retry and connection management
54
- */
55
- async query(text, params, retries = this.maxRetries) {
56
- for (let attempt = 0; attempt < retries; attempt++) {
57
- try {
58
- return await this.pool.query(text, params);
59
- } catch (error) {
60
- if (error.message.includes('timeout exceeded when trying to connect')) {
61
- // Connection timeout - retry with exponential backoff
62
- if (attempt < retries - 1) {
63
- const delay = Math.min(100 * Math.pow(2, attempt), 1000);
64
- await new Promise(resolve => setTimeout(resolve, delay));
65
- continue;
66
- }
67
- }
68
- throw error;
69
- }
70
- }
71
- }
72
-
73
- /**
74
- * Get a client with retry logic
75
- */
76
- async getClient(retries = this.maxRetries) {
77
- for (let attempt = 0; attempt < retries; attempt++) {
78
- try {
79
- const client = await this.pool.connect();
80
-
81
- // Wrap release to ensure proper cleanup
82
- const originalRelease = client.release.bind(client);
83
- client.release = (err) => {
84
- originalRelease(err);
85
- this.processWaitingQueue();
86
- };
87
-
88
- return client;
89
- } catch (error) {
90
- if (error.message.includes('timeout exceeded when trying to connect')) {
91
- if (attempt < retries - 1) {
92
- const delay = Math.min(100 * Math.pow(2, attempt), 1000);
93
- await new Promise(resolve => setTimeout(resolve, delay));
94
- continue;
95
- }
96
- }
97
- throw error;
98
- }
99
- }
100
- }
101
-
102
- /**
103
- * Execute a function with a client, ensuring proper cleanup
104
- */
105
- async withClient(fn) {
106
- const client = await this.getClient();
107
- try {
108
- return await fn(client);
109
- } finally {
110
- client.release();
111
- }
112
- }
113
-
114
- /**
115
- * Execute multiple queries in parallel with connection management
116
- */
117
- async parallelQueries(queries) {
118
- const promises = queries.map(({ text, params }) =>
119
- this.query(text, params)
120
- );
121
- return Promise.all(promises);
122
- }
123
-
124
- /**
125
- * Batch execute with optimal connection usage
126
- */
127
- async batchExecute(items, batchSize, executeFn) {
128
- const results = [];
129
- const batches = [];
130
-
131
- // Split items into batches
132
- for (let i = 0; i < items.length; i += batchSize) {
133
- batches.push(items.slice(i, i + batchSize));
134
- }
135
-
136
- // Process batches with controlled concurrency
137
- const concurrency = Math.min(Math.floor(this.config.max / 2), 10);
138
-
139
- for (let i = 0; i < batches.length; i += concurrency) {
140
- const batchPromises = batches
141
- .slice(i, i + concurrency)
142
- .map(batch => this.withClient(client => executeFn(client, batch)));
143
-
144
- const batchResults = await Promise.all(batchPromises);
145
- results.push(...batchResults.flat());
146
- }
147
-
148
- return results;
149
- }
150
-
151
- /**
152
- * Process any waiting queue operations
153
- */
154
- processWaitingQueue() {
155
- if (this.waitingQueue.length > 0 && this.pool.idleCount > 0) {
156
- const { resolve } = this.waitingQueue.shift();
157
- this.getClient().then(resolve).catch(() => {
158
- // Re-queue if failed
159
- this.waitingQueue.push({ resolve });
160
- });
161
- }
162
- }
163
-
164
- /**
165
- * Get pool statistics
166
- */
167
- getStats() {
168
- return {
169
- total: this.pool.totalCount,
170
- idle: this.pool.idleCount,
171
- waiting: this.pool.waitingCount,
172
- active: this.activeConnections,
173
- queueLength: this.waitingQueue.length,
174
- config: {
175
- max: this.config.max,
176
- connectionTimeout: this.config.connectionTimeoutMillis
177
- }
178
- };
179
- }
180
-
181
- /**
182
- * Gracefully close the pool
183
- */
184
- async close() {
185
- await this.pool.end();
186
- }
187
- }
188
-
189
- // Create a singleton instance
190
- let poolManager;
191
-
192
- export const getPoolManager = (config) => {
193
- if (!poolManager) {
194
- poolManager = new PoolManager(config);
195
- }
196
- return poolManager;
197
- };
198
-
199
- export default PoolManager;
@@ -1,278 +0,0 @@
1
- -- ════════════════════════════════════════════════════════════════
2
- -- Queen Message Queue Schema V3
3
- -- Simplified: Merged cursor+lease, removed messages_status
4
- -- ════════════════════════════════════════════════════════════════
5
-
6
- CREATE SCHEMA IF NOT EXISTS queen;
7
-
8
- -- ────────────────────────────────────────────────────────────────
9
- -- 1. QUEUES - Top-level configuration
10
- -- ────────────────────────────────────────────────────────────────
11
- CREATE TABLE IF NOT EXISTS queen.queues (
12
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
13
- name VARCHAR(255) UNIQUE NOT NULL,
14
- namespace VARCHAR(255),
15
- task VARCHAR(255),
16
- priority INTEGER DEFAULT 0,
17
-
18
- -- Processing configuration
19
- lease_time INTEGER DEFAULT 300,
20
- retry_limit INTEGER DEFAULT 3,
21
- retry_delay INTEGER DEFAULT 1000,
22
- max_size INTEGER DEFAULT 10000,
23
- ttl INTEGER DEFAULT 3600,
24
- dead_letter_queue BOOLEAN DEFAULT FALSE,
25
- dlq_after_max_retries BOOLEAN DEFAULT FALSE,
26
- delayed_processing INTEGER DEFAULT 0,
27
- window_buffer INTEGER DEFAULT 0,
28
-
29
- -- Enterprise features
30
- retention_seconds INTEGER DEFAULT 0,
31
- completed_retention_seconds INTEGER DEFAULT 0,
32
- retention_enabled BOOLEAN DEFAULT FALSE,
33
- encryption_enabled BOOLEAN DEFAULT FALSE,
34
- max_wait_time_seconds INTEGER DEFAULT 0,
35
- max_queue_size INTEGER DEFAULT 0,
36
-
37
- created_at TIMESTAMPTZ DEFAULT NOW()
38
- );
39
-
40
- -- Indexes for queues
41
- CREATE INDEX IF NOT EXISTS idx_queues_name ON queen.queues(name);
42
- CREATE INDEX IF NOT EXISTS idx_queues_priority ON queen.queues(priority DESC);
43
- CREATE INDEX IF NOT EXISTS idx_queues_namespace ON queen.queues(namespace) WHERE namespace IS NOT NULL;
44
- CREATE INDEX IF NOT EXISTS idx_queues_task ON queen.queues(task) WHERE task IS NOT NULL;
45
- CREATE INDEX IF NOT EXISTS idx_queues_namespace_task ON queen.queues(namespace, task)
46
- WHERE namespace IS NOT NULL AND task IS NOT NULL;
47
- CREATE INDEX IF NOT EXISTS idx_queues_retention_enabled ON queen.queues(retention_enabled) WHERE retention_enabled = true;
48
-
49
- -- ────────────────────────────────────────────────────────────────
50
- -- 2. PARTITIONS - FIFO subdivisions within queues
51
- -- ────────────────────────────────────────────────────────────────
52
- CREATE TABLE IF NOT EXISTS queen.partitions (
53
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
54
- queue_id UUID REFERENCES queen.queues(id) ON DELETE CASCADE,
55
- name VARCHAR(255) NOT NULL DEFAULT 'Default',
56
- created_at TIMESTAMPTZ DEFAULT NOW(),
57
- last_activity TIMESTAMPTZ DEFAULT NOW(),
58
- UNIQUE(queue_id, name)
59
- );
60
-
61
- CREATE INDEX IF NOT EXISTS idx_partitions_queue_name
62
- ON queen.partitions(queue_id, name);
63
-
64
- CREATE INDEX IF NOT EXISTS idx_partitions_last_activity
65
- ON queen.partitions(last_activity);
66
-
67
- -- ────────────────────────────────────────────────────────────────
68
- -- 3. MESSAGES - Immutable message data
69
- -- ────────────────────────────────────────────────────────────────
70
- CREATE TABLE IF NOT EXISTS queen.messages (
71
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
72
- transaction_id VARCHAR(255) UNIQUE NOT NULL,
73
- trace_id UUID DEFAULT gen_random_uuid(),
74
- partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
75
- payload JSONB NOT NULL,
76
- created_at TIMESTAMPTZ DEFAULT NOW(),
77
- is_encrypted BOOLEAN DEFAULT FALSE
78
- );
79
-
80
- -- Indexes for messages (critical for cursor-based queries!)
81
- CREATE INDEX IF NOT EXISTS idx_messages_partition_created_id
82
- ON queen.messages(partition_id, created_at, id);
83
-
84
- CREATE INDEX IF NOT EXISTS idx_messages_transaction_id
85
- ON queen.messages(transaction_id);
86
-
87
- CREATE INDEX IF NOT EXISTS idx_messages_trace_id
88
- ON queen.messages(trace_id);
89
-
90
- CREATE INDEX IF NOT EXISTS idx_messages_created_at
91
- ON queen.messages(created_at);
92
-
93
- -- ────────────────────────────────────────────────────────────────
94
- -- 4. PARTITION_CONSUMERS - Unified cursor + lease tracking
95
- -- ────────────────────────────────────────────────────────────────
96
- CREATE TABLE IF NOT EXISTS queen.partition_consumers (
97
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
98
- partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
99
- consumer_group VARCHAR(255) DEFAULT '__QUEUE_MODE__',
100
-
101
- -- ═══════════════════════════════════════════════════════════
102
- -- CURSOR STATE (Persistent - consumption progress)
103
- -- ═══════════════════════════════════════════════════════════
104
- last_consumed_id UUID DEFAULT '00000000-0000-0000-0000-000000000000',
105
- last_consumed_created_at TIMESTAMPTZ,
106
- total_messages_consumed BIGINT DEFAULT 0,
107
- total_batches_consumed BIGINT DEFAULT 0,
108
- last_consumed_at TIMESTAMPTZ,
109
-
110
- -- ═══════════════════════════════════════════════════════════
111
- -- LEASE STATE (Ephemeral - active processing lock)
112
- -- ═══════════════════════════════════════════════════════════
113
- lease_expires_at TIMESTAMPTZ,
114
- lease_acquired_at TIMESTAMPTZ,
115
- message_batch JSONB,
116
- batch_size INTEGER DEFAULT 0,
117
- acked_count INTEGER DEFAULT 0,
118
- worker_id VARCHAR(255),
119
-
120
- -- ═══════════════════════════════════════════════════════════
121
- -- STATISTICS (for dashboard - avoids expensive COUNT queries)
122
- -- ═══════════════════════════════════════════════════════════
123
- pending_estimate BIGINT DEFAULT 0,
124
- last_stats_update TIMESTAMPTZ,
125
-
126
- -- ═══════════════════════════════════════════════════════════
127
- -- METADATA
128
- -- ═══════════════════════════════════════════════════════════
129
- created_at TIMESTAMPTZ DEFAULT NOW(),
130
-
131
- UNIQUE(partition_id, consumer_group),
132
-
133
- -- Constraint: cursor consistency
134
- CHECK (
135
- (last_consumed_id = '00000000-0000-0000-0000-000000000000'
136
- AND last_consumed_created_at IS NULL)
137
- OR
138
- (last_consumed_id != '00000000-0000-0000-0000-000000000000'
139
- AND last_consumed_created_at IS NOT NULL)
140
- )
141
- );
142
-
143
- -- Indexes for partition_consumers (critical for performance!)
144
- CREATE INDEX IF NOT EXISTS idx_partition_consumers_lookup
145
- ON queen.partition_consumers(partition_id, consumer_group);
146
-
147
- CREATE INDEX IF NOT EXISTS idx_partition_consumers_active_leases
148
- ON queen.partition_consumers(partition_id, consumer_group, lease_expires_at)
149
- WHERE lease_expires_at IS NOT NULL;
150
-
151
- CREATE INDEX IF NOT EXISTS idx_partition_consumers_expired_leases
152
- ON queen.partition_consumers(lease_expires_at)
153
- WHERE lease_expires_at IS NOT NULL;
154
-
155
- CREATE INDEX IF NOT EXISTS idx_partition_consumers_progress
156
- ON queen.partition_consumers(last_consumed_at DESC);
157
-
158
- CREATE INDEX IF NOT EXISTS idx_partition_consumers_idle
159
- ON queen.partition_consumers(partition_id, consumer_group)
160
- WHERE lease_expires_at IS NULL;
161
-
162
- CREATE INDEX IF NOT EXISTS idx_partition_consumers_consumer_group
163
- ON queen.partition_consumers(consumer_group);
164
-
165
- -- ────────────────────────────────────────────────────────────────
166
- -- 5. MESSAGES_CONSUMED - ACK tracking for metrics (time-series)
167
- -- ────────────────────────────────────────────────────────────────
168
- CREATE TABLE IF NOT EXISTS queen.messages_consumed (
169
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
170
- partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
171
- consumer_group VARCHAR(255) NOT NULL,
172
-
173
- -- Metrics for this ACK batch
174
- messages_completed INTEGER DEFAULT 0,
175
- messages_failed INTEGER DEFAULT 0,
176
-
177
- -- Timestamp for time-series aggregation
178
- acked_at TIMESTAMPTZ DEFAULT NOW()
179
- );
180
-
181
- -- Indexes for time-series queries
182
- CREATE INDEX IF NOT EXISTS idx_messages_consumed_acked_at
183
- ON queen.messages_consumed(acked_at DESC);
184
-
185
- CREATE INDEX IF NOT EXISTS idx_messages_consumed_partition_acked
186
- ON queen.messages_consumed(partition_id, acked_at DESC);
187
-
188
- CREATE INDEX IF NOT EXISTS idx_messages_consumed_consumer_acked
189
- ON queen.messages_consumed(consumer_group, acked_at DESC);
190
-
191
- -- Composite index for filtering by queue (via partition join)
192
- CREATE INDEX IF NOT EXISTS idx_messages_consumed_partition_id
193
- ON queen.messages_consumed(partition_id);
194
-
195
- -- ────────────────────────────────────────────────────────────────
196
- -- 6. DEAD_LETTER_QUEUE - Failed messages
197
- -- ────────────────────────────────────────────────────────────────
198
- CREATE TABLE IF NOT EXISTS queen.dead_letter_queue (
199
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
200
- message_id UUID REFERENCES queen.messages(id) ON DELETE CASCADE,
201
- partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
202
- consumer_group VARCHAR(255),
203
- error_message TEXT,
204
- retry_count INTEGER DEFAULT 0,
205
- original_created_at TIMESTAMPTZ,
206
- failed_at TIMESTAMPTZ DEFAULT NOW()
207
- );
208
-
209
- CREATE INDEX IF NOT EXISTS idx_dlq_partition
210
- ON queen.dead_letter_queue(partition_id);
211
-
212
- CREATE INDEX IF NOT EXISTS idx_dlq_consumer_group
213
- ON queen.dead_letter_queue(consumer_group);
214
-
215
- CREATE INDEX IF NOT EXISTS idx_dlq_failed_at
216
- ON queen.dead_letter_queue(failed_at DESC);
217
-
218
- CREATE INDEX IF NOT EXISTS idx_dlq_message_consumer
219
- ON queen.dead_letter_queue(message_id, consumer_group);
220
-
221
- -- ────────────────────────────────────────────────────────────────
222
- -- 7. RETENTION_HISTORY - Optional audit of cleanup operations
223
- -- ────────────────────────────────────────────────────────────────
224
- CREATE TABLE IF NOT EXISTS queen.retention_history (
225
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
226
- partition_id UUID REFERENCES queen.partitions(id) ON DELETE CASCADE,
227
- messages_deleted INTEGER DEFAULT 0,
228
- retention_type VARCHAR(50), -- 'retention', 'completed', 'evicted'
229
- executed_at TIMESTAMPTZ DEFAULT NOW()
230
- );
231
-
232
- CREATE INDEX IF NOT EXISTS idx_retention_history_partition
233
- ON queen.retention_history(partition_id);
234
-
235
- CREATE INDEX IF NOT EXISTS idx_retention_history_executed
236
- ON queen.retention_history(executed_at);
237
-
238
- -- ────────────────────────────────────────────────────────────────
239
- -- TRIGGERS
240
- -- ────────────────────────────────────────────────────────────────
241
-
242
- -- Update partition last_activity on message insert
243
- CREATE OR REPLACE FUNCTION update_partition_last_activity()
244
- RETURNS TRIGGER AS $$
245
- BEGIN
246
- UPDATE queen.partitions
247
- SET last_activity = NOW()
248
- WHERE id = NEW.partition_id;
249
- RETURN NEW;
250
- END;
251
- $$ LANGUAGE plpgsql;
252
-
253
- DROP TRIGGER IF EXISTS trigger_update_partition_activity ON queen.messages;
254
- CREATE TRIGGER trigger_update_partition_activity
255
- AFTER INSERT ON queen.messages
256
- FOR EACH ROW
257
- EXECUTE FUNCTION update_partition_last_activity();
258
-
259
- -- Update pending_estimate on message insert
260
- CREATE OR REPLACE FUNCTION update_pending_on_push()
261
- RETURNS TRIGGER AS $$
262
- BEGIN
263
- -- Increment pending estimate for all consumers on this partition
264
- UPDATE queen.partition_consumers
265
- SET pending_estimate = pending_estimate + 1,
266
- last_stats_update = NOW()
267
- WHERE partition_id = NEW.partition_id;
268
-
269
- -- If no consumers exist yet, that's OK - they'll be created on first POP
270
- RETURN NEW;
271
- END;
272
- $$ LANGUAGE plpgsql;
273
-
274
- DROP TRIGGER IF EXISTS trigger_update_pending_on_push ON queen.messages;
275
- CREATE TRIGGER trigger_update_pending_on_push
276
- AFTER INSERT ON queen.messages
277
- FOR EACH ROW
278
- EXECUTE FUNCTION update_pending_on_push();
@@ -1,59 +0,0 @@
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
- };