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
81 changes: 81 additions & 0 deletions packages/client/lib/commands/DELEX.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { strict as assert } from "node:assert";
import DELEX, { DelexCondition } from "./DELEX";
import { parseArgs } from "./generic-transformers";
import testUtils, { GLOBAL } from "../test-utils";

describe("DELEX", () => {
describe("transformArguments", () => {
it("no condition", () => {
assert.deepEqual(parseArgs(DELEX, "key"), ["DELEX", "key"]);
});

it("with condition", () => {
assert.deepEqual(
parseArgs(DELEX, "key", {
condition: DelexCondition.IFEQ,
matchValue: "some-value",
}),
["DELEX", "key", "IFEQ", "some-value"]
);
});
});

testUtils.testAll(
"non-existing key",
async (client) => {
assert.equal(await client.delEx("key{tag}"), 0);
},
{
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
}
);

testUtils.testAll(
"non-existing key with condition",
async (client) => {
assert.equal(
await client.delEx("key{tag}", {
condition: DelexCondition.IFDEQ,
matchValue: "digest",
}),
0
);
},
{
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
}
);

testUtils.testAll(
"existing key no condition",
async (client) => {
await client.set("key{tag}", "value");
assert.equal(await client.delEx("key{tag}"), 1);
},
{
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
}
);

testUtils.testAll(
"existing key and condition",
async (client) => {
await client.set("key{tag}", "some-value");

assert.equal(
await client.delEx("key{tag}", {
condition: DelexCondition.IFEQ,
matchValue: "some-value",
}),
1
);
},
{
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
}
);
});
60 changes: 60 additions & 0 deletions packages/client/lib/commands/DELEX.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { CommandParser } from "../client/parser";
import { NumberReply, Command, RedisArgument } from "../RESP/types";

export const DelexCondition = {
/**
* Delete if value equals match-value.
*/
IFEQ: "IFEQ",
/**
* Delete if value does not equal match-value.
*/
IFNE: "IFNE",
/**
* Delete if value digest equals match-digest.
*/
IFDEQ: "IFDEQ",
/**
* Delete if value digest does not equal match-digest.
*/
IFDNE: "IFDNE",
} as const;

type DelexCondition = (typeof DelexCondition)[keyof typeof DelexCondition];

export default {
IS_READ_ONLY: false,
/**
* Conditionally removes the specified key based on value or digest comparison.
*
* @param parser - The Redis command parser
* @param key - Key to delete
*/
parseCommand(
parser: CommandParser,
key: RedisArgument,
options?: {
/**
* The condition to apply when deleting the key.
* - `IFEQ` - Delete if value equals match-value
* - `IFNE` - Delete if value does not equal match-value
* - `IFDEQ` - Delete if value digest equals match-digest
* - `IFDNE` - Delete if value digest does not equal match-digest
*/
condition: DelexCondition;
/**
* The value or digest to compare against
*/
matchValue: RedisArgument;
}
) {
parser.push("DELEX");
parser.pushKey(key);

if (options) {
parser.push(options.condition);
parser.push(options.matchValue);
}
},
transformReply: undefined as unknown as () => NumberReply<1 | 0>,
} as const satisfies Command;
35 changes: 35 additions & 0 deletions packages/client/lib/commands/DIGEST.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { strict as assert } from "node:assert";
import DIGEST from "./DIGEST";
import { parseArgs } from "./generic-transformers";
import testUtils, { GLOBAL } from "../test-utils";

describe("DIGEST", () => {
describe("transformArguments", () => {
it("digest", () => {
assert.deepEqual(parseArgs(DIGEST, "key"), ["DIGEST", "key"]);
});
});

testUtils.testAll(
"existing key",
async (client) => {
await client.set("key{tag}", "value");
assert.equal(typeof await client.digest("key{tag}"), "string");
},
{
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
}
);

testUtils.testAll(
"non-existing key",
async (client) => {
assert.equal(await client.digest("key{tag}"), null);
},
{
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
}
);
});
17 changes: 17 additions & 0 deletions packages/client/lib/commands/DIGEST.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CommandParser } from "../client/parser";
import { Command, RedisArgument, SimpleStringReply } from "../RESP/types";

