You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-04 15:02:09 +03:00
feat: add support for vector sets (#2998)
* wip * improve the vadd api * resp3 tests * fix some tests * extract json helper functions in client package * use transformJsonReply * remove the CACHEABLE flag for all vector set commands currently, client side caching is not supported for vector set commands by the server * properly transform vinfo result * add resp3 test for vlinks * add more tests for vrandmember * fix vrem return types * fix vsetattr return type * fix vsim_withscores * implement vlinks_withscores * set minimum docker image version to 8 * align return types * add RAW variant for VEMB -> VEMB_RAW * use the new parseCommand api
This commit is contained in:
committed by
GitHub
parent
b52177752e
commit
c5b4f47975
121
packages/client/lib/commands/VADD.spec.ts
Normal file
121
packages/client/lib/commands/VADD.spec.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VADD from './VADD';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VADD', () => {
|
||||
describe('parseCommand', () => {
|
||||
it('basic usage', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VADD.parseCommand(parser, 'key', [1.0, 2.0, 3.0], 'element');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VADD', 'key', 'VALUES', '3', '1', '2', '3', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
it('with REDUCE option', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VADD.parseCommand(parser, 'key', [1.0, 2], 'element', { REDUCE: 50 });
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VADD', 'key', 'REDUCE', '50', 'VALUES', '2', '1', '2', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
it('with quantization options', () => {
|
||||
let parser = new BasicCommandParser();
|
||||
VADD.parseCommand(parser, 'key', [1.0, 2.0], 'element', { QUANT: 'Q8' });
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VADD', 'key', 'VALUES', '2', '1', '2', 'element', 'Q8']
|
||||
);
|
||||
|
||||
parser = new BasicCommandParser();
|
||||
VADD.parseCommand(parser, 'key', [1.0, 2.0], 'element', { QUANT: 'BIN' });
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VADD', 'key', 'VALUES', '2', '1', '2', 'element', 'BIN']
|
||||
);
|
||||
|
||||
parser = new BasicCommandParser();
|
||||
VADD.parseCommand(parser, 'key', [1.0, 2.0], 'element', { QUANT: 'NOQUANT' });
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VADD', 'key', 'VALUES', '2', '1', '2', 'element', 'NOQUANT']
|
||||
);
|
||||
});
|
||||
|
||||
it('with all options', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VADD.parseCommand(parser, 'key', [1.0, 2.0], 'element', {
|
||||
REDUCE: 50,
|
||||
CAS: true,
|
||||
QUANT: 'Q8',
|
||||
EF: 200,
|
||||
SETATTR: { name: 'test', value: 42 },
|
||||
M: 16
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'VADD', 'key', 'REDUCE', '50', 'VALUES', '2', '1', '2', 'element',
|
||||
'CAS', 'Q8', 'EF', '200', 'SETATTR', '{"name":"test","value":42}', 'M', '16'
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testAll('vAdd', async client => {
|
||||
assert.equal(
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element'),
|
||||
true
|
||||
);
|
||||
|
||||
// same element should not be added again
|
||||
assert.equal(
|
||||
await client.vAdd('key', [1, 2 , 3], 'element'),
|
||||
false
|
||||
);
|
||||
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vAdd with RESP3', async client => {
|
||||
// Test basic functionality with RESP3
|
||||
assert.equal(
|
||||
await client.vAdd('resp3-key', [1.5, 2.5, 3.5], 'resp3-element'),
|
||||
true
|
||||
);
|
||||
|
||||
// same element should not be added again
|
||||
assert.equal(
|
||||
await client.vAdd('resp3-key', [1, 2 , 3], 'resp3-element'),
|
||||
false
|
||||
);
|
||||
|
||||
// Test with options to ensure complex parameters work with RESP3
|
||||
assert.equal(
|
||||
await client.vAdd('resp3-key', [4.0, 5.0, 6.0], 'resp3-element2', {
|
||||
QUANT: 'Q8',
|
||||
CAS: true,
|
||||
SETATTR: { type: 'test', value: 123 }
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
// Verify the vector set was created correctly
|
||||
assert.equal(
|
||||
await client.vCard('resp3-key'),
|
||||
2
|
||||
);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
65
packages/client/lib/commands/VADD.ts
Normal file
65
packages/client/lib/commands/VADD.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, Command } from '../RESP/types';
|
||||
import { transformBooleanReply, transformDoubleArgument } from './generic-transformers';
|
||||
|
||||
export interface VAddOptions {
|
||||
REDUCE?: number;
|
||||
CAS?: boolean;
|
||||
QUANT?: 'NOQUANT' | 'BIN' | 'Q8',
|
||||
EF?: number;
|
||||
SETATTR?: Record<string, any>;
|
||||
M?: number;
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Add a new element into the vector set specified by key
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The name of the key that will hold the vector set data
|
||||
* @param vector - The vector data as array of numbers
|
||||
* @param element - The name of the element being added to the vector set
|
||||
* @param options - Optional parameters for vector addition
|
||||
* @see https://redis.io/commands/vadd/
|
||||
*/
|
||||
parseCommand(
|
||||
parser: CommandParser,
|
||||
key: RedisArgument,
|
||||
vector: Array<number>,
|
||||
element: RedisArgument,
|
||||
options?: VAddOptions
|
||||
) {
|
||||
parser.push('VADD');
|
||||
parser.pushKey(key);
|
||||
|
||||
if (options?.REDUCE !== undefined) {
|
||||
parser.push('REDUCE', options.REDUCE.toString());
|
||||
}
|
||||
|
||||
parser.push('VALUES', vector.length.toString());
|
||||
for (const value of vector) {
|
||||
parser.push(transformDoubleArgument(value));
|
||||
}
|
||||
|
||||
parser.push(element);
|
||||
|
||||
if (options?.CAS) {
|
||||
parser.push('CAS');
|
||||
}
|
||||
|
||||
options?.QUANT && parser.push(options.QUANT);
|
||||
|
||||
if (options?.EF !== undefined) {
|
||||
parser.push('EF', options.EF.toString());
|
||||
}
|
||||
|
||||
if (options?.SETATTR) {
|
||||
parser.push('SETATTR', JSON.stringify(options.SETATTR));
|
||||
}
|
||||
|
||||
if (options?.M !== undefined) {
|
||||
parser.push('M', options.M.toString());
|
||||
}
|
||||
},
|
||||
transformReply: transformBooleanReply
|
||||
} as const satisfies Command;
|
60
packages/client/lib/commands/VCARD.spec.ts
Normal file
60
packages/client/lib/commands/VCARD.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VCARD from './VCARD';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VCARD', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VCARD.parseCommand(parser, 'key')
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VCARD', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vCard', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [4.0, 5.0, 6.0], 'element2');
|
||||
|
||||
assert.equal(
|
||||
await client.vCard('key'),
|
||||
2
|
||||
);
|
||||
|
||||
assert.equal(await client.vCard('unknown'), 0);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vCard with RESP3', async client => {
|
||||
// Test empty vector set
|
||||
assert.equal(
|
||||
await client.vCard('resp3-empty-key'),
|
||||
0
|
||||
);
|
||||
|
||||
// Add elements and test cardinality
|
||||
await client.vAdd('resp3-key', [1.0, 2.0], 'elem1');
|
||||
assert.equal(
|
||||
await client.vCard('resp3-key'),
|
||||
1
|
||||
);
|
||||
|
||||
await client.vAdd('resp3-key', [3.0, 4.0], 'elem2');
|
||||
await client.vAdd('resp3-key', [5.0, 6.0], 'elem3');
|
||||
assert.equal(
|
||||
await client.vCard('resp3-key'),
|
||||
3
|
||||
);
|
||||
|
||||
assert.equal(await client.vCard('unknown'), 0);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
18
packages/client/lib/commands/VCARD.ts
Normal file
18
packages/client/lib/commands/VCARD.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, NumberReply, Command } from '../RESP/types';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve the number of elements in a vector set
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @see https://redis.io/commands/vcard/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument) {
|
||||
parser.push('VCARD');
|
||||
parser.pushKey(key);
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply
|
||||
} as const satisfies Command;
|
43
packages/client/lib/commands/VDIM.spec.ts
Normal file
43
packages/client/lib/commands/VDIM.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VDIM from './VDIM';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VDIM', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VDIM.parseCommand(parser, 'key');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VDIM', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vDim', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element');
|
||||
|
||||
assert.equal(
|
||||
await client.vDim('key'),
|
||||
3
|
||||
);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vDim with RESP3', async client => {
|
||||
await client.vAdd('resp3-5d', [1.0, 2.0, 3.0, 4.0, 5.0], 'elem5d');
|
||||
|
||||
assert.equal(
|
||||
await client.vDim('resp3-5d'),
|
||||
5
|
||||
);
|
||||
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
18
packages/client/lib/commands/VDIM.ts
Normal file
18
packages/client/lib/commands/VDIM.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, NumberReply, Command } from '../RESP/types';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve the dimension of the vectors in a vector set
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @see https://redis.io/commands/vdim/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument) {
|
||||
parser.push('VDIM');
|
||||
parser.pushKey(key);
|
||||
},
|
||||
transformReply: undefined as unknown as () => NumberReply
|
||||
} as const satisfies Command;
|
42
packages/client/lib/commands/VEMB.spec.ts
Normal file
42
packages/client/lib/commands/VEMB.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VEMB from './VEMB';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VEMB', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VEMB.parseCommand(parser, 'key', 'element');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VEMB', 'key', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vEmb', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element');
|
||||
|
||||
const result = await client.vEmb('key', 'element');
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 3);
|
||||
assert.equal(typeof result[0], 'number');
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vEmb with RESP3', async client => {
|
||||
await client.vAdd('resp3-key', [1.5, 2.5, 3.5, 4.5], 'resp3-element');
|
||||
|
||||
const result = await client.vEmb('resp3-key', 'resp3-element');
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 4);
|
||||
assert.equal(typeof result[0], 'number');
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
21
packages/client/lib/commands/VEMB.ts
Normal file
21
packages/client/lib/commands/VEMB.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, Command } from '../RESP/types';
|
||||
import { transformDoubleArrayReply } from './generic-transformers';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve the approximate vector associated with a vector set element
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param element - The name of the element to retrieve the vector for
|
||||
* @see https://redis.io/commands/vemb/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument, element: RedisArgument) {
|
||||
parser.push('VEMB');
|
||||
parser.pushKey(key);
|
||||
parser.push(element);
|
||||
},
|
||||
transformReply: transformDoubleArrayReply
|
||||
} as const satisfies Command;
|
68
packages/client/lib/commands/VEMB_RAW.spec.ts
Normal file
68
packages/client/lib/commands/VEMB_RAW.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VEMB_RAW from './VEMB_RAW';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VEMB_RAW', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VEMB_RAW.parseCommand(parser, 'key', 'element');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VEMB', 'key', 'element', 'RAW']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vEmbRaw', async client => {
|
||||
await client.vAdd('key1', [1.0, 2.0, 3.0], 'element');
|
||||
const result1 = await client.vEmbRaw('key1', 'element');
|
||||
assert.equal(result1.quantization, 'int8');
|
||||
assert.ok(result1.quantizationRange !== undefined);
|
||||
|
||||
await client.vAdd('key2', [1.0, 2.0, 3.0], 'element', { QUANT: 'Q8' });
|
||||
const result2 = await client.vEmbRaw('key2', 'element');
|
||||
assert.equal(result2.quantization, 'int8');
|
||||
assert.ok(result2.quantizationRange !== undefined);
|
||||
|
||||
await client.vAdd('key3', [1.0, 2.0, 3.0], 'element', { QUANT: 'NOQUANT' });
|
||||
const result3 = await client.vEmbRaw('key3', 'element');
|
||||
assert.equal(result3.quantization, 'f32');
|
||||
assert.equal(result3.quantizationRange, undefined);
|
||||
|
||||
await client.vAdd('key4', [1.0, 2.0, 3.0], 'element', { QUANT: 'BIN' });
|
||||
const result4 = await client.vEmbRaw('key4', 'element');
|
||||
assert.equal(result4.quantization, 'bin');
|
||||
assert.equal(result4.quantizationRange, undefined);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vEmbRaw with RESP3', async client => {
|
||||
await client.vAdd('key1', [1.0, 2.0, 3.0], 'element');
|
||||
const result1 = await client.vEmbRaw('key1', 'element');
|
||||
assert.equal(result1.quantization, 'int8');
|
||||
assert.ok(result1.quantizationRange !== undefined);
|
||||
|
||||
await client.vAdd('key2', [1.0, 2.0, 3.0], 'element', { QUANT: 'Q8' });
|
||||
const result2 = await client.vEmbRaw('key2', 'element');
|
||||
assert.equal(result2.quantization, 'int8');
|
||||
assert.ok(result2.quantizationRange !== undefined);
|
||||
|
||||
await client.vAdd('key3', [1.0, 2.0, 3.0], 'element', { QUANT: 'NOQUANT' });
|
||||
const result3 = await client.vEmbRaw('key3', 'element');
|
||||
assert.equal(result3.quantization, 'f32');
|
||||
assert.equal(result3.quantizationRange, undefined);
|
||||
|
||||
await client.vAdd('key4', [1.0, 2.0, 3.0], 'element', { QUANT: 'BIN' });
|
||||
const result4 = await client.vEmbRaw('key4', 'element');
|
||||
assert.equal(result4.quantization, 'bin');
|
||||
assert.equal(result4.quantizationRange, undefined);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
57
packages/client/lib/commands/VEMB_RAW.ts
Normal file
57
packages/client/lib/commands/VEMB_RAW.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import {
|
||||
RedisArgument,
|
||||
Command,
|
||||
BlobStringReply,
|
||||
SimpleStringReply,
|
||||
DoubleReply
|
||||
} from '../RESP/types';
|
||||
import { transformDoubleReply } from './generic-transformers';
|
||||
import VEMB from './VEMB';
|
||||
|
||||
type RawVembReply = {
|
||||
quantization: SimpleStringReply;
|
||||
raw: BlobStringReply;
|
||||
l2Norm: DoubleReply;
|
||||
quantizationRange?: DoubleReply;
|
||||
};
|
||||
|
||||
const transformRawVembReply = {
|
||||
2: (reply: any[]): RawVembReply => {
|
||||
return {
|
||||
quantization: reply[0],
|
||||
raw: reply[1],
|
||||
l2Norm: transformDoubleReply[2](reply[2]),
|
||||
...(reply[3] !== undefined && { quantizationRange: transformDoubleReply[2](reply[3]) })
|
||||
};
|
||||
},
|
||||
3: (reply: any[]): RawVembReply => {
|
||||
return {
|
||||
quantization: reply[0],
|
||||
raw: reply[1],
|
||||
l2Norm: reply[2],
|
||||
quantizationRange: reply[3]
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve the RAW approximate vector associated with a vector set element
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param element - The name of the element to retrieve the vector for
|
||||
* @see https://redis.io/commands/vemb/
|
||||
*/
|
||||
parseCommand(
|
||||
parser: CommandParser,
|
||||
key: RedisArgument,
|
||||
element: RedisArgument
|
||||
) {
|
||||
VEMB.parseCommand(parser, key, element);
|
||||
parser.push('RAW');
|
||||
},
|
||||
transformReply: transformRawVembReply
|
||||
} as const satisfies Command;
|
77
packages/client/lib/commands/VGETATTR.spec.ts
Normal file
77
packages/client/lib/commands/VGETATTR.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VGETATTR from './VGETATTR';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VGETATTR', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VGETATTR.parseCommand(parser, 'key', 'element');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VGETATTR', 'key', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vGetAttr', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element');
|
||||
|
||||
const nullResult = await client.vGetAttr('key', 'element');
|
||||
assert.equal(nullResult, null);
|
||||
|
||||
await client.vSetAttr('key', 'element', { name: 'test' });
|
||||
|
||||
const result = await client.vGetAttr('key', 'element');
|
||||
|
||||
assert.ok(result !== null);
|
||||
assert.equal(typeof result, 'object')
|
||||
|
||||
assert.deepEqual(result, {
|
||||
name: 'test'
|
||||
})
|
||||
|
||||
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vGetAttr with RESP3', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0], 'resp3-element');
|
||||
|
||||
// Test null case (no attributes set)
|
||||
const nullResult = await client.vGetAttr('resp3-key', 'resp3-element');
|
||||
|
||||
assert.equal(nullResult, null);
|
||||
|
||||
// Set complex attributes and retrieve them
|
||||
const complexAttrs = {
|
||||
name: 'test-item',
|
||||
category: 'electronics',
|
||||
price: 99.99,
|
||||
inStock: true,
|
||||
tags: ['new', 'featured']
|
||||
};
|
||||
await client.vSetAttr('resp3-key', 'resp3-element', complexAttrs);
|
||||
|
||||
const result = await client.vGetAttr('resp3-key', 'resp3-element');
|
||||
|
||||
assert.ok(result !== null);
|
||||
assert.equal(typeof result, 'object')
|
||||
|
||||
assert.deepEqual(result, {
|
||||
name: 'test-item',
|
||||
category: 'electronics',
|
||||
price: 99.99,
|
||||
inStock: true,
|
||||
tags: ['new', 'featured']
|
||||
})
|
||||
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
21
packages/client/lib/commands/VGETATTR.ts
Normal file
21
packages/client/lib/commands/VGETATTR.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, Command } from '../RESP/types';
|
||||
import { transformRedisJsonNullReply } from './generic-transformers';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve the attributes of a vector set element
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param element - The name of the element to retrieve attributes for
|
||||
* @see https://redis.io/commands/vgetattr/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument, element: RedisArgument) {
|
||||
parser.push('VGETATTR');
|
||||
parser.pushKey(key);
|
||||
parser.push(element);
|
||||
},
|
||||
transformReply: transformRedisJsonNullReply
|
||||
} as const satisfies Command;
|
58
packages/client/lib/commands/VINFO.spec.ts
Normal file
58
packages/client/lib/commands/VINFO.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VINFO from './VINFO';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VINFO', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VINFO.parseCommand(parser, 'key');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VINFO', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vInfo', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element');
|
||||
|
||||
const result = await client.vInfo('key');
|
||||
assert.ok(typeof result === 'object' && result !== null);
|
||||
|
||||
assert.equal(result['vector-dim'], 3);
|
||||
assert.equal(result['size'], 1);
|
||||
assert.ok('quant-type' in result);
|
||||
assert.ok('hnsw-m' in result);
|
||||
assert.ok('projection-input-dim' in result);
|
||||
assert.ok('max-level' in result);
|
||||
assert.ok('attributes-count' in result);
|
||||
assert.ok('vset-uid' in result);
|
||||
assert.ok('hnsw-max-node-uid' in result);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vInfo with RESP3', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'resp3-element');
|
||||
|
||||
const result = await client.vInfo('resp3-key');
|
||||
assert.ok(typeof result === 'object' && result !== null);
|
||||
|
||||
assert.equal(result['vector-dim'], 3);
|
||||
assert.equal(result['size'], 1);
|
||||
assert.ok('quant-type' in result);
|
||||
assert.ok('hnsw-m' in result);
|
||||
assert.ok('projection-input-dim' in result);
|
||||
assert.ok('max-level' in result);
|
||||
assert.ok('attributes-count' in result);
|
||||
assert.ok('vset-uid' in result);
|
||||
assert.ok('hnsw-max-node-uid' in result);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
38
packages/client/lib/commands/VINFO.ts
Normal file
38
packages/client/lib/commands/VINFO.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, Command, UnwrapReply, Resp2Reply, TuplesToMapReply, SimpleStringReply, NumberReply } from '../RESP/types';
|
||||
|
||||
export type VInfoReplyMap = TuplesToMapReply<[
|
||||
[SimpleStringReply<'quant-type'>, SimpleStringReply],
|
||||
[SimpleStringReply<'vector-dim'>, NumberReply],
|
||||
[SimpleStringReply<'size'>, NumberReply],
|
||||
[SimpleStringReply<'max-level'>, NumberReply],
|
||||
[SimpleStringReply<'vset-uid'>, NumberReply],
|
||||
[SimpleStringReply<'hnsw-max-node-uid'>, NumberReply],
|
||||
]>;
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve metadata and internal details about a vector set, including size, dimensions, quantization type, and graph structure
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @see https://redis.io/commands/vinfo/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument) {
|
||||
parser.push('VINFO');
|
||||
parser.pushKey(key);
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: UnwrapReply<Resp2Reply<VInfoReplyMap>>): VInfoReplyMap => {
|
||||
const ret = Object.create(null);
|
||||
|
||||
for (let i = 0; i < reply.length; i += 2) {
|
||||
ret[reply[i].toString()] = reply[i + 1];
|
||||
}
|
||||
|
||||
return ret as unknown as VInfoReplyMap;
|
||||
},
|
||||
3: undefined as unknown as () => VInfoReplyMap
|
||||
}
|
||||
} as const satisfies Command;
|
42
packages/client/lib/commands/VLINKS.spec.ts
Normal file
42
packages/client/lib/commands/VLINKS.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VLINKS from './VLINKS';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VLINKS', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VLINKS.parseCommand(parser, 'key', 'element');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VLINKS', 'key', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vLinks', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [1.1, 2.1, 3.1], 'element2');
|
||||
|
||||
const result = await client.vLinks('key', 'element1');
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.ok(result.length)
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vLinks with RESP3', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [1.1, 2.1, 3.1], 'element2');
|
||||
|
||||
const result = await client.vLinks('resp3-key', 'element1');
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.ok(result.length)
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
20
packages/client/lib/commands/VLINKS.ts
Normal file
20
packages/client/lib/commands/VLINKS.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '../RESP/types';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve the neighbors of a specified element in a vector set; the connections for each layer of the HNSW graph
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param element - The name of the element to retrieve neighbors for
|
||||
* @see https://redis.io/commands/vlinks/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument, element: RedisArgument) {
|
||||
parser.push('VLINKS');
|
||||
parser.pushKey(key);
|
||||
parser.push(element);
|
||||
},
|
||||
transformReply: undefined as unknown as () => ArrayReply<ArrayReply<BlobStringReply>>
|
||||
} as const satisfies Command;
|
75
packages/client/lib/commands/VLINKS_WITHSCORES.spec.ts
Normal file
75
packages/client/lib/commands/VLINKS_WITHSCORES.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VLINKS_WITHSCORES from './VLINKS_WITHSCORES';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VLINKS WITHSCORES', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VLINKS_WITHSCORES.parseCommand(parser, 'key', 'element');
|
||||
assert.deepEqual(parser.redisArgs, [
|
||||
'VLINKS',
|
||||
'key',
|
||||
'element',
|
||||
'WITHSCORES'
|
||||
]);
|
||||
});
|
||||
|
||||
testUtils.testAll(
|
||||
'vLinksWithScores',
|
||||
async client => {
|
||||
// Create a vector set with multiple elements to build HNSW graph layers
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [1.1, 2.1, 3.1], 'element2');
|
||||
await client.vAdd('key', [1.2, 2.2, 3.2], 'element3');
|
||||
await client.vAdd('key', [2.0, 3.0, 4.0], 'element4');
|
||||
|
||||
const result = await client.vLinksWithScores('key', 'element1');
|
||||
|
||||
assert.ok(Array.isArray(result));
|
||||
|
||||
for (const layer of result) {
|
||||
assert.equal(
|
||||
typeof layer,
|
||||
'object'
|
||||
);
|
||||
}
|
||||
|
||||
assert.ok(result.length >= 1, 'Should have at least layer 0');
|
||||
},
|
||||
{
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
}
|
||||
);
|
||||
|
||||
testUtils.testWithClient(
|
||||
'vLinksWithScores with RESP3',
|
||||
async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [1.1, 2.1, 3.1], 'element2');
|
||||
await client.vAdd('resp3-key', [1.2, 2.2, 3.2], 'element3');
|
||||
await client.vAdd('resp3-key', [2.0, 3.0, 4.0], 'element4');
|
||||
|
||||
const result = await client.vLinksWithScores('resp3-key', 'element1');
|
||||
|
||||
assert.ok(Array.isArray(result));
|
||||
|
||||
for (const layer of result) {
|
||||
assert.equal(
|
||||
typeof layer,
|
||||
'object'
|
||||
);
|
||||
}
|
||||
|
||||
assert.ok(result.length >= 1, 'Should have at least layer 0');
|
||||
},
|
||||
{
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
}
|
||||
);
|
||||
});
|
42
packages/client/lib/commands/VLINKS_WITHSCORES.ts
Normal file
42
packages/client/lib/commands/VLINKS_WITHSCORES.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { BlobStringReply, Command, DoubleReply, MapReply } from '../RESP/types';
|
||||
import { transformDoubleReply } from './generic-transformers';
|
||||
import VLINKS from './VLINKS';
|
||||
|
||||
|
||||
function transformVLinksWithScoresReply(reply: any): Array<Record<string, DoubleReply>> {
|
||||
const layers: Array<Record<string, DoubleReply>> = [];
|
||||
|
||||
for (const layer of reply) {
|
||||
const obj: Record<string, DoubleReply> = Object.create(null);
|
||||
|
||||
// Each layer contains alternating element names and scores
|
||||
for (let i = 0; i < layer.length; i += 2) {
|
||||
const element = layer[i];
|
||||
const score = transformDoubleReply[2](layer[i + 1]);
|
||||
obj[element.toString()] = score;
|
||||
}
|
||||
|
||||
layers.push(obj);
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: VLINKS.IS_READ_ONLY,
|
||||
/**
|
||||
* Get the connections for each layer of the HNSW graph with similarity scores
|
||||
* @param args - Same parameters as the VLINKS command
|
||||
* @see https://redis.io/commands/vlinks/
|
||||
*/
|
||||
parseCommand(...args: Parameters<typeof VLINKS.parseCommand>) {
|
||||
const parser = args[0];
|
||||
|
||||
VLINKS.parseCommand(...args);
|
||||
parser.push('WITHSCORES');
|
||||
},
|
||||
transformReply: {
|
||||
2: transformVLinksWithScoresReply,
|
||||
3: undefined as unknown as () => Array<MapReply<BlobStringReply, DoubleReply>>
|
||||
}
|
||||
} as const satisfies Command;
|
201
packages/client/lib/commands/VRANDMEMBER.spec.ts
Normal file
201
packages/client/lib/commands/VRANDMEMBER.spec.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VRANDMEMBER from './VRANDMEMBER';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VRANDMEMBER', () => {
|
||||
describe('parseCommand', () => {
|
||||
it('without count', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VRANDMEMBER.parseCommand(parser, 'key');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VRANDMEMBER', 'key']
|
||||
);
|
||||
});
|
||||
|
||||
it('with count', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VRANDMEMBER.parseCommand(parser, 'key', 2);
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VRANDMEMBER', 'key', '2']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RESP2 tests', () => {
|
||||
testUtils.testAll('vRandMember without count - returns single element as string', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [4.0, 5.0, 6.0], 'element2');
|
||||
await client.vAdd('key', [7.0, 8.0, 9.0], 'element3');
|
||||
|
||||
const result = await client.vRandMember('key');
|
||||
assert.equal(typeof result, 'string');
|
||||
assert.ok(['element1', 'element2', 'element3'].includes(result as string));
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testAll('vRandMember with positive count - returns distinct elements', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [4.0, 5.0, 6.0], 'element2');
|
||||
await client.vAdd('key', [7.0, 8.0, 9.0], 'element3');
|
||||
|
||||
const result = await client.vRandMember('key', 2);
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 2);
|
||||
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testAll('vRandMember with negative count - allows duplicates', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [4.0, 5.0, 6.0], 'element2');
|
||||
|
||||
const result = await client.vRandMember('key', -5);
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 5);
|
||||
|
||||
// All elements should be from our set (duplicates allowed)
|
||||
result.forEach(element => {
|
||||
assert.ok(['element1', 'element2'].includes(element));
|
||||
});
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testAll('vRandMember count exceeds set size - returns entire set', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [4.0, 5.0, 6.0], 'element2');
|
||||
|
||||
const result = await client.vRandMember('key', 10);
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 2); // Only 2 elements exist
|
||||
|
||||
// Should contain both elements
|
||||
assert.ok(result.includes('element1'));
|
||||
assert.ok(result.includes('element2'));
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testAll('vRandMember on non-existent key', async client => {
|
||||
// Without count - should return null
|
||||
const resultNoCount = await client.vRandMember('nonexistent');
|
||||
assert.equal(resultNoCount, null);
|
||||
|
||||
// With count - should return empty array
|
||||
const resultWithCount = await client.vRandMember('nonexistent', 5);
|
||||
assert.ok(Array.isArray(resultWithCount));
|
||||
assert.equal(resultWithCount.length, 0);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
});
|
||||
|
||||
describe('RESP3 tests', () => {
|
||||
testUtils.testWithClient('vRandMember without count - returns single element as string', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [4.0, 5.0, 6.0], 'element2');
|
||||
await client.vAdd('resp3-key', [7.0, 8.0, 9.0], 'element3');
|
||||
|
||||
const result = await client.vRandMember('resp3-key');
|
||||
assert.equal(typeof result, 'string');
|
||||
assert.ok(['element1', 'element2', 'element3'].includes(result as string));
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vRandMember with positive count - returns distinct elements', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [4.0, 5.0, 6.0], 'element2');
|
||||
await client.vAdd('resp3-key', [7.0, 8.0, 9.0], 'element3');
|
||||
|
||||
const result = await client.vRandMember('resp3-key', 2);
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 2);
|
||||
|
||||
// Should be distinct elements (no duplicates)
|
||||
const uniqueElements = new Set(result);
|
||||
assert.equal(uniqueElements.size, 2);
|
||||
|
||||
// All elements should be from our set
|
||||
result.forEach(element => {
|
||||
assert.ok(['element1', 'element2', 'element3'].includes(element));
|
||||
});
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vRandMember with negative count - allows duplicates', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [4.0, 5.0, 6.0], 'element2');
|
||||
|
||||
const result = await client.vRandMember('resp3-key', -5);
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 5);
|
||||
|
||||
// All elements should be from our set (duplicates allowed)
|
||||
result.forEach(element => {
|
||||
assert.ok(['element1', 'element2'].includes(element));
|
||||
});
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vRandMember count exceeds set size - returns entire set', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [4.0, 5.0, 6.0], 'element2');
|
||||
|
||||
const result = await client.vRandMember('resp3-key', 10);
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.equal(result.length, 2); // Only 2 elements exist
|
||||
|
||||
// Should contain both elements
|
||||
assert.ok(result.includes('element1'));
|
||||
assert.ok(result.includes('element2'));
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vRandMember on non-existent key', async client => {
|
||||
// Without count - should return null
|
||||
const resultNoCount = await client.vRandMember('resp3-nonexistent');
|
||||
assert.equal(resultNoCount, null);
|
||||
|
||||
// With count - should return empty array
|
||||
const resultWithCount = await client.vRandMember('resp3-nonexistent', 5);
|
||||
assert.ok(Array.isArray(resultWithCount));
|
||||
assert.equal(resultWithCount.length, 0);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
||||
});
|
23
packages/client/lib/commands/VRANDMEMBER.ts
Normal file
23
packages/client/lib/commands/VRANDMEMBER.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, BlobStringReply, ArrayReply, Command, NullReply } from '../RESP/types';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve random elements of a vector set
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param count - Optional number of elements to return
|
||||
* @see https://redis.io/commands/vrandmember/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument, count?: number) {
|
||||
parser.push('VRANDMEMBER');
|
||||
parser.pushKey(key);
|
||||
|
||||
if (count !== undefined) {
|
||||
parser.push(count.toString());
|
||||
}
|
||||
},
|
||||
transformReply: undefined as unknown as () => BlobStringReply | ArrayReply<BlobStringReply> | NullReply
|
||||
} as const satisfies Command;
|
63
packages/client/lib/commands/VREM.spec.ts
Normal file
63
packages/client/lib/commands/VREM.spec.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VREM from './VREM';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VREM', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VREM.parseCommand(parser, 'key', 'element');
|
||||
it('parseCommand', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VREM', 'key', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testAll('vRem', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element');
|
||||
|
||||
assert.equal(
|
||||
await client.vRem('key', 'element'),
|
||||
true
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await client.vRem('key', 'element'),
|
||||
false
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await client.vCard('key'),
|
||||
0
|
||||
);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vRem with RESP3', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'resp3-element');
|
||||
|
||||
assert.equal(
|
||||
await client.vRem('resp3-key', 'resp3-element'),
|
||||
true
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await client.vRem('resp3-key', 'resp3-element'),
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
assert.equal(
|
||||
await client.vCard('resp3-key'),
|
||||
0
|
||||
);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
20
packages/client/lib/commands/VREM.ts
Normal file
20
packages/client/lib/commands/VREM.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, Command } from '../RESP/types';
|
||||
import { transformBooleanReply } from './generic-transformers';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Remove an element from a vector set
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param element - The name of the element to remove from the vector set
|
||||
* @see https://redis.io/commands/vrem/
|
||||
*/
|
||||
parseCommand(parser: CommandParser, key: RedisArgument, element: RedisArgument) {
|
||||
parser.push('VREM');
|
||||
parser.pushKey(key);
|
||||
parser.push(element);
|
||||
},
|
||||
transformReply: transformBooleanReply
|
||||
} as const satisfies Command;
|
58
packages/client/lib/commands/VSETATTR.spec.ts
Normal file
58
packages/client/lib/commands/VSETATTR.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VSETATTR from './VSETATTR';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VSETATTR', () => {
|
||||
describe('parseCommand', () => {
|
||||
it('with object', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VSETATTR.parseCommand(parser, 'key', 'element', { name: 'test', value: 42 }),
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VSETATTR', 'key', 'element', '{"name":"test","value":42}']
|
||||
);
|
||||
});
|
||||
|
||||
it('with string', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VSETATTR.parseCommand(parser, 'key', 'element', '{"name":"test"}'),
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VSETATTR', 'key', 'element', '{"name":"test"}']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testAll('vSetAttr', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element');
|
||||
|
||||
assert.equal(
|
||||
await client.vSetAttr('key', 'element', { name: 'test', value: 42 }),
|
||||
true
|
||||
);
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vSetAttr with RESP3 - returns boolean', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'resp3-element');
|
||||
|
||||
const result = await client.vSetAttr('resp3-key', 'resp3-element', {
|
||||
name: 'test-item',
|
||||
category: 'electronics',
|
||||
price: 99.99
|
||||
});
|
||||
|
||||
// RESP3 returns boolean instead of number
|
||||
assert.equal(typeof result, 'boolean');
|
||||
assert.equal(result, true);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
32
packages/client/lib/commands/VSETATTR.ts
Normal file
32
packages/client/lib/commands/VSETATTR.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, Command } from '../RESP/types';
|
||||
import { transformBooleanReply } from './generic-transformers';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Set or replace attributes on a vector set element
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param element - The name of the element to set attributes for
|
||||
* @param attributes - The attributes to set (as JSON string or object)
|
||||
* @see https://redis.io/commands/vsetattr/
|
||||
*/
|
||||
parseCommand(
|
||||
parser: CommandParser,
|
||||
key: RedisArgument,
|
||||
element: RedisArgument,
|
||||
attributes: RedisArgument | Record<string, any>
|
||||
) {
|
||||
parser.push('VSETATTR');
|
||||
parser.pushKey(key);
|
||||
parser.push(element);
|
||||
|
||||
if (typeof attributes === 'object' && attributes !== null) {
|
||||
parser.push(JSON.stringify(attributes));
|
||||
} else {
|
||||
parser.push(attributes);
|
||||
}
|
||||
},
|
||||
transformReply: transformBooleanReply
|
||||
} as const satisfies Command;
|
85
packages/client/lib/commands/VSIM.spec.ts
Normal file
85
packages/client/lib/commands/VSIM.spec.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VSIM from './VSIM';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VSIM', () => {
|
||||
describe('parseCommand', () => {
|
||||
it('with vector', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VSIM.parseCommand(parser, 'key', [1.0, 2.0, 3.0]),
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VSIM', 'key', 'VALUES', '3', '1', '2', '3']
|
||||
);
|
||||
});
|
||||
|
||||
it('with element', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VSIM.parseCommand(parser, 'key', 'element');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['VSIM', 'key', 'ELE', 'element']
|
||||
);
|
||||
});
|
||||
|
||||
it('with options', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VSIM.parseCommand(parser, 'key', 'element', {
|
||||
COUNT: 5,
|
||||
EF: 100,
|
||||
FILTER: '.price > 20',
|
||||
'FILTER-EF': 50,
|
||||
TRUTH: true,
|
||||
NOTHREAD: true
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'VSIM', 'key', 'ELE', 'element',
|
||||
'COUNT', '5', 'EF', '100', 'FILTER', '.price > 20',
|
||||
'FILTER-EF', '50', 'TRUTH', 'NOTHREAD'
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testAll('vSim', async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [1.1, 2.1, 3.1], 'element2');
|
||||
|
||||
const result = await client.vSim('key', 'element1');
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.ok(result.includes('element1'));
|
||||
}, {
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
});
|
||||
|
||||
testUtils.testWithClient('vSim with RESP3', async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [1.1, 2.1, 3.1], 'element2');
|
||||
await client.vAdd('resp3-key', [2.0, 3.0, 4.0], 'element3');
|
||||
|
||||
// Test similarity search with vector
|
||||
const resultWithVector = await client.vSim('resp3-key', [1.05, 2.05, 3.05]);
|
||||
assert.ok(Array.isArray(resultWithVector));
|
||||
assert.ok(resultWithVector.length > 0);
|
||||
|
||||
// Test similarity search with element
|
||||
const resultWithElement = await client.vSim('resp3-key', 'element1');
|
||||
assert.ok(Array.isArray(resultWithElement));
|
||||
assert.ok(resultWithElement.includes('element1'));
|
||||
|
||||
// Test with options
|
||||
const resultWithOptions = await client.vSim('resp3-key', 'element1', { COUNT: 2 });
|
||||
assert.ok(Array.isArray(resultWithOptions));
|
||||
assert.ok(resultWithOptions.length <= 2);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
});
|
||||
});
|
68
packages/client/lib/commands/VSIM.ts
Normal file
68
packages/client/lib/commands/VSIM.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { CommandParser } from '../client/parser';
|
||||
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '../RESP/types';
|
||||
import { transformDoubleArgument } from './generic-transformers';
|
||||
|
||||
export interface VSimOptions {
|
||||
COUNT?: number;
|
||||
EF?: number;
|
||||
FILTER?: string;
|
||||
'FILTER-EF'?: number;
|
||||
TRUTH?: boolean;
|
||||
NOTHREAD?: boolean;
|
||||
}
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: true,
|
||||
/**
|
||||
* Retrieve elements similar to a given vector or element with optional filtering
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param key - The key of the vector set
|
||||
* @param query - The query vector (array of numbers) or element name (string)
|
||||
* @param options - Optional parameters for similarity search
|
||||
* @see https://redis.io/commands/vsim/
|
||||
*/
|
||||
parseCommand(
|
||||
parser: CommandParser,
|
||||
key: RedisArgument,
|
||||
query: RedisArgument | Array<number>,
|
||||
options?: VSimOptions
|
||||
) {
|
||||
parser.push('VSIM');
|
||||
parser.pushKey(key);
|
||||
|
||||
if (Array.isArray(query)) {
|
||||
parser.push('VALUES', query.length.toString());
|
||||
for (const value of query) {
|
||||
parser.push(transformDoubleArgument(value));
|
||||
}
|
||||
} else {
|
||||
parser.push('ELE', query);
|
||||
}
|
||||
|
||||
if (options?.COUNT !== undefined) {
|
||||
parser.push('COUNT', options.COUNT.toString());
|
||||
}
|
||||
|
||||
if (options?.EF !== undefined) {
|
||||
parser.push('EF', options.EF.toString());
|
||||
}
|
||||
|
||||
if (options?.FILTER) {
|
||||
parser.push('FILTER', options.FILTER);
|
||||
}
|
||||
|
||||
if (options?.['FILTER-EF'] !== undefined) {
|
||||
parser.push('FILTER-EF', options['FILTER-EF'].toString());
|
||||
}
|
||||
|
||||
if (options?.TRUTH) {
|
||||
parser.push('TRUTH');
|
||||
}
|
||||
|
||||
if (options?.NOTHREAD) {
|
||||
parser.push('NOTHREAD');
|
||||
}
|
||||
},
|
||||
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
|
||||
} as const satisfies Command;
|
62
packages/client/lib/commands/VSIM_WITHSCORES.spec.ts
Normal file
62
packages/client/lib/commands/VSIM_WITHSCORES.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import VSIM_WITHSCORES from './VSIM_WITHSCORES';
|
||||
import { BasicCommandParser } from '../client/parser';
|
||||
|
||||
describe('VSIM WITHSCORES', () => {
|
||||
it('parseCommand', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
VSIM_WITHSCORES.parseCommand(parser, 'key', 'element')
|
||||
assert.deepEqual(parser.redisArgs, [
|
||||
'VSIM',
|
||||
'key',
|
||||
'ELE',
|
||||
'element',
|
||||
'WITHSCORES'
|
||||
]);
|
||||
});
|
||||
|
||||
testUtils.testAll(
|
||||
'vSimWithScores',
|
||||
async client => {
|
||||
await client.vAdd('key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('key', [1.1, 2.1, 3.1], 'element2');
|
||||
|
||||
const result = await client.vSimWithScores('key', 'element1');
|
||||
|
||||
assert.ok(typeof result === 'object');
|
||||
assert.ok('element1' in result);
|
||||
assert.ok('element2' in result);
|
||||
assert.equal(typeof result['element1'], 'number');
|
||||
assert.equal(typeof result['element2'], 'number');
|
||||
},
|
||||
{
|
||||
client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 0] },
|
||||
cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 0] }
|
||||
}
|
||||
);
|
||||
|
||||
testUtils.testWithClient(
|
||||
'vSimWithScores with RESP3 - returns Map with scores',
|
||||
async client => {
|
||||
await client.vAdd('resp3-key', [1.0, 2.0, 3.0], 'element1');
|
||||
await client.vAdd('resp3-key', [1.1, 2.1, 3.1], 'element2');
|
||||
await client.vAdd('resp3-key', [2.0, 3.0, 4.0], 'element3');
|
||||
|
||||
const result = await client.vSimWithScores('resp3-key', 'element1');
|
||||
|
||||
assert.ok(typeof result === 'object');
|
||||
assert.ok('element1' in result);
|
||||
assert.ok('element2' in result);
|
||||
assert.equal(typeof result['element1'], 'number');
|
||||
assert.equal(typeof result['element2'], 'number');
|
||||
},
|
||||
{
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
RESP: 3
|
||||
},
|
||||
minimumDockerVersion: [8, 0]
|
||||
}
|
||||
);
|
||||
});
|
36
packages/client/lib/commands/VSIM_WITHSCORES.ts
Normal file
36
packages/client/lib/commands/VSIM_WITHSCORES.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
ArrayReply,
|
||||
BlobStringReply,
|
||||
Command,
|
||||
DoubleReply,
|
||||
MapReply,
|
||||
UnwrapReply
|
||||
} from '../RESP/types';
|
||||
import { transformDoubleReply } from './generic-transformers';
|
||||
import VSIM from './VSIM';
|
||||
|
||||
export default {
|
||||
IS_READ_ONLY: VSIM.IS_READ_ONLY,
|
||||
/**
|
||||
* Retrieve elements similar to a given vector or element with similarity scores
|
||||
* @param args - Same parameters as the VSIM command
|
||||
* @see https://redis.io/commands/vsim/
|
||||
*/
|
||||
parseCommand(...args: Parameters<typeof VSIM.parseCommand>) {
|
||||
const parser = args[0];
|
||||
|
||||
VSIM.parseCommand(...args);
|
||||
parser.push('WITHSCORES');
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: ArrayReply<BlobStringReply>) => {
|
||||
const inferred = reply as unknown as UnwrapReply<typeof reply>;
|
||||
const members: Record<string, DoubleReply> = {};
|
||||
for (let i = 0; i < inferred.length; i += 2) {
|
||||
members[inferred[i].toString()] = transformDoubleReply[2](inferred[i + 1]);
|
||||
}
|
||||
return members;
|
||||
},
|
||||
3: undefined as unknown as () => MapReply<BlobStringReply, DoubleReply>
|
||||
}
|
||||
} as const satisfies Command;
|
@@ -662,3 +662,21 @@ export function transformStreamsMessagesReplyResp3(reply: UnwrapReply<StreamsMes
|
||||
return ret as unknown as MapReply<BlobStringReply, StreamMessagesReply>
|
||||
}
|
||||
}
|
||||
|
||||
export type RedisJSON = null | boolean | number | string | Date | Array<RedisJSON> | {
|
||||
[key: string]: RedisJSON;
|
||||
[key: number]: RedisJSON;
|
||||
};
|
||||
|
||||
export function transformRedisJsonArgument(json: RedisJSON): string {
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
|
||||
export function transformRedisJsonReply(json: BlobStringReply): RedisJSON {
|
||||
const res = JSON.parse((json as unknown as UnwrapReply<typeof json>).toString());
|
||||
return res;
|
||||
}
|
||||
|
||||
export function transformRedisJsonNullReply(json: NullReply | BlobStringReply): NullReply | RedisJSON {
|
||||
return isNullReply(json) ? json : transformRedisJsonReply(json);
|
||||
}
|
||||
|
@@ -344,6 +344,20 @@ import ZSCORE from './ZSCORE';
|
||||
import ZUNION_WITHSCORES from './ZUNION_WITHSCORES';
|
||||
import ZUNION from './ZUNION';
|
||||
import ZUNIONSTORE from './ZUNIONSTORE';
|
||||
import VADD from './VADD';
|
||||
import VCARD from './VCARD';
|
||||
import VDIM from './VDIM';
|
||||
import VEMB from './VEMB';
|
||||
import VEMB_RAW from './VEMB_RAW';
|
||||
import VGETATTR from './VGETATTR';
|
||||
import VINFO from './VINFO';
|
||||
import VLINKS from './VLINKS';
|
||||
import VLINKS_WITHSCORES from './VLINKS_WITHSCORES';
|
||||
import VRANDMEMBER from './VRANDMEMBER';
|
||||
import VREM from './VREM';
|
||||
import VSETATTR from './VSETATTR';
|
||||
import VSIM from './VSIM';
|
||||
import VSIM_WITHSCORES from './VSIM_WITHSCORES';
|
||||
|
||||
export default {
|
||||
ACL_CAT,
|
||||
@@ -1037,5 +1051,33 @@ export default {
|
||||
ZUNION,
|
||||
zUnion: ZUNION,
|
||||
ZUNIONSTORE,
|
||||
zUnionStore: ZUNIONSTORE
|
||||
zUnionStore: ZUNIONSTORE,
|
||||
VADD,
|
||||
vAdd: VADD,
|
||||
VCARD,
|
||||
vCard: VCARD,
|
||||
VDIM,
|
||||
vDim: VDIM,
|
||||
VEMB,
|
||||
vEmb: VEMB,
|
||||
VEMB_RAW,
|
||||
vEmbRaw: VEMB_RAW,
|
||||
VGETATTR,
|
||||
vGetAttr: VGETATTR,
|
||||
VINFO,
|
||||
vInfo: VINFO,
|
||||
VLINKS,
|
||||
vLinks: VLINKS,
|
||||
VLINKS_WITHSCORES,
|
||||
vLinksWithScores: VLINKS_WITHSCORES,
|
||||
VRANDMEMBER,
|
||||
vRandMember: VRANDMEMBER,
|
||||
VREM,
|
||||
vRem: VREM,
|
||||
VSETATTR,
|
||||
vSetAttr: VSETATTR,
|
||||
VSIM,
|
||||
vSim: VSIM,
|
||||
VSIM_WITHSCORES,
|
||||
vSimWithScores: VSIM_WITHSCORES
|
||||
} as const satisfies RedisCommands;
|
||||
|
Reference in New Issue
Block a user