Skip to content

Default Resp3#3215

Open
nkaradzhov wants to merge 11 commits into
redis:masterfrom
nkaradzhov:resp3
Open

Default Resp3#3215
nkaradzhov wants to merge 11 commits into
redis:masterfrom
nkaradzhov:resp3

Conversation

@nkaradzhov
Copy link
Copy Markdown
Collaborator

@nkaradzhov nkaradzhov commented Mar 31, 2026

Description

Describe your pull request here


Checklist

  • Does npm test pass with this change (including linting)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?

Note

Medium Risk
Changes the default wire protocol to RESP3 and adjusts multiple command reply transforms/types (including object prototype normalization), which can break consumers relying on RESP2 defaults or exact reply shapes/prototypes.

Overview
RESP3 (RESP: 3) is now the default across createClient/cluster/pool/legacy paths via a new exported DEFAULT_RESP, with option validation and maintenance/client-side-cache behavior updated so RESP omitted implies RESP3.

Stabilizes and normalizes previously unstable RESP3 transforms by removing the unstableResp3 gating and adding concrete RESP3 transforms for commands like HOTKEYS GET, XREAD/XREADGROUP (now v4/v5-compatible list shape), and MODULE LIST (handles array/map/object variants).

Normalizes map/object decoding and various command transforms to return plain objects instead of Object.create(null), and aligns reply typing/behavior changes including GEO WITH* distance/coordinates as number and CF.INSERTNX returning Array<number>.

Adds a v5→v6 migration guide and updates/extends unit/integration tests to assert the new RESP3-default behaviors and reply structures.

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

@nkaradzhov nkaradzhov force-pushed the resp3 branch 5 times, most recently from e4d84b7 to 311595a Compare April 6, 2026 15:52
@nkaradzhov nkaradzhov marked this pull request as ready for review April 16, 2026 15:32
@nkaradzhov nkaradzhov mentioned this pull request Apr 16, 2026
3 tasks
Comment thread packages/client/lib/commands/XREAD.ts Outdated
);
}

object[key] = value;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we use Object.defineProperty here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

why?

default:
return this.#decodeMapAsObject(
Object.create(null),
{},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this cause issues if the server returns a __proto__ key in a map? Related to this comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, and we added this type of change to the v5-to-v6 migration doc

Comment thread packages/test-utils/lib/index.ts Outdated
const RESP = (options.clusterConfiguration?.RESP ?? 3) as RESP;
const {
RESP: _RESP,
minimizeConnections = false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is minimizeConnections: false intentional?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

no, fixing

parseCfInsertArguments(...args);
},
transformReply: INSERT.transformReply
transformReply: undefined as unknown as () => ArrayReply<NumberReply<-1 | 0 | 1>>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This might break the old RESP 2 responses as now it will return numbers instead of booleans for both protocols.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

that is intentional, documented in v5-to-v6

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, i see what you mean. This actually breaks the RESP 2, but we know that the server is in fact returning number in both resp2 and resp3. so its more of a fix for resp2. So i would keep it like this

Comment on lines +169 to +171
if (Array.isArray(rawReply)) {
return transformAggregateReplyResp2(rawReply as unknown as AggregateRawReply, preserve, typeMapping);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this break RESP3 clients using RESP_TYPES.MAP: Array, since map replies decode as flat arrays and would be parsed as RESP2 here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

true, has to be fixed

Comment on lines +481 to +488
if (
key === "id" ||
key === "values" ||
key.toLowerCase() === "extra_attributes" ||
key === "extraAttributes"
) {
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will this stop working for RESP2, because we'll still need to JSON.parse there?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

JSON.parse still runs — it just moved into parseDocumentValue → assignDocumentField.
Worth adding a JSON test though.

@PavelPashov
Copy link
Copy Markdown
Contributor

Suggestion: Should we add a DEFAULT_RESP const so we don't hardcode 3 everywhere?

- Add RESP2 vs RESP3 structural assertions across command specs.
- Consolidate duplicate, renamed, and flaky test cases in one pass.
- Keep this commit spec-only to isolate test intent.
- Switch default RESP behavior to RESP3 across client entry points.
- Align cluster and sentinel command paths with RESP3 defaults.
- Keep compatibility normalization and module fixes for later commits.
- Normalize cross-module reply-shape handling for RESP2 and RESP3.
- Apply shared parser and transformer updates for stable compatibility.
- Leave targeted module bugfixes isolated for the next commit.
- Fix GEO float reply handling across geosearch-compatible paths.
- Fix Bloom CF.INSERTNX status handling and Search PROFILE parsing edge cases.
- Fix TimeSeries MRANGE selected-label/groupby compatibility behavior.
generic-transformers.spec.ts was newly uncommented and needed some assertion updates
- refactor(streams): share RESP3 compat transformer across XREAD/XREADGROUP
- test-utils: restore pre-resp3 testWithCluster minimizeConnections default
- fix(search): handle FT.AGGREGATE RESP3 reply under typeMapping[MAP]=Array
- test(search): cover FT.HYBRID JSON-doc reply for RESP2 and RESP3
Replace hardcoded `?? 3` defaults with a named DEFAULT_RESP constant
exported from @redis/client. Covers all runtime fallback sites in
client/, sentinel/, cluster/, commander, legacy-mode, and the test-utils
helpers. TS generic defaults, JSDoc examples, and explicit per-test
RESP pins are intentionally left as literals.
Comment thread packages/client/lib/commands/generic-transformers.ts
@nkaradzhov nkaradzhov requested a review from PavelPashov May 14, 2026 10:08
@nkaradzhov
Copy link
Copy Markdown
Collaborator Author

Suggestion: Should we add a DEFAULT_RESP const so we don't hardcode 3 everywhere?

the value depends on which 3s you mean:

  • True defaults (this.#options.RESP ?? 3 at client/index.ts:689,701,751, plus the RESP = 3 fallback in sentinel/test-util.ts:196 and test-utils/lib/index.ts:655): ~5 sites total — a const would help name the intent.
  • TS generic defaults (RESP extends RespVersions = 3 — ~6 sites in client/index.ts, sentinel/index.ts): cant reference a runtime const, so we would end up with a parallel type DefaultResp = 3 — duplication.
  • Per-test/per-package RESP: 3 as const (~10+ sites across */lib/test-utils.ts and the e2e specs): these are statements of intent ("I am pinning RESP3 here"), not deferrals to a default. Replacing with DEFAULT_RESP would obscure that — and silently change behavior if the const
    ever moved.

transformStreamsMessagesReplyResp3Compat now matches the TransformReply
contract and propagates typeMapping into the inner Resp3 transform.
transformStreamsMessagesReplyResp3 accepts and forwards typeMapping to
transformStreamMessagesReply in every branch, and the V4-compat path in
transformStreamsMessagesReplyResp2 does the same — so XREAD/XREADGROUP
message bodies now honor RESP_TYPES.MAP like XRANGE does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 25b36fb. Configure here.

Comment thread packages/client/lib/commands/HOTKEYS_GET.ts
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.

2 participants