export default {
IS_READ_ONLY: true,
/**
* Returns the XXH3 hash of a string value.
*
* @param parser - The Redis command parser
* @param key - Key to get the digest of
*/
parseCommand(parser: CommandParser, key: RedisArgument) {
parser.push("DIGEST");
parser.pushKey(key);
},
transformReply: undefined as unknown as () => SimpleStringReply,
} as const satisfies Command;
26 changes: 26 additions & 0 deletions packages/client/lib/commands/SET.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import SET from './SET';
Expand Down Expand Up @@ -127,6 +128,16 @@ describe('SET', () => {
['SET', 'key', 'value', 'XX']
);
});

it('with IFDEQ condition', () => {
assert.deepEqual(
parseArgs(SET, 'key', 'value', {
condition: 'IFDEQ',
matchValue: 'some-value'
}),
['SET', 'key', 'value', 'IFDEQ', 'some-value']
);
});
});

it('with GET', () => {
Expand Down Expand Up @@ -162,4 +173,19 @@ describe('SET', () => {
client: GLOBAL.SERVERS.OPEN,
cluster: GLOBAL.CLUSTERS.OPEN
});

testUtils.testAll('set with IFEQ', async client => {
await client.set('key{tag}', 'some-value');

assert.equal(
await client.set('key{tag}', 'some-value', {
condition: 'IFEQ',
matchValue: 'some-value'
}),
'OK'
);
}, {
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 4] },
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 4] },
});
});
20 changes: 19 additions & 1 deletion packages/client/lib/commands/SET.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,22 @@ export interface SetOptions {
*/
KEEPTTL?: boolean;

condition?: 'NX' | 'XX';
/**
* Condition for setting the key:
* - `NX` - Set if key does not exist
* - `XX` - Set if key already exists
* - `IFEQ` - Set if current value equals match-value (since 8.4, requires `matchValue`)
* - `IFNE` - Set if current value does not equal match-value (since 8.4, requires `matchValue`)
* - `IFDEQ` - Set if current value digest equals match-digest (since 8.4, requires `matchValue`)
* - `IFDNE` - Set if current value digest does not equal match-digest (since 8.4, requires `matchValue`)
*/
condition?: 'NX' | 'XX' | 'IFEQ' | 'IFNE' | 'IFDEQ' | 'IFDNE';

/**
* Value or digest to compare against. Required when using `IFEQ`, `IFNE`, `IFDEQ`, or `IFDNE` conditions.
*/
matchValue?: RedisArgument;

/**
* @deprecated Use `{ condition: 'NX' }` instead.
*/
Expand Down Expand Up @@ -82,6 +97,9 @@ export default {

if (options?.condition) {
parser.push(options.condition);
if (options?.matchValue !== undefined) {
parser.push(options.matchValue);
}
} else if (options?.NX) {
parser.push('NX');
} else if (options?.XX) {
Expand Down
6 changes: 6 additions & 0 deletions packages/client/lib/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ import DBSIZE from './DBSIZE';
import DECR from './DECR';
import DECRBY from './DECRBY';
import DEL from './DEL';
import DELEX from './DELEX';
import DIGEST from './DIGEST';
import DUMP from './DUMP';
import ECHO from './ECHO';
import EVAL_RO from './EVAL_RO';
Expand Down Expand Up @@ -543,6 +545,10 @@ export default {
decrBy: DECRBY,
DEL,
del: DEL,
DELEX,
delEx: DELEX,
DIGEST,
digest: DIGEST,
DUMP,
dump: DUMP,
ECHO,
Expand Down
Loading