node-redis icon indicating copy to clipboard operation
node-redis copied to clipboard

Migration Guide and Breaking Changes is not full enough

Open npdev453 opened this issue 4 years ago • 14 comments

  • [X] 1) Many methods now is not available by old names (with legacyMode also too)

So, only way for now (without modifying thousands of files) is patch client object like this, or remap keys with .toLowerCase():

    client.hgetall = client.hGetAll;
    client.hget = client.hGet;
    client.hset = client.hSet;
    client.hmset = client.hSet;
    client.setex = client.setEx;
    ...
  • [x] 2) hSet does not allow objects as second parameter anymore
 client.hSet('key', {a: 1, b:2}, callback); 

< ./node_modules/@node-redis/client/dist/lib/commander.js:72
        yield `$${byteLength.toString()}${DELIMITER}`;
                             ^

TypeError: Cannot read properties of undefined (reading 'toString')
    at encodeCommand (./node_modules/@node-redis/client/dist/lib/commander.js:72:30)
    at encodeCommand.next (<anonymous>)
    at RedisSocket.writeCommand (./node_modules/@node-redis/client/dist/lib/client/socket.js:56:20)
    at Commander._RedisClient_tick (./node_modules/@node-redis/client/dist/lib/client/index.js:415:64)
    at Commander._RedisClient_sendCommand (./node_modules/@node-redis/client/dist/lib/client/index.js:396:82)
    at Commander.sendCommand (./node_modules/@node-redis/client/dist/lib/client/index.js:349:93)
    at Commander.<computed> (./node_modules/@node-redis/client/dist/lib/client/index.js:383:14)

But previously, on 3.x its works:

client.hmset('key', {a: 1, b: 2}, callback); 

< HMSET key a 1 b 2

Functionality hiddenly broken, and no alternative provided.

Current solution is writing a wrap for hmset like this:

client.hmset = (hkey, parms, callback) => {
    client.hSet(hkey, Object.entries(parms).flat(), callback)
}
  • [ ] 3) Connection errors did not crash process with uncatched errors anymore. Strange thing, but seems like something changed in error policy of this module. I haven't figured it out yet, but when connection is lost, algorithm simply stop on redis commandSending forever.

  • [ ] 4) (Bonus) Types for legacyMode and callbacks is not provided, but in 3.x its does, and for 2.x too: https://www.npmjs.com/package/@types/redis,
    Dirty way for now is patch node_modules/@node-redis/client/dist/lib/client/index.d.ts and add it to WithCommands type
export declare type RedisClientCommandSignatureLegacy<C extends RedisCommand> = (...args: [...options: Parameters<C['transformArguments']>, callback: (e: Error | null, result: RedisCommandReply<C>) => void]) => void

Also, I think that users can additionally import something like LegacyTypesHelper module that will override signatures until legacyMode enabled.


  • [ ] 5) Importing of { RedisClient } from "redis" now is not available anymore. Current solution is craft it by yourself: (but it seems like a crutch)
import { createClient } from 'redis'
type RedisClient = ReturnType<typeof createClient>

But I think develops can provide more useful type with generics, but not delete it at all. Many things, like cache adapters, models or abstract classes require that type. Also, will be useful, if develop may declare that code expected pure RedisClient or RedisClient with RediJson module.

Anyway it should be described in migration guide.

  • [ ] 6) Another thing for the migration guide. SCAN args have changed, requiring client.SCAN(cursor, { MATCH: keyPattern, COUNT: pageSize }) pageSize is a Number. It used to take client.SCAN(cursorString, 'MATCH', keyPattern, 'COUNT', pageSize.toString()).

    And the response has changed. Instead of an array of strings [<cursor>, <keys>], it now returns a structure {cursor: <number>, keys: <keys>}. (thx for reporting @jimsnab)


Finally, I should say that is very sad example of "how modules shouldn't make breaking updates", especially of so popular module. Feels like a spit into consumers.

npdev453 avatar Dec 06 '21 00:12 npdev453

I agree with you, and I have encountered the same first problem as you these days right after my pr.

