Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added lua scripts support in node. #775

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions node/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export { Script } from "glide-rs";
export {
BaseClientConfiguration,
ProtocolVersion,
ReturnType,
ScriptOptions,
} from "./src/BaseClient";
export {
ExpireOptions,
Expand Down
59 changes: 56 additions & 3 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
DEFAULT_TIMEOUT_IN_MILLISECONDS,
Script,
StartSocketConnection,
valueFromSplitPointer,
} from "glide-rs";
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -297,7 +309,10 @@ export class BaseClient {
* @internal
*/
protected createWritePromise<T>(
command: redis_request.Command | redis_request.Command[],
command:
| redis_request.Command
| redis_request.Command[]
| redis_request.ScriptInvocation,
route?: redis_request.Routes
): Promise<T> {
if (this.isClosed) {
Expand All @@ -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)
Expand All @@ -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;

Expand Down Expand Up @@ -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<ReturnType> {
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
Expand Down
42 changes: 42 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ProtocolVersion,
RedisClient,
RedisClusterClient,
Script,
parseInfoResponse,
} from "../";
import { Client, GetAndSetRandomValue, getFirstResult } from "./TestUtilities";
Expand Down Expand Up @@ -1203,6 +1204,47 @@ export function runBaseTests<Context>(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])");
shachlanAmazon marked this conversation as resolved.
Show resolved Hide resolved
expect(
await client.invokeScript(script, { keys: [key1] })
).toEqual("value1");

expect(
await client.invokeScript(script, { keys: [key2] })
).toEqual("value2");
});
},
config.timeout
);
}

export function runCommonTests<Context>(config: {
Expand Down