Skip to content

Commit

Permalink
Node: Add invokeScript for cluster client. (#2284)
Browse files Browse the repository at this point in the history
* Add `invokeScript` for cluster client.

Signed-off-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]>
Co-authored-by: jonathanl-bq <[email protected]>
  • Loading branch information
3 people committed Sep 12, 2024
1 parent fca3851 commit 0d6a44f
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes
* Node: Added `invokeScript` API with routing for cluster client ([#2284](https://github.com/valkey-io/valkey-glide/pull/2284))
* Python: Replace instances of Redis with Valkey ([#2266](https://github.com/valkey-io/valkey-glide/pull/2266))
* Java: Replace instances of Redis with Valkey ([#2268](https://github.com/valkey-io/valkey-glide/pull/2268))
* Node: Replace instances of Redis with Valkey ([#2260](https://github.com/valkey-io/valkey-glide/pull/2260))
Expand Down
2 changes: 0 additions & 2 deletions node/npm/glide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ function initialize() {
StreamReadOptions,
StreamClaimOptions,
StreamPendingOptions,
ScriptOptions,
ClosingError,
ConfigurationError,
ExecAbortError,
Expand Down Expand Up @@ -284,7 +283,6 @@ function initialize() {
StreamReadGroupOptions,
StreamReadOptions,
StreamPendingOptions,
ScriptOptions,
ClosingError,
ConfigurationError,
ExecAbortError,
Expand Down
42 changes: 11 additions & 31 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,17 +565,6 @@ export interface BaseClientConfiguration {
defaultDecoder?: Decoder;
}

export interface ScriptOptions {
/**
* The keys that are used in the script.
*/
keys?: GlideString[];
/**
* The arguments for the script.
*/
args?: GlideString[];
}

/**
* Enum of Valkey data types
* `STRING`
Expand Down Expand Up @@ -3695,9 +3684,13 @@ export class BaseClient {
* it will be loaded automatically using the `SCRIPT LOAD` command. After that, it will be invoked using the `EVALSHA` command.
*
* @see {@link https://valkey.io/commands/script-load/|SCRIPT LOAD} and {@link https://valkey.io/commands/evalsha/|EVALSHA} on valkey.io for details.
* @remarks When in cluster mode, all `keys` must map to the same hash slot.
*
* @param script - The Lua script to execute.
* @param options - (Optional) See {@link ScriptOptions} and {@link DecoderOption}.
* @param options - (Optional) Additional parameters:
* - (Optional) `keys` : the keys that are used in the script.
* - (Optional) `args`: the arguments for the script.
* - (Optional) `decoder`: see {@link DecoderOption}.
* @returns A value that depends on the script that was executed.
*
* @example
Expand All @@ -3713,28 +3706,15 @@ export class BaseClient {
*/
public async invokeScript(
script: Script,
options?: ScriptOptions & DecoderOption,
options?: {
keys?: GlideString[];
args?: GlideString[];
} & DecoderOption,
): Promise<GlideReturnType> {
const scriptInvocation = command_request.ScriptInvocation.create({
hash: script.getHash(),
keys: options?.keys?.map((item) => {
if (typeof item === "string") {
// Convert the string to a Buffer
return Buffer.from(item);
} else {
// If it's already a Buffer, just return it
return item;
}
}),
args: options?.args?.map((item) => {
if (typeof item === "string") {
// Convert the string to a Buffer
return Buffer.from(item);
} else {
// If it's already a Buffer, just return it
return item;
}
}),
keys: options?.keys?.map(Buffer.from),
args: options?.args?.map(Buffer.from),
});
return this.createWritePromise(scriptInvocation, options);
}
Expand Down
6 changes: 3 additions & 3 deletions node/src/GlideClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ export class GlideClient extends BaseClient {
}

/**
* Check existence of scripts in the script cache by their SHA1 digest.
* Checks existence of scripts in the script cache by their SHA1 digest.
*
* @see {@link https://valkey.io/commands/script-exists/|valkey.io} for more details.
*
Expand All @@ -941,7 +941,7 @@ export class GlideClient extends BaseClient {
}

/**
* Flush the Lua scripts cache.
* Flushes the Lua scripts cache.
*
* @see {@link https://valkey.io/commands/script-flush/|valkey.io} for more details.
*
Expand All @@ -961,7 +961,7 @@ export class GlideClient extends BaseClient {
}

/**
* Kill the currently executing Lua script, assuming no write operation was yet performed by the script.
* Kills the currently executing Lua script, assuming no write operation was yet performed by the script.
*
* @see {@link https://valkey.io/commands/script-kill/|valkey.io} for more details.
*
Expand Down
46 changes: 43 additions & 3 deletions node/src/GlideClusterClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { ClusterScanCursor } from "glide-rs";
import { Script } from "index";
import * as net from "net";
import {
BaseClient,
Expand Down Expand Up @@ -1442,7 +1443,46 @@ export class GlideClusterClient extends BaseClient {
}

/**
* Check existence of scripts in the script cache by their SHA1 digest.
* Invokes a Lua script with arguments.
* This method simplifies the process of invoking scripts on a Valkey 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 `SCRIPT LOAD` command. After that, it will be invoked using the `EVALSHA` command.
*
* The command will be routed to a random node, unless `route` is provided.
*
* @see {@link https://valkey.io/commands/script-load/|SCRIPT LOAD} and {@link https://valkey.io/commands/evalsha/|EVALSHA} on valkey.io for details.
*
* @param script - The Lua script to execute.
* @param options - (Optional) Additional parameters:
* - (Optional) `args`: the arguments for the script.
* - (Optional) `decoder`: see {@link DecoderOption}.
* - (Optional) `route`: see {@link RouteOption}.
* @returns A value that depends on the script that was executed.
*
* @example
* ```typescript
* const luaScript = new Script("return { ARGV[1] }");
* const result = await invokeScript(luaScript, { args: ["bar"] });
* console.log(result); // Output: ['bar']
* ```
*/
public async invokeScriptWithRoute(
script: Script,
options?: { args?: GlideString[] } & DecoderOption & RouteOption,
): Promise<ClusterResponse<GlideReturnType>> {
const scriptInvocation = command_request.ScriptInvocation.create({
hash: script.getHash(),
keys: [],
args: options?.args?.map(Buffer.from),
});
return this.createWritePromise<ClusterGlideRecord<GlideReturnType>>(
scriptInvocation,
options,
).then((res) => convertClusterGlideRecord(res, true, options?.route));
}

/**
* Checks existence of scripts in the script cache by their SHA1 digest.
*
* @see {@link https://valkey.io/commands/script-exists/|valkey.io} for more details.
*
Expand All @@ -1464,7 +1504,7 @@ export class GlideClusterClient extends BaseClient {
}

/**
* Flush the Lua scripts cache.
* Flushes the Lua scripts cache.
*
* @see {@link https://valkey.io/commands/script-flush/|valkey.io} for more details.
*
Expand All @@ -1491,7 +1531,7 @@ export class GlideClusterClient extends BaseClient {
}

/**
* Kill the currently executing Lua script, assuming no write operation was yet performed by the script.
* Kills the currently executing Lua script, assuming no write operation was yet performed by the script.
*
* @see {@link https://valkey.io/commands/script-kill/|valkey.io} for more details.
* @remarks The command is routed to all nodes, and aggregates the response to a single array.
Expand Down
61 changes: 61 additions & 0 deletions node/tests/GlideClusterClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,67 @@ describe("GlideClusterClient", () => {
client.close();
}
});

it(
"invoke script with route invokeScriptWithRoute %p",
async () => {
const client =
await GlideClusterClient.createClient(
getClientConfigurationOption(
cluster.getAddresses(),
protocol,
),
);
const route: Routes = singleNodeRoute
? { type: "primarySlotKey", key: "1" }
: "allPrimaries";

try {
const arg = uuidv4();
const script = new Script(
Buffer.from("return {ARGV[1]}"),
);
let res = await client.invokeScriptWithRoute(
script,
{ args: [Buffer.from(arg)], route },
);

if (singleNodeRoute) {
expect(res).toEqual([arg]);
} else {
Object.values(
res as Record<string, GlideReturnType>,
).forEach((value) =>
expect(value).toEqual([arg]),
);
}

res = await client.invokeScriptWithRoute(
script,
{
args: [arg],
route,
decoder: Decoder.Bytes,
},
);

if (singleNodeRoute) {
expect(res).toEqual([Buffer.from(arg)]);
} else {
Object.values(
res as Record<string, GlideReturnType>,
).forEach((value) =>
expect(value).toEqual([
Buffer.from(arg),
]),
);
}
} finally {
client.close();
}
},
TIMEOUT,
);
},
);
it(
Expand Down

0 comments on commit 0d6a44f

Please sign in to comment.