To be honest, I think that as a package with millions of downloads every week, if developers want to release a new major version, they should complete the change log and migration guide before proceeding. Because this will cause a big impact, but only developers can quickly find all changes, it is difficult for us as users to find all changes.

AnnAngela avatar Dec 06 '21 14:12 AnnAngela

I maintain the connect-redis package. v4 is breaking enough that it is no longer interoperable with packages like ioredis and redis-mock (which are also both supported). I thought using legacyMode would allow a period for connect-redis users to migrate and support both Redis v3 or v4 as well the other redis clients but it doesn't really work as this case points out, there are methods missing (.e.g. mget) and signatures are incomplete. At this point I think we have to support and recommend v3 until we have something more interoperable. Are there plans to flesh out legacyMode further?

wavded avatar Dec 07 '21 03:12 wavded

Also doesn't mention:

  • What happened to import { RedisClient }

ebebbington avatar Dec 07 '21 11:12 ebebbington

Also doesn't mention:

  • What happened to import { RedisClient }

Thanks, forgot about it. Already added to the list

npdev453 avatar Dec 07 '21 14:12 npdev453

The migration guide should explain how to modify RedisClient for purposes of unit test mocks.

(Was it really necessary to prevent a RedisClient object from being modified or extended?)

jimsnab avatar Dec 07 '21 19:12 jimsnab

Another thing for the migration guide. SCAN args have changed, requiring client.SCAN(cursor, { MATCH: keyPattern, COUNT: pageSize }), pageSize is a Number. It used to take client.SCAN(cursorString, 'MATCH', keyPattern, 'COUNT', pageSize.toString()).

And the response has changed. Instead of an array of strings [<cursor>, <keys>], it now returns a structure {cursor: <number>, keys: <keys>}.

jimsnab avatar Dec 07 '21 22:12 jimsnab

The migration guide should explain how to modify RedisClient for purposes of unit test mocks.

(Was it really necessary to prevent a RedisClient object from being modified or extended?)

Could you please explain in details? You are free to modify client object after calling const client = createClient();

npdev453 avatar Dec 07 '21 22:12 npdev453

My unit tests had class MockRedisClient extends redis.RedisClient {.

This has to be completely reworked.

jimsnab avatar Dec 07 '21 23:12 jimsnab

Anyone know where can I find the old V3 docs? While the changelog and migration guide are very helpful at telling me how things work in the new version compared to the previous one, I'm having trouble finding the documentation on what hasn't changed. There are a lot of gaps to fill if you aren't someone who is migrating to V4 but instead are starting off with V4.

Also the docs here need to be updated or - probably better - clearly identified as relevant to V3 - I've only been tinkering the repo for a couple hours so far, and almost every example there that I had to use is now out of date. 😄

jayeclark avatar Dec 13 '21 21:12 jayeclark

@jayeclark the old docs can be found in the v3.1 branch. I'll make sure that https://docs.redis.com/latest/rs/references/client_references/client_nodejs/ will be updated. If you can prepare a list of what is missing in the docs it'll be very helpful.

leibale avatar Dec 13 '21 22:12 leibale

@leibale oh, that makes perfect sense - I can’t believe I didn’t think to look there. 🤦 Sure, I can make a list as I go through - may not be comprehensive but it should be a good start especially for newer users.

jayeclark avatar Dec 13 '21 22:12 jayeclark

Using this workaround for expressing the client type:

type RedisClient = ReturnType<typeof createClient>;

does not seem to give a very good type. It's sort of like it treats nonexistent commands as any. Here's a Typescript playground to demonstrate the issue.

OxleyS avatar Jan 11 '22 12:01 OxleyS

Using this workaround for expressing the client type:

type RedisClient = ReturnType<typeof createClient>;

does not seem to give a very good type. It's sort of like it treats nonexistent commands as any. Here's a Typescript playground to demonstrate the issue.

I was having trouble finding the type of the client and this helped me solve my problem, but it shouldn't be the way to go, rather there should be a clean type client that can be imported and used.

andrefedev avatar Jan 23 '22 02:01 andrefedev

Switched to v4, now all the values must be strings. Type boolean throws an error...

bschelling avatar Mar 31 '25 09:03 bschelling