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

convert "resp types" to interfaces to allow circular references

This commit is contained in:
Leibale
2023-07-13 13:42:59 -04:00
parent 54c3a66c72
commit a3e813d3ac
41 changed files with 522 additions and 428 deletions

View File

@@ -1,3 +1,4 @@
import { BlobError, SimpleError } from '../errors';
import { RedisScriptConfig, SHA1 } from '../lua-script';
import { RESP_TYPES } from './decoder';
import { VerbatimString } from './verbatim-string';
@@ -6,120 +7,138 @@ export type RESP_TYPES = typeof RESP_TYPES;
export type RespTypes = RESP_TYPES[keyof RESP_TYPES];
export type RespType<
// using interface(s) to allow circular references
// type X = BlobStringReply | ArrayReply<X>;
export interface RespType<
RESP_TYPE extends RespTypes,
DEFAULT,
TYPES = never,
FLAG_TYPES = DEFAULT | TYPES
> = (DEFAULT | TYPES) & {
TYPE_MAPPING = DEFAULT | TYPES
> {
RESP_TYPE: RESP_TYPE;
DEFAULT: DEFAULT;
TYPES: TYPES;
FLAG: Flag<FLAG_TYPES>;
};
TYPE_MAPPING: MappedType<TYPE_MAPPING>;
}
export type NullReply = RespType<
export interface NullReply extends RespType<
RESP_TYPES['NULL'],
null
>;
export type BooleanReply<
> {}
export interface BooleanReply<
T extends boolean = boolean
> = RespType<
> extends RespType<
RESP_TYPES['BOOLEAN'],
T
>;
export type NumberReply<
> {}
export interface NumberReply<
T extends number = number
> = RespType<
> extends RespType<
RESP_TYPES['NUMBER'],
T,
`${T}`,
number | string
>;
export type BigNumberReply<
> {}
export interface BigNumberReply<
T extends bigint = bigint
> = RespType<
> extends RespType<
RESP_TYPES['BIG_NUMBER'],
T,
number | `${T}`,
bigint | number | string
>;
export type DoubleReply<
> {}
export interface DoubleReply<
T extends number = number
> = RespType<
> extends RespType<
RESP_TYPES['DOUBLE'],
T,
`${T}`,
number | string
>;
export type SimpleStringReply<
> {}
export interface SimpleStringReply<
T extends string = string
> = RespType<
> extends RespType<
RESP_TYPES['SIMPLE_STRING'],
T,
Buffer,
string | Buffer
>;
export type BlobStringReply<
> {}
export interface BlobStringReply<
T extends string = string
> = RespType<
> extends RespType<
RESP_TYPES['BLOB_STRING'],
T,
Buffer,
string | Buffer
>;
export type VerbatimStringReply<
> {}
export interface VerbatimStringReply<
T extends string = string
> = RespType<
> extends RespType<
RESP_TYPES['VERBATIM_STRING'],
T,
Buffer | VerbatimString,
string | Buffer | VerbatimString
>;
export type SimpleErrorReply = RespType<
> {}
export interface SimpleErrorReply extends RespType<
RESP_TYPES['SIMPLE_ERROR'],
SimpleError,
Buffer
>;
export type BlobErrorReply = RespType<
> {}
export interface BlobErrorReply extends RespType<
RESP_TYPES['BLOB_ERROR'],
BlobError,
Buffer
>;
export type ArrayReply<T> = RespType<
> {}
export interface ArrayReply<T> extends RespType<
RESP_TYPES['ARRAY'],
Array<T>,
never,
Array<any>
>;
export type TuplesReply<T extends [...Array<unknown>]> = RespType<
> {}
export interface TuplesReply<T extends [...Array<unknown>]> extends RespType<
RESP_TYPES['ARRAY'],
T,
never,
Array<any>
>;
export type SetReply<T> = RespType<
> {}
export interface SetReply<T> extends RespType<
RESP_TYPES['SET'],
Array<T>,
Set<T>,
Array<any> | Set<any>
>;
export type MapReply<K, V> = RespType<
> {}
export interface MapReply<K, V> extends RespType<
RESP_TYPES['MAP'],
{ [key: string]: V },
Map<K, V> | Array<K | V>,
Map<any, any> | Array<any>
>;
> {}
type MapKeyValue = [key: BlobStringReply, value: unknown];
type MapTuples = Array<MapKeyValue>;
export type TuplesToMapReply<T extends MapTuples> = RespType<
export interface TuplesToMapReply<T extends MapTuples> extends RespType<
RESP_TYPES['MAP'],
{
[P in T[number] as P[0] extends BlobStringReply<infer S> ? S : never]: P[1];
},
Map<T[number][0], T[number][1]> | FlattenTuples<T>
>;
> {}
type FlattenTuples<T> = (
T extends [] ? [] :
@@ -131,31 +150,28 @@ type FlattenTuples<T> = (
never
);
export type ReplyUnion = NullReply | BooleanReply | NumberReply | BigNumberReply | DoubleReply | SimpleStringReply | BlobStringReply | VerbatimStringReply | SimpleErrorReply | BlobErrorReply |
// cannot reuse ArrayReply, SetReply and MapReply because of circular reference
RespType<
RESP_TYPES['ARRAY'],
Array<ReplyUnion>
> |
RespType<
RESP_TYPES['SET'],
Array<ReplyUnion>,
Set<ReplyUnion>
> |
RespType<
RESP_TYPES['MAP'],
{ [key: string]: ReplyUnion },
Map<ReplyUnion, ReplyUnion> | Array<ReplyUnion | ReplyUnion>
>;
export type ReplyUnion = (
NullReply |
BooleanReply |
NumberReply |
BigNumberReply |
DoubleReply |
SimpleStringReply |
BlobStringReply |
VerbatimStringReply |
SimpleErrorReply |
BlobErrorReply |
ArrayReply<ReplyUnion> |
SetReply<ReplyUnion> |
MapReply<ReplyUnion, ReplyUnion>
);
export type Reply = ReplyWithTypeMapping<ReplyUnion, {}>;
export type MappedType<T> = ((...args: any) => T) | (new (...args: any) => T);
export type Flag<T> = ((...args: any) => T) | (new (...args: any) => T);
type RespTypeUnion<T> = T extends RespType<RespTypes, unknown, unknown, infer FLAG_TYPES> ? FLAG_TYPES : never;
type InferTypeMapping<T> = T extends RespType<RespTypes, unknown, unknown, infer FLAG_TYPES> ? FLAG_TYPES : never;
export type TypeMapping = {
[P in RespTypes]?: Flag<RespTypeUnion<Extract<ReplyUnion, RespType<P, any, any, any>>>>;
[P in RespTypes]?: MappedType<InferTypeMapping<Extract<ReplyUnion, RespType<P, any, any, any>>>>;
};
type MapKey<
@@ -167,13 +183,15 @@ type MapKey<
[RESP_TYPES.BLOB_STRING]: StringConstructor;
}>;
export type UnwrapReply<REPLY extends RespType<any, any, any, any>> = REPLY['DEFAULT' | 'TYPES'];
export type ReplyWithTypeMapping<
REPLY,
TYPE_MAPPING extends TypeMapping
> = (
// if REPLY is a type, extract the coresponding type from TYPE_MAPPING or use the default type
REPLY extends RespType<infer RESP_TYPE, infer DEFAULT, infer TYPES, unknown> ?
TYPE_MAPPING[RESP_TYPE] extends Flag<infer T> ?
TYPE_MAPPING[RESP_TYPE] extends MappedType<infer T> ?
ReplyWithTypeMapping<Extract<DEFAULT | TYPES, T>, TYPE_MAPPING> :
ReplyWithTypeMapping<DEFAULT, TYPE_MAPPING>
: (
@@ -193,6 +211,11 @@ export type ReplyWithTypeMapping<
)
);
type a = ReplyWithTypeMapping<
ArrayReply<TuplesReply<[BlobStringReply | NullReply]>>,
{}
>;
export type TransformReply = (this: void, reply: any, preserve?: any) => any; // TODO;
export type RedisArgument = string | Buffer;
@@ -323,7 +346,7 @@ export type CommandReply<
// if transformReply[RESP] is a function, use its return type
COMMAND['transformReply'] extends Record<RESP, (...args: any) => infer T> ? T :
// otherwise use the generic reply type
Reply
ReplyUnion
);
export type CommandSignature<

