lisaogren / axios-cache-adapter

Caching adapter for axios. Store request results in a configurable store to prevent unneeded network requests.
MIT License
725 stars 108 forks source link

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

Open OiYouYeahYou opened 2 years ago

OiYouYeahYou commented 2 years ago

The code example Use redis as cache store when configured correctly

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 commented 2 years ago

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

timminss commented 2 years ago

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)
OiYouYeahYou commented 2 years ago

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

mingfunwong commented 2 years ago

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;