import { TransformArgumentsReply } from '.'; export function transformReplyNumber(reply: number): number { return reply; } export function transformReplyNumberNull(reply: number | null): number | null { return reply; } export function transformReplyNumberArray(reply: Array): Array { return reply; } export function transformReplyNumberNullArray(reply: Array): Array { return reply; } export function transformReplyString(reply: string): string { return reply; } export function transformReplyStringNull(reply: string | null): string | null { return reply; } export function transformReplyStringArray(reply: Array): Array { return reply; } export function transformReplyStringArrayNull(reply: Array | null): Array | null { return reply; } export function transformReplyStringNullArray(reply: Array): Array { return reply; } export function transformReplyBoolean(reply: number): boolean { return reply === 1; } export function transformReplyBooleanArray(reply: Array): Array { return reply.map(transformReplyBoolean); } export type BitValue = 0 | 1; export function transformReplyBit(reply: BitValue): BitValue { return reply; } export function transformReplyVoid(): void {} export interface ScanOptions { MATCH?: string; COUNT?: number; } export function pushScanArguments(args: Array, cursor: number, options?: ScanOptions): Array { args.push(cursor.toString()); if (options?.MATCH) { args.push('MATCH', options.MATCH); } if (options?.COUNT) { args.push('COUNT', options.COUNT.toString()); } return args; } export function transformReplyNumberInfinity(reply: string): number { switch (reply) { case '+inf': return Infinity; case '-inf': return -Infinity; default: return Number(reply); } } export function transformReplyNumberInfinityArray(reply: Array): Array { return reply.map(transformReplyNumberInfinity); } export function transformReplyNumberInfinityNull(reply: string | null): number | null { if (reply === null) return null; return transformReplyNumberInfinity(reply); } export function transformReplyNumberInfinityNullArray(reply: Array): Array { return reply.map(transformReplyNumberInfinityNull); } export function transformArgumentNumberInfinity(num: number): string { switch (num) { case Infinity: return '+inf'; case -Infinity: return '-inf'; default: return num.toString(); } } export interface TuplesObject { [field: string]: string; } export function transformReplyTuples(reply: Array): TuplesObject { const message = Object.create(null); for (let i = 0; i < reply.length; i += 2) { message[reply[i]] = reply[i + 1]; } return message; } export interface StreamMessageReply { id: string; message: TuplesObject; } export type StreamMessagesReply = Array; export function transformReplyStreamMessages(reply: Array): StreamMessagesReply { const messages = []; for (const [id, message] of reply) { messages.push({ id, message: transformReplyTuples(message) }); } return messages; } export type StreamsMessagesReply = Array<{ name: string; messages: StreamMessagesReply; }> | null; export function transformReplyStreamsMessages(reply: Array | null): StreamsMessagesReply | null { if (reply === null) return null; return reply.map(([name, rawMessages]) => ({ name, messages: transformReplyStreamMessages(rawMessages) })); } export interface ZMember { score: number; value: string; } export function transformReplySortedSetWithScores(reply: Array): Array { const members = []; for (let i = 0; i < reply.length; i += 2) { members.push({ value: reply[i], score: transformReplyNumberInfinity(reply[i + 1]) }); } return members; } type GeoCountArgument = number | { value: number; ANY?: true }; export function pushGeoCountArgument(args: Array, count: GeoCountArgument | undefined): Array { if (typeof count === 'number') { args.push('COUNT', count.toString()); } else if (count) { args.push('COUNT', count.value.toString()); if (count.ANY) { args.push('ANY'); } } return args; } export type GeoUnits = 'm' | 'km' | 'mi' | 'ft'; export interface GeoCoordinates { longitude: string | number; latitude: string | number; } type GeoSearchFromMember = string; export type GeoSearchFrom = GeoSearchFromMember | GeoCoordinates; interface GeoSearchByRadius { radius: number; unit: GeoUnits; } interface GeoSearchByBox { width: number; height: number; unit: GeoUnits; } export type GeoSearchBy = GeoSearchByRadius | GeoSearchByBox; export interface GeoSearchOptions { SORT?: 'ASC' | 'DESC'; COUNT?: GeoCountArgument; } export function pushGeoSearchArguments( args: Array, key: string, from: GeoSearchFrom, by: GeoSearchBy, options?: GeoSearchOptions ): Array { args.push(key); if (typeof from === 'string') { args.push('FROMMEMBER', from); } else { args.push('FROMLONLAT', from.longitude.toString(), from.latitude.toString()); } if ('radius' in by) { args.push('BYRADIUS', by.radius.toString()); } else { args.push('BYBOX', by.width.toString(), by.height.toString()); } if (by.unit) { args.push(by.unit); } if (options?.SORT) { args.push(options?.SORT); } pushGeoCountArgument(args, options?.COUNT); return args; } export enum GeoReplyWith { DISTANCE = 'WITHDIST', HASH = 'WITHHASH', COORDINATES = 'WITHCOORD' } export interface GeoReplyWithMember { member: string; distance?: number; hash?: string; coordinates?: { longitude: string; latitude: string; }; } export function transformGeoMembersWithReply(reply: Array>, replyWith: Array): Array { const replyWithSet = new Set(replyWith); let index = 0; const distanceIndex = replyWithSet.has(GeoReplyWith.DISTANCE) && ++index, hashIndex = replyWithSet.has(GeoReplyWith.HASH) && ++index, coordinatesIndex = replyWithSet.has(GeoReplyWith.COORDINATES) && ++index; return reply.map(member => { const transformedMember: GeoReplyWithMember = { member: member[0] }; if (distanceIndex) { transformedMember.distance = member[distanceIndex]; } if (hashIndex) { transformedMember.hash = member[hashIndex]; } if (coordinatesIndex) { const [longitude, latitude] = member[coordinatesIndex]; transformedMember.coordinates = { longitude, latitude }; } return transformedMember; }); } export function transformEXAT(EXAT: number | Date): string { return (typeof EXAT === 'number' ? EXAT : Math.floor(EXAT.getTime() / 1000)).toString(); } export function transformPXAT(PXAT: number | Date): string { return (typeof PXAT === 'number' ? PXAT : PXAT.getTime()).toString(); } export interface EvalOptions { keys?: Array; arguments?: Array; } export function pushEvalArguments(args: Array, options?: EvalOptions): Array { if (options?.keys) { args.push( options.keys.length.toString(), ...options.keys ); } else { args.push('0'); } if (options?.arguments) { args.push(...options.arguments); } return args; } export type StringTuplesArguments = Array<[string, string]> | Array | Record; export function pushStringTuplesArguments(args: Array, tuples: StringTuplesArguments): Array { if (Array.isArray(tuples)) { args.push(...tuples.flat()); } else { for (const key of Object.keys(tuples)) { args.push(key, tuples[key]); } } return args; } export function pushVerdictArguments(args: TransformArgumentsReply, value: string | Array): TransformArgumentsReply { if (typeof value === 'string') { args.push(value); } else { args.push(...value); } return args; } export function pushVerdictArgument(args: TransformArgumentsReply, value: string | Array): TransformArgumentsReply { if (typeof value === 'string') { args.push('1', value); } else { args.push(value.length.toString(), ...value); } return args; } export function pushOptionalVerdictArgument(args: TransformArgumentsReply, name: string, value: undefined | string | Array): TransformArgumentsReply { if (value === undefined) return args; args.push(name); return pushVerdictArgument(args, value); }