Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ioredis compatibility #115

Open
bkniffler opened this issue Jul 22, 2024 · 13 comments
Open

ioredis compatibility #115

bkniffler opened this issue Jul 22, 2024 · 13 comments

Comments

@bkniffler
Copy link

Any chance we could get this ioredis compatible? Would like to use upstash via ioredis to remove http overhead, and ioredis is one of the recommended clients for upstash, but its not compatible with ratelimit-js.

@ogzhanolguncu
Copy link
Contributor

ogzhanolguncu commented Jul 23, 2024

Totally doable, but our main focus on serverless environments. Maybe we can let people use ioredis and/or redis for serverful environments. I'll discuss this with the team.

Edit:
For now you can do the following -> #60

@sourabpramanik
Copy link
Contributor

@ogzhanolguncu If the team is moving forward with this integration, I would like to work on it.

@bkniffler
Copy link
Author

@ogzhanolguncu the linked solution does not work, up stash rate limit depends on more functions by now.
hset, evalsha, scriptLoad, smismember, multi

Bildschirmfoto 2024-07-26 um 11 16 44

using ioredis for these methods seems to not work either out of the box, think the adapter needs more effort to be made working. would love this to be part of ratelimit-js, then at least its clear when compatibility breaks.

@bkniffler
Copy link
Author

Any chance this is going to land sometime?

@ogzhanolguncu
Copy link
Contributor

Sorry to say that, but team don't wanna move forward with that approach for now. But, we can't say you guys can't come up with an adapter 😉 If you know what I mean. So feel free to @sourabpramanik or @bkniffler make your own adapters.

@sourabpramanik
Copy link
Contributor

Gotcha @ogzhanolguncu

@ogzhanolguncu
Copy link
Contributor

If you have a working solution make sure to put it here so other people can use it.

@LouisHaftmann
Copy link

I got it working with this but it has a lot of type assertions which is not ideal:

import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from 'ioredis'

const redis = new Redis(`redis://localhost:6379`)
const limiter = new Ratelimit({
  redis: {
    sadd: async <TData>(key: string, ...members: TData[]) =>
      redis.sadd(key, ...members.map(String)),
    eval: async <TArgs extends unknown[], TData = unknown>(
      script: string,
      keys: string[],
      args: TArgs,
    ) => redis.eval(script, keys.length, ...keys, ...(args ?? []).map(String)) as Promise<TData>,
    scriptLoad: async (script: string) => redis.script('LOAD', script) as Promise<string>,
    smismember: async (key: string, members: unknown[]) =>
      redis.smismember(key, ...(members as string[])) as Promise<(0 | 1)[]>,
    evalsha: async <TData>(sha1: string, keys: string[], args: unknown[]) =>
      redis.evalsha(sha1, keys.length, ...keys, ...(args as string[])) as TData,
    hset: async <TData>(key: string, kv: Record<string, TData>) => redis.hset(key, kv),
  } as any,
  // ...
})

@yarabramasta
Copy link

I got it working with this but it has a lot of type assertions which is not ideal:

import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from 'ioredis'

const redis = new Redis(`redis://localhost:6379`)
const limiter = new Ratelimit({
  redis: {
    sadd: async <TData>(key: string, ...members: TData[]) =>
      redis.sadd(key, ...members.map(String)),
    eval: async <TArgs extends unknown[], TData = unknown>(
      script: string,
      keys: string[],
      args: TArgs,
    ) => redis.eval(script, keys.length, ...keys, ...(args ?? []).map(String)) as Promise<TData>,
    scriptLoad: async (script: string) => redis.script('LOAD', script) as Promise<string>,
    smismember: async (key: string, members: unknown[]) =>
      redis.smismember(key, ...(members as string[])) as Promise<(0 | 1)[]>,
    evalsha: async <TData>(sha1: string, keys: string[], args: unknown[]) =>
      redis.evalsha(sha1, keys.length, ...keys, ...(args as string[])) as TData,
    hset: async <TData>(key: string, kv: Record<string, TData>) => redis.hset(key, kv),
  } as any,
  // ...
})

