Skip to content

feat: add COMMAND DOCS support - #3362

Merged
nkaradzhov merged 3 commits into
redis:masterfrom
Hashim1999164:feat/command-docs
Jul 30, 2026
Merged

feat: add COMMAND DOCS support#3362
nkaradzhov merged 3 commits into
redis:masterfrom
Hashim1999164:feat/command-docs

Conversation

@Hashim1999164

@Hashim1999164 Hashim1999164 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds COMMAND DOCS (client.commandDocs) for Redis 7+, including optional command name filters.
  • Transforms RESP2 flat map replies into the RESP3 object shape, including nested arguments and recursive subcommands.
  • Registers the command in the client command index with JSDoc (@since 7.0.0).

Fixes #1934

Why previous attempts were not enough

Test plan

  • parseArgs covers no args, string, and array filters
  • Unit test for RESP2 transformReply[2] nested map/arguments/subcommands
  • Integration: client.commandDocs() and client.commandDocs(['GET', 'SET']) against Redis 7+
  • npm run check:command-jsdoc and lint on changed files

Note

Low Risk
Read-only server introspection with localized reply parsing; no auth, data, or connection behavior changes.

Overview
Adds COMMAND DOCS as client.commandDocs / COMMAND_DOCS (Redis 7+), with optional variadic command name filters.

The implementation builds COMMAND DOCS via parseCommand, marks the command read-only, and registers it in the command index with JSDoc (@since 7.0.0). For RESP2, a dedicated transformReply[2] turns flat map arrays into the same nested object shape as RESP3—recursive arguments, subcommands, and withTypeMapping support for plain objects vs Map/Array. RESP3 relies on the default map decoder (transformReply[3] is unset).

Tests cover argument parsing, RESP2 reply transformation, integration against Redis 7+, filtered queries, and Map type mapping on RESP2.

Reviewed by Cursor Bugbot for commit 12dbaa1. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f94d60805a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
},
transformReply: {
2: (reply: UnwrapReply<Resp2Reply<CommandDocsReply>>) => transformDocsMap(reply),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor map type mappings in the RESP2 transform

When an RESP2 client uses withTypeMapping({ [RESP_TYPES.MAP]: Map }) or maps maps to Array, the public return type is adjusted to that representation, but this transform ignores the supplied typeMapping argument and always converts the top-level reply to a plain object. Code that follows the declared type, such as calling docs.get('get'), therefore fails at runtime; pass the mapping through the recursive transforms and produce the requested representation.

Useful? React with 👍 / 👎.

deprecated_since?: BlobStringReply;
replaced_by?: BlobStringReply;
history?: ArrayReply<TuplesReply<[BlobStringReply, BlobStringReply]>>;
arguments?: ArrayReply<ReplyUnion>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose transformed arguments as CommandDocsArgument

For TypeScript consumers, each docs.get.arguments entry is currently inferred as the broad ReplyUnion, so ordinary access such as docs.get.arguments?.[0].name is rejected even though transformArgument returns a CommandDocsArgument object and the integration test uses that shape. Type this field as an array of CommandDocsArgument entries, and make the same correction to the recursive CommandDocsArgument.arguments field.

Useful? React with 👍 / 👎.

@nkaradzhov nkaradzhov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hashim1999164 thanks for the PR!

Three points before merge.

First, the RESP2 transformReply should honor the typeMapping parameter (see transformTuplesReply and CONFIG_GET) so users mapping MAP to Map or Array get consistent results across protocols.

Second, please add an integration test running commandDocs against a real Redis 7 server with RESP 2 client options — the nested transform is currently covered only by a synthetic fixture, which is exactly where the previous PR broke.

Third, drop the NOT_KEYED_COMMAND flag, it has no runtime consumer and the other COMMAND family commands do not set it.

Address review feedback: preserve Map/Array type mappings in the RESP2
transform, and type nested arguments as CommandDocsArgument.
@Hashim1999164

Copy link
Copy Markdown
Contributor Author

Addressed the Codex review notes:

  1. RESP2 transformReply now accepts typeMapping and returns Map / flat Array / plain object according to RESP_TYPES.MAP.
  2. Nested arguments are typed as CommandDocsArgument (including recursive arguments) so docs.get.arguments?.[0].name type-checks.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd3f52841d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return reply as CommandDoc;
}

