Ioredis: [Feature] add typescript types in this repo

Created on 7 Feb 2020  路  16Comments  路  Source: luin/ioredis

Currently types are located in https://www.npmjs.com/package/@types/ioredis

Types ares out of sync with ioredis repo.

It would be easier to fix and maintain there.

Most helpful comment

I am confused. I must be missing something.

  • This code base seems to be done with .ts
  • But when I use it, it says that I have to install @types/ioredis which I did.
  • Then, @types/ioredis seems to be relatively off, as for example. xread(...args: ValueType[]): Array<[string, string[]]>;, which should union null as result, and even the type seems to be incomplete. For now, I do (which is not optimimum)
type StreamName = string;
type EntryId = string;
type StreamReadRaw = [StreamName, [EntryId, string[]][]][];

declare module "ioredis" {
    interface Redis {
        xread(...args: ValueType[]): StreamReadRaw | null;
    }
}

So,

a) I am not sure if @types/ioredis is generated from this project .ts, but right now it seems really off.
b) I do not know how many more apis defined in @types/ioredis are not aligned with ioredis
c) I read the #981 #issuecomment-538718567, but even having a "any" for some of those corner cases before generic can be figured out would be nice. Right now, I am not even sure how the types can be used (at last for xread)

We plan to move to ioredis from node-redis mostly because of the native promise/async/await support, but those typing issues are making us a little worried.

All 16 comments

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 7 days if no further activity occurs, but feel free to re-open a closed issue if needed.

Bumping to keep this issue alive.

I recently ran into an issue with @types/ioredis - namely that it didn't include the geo* family of functions.

Seeing as this library is written in TypeScript I hope the maintainers are open to moving types into this repo.

Here is the author's answer on why declarations are not part of this repo: https://github.com/luin/ioredis/pull/981#issuecomment-538718567

I am confused. I must be missing something.

  • This code base seems to be done with .ts
  • But when I use it, it says that I have to install @types/ioredis which I did.
  • Then, @types/ioredis seems to be relatively off, as for example. xread(...args: ValueType[]): Array<[string, string[]]>;, which should union null as result, and even the type seems to be incomplete. For now, I do (which is not optimimum)
type StreamName = string;
type EntryId = string;
type StreamReadRaw = [StreamName, [EntryId, string[]][]][];

declare module "ioredis" {
    interface Redis {
        xread(...args: ValueType[]): StreamReadRaw | null;
    }
}

So,

a) I am not sure if @types/ioredis is generated from this project .ts, but right now it seems really off.
b) I do not know how many more apis defined in @types/ioredis are not aligned with ioredis
c) I read the #981 #issuecomment-538718567, but even having a "any" for some of those corner cases before generic can be figured out would be nice. Right now, I am not even sure how the types can be used (at last for xread)

We plan to move to ioredis from node-redis mostly because of the native promise/async/await support, but those typing issues are making us a little worried.

I analyzed this issue for a few hours. I think the biggest drawback is that the used COMMAND command does not describe the return type nor does it describe the type of the input parameters. So you could say, that it is actually like the redis-cli, where you just type in string commands, which then will be interpreted and the input values be converted in redis to the needed data type.

So a automated generated typedefinition would be usually full with any types.

So a get currently is typed as:

    get(key: KeyType, callback: Callback<string | null>): void;
    get(key: KeyType): Promise<string | null>;

but in an automated generation we would have

    get(key: KeyType, callback: Callback<any>): void;
    get(key: KeyType): Promise<any>;

as we dont know the return value, because the command COMMAND does not provide the necessary information.

If we could determine, how the redis documentation page is generated, then maybe we can use the same source to write proper typings.

redis-doc is unfortunately not in a machine readable style.

but we could actually add those any typings anyway if we dont find corresponding types in the typings?

Sorry, I forgot to reply to this thread.

To add some backgrounds:

  1. Previously, ioredis was written in JavaScript, and after two years we started to migrate to TypeScript for better maintainability. @types/ioredis was created before the migration.
  2. We don't publish the declaration file intentionally because although all ioredis is now a 100% TypeScript project, most Redis command methods (e.g. redis.get) are generated in runtime, thus the method definitions won't be included in the declaration. This is the reason why @types/ioredis is still required.

As for the solution, either keeping using @types/ioredis or exposing declarations directly in ioredis repo works for me. For now, I'd think long-term we should expose declarations directly both for better developer experience and making declarations easier to maintain.

It's worth noticing that different Redis versions can have different command declarations. For example, old Redis versions only support AUTH password but in 6.x the command supports two parameters: AUTH username password. Having said that, I think the difference is minor and we can just align the declarations with the latest Redis version (or even better, we can include the version difference in the method descriptions).

redis-doc is unfortunately not in a machine readable style.

I think Redis website is generated from https://github.com/antirez/redis-doc/blob/master/commands.json, which do provide some typing infos like "SET":

  "SET": {
    "summary": "Set the string value of a key",
    "complexity": "O(1)",
    "arguments": [
      {
        "name": "key",
        "type": "key"
      },
      {
        "name": "value",
        "type": "string"
      },
      {
        "name": "expiration",
        "type": "enum",
        "enum": [
          "EX seconds",
          "PX milliseconds"
        ],
        "optional": true
      },
      {
        "name": "condition",
        "type": "enum",
        "enum": [
          "NX",
          "XX"
        ],
        "optional": true
      },
      {
        "name": "keepttl",
        "type": "enum",
        "enum": [
          "KEEPTTL"
        ],
        "optional": true
      }
    ],
    "since": "1.0.0",
    "group": "string"
  },