which version do you use?
im using "@upstash/ratelimit": "^2.0.5" and now demanding only get and set

type Redis = Pick<Redis$1, "get" | "set">;
type RegionRatelimitConfig = {
    /**
     * Instance of `@upstash/redis`
     * @see https://github.com/upstash/upstash-redis#quick-start
     */
    redis: Redis;
    ...
}

@LouisHaftmann
Copy link

I tried it with just get and set but it actually uses the functions from the old type. The type is just wrong.

@CahidArda
Copy link
Contributor

For simple usage, the old type is sufficient.

But we added opt-in features later which required other methods too. Features such as traffic protection.

@jasonviipers
Copy link

jasonviipers commented Mar 6, 2025

To make @upstash/ratelimit work with ioredis, We create a custom Redis object that wraps ioredis methods to match the interface @upstash/ratelimit expects.

import { Ratelimit } from '@upstash/ratelimit';
import Redis from 'ioredis';

// Define the Redis interface expected by @upstash/ratelimit
interface RatelimitRedis {
  get: (key: string) => Promise<string | null>;
  set: (key: string, value: string, options?: { ex?: number }) => Promise<unknown>;
  sadd: <TData>(key: string, ...members: TData[]) => Promise<number>;
  eval: <TArgs extends unknown[], TData = unknown>(
    script: string,
    keys: string[],
    args: TArgs
  ) => Promise<TData>;
  scriptLoad: (script: string) => Promise<string>;
  smismember: (key: string, members: unknown[]) => Promise<(0 | 1)[]>;
  evalsha: <TData>(sha1: string, keys: string[], args: unknown[]) => Promise<TData>;
  hset: <TData>(key: string, kv: Record<string, TData>) => Promise<number>;
}

// Create an ioredis instance
const ioredis = new Redis('redis://localhost:6379');

// Implement the custom Redis adapter
const customRedis: RatelimitRedis = {
  get: async (key: string) => ioredis.get(key),
  set: async (key: string, value: string, options?: { ex?: number }) => {
    if (options?.ex) {
      return ioredis.set(key, value, 'EX', options.ex);
    }
    return ioredis.set(key, value);
  },
  sadd: async <TData>(key: string, ...members: TData[]) =>
    ioredis.sadd(key, ...members.map(String)),
  eval: async <TArgs extends unknown[], TData = unknown>(
    script: string,
    keys: string[],
    args: TArgs
  ) => ioredis.eval(script, keys.length, ...keys, ...(args ?? []).map(String)) as Promise<TData>,
  scriptLoad: async (script: string) => ioredis.script('LOAD', script),
  smismember: async (key: string, members: unknown[]) =>
    ioredis.smismember(key, ...(members as string[])) as Promise<(0 | 1)[]>,
  evalsha: async <TData>(sha1: string, keys: string[], args: unknown[]) =>
    ioredis.evalsha(sha1, keys.length, ...keys, ...(args as string[])) as Promise<TData>,
  hset: async <TData>(key: string, kv: Record<string, TData>) => ioredis.hset(key, kv),
};

// Initialize Ratelimit with the custom adapter
const limiter = new Ratelimit({
  redis: customRedis,
  limiter: Ratelimit.slidingWindow(10, '10 s'), // Example configuration
  // Add other options as needed
});

// Example usage
async function testLimiter() {
  const { success } = await limiter.limit('user_123');
  console.log(success ? 'Request allowed' : 'Request blocked');
}

testLimiter();

@dy0gu
Copy link

dy0gu commented Apr 6, 2025

On this topic, would it be feasible to make @upstash/core-analytics a peer or optional dependency of this package?

Currently, when integrating @upstash/ratelimit with ioredis, the @upstash/redis package is still installed as a transitive dependency, which is a bit of a hassle. This occurs despite it being listed only as a peer dependency in this package, due to @upstash/core-analytics declaring it as a regular dependency downstream.

If the analytics integration is not tightly coupled with the core functionality of this package, it would be beneficial to allow us to opt out of it, particularly for use cases that do not require analytics and want to avoid unnecessary dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants