axios-cache-adapter icon indicating copy to clipboard operation
axios-cache-adapter copied to clipboard

Code in documentation fails: `Use redis as cache store`

Open OiYouYeahYou opened this issue 3 years ago • 4 comments

The code example Use redis as cache store when configured correctly

  • Node: 16.6.1
  • axios-cache-adapter: 2.7.3
  • redis: 6
  • Ubuntu: 20.04

The error stems from this code: https://github.com/RasCarlito/axios-cache-adapter/blob/2d51cee4070ff88f2272533f9593fd41a392f52c/src/redis.js#L7-L17

Could this assertion be removed to allow more flexibility and reduce the chance future bugs based on minor version bump changes of variable names?

OiYouYeahYou avatar Dec 08 '21 22:12 OiYouYeahYou

So digging into the issue more, and the constructor name is Commander instead of RedisClient

OiYouYeahYou avatar Dec 09 '21 00:12 OiYouYeahYou

It looks as though node-redis had a major version bump recently, causing this issue. A couple of options:

  1. Use node-redis v3
  2. Write your own version of RedisStore, using a redis client of your choosing (ioredis is another option)

timminss avatar Dec 20 '21 15:12 timminss

@timminss, Is this really a solution though? Would a better solution to be to remove the assertion or to update the documentation?

OiYouYeahYou avatar Dec 23 '21 17:12 OiYouYeahYou

It's adapter for radis 4.0.4 You can replace import { RedisDefaultStore } from 'axios-cache-adapter' with import RedisDefaultStore from './RedisDefaultStore';.

RedisDefaultStore.ts

import { RedisClientType } from 'redis';
import { RedisDefaultOptions } from 'axios-cache-adapter';

interface IEntry {
  expires: number;
  data: string;
}

class RedisDefaultStore {
  private client: RedisClientType;
  private prefix: string | any;
  private maxScanCount: number;
  private getAsync: any;
  private psetexAsync: any;
  private delAsync: any;
  private scanAsync: any;

  constructor(
    client: RedisClientType | any,
    options: RedisDefaultOptions = {},
  ) {
    client.connect();
    this.client = client;
    this.prefix = options.prefix || 'axios-cache';
    this.maxScanCount = options.maxScanCount || 1000;
    this.getAsync = client.get.bind(client);
    this.psetexAsync = client.set.bind(client);
    this.delAsync = client.del.bind(client);
    this.scanAsync = client.scan.bind(client);
  }

  calculateTTL(value: IEntry) {
    const now = Date.now();

    if (value.expires && value.expires > now) {
      return value.expires - now;
    }

    return -1;
  }

  transformKey(key: string) {
    return this.prefix + '_' + key;
  }

  async getItem(key: string) {
    const item = (await this.getAsync(this.transformKey(key))) || null;

    return JSON.parse(item);
  }

  async setItem(key: string, value: IEntry) {
    const computedKey = this.transformKey(key);

    const ttl = this.calculateTTL(value);

    if (ttl > 0) {
      await this.psetexAsync(computedKey, JSON.stringify(value), { EX: ttl });
    }

    return value;
  }

  async removeItem(key: string) {
    await this.delAsync(this.transformKey(key));
  }

  async scan(operation) {
    let cursor = '0';

    do {
      const reply = await this.scanAsync(
        cursor,
        'MATCH',
        this.transformKey('*'),
        'COUNT',
        this.maxScanCount,
      );

      cursor = reply[0];

      await operation(reply[1]);
    } while (cursor !== '0');
  }

  async clear() {
    await this.scan((keys) => this.delAsync(keys));
  }

  async length() {
    let length = 0;

    await this.scan((keys) => {
      length += keys.length;
    });

    return length;
  }

  async iterate(fn) {
    async function runFunction(key) {
      const item = (await this.getAsync(key)) || null;

      const value = JSON.parse(item);

      return await fn(value, key);
    }

    await this.scan((keys) => Promise.all(keys.map(runFunction.bind(this))));

    return Promise.resolve([]);
  }
}

export default RedisDefaultStore;

mingfunwong avatar Mar 01 '22 12:03 mingfunwong