Skip to content

feat: Add Store.redis() for standard Redis/ioredis/Valkey clients #208

@tenequm

Description

@tenequm

Problem

Store.upstash() works because the Upstash REST SDK auto-serializes JSON (including BigInt) on their end. Standard Redis clients (ioredis, node-redis, Valkey) only accept strings, so passing channel state with BigInt fields directly to client.set() either throws or corrupts data.

Anyone running self-hosted Redis/Valkey (common in Kubernetes deployments) has to write a custom store adapter with BigInt-safe serialization:

const BIGINT_SUFFIX = "#__bigint";

function createRedisStore(redis: IoRedis): Store {
  return Store.from({
    async get(key) {
      const raw = await redis.get(key);
      if (raw == null) return null;
      return JSON.parse(raw, (_, v) =>
        typeof v === "string" && v.endsWith(BIGINT_SUFFIX)
          ? BigInt(v.slice(0, -BIGINT_SUFFIX.length))
          : v,
      );
    },
    async put(key, value) {
      await redis.set(
        key,
        JSON.stringify(value, (_, v) =>
          typeof v === "bigint" ? \`\${v}\${BIGINT_SUFFIX}\` : v,
        ),
      );
    },
    async delete(key) {
      await redis.del(key);
    },
  });
}

This is error-prone and every self-hosted user will rediscover the BigInt serialization issue independently.

Proposal

Add Store.redis() that accepts any client with standard get/set/del string methods and handles #__bigint serialization internally (using ox's Json.parse/Json.stringify, same as Store.cloudflare() and Store.memory() already do):

import { Store } from "mppx/server";
import Redis from "ioredis";

const redis = new Redis("redis://localhost:6379");
const store = Store.redis(redis);

// Works with any client that has get/set/del string methods:
// - ioredis
// - node-redis (@redis/client)
// - Valkey

Why not extend Store.upstash()?

  • upstash is named after a specific product - using it for generic Redis would be confusing
  • The serialization behavior is fundamentally different (Upstash auto-serializes, standard clients don't)
  • A separate Store.redis() is additive with no breaking changes

Interface

export function redis(client: redis.Parameters): Store

export declare namespace redis {
  type Parameters = {
    get: (key: string) => Promise<string | null>
    set: (key: string, value: string) => Promise<unknown>
    del: (key: string) => Promise<unknown>
  }
}

This covers ioredis, node-redis, and any Redis-compatible client without requiring a specific package dependency.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions