diff --git a/command.ts b/command.ts index ab41537a..054e7483 100644 --- a/command.ts +++ b/command.ts @@ -178,9 +178,20 @@ export type SScanOpts = BaseScanOpts; export type ZScanOpts = BaseScanOpts; export interface ZAddOpts { + /** @deprecated Use {@linkcode ZAddOpts.nx}/{@linkcode ZAddOpts.xx} instead. This option will be removed in the future. */ mode?: "NX" | "XX"; ch?: boolean; + /** Enables `NX` option */ + nx?: boolean; + /** Enables `XX` option */ + xx?: boolean; } +/** Return type for {@linkcode RedisCommands.zadd} */ +export type ZAddReply = T extends { mode: "NX" | "XX" } + ? Integer | BulkNil + : T extends { nx: true } ? Integer | BulkNil + : T extends { xx: true } ? Integer | BulkNil + : Integer; interface ZStoreOpts { aggregate?: "SUM" | "MIN" | "MAX"; @@ -1006,22 +1017,22 @@ XRANGE somestream - + timeout: number, ...keys: string[] ): Promise<[BulkString, BulkString, BulkString] | BulkNil>; - zadd( + zadd( key: string, score: number, member: RedisValue, - opts?: ZAddOpts, - ): Promise; - zadd( + opts?: TZAddOpts, + ): Promise>; + zadd( key: string, score_members: [number, RedisValue][], - opts?: ZAddOpts, - ): Promise; - zadd( + opts?: TZAddOpts, + ): Promise>; + zadd( key: string, member_scores: Record, - opts?: ZAddOpts, - ): Promise; + opts?: TZAddOpts, + ): Promise>; zaddIncr( key: string, score: number, diff --git a/mod.ts b/mod.ts index a6055956..df274c03 100644 --- a/mod.ts +++ b/mod.ts @@ -46,6 +46,7 @@ export type { StralgoOpts, StralgoTarget, ZAddOpts, + ZAddReply, ZInterOpts, ZInterstoreOpts, ZRangeByLexOpts, diff --git a/protocol/deno_streams/reply.ts b/protocol/deno_streams/reply.ts index 1ffbad84..b0eae69b 100644 --- a/protocol/deno_streams/reply.ts +++ b/protocol/deno_streams/reply.ts @@ -65,6 +65,7 @@ async function readBulkReply( } const size = parseSize(line); + console.info("bulkReply", { size, line: new TextDecoder().decode(line) }); if (size < 0) { // nil bulk reply return null; diff --git a/redis.ts b/redis.ts index 821ac4be..36789651 100644 --- a/redis.ts +++ b/redis.ts @@ -33,6 +33,7 @@ import type { StralgoOpts, StralgoTarget, ZAddOpts, + ZAddReply, ZInterOpts, ZInterstoreOpts, ZRangeByLexOpts, @@ -2095,22 +2096,22 @@ class RedisImpl implements Redis { return this.execIntegerReply("XTRIM", key, "MAXLEN", ...args); } - zadd( + zadd( key: string, score: number, member: string, - opts?: ZAddOpts, - ): Promise; - zadd( + opts?: TZAddOpts, + ): Promise>; + zadd( key: string, scoreMembers: [number, string][], - opts?: ZAddOpts, - ): Promise; - zadd( + opts?: TZAddOpts, + ): Promise>; + zadd( key: string, memberScores: Record, - opts?: ZAddOpts, - ): Promise; + opts?: TZAddOpts, + ): Promise>; zadd( key: string, param1: number | [number, string][] | Record, @@ -2118,32 +2119,44 @@ class RedisImpl implements Redis { opts?: ZAddOpts, ) { const args: (string | number)[] = [key]; + let isAbleToReturnNil = false; if (Array.isArray(param1)) { - this.pushZAddOpts(args, param2 as ZAddOpts); + isAbleToReturnNil = this.pushZAddOpts(args, param2 as ZAddOpts); args.push(...param1.flatMap((e) => e)); opts = param2 as ZAddOpts; } else if (typeof param1 === "object") { - this.pushZAddOpts(args, param2 as ZAddOpts); + isAbleToReturnNil = this.pushZAddOpts(args, param2 as ZAddOpts); for (const [member, score] of Object.entries(param1)) { args.push(score as number, member); } } else { - this.pushZAddOpts(args, opts); + isAbleToReturnNil = this.pushZAddOpts(args, opts); args.push(param1, param2 as string); } - return this.execIntegerReply("ZADD", ...args); + return isAbleToReturnNil + ? this.execIntegerOrNilReply("ZADD", ...args) + : this.execIntegerReply("ZADD", ...args); } private pushZAddOpts( args: (string | number)[], opts?: ZAddOpts, - ): void { - if (opts?.mode) { + ): boolean { + let isAbleToReturnNil = false; + if (opts?.nx) { + args.push("NX"); + isAbleToReturnNil = true; + } else if (opts?.xx) { + args.push("XX"); + isAbleToReturnNil = true; + } else if (opts?.mode) { args.push(opts.mode); + isAbleToReturnNil = true; } if (opts?.ch) { args.push("CH"); } + return isAbleToReturnNil; } zaddIncr( diff --git a/tests/commands/sorted_set.ts b/tests/commands/sorted_set.ts index 72a9b2b5..a98fe685 100644 --- a/tests/commands/sorted_set.ts +++ b/tests/commands/sorted_set.ts @@ -1,5 +1,13 @@ import { assert, assertEquals } from "../../deps/std/assert.ts"; -import { afterAll, beforeAll, beforeEach, it } from "../../deps/std/testing.ts"; +import type { IsExact } from "../../deps/std/testing.ts"; +import { + afterAll, + assertType, + beforeAll, + beforeEach, + describe, + it, +} from "../../deps/std/testing.ts"; import type { Connector, TestServer } from "../test_util.ts"; import type { Redis } from "../../mod.ts"; @@ -39,35 +47,59 @@ export function zsetTests( assertEquals(arr, null); }); - it("zadd", async () => { - assertEquals(await client.zadd("key", { "1": 1, "2": 2 }), 2); - assertEquals(await client.zadd("key", 3, "3"), 1); - assertEquals( - await client.zadd("key", [ + describe("zadd", () => { + it("adds specified members to a sorted set", async () => { + const v = await client.zadd("key", { "1": 1, "2": 2 }); + assertEquals(v, 2); + + const v2 = await client.zadd("key", 3, "3"); + assertEquals(v2, 1); + + const v3 = await client.zadd("key", [ [4, "4"], [5, "5"], - ]), - 2, - ); - }); + ]); + assertEquals( + v3, + 2, + ); + assertType>(true); + }); - it("zaddWithMode", async () => { - assertEquals(await client.zadd("key", 1, "1", { mode: "NX" }), 1); - assertEquals(await client.zadd("key", { "1": 1 }, { mode: "XX" }), 0); - assertEquals( - await client.zadd("key", [[1, "1"], [2, "2"]], { mode: "NX" }), - 1, - ); - }); + it("supports `NX` and `XX`", async () => { + const v = await client.zadd("zaddWithNXOrXX", 1, "1", { nx: true }); + assertEquals(v, 1); + assertType>(true); - it("zaddWithCH", async () => { - assertEquals(await client.zadd("key", [[1, "foo"], [2, "bar"]]), 2); - assertEquals( - await client.zadd("key", { "foo": 1, "bar": 3, "baz": 4 }, { ch: true }), - 2, - ); - }); + const v2 = await client.zadd("zaddWithNXOrXX", 2, "1", { mode: "NX" }); + assertEquals(v2, null); + assertType>(true); + const v3 = await client.zadd("zaddWithNXOrXX", 3, "1", { xx: true }); + assertEquals(v3, 1); + assertType>(true); + + const v4 = await client.zadd("zaddWithNXOrXX", [[1, "2"]], { + mode: "NX", + }); + assertEquals(v4, null); + assertType>(true); + }); + + it("supports `CH`", async () => { + assertEquals(await client.zadd("keyWithCH", [[1, "foo"], [2, "bar"]]), 2); + const v = await client.zadd( + "keyWithCH", + { "foo": 1, "bar": 3, "baz": 4 }, + { ch: true }, + ); + assertEquals( + v, + 2, + ); + assertType>(true); + }); + }); it("zaddIncr", async () => { await client.zadd("key", 1, "a"); await client.zaddIncr("key", 2, "a");