redis / node-redis

Redis Node.js client
https://redis.js.org/
MIT License
16.93k stars 1.89k forks source link

RedisClientType not compatible across @redis/client versions #2556

Open marwankhalili opened 1 year ago

marwankhalili commented 1 year ago

Description

Hi!

I'm trying to write a utility package for my microservices. Part of the abstraction is related to data stored in Redis‚ so I thought my utility package could reuse the microservice's client connection:

// Utility package - Installs redis as peerDependency
import type { RedisClientType } from "redis";

async function foo(redisClient: RedisClientType) {
  // Use redis client provided by the microservice
}

This utility package worked fine in one microservice, but caused a TypeScript error in another. I noticed the problem occurs when the internal @redis/client version differs between the utility package and the microservice.

Minimal reproducible example:

import type { RedisClientType as ClientA } from "@redis/client-a"; // @redis/client@1.5.7
import type { RedisClientType as ClientB } from "@redis/client-b"; // @redis/client@1.5.6

let clientA: ClientA;
let clientB: ClientB;

clientA = clientB; // TypeScript error:  Property '#private' is missing in type 'RedisClient<Record<string, never>, Record<string, never>, Record<string, never>> & WithCommands & WithModules<Record<string, never>> & WithFunctions<...> & WithScripts<...>' but required in type 'RedisClient<Record<string, never>, Record<string, never>, Record<string, never>>'.ts(2322)

package.json:

{
  "dependencies": {
    "@redis/client-a": "npm:@redis/client@1.5.7",
    "@redis/client-b": "npm:@redis/client@1.5.6",
    "typescript": "5.1.6"
  }
}

The TypeScript compatibility issue is partially due to the "private" class fields in the declaration files (see https://github.com/microsoft/TypeScript/issues/18499) but also due to the unique symbol used in CommandOptions<T>. The example above only works if I remove both from the declaration files of @redis/client.

Node Redis Version

4.6.6

leibale commented 1 year ago

We are changing the command options API in v5, which removes the CommanOptions<T> type, but anyway, just move the @redis/client from dependencies to peerDependencies in your utility project, and don't lock it to a specific version (this way you'll be able to install the utility package + whatever version of @redis/client you need).

marwankhalili commented 1 year ago

Cool I hope this can be resolved in v5!

I tried the peerDependencies-route initially (first code example) but the problem is that I'm working on a monorepo. Relying on @redis/client being hoisted won't work since various versions of the library is used across services.

I published an example repository to illustrate this: https://github.com/marwankhalili/redis-utility-example

packages/service-a installs redis@4.6.6 -> @redis/client@1.5.7 packages/service-b installs redis@4.6.5 -> @redis/client@1.5.6

marwankhalili commented 1 year ago

Rethinking this, the root issue might be that redis has specified exact versions of internal @redis libraries:

https://github.com/redis/node-redis/blob/a7d5bc7ca4da6cde66140ae4fea96a8ea6e7ce64/package.json#L25-L32

I believe this issue could be resolved if the caret (^) semver range specifier was used instead, but perhaps there's a reason why that isn't being used?