Skip to content

Commit 2fa33db

Browse files
committed
legacy mode multi
1 parent dc094ea commit 2fa33db

File tree

5 files changed

+136
-106
lines changed

5 files changed

+136
-106
lines changed

packages/client/lib/client/index.ts

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -689,38 +689,71 @@ export default class RedisClient<
689689
);
690690
}
691691

692-
MULTI(): RedisClientMultiCommandType<[], M, F, S, RESP, FLAGS> {
693-
return new (this as any).Multi(
694-
async (
692+
/**
693+
* @internal
694+
*/
695+
async executePipeline(commands: Array<RedisMultiQueuedCommand>) {
696+
if (!this._socket.isOpen) {
697+
return Promise.reject(new ClientClosedError());
698+
}
699+
700+
const promise = Promise.all(
701+
commands.map(({ args }) => this._queue.addCommand(args, {
702+
flags: (this as ProxyClient).commandOptions?.flags
703+
}))
704+
);
705+
this._tick();
706+
return promise;
707+
}
708+
709+
/**
710+
* @internal
711+
*/
712+
async executeMulti(
695713
commands: Array<RedisMultiQueuedCommand>,
696-
selectedDB?: number,
697-
chainId?: symbol
698-
) => {
714+
selectedDB?: number
715+
) {
699716
if (!this._socket.isOpen) {
700717
return Promise.reject(new ClientClosedError());
701718
}
702719

703720
const flags = (this as ProxyClient).commandOptions?.flags,
704-
promise = chainId ?
705-
// if `chainId` has a value, it's a `MULTI` (and not "pipeline") - need to add the `MULTI` and `EXEC` commands
706-
Promise.all([
721+
chainId = Symbol('MULTI Chain'),
722+
promises = [
707723
this._queue.addCommand(['MULTI'], { chainId }),
708-
this._addMultiCommands(commands, chainId),
709-
this._queue.addCommand(['EXEC'], { chainId, flags })
710-
]) :
711-
this._addMultiCommands(commands, undefined, flags);
724+
];
725+
726+
for (const { args } of commands) {
727+
promises.push(
728+
this._queue.addCommand(args, {
729+
chainId,
730+
flags
731+
})
732+
);
733+
}
734+
735+
promises.push(
736+
this._queue.addCommand(['EXEC'], { chainId })
737+
);
712738

713739
this._tick();
714740

715-
const results = await promise;
741+
const results = await Promise.all(promises),
742+
execResult = results[results.length - 1];
743+
744+
if (execResult === null) {
745+
throw new WatchError();
746+
}
716747

717748
if (selectedDB !== undefined) {
718749
this._selectedDB = selectedDB;
719750
}
720751

721-
return results;
752+
return execResult as Array<unknown>;
722753
}
723-
);
754+
755+
MULTI(): RedisClientMultiCommandType<[], M, F, S, RESP, FLAGS> {
756+
return new (this as any).Multi(this);
724757
}
725758

726759
multi = this.MULTI;

packages/client/lib/client/legacy-mode.ts

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { RedisClientType } from '.';
33
import { getTransformReply } from '../commander';
44
import { ErrorReply } from '../errors';
55
import COMMANDS from '../commands';
6+
import RedisMultiCommand from '../multi-command';
67

78
type LegacyArgument = string | Buffer | number | Date;
89

@@ -15,10 +16,8 @@ type LegacyCommandArguments = LegacyArguments | [
1516
callback: LegacyCallback
1617
];
1718

18-
export type CommandSignature = (...args: LegacyCommandArguments) => void;
19-
2019
type WithCommands = {
21-
[P in keyof typeof COMMANDS]: CommandSignature;
20+
[P in keyof typeof COMMANDS]: (...args: LegacyCommandArguments) => void;
2221
};
2322

2423
export type RedisLegacyClientType = RedisLegacyClient & WithCommands;
@@ -30,16 +29,16 @@ export class RedisLegacyClient {
3029
callback = args.pop() as LegacyCallback;
3130
}
3231

33-
RedisLegacyClient._pushArguments(redisArgs, args as LegacyArguments);
32+
RedisLegacyClient.pushArguments(redisArgs, args as LegacyArguments);
3433

3534
return callback;
3635
}
3736

38-
private static _pushArguments(redisArgs: CommandArguments, args: LegacyArguments) {
37+
static pushArguments(redisArgs: CommandArguments, args: LegacyArguments) {
3938
for (let i = 0; i < args.length; ++i) {
4039
const arg = args[i];
4140
if (Array.isArray(arg)) {
42-
RedisLegacyClient._pushArguments(redisArgs, arg);
41+
RedisLegacyClient.pushArguments(redisArgs, arg);
4342
} else {
4443
redisArgs.push(
4544
typeof arg === 'number' || arg instanceof Date ?
@@ -50,14 +49,14 @@ export class RedisLegacyClient {
5049
}
5150
}
5251

53-
private static _getTransformReply(command: Command, resp: RespVersions) {
52+
static getTransformReply(command: Command, resp: RespVersions) {
5453
return command.TRANSFORM_LEGACY_REPLY ?
5554
getTransformReply(command, resp) :
5655
undefined;
5756
}
5857

5958
private static _createCommand(name: string, command: Command, resp: RespVersions) {
60-
const transformReply = RedisLegacyClient._getTransformReply(command, resp);
59+
const transformReply = RedisLegacyClient.getTransformReply(command, resp);
6160
return async function (this: RedisLegacyClient, ...args: LegacyCommandArguments) {
6261
const redisArgs = [name],
6362
callback = RedisLegacyClient._transformArguments(redisArgs, args),
@@ -74,6 +73,8 @@ export class RedisLegacyClient {
7473
};
7574
}
7675

76+
private _Multi: ReturnType<typeof LegacyMultiCommand['factory']>;
77+
7778
constructor(
7879
private _client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>
7980
) {
@@ -87,7 +88,7 @@ export class RedisLegacyClient {
8788
);
8889
}
8990

90-
// TODO: Multi
91+
this._Multi = LegacyMultiCommand.factory(RESP);
9192
}
9293

9394
sendCommand(...args: LegacyArguments) {
@@ -104,4 +105,68 @@ export class RedisLegacyClient {
104105
.then(reply => callback(null, reply))
105106
.catch(err => callback(err));
106107
}
108+
109+
multi() {
110+
return this._Multi(this._client);
111+
}
112+
}
113+
114+
type MultiWithCommands = {
115+
[P in keyof typeof COMMANDS]: (...args: LegacyCommandArguments) => RedisLegacyMultiType;
116+
};
117+
118+
export type RedisLegacyMultiType = Omit<LegacyMultiCommand, '_client'> & MultiWithCommands;
119+
120+
class LegacyMultiCommand extends RedisMultiCommand {
121+
private static _createCommand(name: string, command: Command, resp: RespVersions) {
122+
const transformReply = RedisLegacyClient.getTransformReply(command, resp);
123+
return function (this: LegacyMultiCommand, ...args: LegacyArguments) {
124+
const redisArgs = [name];
125+
RedisLegacyClient.pushArguments(redisArgs, args);
126+
return this.addCommand(redisArgs, transformReply);
127+
};
128+
}
129+
130+
static factory(resp: RespVersions) {
131+
const Multi = class extends LegacyMultiCommand {};
132+
133+
for (const [name, command] of Object.entries(COMMANDS)) {
134+
// TODO: as any?
135+
(Multi as any).prototype[name] = LegacyMultiCommand._createCommand(
136+
name,
137+
command,
138+
resp
139+
);
140+
}
141+
142+
return (client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>) => {
143+
return new Multi(client) as unknown as RedisLegacyMultiType;
144+
};
145+
}
146+
147+
private _client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>;
148+
149+
constructor(client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>) {
150+
super();
151+
this._client = client;
152+
}
153+
154+
sendCommand(...args: LegacyArguments) {
155+
const redisArgs: CommandArguments = [];
156+
RedisLegacyClient.pushArguments(redisArgs, args);
157+
return this.addCommand(redisArgs);
158+
}
159+
160+
exec(cb?: (err: ErrorReply | null, replies?: Array<unknown>) => unknown) {
161+
const promise = this._client.executeMulti(this.queue);
162+
163+
if (!cb) {
164+
promise.catch(err => this._client.emit('error', err));
165+
return;
166+
}
167+
168+
promise
169+
.then(results => cb(null, this.transformReplies(results)))
170+
.catch(err => cb?.(err));
171+
}
107172
}

packages/client/lib/client/linked-list.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { SinglyLinkedList, DoublyLinkedList } from './linked-list';
22
import { equal, deepEqual } from 'assert/strict';
33

4-
describe.only('DoublyLinkedList', () => {
4+
describe('DoublyLinkedList', () => {
55
const list = new DoublyLinkedList();
66

77
it('should start empty', () => {
@@ -78,7 +78,7 @@ describe.only('DoublyLinkedList', () => {
7878
});
7979
});
8080

81-
describe.only('SinglyLinkedList', () => {
81+
describe('SinglyLinkedList', () => {
8282
const list = new SinglyLinkedList();
8383

8484
it('should start empty', () => {

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

Lines changed: 10 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import COMMANDS from '../commands';
22
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
33
import { ReplyWithFlags, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, Flags, ReplyUnion } from '../RESP/types';
44
import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander';
5+
import { RedisClientType } from '.';
56

67
type CommandSignature<
78
REPLIES extends Array<unknown>,
@@ -90,7 +91,7 @@ type MULTI_REPLY = {
9091

9192
type MultiReply = MULTI_REPLY[keyof MULTI_REPLY];
9293

93-
type ReplyType<T extends MultiReply, REPLIES> = T extends MULTI_REPLY['TYPED'] ? REPLIES : Array<ReplyUnion>;
94+
type ReplyType<T extends MultiReply, REPLIES> = T extends MULTI_REPLY['TYPED'] ? REPLIES : Array<unknown>;
9495

9596
export type RedisClientMultiExecutor = (
9697
queue: Array<RedisMultiQueuedCommand>,
@@ -161,78 +162,26 @@ export default class RedisClientMultiCommand<REPLIES = []> extends RedisMultiCom
161162
});
162163
}
163164

164-
// readonly #multi = new RedisMultiCommand();
165-
readonly #executor: RedisClientMultiExecutor;
166-
// readonly v4: Record<string, any> = {};
165+
readonly #client: RedisClientType;
167166
#selectedDB?: number;
168167

169-
constructor(executor: RedisClientMultiExecutor, legacyMode = false) {
168+
constructor(client: RedisClientType) {
170169
super();
171-
this.#executor = executor;
172-
// if (legacyMode) {
173-
// this.#legacyMode();
174-
// }
170+
this.#client = client;
175171
}
176172

177-
// #legacyMode(): void {
178-
// this.v4.addCommand = this.addCommand.bind(this);
179-
// (this as any).addCommand = (...args: Array<any>): this => {
180-
// this.#multi.addCommand(transformLegacyCommandArguments(args));
181-
// return this;
182-
// };
183-
// this.v4.exec = this.exec.bind(this);
184-
// (this as any).exec = (callback?: (err: Error | null, replies?: Array<unknown>) => unknown): void => {
185-
// this.v4.exec()
186-
// .then((reply: Array<unknown>) => {
187-
// if (!callback) return;
188-
189-
// callback(null, reply);
190-
// })
191-
// .catch((err: Error) => {
192-
// if (!callback) {
193-
// // this.emit('error', err);
194-
// return;
195-
// }
196-
197-
// callback(err);
198-
// });
199-
// };
200-
201-
// for (const [name, command] of Object.entries(COMMANDS as RedisCommands)) {
202-
// this.#defineLegacyCommand(name, command);
203-
// (this as any)[name.toLowerCase()] ??= (this as any)[name];
204-
// }
205-
// }
206-
207-
// #defineLegacyCommand(this: any, name: string, command?: RedisCommand): void {
208-
// this.v4[name] = this[name].bind(this.v4);
209-
// this[name] = command && command.TRANSFORM_LEGACY_REPLY && command.transformReply ?
210-
// (...args: Array<unknown>) => {
211-
// this.#multi.addCommand(
212-
// [name, ...transformLegacyCommandArguments(args)],
213-
// command.transformReply
214-
// );
215-
// return this;
216-
// } :
217-
// (...args: Array<unknown>) => this.addCommand(name, ...args);
218-
// }
219-
220173
SELECT(db: number, transformReply?: TransformReply): this {
221174
this.#selectedDB = db;
222175
return this.addCommand(['SELECT', db.toString()], transformReply);
223176
}
224177

225178
select = this.SELECT;
226179

227-
async exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline = false) {
180+
async exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline = false): Promise<ReplyType<T, REPLIES>> {
228181
if (execAsPipeline) return this.execAsPipeline<T>();
229182

230-
return this.handleExecReplies(
231-
await this.#executor(
232-
this.queue,
233-
this.#selectedDB,
234-
RedisMultiCommand.generateChainId()
235-
)
183+
return this.transformReplies(
184+
await this.#client.executeMulti(this.queue, this.#selectedDB)
236185
) as ReplyType<T, REPLIES>;
237186
}
238187

@@ -242,14 +191,11 @@ export default class RedisClientMultiCommand<REPLIES = []> extends RedisMultiCom
242191
return this.exec<MULTI_REPLY['TYPED']>(execAsPipeline);
243192
}
244193

245-
async execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>() {
194+
async execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>(): Promise<ReplyType<T, REPLIES>> {
246195
if (this.queue.length === 0) return [] as ReplyType<T, REPLIES>;
247196

248197
return this.transformReplies(
249-
await this.#executor(
250-
this.queue,
251-
this.#selectedDB
252-
)
198+
await this.#client.executePipeline(this.queue)
253199
) as ReplyType<T, REPLIES>;
254200
}
255201

0 commit comments

Comments
 (0)