Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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: 1 addition & 1 deletion examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "next start"
},
"dependencies": {
"@upstash/redis": "0.0.0-ci.1882836b-20221026",
"@upstash/redis": "1.16.1",
"next": "^12.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
4 changes: 0 additions & 4 deletions pkg/commands/hgetall.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { Command, CommandOptions } from "./command.ts";

/**
* @param result De
* @returns
*/
function deserialize<TData extends Record<string, unknown>>(
result: string[],
): TData | null {
Expand Down
73 changes: 73 additions & 0 deletions pkg/commands/hrandfield.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { keygen, newHttpClient, randomID } from "../test-utils.ts";
import {
assert,
assertEquals,
} from "https://deno.land/[email protected]/testing/asserts.ts";
import { afterAll } from "https://deno.land/[email protected]/testing/bdd.ts";
import { HSetCommand } from "./hset.ts";
import { HRandFieldCommand } from "./hrandfield.ts";

const client = newHttpClient();

const { newKey, cleanup } = keygen();
afterAll(cleanup);
Deno.test("with single field present", async (t) => {
await t.step("returns the field", async () => {
const key = newKey();
const field1 = randomID();
const value1 = randomID();
await new HSetCommand([key, { [field1]: value1 }]).exec(
client,
);

const res = await new HRandFieldCommand([key]).exec(client);

assertEquals(res, field1);
});
});

Deno.test("with multiple fields present", async (t) => {
await t.step("returns a random field", async () => {
const key = newKey();
const fields: Record<string, string> = {};
for (let i = 0; i < 10; i++) {
fields[randomID()] = randomID();
}
await new HSetCommand([key, fields]).exec(
client,
);

const res = await new HRandFieldCommand<string>([key]).exec(client);

assert(res in fields);
});
});

Deno.test("with withvalues", async (t) => {
await t.step("returns a subset with values", async () => {
const key = newKey();
const fields: Record<string, string> = {};
for (let i = 0; i < 10; i++) {
fields[randomID()] = randomID();
}
await new HSetCommand([key, fields]).exec(
client,
);

const res = await new HRandFieldCommand<Record<string, string>>([
key,
2,
true,
]).exec(client);
for (const [k, v] of Object.entries(res)) {
assert(k in fields);
assert(fields[k] === v);
}
});
});
Deno.test("when hash does not exist", async (t) => {
await t.step("it returns null", async () => {
const res = await new HRandFieldCommand([randomID()]).exec(client);
assertEquals(res, null);
});
});
59 changes: 59 additions & 0 deletions pkg/commands/hrandfield.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Command, CommandOptions } from "./command.ts";

function deserialize<TData extends Record<string, unknown>>(
result: string[],
): TData | null {
if (result.length === 0) {
return null;
}
const obj: Record<string, unknown> = {};
while (result.length >= 2) {
const key = result.shift()!;
const value = result.shift()!;
try {
obj[key] = JSON.parse(value);
} catch {
obj[key] = value;
}
}
return obj as TData;
}

/**
* @see https://redis.io/commands/hrandfield
*/
export class HRandFieldCommand<
TData extends string | string[] | Record<string, unknown>,
> extends Command<
string | string[],
TData
> {
constructor(cmd: [key: string], opts?: CommandOptions<string, string>);
constructor(
cmd: [key: string, count: number],
opts?: CommandOptions<string[], string[]>,
);
constructor(
cmd: [key: string, count: number, withValues: boolean],
opts?: CommandOptions<string[], Partial<TData>>,
);
constructor(
cmd: [key: string, count?: number, withValues?: boolean],
opts?: CommandOptions<any, string | string[] | Partial<TData>>,
) {
const command = ["hrandfield", cmd[0]] as unknown[];
if (typeof cmd[1] === "number") {
command.push(cmd[1]);
}
if (cmd[2]) {
command.push("WITHVALUES");
}
super(command, {
// @ts-ignore TODO:
deserialize: cmd[2]
? (result) => deserialize(result as string[])
: opts?.deserialize,
...opts,
});
}
}
3 changes: 2 additions & 1 deletion pkg/commands/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export * from "./flushall.ts";
export * from "./flushdb.ts";
export * from "./get.ts";
export * from "./getbit.ts";
export * from "./getdel.ts";
export * from "./getrange.ts";
export * from "./getset.ts";
export * from "./getdel.ts";
export * from "./hdel.ts";
export * from "./hexists.ts";
export * from "./hget.ts";
Expand All @@ -30,6 +30,7 @@ export * from "./hkeys.ts";
export * from "./hlen.ts";
export * from "./hmget.ts";
export * from "./hmset.ts";
export * from "./hrandfield.ts";
export * from "./hscan.ts";
export * from "./hset.ts";
export * from "./hsetnx.ts";
Expand Down
5 changes: 4 additions & 1 deletion pkg/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ Deno.test("use all the things", async (t) => {
.lrem(newKey(), 1, "value")
.lset(persistentKey, 0, "value")
.ltrim(newKey(), 0, 1)
.hrandfield(newKey())
.hrandfield(newKey(), 2)
.hrandfield(newKey(), 3, true)
.mget<[string, string]>(newKey(), newKey())
.mset({ key1: "value", key2: "value" })
.msetnx({ key3: "value", key4: "value" })
Expand Down Expand Up @@ -215,6 +218,6 @@ Deno.test("use all the things", async (t) => {
.zunionstore(newKey(), 1, [newKey()]);

const res = await p.exec();
assertEquals(res.length, 115);
assertEquals(res.length, 118);
});
});
16 changes: 16 additions & 0 deletions pkg/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import { Requester } from "./http.ts";
import { UpstashResponse } from "./http.ts";
import { CommandArgs } from "./types.ts";
import { ZMScoreCommand } from "./commands/zmscore.ts";
import { HRandFieldCommand } from "./commands/hrandfield.ts";

/**
* Upstash REST API supports command pipelining to send multiple commands in
Expand Down Expand Up @@ -431,6 +432,21 @@ export class Pipeline {
hmset = <TData>(key: string, kv: { [field: string]: TData }) =>
this.chain(new HMSetCommand([key, kv], this.commandOptions));

/**
* @see https://redis.io/commands/hrandfield
*/
hrandfield = <TData extends string | string[] | Record<string, unknown>>(
key: string,
count?: number,
withValues?: boolean,
) =>
this.chain(
new HRandFieldCommand<TData>(
[key, count, withValues] as any,
this.commandOptions,
),
);

/**
* @see https://redis.io/commands/hscan
*/
Expand Down
20 changes: 20 additions & 0 deletions pkg/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
HLenCommand,
HMGetCommand,
HMSetCommand,
HRandFieldCommand,
HScanCommand,
HSetCommand,
HSetNXCommand,
Expand Down Expand Up @@ -410,6 +411,25 @@ export class Redis {
hmset = <TData>(key: string, kv: { [field: string]: TData }) =>
new HMSetCommand([key, kv], this.opts).exec(this.client);

/**
* @see https://redis.io/commands/hrandfield
*/
hrandfield: {
(key: string): Promise<string>;
(key: string, count: number): Promise<string[]>;
<TData extends Record<string, unknown>>(
key: string,
count: number,
withValues: boolean,
): Promise<Partial<TData>>;
} = <TData extends string | string[] | Record<string, unknown>>(
key: string,
count?: number,
withValues?: boolean,
) =>
new HRandFieldCommand<TData>([key, count, withValues] as any, this.opts)
.exec(this.client);

/**
* @see https://redis.io/commands/hscan
*/
Expand Down