From 8a0675e097fb8f6c8c2a7878b0e1d4397f4146b7 Mon Sep 17 00:00:00 2001 From: Adan Date: Thu, 11 Jan 2024 17:05:50 +0000 Subject: [PATCH] added lua scripts support in node. --- node/index.ts | 2 ++ node/src/BaseClient.ts | 59 +++++++++++++++++++++++++++++++++++++-- node/tests/SharedTests.ts | 42 ++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/node/index.ts b/node/index.ts index b3ffacd2bc..de3fd6be02 100644 --- a/node/index.ts +++ b/node/index.ts @@ -1,7 +1,9 @@ +export { Script } from "glide-rs"; export { BaseClientConfiguration, ProtocolVersion, ReturnType, + ScriptOptions, } from "./src/BaseClient"; export { ExpireOptions, diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 9d672ed751..f9fef9e608 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -1,5 +1,6 @@ import { DEFAULT_TIMEOUT_IN_MILLISECONDS, + Script, StartSocketConnection, valueFromSplitPointer, } from "glide-rs"; @@ -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 { @@ -297,7 +309,10 @@ export class BaseClient { * @internal */ protected createWritePromise( - command: redis_request.Command | redis_request.Command[], + command: + | redis_request.Command + | redis_request.Command[] + | redis_request.ScriptInvocation, route?: redis_request.Routes ): Promise { if (this.isClosed) { @@ -315,7 +330,10 @@ export class BaseClient { private writeOrBufferRedisRequest( callbackIdx: number, - command: redis_request.Command | redis_request.Command[], + command: + | redis_request.Command + | redis_request.Command[] + | redis_request.ScriptInvocation, route?: redis_request.Routes ) { const message = Array.isArray(command) @@ -325,9 +343,14 @@ export class BaseClient { commands: command, }), }) - : redis_request.RedisRequest.create({ + : command instanceof redis_request.Command + ? redis_request.RedisRequest.create({ callbackIdx, singleCommand: command, + }) + : redis_request.RedisRequest.create({ + callbackIdx, + scriptInvocation: command, }); message.route = route; @@ -888,6 +911,36 @@ export class BaseClient { return this.createWritePromise(createTTL(key)); } + /** Invokes a Lua script with its keys and arguments. + * This method simplifies the process of invoking scripts on a Redis server by using an object that represents a Lua script. + * The script loading, argument preparation, and execution will all be handled internally. If the script has not already been loaded, + * it will be loaded automatically using the Redis `SCRIPT LOAD` command. After that, it will be invoked using the Redis `EVALSHA` command + * + * @param script - The Lua script to execute. + * @param options - The script option that contains keys and arguments for the script. + * @returns a value that depends on the script that was executed. + * + * @example + * const luaScript = "return \{ KEYS[1], ARGV[1] \}"; + * const scriptOptions = \{ + * keys: ["foo"], + * args: ["bar"], + * \}; + * await invokeScript(luaScript, scriptOptions); + * ["foo", "bar"] + */ + public invokeScript( + script: Script, + option?: ScriptOptions + ): Promise { + const scriptInvocation = redis_request.ScriptInvocation.create({ + hash: script.getHash(), + keys: option?.keys, + args: option?.args, + }); + return this.createWritePromise(scriptInvocation); + } + private readonly MAP_READ_FROM_STRATEGY: Record< ReadFrom, connection_request.ReadFrom diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index f6bdb98c1e..84df565641 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,47 @@ 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: ["value1"], + }) + ).toEqual("OK"); + + /// Reuse the same script with different parameters. + expect( + await client.invokeScript(script, { + keys: [key2], + args: ["value2"], + }) + ).toEqual("OK"); + + script = new Script("return redis.call('GET', KEYS[1])"); + expect( + await client.invokeScript(script, { keys: [key1] }) + ).toEqual("value1"); + + expect( + await client.invokeScript(script, { keys: [key2] }) + ).toEqual("value2"); + }); + }, + config.timeout + ); } export function runCommonTests(config: {