diff --git a/node/index.ts b/node/index.ts index b3ffacd2bc..165848d374 100644 --- a/node/index.ts +++ b/node/index.ts @@ -2,21 +2,23 @@ export { BaseClientConfiguration, ProtocolVersion, ReturnType, + ScriptOptions } from "./src/BaseClient"; export { ExpireOptions, InfoOptions, SetOptions, - parseInfoResponse, + parseInfoResponse } from "./src/Commands"; export { ClosingError, ExecAbortError, RedisError, RequestError, - TimeoutError, + TimeoutError } from "./src/Errors"; export { Logger } from "./src/Logger"; export { RedisClient } from "./src/RedisClient"; export { RedisClusterClient } from "./src/RedisClusterClient"; +export { ScriptObject as Script } from "./src/Script"; export { ClusterTransaction, Transaction } from "./src/Transaction"; diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 9d672ed751..3920545663 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -56,6 +56,7 @@ import { } from "./Errors"; import { Logger } from "./Logger"; import { connection_request, redis_request, response } from "./ProtobufMessage"; +import { ScriptObject } from "./Script"; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ type PromiseFunction = (value?: any) => void; @@ -154,6 +155,17 @@ export type BaseClientConfiguration = { clientName?: string; }; +export type ScriptOptions = { + /** + * The keys that are used in the script. + */ + keys?: string[]; + /** + * The arguments for the script. + */ + args?: string[]; +}; + function getRequestErrorClass( type: response.RequestErrorType | null | undefined ): typeof RequestError { @@ -313,6 +325,41 @@ export class BaseClient { }); } + /** + * @internal + */ + protected createScriptWritePromise( + hash: string, + keys?: string[], + args?: string[], + ): Promise { + if (this.isClosed) { + throw new ClosingError( + "Unable to execute requests; the client is closed. Please create a new client." + ); + } + + return new Promise((resolve, reject) => { + const callbackIndex = this.getCallbackIndex(); + this.promiseCallbackFunctions[callbackIndex] = [resolve, reject]; + const message = redis_request.RedisRequest.create({ + callbackIdx: callbackIndex, + scriptInvocation: redis_request.ScriptInvocation.create({ + hash: hash, + keys: keys, + args: args + }) + }); + + this.writeOrBufferRequest( + message, + (message: redis_request.RedisRequest, writer: Writer) => { + redis_request.RedisRequest.encodeDelimited(message, writer); + } + ); + }); + } + private writeOrBufferRedisRequest( callbackIdx: number, command: redis_request.Command | redis_request.Command[], @@ -888,6 +935,16 @@ export class BaseClient { return this.createWritePromise(createTTL(key)); } + /** Invoke the given script with its keys and arguments. + * + * @param script - The script to invoke. + * @param option - The script option that includes the script keys and arguments. + * @returns a value that depends on the script that was executed. + */ + public invokeScript(script: ScriptObject, option?: ScriptOptions): Promise { + return this.createScriptWritePromise(script.getHash(), option?.keys, option?.args); + } + private readonly MAP_READ_FROM_STRATEGY: Record< ReadFrom, connection_request.ReadFrom diff --git a/node/src/Script.ts b/node/src/Script.ts new file mode 100644 index 0000000000..f74f4fb0ed --- /dev/null +++ b/node/src/Script.ts @@ -0,0 +1,13 @@ +import { Script } from "glide-rs"; + +export class ScriptObject { + private script: Script; + + constructor(script: string) { + this.script = new Script(script); + } + + public getHash(): string{ + return this.script.getHash(); + } +} diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index f6bdb98c1e..18d48559ca 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -8,6 +8,7 @@ import { ProtocolVersion, RedisClient, RedisClusterClient, + Script, parseInfoResponse, } from "../"; import { Client, GetAndSetRandomValue, getFirstResult } from "./TestUtilities"; @@ -1203,6 +1204,41 @@ export function runBaseTests(config: { }, config.timeout ); + + it( + "script test", + async () => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const key2 = uuidv4(); + + let script = new Script("return 'Hello'"); + expect(await client.invokeScript(script)).toEqual("Hello"); + + script = new Script("return redis.call('SET', KEYS[1], ARGV[1])"); + expect( + await client.invokeScript(script, { + keys: [key1], + args: ["bar"], + }) + ).toEqual("OK"); + + /// Reuse the same script with different parameters. + expect( + await client.invokeScript(script, { + keys: [key2], + args: ["bar"], + }) + ).toEqual("OK"); + + script = new Script("return redis.call('GET', KEYS[1])"); + expect( + await client.invokeScript(script, { keys: [key1] }) + ).toEqual("bar"); + }); + }, + config.timeout + ); } export function runCommonTests(config: {