catstodon/streaming/redis.js

135 lines
3.8 KiB
JavaScript

import { Redis } from 'ioredis';
import { parseIntFromEnvValue } from './utils.js';
/**
* @typedef RedisConfiguration
* @property {string|undefined} namespace
* @property {string|undefined} url
* @property {import('ioredis').RedisOptions} options
*/
/**
*
* @param {NodeJS.ProcessEnv} env
* @returns {boolean}
*/
function hasSentinelConfiguration(env) {
return (
typeof env.REDIS_SENTINELS === 'string' &&
env.REDIS_SENTINELS.length > 0 &&
typeof env.REDIS_SENTINEL_MASTER === 'string' &&
env.REDIS_SENTINEL_MASTER.length > 0
);
}
/**
*
* @param {NodeJS.ProcessEnv} env
* @param {import('ioredis').SentinelConnectionOptions} commonOptions
* @returns {import('ioredis').SentinelConnectionOptions}
*/
function getSentinelConfiguration(env, commonOptions) {
const redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
const sentinelPort = parseIntFromEnvValue(env.REDIS_SENTINEL_PORT, 26379, 'REDIS_SENTINEL_PORT');
const sentinels = env.REDIS_SENTINELS.split(',').map((sentinel) => {
const [host, port] = sentinel.split(':', 2);
/** @type {import('ioredis').SentinelAddress} */
return {
host: host,
port: port ?? sentinelPort,
// Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
// only allowing IPv4 connections:
// https://github.com/redis/ioredis/issues/1576
family: 0
};
});
return {
db: redisDatabase,
name: env.REDIS_SENTINEL_MASTER,
username: env.REDIS_USERNAME,
password: env.REDIS_PASSWORD,
sentinelUsername: env.REDIS_SENTINEL_USERNAME ?? env.REDIS_USERNAME,
sentinelPassword: env.REDIS_SENTINEL_PASSWORD ?? env.REDIS_PASSWORD,
sentinels,
...commonOptions,
};
}
/**
* @param {NodeJS.ProcessEnv} env the `process.env` value to read configuration from
* @returns {RedisConfiguration} configuration for the Redis connection
*/
export function configFromEnv(env) {
const redisNamespace = env.REDIS_NAMESPACE;
// These options apply for both REDIS_URL based connections and connections
// using the other REDIS_* environment variables:
const commonOptions = {
// Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
// only allowing IPv4 connections:
// https://github.com/redis/ioredis/issues/1576
family: 0
// Note: we don't use auto-prefixing of keys since this doesn't apply to
// subscribe/unsubscribe which have "channel" instead of "key" arguments
};
// If we receive REDIS_URL, don't continue parsing any other REDIS_*
// environment variables:
if (typeof env.REDIS_URL === 'string' && env.REDIS_URL.length > 0) {
return {
url: env.REDIS_URL,
options: commonOptions,
namespace: redisNamespace
};
}
// If we have configuration for Redis Sentinel mode, prefer that:
if (hasSentinelConfiguration(env)) {
return {
options: getSentinelConfiguration(env, commonOptions),
namespace: redisNamespace
};
}
// Finally, handle all the other REDIS_* environment variables:
let redisPort = parseIntFromEnvValue(env.REDIS_PORT, 6379, 'REDIS_PORT');
let redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
/** @type {import('ioredis').RedisOptions} */
const options = {
host: env.REDIS_HOST ?? '127.0.0.1',
port: redisPort,
db: redisDatabase,
username: env.REDIS_USERNAME,
password: env.REDIS_PASSWORD,
...commonOptions,
};
return {
options,
namespace: redisNamespace
};
}
/**
* @param {RedisConfiguration} config
* @param {import('pino').Logger} logger
* @returns {Redis}
*/
export function createClient({ url, options }, logger) {
let client;
if (typeof url === 'string') {
client = new Redis(url, options);
} else {
client = new Redis(options);
}
client.on('error', (err) => logger.error({ err }, 'Redis Client Error!'));
return client;
}