-
Notifications
You must be signed in to change notification settings - Fork 2k
Default Resp3 #3215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Default Resp3 #3215
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
92e3f34
test(resp2-resp3): expand coverage and consolidate spec cleanup
nkaradzhov 261f28f
feat(client): default connections to RESP3
nkaradzhov 8c222bd
fix(compat): normalize RESP2/RESP3 reply transforms
nkaradzhov e5dcaae
fix(modules): address targeted RESP compatibility regressions
nkaradzhov 5b18a00
tests: fix some wrong assertions
nkaradzhov 2ede56c
docs: add migration doc
nkaradzhov 3c84ae3
test: stabilize generic-transformers spec across Node versions
nkaradzhov cccfe57
fix merge and lint issues
nkaradzhov c905730
address PR comments
nkaradzhov 9b6696e
refactor(client): introduce DEFAULT_RESP for runtime fallbacks
nkaradzhov 91084aa
fix(client): forward typeMapping through stream message transforms
nkaradzhov 9b47f14
fix(search): forward typeMapping through composed RESP3 transforms
nkaradzhov d815010
fix(search): make toCompatObject properties writable
nkaradzhov 0e91538
fix(client): apply DOUBLE typeMapping in GEOSEARCH_WITH on RESP3
nkaradzhov a4f0390
fix(client): harden HOTKEYS GET reply parsing
nkaradzhov aef2399
fix(client): MODULE LIST preserves unknown keys and avoids undefined …
nkaradzhov f013889
types(time-series): replace `never` metadata slot in MRANGE SELECTED_…
nkaradzhov 6bf1b88
fix(search): avoid "[object Object]" warnings from FT.HYBRID
nkaradzhov 9c8648c
fix(client): normalize stream name in Resp3Compat Array branch and do…
nkaradzhov d49fa6f
docs(migration): call out legacy callback mode now defaulting to RESP3
nkaradzhov 75604f1
docs(migration): add XRANGE/XREVRANGE/XCLAIM/XAUTOCLAIM to prototype …
nkaradzhov 856f101
docs(migration): note removal of unstableResp3/unstableResp3Modules
nkaradzhov b88f72a
docs(migration): document maintNotifications default flip to "auto" o…
nkaradzhov a495521
types(client): narrow DEFAULT_RESP from RespVersions to literal 3
nkaradzhov 08577e3
test(client): use DEFAULT_RESP in HELLO spec instead of hardcoded 3
nkaradzhov deb1d0f
refactor(client): centralize map-like reply primitives in reply-utils
nkaradzhov 51aa7e2
test(client): add unit tests for map-like reply primitives
nkaradzhov 5110f79
style(search): drop unused eslint-disable for no-unused-vars
nkaradzhov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| # v5 to v6 migration guide | ||
|
|
||
| ## RESP3 is now the default protocol | ||
|
|
||
| In v5, Node-Redis defaulted to `RESP: 2` unless you explicitly configured `RESP: 3`. | ||
| In v6, the default is now `RESP: 3`. | ||
|
|
||
| RESP3 introduces a bunch of “on the wire” formats that replace RESP2 workarounds. | ||
| Node-Redis already maps most of the RESP2 workarounds to the proper javascript type, but there are some commands that were missed. | ||
| Those are now aligned to return the proper types. For more details on protocol type mapping, see [RESP type mapping](./RESP.md). | ||
|
|
||
|
|
||
| ## Default behavior changes (v5 default -> v6 default) | ||
|
|
||
| - `GEOSEARCH_WITH`, `GEORADIUS_WITH`, `GEORADIUS_RO_WITH`, `GEORADIUSBYMEMBER_WITH`, `GEORADIUSBYMEMBER_RO_WITH` - `distance`, `coordinates.longitude`, and `coordinates.latitude` are now `number` (previously `string`). | ||
| - `CF.INSERTNX` changed from `Array<boolean>` to `Array<number>`. | ||
|
|
||
|
|
||
| ## Stabilized APIs | ||
| In v5, some command transforms were unstable under RESP3. In v6, those commands are stabilized and normalized: | ||
| These stabilization changes are RESP3-only: RESP2 transforms are unchanged. | ||
| They are breaking only for clients using RESP3 (including v5 users who explicitly opted into RESP3, and v6 users on the new default RESP3). | ||
|
|
||
| | Package | Command | Return type change | Notes | | ||
| |---|---|---|---| | ||
| | `@redis/client` | `HOTKEYS GET` | `ReplyUnion -> HotkeysGetReply \| null` | RESP3 reply now normalized to stable structured output. | | ||
| | `@redis/client` | `XREAD` | `ReplyUnion -> StreamsMessagesReply \| null` | RESP3 reply is normalized to v4/v5-compatible stream list shape. | | ||
| | `@redis/client` | `XREADGROUP` | `ReplyUnion -> StreamsMessagesReply \| null` | RESP3 reply is normalized to v4/v5-compatible stream list shape. | | ||
| | `@redis/search` | `FT.AGGREGATE` | `ReplyUnion -> AggregateReply` | RESP3 map/array variants normalized to aggregate reply shape. | | ||
| | `@redis/search` | `FT.AGGREGATE WITHCURSOR` | `ReplyUnion -> AggregateWithCursorReply` | Cursor + results are normalized for RESP3. | | ||
| | `@redis/search` | `FT.CURSOR READ` | `ReplyUnion -> AggregateWithCursorReply` | RESP3 cursor-read map/array wrapper variants are normalized to a stable `{ total, results, cursor }` reply shape. | | ||
| | `@redis/search` | `FT.SEARCH` | `ReplyUnion -> SearchReply` | RESP3 map-like payload normalized to `{ total, documents }`. | | ||
| | `@redis/search` | `FT.SEARCH NOCONTENT` | `ReplyUnion -> SearchNoContentReply` | RESP3 normalized through `FT.SEARCH` then projected to ids. | | ||
| | `@redis/search` | `FT.SPELLCHECK` | `ReplyUnion -> SpellCheckReply` | RESP3 result/suggestion map variants normalized. | | ||
| | `@redis/search` | `FT.HYBRID` | `ReplyUnion -> HybridSearchResult` | RESP3 map-like payload normalized to hybrid result object. | | ||
| | `@redis/search` | `FT.INFO` | `ReplyUnion -> InfoReply` | RESP3 map-like payload normalized to stable info object shape. | | ||
| | `@redis/search` | `FT.PROFILE SEARCH` | `ReplyUnion -> ProfileReplyResp2` | RESP3 profile/results wrappers normalized (Redis 7.4/8 layouts). | | ||
| | `@redis/search` | `FT.PROFILE AGGREGATE` | `ReplyUnion -> ProfileReplyResp2` | RESP3 profile/results wrappers normalized (Redis 7.4/8 layouts). | | ||
| | `@redis/time-series` | `TS.INFO` | `ReplyUnion -> InfoReply` | RESP3 map/array variants normalized to `InfoReply`. | | ||
| | `@redis/time-series` | `TS.INFO DEBUG` | `ReplyUnion -> InfoDebugReply` | RESP3 `keySelfName`/`chunks` payload normalized. | | ||
| | `@redis/time-series` | `TS.MRANGE GROUPBY` | `{ sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { samples: Array<{ timestamp: number; value: number }> }` | `sources` removed from RESP3 grouped reply. | | ||
| | `@redis/time-series` | `TS.MREVRANGE GROUPBY` | `{ sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { samples: Array<{ timestamp: number; value: number }> }` | In RESP3 grouped reverse-range replies, `sources` is removed and output now includes only `{ samples }`. | | ||
| | `@redis/time-series` | `TS.MRANGE SELECTED_LABELS GROUPBY` | `{ labels: Record<string, string \| null>; sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { labels: Record<string, string \| null>; samples: Array<{ timestamp: number; value: number }> }` | `sources` removed from RESP3 selected-labels grouped reply. | | ||
| | `@redis/time-series` | `TS.MREVRANGE SELECTED_LABELS GROUPBY` | `{ labels: Record<string, string \| null>; sources: Array<string>; samples: Array<{ timestamp: number; value: number }> } -> { labels: Record<string, string \| null>; samples: Array<{ timestamp: number; value: number }> }` | In RESP3 selected-labels grouped reverse-range replies, `sources` is removed and output now includes `{ labels, samples }`. | | ||
|
|
||
| ## Object Prototype Normalization | ||
| In v6, object-like replies are normalized to plain objects (`{}` / `Object.defineProperties({}, ...)`) instead of null-prototype objects (`Object.create(null)`). | ||
|
|
||
| Compatibility impact: this can be technically breaking for code/tests that assert a `null` prototype (for example `Object.getPrototypeOf(reply) === null` or deep-equality against `Object.create(null)`), but for most users key access/iteration/serialization behavior remains the same. | ||
|
|
||
| Commands affected: | ||
|
|
||
| - `@redis/client`: `CONFIG GET`, `FUNCTION STATS`, `HGETALL`, `LATENCY HISTOGRAM` (`histogram_usec`), `PUBSUB NUMSUB`, `PUBSUB SHARDNUMSUB`, `VINFO`, `VLINKS WITHSCORES`, `XINFO STREAM` (entry message objects), `XREAD`/`XREADGROUP` (message objects), `XRANGE`/`XREVRANGE` (message objects), `XCLAIM`/`XAUTOCLAIM` (message objects) | ||
| - `@redis/search`: `FT.AGGREGATE`, `FT.AGGREGATE WITHCURSOR`, `FT.CURSOR READ`, `FT.CONFIG GET`, `FT.HYBRID`, `FT.INFO`, `FT.SEARCH`, `FT.PROFILE SEARCH`, `FT.PROFILE AGGREGATE` | ||
| - `@redis/time-series`: `TS.MGET`, `TS.MGET WITHLABELS`, `TS.MGET SELECTED_LABELS`, `TS.MRANGE`, `TS.MREVRANGE`, `TS.MRANGE GROUPBY`, `TS.MREVRANGE GROUPBY`, `TS.MRANGE WITHLABELS`, `TS.MREVRANGE WITHLABELS`, `TS.MRANGE WITHLABELS GROUPBY`, `TS.MREVRANGE WITHLABELS GROUPBY`, `TS.MRANGE SELECTED_LABELS`, `TS.MREVRANGE SELECTED_LABELS`, `TS.MRANGE SELECTED_LABELS GROUPBY`, `TS.MREVRANGE SELECTED_LABELS GROUPBY` | ||
| - `@redis/bloom`: `BF.INFO`, `CF.INFO`, `CMS.INFO`, `TOPK.INFO`, `TDIGEST.INFO` | ||
|
|
||
| Additionally, RESP3 map decoding now creates plain objects by default, so commands that expose raw RESP3 maps as JS objects inherit the same prototype change. | ||
|
|
||
|
|
||
| ## `unstableResp3` and `unstableResp3Modules` are removed | ||
|
|
||
| Both options were v5 gates for opt-in access to in-development RESP3 transforms. In v6 those transforms are stable (see the [Stabilized APIs](#stabilized-apis) table above) and the gates have been deleted. If your v5 code passed either option, remove the property — TypeScript will surface it as an unknown-property error on the options literal: | ||
|
|
||
| ```javascript | ||
| // v5 | ||
| const client = createClient({ RESP: 3, unstableResp3: true }); | ||
|
|
||
| // v6 | ||
| const client = createClient({ RESP: 3 }); | ||
| ``` | ||
|
|
||
|
|
||
| ## `maintNotifications` default is now derived from `RESP` | ||
|
|
||
| In v5, `maintNotifications` defaulted to `"disabled"` regardless of protocol. In v6, the default is derived from the negotiated RESP version: `"auto"` when `RESP: 3` (the new default), and `"disabled"` when `RESP: 2`. Because v6 also flips the protocol default to RESP3, clients that don't set `maintNotifications` explicitly now opt into Enterprise maintenance push notifications, the relaxed socket/command timeouts, and endpoint-type negotiation — behavior v5 never enabled by default. | ||
|
|
||
| `"auto"` is the functional on-switch for maintenance handling; it is not a no-op. The client subscribes to maintenance push frames (moving/migrating/failing-over) and adjusts socket/command timeouts during maintenance windows. On non-Enterprise servers this is generally harmless, but it does change topology-change behavior versus v5. | ||
|
|
||
| To keep the v5 default, set `maintNotifications: "disabled"` explicitly (or pin `RESP: 2`): | ||
|
|
||
| ```javascript | ||
| // Keep v5 maintenance semantics on RESP3 | ||
| const client = createClient({ maintNotifications: "disabled" }); | ||
|
|
||
| // Or pin RESP2 and inherit the disabled default | ||
| const client = createClient({ RESP: 2 }); | ||
| ``` | ||
|
|
||
|
|
||
| ## Legacy (callback) mode now uses RESP3 | ||
|
|
||
| `createClient().legacy()` reads the parent client's RESP version. With the v6 default of RESP3, legacy callback consumers will see RESP3-shaped replies for any command whose transforms differ between protocol versions (for example, doubles arriving as `number` instead of `string`, or hash-like replies arriving as `Map`s). To keep the v5 callback reply shapes, pin `RESP: 2` on the parent client: | ||
|
|
||
| ```javascript | ||
| const legacy = createClient({ RESP: 2 }).legacy(); | ||
| ``` | ||
|
|
||
|
|
||
| ## If you need to preserve v5 default behavior while migrating, pin RESP2 explicitly: | ||
|
|
||
| ```javascript | ||
| // Single node | ||
| const client = createClient({ RESP: 2 }); | ||
|
|
||
| // Cluster | ||
| const cluster = createCluster({ RESP: 2, ...}); | ||
|
|
||
| // Sentinel | ||
| const sentinel = createSentinel({ RESP: 2, ... }); | ||
|
|
||
| // Pool | ||
| const pool = createClientPool({ RESP: 2 }); | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,30 @@ | ||
| import { RESP_TYPES, TypeMapping } from "@redis/client"; | ||
|
|
||
| export function transformInfoV2Reply<T>(reply: Array<any>, typeMapping?: TypeMapping): T { | ||
| export function transformInfoV2Reply<T>(reply: Array<unknown>, typeMapping?: TypeMapping): T { | ||
| const entries = reply as Array<{ toString(): string }>; | ||
| const mapType = typeMapping ? typeMapping[RESP_TYPES.MAP] : undefined; | ||
|
|
||
| switch (mapType) { | ||
| case Array: { | ||
| return reply as unknown as T; | ||
| } | ||
| case Map: { | ||
| const ret = new Map<string, any>(); | ||
| const ret = new Map<string, unknown>(); | ||
|
|
||
| for (let i = 0; i < reply.length; i += 2) { | ||
| ret.set(reply[i].toString(), reply[i + 1]); | ||
| for (let i = 0; i < entries.length; i += 2) { | ||
| ret.set(entries[i].toString(), entries[i + 1]); | ||
| } | ||
|
|
||
| return ret as unknown as T; | ||
| } | ||
| default: { | ||
| const ret = Object.create(null); | ||
| const ret: Record<string, unknown> = {}; | ||
|
|
||
| for (let i = 0; i < reply.length; i += 2) { | ||
| ret[reply[i].toString()] = reply[i + 1]; | ||
| for (let i = 0; i < entries.length; i += 2) { | ||
| ret[entries[i].toString()] = entries[i + 1]; | ||
| } | ||
|
|
||
| return ret as unknown as T; | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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