Skip to content

Add RESP3 - #2127

Merged
PavelPashov merged 38 commits into
redis:mainfrom
PavelPashov:feat/add-resp3
Jul 20, 2026
Merged

PavelPashov merged 38 commits into
redis:mainfrom
PavelPashov:feat/add-resp3

Conversation

@PavelPashov

@PavelPashov PavelPashov commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

Note

High Risk
Touches connection handshake, wire parsing, pub/sub routing, and default protocol behavior—any regression affects all commands; RESP3 subscriber semantics and reply-shape changes can break callers expecting v5 RESP2-only behavior.

Overview
This is a major protocol and typing upgrade for v6: connections default to RESP3 (HELLO 3), fall back to RESP2 when the server cannot negotiate, and expose protocol plus replyMapping ("legacy" vs "resp3") to control how maps and doubles appear in JavaScript.

Runtime: redis-parser is replaced by an in-tree RESP3 decoder (lib/resp/decoder.ts). DataHandler routes push frames for pub/sub on RESP3, skips RESP2-style subscriber reply sniffing on protocol 3, and applies legacy vs resp3 type mappings at parse time. Connect handshake is reworked around ordered HELLO/auth/select/client/readonly/setup with a handshake gate so user commands cannot race ahead of readiness; unsupported RESP3 triggers downgrade and a warning when "resp3" mapping was requested.

Replies: Command reply transformers are context-aware (protocol + mapping) for sorted-set pairs, streams (xread/xreadgroup), and vsim. Generated commander types gain Resp2/Resp3/RespShape branches and many per-command return type splits in bin/returnTypes.js.

Ecosystem: README documents RESP3 and v6 (Node ≥20); release pipeline generates lib/utils/version.ts for CLIENT SETINFO instead of reading package metadata at runtime. Cluster/Sentinel paths propagate protocol options; cluster slot refresh uses explicit RESP2/legacy on a duplicate connection.

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

@PavelPashov
PavelPashov marked this pull request as draft June 25, 2026 14:05
@PavelPashov
PavelPashov force-pushed the feat/add-resp3 branch 2 times, most recently from 6225346 to fed5a8e Compare July 1, 2026 11:57
@coveralls

coveralls commented Jul 6, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 29090997366

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Warning

No base build found for commit 4028573 on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 89.501%

Details

  • Patch coverage: Could not be determined — this PR's diff is too large for GitHub to return (406 error at GitHub).

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 3464
Covered Lines: 3175
Line Coverage: 91.66%
Relevant Branches: 2032
Covered Branches: 1744
Branch Coverage: 85.83%
Branches in Coverage %: Yes
Coverage Strength: 37250.6 hits per line

💛 - Coveralls

@PavelPashov
PavelPashov marked this pull request as ready for review July 10, 2026 11:57

@bobymicroby bobymicroby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

Add a replyMapping option for RESP3 connections, keeping legacy RESP2-like
reply shapes by default while allowing native RESP3 map and double replies.

Also update RESP3 subscriber-mode handling so regular commands remain usable
while subscribed, preserve object reply encoding conversion, harden RESP3 map
object decoding for special keys, and add migration notes plus functional
coverage for reply mapping and RESP2/RESP3 parity.
Add RESP3 reply-mapping return types and the generic plumbing that wires
them through the client:

- bin codegen (returnTypes/template) emits RespShape/Resp2/Resp3 variants
  and regenerates RedisCommander return types
- ReplyMapping type helpers in types.ts
- Redis/Cluster gain a ReplyMapping type parameter inferred from the
  options literal, threaded through duplicate()
- cluster passthrough of protocol/replyMapping to subscriber/refresher
  nodes
Move the active protocol into the connection condition and add an
internal handshake gate so user commands cannot race ahead of the ready
check. If the server rejects HELLO 3, downgrade the connection to RESP2
in place (re-running the handshake only when auth was bundled) instead
of failing the connection.

- Condition gains protocol/handshake; DataHandler reads condition.protocol
- connectHandler runs the handshake async, gates writability, and recovers
  from protocol-negotiation errors
- isResp2SubscriberMode derives protocol from the condition
- exclude HELLO from auto-pipelining
- tests for protocol downgrade and connect-state writability
Comment thread lib/redis/event_handler.ts

@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 ded6122. Configure here.

Comment thread lib/resp/decoder.ts
#decodeNull() {
this.#cursor += 2; // skip \r\n
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Null reply split parse bug

Medium Severity

The RESP3 null decoder always advances the cursor by two bytes without waiting for a complete _ terminator sequence. When _ and \r\n arrive in separate TCP chunks, the cursor moves past the available data and the parser can misalign on the next read, corrupting command pairing or triggering fatal protocol errors.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ded6122. Configure here.

@PavelPashov
PavelPashov merged commit 5d0862e into redis:main Jul 20, 2026
22 checks passed
GiHoon1123 added a commit to GiHoon1123/ioredis that referenced this pull request Jul 28, 2026
…er stack overflow

