@@ -2,6 +2,7 @@ import { CommandParser } from '@redis/client/dist/lib/client/parser';
22import { ArrayReply , Command , RedisArgument , ReplyUnion , TuplesReply , UnwrapReply } from '@redis/client/dist/lib/RESP/types' ;
33import { AggregateReply } from './AGGREGATE' ;
44import SEARCH , { FtSearchOptions , SearchRawReply , SearchReply , parseSearchOptions } from './SEARCH' ;
5+ import { getMapValue , mapLikeEntries , mapLikeToObject , normalizeProfileReply } from './reply-transformers' ;
56
67export type ProfileRawReplyResp2 < T > = TuplesReply < [
78 T ,
@@ -19,6 +20,73 @@ export interface ProfileOptions {
1920 LIMITED ?: true ;
2021}
2122
23+ export function extractProfileResultsReply ( reply : ReplyUnion ) : ReplyUnion {
24+ const replyObject = mapLikeToObject ( reply ) ;
25+
26+ // Redis 8+ wraps results under `Results`.
27+ if ( Object . hasOwn ( replyObject , 'Results' ) ) {
28+ return replyObject [ 'Results' ] as ReplyUnion ;
29+ }
30+
31+ // Redis 7.4 RESP3 returns search/aggregate payload directly at top-level.
32+ if (
33+ ( Object . hasOwn ( replyObject , 'total_results' ) || Object . hasOwn ( replyObject , 'total' ) ) &&
34+ Object . hasOwn ( replyObject , 'results' )
35+ ) {
36+ return reply ;
37+ }
38+
39+ if ( Object . hasOwn ( replyObject , 'results' ) ) {
40+ return replyObject [ 'results' ] as ReplyUnion ;
41+ }
42+
43+ return ( getMapValue ( replyObject , [ 'results' ] ) ?? reply ) as ReplyUnion ;
44+ }
45+
46+ function normalizeLegacyProfileReply ( profile : ReplyUnion ) : ReplyUnion {
47+ return mapLikeEntries ( profile ) . map ( ( [ key , value ] ) => {
48+ // Redis 7.4 often wraps iterator profiles as a single-element array containing an object.
49+ // Tests expect the inner object normalized directly as a flat key/value list.
50+ if ( Array . isArray ( value ) && value . length === 1 ) {
51+ const first = value [ 0 ] ;
52+ if ( Object . keys ( mapLikeToObject ( first ) ) . length > 0 ) {
53+ return [ key , normalizeProfileReply ( first ) ] ;
54+ }
55+ }
56+
57+ return [ key , normalizeProfileReply ( value ) ] ;
58+ } ) as unknown as ReplyUnion ;
59+ }
60+
61+ export function transformProfileReply ( reply : ReplyUnion ) : ReplyUnion {
62+ const replyObject = mapLikeToObject ( reply ) ;
63+ const profile = (
64+ Object . hasOwn ( replyObject , 'Profile' ) ?
65+ replyObject [ 'Profile' ] :
66+ Object . hasOwn ( replyObject , 'profile' ) ?
67+ replyObject [ 'profile' ] :
68+ getMapValue ( replyObject , [ 'Profile' , 'profile' ] )
69+ ) as ReplyUnion ;
70+
71+ const profileObject = mapLikeToObject ( profile ) ;
72+
73+ // Redis 7.2 - 7.4 profile payload is a plain map keyed by timing labels.
74+ if ( Object . hasOwn ( profileObject , 'Total profile time' ) ) {
75+ return normalizeLegacyProfileReply ( profile ) ;
76+ }
77+
78+ return normalizeProfileReply ( profile ) as ReplyUnion ;
79+ }
80+
81+ function transformProfileSearchReplyResp3 ( reply : ReplyUnion ) : ProfileReplyResp2 {
82+ return {
83+ results : SEARCH . transformReply [ 3 ] (
84+ extractProfileResultsReply ( reply )
85+ ) ,
86+ profile : transformProfileReply ( reply )
87+ } ;
88+ }
89+
2290export default {
2391 NOT_KEYED_COMMAND : true ,
2492 IS_READ_ONLY : true ,
@@ -54,7 +122,7 @@ export default {
54122 profile : reply [ 1 ]
55123 } ;
56124 } ,
57- 3 : ( reply : ReplyUnion ) : ReplyUnion => reply
125+ 3 : transformProfileSearchReplyResp3
58126 } ,
59127 unstableResp3 : true
60128} as const satisfies Command ;
0 commit comments