1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

V5 bringing RESP3, Sentinel and TypeMapping to node-redis

RESP3 Support
   - Some commands responses in RESP3 aren't stable yet and therefore return an "untyped" ReplyUnion.
 
Sentinel

TypeMapping

Correctly types Multi commands

Note: some API changes to be further documented in v4-to-v5.md
This commit is contained in:
Shaya Potter
2024-10-15 17:46:52 +03:00
committed by GitHub
parent 2fc79bdfb3
commit b2d35c5286
1174 changed files with 45931 additions and 36274 deletions

View File

@@ -1,22 +1,21 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './ADD';
import ADD from './ADD';
describe('TOPK ADD', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', 'item'),
['TOPK.ADD', 'key', 'item']
);
});
describe('TOPK.ADD', () => {
it('transformArguments', () => {
assert.deepEqual(
ADD.transformArguments('key', 'item'),
['TOPK.ADD', 'key', 'item']
);
});
testUtils.testWithClient('client.topK.add', async client => {
await client.topK.reserve('topK', 3);
testUtils.testWithClient('client.topK.add', async client => {
const [, reply] = await Promise.all([
client.topK.reserve('topK', 3),
client.topK.add('topK', 'item')
]);
assert.deepEqual(
await client.topK.add('topK', 'item'),
[null]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepEqual(reply, [null]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,13 +1,11 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(
key: string,
items: string | Array<string>
): RedisCommandArguments {
return pushVerdictArguments(['TOPK.ADD', key], items);
}
export declare function transformReply(): Array<null | string>;
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(key: RedisArgument, items: RedisVariadicArgument) {
return pushVariadicArguments(['TOPK.ADD', key], items);
},
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
} as const satisfies Command;

View File

@@ -1,21 +1,21 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './COUNT';
import COUNT from './COUNT';
describe('TOPK COUNT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', 'item'),
['TOPK.COUNT', 'key', 'item']
);
});
describe('TOPK.COUNT', () => {
it('transformArguments', () => {
assert.deepEqual(
COUNT.transformArguments('key', 'item'),
['TOPK.COUNT', 'key', 'item']
);
});
testUtils.testWithClient('client.topK.count', async client => {
await client.topK.reserve('key', 3);
testUtils.testWithClient('client.topK.count', async client => {
const [, reply] = await Promise.all([
client.topK.reserve('key', 3),
client.topK.count('key', 'item')
]);
assert.deepEqual(
await client.topK.count('key', 'item'),
[0]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepEqual(reply, [0]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,15 +1,11 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
import { RedisArgument, ArrayReply, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(
key: string,
items: string | Array<string>
): RedisCommandArguments {
return pushVerdictArguments(['TOPK.COUNT', key], items);
}
export declare function transformReply(): Array<number>;
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments(key: RedisArgument, items: RedisVariadicArgument) {
return pushVariadicArguments(['TOPK.COUNT', key], items);
},
transformReply: undefined as unknown as () => ArrayReply<NumberReply>
} as const satisfies Command;

View File

@@ -1,42 +1,42 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './INCRBY';
import INCRBY from './INCRBY';
describe('TOPK INCRBY', () => {
describe('transformArguments', () => {
it('single item', () => {
assert.deepEqual(
transformArguments('key', {
item: 'item',
incrementBy: 1
}),
['TOPK.INCRBY', 'key', 'item', '1']
);
});
it('multiple items', () => {
assert.deepEqual(
transformArguments('key', [{
item: 'a',
incrementBy: 1
}, {
item: 'b',
incrementBy: 2
}]),
['TOPK.INCRBY', 'key', 'a', '1', 'b', '2']
);
});
describe('TOPK.INCRBY', () => {
describe('transformArguments', () => {
it('single item', () => {
assert.deepEqual(
INCRBY.transformArguments('key', {
item: 'item',
incrementBy: 1
}),
['TOPK.INCRBY', 'key', 'item', '1']
);
});
testUtils.testWithClient('client.topK.incrby', async client => {
await client.topK.reserve('key', 5);
it('multiple items', () => {
assert.deepEqual(
INCRBY.transformArguments('key', [{
item: 'a',
incrementBy: 1
}, {
item: 'b',
incrementBy: 2
}]),
['TOPK.INCRBY', 'key', 'a', '1', 'b', '2']
);
});
});
assert.deepEqual(
await client.topK.incrBy('key', {
item: 'item',
incrementBy: 1
}),
[null]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('client.topK.incrby', async client => {
const [, reply] = await Promise.all([
client.topK.reserve('key', 5),
client.topK.incrBy('key', {
item: 'item',
incrementBy: 1
})
]);
assert.deepEqual(reply, [null]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,29 +1,32 @@
export const FIRST_KEY_INDEX = 1;
import { RedisArgument, ArrayReply, SimpleStringReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types';
interface IncrByItem {
item: string;
incrementBy: number;
export interface TopKIncrByItem {
item: string;
incrementBy: number;
}
export function transformArguments(
key: string,
items: IncrByItem | Array<IncrByItem>
): Array<string> {
function pushIncrByItem(args: Array<RedisArgument>, { item, incrementBy }: TopKIncrByItem) {
args.push(item, incrementBy.toString());
}
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(
key: RedisArgument,
items: TopKIncrByItem | Array<TopKIncrByItem>
) {
const args = ['TOPK.INCRBY', key];
if (Array.isArray(items)) {
for (const item of items) {
pushIncrByItem(args, item);
}
for (const item of items) {
pushIncrByItem(args, item);
}
} else {
pushIncrByItem(args, items);
pushIncrByItem(args, items);
}
return args;
}
function pushIncrByItem(args: Array<string>, { item, incrementBy }: IncrByItem): void {
args.push(item, incrementBy.toString());
}
export declare function transformReply(): Array<string | null>;
},
transformReply: undefined as unknown as () => ArrayReply<SimpleStringReply | NullReply>
} as const satisfies Command;

View File

@@ -1,23 +1,26 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './INFO';
import INFO from './INFO';
describe('TOPK INFO', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
['TOPK.INFO', 'key']
);
});
it('transformArguments', () => {
assert.deepEqual(
INFO.transformArguments('key'),
['TOPK.INFO', 'key']
);
});
testUtils.testWithClient('client.topK.info', async client => {
await client.topK.reserve('key', 3);
testUtils.testWithClient('client.topK.info', async client => {
const k = 3,
[, reply] = await Promise.all([
client.topK.reserve('key', 3),
client.topK.info('key')
]);
const info = await client.topK.info('key');
assert.equal(typeof info, 'object');
assert.equal(info.k, 3);
assert.equal(typeof info.width, 'number');
assert.equal(typeof info.depth, 'number');
assert.equal(typeof info.decay, 'number');
}, GLOBAL.SERVERS.OPEN);
assert.equal(typeof reply, 'object');
assert.equal(reply.k, k);
assert.equal(typeof reply.width, 'number');
assert.equal(typeof reply.depth, 'number');
assert.equal(typeof reply.decay, 'number');
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,34 +1,26 @@
export const FIRST_KEY_INDEX = 1;
import { transformDoubleReply } from '@redis/client/dist/lib/commands/generic-transformers';
import { RedisArgument, TuplesToMapReply, NumberReply, DoubleReply, UnwrapReply, Resp2Reply, Command, SimpleStringReply, TypeMapping } from '@redis/client/dist/lib/RESP/types';
import { transformInfoV2Reply } from '../bloom';
export const IS_READ_ONLY = true;
export type TopKInfoReplyMap = TuplesToMapReply<[
[SimpleStringReply<'k'>, NumberReply],
[SimpleStringReply<'width'>, NumberReply],
[SimpleStringReply<'depth'>, NumberReply],
[SimpleStringReply<'decay'>, DoubleReply]
]>;
export function transformArguments(key: string): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments(key: RedisArgument) {
return ['TOPK.INFO', key];
}
},
transformReply: {
2: (reply: UnwrapReply<Resp2Reply<TopKInfoReplyMap>>, preserve?: any, typeMapping?: TypeMapping): TopKInfoReplyMap => {
reply[7] = transformDoubleReply[2](reply[7], preserve, typeMapping) as any;
export type InfoRawReply = [
_: string,
k: number,
_: string,
width: number,
_: string,
depth: number,
_: string,
decay: string
];
export interface InfoReply {
k: number,
width: number;
depth: number;
decay: number;
}
export function transformReply(reply: InfoRawReply): InfoReply {
return {
k: reply[1],
width: reply[3],
depth: reply[5],
decay: Number(reply[7])
};
}
return transformInfoV2Reply<TopKInfoReplyMap>(reply, typeMapping);
},
3: undefined as unknown as () => TopKInfoReplyMap
}
} as const satisfies Command

View File

@@ -1,21 +1,21 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './LIST';
import LIST from './LIST';
describe('TOPK LIST', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
['TOPK.LIST', 'key']
);
});
describe('TOPK.LIST', () => {
it('transformArguments', () => {
assert.deepEqual(
LIST.transformArguments('key'),
['TOPK.LIST', 'key']
);
});
testUtils.testWithClient('client.topK.list', async client => {
await client.topK.reserve('key', 3);
testUtils.testWithClient('client.topK.list', async client => {
const [, reply] = await Promise.all([
client.topK.reserve('key', 3),
client.topK.list('key')
]);
assert.deepEqual(
await client.topK.list('key'),
[]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepEqual(reply, []);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,9 +1,10 @@
export const FIRST_KEY_INDEX = 1;
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
export const IS_READ_ONLY = true;
export function transformArguments(key: string): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments(key: RedisArgument) {
return ['TOPK.LIST', key];
}
export declare function transformReply(): Array<string | null>;
},
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
} as const satisfies Command;

View File

@@ -1,30 +1,27 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './LIST_WITHCOUNT';
import LIST_WITHCOUNT from './LIST_WITHCOUNT';
describe('TOPK LIST WITHCOUNT', () => {
testUtils.isVersionGreaterThanHook([2, 2, 9]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
['TOPK.LIST', 'key', 'WITHCOUNT']
);
});
describe('TOPK.LIST WITHCOUNT', () => {
testUtils.isVersionGreaterThanHook([2, 2, 9]);
testUtils.testWithClient('client.topK.listWithCount', async client => {
const [,, list] = await Promise.all([
client.topK.reserve('key', 3),
client.topK.add('key', 'item'),
client.topK.listWithCount('key')
]);
it('transformArguments', () => {
assert.deepEqual(
LIST_WITHCOUNT.transformArguments('key'),
['TOPK.LIST', 'key', 'WITHCOUNT']
);
});
assert.deepEqual(
list,
[{
item: 'item',
count: 1
}]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('client.topK.listWithCount', async client => {
const [, , list] = await Promise.all([
client.topK.reserve('key', 3),
client.topK.add('key', 'item'),
client.topK.listWithCount('key')
]);
assert.deepEqual(list, [{
item: 'item',
count: 1
}]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,26 +1,24 @@
export const FIRST_KEY_INDEX = 1;
import { RedisArgument, ArrayReply, BlobStringReply, NumberReply, UnwrapReply, Command } from '@redis/client/dist/lib/RESP/types';
export const IS_READ_ONLY = true;
export function transformArguments(key: string): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments(key: RedisArgument) {
return ['TOPK.LIST', key, 'WITHCOUNT'];
}
type ListWithCountRawReply = Array<string | number>;
type ListWithCountReply = Array<{
item: string,
count: number
}>;
export function transformReply(rawReply: ListWithCountRawReply): ListWithCountReply {
const reply: ListWithCountReply = [];
},
transformReply(rawReply: UnwrapReply<ArrayReply<BlobStringReply | NumberReply>>) {
const reply: Array<{
item: BlobStringReply;
count: NumberReply;
}> = [];
for (let i = 0; i < rawReply.length; i++) {
reply.push({
item: rawReply[i] as string,
count: rawReply[++i] as number
});
reply.push({
item: rawReply[i] as BlobStringReply,
count: rawReply[++i] as NumberReply
});
}
return reply;
}
}
} as const satisfies Command;

View File

@@ -1,21 +1,21 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './QUERY';
import QUERY from './QUERY';
describe('TOPK QUERY', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', 'item'),
['TOPK.QUERY', 'key', 'item']
);
});
describe('TOPK.QUERY', () => {
it('transformArguments', () => {
assert.deepEqual(
QUERY.transformArguments('key', 'item'),
['TOPK.QUERY', 'key', 'item']
);
});
testUtils.testWithClient('client.cms.query', async client => {
await client.topK.reserve('key', 3);
testUtils.testWithClient('client.topK.query', async client => {
const [, reply] = await Promise.all([
client.topK.reserve('key', 3),
client.topK.query('key', 'item')
]);
assert.deepEqual(
await client.topK.query('key', 'item'),
[0]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepEqual(reply, [false]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,15 +1,11 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
import { RedisArgument, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument, pushVariadicArguments, transformBooleanArrayReply } from '@redis/client/dist/lib/commands/generic-transformers';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(
key: string,
items: string | Array<string>
): RedisCommandArguments {
return pushVerdictArguments(['TOPK.QUERY', key], items);
}
export declare function transformReply(): Array<number>;
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: false,
transformArguments(key: RedisArgument, items: RedisVariadicArgument) {
return pushVariadicArguments(['TOPK.QUERY', key], items);
},
transformReply: transformBooleanArrayReply
} as const satisfies Command;

View File

@@ -1,32 +1,32 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../../test-utils';
import { transformArguments } from './RESERVE';
import RESERVE from './RESERVE';
describe('TOPK RESERVE', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments('topK', 3),
['TOPK.RESERVE', 'topK', '3']
);
});
it('with options', () => {
assert.deepEqual(
transformArguments('topK', 3, {
width: 8,
depth: 7,
decay: 0.9
}),
['TOPK.RESERVE', 'topK', '3', '8', '7', '0.9']
);
});
describe('TOPK.RESERVE', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
RESERVE.transformArguments('topK', 3),
['TOPK.RESERVE', 'topK', '3']
);
});
testUtils.testWithClient('client.topK.reserve', async client => {
assert.equal(
await client.topK.reserve('topK', 3),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
it('with options', () => {
assert.deepEqual(
RESERVE.transformArguments('topK', 3, {
width: 8,
depth: 7,
decay: 0.9
}),
['TOPK.RESERVE', 'topK', '3', '8', '7', '0.9']
);
});
});
testUtils.testWithClient('client.topK.reserve', async client => {
assert.equal(
await client.topK.reserve('topK', 3),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,29 +1,26 @@
export const FIRST_KEY_INDEX = 1;
import { SimpleStringReply, Command, RedisArgument } from '@redis/client/dist/lib/RESP/types';
export const IS_READ_ONLY = true;
interface ReserveOptions {
width: number;
depth: number;
decay: number;
export interface TopKReserveOptions {
width: number;
depth: number;
decay: number;
}
export function transformArguments(
key: string,
topK: number,
options?: ReserveOptions
): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(key: RedisArgument, topK: number, options?: TopKReserveOptions) {
const args = ['TOPK.RESERVE', key, topK.toString()];
if (options) {
args.push(
options.width.toString(),
options.depth.toString(),
options.decay.toString()
);
args.push(
options.width.toString(),
options.depth.toString(),
options.decay.toString()
);
}
return args;
}
export declare function transformReply(): 'OK';
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,27 +1,28 @@
import * as ADD from './ADD';
import * as COUNT from './COUNT';
import * as INCRBY from './INCRBY';
import * as INFO from './INFO';
import * as LIST_WITHCOUNT from './LIST_WITHCOUNT';
import * as LIST from './LIST';
import * as QUERY from './QUERY';
import * as RESERVE from './RESERVE';
import type { RedisCommands } from '@redis/client/dist/lib/RESP/types';
import ADD from './ADD';
import COUNT from './COUNT';
import INCRBY from './INCRBY';
import INFO from './INFO';
import LIST_WITHCOUNT from './LIST_WITHCOUNT';
import LIST from './LIST';
import QUERY from './QUERY';
import RESERVE from './RESERVE';
export default {
ADD,
add: ADD,
COUNT,
count: COUNT,
INCRBY,
incrBy: INCRBY,
INFO,
info: INFO,
LIST_WITHCOUNT,
listWithCount: LIST_WITHCOUNT,
LIST,
list: LIST,
QUERY,
query: QUERY,
RESERVE,
reserve: RESERVE
};
ADD,
add: ADD,
COUNT,
count: COUNT,
INCRBY,
incrBy: INCRBY,
INFO,
info: INFO,
LIST_WITHCOUNT,
listWithCount: LIST_WITHCOUNT,
LIST,
list: LIST,
QUERY,
query: QUERY,
RESERVE,
reserve: RESERVE
} as const satisfies RedisCommands;