From e015268b262d5322f4f630cda4e65324cc23b155 Mon Sep 17 00:00:00 2001 From: chronark Date: Mon, 9 Oct 2023 14:01:53 +0200 Subject: [PATCH 1/6] feat: xadd --- pkg/commands/mod.ts | 1 + pkg/commands/xadd.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ pkg/commands/zadd.ts | 1 + pkg/redis.ts | 11 +++++++++++ 4 files changed, 56 insertions(+) create mode 100644 pkg/commands/xadd.ts diff --git a/pkg/commands/mod.ts b/pkg/commands/mod.ts index 30d8b62c..9a65af28 100644 --- a/pkg/commands/mod.ts +++ b/pkg/commands/mod.ts @@ -120,6 +120,7 @@ export * from "./touch.ts"; export * from "./ttl.ts"; export * from "./type.ts"; export * from "./unlink.ts"; +export * from "./xadd.ts" export * from "./zadd.ts"; export * from "./zcard.ts"; export * from "./zcount.ts"; diff --git a/pkg/commands/xadd.ts b/pkg/commands/xadd.ts new file mode 100644 index 00000000..40ea36be --- /dev/null +++ b/pkg/commands/xadd.ts @@ -0,0 +1,43 @@ +import { Command, CommandOptions } from "./command.js"; + +/** + * @see https://redis.io/commands/xadd + */ +export class XAddCommand extends Command { + constructor([key,id,entries,opts]: [key: string, id: "*"|string, entries: {[field: string]: unknown}, opts?: { + nomkStream?: "*" | string, + trim?: { + type: "MINID" | "MAXLEN" | "minid" | "maxlen" + comparison: "="|"~", + threshold: number, + limit?:number + } + }], commandOptions?: CommandOptions) { + + const command : unknown[] = ["XADD", key] + + + // opts + if (opts){ + if (opts.nomkStream){ + command.push("NOMKSTREAM") + } + if (opts.trim){ + command.push(opts.trim.type, opts.trim.comparison, opts.trim.threshold) + if (typeof opts.trim.limit!==undefined){ + command.push("LIMIT", opts.trim.limit) + } + } + + } + + command.push(id) + + // entries + Object.entries(entries).forEach(([k,v])=>{ + command.push(k,v) + }) + + super(command, commandOptions); + } +} diff --git a/pkg/commands/zadd.ts b/pkg/commands/zadd.ts index aa53a7d3..9013599b 100644 --- a/pkg/commands/zadd.ts +++ b/pkg/commands/zadd.ts @@ -70,3 +70,4 @@ export class ZAddCommand extends Command< super(command, opts); } } + diff --git a/pkg/redis.ts b/pkg/redis.ts index d717b498..a371638c 100644 --- a/pkg/redis.ts +++ b/pkg/redis.ts @@ -144,6 +144,7 @@ import { ZScanCommand, ZScoreCommand, ZUnionStoreCommand, + XAddCommand } from "./commands/mod.ts"; import { Requester, UpstashRequest, UpstashResponse } from "./http.ts"; import { Pipeline } from "./pipeline.ts"; @@ -1014,6 +1015,14 @@ export class Redis { unlink = (...args: CommandArgs) => new UnlinkCommand(args, this.opts).exec(this.client); + + + + // /** + // * @see https://redis.io/commands/xadd + // */ + // xadd = + /** * @see https://redis.io/commands/zadd */ @@ -1172,3 +1181,5 @@ export class Redis { zunionstore = (...args: CommandArgs) => new ZUnionStoreCommand(args, this.opts).exec(this.client); } + + From 49145c8c73e086535b5ac78245eb15edd0b36477 Mon Sep 17 00:00:00 2001 From: ogzhanolguncu Date: Mon, 9 Oct 2023 16:55:28 +0300 Subject: [PATCH 2/6] Add xrange and xrange test --- deno.lock | 39 +++++++++++++++++++++++++- pkg/commands/xadd.ts | 55 ++++++++++++++++++++----------------- pkg/commands/xrange.test.ts | 50 +++++++++++++++++++++++++++++++++ pkg/commands/xrange.ts | 48 ++++++++++++++++++++++++++++++++ version.ts | 1 + 5 files changed, 167 insertions(+), 26 deletions(-) create mode 100644 pkg/commands/xrange.test.ts create mode 100644 pkg/commands/xrange.ts diff --git a/deno.lock b/deno.lock index 3710e3b7..a5c56902 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,9 @@ { - "version": "2", + "version": "3", + "redirects": { + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.203.0/testing/asserts.ts", + "https://deno.land/x/base64/base64url.ts": "https://deno.land/x/base64@v0.2.1/base64url.ts" + }, "remote": { "https://deno.land/std@0.111.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", "https://deno.land/std@0.111.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", @@ -97,6 +101,39 @@ "https://deno.land/std@0.177.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", "https://deno.land/std@0.177.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab", "https://deno.land/std@0.177.0/testing/bdd.ts": "c5ca6d85940dbcc19b4d2bc3608d49ab65d81470aa91306d5efa4b0d5c945731", + "https://deno.land/std@0.203.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.203.0/assert/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", + "https://deno.land/std@0.203.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.203.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.203.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.203.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.203.0/assert/assert_equals.ts": "d8ec8a22447fbaf2fc9d7c3ed2e66790fdb74beae3e482855d75782218d68227", + "https://deno.land/std@0.203.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.203.0/assert/assert_false.ts": "0ccbcaae910f52c857192ff16ea08bda40fdc79de80846c206bfc061e8c851c6", + "https://deno.land/std@0.203.0/assert/assert_greater.ts": "ae2158a2d19313bf675bf7251d31c6dc52973edb12ac64ac8fc7064152af3e63", + "https://deno.land/std@0.203.0/assert/assert_greater_or_equal.ts": "1439da5ebbe20855446cac50097ac78b9742abe8e9a43e7de1ce1426d556e89c", + "https://deno.land/std@0.203.0/assert/assert_instance_of.ts": "3aedb3d8186e120812d2b3a5dea66a6e42bf8c57a8bd927645770bd21eea554c", + "https://deno.land/std@0.203.0/assert/assert_is_error.ts": "c21113094a51a296ffaf036767d616a78a2ae5f9f7bbd464cd0197476498b94b", + "https://deno.land/std@0.203.0/assert/assert_less.ts": "aec695db57db42ec3e2b62e97e1e93db0063f5a6ec133326cc290ff4b71b47e4", + "https://deno.land/std@0.203.0/assert/assert_less_or_equal.ts": "5fa8b6a3ffa20fd0a05032fe7257bf985d207b85685fdbcd23651b70f928c848", + "https://deno.land/std@0.203.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.203.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.203.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.203.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.203.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", + "https://deno.land/std@0.203.0/assert/assert_object_match.ts": "d8fc2867cfd92eeacf9cea621e10336b666de1874a6767b5ec48988838370b54", + "https://deno.land/std@0.203.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.203.0/assert/assert_strict_equals.ts": "b1f538a7ea5f8348aeca261d4f9ca603127c665e0f2bbfeb91fa272787c87265", + "https://deno.land/std@0.203.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.203.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.203.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.203.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.203.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.203.0/assert/mod.ts": "37c49a26aae2b254bbe25723434dc28cd7532e444cf0b481a97c045d110ec085", + "https://deno.land/std@0.203.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.203.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.203.0/fmt/colors.ts": "c51c4642678eb690dcf5ffee5918b675bf01a33fba82acf303701ae1a4f8c8d9", + "https://deno.land/std@0.203.0/testing/asserts.ts": "b4e4b1359393aeff09e853e27901a982c685cb630df30426ed75496961931946", "https://deno.land/x/base64@v0.2.1/base.ts": "47dc8d68f07dc91524bdd6db36eccbe59cf4d935b5fc09f27357a3944bb3ff7b", "https://deno.land/x/base64@v0.2.1/base64url.ts": "18bbf879b31f1f32cca8adaa2b6885ae325c2cec6a66c5817b684ca12c46ad5e", "https://deno.land/x/code_block_writer@11.0.0/comment_char.ts": "22b66890bbdf7a2d59777ffd8231710c1fda1c11fadada67632a596937a1a314", diff --git a/pkg/commands/xadd.ts b/pkg/commands/xadd.ts index 40ea36be..89a46f66 100644 --- a/pkg/commands/xadd.ts +++ b/pkg/commands/xadd.ts @@ -1,42 +1,47 @@ -import { Command, CommandOptions } from "./command.js"; +import { Command, CommandOptions } from "./command.ts"; /** * @see https://redis.io/commands/xadd */ export class XAddCommand extends Command { - constructor([key,id,entries,opts]: [key: string, id: "*"|string, entries: {[field: string]: unknown}, opts?: { - nomkStream?: "*" | string, - trim?: { - type: "MINID" | "MAXLEN" | "minid" | "maxlen" - comparison: "="|"~", - threshold: number, - limit?:number - } - }], commandOptions?: CommandOptions) { - - const command : unknown[] = ["XADD", key] - + constructor( + [key, id, entries, opts]: [ + key: string, + id: "*" | string, + entries: { [field: string]: unknown }, + opts?: { + nomkStream?: "*" | string; + trim?: { + type: "MINID" | "MAXLEN" | "minid" | "maxlen"; + comparison: "=" | "~"; + threshold: number; + limit?: number; + }; + } + ], + commandOptions?: CommandOptions + ) { + const command: unknown[] = ["XADD", key]; // opts - if (opts){ - if (opts.nomkStream){ - command.push("NOMKSTREAM") + if (opts) { + if (opts.nomkStream) { + command.push("NOMKSTREAM"); } - if (opts.trim){ - command.push(opts.trim.type, opts.trim.comparison, opts.trim.threshold) - if (typeof opts.trim.limit!==undefined){ - command.push("LIMIT", opts.trim.limit) + if (opts.trim) { + command.push(opts.trim.type, opts.trim.comparison, opts.trim.threshold); + if (typeof opts.trim.limit !== "undefined") { + command.push("LIMIT", opts.trim.limit); } } - } - command.push(id) + command.push(id); // entries - Object.entries(entries).forEach(([k,v])=>{ - command.push(k,v) - }) + Object.entries(entries).forEach(([k, v]) => { + command.push(k, v); + }); super(command, commandOptions); } diff --git a/pkg/commands/xrange.test.ts b/pkg/commands/xrange.test.ts new file mode 100644 index 00000000..350683de --- /dev/null +++ b/pkg/commands/xrange.test.ts @@ -0,0 +1,50 @@ +import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts"; +import { keygen, newHttpClient, randomID } from "../test-utils.ts"; + +import { afterAll } from "https://deno.land/std@0.177.0/testing/bdd.ts"; +import { XAddCommand } from "./xadd.ts"; +import { XRangeCommand } from "./xrange.ts"; + +const client = newHttpClient(); + +const { newKey, cleanup } = keygen(); +afterAll(cleanup); + +Deno.test("without options", async (t) => { + await t.step("returns the set", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]).exec(client); + + const res = await new XRangeCommand([key, "-", "+"]).exec(client); + console.log(res); + assertEquals(Object.keys(res).length, 1); + assertEquals(Object.values(res)[0], { [field1]: member1, [field2]: member2 }); + }); +}); + +// Deno.test("limit", async (t) => { +// await t.step("returns only the first 2", async () => { +// const key = newKey(); +// for (let i = 0; i < 10; i++) { +// await new ZAddCommand([key, { score: i, member: randomID() }]).exec(client); +// } + +// const res = await new ZRangeCommand([ +// key, +// 0, +// 7, +// { +// byScore: true, +// offset: 0, +// count: 2, +// }, +// ]).exec(client); +// assertEquals(res.length, 2); +// }); +// }); diff --git a/pkg/commands/xrange.ts b/pkg/commands/xrange.ts new file mode 100644 index 00000000..8c1abe94 --- /dev/null +++ b/pkg/commands/xrange.ts @@ -0,0 +1,48 @@ +import { Command, CommandOptions } from "./command.ts"; + +function deserialize>>( + result: (string | string[])[] +): TData { + if (result.length === 0) { + return {} as TData; + } + const obj: Record> = {}; + while (result.length >= 2) { + const streamId = result.shift() as string; + const entries = result.shift()!; + + if (!(streamId in obj)) { + obj[streamId] = {}; + } + while (entries.length >= 2) { + const field = (entries as string[]).shift()! as string; + const value = (entries as string[]).shift()! as string; + + try { + obj[streamId][field] = JSON.parse(value); + } catch { + obj[streamId][field] = value; + } + } + } + return obj as TData; +} + +export class XRangeCommand>> extends Command< + string[][], + TData +> { + constructor( + [key, start, end, count]: [key: string, start: string, end: string, count?: number], + opts?: CommandOptions + ) { + const command: unknown[] = ["XRANGE", key, start, end]; + if (typeof count === "number") { + command.push("COUNT", count); + } + super(command, { + deserialize: (result) => deserialize(result[0] as any), + ...opts, + }); + } +} diff --git a/version.ts b/version.ts index 1155b94a..2060f597 100644 --- a/version.ts +++ b/version.ts @@ -1 +1,2 @@ export const VERSION = "v0.0.0"; +console.log({ VERSION, envs: Deno.env.get("UPSTASH_REDIS_REST_TOKEN") }); From d18c9c5dba7e0fc5bc995bc3d212ec4aa6c10a4a Mon Sep 17 00:00:00 2001 From: chronark Date: Tue, 10 Oct 2023 12:49:31 +0200 Subject: [PATCH 3/6] fix: nested decoding --- pkg/commands/json_get.test.ts | 4 ++-- pkg/commands/mod.ts | 2 +- pkg/commands/xadd.ts | 4 ++-- pkg/commands/xrange.test.ts | 8 ++++++-- pkg/commands/xrange.ts | 16 ++++++++++++---- pkg/commands/zadd.ts | 1 - pkg/http.ts | 30 ++++++++++++++++++++---------- pkg/pipeline.ts | 1 + pkg/redis.ts | 9 ++------- version.ts | 1 - 10 files changed, 46 insertions(+), 30 deletions(-) diff --git a/pkg/commands/json_get.test.ts b/pkg/commands/json_get.test.ts index 1b058a83..33ae8edf 100644 --- a/pkg/commands/json_get.test.ts +++ b/pkg/commands/json_get.test.ts @@ -20,6 +20,6 @@ Deno.test("Return the value at path in JSON serialized form", async () => { assertEquals(res1, "OK"); const res2 = await new JsonGetCommand([key, "$..b"]).exec(client); assertEquals(res2, [null, 3]); - const res3 = await new JsonGetCommand([key, "..a", "$..b"]).exec(client); - assertEquals(res3, { "$..b": [null, 3], "..a": [4, 2] }); + const res3 = await new JsonGetCommand([key, "$..a", "$..b"]).exec(client); + assertEquals(res3, { "$..b": [null, 3], "$..a": [4, 2] }); }); diff --git a/pkg/commands/mod.ts b/pkg/commands/mod.ts index 9a65af28..8ca08e97 100644 --- a/pkg/commands/mod.ts +++ b/pkg/commands/mod.ts @@ -120,7 +120,7 @@ export * from "./touch.ts"; export * from "./ttl.ts"; export * from "./type.ts"; export * from "./unlink.ts"; -export * from "./xadd.ts" +export * from "./xadd.ts"; export * from "./zadd.ts"; export * from "./zcard.ts"; export * from "./zcount.ts"; diff --git a/pkg/commands/xadd.ts b/pkg/commands/xadd.ts index 89a46f66..deea66bb 100644 --- a/pkg/commands/xadd.ts +++ b/pkg/commands/xadd.ts @@ -17,9 +17,9 @@ export class XAddCommand extends Command { threshold: number; limit?: number; }; - } + }, ], - commandOptions?: CommandOptions + commandOptions?: CommandOptions, ) { const command: unknown[] = ["XADD", key]; diff --git a/pkg/commands/xrange.test.ts b/pkg/commands/xrange.test.ts index 350683de..4e9fbce3 100644 --- a/pkg/commands/xrange.test.ts +++ b/pkg/commands/xrange.test.ts @@ -19,12 +19,16 @@ Deno.test("without options", async (t) => { const field2 = "field2"; const member2 = randomID(); - await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]).exec(client); + await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]) + .exec(client); const res = await new XRangeCommand([key, "-", "+"]).exec(client); console.log(res); assertEquals(Object.keys(res).length, 1); - assertEquals(Object.values(res)[0], { [field1]: member1, [field2]: member2 }); + assertEquals(Object.values(res)[0], { + [field1]: member1, + [field2]: member2, + }); }); }); diff --git a/pkg/commands/xrange.ts b/pkg/commands/xrange.ts index 8c1abe94..c6e1f35d 100644 --- a/pkg/commands/xrange.ts +++ b/pkg/commands/xrange.ts @@ -1,8 +1,9 @@ import { Command, CommandOptions } from "./command.ts"; function deserialize>>( - result: (string | string[])[] + result: (string | string[])[], ): TData { + console.log("result", result); if (result.length === 0) { return {} as TData; } @@ -28,13 +29,20 @@ function deserialize>>( return obj as TData; } -export class XRangeCommand>> extends Command< +export class XRangeCommand< + TData extends Record>, +> extends Command< string[][], TData > { constructor( - [key, start, end, count]: [key: string, start: string, end: string, count?: number], - opts?: CommandOptions + [key, start, end, count]: [ + key: string, + start: string, + end: string, + count?: number, + ], + opts?: CommandOptions, ) { const command: unknown[] = ["XRANGE", key, start, end]; if (typeof count === "number") { diff --git a/pkg/commands/zadd.ts b/pkg/commands/zadd.ts index 9013599b..aa53a7d3 100644 --- a/pkg/commands/zadd.ts +++ b/pkg/commands/zadd.ts @@ -70,4 +70,3 @@ export class ZAddCommand extends Command< super(command, opts); } } - diff --git a/pkg/http.ts b/pkg/http.ts index e6fbc38b..3d504f06 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -213,11 +213,21 @@ export class HttpClient implements Requester { const body = (await res.json()) as UpstashResponse; if (!res.ok) { - throw new UpstashError(body.error!); + throw new UpstashError( + `${body.error}, command was: ${JSON.stringify(req.body)}`, + ); } if (this.options?.responseEncoding === "base64") { - return Array.isArray(body) ? body.map(decode) : decode(body) as any; + if (Array.isArray(body)) { + return body.map(({ result, error }) => ({ + result: decode(result), + error, + })) as UpstashResponse; + } + + const result = decode(body.result) as any; + return { result, error: body.error }; } return body as UpstashResponse; } @@ -247,23 +257,23 @@ function base64decode(b64: string): string { // } } -function decode(raw: ResultError): ResultError { +function decode(raw: ResultError["result"]): ResultError["result"] { let result: any = undefined; - switch (typeof raw.result) { + switch (typeof raw) { case "undefined": return raw; case "number": { - result = raw.result; + result = raw; break; } case "object": { - if (Array.isArray(raw.result)) { - result = raw.result.map((v) => + if (Array.isArray(raw)) { + result = raw.map((v) => typeof v === "string" ? base64decode(v) : Array.isArray(v) - ? v.map(base64decode) + ? v.map(decode) : v ); } else { @@ -275,7 +285,7 @@ function decode(raw: ResultError): ResultError { } case "string": { - result = raw.result === "OK" ? "OK" : base64decode(raw.result); + result = raw === "OK" ? "OK" : base64decode(raw); break; } @@ -283,5 +293,5 @@ function decode(raw: ResultError): ResultError { break; } - return { result, error: raw.error }; + return result; } diff --git a/pkg/pipeline.ts b/pkg/pipeline.ts index 7f675d26..7d722585 100644 --- a/pkg/pipeline.ts +++ b/pkg/pipeline.ts @@ -237,6 +237,7 @@ export class Pipeline[] = []> { body: Object.values(this.commands).map((c) => c.command), })) as UpstashResponse[]; + console.log("after req", { res }); return res.map(({ error, result }, i) => { if (error) { throw new UpstashError( diff --git a/pkg/redis.ts b/pkg/redis.ts index a371638c..88f3d719 100644 --- a/pkg/redis.ts +++ b/pkg/redis.ts @@ -123,6 +123,7 @@ import { TtlCommand, TypeCommand, UnlinkCommand, + XAddCommand, ZAddCommand, ZAddCommandOptions, ZAddCommandOptionsWithIncr, @@ -144,7 +145,6 @@ import { ZScanCommand, ZScoreCommand, ZUnionStoreCommand, - XAddCommand } from "./commands/mod.ts"; import { Requester, UpstashRequest, UpstashResponse } from "./http.ts"; import { Pipeline } from "./pipeline.ts"; @@ -1015,13 +1015,10 @@ export class Redis { unlink = (...args: CommandArgs) => new UnlinkCommand(args, this.opts).exec(this.client); - - - // /** // * @see https://redis.io/commands/xadd // */ - // xadd = + // xadd = /** * @see https://redis.io/commands/zadd @@ -1181,5 +1178,3 @@ export class Redis { zunionstore = (...args: CommandArgs) => new ZUnionStoreCommand(args, this.opts).exec(this.client); } - - diff --git a/version.ts b/version.ts index 2060f597..1155b94a 100644 --- a/version.ts +++ b/version.ts @@ -1,2 +1 @@ export const VERSION = "v0.0.0"; -console.log({ VERSION, envs: Deno.env.get("UPSTASH_REDIS_REST_TOKEN") }); From 9060a8337682648d8a86f81c959d6b9f1bc44838 Mon Sep 17 00:00:00 2001 From: ogzhanolguncu Date: Tue, 10 Oct 2023 14:01:24 +0300 Subject: [PATCH 4/6] Add tests for xrange --- pkg/commands/xrange.test.ts | 43 +++++++++++++++++-------------------- pkg/commands/xrange.ts | 16 ++++---------- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/pkg/commands/xrange.test.ts b/pkg/commands/xrange.test.ts index 4e9fbce3..c7e54876 100644 --- a/pkg/commands/xrange.test.ts +++ b/pkg/commands/xrange.test.ts @@ -19,11 +19,9 @@ Deno.test("without options", async (t) => { const field2 = "field2"; const member2 = randomID(); - await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]) - .exec(client); + await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]).exec(client); const res = await new XRangeCommand([key, "-", "+"]).exec(client); - console.log(res); assertEquals(Object.keys(res).length, 1); assertEquals(Object.values(res)[0], { [field1]: member1, @@ -32,23 +30,22 @@ Deno.test("without options", async (t) => { }); }); -// Deno.test("limit", async (t) => { -// await t.step("returns only the first 2", async () => { -// const key = newKey(); -// for (let i = 0; i < 10; i++) { -// await new ZAddCommand([key, { score: i, member: randomID() }]).exec(client); -// } - -// const res = await new ZRangeCommand([ -// key, -// 0, -// 7, -// { -// byScore: true, -// offset: 0, -// count: 2, -// }, -// ]).exec(client); -// assertEquals(res.length, 2); -// }); -// }); +Deno.test("limit", async (t) => { + await t.step("returns the only the first one", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + await new XAddCommand([key, "*", { [field1]: member1 }]).exec(client); + await new XAddCommand([key, "*", { [field2]: member2 }]).exec(client); + + const res = await new XRangeCommand([key, "-", "+", 1]).exec(client); + assertEquals(Object.keys(res).length, 1); + assertEquals(Object.values(res)[0], { + [field1]: member1, + }); + }); +}); diff --git a/pkg/commands/xrange.ts b/pkg/commands/xrange.ts index c6e1f35d..8c1abe94 100644 --- a/pkg/commands/xrange.ts +++ b/pkg/commands/xrange.ts @@ -1,9 +1,8 @@ import { Command, CommandOptions } from "./command.ts"; function deserialize>>( - result: (string | string[])[], + result: (string | string[])[] ): TData { - console.log("result", result); if (result.length === 0) { return {} as TData; } @@ -29,20 +28,13 @@ function deserialize>>( return obj as TData; } -export class XRangeCommand< - TData extends Record>, -> extends Command< +export class XRangeCommand>> extends Command< string[][], TData > { constructor( - [key, start, end, count]: [ - key: string, - start: string, - end: string, - count?: number, - ], - opts?: CommandOptions, + [key, start, end, count]: [key: string, start: string, end: string, count?: number], + opts?: CommandOptions ) { const command: unknown[] = ["XRANGE", key, start, end]; if (typeof count === "number") { From c9c0d9a810714c8c146e5722c7b990eb5bd55b6d Mon Sep 17 00:00:00 2001 From: ogzhanolguncu Date: Tue, 10 Oct 2023 14:50:17 +0300 Subject: [PATCH 5/6] Add tests for xadd --- pkg/commands/xadd.test.ts | 125 ++++++++++++++++++++++++++++++++++++ pkg/commands/xadd.ts | 36 ++++++++--- pkg/commands/xrange.test.ts | 3 +- pkg/commands/xrange.ts | 15 +++-- 4 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 pkg/commands/xadd.test.ts diff --git a/pkg/commands/xadd.test.ts b/pkg/commands/xadd.test.ts new file mode 100644 index 00000000..eafb9374 --- /dev/null +++ b/pkg/commands/xadd.test.ts @@ -0,0 +1,125 @@ +import { assert } from "https://deno.land/std@0.177.0/testing/asserts.ts"; +import { keygen, newHttpClient, randomID } from "../test-utils.ts"; + +import { afterAll } from "https://deno.land/std@0.177.0/testing/bdd.ts"; +import { XAddCommand } from "./xadd.ts"; +import { XRangeCommand } from "./xrange.ts"; + +const client = newHttpClient(); + +const { newKey, cleanup } = keygen(); +afterAll(cleanup); + +Deno.test("without options", async (t) => { + await t.step("should return valid stream id", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + const res = await new XAddCommand([key, "*", { + [field1]: member1, + [field2]: member2, + }]).exec( + client, + ); + + assert(res.length > 0); + }); +}); + +Deno.test("with NOMKSTREAM", async (t) => { + await t.step("should return valid stream id", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + const first = await new XAddCommand([key, "*", { + [field1]: member1, + [field2]: member2, + }]).exec( + client, + ); + assert(first.length > 0); + + const res = await new XAddCommand([ + key, + "*", + { [field1]: member1, [field2]: member2 }, + { nomkStream: true }, + ]).exec(client); + assert(res.length > 0); + }); + + await t.step("should return null", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + const res = await new XAddCommand([ + key, + "*", + { [field1]: member1, [field2]: member2 }, + { nomkStream: true }, + ]).exec(client); + + assert(res === null); + }); +}); + +Deno.test("with threshold", async (t) => { + await t.step("should always return less than or equal to 5", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + for (let i = 0; i < 10; i++) { + const xaddRes = await new XAddCommand([ + key, + "*", + { [field1]: member1, [field2]: member2 }, + { trim: { comparison: "=", threshold: 5, type: "MAXLEN" } }, + ]).exec(client); + assert(xaddRes.length > 0); + + const xrangeRes = await new XRangeCommand([key, "-", "+"]).exec(client); + assert(Object.keys(xrangeRes).length <= 5); + } + }); + + await t.step("should trim the stream by stream id", async () => { + const key = newKey(); + const field1 = "field1"; + const member1 = randomID(); + + const field2 = "field2"; + const member2 = randomID(); + + const xaddRes = await new XAddCommand([ + key, + "*", + { [field1]: member1, [field2]: member2 }, + ]).exec(client); + + await new XAddCommand([ + key, + "*", + { [field1]: member1, [field2]: member2 }, + { trim: { type: "minid", threshold: xaddRes, comparison: "=" } }, + ]).exec(client); + + const xrangeRes = await new XRangeCommand([key, "-", "+"]).exec(client); + assert(Object.keys(xrangeRes).length === 1); + }); +}); diff --git a/pkg/commands/xadd.ts b/pkg/commands/xadd.ts index deea66bb..c35aa285 100644 --- a/pkg/commands/xadd.ts +++ b/pkg/commands/xadd.ts @@ -1,5 +1,30 @@ import { Command, CommandOptions } from "./command.ts"; +type XAddCommandOptions = { + nomkStream?: boolean; + trim?: + & ( + | { + type: "MAXLEN" | "maxlen"; + threshold: number; + } + | { + type: "MINID" | "minid"; + threshold: string; + } + ) + & ( + | { + comparison: "~"; + limit?: number; + } + | { + comparison: "="; + limit?: never; + } + ); +}; + /** * @see https://redis.io/commands/xadd */ @@ -9,21 +34,12 @@ export class XAddCommand extends Command { key: string, id: "*" | string, entries: { [field: string]: unknown }, - opts?: { - nomkStream?: "*" | string; - trim?: { - type: "MINID" | "MAXLEN" | "minid" | "maxlen"; - comparison: "=" | "~"; - threshold: number; - limit?: number; - }; - }, + opts?: XAddCommandOptions, ], commandOptions?: CommandOptions, ) { const command: unknown[] = ["XADD", key]; - // opts if (opts) { if (opts.nomkStream) { command.push("NOMKSTREAM"); diff --git a/pkg/commands/xrange.test.ts b/pkg/commands/xrange.test.ts index c7e54876..43606837 100644 --- a/pkg/commands/xrange.test.ts +++ b/pkg/commands/xrange.test.ts @@ -19,7 +19,8 @@ Deno.test("without options", async (t) => { const field2 = "field2"; const member2 = randomID(); - await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]).exec(client); + await new XAddCommand([key, "*", { [field1]: member1, [field2]: member2 }]) + .exec(client); const res = await new XRangeCommand([key, "-", "+"]).exec(client); assertEquals(Object.keys(res).length, 1); diff --git a/pkg/commands/xrange.ts b/pkg/commands/xrange.ts index 8c1abe94..df42acb3 100644 --- a/pkg/commands/xrange.ts +++ b/pkg/commands/xrange.ts @@ -1,7 +1,7 @@ import { Command, CommandOptions } from "./command.ts"; function deserialize>>( - result: (string | string[])[] + result: (string | string[])[], ): TData { if (result.length === 0) { return {} as TData; @@ -28,13 +28,20 @@ function deserialize>>( return obj as TData; } -export class XRangeCommand>> extends Command< +export class XRangeCommand< + TData extends Record>, +> extends Command< string[][], TData > { constructor( - [key, start, end, count]: [key: string, start: string, end: string, count?: number], - opts?: CommandOptions + [key, start, end, count]: [ + key: string, + start: string, + end: string, + count?: number, + ], + opts?: CommandOptions, ) { const command: unknown[] = ["XRANGE", key, start, end]; if (typeof count === "number") { From cedc1c567c546455918c150d261b63834d6724da Mon Sep 17 00:00:00 2001 From: ogzhanolguncu Date: Tue, 10 Oct 2023 14:57:04 +0300 Subject: [PATCH 6/6] Remove dependabot --- .github/dependabot.yml | 66 ------------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 7e44b55c..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,66 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "npm" - directory: "examples/aws-lambda" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/cloudflare-workers" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/cloudflare-workers-with-typescript" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/cloudflare-workers-with-wrangler-1" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/deno" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/fastly" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/gcp" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/netlify" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/netlify-edge" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/nextjs" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/nextjs_edge" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/nextjs_export" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/nodejs" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/nodejs-18" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/vite" - schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "examples/with-sentry" - schedule: - interval: "daily"