Skip to content

Commit 605d5c1

Browse files
committed
implement parser conversion with firs GET command implemented
1 parent f925235 commit 605d5c1

File tree

12 files changed

+562
-170
lines changed

12 files changed

+562
-170
lines changed

packages/client/lib/RESP/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CommandParser } from '../client/parser';
12
import { BlobError, SimpleError } from '../errors';
23
import { RedisScriptConfig, SHA1 } from '../lua-script';
34
import { RESP_TYPES } from './decoder';
@@ -272,6 +273,7 @@ export type Command = {
272273
*/
273274
IS_FORWARD_COMMAND?: boolean;
274275
// POLICIES?: CommandPolicies;
276+
parseCommand?(this: void, parser: CommandParser, ...args: Array<any>): void;
275277
transformArguments(this: void, ...args: Array<any>): CommandArguments;
276278
TRANSFORM_LEGACY_REPLY?: boolean;
277279
transformReply: TransformReply | Record<RespVersions, TransformReply>;

packages/client/lib/client/index.ts

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { ClientClosedError, ClientOfflineError, DisconnectsClientError, WatchErr
77
import { URL } from 'node:url';
88
import { TcpSocketConnectOpts } from 'node:net';
99
import { PUBSUB_TYPE, PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
10-
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply } from '../RESP/types';
10+
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply } from '../RESP/types';
1111
import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command';
1212
import { RedisMultiQueuedCommand } from '../multi-command';
1313
import HELLO, { HelloOptions } from '../commands/HELLO';
1414
import { ScanOptions, ScanCommonOptions } from '../commands/SCAN';
1515
import { RedisLegacyClient, RedisLegacyClientType } from './legacy-mode';
1616
import { RedisPoolOptions, RedisClientPool } from './pool';
1717
import { RedisVariadicArgument, pushVariadicArguments } from '../commands/generic-transformers';
18+
import { BasicCommandParser, CommandParser } from './parser';
1819

1920
export interface RedisClientOptions<
2021
M extends RedisModules = RedisModules,
@@ -151,52 +152,84 @@ export default class RedisClient<
151152
> extends EventEmitter {
152153
static #createCommand(command: Command, resp: RespVersions) {
153154
const transformReply = getTransformReply(command, resp);
155+
154156
return async function (this: ProxyClient, ...args: Array<unknown>) {
155-
const redisArgs = command.transformArguments(...args),
156-
reply = await this.sendCommand(redisArgs, this._commandOptions);
157-
return transformReply ?
158-
transformReply(reply, redisArgs.preserve) :
159-
reply;
160-
};
157+
if (command.parseCommand) {
158+
const parser = this._self.#newCommandParser(resp);
159+
command.parseCommand(parser, ...args);
160+
161+
return this.executeCommand(undefined, parser, this._commandOptions, transformReply);
162+
} else {
163+
const redisArgs = command.transformArguments(...args),
164+
reply = await this.sendCommand(redisArgs, this._commandOptions);
165+
return transformReply ?
166+
transformReply(reply, redisArgs.preserve) :
167+
reply;
168+
};
169+
}
161170
}
162171

163172
static #createModuleCommand(command: Command, resp: RespVersions) {
164173
const transformReply = getTransformReply(command, resp);
174+
165175
return async function (this: NamespaceProxyClient, ...args: Array<unknown>) {
166-
const redisArgs = command.transformArguments(...args),
167-
reply = await this._self.sendCommand(redisArgs, this._self._commandOptions);
168-
return transformReply ?
169-
transformReply(reply, redisArgs.preserve) :
170-
reply;
176+
if (command.parseCommand) {
177+
const parser = this._self._self.#newCommandParser(resp);
178+
command.parseCommand(parser, ...args);
179+
180+
return this._self.executeCommand(undefined, parser, this._self._commandOptions, transformReply);
181+
} else {
182+
const redisArgs = command.transformArguments(...args),
183+
reply = await this._self.sendCommand(redisArgs, this._self._commandOptions);
184+
return transformReply ?
185+
transformReply(reply, redisArgs.preserve) :
186+
reply;
187+
}
171188
};
172189
}
173190

174191
static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
175-
const prefix = functionArgumentsPrefix(name, fn),
176-
transformReply = getTransformReply(fn, resp);
192+
const prefix = functionArgumentsPrefix(name, fn);
193+
const transformReply = getTransformReply(fn, resp);
194+
177195
return async function (this: NamespaceProxyClient, ...args: Array<unknown>) {
178-
const fnArgs = fn.transformArguments(...args),
179-
reply = await this._self.sendCommand(
180-
prefix.concat(fnArgs),
181-
this._self._commandOptions
182-
);
183-
return transformReply ?
184-
transformReply(reply, fnArgs.preserve) :
185-
reply;
196+
if (fn.parseCommand) {
197+
const parser = this._self._self.#newCommandParser(resp);
198+
fn.parseCommand(parser, ...args);
199+
200+
return this._self.executeCommand(prefix, parser, this._self._commandOptions, transformReply);
201+
} else {
202+
const fnArgs = fn.transformArguments(...args),
203+
reply = await this._self.sendCommand(
204+
prefix.concat(fnArgs),
205+
this._self._commandOptions
206+
);
207+
return transformReply ?
208+
transformReply(reply, fnArgs.preserve) :
209+
reply;
210+
}
186211
};
187212
}
188213

