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

fix: fix various command import issues (#2944)

* fix: fix various command import issues

there was some sort of a circular dependency
in <module>/lib/commands/index.ts for various modules

fixes #2937 fixes #2941

* remove redundant definition
This commit is contained in:
Nikolay Karadzhov
2025-05-07 16:10:35 +03:00
committed by GitHub
parent bc4b2101ee
commit 7b737821b2
42 changed files with 416 additions and 391 deletions

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, Command, UnwrapReply, NullReply, NumberReply, TuplesToMapReply, Resp2Reply, SimpleStringReply, TypeMapping } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, Command, UnwrapReply, NullReply, NumberReply, TuplesToMapReply, Resp2Reply, SimpleStringReply, TypeMapping } from '@redis/client/dist/lib/RESP/types';
import { transformInfoV2Reply } from '.'; import { transformInfoV2Reply } from './helpers';
export type BfInfoReplyMap = TuplesToMapReply<[ export type BfInfoReplyMap = TuplesToMapReply<[
[SimpleStringReply<'Capacity'>, NumberReply], [SimpleStringReply<'Capacity'>, NumberReply],

View File

@@ -0,0 +1,29 @@
import { RESP_TYPES, TypeMapping } from "@redis/client";
export function transformInfoV2Reply<T>(reply: Array<any>, typeMapping?: TypeMapping): T {
const mapType = typeMapping ? typeMapping[RESP_TYPES.MAP] : undefined;
switch (mapType) {
case Array: {
return reply as unknown as T;
}
case Map: {
const ret = new Map<string, any>();
for (let i = 0; i < reply.length; i += 2) {
ret.set(reply[i].toString(), reply[i + 1]);
}
return ret as unknown as T;
}
default: {
const ret = Object.create(null);
for (let i = 0; i < reply.length; i += 2) {
ret[reply[i].toString()] = reply[i + 1];
}
return ret as unknown as T;
}
}
}

View File

@@ -1,4 +1,4 @@
import type { RedisCommands, TypeMapping } from '@redis/client/dist/lib/RESP/types'; import type { RedisCommands } from '@redis/client/dist/lib/RESP/types';
import ADD from './ADD'; import ADD from './ADD';
import CARD from './CARD'; import CARD from './CARD';
@@ -10,7 +10,8 @@ import MADD from './MADD';
import MEXISTS from './MEXISTS'; import MEXISTS from './MEXISTS';
import RESERVE from './RESERVE'; import RESERVE from './RESERVE';
import SCANDUMP from './SCANDUMP'; import SCANDUMP from './SCANDUMP';
import { RESP_TYPES } from '@redis/client';
export * from './helpers';
export default { export default {
ADD, ADD,
@@ -34,31 +35,3 @@ export default {
SCANDUMP, SCANDUMP,
scanDump: SCANDUMP scanDump: SCANDUMP
} as const satisfies RedisCommands; } as const satisfies RedisCommands;
export function transformInfoV2Reply<T>(reply: Array<any>, typeMapping?: TypeMapping): T {
const mapType = typeMapping ? typeMapping[RESP_TYPES.MAP] : undefined;
switch (mapType) {
case Array: {
return reply as unknown as T;
}
case Map: {
const ret = new Map<string, any>();
for (let i = 0; i < reply.length; i += 2) {
ret.set(reply[i].toString(), reply[i + 1]);
}
return ret as unknown as T;
}
default: {
const ret = Object.create(null);
for (let i = 0; i < reply.length; i += 2) {
ret[reply[i].toString()] = reply[i + 1];
}
return ret as unknown as T;
}
}
}

View File

@@ -704,7 +704,8 @@ export default class RedisClient<
const reply = await this.sendCommand(parser.redisArgs, commandOptions); const reply = await this.sendCommand(parser.redisArgs, commandOptions);
if (transformReply) { if (transformReply) {
return transformReply(reply, parser.preserve, commandOptions?.typeMapping); const res = transformReply(reply, parser.preserve, commandOptions?.typeMapping);
return res
} }
return reply; return reply;

View File

@@ -1,5 +1,5 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisJSON, transformRedisJsonArgument } from '.'; import { RedisJSON, transformRedisJsonArgument } from './helpers';
import { RedisArgument, NumberReply, ArrayReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, NumberReply, ArrayReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types';
export default { export default {

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, NumberReply, ArrayReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, NumberReply, ArrayReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisJSON, transformRedisJsonArgument } from '.'; import { RedisJSON, transformRedisJsonArgument } from './helpers';
export interface JsonArrIndexOptions { export interface JsonArrIndexOptions {
range?: { range?: {

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, NumberReply, ArrayReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, NumberReply, ArrayReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisJSON, transformRedisJsonArgument } from '.'; import { RedisJSON, transformRedisJsonArgument } from './helpers';
export default { export default {
IS_READ_ONLY: false, IS_READ_ONLY: false,

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, ArrayReply, NullReply, BlobStringReply, Command, UnwrapReply } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, ArrayReply, NullReply, BlobStringReply, Command, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
import { isArrayReply } from '@redis/client/dist/lib/commands/generic-transformers'; import { isArrayReply } from '@redis/client/dist/lib/commands/generic-transformers';
import { transformRedisJsonNullReply } from '.'; import { transformRedisJsonNullReply } from './helpers';
export interface RedisArrPopOptions { export interface RedisArrPopOptions {
path: RedisArgument; path: RedisArgument;

View File

@@ -34,5 +34,11 @@ describe('JSON.GET', () => {
await client.json.get('key'), await client.json.get('key'),
null null
); );
await client.json.set('noderedis:users:1', '$', { name: 'Alice', age: 32, })
const res = await client.json.get('noderedis:users:1');
assert.equal(typeof res, 'object')
assert.deepEqual(res, { name: 'Alice', age: 32, })
}, GLOBAL.SERVERS.OPEN); }, GLOBAL.SERVERS.OPEN);
}); });

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { transformRedisJsonNullReply } from '.'; import { transformRedisJsonNullReply } from './helpers';
export interface JsonGetOptions { export interface JsonGetOptions {
path?: RedisVariadicArgument; path?: RedisVariadicArgument;
@@ -9,12 +9,16 @@ export interface JsonGetOptions {
export default { export default {
IS_READ_ONLY: false, IS_READ_ONLY: false,
parseCommand(parser: CommandParser, key: RedisArgument, options?: JsonGetOptions) { parseCommand(
parser: CommandParser,
key: RedisArgument,
options?: JsonGetOptions
) {
parser.push('JSON.GET'); parser.push('JSON.GET');
parser.pushKey(key); parser.pushKey(key);
if (options?.path !== undefined) { if (options?.path !== undefined) {
parser.pushVariadic(options.path) parser.pushVariadic(options.path);
} }
}, },
transformReply: transformRedisJsonNullReply transformReply: transformRedisJsonNullReply
} as const satisfies Command; } as const satisfies Command;

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { SimpleStringReply, Command, RedisArgument } from '@redis/client/dist/lib/RESP/types'; import { SimpleStringReply, Command, RedisArgument } from '@redis/client/dist/lib/RESP/types';
import { RedisJSON, transformRedisJsonArgument } from '.'; import { RedisJSON, transformRedisJsonArgument } from './helpers';
export default { export default {
IS_READ_ONLY: false, IS_READ_ONLY: false,

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, UnwrapReply, ArrayReply, NullReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, UnwrapReply, ArrayReply, NullReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types';
import { transformRedisJsonNullReply } from '.'; import { transformRedisJsonNullReply } from './helpers';
export default { export default {
IS_READ_ONLY: true, IS_READ_ONLY: true,

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisJSON, transformRedisJsonArgument } from '.'; import { RedisJSON, transformRedisJsonArgument } from './helpers';
export interface JsonMSetItem { export interface JsonMSetItem {
key: RedisArgument; key: RedisArgument;

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, SimpleStringReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, SimpleStringReply, NullReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisJSON, transformRedisJsonArgument } from '.'; import { RedisJSON, transformRedisJsonArgument } from './helpers';
export interface JsonSetOptions { export interface JsonSetOptions {
condition?: 'NX' | 'XX'; condition?: 'NX' | 'XX';

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, Command, NullReply, NumberReply, ArrayReply } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, Command, NullReply, NumberReply, ArrayReply } from '@redis/client/dist/lib/RESP/types';
import { transformRedisJsonArgument } from '.'; import { transformRedisJsonArgument } from './helpers';
export interface JsonStrAppendOptions { export interface JsonStrAppendOptions {
path?: RedisArgument; path?: RedisArgument;

View File

@@ -0,0 +1,22 @@
import { isNullReply } from "@redis/client/dist/lib/commands/generic-transformers";
import { BlobStringReply, NullReply, UnwrapReply } from "@redis/client/dist/lib/RESP/types";
export function transformRedisJsonNullReply(json: NullReply | BlobStringReply): NullReply | RedisJSON {
console.log('transformRedisJsonNullReply', json)
return isNullReply(json) ? json : transformRedisJsonReply(json);
}
export type RedisJSON = null | boolean | number | string | Date | Array<RedisJSON> | {
[key: string]: RedisJSON;
[key: number]: RedisJSON;
};
export function transformRedisJsonArgument(json: RedisJSON): string {
return JSON.stringify(json);
}
export function transformRedisJsonReply(json: BlobStringReply): RedisJSON {
const res = JSON.parse((json as unknown as UnwrapReply<typeof json>).toString());
console.log('transformRedisJsonReply', json, res)
return res;
}

View File

@@ -1,4 +1,3 @@
import { BlobStringReply, NullReply, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
import ARRAPPEND from './ARRAPPEND'; import ARRAPPEND from './ARRAPPEND';
import ARRINDEX from './ARRINDEX'; import ARRINDEX from './ARRINDEX';
import ARRINSERT from './ARRINSERT'; import ARRINSERT from './ARRINSERT';
@@ -23,7 +22,8 @@ import STRAPPEND from './STRAPPEND';
import STRLEN from './STRLEN'; import STRLEN from './STRLEN';
import TOGGLE from './TOGGLE'; import TOGGLE from './TOGGLE';
import TYPE from './TYPE'; import TYPE from './TYPE';
import { isNullReply } from '@redis/client/dist/lib/commands/generic-transformers';
export * from './helpers';
export default { export default {
ARRAPPEND, ARRAPPEND,
@@ -82,19 +82,3 @@ export default {
type: TYPE type: TYPE
}; };
export type RedisJSON = null | boolean | number | string | Date | Array<RedisJSON> | {
[key: string]: RedisJSON;
[key: number]: RedisJSON;
};
export function transformRedisJsonArgument(json: RedisJSON): string {
return JSON.stringify(json);
}
export function transformRedisJsonReply(json: BlobStringReply): RedisJSON {
return JSON.parse((json as unknown as UnwrapReply<typeof json>).toString());
}
export function transformRedisJsonNullReply(json: NullReply | BlobStringReply): NullReply | RedisJSON {
return isNullReply(json) ? json : transformRedisJsonReply(json);
}

View File

@@ -1,7 +1,7 @@
import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils'; import testUtils, { GLOBAL } from '../test-utils';
import ADD from './ADD'; import ADD from './ADD';
import { TIME_SERIES_ENCODING, TIME_SERIES_DUPLICATE_POLICIES } from '.'; import { TIME_SERIES_ENCODING, TIME_SERIES_DUPLICATE_POLICIES } from './helpers';
import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
describe('TS.ADD', () => { describe('TS.ADD', () => {

View File

@@ -11,7 +11,7 @@ import {
parseLabelsArgument, parseLabelsArgument,
Timestamp, Timestamp,
parseIgnoreArgument parseIgnoreArgument
} from '.'; } from './helpers';
export interface TsIgnoreOptions { export interface TsIgnoreOptions {
maxTimeDiff: number; maxTimeDiff: number;

View File

@@ -1,7 +1,7 @@
import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils'; import testUtils, { GLOBAL } from '../test-utils';
import ALTER from './ALTER'; import ALTER from './ALTER';
import { TIME_SERIES_DUPLICATE_POLICIES } from '.'; import { TIME_SERIES_DUPLICATE_POLICIES } from './helpers';
import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
describe('TS.ALTER', () => { describe('TS.ALTER', () => {

View File

@@ -1,7 +1,8 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
import { TsCreateOptions } from './CREATE'; import { TsCreateOptions } from './CREATE';
import { parseRetentionArgument, parseChunkSizeArgument, parseDuplicatePolicy, parseLabelsArgument, parseIgnoreArgument } from '.'; import { parseRetentionArgument, parseChunkSizeArgument, parseDuplicatePolicy, parseLabelsArgument, parseIgnoreArgument } from './helpers';
export type TsAlterOptions = Pick<TsCreateOptions, 'RETENTION' | 'CHUNK_SIZE' | 'DUPLICATE_POLICY' | 'LABELS' | 'IGNORE'>; export type TsAlterOptions = Pick<TsCreateOptions, 'RETENTION' | 'CHUNK_SIZE' | 'DUPLICATE_POLICY' | 'LABELS' | 'IGNORE'>;

View File

@@ -1,7 +1,7 @@
import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils'; import testUtils, { GLOBAL } from '../test-utils';
import CREATE from './CREATE'; import CREATE from './CREATE';
import { TIME_SERIES_ENCODING, TIME_SERIES_DUPLICATE_POLICIES } from '.'; import { TIME_SERIES_ENCODING, TIME_SERIES_DUPLICATE_POLICIES } from './helpers';
import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
describe('TS.CREATE', () => { describe('TS.CREATE', () => {

View File

@@ -10,7 +10,7 @@ import {
Labels, Labels,
parseLabelsArgument, parseLabelsArgument,
parseIgnoreArgument parseIgnoreArgument
} from '.'; } from './helpers';
import { TsIgnoreOptions } from './ADD'; import { TsIgnoreOptions } from './ADD';
export interface TsCreateOptions { export interface TsCreateOptions {

View File

@@ -1,5 +1,5 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Timestamp, transformTimestampArgument } from '.'; import { Timestamp, transformTimestampArgument } from './helpers';
import { RedisArgument, NumberReply, Command, } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, NumberReply, Command, } from '@redis/client/dist/lib/RESP/types';
export default { export default {

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
import { Timestamp, transformTimestampArgument, parseRetentionArgument, parseChunkSizeArgument, Labels, parseLabelsArgument, parseIgnoreArgument } from '.'; import { Timestamp, transformTimestampArgument, parseRetentionArgument, parseChunkSizeArgument, Labels, parseLabelsArgument, parseIgnoreArgument } from './helpers';
import { TsIgnoreOptions } from './ADD'; import { TsIgnoreOptions } from './ADD';
export interface TsIncrByOptions { export interface TsIncrByOptions {

View File

@@ -1,5 +1,5 @@
import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
import { TIME_SERIES_DUPLICATE_POLICIES } from '.'; import { TIME_SERIES_DUPLICATE_POLICIES } from './helpers';
import testUtils, { GLOBAL } from '../test-utils'; import testUtils, { GLOBAL } from '../test-utils';
import INFO, { InfoReply } from './INFO'; import INFO, { InfoReply } from './INFO';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE'; import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { ArrayReply, BlobStringReply, Command, DoubleReply, NumberReply, ReplyUnion, SimpleStringReply, TypeMapping } from "@redis/client/dist/lib/RESP/types"; import { ArrayReply, BlobStringReply, Command, DoubleReply, NumberReply, ReplyUnion, SimpleStringReply, TypeMapping } from "@redis/client/dist/lib/RESP/types";
import { TimeSeriesDuplicatePolicies } from "."; import { TimeSeriesDuplicatePolicies } from "./helpers";
import { TimeSeriesAggregationType } from "./CREATERULE"; import { TimeSeriesAggregationType } from "./CREATERULE";
import { transformDoubleReply } from '@redis/client/dist/lib/commands/generic-transformers'; import { transformDoubleReply } from '@redis/client/dist/lib/commands/generic-transformers';

View File

@@ -1,5 +1,5 @@
import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
import { TIME_SERIES_DUPLICATE_POLICIES } from '.'; import { TIME_SERIES_DUPLICATE_POLICIES } from './helpers';
import testUtils, { GLOBAL } from '../test-utils'; import testUtils, { GLOBAL } from '../test-utils';
import { assertInfo } from './INFO.spec'; import { assertInfo } from './INFO.spec';
import INFO_DEBUG from './INFO_DEBUG'; import INFO_DEBUG from './INFO_DEBUG';

View File

@@ -1,5 +1,5 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Timestamp, transformTimestampArgument } from '.'; import { Timestamp, transformTimestampArgument } from './helpers';
import { ArrayReply, NumberReply, SimpleErrorReply, Command } from '@redis/client/dist/lib/RESP/types'; import { ArrayReply, NumberReply, SimpleErrorReply, Command } from '@redis/client/dist/lib/RESP/types';
export interface TsMAddSample { export interface TsMAddSample {

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, BlobStringReply, ArrayReply, Resp2Reply, MapReply, TuplesReply, TypeMapping } from '@redis/client/dist/lib/RESP/types'; import { Command, BlobStringReply, ArrayReply, Resp2Reply, MapReply, TuplesReply, TypeMapping } from '@redis/client/dist/lib/RESP/types';
import { resp2MapToValue, resp3MapToValue, SampleRawReply, transformSampleReply } from '.'; import { resp2MapToValue, resp3MapToValue, SampleRawReply, transformSampleReply } from './helpers';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
export interface TsMGetOptions { export interface TsMGetOptions {

View File

@@ -2,7 +2,7 @@ import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, BlobStringReply, NullReply } from '@redis/client/dist/lib/RESP/types'; import { Command, BlobStringReply, NullReply } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { TsMGetOptions, parseLatestArgument, parseFilterArgument } from './MGET'; import { TsMGetOptions, parseLatestArgument, parseFilterArgument } from './MGET';
import { parseSelectedLabelsArguments } from '.'; import { parseSelectedLabelsArguments } from './helpers';
import { createTransformMGetLabelsReply } from './MGET_WITHLABELS'; import { createTransformMGetLabelsReply } from './MGET_WITHLABELS';
export default { export default {

View File

@@ -2,7 +2,7 @@ import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, BlobStringReply, ArrayReply, Resp2Reply, MapReply, TuplesReply, TypeMapping } from '@redis/client/dist/lib/RESP/types'; import { Command, BlobStringReply, ArrayReply, Resp2Reply, MapReply, TuplesReply, TypeMapping } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { TsMGetOptions, parseLatestArgument, parseFilterArgument } from './MGET'; import { TsMGetOptions, parseLatestArgument, parseFilterArgument } from './MGET';
import { RawLabelValue, resp2MapToValue, resp3MapToValue, SampleRawReply, transformRESP2Labels, transformSampleReply } from '.'; import { RawLabelValue, resp2MapToValue, resp3MapToValue, SampleRawReply, transformRESP2Labels, transformSampleReply } from './helpers';
export interface TsMGetWithLabelsOptions extends TsMGetOptions { export interface TsMGetWithLabelsOptions extends TsMGetOptions {
SELECTED_LABELS?: RedisVariadicArgument; SELECTED_LABELS?: RedisVariadicArgument;

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument } from '@redis/client/dist/lib/RESP/types'; import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.'; import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from './helpers';
import { TsRangeOptions, parseRangeArguments } from './RANGE'; import { TsRangeOptions, parseRangeArguments } from './RANGE';
import { parseFilterArgument } from './MGET'; import { parseFilterArgument } from './MGET';

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument, TuplesToMapReply, UnwrapReply } from '@redis/client/dist/lib/RESP/types'; import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument, TuplesToMapReply, UnwrapReply } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.'; import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from './helpers';
import { TsRangeOptions, parseRangeArguments } from './RANGE'; import { TsRangeOptions, parseRangeArguments } from './RANGE';
import { parseFilterArgument } from './MGET'; import { parseFilterArgument } from './MGET';

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, NullReply, RedisArgument } from '@redis/client/dist/lib/RESP/types'; import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, NullReply, RedisArgument } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { parseSelectedLabelsArguments, resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformRESP2Labels, transformSamplesReply } from '.'; import { parseSelectedLabelsArguments, resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformRESP2Labels, transformSamplesReply } from './helpers';
import { TsRangeOptions, parseRangeArguments } from './RANGE'; import { TsRangeOptions, parseRangeArguments } from './RANGE';
import { parseFilterArgument } from './MGET'; import { parseFilterArgument } from './MGET';

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, ArrayReply, BlobStringReply, MapReply, TuplesReply, RedisArgument, NullReply } from '@redis/client/dist/lib/RESP/types'; import { Command, ArrayReply, BlobStringReply, MapReply, TuplesReply, RedisArgument, NullReply } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { parseSelectedLabelsArguments, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.'; import { parseSelectedLabelsArguments, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from './helpers';
import { TsRangeOptions, parseRangeArguments } from './RANGE'; import { TsRangeOptions, parseRangeArguments } from './RANGE';
import { extractResp3MRangeSources, parseGroupByArguments, TsMRangeGroupBy, TsMRangeGroupByRawMetadataReply3 } from './MRANGE_GROUPBY'; import { extractResp3MRangeSources, parseGroupByArguments, TsMRangeGroupBy, TsMRangeGroupByRawMetadataReply3 } from './MRANGE_GROUPBY';
import { parseFilterArgument } from './MGET'; import { parseFilterArgument } from './MGET';

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, UnwrapReply, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument } from '@redis/client/dist/lib/RESP/types'; import { Command, UnwrapReply, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.'; import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from './helpers';
import { TsRangeOptions, parseRangeArguments } from './RANGE'; import { TsRangeOptions, parseRangeArguments } from './RANGE';
import { parseFilterArgument } from './MGET'; import { parseFilterArgument } from './MGET';

View File

@@ -1,7 +1,7 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument } from '@redis/client/dist/lib/RESP/types'; import { Command, ArrayReply, BlobStringReply, Resp2Reply, MapReply, TuplesReply, TypeMapping, RedisArgument } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformRESP2LabelsWithSources, transformSamplesReply } from '.'; import { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformRESP2LabelsWithSources, transformSamplesReply } from './helpers';
import { TsRangeOptions, parseRangeArguments } from './RANGE'; import { TsRangeOptions, parseRangeArguments } from './RANGE';
import { extractResp3MRangeSources, parseGroupByArguments, TsMRangeGroupBy, TsMRangeGroupByRawMetadataReply3 } from './MRANGE_GROUPBY'; import { extractResp3MRangeSources, parseGroupByArguments, TsMRangeGroupBy, TsMRangeGroupByRawMetadataReply3 } from './MRANGE_GROUPBY';
import { parseFilterArgument } from './MGET'; import { parseFilterArgument } from './MGET';

View File

@@ -1,6 +1,6 @@
import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RedisArgument, Command } from '@redis/client/dist/lib/RESP/types'; import { RedisArgument, Command } from '@redis/client/dist/lib/RESP/types';
import { Timestamp, transformTimestampArgument, SamplesRawReply, transformSamplesReply } from '.'; import { Timestamp, transformTimestampArgument, SamplesRawReply, transformSamplesReply } from './helpers';
import { TimeSeriesAggregationType } from './CREATERULE'; import { TimeSeriesAggregationType } from './CREATERULE';
import { Resp2Reply } from '@redis/client/dist/lib/RESP/types'; import { Resp2Reply } from '@redis/client/dist/lib/RESP/types';

View File

@@ -0,0 +1,306 @@
import { CommandParser } from "@redis/client/dist/lib/client/parser";
import { TsIgnoreOptions } from "./ADD";
import { ArrayReply, BlobStringReply, DoubleReply, MapReply, NullReply, NumberReply, ReplyUnion, Resp2Reply, RespType, TuplesReply, TypeMapping, UnwrapReply } from "@redis/client/dist/lib/RESP/types";
import { RESP_TYPES } from "@redis/client";
import { RedisVariadicArgument } from "@redis/client/dist/lib/commands/generic-transformers";
export function parseIgnoreArgument(parser: CommandParser, ignore?: TsIgnoreOptions) {
if (ignore !== undefined) {
parser.push('IGNORE', ignore.maxTimeDiff.toString(), ignore.maxValDiff.toString());
}
}
export function parseRetentionArgument(parser: CommandParser, retention?: number) {
if (retention !== undefined) {
parser.push('RETENTION', retention.toString());
}
}
export const TIME_SERIES_ENCODING = {
COMPRESSED: 'COMPRESSED',
UNCOMPRESSED: 'UNCOMPRESSED'
} as const;
export type TimeSeriesEncoding = typeof TIME_SERIES_ENCODING[keyof typeof TIME_SERIES_ENCODING];
export function parseEncodingArgument(parser: CommandParser, encoding?: TimeSeriesEncoding) {
if (encoding !== undefined) {
parser.push('ENCODING', encoding);
}
}
export function parseChunkSizeArgument(parser: CommandParser, chunkSize?: number) {
if (chunkSize !== undefined) {
parser.push('CHUNK_SIZE', chunkSize.toString());
}
}
export const TIME_SERIES_DUPLICATE_POLICIES = {
BLOCK: 'BLOCK',
FIRST: 'FIRST',
LAST: 'LAST',
MIN: 'MIN',
MAX: 'MAX',
SUM: 'SUM'
} as const;
export type TimeSeriesDuplicatePolicies = typeof TIME_SERIES_DUPLICATE_POLICIES[keyof typeof TIME_SERIES_DUPLICATE_POLICIES];
export function parseDuplicatePolicy(parser: CommandParser, duplicatePolicy?: TimeSeriesDuplicatePolicies) {
if (duplicatePolicy !== undefined) {
parser.push('DUPLICATE_POLICY', duplicatePolicy);
}
}
export type Timestamp = number | Date | string;
export function transformTimestampArgument(timestamp: Timestamp): string {
if (typeof timestamp === 'string') return timestamp;
return (
typeof timestamp === 'number' ?
timestamp :
timestamp.getTime()
).toString();
}
export type Labels = {
[label: string]: string;
};
export function parseLabelsArgument(parser: CommandParser, labels?: Labels) {
if (labels) {
parser.push('LABELS');
for (const [label, value] of Object.entries(labels)) {
parser.push(label, value);
}
}
}
export type SampleRawReply = TuplesReply<[timestamp: NumberReply, value: DoubleReply]>;
export const transformSampleReply = {
2(reply: Resp2Reply<SampleRawReply>) {
const [ timestamp, value ] = reply as unknown as UnwrapReply<typeof reply>;
return {
timestamp,
value: Number(value) // TODO: use double type mapping instead
};
},
3(reply: SampleRawReply) {
const [ timestamp, value ] = reply as unknown as UnwrapReply<typeof reply>;
return {
timestamp,
value
};
}
};
export type SamplesRawReply = ArrayReply<SampleRawReply>;
export const transformSamplesReply = {
2(reply: Resp2Reply<SamplesRawReply>) {
return (reply as unknown as UnwrapReply<typeof reply>)
.map(sample => transformSampleReply[2](sample));
},
3(reply: SamplesRawReply) {
return (reply as unknown as UnwrapReply<typeof reply>)
.map(sample => transformSampleReply[3](sample));
}
};
// TODO: move to @redis/client?
export function resp2MapToValue<
RAW_VALUE extends TuplesReply<[key: BlobStringReply, ...rest: Array<ReplyUnion>]>,
TRANSFORMED
>(
wrappedReply: ArrayReply<RAW_VALUE>,
parseFunc: (rawValue: UnwrapReply<RAW_VALUE>) => TRANSFORMED,
typeMapping?: TypeMapping
): MapReply<BlobStringReply, TRANSFORMED> {
const reply = wrappedReply as unknown as UnwrapReply<typeof wrappedReply>;
switch (typeMapping?.[RESP_TYPES.MAP]) {
case Map: {
const ret = new Map<string, TRANSFORMED>();
for (const wrappedTuple of reply) {
const tuple = wrappedTuple as unknown as UnwrapReply<typeof wrappedTuple>;
const key = tuple[0] as unknown as UnwrapReply<typeof tuple[0]>;
ret.set(key.toString(), parseFunc(tuple));
}
return ret as never;
}
case Array: {
for (const wrappedTuple of reply) {
const tuple = wrappedTuple as unknown as UnwrapReply<typeof wrappedTuple>;
(tuple[1] as unknown as TRANSFORMED) = parseFunc(tuple);
}
return reply as never;
}
default: {
const ret: Record<string, TRANSFORMED> = Object.create(null);
for (const wrappedTuple of reply) {
const tuple = wrappedTuple as unknown as UnwrapReply<typeof wrappedTuple>;
const key = tuple[0] as unknown as UnwrapReply<typeof tuple[0]>;
ret[key.toString()] = parseFunc(tuple);
}
return ret as never;
}
}
}
export function resp3MapToValue<
RAW_VALUE extends RespType<any, any, any, any>, // TODO: simplify types
TRANSFORMED
>(
wrappedReply: MapReply<BlobStringReply, RAW_VALUE>,
parseFunc: (rawValue: UnwrapReply<RAW_VALUE>) => TRANSFORMED
): MapReply<BlobStringReply, TRANSFORMED> {
const reply = wrappedReply as unknown as UnwrapReply<typeof wrappedReply>;
if (reply instanceof Array) {
for (let i = 1; i < reply.length; i += 2) {
(reply[i] as unknown as TRANSFORMED) = parseFunc(reply[i] as unknown as UnwrapReply<RAW_VALUE>);
}
} else if (reply instanceof Map) {
for (const [key, value] of reply.entries()) {
(reply as unknown as Map<BlobStringReply, TRANSFORMED>).set(
key,
parseFunc(value as unknown as UnwrapReply<typeof value>)
);
}
} else {
for (const [key, value] of Object.entries(reply)) {
(reply[key] as unknown as TRANSFORMED) = parseFunc(value as unknown as UnwrapReply<typeof value>);
}
}
return reply as never;
}
export function parseSelectedLabelsArguments(
parser: CommandParser,
selectedLabels: RedisVariadicArgument
) {
parser.push('SELECTED_LABELS');
parser.pushVariadic(selectedLabels);
}
export type RawLabelValue = BlobStringReply | NullReply;
export type RawLabels<T extends RawLabelValue> = ArrayReply<TuplesReply<[
label: BlobStringReply,
value: T
]>>;
export function transformRESP2Labels<T extends RawLabelValue>(
labels: RawLabels<T>,
typeMapping?: TypeMapping
): MapReply<BlobStringReply, T> {
const unwrappedLabels = labels as unknown as UnwrapReply<typeof labels>;
switch (typeMapping?.[RESP_TYPES.MAP]) {
case Map:
const map = new Map<string, T>();
for (const tuple of unwrappedLabels) {
const [key, value] = tuple as unknown as UnwrapReply<typeof tuple>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
map.set(unwrappedKey.toString(), value);
}
return map as never;
case Array:
return unwrappedLabels.flat() as never;
case Object:
default:
const labelsObject: Record<string, T> = Object.create(null);
for (const tuple of unwrappedLabels) {
const [key, value] = tuple as unknown as UnwrapReply<typeof tuple>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
labelsObject[unwrappedKey.toString()] = value;
}
return labelsObject as never;
}
}
export function transformRESP2LabelsWithSources<T extends RawLabelValue>(
labels: RawLabels<T>,
typeMapping?: TypeMapping
) {
const unwrappedLabels = labels as unknown as UnwrapReply<typeof labels>;
const to = unwrappedLabels.length - 2; // ignore __reducer__ and __source__
let transformedLabels: MapReply<BlobStringReply, T>;
switch (typeMapping?.[RESP_TYPES.MAP]) {
case Map:
const map = new Map<string, T>();
for (let i = 0; i < to; i++) {
const [key, value] = unwrappedLabels[i] as unknown as UnwrapReply<typeof unwrappedLabels[number]>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
map.set(unwrappedKey.toString(), value);
}
transformedLabels = map as never;
break;
case Array:
transformedLabels = unwrappedLabels.slice(0, to).flat() as never;
break;
case Object:
default:
const labelsObject: Record<string, T> = Object.create(null);
for (let i = 0; i < to; i++) {
const [key, value] = unwrappedLabels[i] as unknown as UnwrapReply<typeof unwrappedLabels[number]>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
labelsObject[unwrappedKey.toString()] = value;
}
transformedLabels = labelsObject as never;
break;
}
const sourcesTuple = unwrappedLabels[unwrappedLabels.length - 1];
const unwrappedSourcesTuple = sourcesTuple as unknown as UnwrapReply<typeof sourcesTuple>;
// the __source__ label will never be null
const transformedSources = transformRESP2Sources(unwrappedSourcesTuple[1] as BlobStringReply);
return {
labels: transformedLabels,
sources: transformedSources
};
}
function transformRESP2Sources(sourcesRaw: BlobStringReply) {
// if a label contains "," this function will produce incorrcet results..
// there is not much we can do about it, and we assume most users won't be using "," in their labels..
const unwrappedSources = sourcesRaw as unknown as UnwrapReply<typeof sourcesRaw>;
if (typeof unwrappedSources === 'string') {
return unwrappedSources.split(',');
}
const indexOfComma = unwrappedSources.indexOf(',');
if (indexOfComma === -1) {
return [unwrappedSources];
}
const sourcesArray = [
unwrappedSources.subarray(0, indexOfComma)
];
let previousComma = indexOfComma + 1;
while (true) {
const indexOf = unwrappedSources.indexOf(',', previousComma);
if (indexOf === -1) {
sourcesArray.push(
unwrappedSources.subarray(previousComma)
);
break;
}
const source = unwrappedSources.subarray(
previousComma,
indexOf
);
sourcesArray.push(source);
previousComma = indexOf + 1;
}
return sourcesArray;
}

View File

@@ -24,7 +24,7 @@
// TimeSeriesDuplicatePolicies, // TimeSeriesDuplicatePolicies,
// pushLatestArgument, // pushLatestArgument,
// TimeSeriesBucketTimestamp // TimeSeriesBucketTimestamp
// } from '.'; // } from './helpers';
// describe('transformTimestampArgument', () => { // describe('transformTimestampArgument', () => {
// it('number', () => { // it('number', () => {

View File

@@ -1,5 +1,4 @@
import type { DoubleReply, NumberReply, RedisCommands, TuplesReply, UnwrapReply, Resp2Reply, ArrayReply, BlobStringReply, MapReply, NullReply, TypeMapping, ReplyUnion, RespType } from '@redis/client/dist/lib/RESP/types'; import ADD from './ADD';
import ADD, { TsIgnoreOptions } from './ADD';
import ALTER from './ALTER'; import ALTER from './ALTER';
import CREATE from './CREATE'; import CREATE from './CREATE';
import CREATERULE from './CREATERULE'; import CREATERULE from './CREATERULE';
@@ -29,9 +28,9 @@ import MREVRANGE from './MREVRANGE';
import QUERYINDEX from './QUERYINDEX'; import QUERYINDEX from './QUERYINDEX';
import RANGE from './RANGE'; import RANGE from './RANGE';
import REVRANGE from './REVRANGE'; import REVRANGE from './REVRANGE';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers'; import { RedisCommands } from '@redis/client/dist/lib/RESP/types';
import { CommandParser } from '@redis/client/dist/lib/client/parser';
import { RESP_TYPES } from '@redis/client/dist/lib/RESP/decoder'; export * from './helpers';
export default { export default {
ADD, ADD,
@@ -96,303 +95,3 @@ export default {
revRange: REVRANGE revRange: REVRANGE
} as const satisfies RedisCommands; } as const satisfies RedisCommands;
export function parseIgnoreArgument(parser: CommandParser, ignore?: TsIgnoreOptions) {
if (ignore !== undefined) {
parser.push('IGNORE', ignore.maxTimeDiff.toString(), ignore.maxValDiff.toString());
}
}
export function parseRetentionArgument(parser: CommandParser, retention?: number) {
if (retention !== undefined) {
parser.push('RETENTION', retention.toString());
}
}
export const TIME_SERIES_ENCODING = {
COMPRESSED: 'COMPRESSED',
UNCOMPRESSED: 'UNCOMPRESSED'
} as const;
export type TimeSeriesEncoding = typeof TIME_SERIES_ENCODING[keyof typeof TIME_SERIES_ENCODING];
export function parseEncodingArgument(parser: CommandParser, encoding?: TimeSeriesEncoding) {
if (encoding !== undefined) {
parser.push('ENCODING', encoding);
}
}
export function parseChunkSizeArgument(parser: CommandParser, chunkSize?: number) {
if (chunkSize !== undefined) {
parser.push('CHUNK_SIZE', chunkSize.toString());
}
}
export const TIME_SERIES_DUPLICATE_POLICIES = {
BLOCK: 'BLOCK',
FIRST: 'FIRST',
LAST: 'LAST',
MIN: 'MIN',
MAX: 'MAX',
SUM: 'SUM'
} as const;
export type TimeSeriesDuplicatePolicies = typeof TIME_SERIES_DUPLICATE_POLICIES[keyof typeof TIME_SERIES_DUPLICATE_POLICIES];
export function parseDuplicatePolicy(parser: CommandParser, duplicatePolicy?: TimeSeriesDuplicatePolicies) {
if (duplicatePolicy !== undefined) {
parser.push('DUPLICATE_POLICY', duplicatePolicy);
}
}
export type Timestamp = number | Date | string;
export function transformTimestampArgument(timestamp: Timestamp): string {
if (typeof timestamp === 'string') return timestamp;
return (
typeof timestamp === 'number' ?
timestamp :
timestamp.getTime()
).toString();
}
export type Labels = {
[label: string]: string;
};
export function parseLabelsArgument(parser: CommandParser, labels?: Labels) {
if (labels) {
parser.push('LABELS');
for (const [label, value] of Object.entries(labels)) {
parser.push(label, value);
}
}
}
export type SampleRawReply = TuplesReply<[timestamp: NumberReply, value: DoubleReply]>;
export const transformSampleReply = {
2(reply: Resp2Reply<SampleRawReply>) {
const [ timestamp, value ] = reply as unknown as UnwrapReply<typeof reply>;
return {
timestamp,
value: Number(value) // TODO: use double type mapping instead
};
},
3(reply: SampleRawReply) {
const [ timestamp, value ] = reply as unknown as UnwrapReply<typeof reply>;
return {
timestamp,
value
};
}
};
export type SamplesRawReply = ArrayReply<SampleRawReply>;
export const transformSamplesReply = {
2(reply: Resp2Reply<SamplesRawReply>) {
return (reply as unknown as UnwrapReply<typeof reply>)
.map(sample => transformSampleReply[2](sample));
},
3(reply: SamplesRawReply) {
return (reply as unknown as UnwrapReply<typeof reply>)
.map(sample => transformSampleReply[3](sample));
}
};
// TODO: move to @redis/client?
export function resp2MapToValue<
RAW_VALUE extends TuplesReply<[key: BlobStringReply, ...rest: Array<ReplyUnion>]>,
TRANSFORMED
>(
wrappedReply: ArrayReply<RAW_VALUE>,
parseFunc: (rawValue: UnwrapReply<RAW_VALUE>) => TRANSFORMED,
typeMapping?: TypeMapping
): MapReply<BlobStringReply, TRANSFORMED> {
const reply = wrappedReply as unknown as UnwrapReply<typeof wrappedReply>;
switch (typeMapping?.[RESP_TYPES.MAP]) {
case Map: {
const ret = new Map<string, TRANSFORMED>();
for (const wrappedTuple of reply) {
const tuple = wrappedTuple as unknown as UnwrapReply<typeof wrappedTuple>;
const key = tuple[0] as unknown as UnwrapReply<typeof tuple[0]>;
ret.set(key.toString(), parseFunc(tuple));
}
return ret as never;
}
case Array: {
for (const wrappedTuple of reply) {
const tuple = wrappedTuple as unknown as UnwrapReply<typeof wrappedTuple>;
(tuple[1] as unknown as TRANSFORMED) = parseFunc(tuple);
}
return reply as never;
}
default: {
const ret: Record<string, TRANSFORMED> = Object.create(null);
for (const wrappedTuple of reply) {
const tuple = wrappedTuple as unknown as UnwrapReply<typeof wrappedTuple>;
const key = tuple[0] as unknown as UnwrapReply<typeof tuple[0]>;
ret[key.toString()] = parseFunc(tuple);
}
return ret as never;
}
}
}
export function resp3MapToValue<
RAW_VALUE extends RespType<any, any, any, any>, // TODO: simplify types
TRANSFORMED
>(
wrappedReply: MapReply<BlobStringReply, RAW_VALUE>,
parseFunc: (rawValue: UnwrapReply<RAW_VALUE>) => TRANSFORMED
): MapReply<BlobStringReply, TRANSFORMED> {
const reply = wrappedReply as unknown as UnwrapReply<typeof wrappedReply>;
if (reply instanceof Array) {
for (let i = 1; i < reply.length; i += 2) {
(reply[i] as unknown as TRANSFORMED) = parseFunc(reply[i] as unknown as UnwrapReply<RAW_VALUE>);
}
} else if (reply instanceof Map) {
for (const [key, value] of reply.entries()) {
(reply as unknown as Map<BlobStringReply, TRANSFORMED>).set(
key,
parseFunc(value as unknown as UnwrapReply<typeof value>)
);
}
} else {
for (const [key, value] of Object.entries(reply)) {
(reply[key] as unknown as TRANSFORMED) = parseFunc(value as unknown as UnwrapReply<typeof value>);
}
}
return reply as never;
}
export function parseSelectedLabelsArguments(
parser: CommandParser,
selectedLabels: RedisVariadicArgument
) {
parser.push('SELECTED_LABELS');
parser.pushVariadic(selectedLabels);
}
export type RawLabelValue = BlobStringReply | NullReply;
export type RawLabels<T extends RawLabelValue> = ArrayReply<TuplesReply<[
label: BlobStringReply,
value: T
]>>;
export function transformRESP2Labels<T extends RawLabelValue>(
labels: RawLabels<T>,
typeMapping?: TypeMapping
): MapReply<BlobStringReply, T> {
const unwrappedLabels = labels as unknown as UnwrapReply<typeof labels>;
switch (typeMapping?.[RESP_TYPES.MAP]) {
case Map:
const map = new Map<string, T>();
for (const tuple of unwrappedLabels) {
const [key, value] = tuple as unknown as UnwrapReply<typeof tuple>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
map.set(unwrappedKey.toString(), value);
}
return map as never;
case Array:
return unwrappedLabels.flat() as never;
case Object:
default:
const labelsObject: Record<string, T> = Object.create(null);
for (const tuple of unwrappedLabels) {
const [key, value] = tuple as unknown as UnwrapReply<typeof tuple>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
labelsObject[unwrappedKey.toString()] = value;
}
return labelsObject as never;
}
}
export function transformRESP2LabelsWithSources<T extends RawLabelValue>(
labels: RawLabels<T>,
typeMapping?: TypeMapping
) {
const unwrappedLabels = labels as unknown as UnwrapReply<typeof labels>;
const to = unwrappedLabels.length - 2; // ignore __reducer__ and __source__
let transformedLabels: MapReply<BlobStringReply, T>;
switch (typeMapping?.[RESP_TYPES.MAP]) {
case Map:
const map = new Map<string, T>();
for (let i = 0; i < to; i++) {
const [key, value] = unwrappedLabels[i] as unknown as UnwrapReply<typeof unwrappedLabels[number]>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
map.set(unwrappedKey.toString(), value);
}
transformedLabels = map as never;
break;
case Array:
transformedLabels = unwrappedLabels.slice(0, to).flat() as never;
break;
case Object:
default:
const labelsObject: Record<string, T> = Object.create(null);
for (let i = 0; i < to; i++) {
const [key, value] = unwrappedLabels[i] as unknown as UnwrapReply<typeof unwrappedLabels[number]>;
const unwrappedKey = key as unknown as UnwrapReply<typeof key>;
labelsObject[unwrappedKey.toString()] = value;
}
transformedLabels = labelsObject as never;
break;
}
const sourcesTuple = unwrappedLabels[unwrappedLabels.length - 1];
const unwrappedSourcesTuple = sourcesTuple as unknown as UnwrapReply<typeof sourcesTuple>;
// the __source__ label will never be null
const transformedSources = transformRESP2Sources(unwrappedSourcesTuple[1] as BlobStringReply);
return {
labels: transformedLabels,
sources: transformedSources
};
}
function transformRESP2Sources(sourcesRaw: BlobStringReply) {
// if a label contains "," this function will produce incorrcet results..
// there is not much we can do about it, and we assume most users won't be using "," in their labels..
const unwrappedSources = sourcesRaw as unknown as UnwrapReply<typeof sourcesRaw>;
if (typeof unwrappedSources === 'string') {
return unwrappedSources.split(',');
}
const indexOfComma = unwrappedSources.indexOf(',');
if (indexOfComma === -1) {
return [unwrappedSources];
}
const sourcesArray = [
unwrappedSources.subarray(0, indexOfComma)
];
let previousComma = indexOfComma + 1;
while (true) {
const indexOf = unwrappedSources.indexOf(',', previousComma);
if (indexOf === -1) {
sourcesArray.push(
unwrappedSources.subarray(previousComma)
);
break;
}
const source = unwrappedSources.subarray(
previousComma,
indexOf
);
sourcesArray.push(source);
previousComma = indexOf + 1;
}
return sourcesArray;
}