CacheModule.ts 3.5 KB

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