View File

@@ -425,7 +425,6 @@ export default class RedisClient<
} catch (err) {
this._queue.decoder.reset();
this.emit('error', err);
}
})
.on('error', err => {

View File

@@ -1,4 +1,4 @@
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, Resp2Reply, Command } from '../RESP/types';
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
type AclUser = TuplesToMapReply<[
[BlobStringReply<'flags'>, ArrayReply<BlobStringReply>],
@@ -23,17 +23,20 @@ export default {
return ['ACL', 'GETUSER', username];
},
transformReply: {
2: (reply: Resp2Reply<AclUser>) => ({
2: (reply: UnwrapReply<Resp2Reply<AclUser>>) => ({
flags: reply[1],
passwords: reply[3],
commands: reply[5],
keys: reply[7],
channels: reply[9],
selectors: reply[11]?.map(selector => ({
commands: selector[1],
keys: selector[3],
channels: selector[5]
}))
selectors: (reply[11] as unknown as UnwrapReply<typeof reply[11]>)?.map(selector => {
const inferred = selector as unknown as UnwrapReply<typeof selector>;
return {
commands: inferred[1],
keys: inferred[3],
channels: inferred[5]
};
})
}),
3: undefined as unknown as () => AclUser
}

View File

@@ -1,5 +1,4 @@
import { DoubleReply, Resp2Reply } from '../RESP/types';
import { ArrayReply, BlobStringReply, Command, NumberReply, TuplesToMapReply } from '../RESP/types';
import { ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, DoubleReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
export type AclLogReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'count'>, NumberReply],
@@ -30,18 +29,23 @@ export default {
return args;
},
transformReply: {
2: (reply: Resp2Reply<AclLogReply>) => reply.map(item => ({
count: item[1],
reason: item[3],
context: item[5],
object: item[7],
username: item[9],
'age-seconds': Number(item[11]),
'client-info': item[13],
'entry-id': item[15],
'timestamp-created': item[17],
'timestamp-last-updated': item[19]
})),
2: (reply: UnwrapReply<Resp2Reply<AclLogReply>>) => {
return reply.map(item => {
const inferred = item as unknown as UnwrapReply<typeof item>;
return {
count: inferred[1],
reason: inferred[3],
context: inferred[5],
object: inferred[7],
username: inferred[9],
'age-seconds': Number(inferred[11]),
'client-info': inferred[13],
'entry-id': inferred[15],
'timestamp-created': inferred[17],
'timestamp-last-updated': inferred[19]
};
})
},
3: undefined as unknown as () => AclLogReply
}
} as const satisfies Command;

View File

@@ -34,6 +34,12 @@ describe('BITFIELD', () => {
});
testUtils.testAll('bitField', async client => {
const a = client.bitField('key', [{
operation: 'GET',
encoding: 'i8',
offset: 0
}]);
assert.deepEqual(
await client.bitField('key', [{
operation: 'GET',

View File

@@ -1,4 +1,4 @@
import { BlobStringReply, NullReply, Command } from '../RESP/types';
import { UnwrapReply, NullReply, TuplesReply, BlobStringReply, Command } from '../RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export default {
@@ -12,7 +12,7 @@ export default {
args.push(timeout.toString());
return args;
},
transformReply(reply: NullReply | [BlobStringReply, BlobStringReply]) {
transformReply(reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, BlobStringReply]>>) {
if (reply === null) return null;
return {

View File

@@ -1,4 +1,4 @@
import { RedisArgument, Command, NullReply, TuplesReply, BlobStringReply, DoubleReply } from '../RESP/types';
import { RedisArgument, NullReply, TuplesReply, BlobStringReply, DoubleReply, UnwrapReply, Command } from '../RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export function transformBZPopArguments(
@@ -20,14 +20,14 @@ export default {
return transformBZPopArguments('BZPOPMAX', ...args);
},
transformReply: {
2: (reply: NullReply | TuplesReply<[BlobStringReply, BlobStringReply, BlobStringReply]>) => {
2(reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, BlobStringReply, BlobStringReply]>>) {
return reply === null ? null : {
key: reply[0],
value: reply[1],
score: Number(reply[2])
};
},
3: (reply: NullReply | TuplesReply<[BlobStringReply, BlobStringReply, DoubleReply]>) => {
3(reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, BlobStringReply, DoubleReply]>>) {
return reply === null ? null : {
key: reply[0],
value: reply[1],

View File

@@ -1,50 +1,50 @@
import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLIENT_INFO';
import CLIENT_INFO from './CLIENT_INFO';
import testUtils, { GLOBAL } from '../test-utils';
describe('CLIENT INFO', () => {
testUtils.isVersionGreaterThanHook([6, 2]);
testUtils.isVersionGreaterThanHook([6, 2]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'INFO']
);
});
it('transformArguments', () => {
assert.deepEqual(
CLIENT_INFO.transformArguments(),
['CLIENT', 'INFO']
);
});
testUtils.testWithClient('client.clientInfo', async client => {
const reply = await client.clientInfo();
assert.equal(typeof reply.id, 'number');
assert.equal(typeof reply.addr, 'string');
assert.equal(typeof reply.laddr, 'string');
assert.equal(typeof reply.fd, 'number');
assert.equal(typeof reply.name, 'string');
assert.equal(typeof reply.age, 'number');
assert.equal(typeof reply.idle, 'number');
assert.equal(typeof reply.flags, 'string');
assert.equal(typeof reply.db, 'number');
assert.equal(typeof reply.sub, 'number');
assert.equal(typeof reply.psub, 'number');
assert.equal(typeof reply.multi, 'number');
assert.equal(typeof reply.qbuf, 'number');
assert.equal(typeof reply.qbufFree, 'number');
assert.equal(typeof reply.argvMem, 'number');
assert.equal(typeof reply.obl, 'number');
assert.equal(typeof reply.oll, 'number');
assert.equal(typeof reply.omem, 'number');
assert.equal(typeof reply.totMem, 'number');
assert.equal(typeof reply.events, 'string');
assert.equal(typeof reply.cmd, 'string');
assert.equal(typeof reply.user, 'string');
assert.equal(typeof reply.redir, 'number');
testUtils.testWithClient('client.clientInfo', async client => {
const reply = await client.clientInfo();
assert.equal(typeof reply.id, 'number');
assert.equal(typeof reply.addr, 'string');
assert.equal(typeof reply.laddr, 'string');
assert.equal(typeof reply.fd, 'number');
assert.equal(typeof reply.name, 'string');
assert.equal(typeof reply.age, 'number');
assert.equal(typeof reply.idle, 'number');
assert.equal(typeof reply.flags, 'string');
assert.equal(typeof reply.db, 'number');
assert.equal(typeof reply.sub, 'number');
assert.equal(typeof reply.psub, 'number');
assert.equal(typeof reply.multi, 'number');
assert.equal(typeof reply.qbuf, 'number');
assert.equal(typeof reply.qbufFree, 'number');
assert.equal(typeof reply.argvMem, 'number');
assert.equal(typeof reply.obl, 'number');
assert.equal(typeof reply.oll, 'number');
assert.equal(typeof reply.omem, 'number');
assert.equal(typeof reply.totMem, 'number');
assert.equal(typeof reply.events, 'string');
assert.equal(typeof reply.cmd, 'string');
assert.equal(typeof reply.user, 'string');
assert.equal(typeof reply.redir, 'number');
if (testUtils.isVersionGreaterThan([7, 0])) {
assert.equal(typeof reply.multiMem, 'number');
assert.equal(typeof reply.resp, 'number');
}
if (testUtils.isVersionGreaterThan([7, 0])) {
assert.equal(typeof reply.multiMem, 'number');
assert.equal(typeof reply.resp, 'number');
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
assert.equal(typeof reply.ssub, 'number');
}
}, GLOBAL.SERVERS.OPEN);
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
assert.equal(typeof reply.ssub, 'number');
}
}
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,78 +1,77 @@
import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLIENT_LIST';
import CLIENT_LIST from './CLIENT_LIST';
import testUtils, { GLOBAL } from '../test-utils';
describe('CLIENT LIST', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'LIST']
);
});
it('with TYPE', () => {
assert.deepEqual(
transformArguments({
TYPE: 'NORMAL'
}),
['CLIENT', 'LIST', 'TYPE', 'NORMAL']
);
});
it('with ID', () => {
assert.deepEqual(
transformArguments({
ID: ['1', '2']
}),
['CLIENT', 'LIST', 'ID', '1', '2']
);
});
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
CLIENT_LIST.transformArguments(),
['CLIENT', 'LIST']
);
});
testUtils.testWithClient('client.clientList', async client => {
const reply = await client.clientList();
assert.ok(Array.isArray(reply));
it('with TYPE', () => {
assert.deepEqual(
CLIENT_LIST.transformArguments({
TYPE: 'NORMAL'
}),
['CLIENT', 'LIST', 'TYPE', 'NORMAL']
);
});
for (const item of reply) {
assert.equal(typeof item.id, 'number');
assert.equal(typeof item.addr, 'string');
assert.equal(typeof item.fd, 'number');
assert.equal(typeof item.name, 'string');
assert.equal(typeof item.age, 'number');
assert.equal(typeof item.idle, 'number');
assert.equal(typeof item.flags, 'string');
assert.equal(typeof item.db, 'number');
assert.equal(typeof item.sub, 'number');
assert.equal(typeof item.psub, 'number');
assert.equal(typeof item.multi, 'number');
assert.equal(typeof item.qbuf, 'number');
assert.equal(typeof item.qbufFree, 'number');
assert.equal(typeof item.obl, 'number');
assert.equal(typeof item.oll, 'number');
assert.equal(typeof item.omem, 'number');
assert.equal(typeof item.events, 'string');
assert.equal(typeof item.cmd, 'string');
it('with ID', () => {
assert.deepEqual(
CLIENT_LIST.transformArguments({
ID: ['1', '2']
}),
['CLIENT', 'LIST', 'ID', '1', '2']
);
});
});
if (testUtils.isVersionGreaterThan([6, 0])) {
assert.equal(typeof item.argvMem, 'number');
assert.equal(typeof item.totMem, 'number');
assert.equal(typeof item.user, 'string');
}
testUtils.testWithClient('client.clientList', async client => {
const reply = await client.clientList();
assert.ok(Array.isArray(reply));
for (const item of reply) {
assert.equal(typeof item.id, 'number');
assert.equal(typeof item.addr, 'string');
assert.equal(typeof item.fd, 'number');
assert.equal(typeof item.name, 'string');
assert.equal(typeof item.age, 'number');
assert.equal(typeof item.idle, 'number');
assert.equal(typeof item.flags, 'string');
assert.equal(typeof item.db, 'number');
assert.equal(typeof item.sub, 'number');
assert.equal(typeof item.psub, 'number');
assert.equal(typeof item.multi, 'number');
assert.equal(typeof item.qbuf, 'number');
assert.equal(typeof item.qbufFree, 'number');
assert.equal(typeof item.obl, 'number');
assert.equal(typeof item.oll, 'number');
assert.equal(typeof item.omem, 'number');
assert.equal(typeof item.events, 'string');
assert.equal(typeof item.cmd, 'string');
if (testUtils.isVersionGreaterThan([6, 2])) {
assert.equal(typeof item.redir, 'number');
assert.equal(typeof item.laddr, 'string');
}
if (testUtils.isVersionGreaterThan([6, 0])) {
assert.equal(typeof item.argvMem, 'number');
assert.equal(typeof item.totMem, 'number');
assert.equal(typeof item.user, 'string');
if (testUtils.isVersionGreaterThan([7, 0])) {
assert.equal(typeof item.multiMem, 'number');
assert.equal(typeof item.resp, 'number');
}
if (testUtils.isVersionGreaterThan([6, 2])) {
assert.equal(typeof item.redir, 'number');
assert.equal(typeof item.laddr, 'string');
if (testUtils.isVersionGreaterThan([7, 0])) {
assert.equal(typeof item.multiMem, 'number');
assert.equal(typeof item.resp, 'number');
if (testUtils.isVersionGreaterThan([7, 0, 3])) {
assert.equal(typeof item.ssub, 'number');
assert.equal(typeof item.ssub, 'number');
}
}
}
}, GLOBAL.SERVERS.OPEN);
}
}
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -36,7 +36,7 @@ export default {
length = split.length - 1,
reply: Array<ClientInfoReply> = [];
for (let i = 0; i < length; i++) {
reply.push(CLIENT_INFO.transformReply(split[i] as VerbatimStringReply));
reply.push(CLIENT_INFO.transformReply(split[i] as unknown as VerbatimStringReply));
}
return reply;

View File

@@ -1,4 +1,4 @@
import { TuplesToMapReply, BlobStringReply, SetReply, NumberReply, ArrayReply, Resp2Reply, Command } from '../RESP/types';
import { TuplesToMapReply, BlobStringReply, SetReply, NumberReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
type TrackingInfo = TuplesToMapReply<[
[BlobStringReply<'flags'>, SetReply<BlobStringReply>],
@@ -13,7 +13,7 @@ export default {
return ['CLIENT', 'TRACKINGINFO'];
},
transformReply: {
2: (reply: Resp2Reply<TrackingInfo>) => ({
2: (reply: UnwrapReply<Resp2Reply<TrackingInfo>>) => ({
flags: reply[1],
redirect: reply[3],
prefixes: reply[5]

View File

@@ -1,4 +1,4 @@
import { ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, Resp2Reply, Command } from '../RESP/types';
import { ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
type ClusterLinksReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'direction'>, BlobStringReply],
@@ -16,14 +16,17 @@ export default {
return ['CLUSTER', 'LINKS'];
},
transformReply: {
2: (reply: Resp2Reply<ClusterLinksReply>) => reply.map(link => ({
direction: link[1],
node: link[3],
'create-time': link[5],
events: link[7],
'send-buffer-allocated': link[9],
'send-buffer-used': link[11]
})),
2: (reply: UnwrapReply<Resp2Reply<ClusterLinksReply>>) => reply.map(link => {
const unwrapped = link as unknown as UnwrapReply<typeof link>;
return {
direction: unwrapped[1],
node: unwrapped[3],
'create-time': unwrapped[5],
events: unwrapped[7],
'send-buffer-allocated': unwrapped[9],
'send-buffer-used': unwrapped[11]
};
}),
3: undefined as unknown as () => ClusterLinksReply
}
} as const satisfies Command;

View File

@@ -1,10 +1,10 @@
import { NumberReply, ArrayReply, BlobStringReply, Command } from '../RESP/types';
import { TuplesReply, BlobStringReply, NumberReply, ArrayReply, UnwrapReply, Command } from '../RESP/types';
type RawNode = [
type RawNode = TuplesReply<[
host: BlobStringReply,
port: NumberReply,
id: BlobStringReply
];
]>;
type ClusterSlotsRawReply = ArrayReply<[
from: NumberReply,
@@ -21,7 +21,7 @@ export default {
transformArguments() {
return ['CLUSTER', 'SLOTS'];
},
transformReply(reply: ClusterSlotsRawReply) {
transformReply(reply: UnwrapReply<ClusterSlotsRawReply>) {
return reply.map(([from, to, master, ...replicas]) => ({
from,
to,
@@ -31,7 +31,8 @@ export default {
}
} as const satisfies Command;
function transformNode([host, port, id ]: RawNode) {
function transformNode(node: RawNode) {
const [host, port, id] = node as unknown as UnwrapReply<typeof node>;
return {
host,
port,

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, SetReply, Command } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, SetReply, UnwrapReply, Command } from '../RESP/types';
export type CommandGetKeysAndFlagsRawReply = ArrayReply<TuplesReply<[
key: BlobStringReply,
@@ -11,10 +11,13 @@ export default {
transformArguments(args: Array<RedisArgument>) {
return ['COMMAND', 'GETKEYSANDFLAGS', ...args];
},
transformReply(reply: CommandGetKeysAndFlagsRawReply) {
return reply.map(([key, flags]) => ({
key,
flags
}));
transformReply(reply: UnwrapReply<CommandGetKeysAndFlagsRawReply>) {
return reply.map(entry => {
const [key, flags] = entry as unknown as UnwrapReply<typeof entry>;
return {
key,
flags
};
});
}
} as const satisfies Command;

View File

@@ -25,19 +25,21 @@ describe('FUNCTION LIST', () => {
});
testUtils.testWithClient('client.functionList', async client => {
await loadMathFunction(client);
const [, reply] = await Promise.all([
loadMathFunction(client),
client.functionList()
]);
assert.deepEqual(
await client.functionList(),
[{
library_name: MATH_FUNCTION.name,
engine: MATH_FUNCTION.engine,
functions: [{
name: MATH_FUNCTION.library.square.NAME,
description: null,
flags: ['no-writes']
}]
reply[0].library_name;
assert.deepEqual(reply, [{
library_name: MATH_FUNCTION.name,
engine: MATH_FUNCTION.engine,
functions: [{
name: MATH_FUNCTION.library.square.NAME,
description: null,
flags: ['no-writes']
}]
);
}]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,18 +1,18 @@
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, NullReply, SetReply, Resp2Reply, CommandArguments, Command } from '../RESP/types';
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, NullReply, SetReply, UnwrapReply, Resp2Reply, CommandArguments, Command, ReplyWithTypeMapping } from '../RESP/types';
export interface FunctionListOptions {
LIBRARYNAME?: RedisArgument;
}
export type FunctionListReplyItem = [
[BlobStringReply<'library_name'>, BlobStringReply],
[BlobStringReply<'library_name'>, BlobStringReply | NullReply],
[BlobStringReply<'engine'>, BlobStringReply],
[BlobStringReply<'functions'>, ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply],
[BlobStringReply<'description'>, BlobStringReply | NullReply],
[BlobStringReply<'flags'>, SetReply<BlobStringReply>],
]>>]
]
];
export type FunctionListReply = ArrayReply<TuplesToMapReply<FunctionListReplyItem>>;
@@ -29,16 +29,22 @@ export default {
return args;
},
transformReply: {
2: (reply: Resp2Reply<FunctionListReply>) => {
return reply.map(library => ({
library_name: library[1],
engine: library[3],
functions: library[5].map(fn => ({
name: fn[1],
description: fn[3],
flags: fn[5]
}))
}));
2: (reply: UnwrapReply<Resp2Reply<FunctionListReply>>) => {
return reply.map(library => {
const unwrapped = library as unknown as UnwrapReply<typeof library>;
return {
library_name: unwrapped[1],
engine: unwrapped[3],
functions: (unwrapped[5] as unknown as UnwrapReply<typeof unwrapped[5]>).map(fn => {
const unwrapped = fn as unknown as UnwrapReply<typeof fn>;
return {
name: unwrapped[1],
description: unwrapped[3],
flags: unwrapped[5]
};
})
};
});
},
3: undefined as unknown as () => FunctionListReply
}

View File

@@ -25,20 +25,24 @@ describe('FUNCTION LIST WITHCODE', () => {
});
testUtils.testWithClient('client.functionListWithCode', async client => {
await loadMathFunction(client);
const [, reply] = await Promise.all([
loadMathFunction(client),
client.functionListWithCode()
]);
assert.deepEqual(
await client.functionListWithCode(),
[{
library_name: MATH_FUNCTION.name,
engine: MATH_FUNCTION.engine,
functions: [{
name: MATH_FUNCTION.library.square.NAME,
description: null,
flags: ['no-writes']
}],
library_code: MATH_FUNCTION.code
}]
);
const a = reply[0];
const b = a.functions[0].description;
assert.deepEqual(reply, [{
library_name: MATH_FUNCTION.name,
engine: MATH_FUNCTION.engine,
functions: [{
name: MATH_FUNCTION.library.square.NAME,
description: null,
flags: ['no-writes']
}],
library_code: MATH_FUNCTION.code
}]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,4 +1,4 @@
import { TuplesToMapReply, BlobStringReply, ArrayReply, Command, Resp2Reply } from '../RESP/types';
import { TuplesToMapReply, BlobStringReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
import FUNCTION_LIST, { FunctionListReplyItem } from './FUNCTION_LIST';
export type FunctionListWithCodeReply = ArrayReply<TuplesToMapReply<[
@@ -15,17 +15,23 @@ export default {
return redisArgs;
},
transformReply: {
2: (reply: Resp2Reply<FunctionListWithCodeReply>) => {
return reply.map((library: any) => ({
library_name: library[1],
engine: library[3],
functions: library[5].map((fn: any) => ({
name: fn[1],
description: fn[3],
flags: fn[5]
})),
library_code: library[7]
})) as unknown as number;
2: (reply: UnwrapReply<Resp2Reply<FunctionListWithCodeReply>>) => {
return reply.map(library => {
const unwrapped = library as unknown as UnwrapReply<typeof library>;
return {
library_name: unwrapped[1],
engine: unwrapped[3],
functions: (unwrapped[5] as unknown as UnwrapReply<typeof unwrapped[5]>).map(fn => {
const unwrapped = fn as unknown as UnwrapReply<typeof fn>;
return {
name: unwrapped[1],
description: unwrapped[3],
flags: unwrapped[5]
};
}),
library_code: unwrapped[7]
};
});
},
3: undefined as unknown as () => FunctionListWithCodeReply
}

View File

@@ -1,4 +1,4 @@
import { ArrayReply, BlobStringReply, NullReply, Command, RedisArgument } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, NullReply, UnwrapReply, Command } from '../RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export default {
@@ -10,10 +10,13 @@ export default {
) {
return pushVariadicArguments(['GEOPOS', key], member);
},
transformReply(reply: ArrayReply<[BlobStringReply, BlobStringReply] | NullReply>) {
return reply.map(item => item === null ? null : {
longitude: item[0],
latitude: item[1]
transformReply(reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, BlobStringReply]> | NullReply>>) {
return reply.map(item => {
const unwrapped = item as unknown as UnwrapReply<typeof item>;
return unwrapped === null ? null : {
longitude: unwrapped[0],
latitude: unwrapped[1]
};
});
}
} as const satisfies Command;

View File

@@ -1,4 +1,4 @@
import { ArrayReply, BlobStringReply, NumberReply, DoubleReply, Command, RedisArgument } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, NumberReply, DoubleReply, UnwrapReply, Command } from '../RESP/types';
import GEOSEARCH, { GeoSearchBy, GeoSearchFrom, GeoSearchOptions } from './GEOSEARCH';
export const GEO_REPLY_WITH = {
@@ -35,7 +35,7 @@ export default {
return args;
},
transformReply(
reply: ArrayReply<[BlobStringReply, ...Array<any>]>,
reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, ...Array<any>]>>>,
replyWith: Array<GeoReplyWith>
) {
const replyWithSet = new Set(replyWith);
@@ -45,20 +45,22 @@ export default {
coordinatesIndex = replyWithSet.has(GEO_REPLY_WITH.COORDINATES) && ++index;
return reply.map(raw => {
const unwrapped = raw as unknown as UnwrapReply<typeof raw>;
const item: GeoReplyWithMember = {
member: raw[0]
member: unwrapped[0]
};
if (distanceIndex) {
item.distance = raw[distanceIndex];
item.distance = unwrapped[distanceIndex];
}
if (hashIndex) {
item.hash = raw[hashIndex];
item.hash = unwrapped[hashIndex];
}
if (coordinatesIndex) {
const [longitude, latitude] = raw[coordinatesIndex];
const [longitude, latitude] = unwrapped[coordinatesIndex];
item.coordinates = {
longitude,
latitude

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, BlobStringReply, Command, NumberReply, Resp2Reply, RespVersions, TuplesToMapReply } from '../RESP/types';
import { RedisArgument, RespVersions, TuplesToMapReply, BlobStringReply, NumberReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
export interface HelloOptions {
protover?: RespVersions;
@@ -45,7 +45,7 @@ export default {
return args;
},
transformReply: {
2: (reply: Resp2Reply<HelloReply>) => ({
2: (reply: UnwrapReply<Resp2Reply<HelloReply>>) => ({
server: reply[1],
version: reply[3],
proto: reply[5],

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, UnwrapReply, Command } from '../RESP/types';
export type HRandFieldCountWithValuesReply = Array<{
field: BlobStringReply;
@@ -12,7 +12,7 @@ export default {
return ['HRANDFIELD', key, count.toString(), 'WITHVALUES'];
},
transformReply: {
2: (rawReply: ArrayReply<BlobStringReply>) => {
2: (rawReply: UnwrapReply<ArrayReply<BlobStringReply>>) => {
const reply: HRandFieldCountWithValuesReply = [];
let i = 0;
@@ -25,11 +25,14 @@ export default {
return reply;
},
3: (reply: ArrayReply<[BlobStringReply, BlobStringReply]>) => {
return reply.map(([field, value]) => ({
field,
value
})) satisfies HRandFieldCountWithValuesReply;
3: (reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, BlobStringReply]>>>) => {
return reply.map(entry => {
const [field, value] = entry as unknown as UnwrapReply<typeof entry>;
return {
field,
value
};
}) satisfies HRandFieldCountWithValuesReply;
}
}
} as const satisfies Command;

View File

@@ -1,4 +1,4 @@
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, NumberReply, Resp2Reply, Command, TuplesReply } from '../RESP/types';
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, NumberReply, UnwrapReply, Resp2Reply, Command, TuplesReply } from '../RESP/types';
import LCS from './LCS';
export interface LcsIdxOptions {
@@ -41,7 +41,7 @@ export default {
return args;
},
transformReply: {
2: (reply: Resp2Reply<LcsIdxReply>) => ({
2: (reply: UnwrapReply<Resp2Reply<LcsIdxReply>>) => ({
matches: reply[1],
len: reply[3]
}),

View File

@@ -1,4 +1,4 @@
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, NumberReply, Resp2Reply, Command, TuplesReply } from '../RESP/types';
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, TuplesReply, NumberReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
import LCS_IDX, { LcsIdxOptions, LcsIdxRange } from './LCS_IDX';
export type LcsIdxWithMatchLenMatches = ArrayReply<
@@ -27,7 +27,7 @@ export default {
return args;
},
transformReply: {
2: (reply: Resp2Reply<LcsIdxWithMatchLenReply>) => ({
2: (reply: UnwrapReply<Resp2Reply<LcsIdxWithMatchLenReply>>) => ({
matches: reply[1],
len: reply[3]
}),

View File

@@ -1,4 +1,4 @@
import { TuplesToMapReply, BlobStringReply, NumberReply, DoubleReply, Command, Resp2Reply } from '../RESP/types';
import { TuplesToMapReply, BlobStringReply, NumberReply, DoubleReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
export type MemoryStatsReply = TuplesToMapReply<[
[BlobStringReply<'peak.allocated'>, NumberReply],
@@ -39,13 +39,12 @@ export default {
return ['MEMORY', 'STATS'];
},
transformReply: {
2: (rawReply: Array<BlobStringReply | NumberReply>) => {
const reply: Partial<Resp2Reply<MemoryStatsReply['DEFAULT']>> = {};
2: (rawReply: UnwrapReply<ArrayReply<BlobStringReply | NumberReply>>) => {
const reply: any = {};
let i = 0;
while (i < rawReply.length) {
const key = rawReply[i++] as keyof MemoryStatsReply['DEFAULT'];
reply[key] = rawReply[i++] as any;
reply[rawReply[i++] as any] = rawReply[i++];
}
return reply as MemoryStatsReply['DEFAULT'];

View File

@@ -1,4 +1,4 @@
import { ArrayReply, BlobStringReply, NumberReply, Command, Resp2Reply, TuplesToMapReply } from '../RESP/types';
import { ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
export type ModuleListReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply],
@@ -12,11 +12,14 @@ export default {
return ['MODULE', 'LIST'];
},
transformReply: {
2: (reply: Resp2Reply<ModuleListReply>) => {
return reply.map(module => ({
name: module[1],
ver: module[3]
}));
2: (reply: UnwrapReply<Resp2Reply<ModuleListReply>>) => {
return reply.map(module => {
const unwrapped = module as unknown as UnwrapReply<typeof module>;
return {
name: unwrapped[1],
ver: unwrapped[3]
};
});
},
3: undefined as unknown as () => ModuleListReply
}

View File

@@ -1,4 +1,4 @@
import { ArrayReply, BlobStringReply, NumberReply, Command } from '../RESP/types';
import { ArrayReply, BlobStringReply, NumberReply, UnwrapReply, Command } from '../RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export default {
@@ -11,7 +11,7 @@ export default {
return args;
},
transformReply(rawReply: ArrayReply<BlobStringReply | NumberReply>) {
transformReply(rawReply: UnwrapReply<ArrayReply<BlobStringReply | NumberReply>>) {
const reply = Object.create(null);
let i = 0;
while (i < rawReply.length) {

View File

@@ -1,9 +1,9 @@
import { ArrayReply, BlobStringReply, Command, NumberReply } from '../RESP/types';
import { BlobStringReply, NumberReply, ArrayReply, TuplesReply, UnwrapReply, Command } from '../RESP/types';
type MasterRole = [
role: BlobStringReply<'master'>,
replicationOffest: NumberReply,
replicas: ArrayReply<[host: BlobStringReply, port: BlobStringReply, replicationOffest: BlobStringReply]>
replicas: ArrayReply<TuplesReply<[host: BlobStringReply, port: BlobStringReply, replicationOffest: BlobStringReply]>>
];
type SlaveRole = [
@@ -19,7 +19,7 @@ type SentinelRole = [
masterNames: ArrayReply<BlobStringReply>
];
type Role = MasterRole | SlaveRole | SentinelRole;
type Role = TuplesReply<MasterRole | SlaveRole | SentinelRole>;
export default {
FIRST_KEY_INDEX: undefined,
@@ -27,18 +27,21 @@ export default {
transformArguments() {
return ['ROLE'];
},
transformReply(reply: Role) {
switch (reply[0] as Role[0]['DEFAULT']) {
transformReply(reply: UnwrapReply<Role>) {
switch (reply[0] as unknown as UnwrapReply<typeof reply[0]>) {
case 'master': {
const [role, replicationOffest, replicas] = reply as MasterRole;
return {
role,
replicationOffest,
replicas: replicas.map(([host, port, replicationOffest]) => ({
host,
port: Number(port),
replicationOffest: Number(replicationOffest)
})),
replicas: (replicas as unknown as UnwrapReply<typeof replicas>).map(replica => {
const [host, port, replicationOffest] = replica as unknown as UnwrapReply<typeof replica>;
return {
host,
port: Number(port),
replicationOffest: Number(replicationOffest)
};
})
};
}

View File

@@ -1,4 +1,4 @@
import { RedisArgument, TuplesReply, BlobStringReply, ArrayReply, Command } from '../RESP/types';
import { RedisArgument, TuplesReply, BlobStringReply, ArrayReply, UnwrapReply, Command } from '../RESP/types';
import { StreamMessagesRawReply, transformStreamMessagesReply } from './generic-transformers';
export interface XAutoClaimOptions {
@@ -37,7 +37,7 @@ export default {
return args;
},
transformReply(reply: XAutoClaimRawReply) {
transformReply(reply: UnwrapReply<XAutoClaimRawReply>) {
return {
nextId: reply[0],
messages: transformStreamMessagesReply(reply[1]),

View File

@@ -1,4 +1,4 @@
import { TuplesReply, BlobStringReply, ArrayReply, Command } from '../RESP/types';
import { TuplesReply, BlobStringReply, ArrayReply, UnwrapReply, Command } from '../RESP/types';
import XAUTOCLAIM from './XAUTOCLAIM';
type XAutoClaimJustIdRawReply = TuplesReply<[
@@ -15,7 +15,7 @@ export default {
redisArgs.push('JUSTID');
return redisArgs;
},
transformReply(reply: XAutoClaimJustIdRawReply) {
transformReply(reply: UnwrapReply<XAutoClaimJustIdRawReply>) {
return {
nextId: reply[0],
messages: reply[1],

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, Resp2Reply, Command } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
export type XInfoConsumersReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply],
@@ -18,13 +18,16 @@ export default {
return ['XINFO', 'CONSUMERS', key, group];
},
transformReply: {
2: (reply: Resp2Reply<XInfoConsumersReply>) => {
return reply.map(consumer => ({
name: consumer[1],
pending: consumer[3],
idle: consumer[5],
inactive: consumer[7]
}));
2: (reply: UnwrapReply<Resp2Reply<XInfoConsumersReply>>) => {
return reply.map(consumer => {
const unwrapped = consumer as unknown as UnwrapReply<typeof consumer>;
return {
name: unwrapped[1],
pending: unwrapped[3],
idle: unwrapped[5],
inactive: unwrapped[7]
};
});
},
3: undefined as unknown as () => XInfoConsumersReply
}

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, Resp2Reply, Command, NullReply } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesToMapReply, BlobStringReply, NumberReply, NullReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
export type XInfoGroupsReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply],
@@ -18,15 +18,18 @@ export default {
return ['XINFO', 'GROUPS', key];
},
transformReply: {
2: (reply: Resp2Reply<XInfoGroupsReply>) => {
return reply.map(group => ({
name: group[1],
consumers: group[3],
pending: group[5],
'last-delivered-id': group[7],
'entries-read': group[9],
lag: group[11]
}));
2: (reply: UnwrapReply<Resp2Reply<XInfoGroupsReply>>) => {
return reply.map(group => {
const unwrapped = group as unknown as UnwrapReply<typeof group>;
return {
name: unwrapped[1],
consumers: unwrapped[3],
pending: unwrapped[5],
'last-delivered-id': unwrapped[7],
'entries-read': unwrapped[9],
lag: unwrapped[11]
};
});
},
3: undefined as unknown as () => XInfoGroupsReply
}

View File

@@ -67,5 +67,5 @@ export default {
} as const satisfies Command;
function transformEntry(entry: StreamMessageRawReply | NullReply) {
return entry === null ? null : transformStreamMessageReply(entry);
return entry === null ? null : transformStreamMessageReply(entry as StreamMessageRawReply);
}

View File

@@ -1,4 +1,4 @@
import { RedisArgument, BlobStringReply, NullReply, TuplesReply, NumberReply, Command, ArrayReply } from '../RESP/types';
import { RedisArgument, BlobStringReply, NullReply, ArrayReply, TuplesReply, NumberReply, UnwrapReply, Command } from '../RESP/types';
type XPendingRawReply = TuplesReply<[
pending: NumberReply,
@@ -16,15 +16,19 @@ export default {
transformArguments(key: RedisArgument, group: RedisArgument) {
return ['XPENDING', key, group];
},
transformReply(reply: XPendingRawReply) {
transformReply(reply: UnwrapReply<XPendingRawReply>) {
const consumers = reply[3] as unknown as UnwrapReply<typeof reply[3]>;
return {
pending: reply[0],
firstId: reply[1],
lastId: reply[2],
consumers: reply[3] === null ? null : reply[3].map(([name, deliveriesCounter]) => ({
name,
deliveriesCounter: Number(deliveriesCounter)
}))
consumers: consumers === null ? null : consumers.map(consumer => {
const [name, deliveriesCounter] = consumer as unknown as UnwrapReply<typeof consumer>;
return {
name,
deliveriesCounter: Number(deliveriesCounter)
};
})
}
}
} as const satisfies Command;

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, NumberReply, Command } from '../RESP/types';
import { RedisArgument, ArrayReply, TuplesReply, BlobStringReply, NumberReply, UnwrapReply, Command } from '../RESP/types';
export interface XPendingRangeOptions {
IDLE?: number;
@@ -41,12 +41,15 @@ export default {
return args;
},
transformReply(reply: XPendingRangeRawReply) {
return reply.map(pending => ({
id: pending[0],
consumer: pending[1],
millisecondsSinceLastDelivery: pending[2],
deliveriesCounter: pending[3]
}));
transformReply(reply: UnwrapReply<XPendingRangeRawReply>) {
return reply.map(pending => {
const unwrapped = pending as unknown as UnwrapReply<typeof pending>;
return {
id: unwrapped[0],
consumer: unwrapped[1],
millisecondsSinceLastDelivery: unwrapped[2],
deliveriesCounter: unwrapped[3]
};
});
}
} as const satisfies Command;

View File

@@ -1,5 +1,5 @@
import { NullReply, TuplesReply, BlobStringReply, DoubleReply, ArrayReply, Resp2Reply, Command, RedisArgument } from '../RESP/types';
import { pushVariadicArgument, RedisVariadicArgument, SortedSetSide, transformSortedSetReply } from './generic-transformers';
import { RedisArgument, NullReply, TuplesReply, BlobStringReply, DoubleReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
import { pushVariadicArgument, RedisVariadicArgument, SortedSetSide, transformSortedSetReply, transformDoubleReply } from './generic-transformers';
export interface ZMPopOptions {
COUNT?: number;
@@ -39,20 +39,23 @@ export default {
return transformZMPopArguments(['ZMPOP'], ...args);
},
transformReply: {
2: (reply: Resp2Reply<ZMPopRawReply>) => {
2(reply: UnwrapReply<Resp2Reply<ZMPopRawReply>>) {
return reply === null ? null : {
key: reply[0],
members: reply[1].map(([value, score]) => ({
value,
score: Number(score)
}))
members: (reply[1] as unknown as UnwrapReply<typeof reply[1]>).map(member => {
const [value, score] = member as unknown as UnwrapReply<typeof member>;
return {
value,
score: transformDoubleReply[2](score)
};
})
};
},
3: (reply: ZMPopRawReply) => {
3(reply: UnwrapReply<ZMPopRawReply>) {
return reply === null ? null : {
key: reply[0],
members: transformSortedSetReply[3](reply[1])
};
},
}
}
} as const satisfies Command;

View File

@@ -1,4 +1,4 @@
import { RedisArgument, ArrayReply, NullReply, BlobStringReply, DoubleReply, Command } from '../RESP/types';
import { RedisArgument, ArrayReply, NullReply, BlobStringReply, DoubleReply, UnwrapReply, Command } from '../RESP/types';
import { pushVariadicArguments, RedisVariadicArgument, transformNullableDoubleReply } from './generic-transformers';
export default {
@@ -11,7 +11,7 @@ export default {
return pushVariadicArguments(['ZMSCORE', key], member);
},
transformReply: {
2: (reply: ArrayReply<NullReply | BlobStringReply>) => {
2: (reply: UnwrapReply<ArrayReply<NullReply | BlobStringReply>>) => {
return reply.map(transformNullableDoubleReply[2]);
},
3: undefined as unknown as () => ArrayReply<NullReply | DoubleReply>

View File

@@ -1,4 +1,4 @@
import { RedisArgument, TuplesReply, BlobStringReply, DoubleReply, Command } from '../RESP/types';
import { RedisArgument, TuplesReply, BlobStringReply, DoubleReply, UnwrapReply, Command } from '../RESP/types';
export default {
FIRST_KEY_INDEX: 1,
@@ -7,7 +7,7 @@ export default {
return ['ZPOPMAX', key];
},
transformReply: {
2: (reply: TuplesReply<[]> | TuplesReply<[BlobStringReply, BlobStringReply]>) => {
2: (reply: UnwrapReply<TuplesReply<[] | [BlobStringReply, BlobStringReply]>>) => {
if (reply.length === 0) return null;
return {
@@ -15,7 +15,7 @@ export default {
score: Number(reply[1])
};
},
3: (reply: TuplesReply<[]> | TuplesReply<[BlobStringReply, DoubleReply]>) => {
3: (reply: UnwrapReply<TuplesReply<[] | [BlobStringReply, DoubleReply]>>) => {
if (reply.length === 0) return null;
return {

View File

@@ -1,4 +1,4 @@
import { NullReply, TuplesReply, NumberReply, BlobStringReply, DoubleReply, Command } from '../RESP/types';
import { NullReply, TuplesReply, NumberReply, BlobStringReply, DoubleReply, UnwrapReply, Command } from '../RESP/types';
import ZRANK from './ZRANK';
export default {
@@ -10,7 +10,7 @@ export default {
return redisArgs;
},
transformReply: {
2: (reply: NullReply | TuplesReply<[NumberReply, BlobStringReply]>) => {
2: (reply: UnwrapReply<NullReply | TuplesReply<[NumberReply, BlobStringReply]>>) => {
if (reply === null) return null;
return {
@@ -18,7 +18,7 @@ export default {
score: Number(reply[1])
};
},
3: (reply: NullReply | TuplesReply<[BlobStringReply, DoubleReply]>) => {
3: (reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, DoubleReply]>>) => {
if (reply === null) return null;
return {

View File

@@ -1,12 +1,14 @@
import { ArrayReply, BlobStringReply, BooleanReply, CommandArguments, DoubleReply, NullReply, NumberReply, RedisArgument, TuplesReply } from '../RESP/types';
import { UnwrapReply, ArrayReply, BlobStringReply, BooleanReply, CommandArguments, DoubleReply, MapReply, NullReply, NumberReply, RedisArgument, TuplesReply } from '../RESP/types';
export const transformBooleanReply = {
2: (reply: NumberReply<0 | 1>) => reply === 1,
2: (reply: NumberReply<0 | 1>) => reply as unknown as UnwrapReply<typeof reply> === 1,
3: undefined as unknown as () => BooleanReply
};
export const transformBooleanArrayReply = {
2: (reply: ArrayReply<NumberReply<0 | 1>>) => reply.map(transformBooleanReply[2]),
2: (reply: ArrayReply<NumberReply<0 | 1>>) => {
return (reply as unknown as UnwrapReply<typeof reply>).map(transformBooleanReply[2]);
},
3: undefined as unknown as () => ArrayReply<BooleanReply>
};
@@ -60,7 +62,7 @@ export const transformNullableDoubleReply = {
2: (reply: BlobStringReply | NullReply) => {
if (reply === null) return null;
return transformDoubleReply[2](reply);
return transformDoubleReply[2](reply as BlobStringReply);
},
3: undefined as unknown as () => DoubleReply | NullReply
};
@@ -68,10 +70,11 @@ export const transformNullableDoubleReply = {
export function transformTuplesReply(
reply: ArrayReply<BlobStringReply>
): Record<string, BlobStringReply> {
const message = Object.create(null);
const inferred = reply as unknown as UnwrapReply<typeof reply>,
message = Object.create(null);
for (let i = 0; i < reply.length; i += 2) {
message[reply[i].toString()] = reply[i + 1];
for (let i = 0; i < inferred.length; i += 2) {
message[inferred[i].toString()] = inferred[i + 1];
}
return message;
@@ -82,7 +85,8 @@ export type StreamMessageRawReply = TuplesReply<[
message: ArrayReply<BlobStringReply>
]>;
export function transformStreamMessageReply([id, message]: StreamMessageRawReply) {
export function transformStreamMessageReply(reply: StreamMessageRawReply) {
const [id, message] = reply as unknown as UnwrapReply<typeof reply>;
return {
id,
message: transformTuplesReply(message)
@@ -92,13 +96,11 @@ export function transformStreamMessageReply([id, message]: StreamMessageRawReply
export type StreamMessagesRawReply = ArrayReply<StreamMessageRawReply>;
export function transformStreamMessagesReply(reply: StreamMessagesRawReply) {
return reply.map(transformStreamMessageReply);
return (reply as unknown as UnwrapReply<typeof reply>)
.map(message => transformStreamMessageReply(message));
}
// export type StreamsMessagesReply = Array<{
// name: RedisArgument;
// messages: StreamMessagesReply;
// }> | null;
// export type StreamsMessagesReply = MapReply<BlobStringReply, StreamMessagesRawReply>;
// export function transformStreamsMessagesReply(reply: Array<any> | null): StreamsMessagesReply | null {
// if (reply === null) return null;
@@ -118,21 +120,25 @@ export type SortedSetSide = 'MIN' | 'MAX';
export const transformSortedSetReply = {
2: (reply: ArrayReply<BlobStringReply>) => {
const members = [];
for (let i = 0; i < reply.length; i += 2) {
const inferred = reply as unknown as UnwrapReply<typeof reply>,
members = [];
for (let i = 0; i < inferred.length; i += 2) {
members.push({
value: reply[i],
score: transformDoubleReply[2](reply[i + 1])
value: inferred[i],
score: transformDoubleReply[2](inferred[i + 1])
});
}
return members;
},
3: (reply: ArrayReply<TuplesReply<[BlobStringReply, DoubleReply]>>) => {
return reply.map(([value, score]) => ({
value,
score
}));
return (reply as unknown as UnwrapReply<typeof reply>).map(member => {
const [value, score] = member as unknown as UnwrapReply<typeof member>;
return {
value,
score
};
});
}
}

View File

@@ -1,67 +1,67 @@
export class AbortError extends Error {
constructor() {
super('The command was aborted');
}
constructor() {
super('The command was aborted');
}
}
export class WatchError extends Error {
constructor() {
super('One (or more) of the watched keys has been changed');
}
constructor() {
super('One (or more) of the watched keys has been changed');
}
}
export class ConnectionTimeoutError extends Error {
constructor() {
super('Connection timeout');
}
constructor() {
super('Connection timeout');
}
}
export class ClientClosedError extends Error {
constructor() {
super('The client is closed');
}
constructor() {
super('The client is closed');
}
}
export class ClientOfflineError extends Error {
constructor() {
super('The client is offline');
}
constructor() {
super('The client is offline');
}
}
export class DisconnectsClientError extends Error {
constructor() {
super('Disconnects client');
}
constructor() {
super('Disconnects client');
}
}
export class SocketClosedUnexpectedlyError extends Error {
constructor() {
super('Socket closed unexpectedly');
}
constructor() {
super('Socket closed unexpectedly');
}
}
export class RootNodesUnavailableError extends Error {
constructor() {
super('All the root nodes are unavailable');
}
constructor() {
super('All the root nodes are unavailable');
}
}
export class ReconnectStrategyError extends Error {
originalError: Error;
socketError: unknown;
originalError: Error;
socketError: unknown;
constructor(originalError: Error, socketError: unknown) {
super(originalError.message);
this.originalError = originalError;
this.socketError = socketError;
}
constructor(originalError: Error, socketError: unknown) {
super(originalError.message);
this.originalError = originalError;
this.socketError = socketError;
}
}
export class ErrorReply extends Error {
constructor(message: string) {
super(message);
this.stack = undefined;
}
constructor(message: string) {
super(message);
this.stack = undefined;
}
}
export class SimpleError extends ErrorReply {}