2024-08-27 08:40:04 +00:00
|
|
|
import { Redis } from 'ioredis';
|
|
|
|
|
|
|
|
import { parseIntFromEnvValue } from './utils.js';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef RedisConfiguration
|
2024-09-04 14:10:26 +00:00
|
|
|
* @property {string|undefined} namespace
|
|
|
|
* @property {string|undefined} url
|
|
|
|
* @property {import('ioredis').RedisOptions} options
|
2024-08-27 08:40:04 +00:00
|
|
|
*/
|
|
|
|
|
2024-09-04 14:10:26 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-08-27 08:40:04 +00:00
|
|
|
/**
|
|
|
|
* @param {NodeJS.ProcessEnv} env the `process.env` value to read configuration from
|
|
|
|
* @returns {RedisConfiguration} configuration for the Redis connection
|
|
|
|
*/
|
|
|
|
export function configFromEnv(env) {
|
2024-09-04 14:10:26 +00:00
|
|
|
const redisNamespace = env.REDIS_NAMESPACE;
|
2024-08-27 08:40:04 +00:00
|
|
|
|
2024-09-04 14:10:26 +00:00
|
|
|
// 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:
|
2024-08-27 08:40:04 +00:00
|
|
|
let redisPort = parseIntFromEnvValue(env.REDIS_PORT, 6379, 'REDIS_PORT');
|
|
|
|
let redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
|
|
|
|
|
|
|
|
/** @type {import('ioredis').RedisOptions} */
|
2024-09-04 14:10:26 +00:00
|
|
|
const options = {
|
|
|
|
host: env.REDIS_HOST ?? '127.0.0.1',
|
2024-08-27 08:40:04 +00:00
|
|
|
port: redisPort,
|
|
|
|
db: redisDatabase,
|
2024-09-04 14:10:26 +00:00
|
|
|
username: env.REDIS_USERNAME,
|
|
|
|
password: env.REDIS_PASSWORD,
|
|
|
|
...commonOptions,
|
2024-08-27 08:40:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
2024-09-04 14:10:26 +00:00
|
|
|
options,
|
|
|
|
namespace: redisNamespace
|
2024-08-27 08:40:04 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {RedisConfiguration} config
|
|
|
|
* @param {import('pino').Logger} logger
|
|
|
|
* @returns {Redis}
|
|
|
|
*/
|
2024-09-04 14:10:26 +00:00
|
|
|
export function createClient({ url, options }, logger) {
|
2024-08-27 08:40:04 +00:00
|
|
|
let client;
|
|
|
|
|
2024-09-04 14:10:26 +00:00
|
|
|
if (typeof url === 'string') {
|
|
|
|
client = new Redis(url, options);
|
2024-08-27 08:40:04 +00:00
|
|
|
} else {
|
2024-09-04 14:10:26 +00:00
|
|
|
client = new Redis(options);
|
2024-08-27 08:40:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
client.on('error', (err) => logger.error({ err }, 'Redis Client Error!'));
|
|
|
|
|
|
|
|
return client;
|
|
|
|
}
|