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
JavaScript
;
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