CacheModule.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import config from "config";
  2. import {
  3. RedisClientOptions,
  4. RedisClientType,
  5. RedisDefaultModules,
  6. RedisFunctions,
  7. RedisModules,
  8. RedisScripts,
  9. createClient
  10. } from "redis";
  11. import { forEachIn } from "@common/utils/forEachIn";
  12. import BaseModule, { ModuleStatus } from "@/BaseModule";
  13. export class CacheModule extends BaseModule {
  14. private _redisClient?: RedisClientType<
  15. RedisDefaultModules & RedisModules,
  16. RedisFunctions,
  17. RedisScripts
  18. >;
  19. /**
  20. * Cache Module
  21. */
  22. public constructor() {
  23. super("cache");
  24. }
  25. /**
  26. * startup - Startup cache module
  27. */
  28. public override async startup() {
  29. await super.startup();
  30. this._redisClient = createClient({
  31. ...config.get<RedisClientOptions>("redis"),
  32. socket: {
  33. reconnectStrategy: (retries: number, error) => {
  34. if (
  35. retries >= 10 ||
  36. ![ModuleStatus.STARTING, ModuleStatus.STARTED].includes(
  37. this.getStatus()
  38. )
  39. )
  40. return false;
  41. this.log({
  42. type: "debug",
  43. message: `Redis reconnect attempt ${retries}`,
  44. data: error
  45. });
  46. return Math.min(retries * 50, 500);
  47. }
  48. }
  49. });
  50. this._redisClient.on("error", error => {
  51. this.log({ type: "error", message: error.message, data: error });
  52. this.setStatus(ModuleStatus.ERROR);
  53. });
  54. this._redisClient.on("ready", () => {
  55. this.log({ type: "debug", message: "Redis connection ready" });
  56. if (this.getStatus() === ModuleStatus.ERROR)
  57. this.setStatus(ModuleStatus.STARTED);
  58. });
  59. await this._redisClient.connect();
  60. const redisConfigResponse = await this._redisClient.sendCommand([
  61. "CONFIG",
  62. "GET",
  63. "notify-keyspace-events"
  64. ]);
  65. if (
  66. !(
  67. Array.isArray(redisConfigResponse) &&
  68. redisConfigResponse[1] === "xE"
  69. )
  70. )
  71. throw new Error(
  72. `notify-keyspace-events is NOT configured correctly! It is set to: ${
  73. (Array.isArray(redisConfigResponse) &&
  74. redisConfigResponse[1]) ||
  75. "unknown"
  76. }`
  77. );
  78. await super._started();
  79. }
  80. /**
  81. * shutdown - Shutdown cache module
  82. */
  83. public override async shutdown() {
  84. await super.shutdown();
  85. if (this._redisClient) await this._redisClient.quit();
  86. await this._stopped();
  87. }
  88. public canRunJobs(): boolean {
  89. return this._redisClient?.isReady === true && super.canRunJobs();
  90. }
  91. public async getKeys(pattern: string) {
  92. return this._redisClient!.KEYS(pattern);
  93. }
  94. public async get(key: string) {
  95. const value = await this._redisClient!.GET(key);
  96. return value === null ? null : JSON.parse(value);
  97. }
  98. public async set(key: string, value: any, ttl?: number) {
  99. await this._redisClient!.SET(key, JSON.stringify(value), { EX: ttl });
  100. }
  101. public async remove(key: string) {
  102. await this._redisClient!.DEL(key);
  103. }
  104. public async removeMany(keys: string | string[]) {
  105. await forEachIn(Array.isArray(keys) ? keys : [keys], async pattern => {
  106. for await (const key of this._redisClient!.scanIterator({
  107. MATCH: pattern
  108. })) {
  109. await this.remove(key);
  110. }
  111. });
  112. }
  113. public async getTtl(key: string) {
  114. return this._redisClient!.TTL(key);
  115. }
  116. public async getTable(key: string) {
  117. return this._redisClient!.HGETALL(key);
  118. }
  119. public async getTableItem(table: string, key: string) {
  120. return this._redisClient!.HGET(table, key);
  121. }
  122. public async setTableItem(table: string, key: string, value: any) {
  123. return this._redisClient!.HSET(table, key, value);
  124. }
  125. public async removeTableItem(table: string, key: string) {
  126. return this._redisClient!.HDEL(table, key);
  127. }
  128. }
  129. export default new CacheModule();