There are integers, strings, and even enums. However, I can see there are some challenges in mapping them to TypeScript declarations.

Anyway, PRs are welcome for adding more types!

@luin

I would still recommend to have a "complete" list of operation set. So I wanted to work now on an extended typing generation. It would be like this:

  1. use redis-commands like currently used to get the command of the latest redis server
  2. create any typed methods like described above and use declaration merging of typescript so we can add another namespace like Pipeline and thus resulting, that we have next to the current typings also any typings.

  3. "attach" the new namespaces to the current index.d.ts file.

Now you have all methods of redis.

Oha!

I think I can make from that commands.js proper typings!

Btw, the @types/ioredis seems to have the xread and xreadgroup typing wrong. See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/44301

Also, other types are relatively hard to work with. For example, xadd does not support simple variadic, and it made our code impossible to type. I had to add xadd(key: string, ...args: ValueType[]): Promise<string>.

And to make things even more complicated, now, overriding the IORedis.Redis type is not possible since the last few versions, so, you you have to something like.

export interface IORedisFixedType extends Omit<IORedis.Redis, 'xread' | 'xreadgroup' | 'xgroup' | 'xadd' | 'pipeline'> {
    xread: (...args: ValueType[]) => Promise<StreamReadRaw | null>;
    xreadgroup(...args: ValueType[]): Promise<StreamReadRaw>;
    xgroup(...args: ValueType[]): Promise<Ok | number>;
    xadd(key: string, ...args: ValueType[]): Promise<string>

    pipeline(): PipelineFixedType
}

Anyway, all of these changes work great in our redstream redis stream library but it would be nice not just uses the ioredis correct types.

Wrote a little PoC

https://github.com/Uzlopak/ioredis-types

the generated.d.ts still contains some errors but is there anybody interested on collaborate on the generation of the right typings?

I think following steps have to be done to have a productive state:

  • [ ] generate all variants of commands, as optional parameters should be not using the ? notation
  • [ ] write methods to determine if a return value is maybe "OK" and other types (boolean)
  • [ ] use redis-commands package for aridity value or multiple-field in commands.json of redis-doc to determine if a field can be used multple times
  • [ ] develop a strategy to handle multiple key and property fields as they could be using the same name so that we avoid collisions. simplest strategy is to add a number after the duplicated fieldname.
  • [ ] handle subcommands correctly, e.g. ACL AUTH

nice to have

  • [ ] use generic type for Redis to inject the right version of used redis server so that you have on compile time the right set of commands, e.g. const redis = new Redis<"6.0.0">()

@jeremychone

Btw, the @types/ioredis seems to have the xread and xreadgroup typing wrong. See DefinitelyTyped/DefinitelyTyped#44301

Also, other types are relatively hard to work with. For example, xadd does not support simple variadic, and it made our code impossible to type. I had to add xadd(key: string, ...args: ValueType[]): Promise<string>.

And to make things even more complicated, now, overriding the IORedis.Redis type is not possible since the last few versions, so, you you have to something like.

export interface IORedisFixedType extends Omit<IORedis.Redis, 'xread' | 'xreadgroup' | 'xgroup' | 'xadd' | 'pipeline'> {
  xread: (...args: ValueType[]) => Promise<StreamReadRaw | null>;
  xreadgroup(...args: ValueType[]): Promise<StreamReadRaw>;
  xgroup(...args: ValueType[]): Promise<Ok | number>;
  xadd(key: string, ...args: ValueType[]): Promise<string>

  pipeline(): PipelineFixedType
}

Anyway, all of these changes work great in our redstream redis stream library but it would be nice not just uses the ioredis correct types.

Not only that, but 'hset' is not variadic as well.
I've just started using ioredis and already got typing problems.
There should be a meme somewhere with "TypeScript Type Definitions: Expectation | Reality" :smile:.

Besides jokes, ioredis is the only package that supports Redis Sentinels (if memory doesn't fail), and having proper typing is very critical.

@Uzlopak

use generic type for Redis to inject the right version of used redis server so that you have on compile time the right set of commands, e.g. const redis = new Redis<"6.0.0">()

Is Redis using proper semantic versioning?

If they do, all commands should be backward-compatible right?
So we can actually pre-define major Redis versions and use them as

import { v6 as Redis } from 'ioredis'

const redis = new Redis(/*...*/)
/*...*/

@luin thanks for the context.

IMO, while code-gen the types could be good to get started, it will eventually need to hand-tuned and maintained for better developer experience.

One example of fine-tuning would be xread can return null, whereas xreadgroup cannot. We can try to express that in another definition file, but at some point there are diminishing return, moreover of APIs that relatively static anyway.

An alternative would be to use the type of the source code, and for the methods that are code gen, generate the TypeScript version of the code. This might not help the nuance above, but at least, it will keep types closer to implementation, which always help accuracy in Typescript.

Those are just some thoughts. Unfortunately, I swamped these days, and we are mostly using stream API, which I have already wrapped. If/when we use more of redis API, we might contribute in some ways.

Was this page helpful?
0 / 5 - 0 ratings