The RESP3 rewrite (redis#2127) replaced redis-parser with a new Decoder, and as
part of that already wraps decoder.write() in try/catch, routing failures
through returnFatalError -> recoverFromFatalError the same way other
parser-detected fatal errors are handled.

The new Decoder still recurses once per level of RESP aggregate nesting
with no depth limit (confirmed directly against lib/resp/decoder.ts), so a
sufficiently deeply nested reply still throws a RangeError - it's just
already caught and routed through the existing recovery path instead of
crashing the process. There was no test pinning this down, so this adds
one.

Fixes redis#2108
github-actions Bot pushed a commit that referenced this pull request Jul 29, 2026
# [6.0.0-beta.1](v5.11.1...v6.0.0-beta.1) (2026-07-29)

* Add RESP3 ([#2127](#2127)) ([5d0862e](5d0862e))

### Bug Fixes

* clear stale socket timeout on reconnect ([#2148](#2148)) ([6455dbe](6455dbe))
* **cluster:** recreate stale connection on circular MOVED ([#2135](#2135)) ([08c8967](08c8967))
* **cluster:** validate MOVED slot to prevent Array.prototype pollution ([#2151](#2151)) ([9618206](9618206)), closes [#1267](#1267)
* **command:** serialize large integer arguments in decimal notation ([#2136](#2136)) ([09b8d04](09b8d04))
* **redis:** keep reconnecting when connection closes during client setup ([#2099](#2099)) ([#2123](#2123)) ([f9a66bc](f9a66bc))
* **sentinel:** preserve zero preferred slave priority ([#2129](#2129)) ([a3f9f2d](a3f9f2d))
* **tracing:** redact values for GETSET and PSETEX ([#2134](#2134)) ([832765d](832765d))

### Features

* add LMOVEM and BLMOVEM command support ([#2144](#2144)) ([c26af46](c26af46))
* add Redis 8.10 set cardinality commands ([#2143](#2143)) ([301099b](301099b))
* support MAXCOUNT and MAXSIZE for stream reads ([#2142](#2142)) ([ae5e41b](ae5e41b))

### BREAKING CHANGES

* ioredis now requires Node.js 20 or newer and uses RESP3 by default. Set `protocol: 2` to retain the v5 wire protocol.
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.0.0-beta.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

github-actions Bot pushed a commit that referenced this pull request Jul 31, 2026
# [6.0.0](v5.11.1...v6.0.0) (2026-07-31)

* Add RESP3 ([#2127](#2127)) ([5d0862e](5d0862e))

### Bug Fixes

* clear stale socket timeout on reconnect ([#2148](#2148)) ([6455dbe](6455dbe))
* **cluster:** recreate stale connection on circular MOVED ([#2135](#2135)) ([08c8967](08c8967))
* **cluster:** validate MOVED slot to prevent Array.prototype pollution ([#2151](#2151)) ([9618206](9618206)), closes [#1267](#1267)
* **command:** serialize large integer arguments in decimal notation ([#2136](#2136)) ([09b8d04](09b8d04))
* **redis:** keep reconnecting when connection closes during client setup ([#2099](#2099)) ([#2123](#2123)) ([f9a66bc](f9a66bc))
* **sentinel:** preserve zero preferred slave priority ([#2129](#2129)) ([a3f9f2d](a3f9f2d))
* **tracing:** redact values for GETSET and PSETEX ([#2134](#2134)) ([832765d](832765d))
* **types:** export ScanStreamOptions, RedisStatus and ClusterStatus ([#2158](#2158)) ([cf3bf71](cf3bf71))

### Features

* add LMOVEM and BLMOVEM command support ([#2144](#2144)) ([c26af46](c26af46))
* add Redis 8.10 set cardinality commands ([#2143](#2143)) ([301099b](301099b))
* himport managed fieldsets ([#2159](#2159)) ([729f174](729f174))
* improve default connection resilience ([#2160](#2160)) ([6d0716e](6d0716e))
* support MAXCOUNT and MAXSIZE for stream reads ([#2142](#2142)) ([ae5e41b](ae5e41b))

### BREAKING CHANGES

* ioredis now requires Node.js 20 or newer and uses RESP3 by default. Set `protocol: 2` to retain the v5 wire protocol.
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

PavelPashov pushed a commit that referenced this pull request Aug 6, 2026
…er stack overflow (#2138)

The RESP3 rewrite (#2127) replaced redis-parser with a new Decoder, and as
part of that already wraps decoder.write() in try/catch, routing failures
through returnFatalError -> recoverFromFatalError the same way other
parser-detected fatal errors are handled.

The new Decoder still recurses once per level of RESP aggregate nesting
with no depth limit (confirmed directly against lib/resp/decoder.ts), so a
sufficiently deeply nested reply still throws a RangeError - it's just
already caught and routed through the existing recovery path instead of
crashing the process. There was no test pinning this down, so this adds
one.

Fixes #2108
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants