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

View File

@@ -425,7 +425,6 @@ export default class RedisClient<
} catch (err) { } catch (err) {
this._queue.decoder.reset(); this._queue.decoder.reset();
this.emit('error', err); this.emit('error', err);
} }
}) })
.on('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<[ type AclUser = TuplesToMapReply<[
[BlobStringReply<'flags'>, ArrayReply<BlobStringReply>], [BlobStringReply<'flags'>, ArrayReply<BlobStringReply>],
@@ -23,17 +23,20 @@ export default {
return ['ACL', 'GETUSER', username]; return ['ACL', 'GETUSER', username];
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<AclUser>) => ({ 2: (reply: UnwrapReply<Resp2Reply<AclUser>>) => ({
flags: reply[1], flags: reply[1],
passwords: reply[3], passwords: reply[3],
commands: reply[5], commands: reply[5],
keys: reply[7], keys: reply[7],
channels: reply[9], channels: reply[9],
selectors: reply[11]?.map(selector => ({ selectors: (reply[11] as unknown as UnwrapReply<typeof reply[11]>)?.map(selector => {
commands: selector[1], const inferred = selector as unknown as UnwrapReply<typeof selector>;
keys: selector[3], return {
channels: selector[5] commands: inferred[1],
})) keys: inferred[3],
channels: inferred[5]
};
})
}), }),
3: undefined as unknown as () => AclUser 3: undefined as unknown as () => AclUser
} }

View File

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

View File

@@ -34,6 +34,12 @@ describe('BITFIELD', () => {
}); });
testUtils.testAll('bitField', async client => { testUtils.testAll('bitField', async client => {
const a = client.bitField('key', [{
operation: 'GET',
encoding: 'i8',
offset: 0
}]);
assert.deepEqual( assert.deepEqual(
await client.bitField('key', [{ await client.bitField('key', [{
operation: 'GET', 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'; import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export default { export default {
@@ -12,7 +12,7 @@ export default {
args.push(timeout.toString()); args.push(timeout.toString());
return args; return args;
}, },
transformReply(reply: NullReply | [BlobStringReply, BlobStringReply]) { transformReply(reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, BlobStringReply]>>) {
if (reply === null) return null; if (reply === null) return null;
return { 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'; import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export function transformBZPopArguments( export function transformBZPopArguments(
@@ -20,14 +20,14 @@ export default {
return transformBZPopArguments('BZPOPMAX', ...args); return transformBZPopArguments('BZPOPMAX', ...args);
}, },
transformReply: { transformReply: {
2: (reply: NullReply | TuplesReply<[BlobStringReply, BlobStringReply, BlobStringReply]>) => { 2(reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, BlobStringReply, BlobStringReply]>>) {
return reply === null ? null : { return reply === null ? null : {
key: reply[0], key: reply[0],
value: reply[1], value: reply[1],
score: Number(reply[2]) score: Number(reply[2])
}; };
}, },
3: (reply: NullReply | TuplesReply<[BlobStringReply, BlobStringReply, DoubleReply]>) => { 3(reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, BlobStringReply, DoubleReply]>>) {
return reply === null ? null : { return reply === null ? null : {
key: reply[0], key: reply[0],
value: reply[1], value: reply[1],

View File

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

View File

@@ -1,78 +1,77 @@
import { strict as assert } from 'assert'; import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLIENT_LIST'; import CLIENT_LIST from './CLIENT_LIST';
import testUtils, { GLOBAL } from '../test-utils'; import testUtils, { GLOBAL } from '../test-utils';
describe('CLIENT LIST', () => { describe('CLIENT LIST', () => {
describe('transformArguments', () => { describe('transformArguments', () => {
it('simple', () => { it('simple', () => {
assert.deepEqual( assert.deepEqual(
transformArguments(), CLIENT_LIST.transformArguments(),
['CLIENT', 'LIST'] ['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']
);
});
}); });
testUtils.testWithClient('client.clientList', async client => { it('with TYPE', () => {
const reply = await client.clientList(); assert.deepEqual(
assert.ok(Array.isArray(reply)); CLIENT_LIST.transformArguments({
TYPE: 'NORMAL'
}),
['CLIENT', 'LIST', 'TYPE', 'NORMAL']
);
});
for (const item of reply) { it('with ID', () => {
assert.equal(typeof item.id, 'number'); assert.deepEqual(
assert.equal(typeof item.addr, 'string'); CLIENT_LIST.transformArguments({
assert.equal(typeof item.fd, 'number'); ID: ['1', '2']
assert.equal(typeof item.name, 'string'); }),
assert.equal(typeof item.age, 'number'); ['CLIENT', 'LIST', 'ID', '1', '2']
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, 0])) { testUtils.testWithClient('client.clientList', async client => {
assert.equal(typeof item.argvMem, 'number'); const reply = await client.clientList();
assert.equal(typeof item.totMem, 'number'); assert.ok(Array.isArray(reply));
assert.equal(typeof item.user, 'string'); 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])) { if (testUtils.isVersionGreaterThan([6, 0])) {
assert.equal(typeof item.redir, 'number'); assert.equal(typeof item.argvMem, 'number');
assert.equal(typeof item.laddr, 'string'); assert.equal(typeof item.totMem, 'number');
} assert.equal(typeof item.user, 'string');
if (testUtils.isVersionGreaterThan([7, 0])) { if (testUtils.isVersionGreaterThan([6, 2])) {
assert.equal(typeof item.multiMem, 'number'); assert.equal(typeof item.redir, 'number');
assert.equal(typeof item.resp, '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])) { 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, length = split.length - 1,
reply: Array<ClientInfoReply> = []; reply: Array<ClientInfoReply> = [];
for (let i = 0; i < length; i++) { 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; 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<[ type TrackingInfo = TuplesToMapReply<[
[BlobStringReply<'flags'>, SetReply<BlobStringReply>], [BlobStringReply<'flags'>, SetReply<BlobStringReply>],
@@ -13,7 +13,7 @@ export default {
return ['CLIENT', 'TRACKINGINFO']; return ['CLIENT', 'TRACKINGINFO'];
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<TrackingInfo>) => ({ 2: (reply: UnwrapReply<Resp2Reply<TrackingInfo>>) => ({
flags: reply[1], flags: reply[1],
redirect: reply[3], redirect: reply[3],
prefixes: reply[5] 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<[ type ClusterLinksReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'direction'>, BlobStringReply], [BlobStringReply<'direction'>, BlobStringReply],
@@ -16,14 +16,17 @@ export default {
return ['CLUSTER', 'LINKS']; return ['CLUSTER', 'LINKS'];
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<ClusterLinksReply>) => reply.map(link => ({ 2: (reply: UnwrapReply<Resp2Reply<ClusterLinksReply>>) => reply.map(link => {
direction: link[1], const unwrapped = link as unknown as UnwrapReply<typeof link>;
node: link[3], return {
'create-time': link[5], direction: unwrapped[1],
events: link[7], node: unwrapped[3],
'send-buffer-allocated': link[9], 'create-time': unwrapped[5],
'send-buffer-used': link[11] events: unwrapped[7],
})), 'send-buffer-allocated': unwrapped[9],
'send-buffer-used': unwrapped[11]
};
}),
3: undefined as unknown as () => ClusterLinksReply 3: undefined as unknown as () => ClusterLinksReply
} }
} as const satisfies Command; } 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, host: BlobStringReply,
port: NumberReply, port: NumberReply,
id: BlobStringReply id: BlobStringReply
]; ]>;
type ClusterSlotsRawReply = ArrayReply<[ type ClusterSlotsRawReply = ArrayReply<[
from: NumberReply, from: NumberReply,
@@ -21,7 +21,7 @@ export default {
transformArguments() { transformArguments() {
return ['CLUSTER', 'SLOTS']; return ['CLUSTER', 'SLOTS'];
}, },
transformReply(reply: ClusterSlotsRawReply) { transformReply(reply: UnwrapReply<ClusterSlotsRawReply>) {
return reply.map(([from, to, master, ...replicas]) => ({ return reply.map(([from, to, master, ...replicas]) => ({
from, from,
to, to,
@@ -31,7 +31,8 @@ export default {
} }
} as const satisfies Command; } 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 { return {
host, host,
port, 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<[ export type CommandGetKeysAndFlagsRawReply = ArrayReply<TuplesReply<[
key: BlobStringReply, key: BlobStringReply,
@@ -11,10 +11,13 @@ export default {
transformArguments(args: Array<RedisArgument>) { transformArguments(args: Array<RedisArgument>) {
return ['COMMAND', 'GETKEYSANDFLAGS', ...args]; return ['COMMAND', 'GETKEYSANDFLAGS', ...args];
}, },
transformReply(reply: CommandGetKeysAndFlagsRawReply) { transformReply(reply: UnwrapReply<CommandGetKeysAndFlagsRawReply>) {
return reply.map(([key, flags]) => ({ return reply.map(entry => {
key, const [key, flags] = entry as unknown as UnwrapReply<typeof entry>;
flags return {
})); key,
flags
};
});
} }
} as const satisfies Command; } as const satisfies Command;

View File

@@ -25,19 +25,21 @@ describe('FUNCTION LIST', () => {
}); });
testUtils.testWithClient('client.functionList', async client => { testUtils.testWithClient('client.functionList', async client => {
await loadMathFunction(client); const [, reply] = await Promise.all([
loadMathFunction(client),
client.functionList()
]);
assert.deepEqual( reply[0].library_name;
await client.functionList(),
[{ assert.deepEqual(reply, [{
library_name: MATH_FUNCTION.name, library_name: MATH_FUNCTION.name,
engine: MATH_FUNCTION.engine, engine: MATH_FUNCTION.engine,
functions: [{ functions: [{
name: MATH_FUNCTION.library.square.NAME, name: MATH_FUNCTION.library.square.NAME,
description: null, description: null,
flags: ['no-writes'] flags: ['no-writes']
}]
}] }]
); }]);
}, GLOBAL.SERVERS.OPEN); }, 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 { export interface FunctionListOptions {
LIBRARYNAME?: RedisArgument; LIBRARYNAME?: RedisArgument;
} }
export type FunctionListReplyItem = [ export type FunctionListReplyItem = [
[BlobStringReply<'library_name'>, BlobStringReply], [BlobStringReply<'library_name'>, BlobStringReply | NullReply],
[BlobStringReply<'engine'>, BlobStringReply], [BlobStringReply<'engine'>, BlobStringReply],
[BlobStringReply<'functions'>, ArrayReply<TuplesToMapReply<[ [BlobStringReply<'functions'>, ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply], [BlobStringReply<'name'>, BlobStringReply],
[BlobStringReply<'description'>, BlobStringReply | NullReply], [BlobStringReply<'description'>, BlobStringReply | NullReply],
[BlobStringReply<'flags'>, SetReply<BlobStringReply>], [BlobStringReply<'flags'>, SetReply<BlobStringReply>],
]>>] ]>>]
] ];
export type FunctionListReply = ArrayReply<TuplesToMapReply<FunctionListReplyItem>>; export type FunctionListReply = ArrayReply<TuplesToMapReply<FunctionListReplyItem>>;
@@ -29,16 +29,22 @@ export default {
return args; return args;
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<FunctionListReply>) => { 2: (reply: UnwrapReply<Resp2Reply<FunctionListReply>>) => {
return reply.map(library => ({ return reply.map(library => {
library_name: library[1], const unwrapped = library as unknown as UnwrapReply<typeof library>;
engine: library[3], return {
functions: library[5].map(fn => ({ library_name: unwrapped[1],
name: fn[1], engine: unwrapped[3],
description: fn[3], functions: (unwrapped[5] as unknown as UnwrapReply<typeof unwrapped[5]>).map(fn => {
flags: fn[5] 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 3: undefined as unknown as () => FunctionListReply
} }

View File

@@ -25,20 +25,24 @@ describe('FUNCTION LIST WITHCODE', () => {
}); });
testUtils.testWithClient('client.functionListWithCode', async client => { testUtils.testWithClient('client.functionListWithCode', async client => {
await loadMathFunction(client); const [, reply] = await Promise.all([
loadMathFunction(client),
client.functionListWithCode()
]);
const a = reply[0];
const b = a.functions[0].description;
assert.deepEqual( assert.deepEqual(reply, [{
await client.functionListWithCode(), library_name: MATH_FUNCTION.name,
[{ engine: MATH_FUNCTION.engine,
library_name: MATH_FUNCTION.name, functions: [{
engine: MATH_FUNCTION.engine, name: MATH_FUNCTION.library.square.NAME,
functions: [{ description: null,
name: MATH_FUNCTION.library.square.NAME, flags: ['no-writes']
description: null, }],
flags: ['no-writes'] library_code: MATH_FUNCTION.code
}], }]);
library_code: MATH_FUNCTION.code
}]
);
}, GLOBAL.SERVERS.OPEN); }, 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'; import FUNCTION_LIST, { FunctionListReplyItem } from './FUNCTION_LIST';
export type FunctionListWithCodeReply = ArrayReply<TuplesToMapReply<[ export type FunctionListWithCodeReply = ArrayReply<TuplesToMapReply<[
@@ -15,17 +15,23 @@ export default {
return redisArgs; return redisArgs;
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<FunctionListWithCodeReply>) => { 2: (reply: UnwrapReply<Resp2Reply<FunctionListWithCodeReply>>) => {
return reply.map((library: any) => ({ return reply.map(library => {
library_name: library[1], const unwrapped = library as unknown as UnwrapReply<typeof library>;
engine: library[3], return {
functions: library[5].map((fn: any) => ({ library_name: unwrapped[1],
name: fn[1], engine: unwrapped[3],
description: fn[3], functions: (unwrapped[5] as unknown as UnwrapReply<typeof unwrapped[5]>).map(fn => {
flags: fn[5] const unwrapped = fn as unknown as UnwrapReply<typeof fn>;
})), return {
library_code: library[7] name: unwrapped[1],
})) as unknown as number; description: unwrapped[3],
flags: unwrapped[5]
};
}),
library_code: unwrapped[7]
};
});
}, },
3: undefined as unknown as () => FunctionListWithCodeReply 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'; import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export default { export default {
@@ -10,10 +10,13 @@ export default {
) { ) {
return pushVariadicArguments(['GEOPOS', key], member); return pushVariadicArguments(['GEOPOS', key], member);
}, },
transformReply(reply: ArrayReply<[BlobStringReply, BlobStringReply] | NullReply>) { transformReply(reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, BlobStringReply]> | NullReply>>) {
return reply.map(item => item === null ? null : { return reply.map(item => {
longitude: item[0], const unwrapped = item as unknown as UnwrapReply<typeof item>;
latitude: item[1] return unwrapped === null ? null : {
longitude: unwrapped[0],
latitude: unwrapped[1]
};
}); });
} }
} as const satisfies Command; } 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'; import GEOSEARCH, { GeoSearchBy, GeoSearchFrom, GeoSearchOptions } from './GEOSEARCH';
export const GEO_REPLY_WITH = { export const GEO_REPLY_WITH = {
@@ -35,7 +35,7 @@ export default {
return args; return args;
}, },
transformReply( transformReply(
reply: ArrayReply<[BlobStringReply, ...Array<any>]>, reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, ...Array<any>]>>>,
replyWith: Array<GeoReplyWith> replyWith: Array<GeoReplyWith>
) { ) {
const replyWithSet = new Set(replyWith); const replyWithSet = new Set(replyWith);
@@ -45,20 +45,22 @@ export default {
coordinatesIndex = replyWithSet.has(GEO_REPLY_WITH.COORDINATES) && ++index; coordinatesIndex = replyWithSet.has(GEO_REPLY_WITH.COORDINATES) && ++index;
return reply.map(raw => { return reply.map(raw => {
const unwrapped = raw as unknown as UnwrapReply<typeof raw>;
const item: GeoReplyWithMember = { const item: GeoReplyWithMember = {
member: raw[0] member: unwrapped[0]
}; };
if (distanceIndex) { if (distanceIndex) {
item.distance = raw[distanceIndex]; item.distance = unwrapped[distanceIndex];
} }
if (hashIndex) { if (hashIndex) {
item.hash = raw[hashIndex]; item.hash = unwrapped[hashIndex];
} }
if (coordinatesIndex) { if (coordinatesIndex) {
const [longitude, latitude] = raw[coordinatesIndex]; const [longitude, latitude] = unwrapped[coordinatesIndex];
item.coordinates = { item.coordinates = {
longitude, longitude,
latitude 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 { export interface HelloOptions {
protover?: RespVersions; protover?: RespVersions;
@@ -45,7 +45,7 @@ export default {
return args; return args;
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<HelloReply>) => ({ 2: (reply: UnwrapReply<Resp2Reply<HelloReply>>) => ({
server: reply[1], server: reply[1],
version: reply[3], version: reply[3],
proto: reply[5], 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<{ export type HRandFieldCountWithValuesReply = Array<{
field: BlobStringReply; field: BlobStringReply;
@@ -12,7 +12,7 @@ export default {
return ['HRANDFIELD', key, count.toString(), 'WITHVALUES']; return ['HRANDFIELD', key, count.toString(), 'WITHVALUES'];
}, },
transformReply: { transformReply: {
2: (rawReply: ArrayReply<BlobStringReply>) => { 2: (rawReply: UnwrapReply<ArrayReply<BlobStringReply>>) => {
const reply: HRandFieldCountWithValuesReply = []; const reply: HRandFieldCountWithValuesReply = [];
let i = 0; let i = 0;
@@ -25,11 +25,14 @@ export default {
return reply; return reply;
}, },
3: (reply: ArrayReply<[BlobStringReply, BlobStringReply]>) => { 3: (reply: UnwrapReply<ArrayReply<TuplesReply<[BlobStringReply, BlobStringReply]>>>) => {
return reply.map(([field, value]) => ({ return reply.map(entry => {
field, const [field, value] = entry as unknown as UnwrapReply<typeof entry>;
value return {
})) satisfies HRandFieldCountWithValuesReply; field,
value
};
}) satisfies HRandFieldCountWithValuesReply;
} }
} }
} as const satisfies Command; } 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'; import LCS from './LCS';
export interface LcsIdxOptions { export interface LcsIdxOptions {
@@ -41,7 +41,7 @@ export default {
return args; return args;
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<LcsIdxReply>) => ({ 2: (reply: UnwrapReply<Resp2Reply<LcsIdxReply>>) => ({
matches: reply[1], matches: reply[1],
len: reply[3] 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'; import LCS_IDX, { LcsIdxOptions, LcsIdxRange } from './LCS_IDX';
export type LcsIdxWithMatchLenMatches = ArrayReply< export type LcsIdxWithMatchLenMatches = ArrayReply<
@@ -27,7 +27,7 @@ export default {
return args; return args;
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<LcsIdxWithMatchLenReply>) => ({ 2: (reply: UnwrapReply<Resp2Reply<LcsIdxWithMatchLenReply>>) => ({
matches: reply[1], matches: reply[1],
len: reply[3] 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<[ export type MemoryStatsReply = TuplesToMapReply<[
[BlobStringReply<'peak.allocated'>, NumberReply], [BlobStringReply<'peak.allocated'>, NumberReply],
@@ -39,13 +39,12 @@ export default {
return ['MEMORY', 'STATS']; return ['MEMORY', 'STATS'];
}, },
transformReply: { transformReply: {
2: (rawReply: Array<BlobStringReply | NumberReply>) => { 2: (rawReply: UnwrapReply<ArrayReply<BlobStringReply | NumberReply>>) => {
const reply: Partial<Resp2Reply<MemoryStatsReply['DEFAULT']>> = {}; const reply: any = {};
let i = 0; let i = 0;
while (i < rawReply.length) { while (i < rawReply.length) {
const key = rawReply[i++] as keyof MemoryStatsReply['DEFAULT']; reply[rawReply[i++] as any] = rawReply[i++];
reply[key] = rawReply[i++] as any;
} }
return reply as MemoryStatsReply['DEFAULT']; 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<[ export type ModuleListReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply], [BlobStringReply<'name'>, BlobStringReply],
@@ -12,11 +12,14 @@ export default {
return ['MODULE', 'LIST']; return ['MODULE', 'LIST'];
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<ModuleListReply>) => { 2: (reply: UnwrapReply<Resp2Reply<ModuleListReply>>) => {
return reply.map(module => ({ return reply.map(module => {
name: module[1], const unwrapped = module as unknown as UnwrapReply<typeof module>;
ver: module[3] return {
})); name: unwrapped[1],
ver: unwrapped[3]
};
});
}, },
3: undefined as unknown as () => ModuleListReply 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'; import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
export default { export default {
@@ -11,7 +11,7 @@ export default {
return args; return args;
}, },
transformReply(rawReply: ArrayReply<BlobStringReply | NumberReply>) { transformReply(rawReply: UnwrapReply<ArrayReply<BlobStringReply | NumberReply>>) {
const reply = Object.create(null); const reply = Object.create(null);
let i = 0; let i = 0;
while (i < rawReply.length) { 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 = [ type MasterRole = [
role: BlobStringReply<'master'>, role: BlobStringReply<'master'>,
replicationOffest: NumberReply, replicationOffest: NumberReply,
replicas: ArrayReply<[host: BlobStringReply, port: BlobStringReply, replicationOffest: BlobStringReply]> replicas: ArrayReply<TuplesReply<[host: BlobStringReply, port: BlobStringReply, replicationOffest: BlobStringReply]>>
]; ];
type SlaveRole = [ type SlaveRole = [
@@ -19,7 +19,7 @@ type SentinelRole = [
masterNames: ArrayReply<BlobStringReply> masterNames: ArrayReply<BlobStringReply>
]; ];
type Role = MasterRole | SlaveRole | SentinelRole; type Role = TuplesReply<MasterRole | SlaveRole | SentinelRole>;
export default { export default {
FIRST_KEY_INDEX: undefined, FIRST_KEY_INDEX: undefined,
@@ -27,18 +27,21 @@ export default {
transformArguments() { transformArguments() {
return ['ROLE']; return ['ROLE'];
}, },
transformReply(reply: Role) { transformReply(reply: UnwrapReply<Role>) {
switch (reply[0] as Role[0]['DEFAULT']) { switch (reply[0] as unknown as UnwrapReply<typeof reply[0]>) {
case 'master': { case 'master': {
const [role, replicationOffest, replicas] = reply as MasterRole; const [role, replicationOffest, replicas] = reply as MasterRole;
return { return {
role, role,
replicationOffest, replicationOffest,
replicas: replicas.map(([host, port, replicationOffest]) => ({ replicas: (replicas as unknown as UnwrapReply<typeof replicas>).map(replica => {
host, const [host, port, replicationOffest] = replica as unknown as UnwrapReply<typeof replica>;
port: Number(port), return {
replicationOffest: Number(replicationOffest) 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'; import { StreamMessagesRawReply, transformStreamMessagesReply } from './generic-transformers';
export interface XAutoClaimOptions { export interface XAutoClaimOptions {
@@ -37,7 +37,7 @@ export default {
return args; return args;
}, },
transformReply(reply: XAutoClaimRawReply) { transformReply(reply: UnwrapReply<XAutoClaimRawReply>) {
return { return {
nextId: reply[0], nextId: reply[0],
messages: transformStreamMessagesReply(reply[1]), 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'; import XAUTOCLAIM from './XAUTOCLAIM';
type XAutoClaimJustIdRawReply = TuplesReply<[ type XAutoClaimJustIdRawReply = TuplesReply<[
@@ -15,7 +15,7 @@ export default {
redisArgs.push('JUSTID'); redisArgs.push('JUSTID');
return redisArgs; return redisArgs;
}, },
transformReply(reply: XAutoClaimJustIdRawReply) { transformReply(reply: UnwrapReply<XAutoClaimJustIdRawReply>) {
return { return {
nextId: reply[0], nextId: reply[0],
messages: reply[1], 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<[ export type XInfoConsumersReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply], [BlobStringReply<'name'>, BlobStringReply],
@@ -18,13 +18,16 @@ export default {
return ['XINFO', 'CONSUMERS', key, group]; return ['XINFO', 'CONSUMERS', key, group];
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<XInfoConsumersReply>) => { 2: (reply: UnwrapReply<Resp2Reply<XInfoConsumersReply>>) => {
return reply.map(consumer => ({ return reply.map(consumer => {
name: consumer[1], const unwrapped = consumer as unknown as UnwrapReply<typeof consumer>;
pending: consumer[3], return {
idle: consumer[5], name: unwrapped[1],
inactive: consumer[7] pending: unwrapped[3],
})); idle: unwrapped[5],
inactive: unwrapped[7]
};
});
}, },
3: undefined as unknown as () => XInfoConsumersReply 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<[ export type XInfoGroupsReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'name'>, BlobStringReply], [BlobStringReply<'name'>, BlobStringReply],
@@ -18,15 +18,18 @@ export default {
return ['XINFO', 'GROUPS', key]; return ['XINFO', 'GROUPS', key];
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<XInfoGroupsReply>) => { 2: (reply: UnwrapReply<Resp2Reply<XInfoGroupsReply>>) => {
return reply.map(group => ({ return reply.map(group => {
name: group[1], const unwrapped = group as unknown as UnwrapReply<typeof group>;
consumers: group[3], return {
pending: group[5], name: unwrapped[1],
'last-delivered-id': group[7], consumers: unwrapped[3],
'entries-read': group[9], pending: unwrapped[5],
lag: group[11] 'last-delivered-id': unwrapped[7],
})); 'entries-read': unwrapped[9],
lag: unwrapped[11]
};
});
}, },
3: undefined as unknown as () => XInfoGroupsReply 3: undefined as unknown as () => XInfoGroupsReply
} }

View File

@@ -67,5 +67,5 @@ export default {
} as const satisfies Command; } as const satisfies Command;
function transformEntry(entry: StreamMessageRawReply | NullReply) { 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<[ type XPendingRawReply = TuplesReply<[
pending: NumberReply, pending: NumberReply,
@@ -16,15 +16,19 @@ export default {
transformArguments(key: RedisArgument, group: RedisArgument) { transformArguments(key: RedisArgument, group: RedisArgument) {
return ['XPENDING', key, group]; return ['XPENDING', key, group];
}, },
transformReply(reply: XPendingRawReply) { transformReply(reply: UnwrapReply<XPendingRawReply>) {
const consumers = reply[3] as unknown as UnwrapReply<typeof reply[3]>;
return { return {
pending: reply[0], pending: reply[0],
firstId: reply[1], firstId: reply[1],
lastId: reply[2], lastId: reply[2],
consumers: reply[3] === null ? null : reply[3].map(([name, deliveriesCounter]) => ({ consumers: consumers === null ? null : consumers.map(consumer => {
name, const [name, deliveriesCounter] = consumer as unknown as UnwrapReply<typeof consumer>;
deliveriesCounter: Number(deliveriesCounter) return {
})) name,
deliveriesCounter: Number(deliveriesCounter)
};
})
} }
} }
} as const satisfies Command; } 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 { export interface XPendingRangeOptions {
IDLE?: number; IDLE?: number;
@@ -41,12 +41,15 @@ export default {
return args; return args;
}, },
transformReply(reply: XPendingRangeRawReply) { transformReply(reply: UnwrapReply<XPendingRangeRawReply>) {
return reply.map(pending => ({ return reply.map(pending => {
id: pending[0], const unwrapped = pending as unknown as UnwrapReply<typeof pending>;
consumer: pending[1], return {
millisecondsSinceLastDelivery: pending[2], id: unwrapped[0],
deliveriesCounter: pending[3] consumer: unwrapped[1],
})); millisecondsSinceLastDelivery: unwrapped[2],
deliveriesCounter: unwrapped[3]
};
});
} }
} as const satisfies Command; } as const satisfies Command;

View File

@@ -1,5 +1,5 @@
import { NullReply, TuplesReply, BlobStringReply, DoubleReply, ArrayReply, Resp2Reply, Command, RedisArgument } from '../RESP/types'; import { RedisArgument, NullReply, TuplesReply, BlobStringReply, DoubleReply, ArrayReply, UnwrapReply, Resp2Reply, Command } from '../RESP/types';
import { pushVariadicArgument, RedisVariadicArgument, SortedSetSide, transformSortedSetReply } from './generic-transformers'; import { pushVariadicArgument, RedisVariadicArgument, SortedSetSide, transformSortedSetReply, transformDoubleReply } from './generic-transformers';
export interface ZMPopOptions { export interface ZMPopOptions {
COUNT?: number; COUNT?: number;
@@ -39,20 +39,23 @@ export default {
return transformZMPopArguments(['ZMPOP'], ...args); return transformZMPopArguments(['ZMPOP'], ...args);
}, },
transformReply: { transformReply: {
2: (reply: Resp2Reply<ZMPopRawReply>) => { 2(reply: UnwrapReply<Resp2Reply<ZMPopRawReply>>) {
return reply === null ? null : { return reply === null ? null : {
key: reply[0], key: reply[0],
members: reply[1].map(([value, score]) => ({ members: (reply[1] as unknown as UnwrapReply<typeof reply[1]>).map(member => {
value, const [value, score] = member as unknown as UnwrapReply<typeof member>;
score: Number(score) return {
})) value,
score: transformDoubleReply[2](score)
};
})
}; };
}, },
3: (reply: ZMPopRawReply) => { 3(reply: UnwrapReply<ZMPopRawReply>) {
return reply === null ? null : { return reply === null ? null : {
key: reply[0], key: reply[0],
members: transformSortedSetReply[3](reply[1]) members: transformSortedSetReply[3](reply[1])
}; };
}, }
} }
} as const satisfies Command; } 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'; import { pushVariadicArguments, RedisVariadicArgument, transformNullableDoubleReply } from './generic-transformers';
export default { export default {
@@ -11,7 +11,7 @@ export default {
return pushVariadicArguments(['ZMSCORE', key], member); return pushVariadicArguments(['ZMSCORE', key], member);
}, },
transformReply: { transformReply: {
2: (reply: ArrayReply<NullReply | BlobStringReply>) => { 2: (reply: UnwrapReply<ArrayReply<NullReply | BlobStringReply>>) => {
return reply.map(transformNullableDoubleReply[2]); return reply.map(transformNullableDoubleReply[2]);
}, },
3: undefined as unknown as () => ArrayReply<NullReply | DoubleReply> 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 { export default {
FIRST_KEY_INDEX: 1, FIRST_KEY_INDEX: 1,
@@ -7,7 +7,7 @@ export default {
return ['ZPOPMAX', key]; return ['ZPOPMAX', key];
}, },
transformReply: { transformReply: {
2: (reply: TuplesReply<[]> | TuplesReply<[BlobStringReply, BlobStringReply]>) => { 2: (reply: UnwrapReply<TuplesReply<[] | [BlobStringReply, BlobStringReply]>>) => {
if (reply.length === 0) return null; if (reply.length === 0) return null;
return { return {
@@ -15,7 +15,7 @@ export default {
score: Number(reply[1]) score: Number(reply[1])
}; };
}, },
3: (reply: TuplesReply<[]> | TuplesReply<[BlobStringReply, DoubleReply]>) => { 3: (reply: UnwrapReply<TuplesReply<[] | [BlobStringReply, DoubleReply]>>) => {
if (reply.length === 0) return null; if (reply.length === 0) return null;
return { 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'; import ZRANK from './ZRANK';
export default { export default {
@@ -10,7 +10,7 @@ export default {
return redisArgs; return redisArgs;
}, },
transformReply: { transformReply: {
2: (reply: NullReply | TuplesReply<[NumberReply, BlobStringReply]>) => { 2: (reply: UnwrapReply<NullReply | TuplesReply<[NumberReply, BlobStringReply]>>) => {
if (reply === null) return null; if (reply === null) return null;
return { return {
@@ -18,7 +18,7 @@ export default {
score: Number(reply[1]) score: Number(reply[1])
}; };
}, },
3: (reply: NullReply | TuplesReply<[BlobStringReply, DoubleReply]>) => { 3: (reply: UnwrapReply<NullReply | TuplesReply<[BlobStringReply, DoubleReply]>>) => {
if (reply === null) return null; if (reply === null) return null;
return { 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 = { 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 3: undefined as unknown as () => BooleanReply
}; };
export const transformBooleanArrayReply = { 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> 3: undefined as unknown as () => ArrayReply<BooleanReply>
}; };
@@ -60,7 +62,7 @@ export const transformNullableDoubleReply = {
2: (reply: BlobStringReply | NullReply) => { 2: (reply: BlobStringReply | NullReply) => {
if (reply === null) return null; if (reply === null) return null;
return transformDoubleReply[2](reply); return transformDoubleReply[2](reply as BlobStringReply);
}, },
3: undefined as unknown as () => DoubleReply | NullReply 3: undefined as unknown as () => DoubleReply | NullReply
}; };
@@ -68,10 +70,11 @@ export const transformNullableDoubleReply = {
export function transformTuplesReply( export function transformTuplesReply(
reply: ArrayReply<BlobStringReply> reply: ArrayReply<BlobStringReply>
): Record<string, 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) { for (let i = 0; i < inferred.length; i += 2) {
message[reply[i].toString()] = reply[i + 1]; message[inferred[i].toString()] = inferred[i + 1];
} }
return message; return message;
@@ -82,7 +85,8 @@ export type StreamMessageRawReply = TuplesReply<[
message: ArrayReply<BlobStringReply> 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 { return {
id, id,
message: transformTuplesReply(message) message: transformTuplesReply(message)
@@ -92,13 +96,11 @@ export function transformStreamMessageReply([id, message]: StreamMessageRawReply
export type StreamMessagesRawReply = ArrayReply<StreamMessageRawReply>; export type StreamMessagesRawReply = ArrayReply<StreamMessageRawReply>;
export function transformStreamMessagesReply(reply: StreamMessagesRawReply) { 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<{ // export type StreamsMessagesReply = MapReply<BlobStringReply, StreamMessagesRawReply>;
// name: RedisArgument;
// messages: StreamMessagesReply;
// }> | null;
// export function transformStreamsMessagesReply(reply: Array<any> | null): StreamsMessagesReply | null { // export function transformStreamsMessagesReply(reply: Array<any> | null): StreamsMessagesReply | null {
// if (reply === null) return null; // if (reply === null) return null;
@@ -118,21 +120,25 @@ export type SortedSetSide = 'MIN' | 'MAX';
export const transformSortedSetReply = { export const transformSortedSetReply = {
2: (reply: ArrayReply<BlobStringReply>) => { 2: (reply: ArrayReply<BlobStringReply>) => {
const members = []; const inferred = reply as unknown as UnwrapReply<typeof reply>,
for (let i = 0; i < reply.length; i += 2) { members = [];
for (let i = 0; i < inferred.length; i += 2) {
members.push({ members.push({
value: reply[i], value: inferred[i],
score: transformDoubleReply[2](reply[i + 1]) score: transformDoubleReply[2](inferred[i + 1])
}); });
} }
return members; return members;
}, },
3: (reply: ArrayReply<TuplesReply<[BlobStringReply, DoubleReply]>>) => { 3: (reply: ArrayReply<TuplesReply<[BlobStringReply, DoubleReply]>>) => {
return reply.map(([value, score]) => ({ return (reply as unknown as UnwrapReply<typeof reply>).map(member => {
value, const [value, score] = member as unknown as UnwrapReply<typeof member>;
score return {
})); value,
score
};
});
} }
} }

View File

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