UNPKG

firestore-queue

Version:

A powerful, scalable queue system built on Google Firestore with time-based indexing, auto-configuration, and connection reuse

283 lines 9.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HTTPQueueServer = exports.HTTPWriter = void 0; /** * HTTP Writer for Fire Queue * Allows writing to the queue via HTTP endpoints */ class HTTPWriter { constructor(config) { this.config = { timeout: 30000, // 30 seconds headers: { 'Content-Type': 'application/json', }, validatePayload: true, maxPayloadSize: 1024 * 1024, // 1MB retryAttempts: 3, retryDelayMs: 1000, ...config, }; // Add auth header if token provided if (this.config.authToken) { this.config.headers['Authorization'] = `Bearer ${this.config.authToken}`; } } /** * Write a single message via HTTP */ async write(message) { const startTime = Date.now(); try { const response = await this.makeRequest('/enqueue', { method: 'POST', body: JSON.stringify({ queueName: this.config.queueName, message, }), }); const result = await response.json(); return { success: response.ok, messageId: result.messageId, error: response.ok ? undefined : result.error, timestamp: new Date(), latencyMs: Date.now() - startTime, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), timestamp: new Date(), latencyMs: Date.now() - startTime, }; } } /** * Write multiple messages via HTTP batch endpoint */ async writeBatch(messages) { const startTime = Date.now(); try { const response = await this.makeRequest('/enqueue/batch', { method: 'POST', body: JSON.stringify({ queueName: this.config.queueName, messages, }), }); const result = await response.json(); return { success: response.ok, totalMessages: messages.length, successfulWrites: result.successfulWrites || 0, failedWrites: result.failedWrites || 0, messageIds: result.messageIds || [], errors: result.errors || [], timestamp: new Date(), latencyMs: Date.now() - startTime, }; } catch (error) { return { success: false, totalMessages: messages.length, successfulWrites: 0, failedWrites: messages.length, messageIds: [], errors: [{ index: -1, error: error instanceof Error ? error.message : String(error) }], timestamp: new Date(), latencyMs: Date.now() - startTime, }; } } /** * Make HTTP request with retry logic */ async makeRequest(path, options) { const url = `${this.config.endpoint}${path}`; for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) { try { const response = await fetch(url, { ...options, headers: { ...this.config.headers, ...options.headers, }, signal: AbortSignal.timeout(this.config.timeout), }); // Return response (even if not ok) - let caller handle status return response; } catch (error) { if (attempt === this.config.retryAttempts) { throw error; } // Wait before retry await new Promise(resolve => setTimeout(resolve, this.config.retryDelayMs * attempt)); } } throw new Error('Max retries exceeded'); } } exports.HTTPWriter = HTTPWriter; /** * HTTP Server implementation for receiving queue writes */ class HTTPQueueServer { constructor(port = 3000) { this.queues = new Map(); // FireQueue instances this.port = port; } /** * Register a queue for HTTP access */ registerQueue(name, queue) { this.queues.set(name, queue); } /** * Start HTTP server */ async start() { const express = require('express'); const app = express(); app.use(express.json({ limit: '10mb' })); // Echo incoming trace header so downstream services can correlate requests app.use((req, res, next) => { const traceHeader = req.header('X-Cloud-Trace-Context'); if (traceHeader) { res.set('X-Cloud-Trace-Context', traceHeader); } next(); }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', queues: Array.from(this.queues.keys()), timestamp: new Date().toISOString(), }); }); // Single message enqueue app.post('/enqueue', async (req, res) => { try { const { queueName, message } = req.body; if (!queueName || !message) { return res.status(400).json({ error: 'queueName and message are required' }); } const queue = this.queues.get(queueName); if (!queue) { return res.status(404).json({ error: `Queue '${queueName}' not found` }); } const messageId = await queue.enqueue(message); res.json({ success: true, messageId, timestamp: new Date().toISOString(), }); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) }); } }); // Batch message enqueue app.post('/enqueue/batch', async (req, res) => { try { const { queueName, messages } = req.body; if (!queueName || !Array.isArray(messages)) { return res.status(400).json({ error: 'queueName and messages array are required' }); } const queue = this.queues.get(queueName); if (!queue) { return res.status(404).json({ error: `Queue '${queueName}' not found` }); } const messageIds = []; const errors = []; let successfulWrites = 0; for (let i = 0; i < messages.length; i++) { try { const messageId = await queue.enqueue(messages[i]); messageIds.push(messageId); successfulWrites++; } catch (error) { errors.push({ index: i, error: error instanceof Error ? error.message : String(error) }); } } res.json({ success: true, totalMessages: messages.length, successfulWrites, failedWrites: errors.length, messageIds, errors, timestamp: new Date().toISOString(), }); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) }); } }); // Get queue metrics app.get('/queues/:queueName/metrics', async (req, res) => { try { const { queueName } = req.params; const queue = this.queues.get(queueName); if (!queue) { return res.status(404).json({ error: `Queue '${queueName}' not found` }); } const metrics = await queue.getMetrics(); res.json(metrics); } catch (error) { res.status(500).json({ error: error instanceof Error ? error.message : String(error) }); } }); // List all queues app.get('/queues', (req, res) => { const queueList = Array.from(this.queues.keys()).map(name => ({ name, status: 'active', })); res.json({ queues: queueList, total: queueList.length, }); }); this.server = app.listen(this.port, () => { console.log(`🔥 Fire Queue HTTP server running on port ${this.port}`); console.log(`📝 Enqueue: POST http://localhost:${this.port}/enqueue`); console.log(`📊 Metrics: GET http://localhost:${this.port}/queues/{name}/metrics`); }); } /** * Stop HTTP server */ async stop() { if (this.server) { this.server.close(); this.server = undefined; } } } exports.HTTPQueueServer = HTTPQueueServer; //# sourceMappingURL=HTTPWriter.js.map