redis.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { Redis } from 'ioredis';
  2. import { parseIntFromEnvValue } from './utils.js';
  3. /**
  4. * @typedef RedisConfiguration
  5. * @property {string|undefined} namespace
  6. * @property {string|undefined} url
  7. * @property {import('ioredis').RedisOptions} options
  8. */
  9. /**
  10. *
  11. * @param {NodeJS.ProcessEnv} env
  12. * @returns {boolean}
  13. */
  14. function hasSentinelConfiguration(env) {
  15. return (
  16. typeof env.REDIS_SENTINELS === 'string' &&
  17. env.REDIS_SENTINELS.length > 0 &&
  18. typeof env.REDIS_SENTINEL_MASTER === 'string' &&
  19. env.REDIS_SENTINEL_MASTER.length > 0
  20. );
  21. }
  22. /**
  23. *
  24. * @param {NodeJS.ProcessEnv} env
  25. * @param {import('ioredis').SentinelConnectionOptions} commonOptions
  26. * @returns {import('ioredis').SentinelConnectionOptions}
  27. */
  28. function getSentinelConfiguration(env, commonOptions) {
  29. const redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
  30. const sentinelPort = parseIntFromEnvValue(env.REDIS_SENTINEL_PORT, 26379, 'REDIS_SENTINEL_PORT');
  31. const sentinels = env.REDIS_SENTINELS.split(',').map((sentinel) => {
  32. const [host, port] = sentinel.split(':', 2);
  33. /** @type {import('ioredis').SentinelAddress} */
  34. return {
  35. host: host,
  36. port: port ?? sentinelPort,
  37. // Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
  38. // only allowing IPv4 connections:
  39. // https://github.com/redis/ioredis/issues/1576
  40. family: 0
  41. };
  42. });
  43. return {
  44. db: redisDatabase,
  45. name: env.REDIS_SENTINEL_MASTER,
  46. username: env.REDIS_USER,
  47. password: env.REDIS_PASSWORD,
  48. sentinelUsername: env.REDIS_SENTINEL_USERNAME ?? env.REDIS_USER,
  49. sentinelPassword: env.REDIS_SENTINEL_PASSWORD ?? env.REDIS_PASSWORD,
  50. sentinels,
  51. ...commonOptions,
  52. };
  53. }
  54. /**
  55. * @param {NodeJS.ProcessEnv} env the `process.env` value to read configuration from
  56. * @returns {RedisConfiguration} configuration for the Redis connection
  57. */
  58. export function configFromEnv(env) {
  59. const redisNamespace = env.REDIS_NAMESPACE;
  60. // These options apply for both REDIS_URL based connections and connections
  61. // using the other REDIS_* environment variables:
  62. const commonOptions = {
  63. // Force support for both IPv6 and IPv4, by default ioredis sets this to 4,
  64. // only allowing IPv4 connections:
  65. // https://github.com/redis/ioredis/issues/1576
  66. family: 0
  67. // Note: we don't use auto-prefixing of keys since this doesn't apply to
  68. // subscribe/unsubscribe which have "channel" instead of "key" arguments
  69. };
  70. // If we receive REDIS_URL, don't continue parsing any other REDIS_*
  71. // environment variables:
  72. if (typeof env.REDIS_URL === 'string' && env.REDIS_URL.length > 0) {
  73. return {
  74. url: env.REDIS_URL,
  75. options: commonOptions,
  76. namespace: redisNamespace
  77. };
  78. }
  79. // If we have configuration for Redis Sentinel mode, prefer that:
  80. if (hasSentinelConfiguration(env)) {
  81. return {
  82. options: getSentinelConfiguration(env, commonOptions),
  83. namespace: redisNamespace
  84. };
  85. }
  86. // Finally, handle all the other REDIS_* environment variables:
  87. let redisPort = parseIntFromEnvValue(env.REDIS_PORT, 6379, 'REDIS_PORT');
  88. let redisDatabase = parseIntFromEnvValue(env.REDIS_DB, 0, 'REDIS_DB');
  89. /** @type {import('ioredis').RedisOptions} */
  90. const options = {
  91. host: env.REDIS_HOST ?? '127.0.0.1',
  92. port: redisPort,
  93. db: redisDatabase,
  94. username: env.REDIS_USER,
  95. password: env.REDIS_PASSWORD,
  96. ...commonOptions,
  97. };
  98. return {
  99. options,
  100. namespace: redisNamespace
  101. };
  102. }
  103. /**
  104. * @param {RedisConfiguration} config
  105. * @param {import('pino').Logger} logger
  106. * @returns {Redis}
  107. */
  108. export function createClient({ url, options }, logger) {
  109. let client;
  110. if (typeof url === 'string') {
  111. client = new Redis(url, options);
  112. } else {
  113. client = new Redis(options);
  114. }
  115. client.on('error', (err) => logger.error({ err }, 'Redis Client Error!'));
  116. return client;
  117. }