@@ -152,7 +152,10 @@ import { ZMScoreCommand } from "./commands/zmscore.ts";
152
152
import { HRandFieldCommand } from "./commands/hrandfield.ts" ;
153
153
import { ZDiffStoreCommand } from "./commands/zdiffstore.ts" ;
154
154
155
- type Chain = < T > ( command : Command < any , T > ) => Pipeline ;
155
+ // Given a tuple of commands, returns a tuple of the response data of each command
156
+ type InferResponseData < T extends unknown [ ] > = {
157
+ [ K in keyof T ] : T [ K ] extends Command < any , infer TData > ? TData : unknown ;
158
+ } ;
156
159
157
160
/**
158
161
* Upstash REST API supports command pipelining to send multiple commands in
@@ -182,7 +185,7 @@ type Chain = <T>(command: Command<any, T>) => Pipeline;
182
185
* const res = await p.set("key","value").get("key").exec()
183
186
* ```
184
187
*
185
- * It's not possible to infer correct types with a dynamic pipeline, so you can
188
+ * Return types are inferred if all commands are chained, but you can still
186
189
* override the response type manually:
187
190
* ```ts
188
191
* redis.pipeline()
@@ -192,9 +195,9 @@ type Chain = <T>(command: Command<any, T>) => Pipeline;
192
195
*
193
196
* ```
194
197
*/
195
- export class Pipeline {
198
+ export class Pipeline < TCommands extends Command < any , any > [ ] = [ ] > {
196
199
private client : Requester ;
197
- private commands : Command < unknown , unknown > [ ] ;
200
+ private commands : TCommands ;
198
201
private commandOptions ?: CommandOptions < any , any > ;
199
202
private multiExec : boolean ;
200
203
constructor ( opts : {
@@ -204,7 +207,7 @@ export class Pipeline {
204
207
} ) {
205
208
this . client = opts . client ;
206
209
207
- this . commands = [ ] ;
210
+ this . commands = ( [ ] as unknown ) as TCommands ; // the TCommands generic in the class definition is only used for carrying through chained command types and should never be explicitly set when instantiating the class
208
211
this . commandOptions = opts . commandOptions ;
209
212
this . multiExec = opts . multiExec ?? false ;
210
213
}
@@ -214,13 +217,16 @@ export class Pipeline {
214
217
*
215
218
* Returns an array with the results of all pipelined commands.
216
219
*
217
- * You can define a return type manually to make working in typescript easier
220
+ * If all commands are statically chained from start to finish, types are inferred. You can still define a return type manually if necessary though:
218
221
* ```ts
219
- * redis.pipeline().get("key").exec<[{ greeting: string }]>()
222
+ * const p = redis.pipeline()
223
+ * p.get("key")
224
+ * const result = p.exec<[{ greeting: string }]>()
220
225
* ```
221
226
*/
222
227
exec = async <
223
- TCommandResults extends unknown [ ] = unknown [ ] ,
228
+ TCommandResults extends unknown [ ] = [ ] extends TCommands ? unknown [ ]
229
+ : InferResponseData < TCommands > ,
224
230
> ( ) : Promise < TCommandResults > => {
225
231
if ( this . commands . length === 0 ) {
226
232
throw new Error ( "Pipeline is empty" ) ;
@@ -245,12 +251,14 @@ export class Pipeline {
245
251
} ;
246
252
247
253
/**
248
- * Pushes a command into the pipelien and returns a chainable instance of the
254
+ * Pushes a command into the pipeline and returns a chainable instance of the
249
255
* pipeline
250
256
*/
251
- private chain < T > ( command : Command < any , T > ) : this {
257
+ private chain < T > (
258
+ command : Command < any , T > ,
259
+ ) : Pipeline < [ ...TCommands , Command < any , T > ] > {
252
260
this . commands . push ( command ) ;
253
- return this ;
261
+ return this as any ; // TS thinks we're returning Pipeline<[]> here, because we're not creating a new instance of the class, hence the cast
254
262
}
255
263
256
264
/**
@@ -274,14 +282,18 @@ export class Pipeline {
274
282
destinationKey : string ,
275
283
sourceKey : string ,
276
284
...sourceKeys : string [ ]
277
- ) : Pipeline ;
278
- ( op : "not" , destinationKey : string , sourceKey : string ) : Pipeline ;
285
+ ) : Pipeline < [ ...TCommands , BitOpCommand ] > ;
286
+ (
287
+ op : "not" ,
288
+ destinationKey : string ,
289
+ sourceKey : string ,
290
+ ) : Pipeline < [ ...TCommands , BitOpCommand ] > ;
279
291
} = (
280
292
op : "and" | "or" | "xor" | "not" ,
281
293
destinationKey : string ,
282
294
sourceKey : string ,
283
295
...sourceKeys : string [ ]
284
- ) : Pipeline =>
296
+ ) =>
285
297
this . chain (
286
298
new BitOpCommand (
287
299
[ op as any , destinationKey , sourceKey , ...sourceKeys ] ,
@@ -549,7 +561,7 @@ export class Pipeline {
549
561
direction : "before" | "after" ,
550
562
pivot : TData ,
551
563
value : TData ,
552
- ) : Pipeline =>
564
+ ) =>
553
565
this . chain (
554
566
new LInsertCommand < TData > (
555
567
[ key , direction , pivot , value ] ,
@@ -1070,30 +1082,7 @@ export class Pipeline {
1070
1082
/**
1071
1083
* @see https://redis.io/commands/?group=json
1072
1084
*/
1073
- get json ( ) : {
1074
- arrappend : ( ...args : CommandArgs < typeof JsonArrAppendCommand > ) => Pipeline ;
1075
- arrindex : ( ...args : CommandArgs < typeof JsonArrIndexCommand > ) => Pipeline ;
1076
- arrinsert : ( ...args : CommandArgs < typeof JsonArrInsertCommand > ) => Pipeline ;
1077
- arrlen : ( ...args : CommandArgs < typeof JsonArrLenCommand > ) => Pipeline ;
1078
- arrpop : ( ...args : CommandArgs < typeof JsonArrPopCommand > ) => Pipeline ;
1079
- arrtrim : ( ...args : CommandArgs < typeof JsonArrTrimCommand > ) => Pipeline ;
1080
- clear : ( ...args : CommandArgs < typeof JsonClearCommand > ) => Pipeline ;
1081
- del : ( ...args : CommandArgs < typeof JsonDelCommand > ) => Pipeline ;
1082
- forget : ( ...args : CommandArgs < typeof JsonForgetCommand > ) => Pipeline ;
1083
- get : ( ...args : CommandArgs < typeof JsonGetCommand > ) => Pipeline ;
1084
- mget : ( ...args : CommandArgs < typeof JsonMGetCommand > ) => Pipeline ;
1085
- numincrby : ( ...args : CommandArgs < typeof JsonNumIncrByCommand > ) => Pipeline ;
1086
- nummultby : ( ...args : CommandArgs < typeof JsonNumMultByCommand > ) => Pipeline ;
1087
- objkeys : ( ...args : CommandArgs < typeof JsonObjKeysCommand > ) => Pipeline ;
1088
- objlen : ( ...args : CommandArgs < typeof JsonObjLenCommand > ) => Pipeline ;
1089
- resp : ( ...args : CommandArgs < typeof JsonRespCommand > ) => Pipeline ;
1090
- set : ( ...args : CommandArgs < typeof JsonSetCommand > ) => Pipeline ;
1091
- strappend : ( ...args : CommandArgs < typeof JsonStrAppendCommand > ) => Pipeline ;
1092
- strlen : ( ...args : CommandArgs < typeof JsonStrLenCommand > ) => Pipeline ;
1093
- toggle : ( ...args : CommandArgs < typeof JsonToggleCommand > ) => Pipeline ;
1094
- type : ( ...args : CommandArgs < typeof JsonTypeCommand > ) => Pipeline ;
1095
- } {
1096
- // For some reason we needed to define the types manually, otherwise Deno wouldn't build it
1085
+ get json ( ) {
1097
1086
return {
1098
1087
/**
1099
1088
* @see https://redis.io/commands/json.arrappend
0 commit comments