189214
static #createScriptCommand(script: RedisScript, resp: RespVersions) {
190-
const prefix = scriptArgumentsPrefix(script),
191-
transformReply = getTransformReply(script, resp);
215+
const prefix = scriptArgumentsPrefix(script);
216+
const transformReply = getTransformReply(script, resp);
217+
192218
return async function (this: ProxyClient, ...args: Array<unknown>) {
193-
const scriptArgs = script.transformArguments(...args),
194-
redisArgs = prefix.concat(scriptArgs),
195-
reply = await this.executeScript(script, redisArgs, this._commandOptions);
196-
return transformReply ?
197-
transformReply(reply, scriptArgs.preserve) :
198-
reply;
199-
};
219+
if (script.parseCommand) {
220+
const parser = this._self.#newCommandParser(resp);
221+
script.parseCommand(parser, ...args);
222+
223+
return this.executeCommand(prefix, parser, this._commandOptions, transformReply);
224+
} else {
225+
const scriptArgs = script.transformArguments(...args),
226+
redisArgs = prefix.concat(scriptArgs),
227+
reply = await this.executeScript(script, redisArgs, this._commandOptions);
228+
return transformReply ?
229+
transformReply(reply, scriptArgs.preserve) :
230+
reply;
231+
};
232+
}
200233
}
201234

202235
static factory<
@@ -309,6 +342,10 @@ export default class RedisClient<
309342
this._self.#dirtyWatch = msg;
310343
}
311344

345+
#newCommandParser(resp: RespVersions): CommandParser {
346+
return new BasicCommandParser(resp);
347+
}
348+
312349
constructor(options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>) {
313350
super();
314351
this.#options = this.#initiateOptions(options);
@@ -573,6 +610,24 @@ export default class RedisClient<
573610
return this as unknown as RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
574611
}
575612