const doc: Record<string, unknown> = {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply MAP mappings to nested document maps

When an RESP2 client uses withTypeMapping({ [RESP_TYPES.MAP]: Map }), the new outer switch returns a Map, but transformDoc still unconditionally creates a plain object and transformArgument does the same. RESP3 applies the mapping recursively in the decoder, so the same command returns Map<string, object> under RESP2 but Map<string, Map> under RESP3; nested maps also fail to honor the requested mapping. Fresh evidence after the earlier review comment is that the added mapping logic only changes transformDocsMap, while this allocation remains unconditional; apply the mapping consistently to document and argument maps or normalize both protocols to the same declared shape.

Useful? React with 👍 / 👎.

Comment thread packages/client/lib/commands/COMMAND_DOCS.ts Outdated

@nkaradzhov nkaradzhov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hashim1999164 Thanks for the update! The typeMapping handling at the top level looks right now, but two of the three requested changes are still outstanding, and one part of the first remains:

  1. The nested transforms (transformDoc, transformArgument) still produce plain objects unconditionally, so with RESP_TYPES.MAP: Map a RESP2 client gets Map<string, object> while RESP3 returns Map<string, Map>. Please either apply the mapping consistently to the nested maps or normalize both protocols to the same shape.
  2. Both integration tests use the default RESP3 test setup. Please add a testWithClient run with RESP: 2 client options so the nested RESP2 transform is exercised against a real Redis 7 server — that path is currently only covered by the synthetic fixture.
  3. NOT_KEYED_COMMAND is still set — please drop it; it has no runtime consumer and the rest of the COMMAND family does not use it.

One smaller thing while you're in there: reply_schema is recursed through transformDoc, which special-cases keys like arguments inside JSON Schema content and only converts one nesting level. Passing the raw value through on RESP2 would be safer.

Happy to take another look once these are in.

@Hashim1999164

Copy link
Copy Markdown
Contributor Author

Addressed the remaining review points: nested RESP2 maps now honor the MAP type mapping, reply_schema is left untouched, NOT_KEYED_COMMAND is removed, and an integration test exercises commandDocs against Redis 7 with a RESP2 client and Map mapping.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 12dbaa1. Configure here.

}

export default {
IS_READ_ONLY: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing NOT_KEYED_COMMAND flag on COMMAND_DOCS

Medium Severity

COMMAND_DOCS is missing NOT_KEYED_COMMAND: true, which every other COMMAND_* sibling declares (COMMAND.ts, COMMAND_COUNT.ts, COMMAND_GETKEYS.ts, COMMAND_GETKEYSANDFLAGS.ts, COMMAND_INFO.ts, COMMAND_LIST.ts). The project's own skill checklist in .agents/skills/implement-command/SKILL.md explicitly requires this flag for commands that take no Redis keys. COMMAND DOCS takes only command names, not keys, so the flag is needed for consistency and correctness in cluster routing contexts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 12dbaa1. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12dbaa11fe

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

case 'subcommands':
return transformDocsMap(value, typeMapping);
case 'reply_schema':
return value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize reply_schema maps in RESP2

When a Redis build includes reply_schema in COMMAND DOCS (for example, schema metadata for commands such as GET), RESP2 encodes that schema as nested flattened maps. This branch returns the raw array while the rest of the transformer normalizes maps, so client.commandDocs('GET') under RESP2 yields reply_schema: ['oneOf', ...] and reply_schema.oneOf is undefined even though the RESP3/default shape exposes an object. Recursively transform the schema maps here instead of returning value unchanged.

Useful? React with 👍 / 👎.

summary?: BlobStringReply;
since?: BlobStringReply;
deprecated_since?: BlobStringReply;
flags?: ArrayReply<BlobStringReply>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Declare set-valued docs fields as SetReply

When RESP3 callers opt into withTypeMapping({ [RESP_TYPES.SET]: Set }), argument metadata such as flags is decoded as a Set, but this declaration still advertises an array, so TypeScript consumers can write argument.flags?.includes('optional') and then fail at runtime because Set has no includes. Use SetReply<BlobStringReply> for this set-valued field and the analogous doc-level set fields so the public type follows the decoder output.

Useful? React with 👍 / 👎.

default:
return value;
}
}) as CommandDoc;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose map-mapped document values as maps

Fresh evidence after the runtime mapping fix is that this cast still reports each transformed command document as CommandDoc even when callers use withTypeMapping({ [RESP_TYPES.MAP]: Map }) (or Array), where transformMap now returns a Map for every document and the integration test expects get instanceof Map. TypeScript therefore sees docs.get('get') as a plain object and rejects get.get('arguments') even though that is the runtime shape; model command docs/arguments as map replies or provide mapped variants so nested MAP mappings are reflected.

Useful? React with 👍 / 👎.

@nkaradzhov
nkaradzhov merged commit 043d304 into redis:master Jul 30, 2026
15 checks passed
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

Successfully merging this pull request may close these issues.

Add support for redis command: COMMAND DOCS (R7)

2 participants