diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ac3ee206..9bf594b0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ * Node: Added ZLEXCOUNT command ([#2022](https://github.com/valkey-io/valkey-glide/pull/2022)) * Node: Added ZREMRANGEBYLEX command ([#2025]((https://github.com/valkey-io/valkey-glide/pull/2025)) * Node: Added ZSCAN command ([#2061](https://github.com/valkey-io/valkey-glide/pull/2061)) +* Node: Added SETRANGE command ([#2066](https://github.com/valkey-io/valkey-glide/pull/2066)) #### Breaking Changes * Node: (Refactor) Convert classes to types ([#2005](https://github.com/valkey-io/valkey-glide/pull/2005)) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index 4b552ebcfa..2cd5025517 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -135,6 +135,7 @@ import { createSUnionStore, createSet, createSetBit, + createSetRange, createStrlen, createTTL, createTouch, @@ -4464,6 +4465,34 @@ export class BaseClient { return this.createWritePromise(createTouch(keys)); } + /** + * Overwrites part of the string stored at `key`, starting at the specified `offset`, + * for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`, + * the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist. + * + * See https://valkey.io/commands/setrange/ for more details. + * + * @param key - The key of the string to update. + * @param offset - The position in the string where `value` should be written. + * @param value - The string written with `offset`. + * @returns The length of the string stored at `key` after it was modified. + * + * @example + * ```typescript + * const len = await client.setrange("key", 6, "GLIDE"); + * console.log(len); // Output: 11 - New key was created with length of 11 symbols + * const value = await client.get("key"); + * console.log(result); // Output: "\0\0\0\0\0\0GLIDE" - The string was padded with zero bytes + * ``` + */ + public async setrange( + key: string, + offset: number, + value: string, + ): Promise { + return this.createWritePromise(createSetRange(key, offset, value)); + } + /** * @internal */ diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 2459d93311..c59e56b806 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -3044,3 +3044,12 @@ export function createZScan( return createCommand(RequestType.ZScan, args); } + +/** @internal */ +export function createSetRange( + key: string, + offset: number, + value: string, +): command_request.Command { + return createCommand(RequestType.SetRange, [key, offset.toString(), value]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 59fa7d19c5..3292f20d73 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -162,6 +162,7 @@ import { createSelect, createSet, createSetBit, + createSetRange, createSort, createSortReadOnly, createStrlen, @@ -2694,6 +2695,23 @@ export class BaseTransaction> { public randomKey(): T { return this.addAndReturn(createRandomKey()); } + + /** + * Overwrites part of the string stored at `key`, starting at the specified `offset`, + * for the entire length of `value`. If the `offset` is larger than the current length of the string at `key`, + * the string is padded with zero bytes to make `offset` fit. Creates the `key` if it doesn't exist. + * + * See https://valkey.io/commands/setrange/ for more details. + * + * @param key - The key of the string to update. + * @param offset - The position in the string where `value` should be written. + * @param value - The string written with `offset`. + * + * Command Response - The length of the string stored at `key` after it was modified. + */ + public setrange(key: string, offset: number, value: string): T { + return this.addAndReturn(createSetRange(key, offset, value)); + } } /** diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 1660140a51..84def5e10e 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -4659,6 +4659,36 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + "setrange test_%p", + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const nonStringKey = uuidv4(); + + // new key + expect(await client.setrange(key, 0, "Hello World")).toBe(11); + + // existing key + expect(await client.setrange(key, 6, "GLIDE")).toBe(11); + expect(await client.get(key)).toEqual("Hello GLIDE"); + + // offset > len + expect(await client.setrange(key, 15, "GLIDE")).toBe(20); + expect(await client.get(key)).toEqual( + "Hello GLIDE\0\0\0\0GLIDE", + ); + + // non-string key + expect(await client.lpush(nonStringKey, ["_"])).toBe(1); + await expect( + client.setrange(nonStringKey, 0, "_"), + ).rejects.toThrow(RequestError); + }, protocol); + }, + config.timeout, + ); + // Set command tests async function setWithExpiryOptions(client: BaseClient) { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index f5dc724edd..686241f2b5 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -517,6 +517,8 @@ export async function transactionTest( responseData.push(["mget([key1, key2])", ["bar", "baz"]]); baseTransaction.strlen(key1); responseData.push(["strlen(key1)", 3]); + baseTransaction.setrange(key1, 0, "GLIDE"); + responseData.push(["setrange(key1, 0, 'GLIDE'", 5]); baseTransaction.del([key1]); responseData.push(["del([key1])", 1]); baseTransaction.hset(key4, { [field]: value });