Files
indiekit-endpoint-microsub/lib/cache/redis.js
Ricardo 4819c229cd feat: restore full microsub implementation with reader UI
Restores complete implementation from feat/endpoint-microsub branch:
- Reader UI with views (reader.njk, channel.njk, feeds.njk, etc.)
- Feed polling, parsing, and normalization
- WebSub subscriber
- SSE realtime updates
- Redis caching
- Search indexing
- Media proxy
- Webmention processing
2026-02-06 20:20:25 +01:00

182 lines
3.9 KiB
JavaScript

/**
* Redis caching utilities
* @module cache/redis
*/
import Redis from "ioredis";
let redisClient;
/**
* Get Redis client from application
* @param {object} application - Indiekit application
* @returns {object|undefined} Redis client or undefined
*/
export function getRedisClient(application) {
// Check if Redis is already initialized on the application
if (application.redis) {
return application.redis;
}
// Check if we already created a client
if (redisClient) {
return redisClient;
}
// Check for Redis URL in config
const redisUrl = application.config?.application?.redisUrl;
if (redisUrl) {
try {
redisClient = new Redis(redisUrl, {
maxRetriesPerRequest: 3,
retryStrategy(times) {
const delay = Math.min(times * 50, 2000);
return delay;
},
lazyConnect: true,
});
redisClient.on("error", (error) => {
console.error("[Microsub] Redis error:", error.message);
});
redisClient.on("connect", () => {
console.info("[Microsub] Redis connected");
});
// Connect asynchronously
redisClient.connect().catch((error) => {
console.warn("[Microsub] Redis connection failed:", error.message);
});
return redisClient;
} catch (error) {
console.warn("[Microsub] Failed to initialize Redis:", error.message);
}
}
}
/**
* Get value from cache
* @param {object} redis - Redis client
* @param {string} key - Cache key
* @returns {Promise<object|undefined>} Cached value or undefined
*/
export async function getCache(redis, key) {
if (!redis) {
return;
}
try {
const value = await redis.get(key);
if (value) {
return JSON.parse(value);
}
} catch {
// Ignore cache errors
}
}
/**
* Set value in cache
* @param {object} redis - Redis client
* @param {string} key - Cache key
* @param {object} value - Value to cache
* @param {number} [ttl] - Time to live in seconds
* @returns {Promise<void>}
*/
export async function setCache(redis, key, value, ttl = 300) {
if (!redis) {
return;
}
try {
const serialized = JSON.stringify(value);
await (ttl
? redis.set(key, serialized, "EX", ttl)
: redis.set(key, serialized));
} catch {
// Ignore cache errors
}
}
/**
* Delete value from cache
* @param {object} redis - Redis client
* @param {string} key - Cache key
* @returns {Promise<void>}
*/
export async function deleteCache(redis, key) {
if (!redis) {
return;
}
try {
await redis.del(key);
} catch {
// Ignore cache errors
}
}
/**
* Publish event to channel
* @param {object} redis - Redis client
* @param {string} channel - Channel name
* @param {object} data - Event data
* @returns {Promise<void>}
*/
export async function publishEvent(redis, channel, data) {
if (!redis) {
return;
}
try {
await redis.publish(channel, JSON.stringify(data));
} catch {
// Ignore pub/sub errors
}
}
/**
* Subscribe to channel
* @param {object} redis - Redis client (must be separate connection for pub/sub)
* @param {string} channel - Channel name
* @param {(data: object) => void} callback - Callback function for messages
* @returns {Promise<void>}
*/
export async function subscribeToChannel(redis, channel, callback) {
if (!redis) {
return;
}
try {
await redis.subscribe(channel);
redis.on("message", (receivedChannel, message) => {
if (receivedChannel === channel) {
try {
const data = JSON.parse(message);
callback(data);
} catch {
callback(message);
}
}
});
} catch {
// Ignore subscription errors
}
}
/**
* Cleanup Redis connection on shutdown
*/
export async function closeRedis() {
if (redisClient) {
try {
await redisClient.quit();
redisClient = undefined;
} catch {
// Ignore cleanup errors
}
}
}