613+
async executeCommand<T = ReplyUnion>(
614+
prefix: Array<string | Buffer> | undefined,
615+
parser: CommandParser,
616+
commandOptions: CommandOptions<TYPE_MAPPING> | undefined,
617+
transformReply: TransformReply | undefined
618+
) {
619+
const redisArgs = prefix ? prefix.concat(parser.redisArgs) : parser.redisArgs;
620+
const fn = () => { return this.sendCommand(redisArgs, commandOptions) };
621+
622+
const reply = await fn();
623+
624+
if (transformReply) {
625+
return transformReply(reply, parser.preserve);
626+
}
627+
628+
return reply;
629+
}
630+
576631
sendCommand<T = ReplyUnion>(
577632
args: Array<RedisArgument>,
578633
options?: CommandOptions

packages/client/lib/client/multi-command.ts

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import COMMANDS from '../commands';
22
import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command';
33
import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types';
44
import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander';
5+
import { BasicCommandParser } from './parser';
56

67
type CommandSignature<
78
REPLIES extends Array<unknown>,
@@ -88,31 +89,64 @@ type ExecuteMulti = (commands: Array<RedisMultiQueuedCommand>, selectedDB?: numb
8889
export default class RedisClientMultiCommand<REPLIES = []> {
8990
static #createCommand(command: Command, resp: RespVersions) {
9091
const transformReply = getTransformReply(command, resp);
92+
9193
return function (this: RedisClientMultiCommand, ...args: Array<unknown>) {
94+
let redisArgs: CommandArguments;
95+
96+
if (command.parseCommand) {
97+
const parser = new BasicCommandParser(resp);
98+
command.parseCommand(parser, ...args);
99+
redisArgs = parser.redisArgs;
100+
} else {
101+
redisArgs = command.transformArguments(...args);
102+
}
103+
92104
return this.addCommand(
93-
command.transformArguments(...args),
105+
redisArgs,
94106
transformReply
95107
);
96108
};
97109
}
98110

99111
static #createModuleCommand(command: Command, resp: RespVersions) {
100112
const transformReply = getTransformReply(command, resp);
113+
101114
return function (this: { _self: RedisClientMultiCommand }, ...args: Array<unknown>) {
115+
let redisArgs: CommandArguments;
116+
117+
if (command.parseCommand) {
118+
const parser = new BasicCommandParser(resp);
119+
command.parseCommand(parser, ...args);
120+
redisArgs = parser.redisArgs;
121+
} else {
122+
redisArgs = command.transformArguments(...args);
123+
}
124+
102125
return this._self.addCommand(
103-
command.transformArguments(...args),
126+
redisArgs,
104127
transformReply
105128
);
106129
};
107130
}
108131

109132
static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
110-
const prefix = functionArgumentsPrefix(name, fn),
111-
transformReply = getTransformReply(fn, resp);
133+
const prefix = functionArgumentsPrefix(name, fn);
134+
const transformReply = getTransformReply(fn, resp);
135+
112136
return function (this: { _self: RedisClientMultiCommand }, ...args: Array<unknown>) {
113-
const fnArgs = fn.transformArguments(...args),
114-
redisArgs: CommandArguments = prefix.concat(fnArgs);
137+
let fnArgs: CommandArguments;
138+
139+
if (fn.parseCommand) {
140+
const parser = new BasicCommandParser(resp);
141+
fn.parseCommand(parser, ...args);
142+
fnArgs = parser.redisArgs;
143+
} else {
144+
fnArgs = fn.transformArguments(...args);
145+
}
146+
147+
const redisArgs: CommandArguments = prefix.concat(fnArgs);
115148
redisArgs.preserve = fnArgs.preserve;
149+
116150
return this._self.addCommand(
117151
redisArgs,
118152
transformReply
@@ -122,12 +156,24 @@ export default class RedisClientMultiCommand<REPLIES = []> {
122156

123157
static #createScriptCommand(script: RedisScript, resp: RespVersions) {
124158
const transformReply = getTransformReply(script, resp);
159+
125160
return function (this: RedisClientMultiCommand, ...args: Array<unknown>) {
161+
let redisArgs: CommandArguments;
162+
163+
if (script.parseCommand) {
164+
const parser = new BasicCommandParser(resp);
165+
script.parseCommand(parser, ...args);
166+
redisArgs = parser.redisArgs;
167+
} else {
168+
redisArgs = script.transformArguments(...args);
169+
}
170+
126171
this.#multi.addScript(
127172
script,
128-
script.transformArguments(...args),
173+
redisArgs,
129174
transformReply
130175
);
176+
131177
return this;
132178
};
133179
}

packages/client/lib/client/parser.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { RedisArgument, RespVersions } from "../..";
2+
import { RedisVariadicArgument } from "../commands/generic-transformers";
3+
4+
export interface CommandParser {
5+
redisArgs: Array<RedisArgument>;
6+
respVersion: RespVersions;
7+
preserve: unknown;
8+
9+
push: (arg: RedisArgument) => unknown;
10+
pushVariadic: (vals: RedisVariadicArgument) => unknown;
11+
pushKey: (key: RedisArgument) => unknown; // normal push of keys
12+
pushKeys: (keys: RedisVariadicArgument) => unknown; // push multiple keys at a time
13+
setCachable: () => unknown;
14+
setPreserve: (val: unknown) => unknown;
15+
}
16+
17+
export abstract class AbstractCommandParser implements CommandParser {
18+
#redisArgs: Array<RedisArgument> = [];
19+
#respVersion: RespVersions;
20+
#preserve: unknown;
21+
22+
constructor(respVersion: RespVersions = 2) {
23+
this.#respVersion = respVersion;
24+
}
25+
26+
get redisArgs() {
27+
return this.#redisArgs;
28+
}
29+
30+
get respVersion() {
31+
return this.#respVersion;
32+
}
33+
34+
get preserve() {
35+
return this.#preserve;
36+
}
37+
38+
push(arg: RedisArgument) {
39+
this.#redisArgs.push(arg);
40+
41+
};
42+
43+
pushVariadic(vals: RedisVariadicArgument) {
44+
if (Array.isArray(vals)) {
45+
for (const val of vals) {
46+
this.push(val);
47+
}
48+
} else {
49+
this.push(vals);
50+
}
51+
}
52+
53+
pushKey(key: RedisArgument) {
54+
this.#redisArgs.push(key);
55+
};
56+
57+
pushKeys(keys: RedisVariadicArgument) {
58+
if (Array.isArray(keys)) {
59+
this.#redisArgs.push(...keys);
60+
} else {
61+
this.#redisArgs.push(keys);
62+
}
63+
}
64+
65+
setPreserve(val: unknown) {
66+
this.#preserve = val;
67+
}
68+
69+
setCachable() {};
70+
}
71+
72+
/* Note: I do it this way, where BasicCommandParser extends Abstract without any changes,
73+
and CachedCommandParser extends Abstract with changes, to enable them to be easily
74+
distinguishable at runtime. If Cached extended Basic, then Cached would also be a Basic,
75+
thereby making them harder to distinguish.
76+
*/
77+
export class BasicCommandParser extends AbstractCommandParser {};
78+
79+
export interface ClusterCommandParser extends CommandParser {
80+
firstKey: RedisArgument | undefined;
81+
}
82+
83+
export class BasicClusterCommandParser extends BasicCommandParser implements ClusterCommandParser {
84+
firstKey: RedisArgument | undefined;
85+
86+
override pushKey(key: RedisArgument): void {
87+
if (!this.firstKey) {
88+
this.firstKey = key;
89+
}
90+
super.pushKey(key);
91+
}
92+
}

0 commit comments

Comments
 (0)