diff --git a/packages/time-series/lib/commands/ALTER.spec.ts b/packages/time-series/lib/commands/ALTER.spec.ts index 868d4a9c64..989a036535 100644 --- a/packages/time-series/lib/commands/ALTER.spec.ts +++ b/packages/time-series/lib/commands/ALTER.spec.ts @@ -41,7 +41,7 @@ describe('ALTER', () => { }); testUtils.testWithClient('client.ts.alter', async client => { - await client.ts.create('key'); + await client.ts.create('key'); assert.equal( await client.ts.alter('key', { RETENTION: 1 }), diff --git a/packages/time-series/lib/commands/DECRBY.spec.ts b/packages/time-series/lib/commands/DECRBY.spec.ts new file mode 100644 index 0000000000..345e651404 --- /dev/null +++ b/packages/time-series/lib/commands/DECRBY.spec.ts @@ -0,0 +1,81 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './DECRBY'; + +describe('DECRBY', () => { + describe('transformArguments', () => { + it('without options', () => { + assert.deepEqual( + transformArguments('key', 1), + ['TS.DECRBY', 'key', '1'] + ); + }); + + it('with TIMESTAMP', () => { + assert.deepEqual( + transformArguments('key', 1, { + TIMESTAMP: '*' + }), + ['TS.DECRBY', 'key', '1', 'TIMESTAMP', '*'] + ); + }); + + it('with RETENTION', () => { + assert.deepEqual( + transformArguments('key', 1, { + RETENTION: 1 + }), + ['TS.DECRBY', 'key', '1', 'RETENTION', '1'] + ); + }); + + it('with UNCOMPRESSED', () => { + assert.deepEqual( + transformArguments('key', 1, { + UNCOMPRESSED: true + }), + ['TS.DECRBY', 'key', '1', 'UNCOMPRESSED'] + ); + }); + + it('with CHUNK_SIZE', () => { + assert.deepEqual( + transformArguments('key', 1, { + CHUNK_SIZE: 100 + }), + ['TS.DECRBY', 'key', '1', 'CHUNK_SIZE', '100'] + ); + }); + + it('with LABELS', () => { + assert.deepEqual( + transformArguments('key', 1, { + LABELS: { label: 'value' } + }), + ['TS.DECRBY', 'key', '1', 'LABELS', 'label', 'value'] + ); + }); + + it('with TIMESTAMP, RETENTION, UNCOMPRESSED, CHUNK_SIZE and LABELS', () => { + assert.deepEqual( + transformArguments('key', 1, { + TIMESTAMP: '*', + RETENTION: 1, + UNCOMPRESSED: true, + CHUNK_SIZE: 2, + LABELS: { label: 'value' } + }), + ['TS.DECRBY', 'key', '1', 'TIMESTAMP', '*', 'RETENTION', '1', 'UNCOMPRESSED', 'CHUNK_SIZE', '2', 'LABELS', 'label', 'value'] + ); + }); + }); + + testUtils.testWithClient('client.ts.decrBy', async client => { + assert.equal( + await client.ts.decrBy('key', 1, { + TIMESTAMP: 0 + }), + 0 + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/DEL.spec.ts b/packages/time-series/lib/commands/DEL.spec.ts new file mode 100644 index 0000000000..0fc4b46580 --- /dev/null +++ b/packages/time-series/lib/commands/DEL.spec.ts @@ -0,0 +1,21 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './DEL'; + +describe('DEL', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('key', '-', '+'), + ['TS.DEL', 'key', '-', '+'] + ); + }); + + testUtils.testWithClient('client.ts.del', async client => { + await client.ts.create('key'); + + assert.equal( + await client.ts.del('key', '-', '+'), + 0 + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/DELETERULE.spec.ts b/packages/time-series/lib/commands/DELETERULE.spec.ts new file mode 100644 index 0000000000..5cbcc8edb5 --- /dev/null +++ b/packages/time-series/lib/commands/DELETERULE.spec.ts @@ -0,0 +1,26 @@ +import { strict as assert } from 'assert'; +import { TimeSeriesAggregationType } from '.'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './DELETERULE'; + +describe('DELETERULE', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('source', 'destination'), + ['TS.DELETERULE', 'source', 'destination'] + ); + }); + + testUtils.testWithClient('client.ts.deleteRule', async client => { + await Promise.all([ + client.ts.create('source'), + client.ts.create('destination'), + client.ts.createRule('source', 'destination', TimeSeriesAggregationType.AVARAGE, 1) + ]); + + assert.equal( + await client.ts.deleteRule('source', 'destination'), + 'OK' + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/DELETERULE.ts b/packages/time-series/lib/commands/DELETERULE.ts index b9ef7574c8..fff00a906a 100644 --- a/packages/time-series/lib/commands/DELETERULE.ts +++ b/packages/time-series/lib/commands/DELETERULE.ts @@ -1,4 +1,6 @@ -export function transformArguments(sourceKey: string,destinationKey: string,): Array { +export const FIRST_KEY_INDEX = 1; + +export function transformArguments(sourceKey: string, destinationKey: string): Array { return [ 'TS.DELETERULE', sourceKey, diff --git a/packages/time-series/lib/commands/GET.spec.ts b/packages/time-series/lib/commands/GET.spec.ts new file mode 100644 index 0000000000..0c0113f35d --- /dev/null +++ b/packages/time-series/lib/commands/GET.spec.ts @@ -0,0 +1,35 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './GET'; + +describe('GET', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('key'), + ['TS.GET', 'key'] + ); + }); + + describe('client.ts.get', () => { + testUtils.testWithClient('null', async client => { + await client.ts.create('key'); + + assert.equal( + await client.ts.get('key'), + null + ); + }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('with samples', async client => { + await client.ts.add('key', 0, 1); + + assert.deepEqual( + await client.ts.get('key'), + { + timestamp: 0, + value: 1 + } + ); + }, GLOBAL.SERVERS.OPEN); + }); +}); diff --git a/packages/time-series/lib/commands/INCRBY.spec.ts b/packages/time-series/lib/commands/INCRBY.spec.ts new file mode 100644 index 0000000000..acaa4cd332 --- /dev/null +++ b/packages/time-series/lib/commands/INCRBY.spec.ts @@ -0,0 +1,91 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './INCRBY'; + +describe('INCRBY', () => { + describe('transformArguments', () => { + it('without options', () => { + assert.deepEqual( + transformArguments('key', 1), + ['TS.INCRBY', 'key', '1'] + ); + }); + + it('with TIMESTAMP', () => { + assert.deepEqual( + transformArguments('key', 1, { + TIMESTAMP: '*' + }), + ['TS.INCRBY', 'key', '1', 'TIMESTAMP', '*'] + ); + }); + + it('with RETENTION', () => { + assert.deepEqual( + transformArguments('key', 1, { + RETENTION: 1 + }), + ['TS.INCRBY', 'key', '1', 'RETENTION', '1'] + ); + }); + + it('with UNCOMPRESSED', () => { + assert.deepEqual( + transformArguments('key', 1, { + UNCOMPRESSED: true + }), + ['TS.INCRBY', 'key', '1', 'UNCOMPRESSED'] + ); + }); + + it('without UNCOMPRESSED', () => { + assert.deepEqual( + transformArguments('key', 1, { + UNCOMPRESSED: false + }), + ['TS.INCRBY', 'key', '1'] + ); + }); + + it('with CHUNK_SIZE', () => { + assert.deepEqual( + transformArguments('key', 1, { + CHUNK_SIZE: 1 + }), + ['TS.INCRBY', 'key', '1', 'CHUNK_SIZE', '1'] + ); + }); + + it('with LABELS', () => { + assert.deepEqual( + transformArguments('key', 1, { + LABELS: { label: 'value' } + }), + ['TS.INCRBY', 'key', '1', 'LABELS', 'label', 'value'] + ); + }); + + it('with TIMESTAMP, RETENTION, UNCOMPRESSED, CHUNK_SIZE and LABELS', () => { + assert.deepEqual( + transformArguments('key', 1, { + TIMESTAMP: '*', + RETENTION: 1, + UNCOMPRESSED: true, + CHUNK_SIZE: 1, + LABELS: { label: 'value' } + }), + ['TS.INCRBY', 'key', '1', 'TIMESTAMP', '*', 'RETENTION', '1', 'UNCOMPRESSED', + 'CHUNK_SIZE', '1', 'LABELS', 'label', 'value'] + ); + }); + }); + + testUtils.testWithClient('client.ts.incrBy', async client => { + assert.equal( + await client.ts.incrBy('key', 1, { + TIMESTAMP: 0 + }), + 0 + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/INFO.spec.ts b/packages/time-series/lib/commands/INFO.spec.ts new file mode 100644 index 0000000000..83bad872c6 --- /dev/null +++ b/packages/time-series/lib/commands/INFO.spec.ts @@ -0,0 +1,50 @@ +import { strict as assert } from 'assert'; +import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from '.'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './INFO'; + +describe('INFO', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('key'), + ['TS.INFO', 'key'] + ); + }); + + testUtils.testWithClient('client.ts.info', async client => { + await Promise.all([ + client.ts.create('key', { + LABELS: { id: "2" }, + DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST + }), + client.ts.create('key2'), + client.ts.createRule('key', 'key2', TimeSeriesAggregationType.COUNT, 5), + client.ts.add('key', 1, 10) + ]); + + assert.deepEqual( + await client.ts.info('key'), + { + totalSamples: 1, + memoryUsage: 4261, + firstTimestamp: 1, + lastTimestamp: 1, + retentionTime: 0, + chunkCount: 1, + chunkSize: 4096, + chunkType: 'compressed', + duplicatePolicy: 'last', + labels: [{ + name: 'id', + value: '2' + }], + rules: [{ + aggregationType: 'COUNT', + key: 'key2', + timeBucket: 5 + }], + sourceKey: null + } + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/INFO_DEBUG.spec.ts b/packages/time-series/lib/commands/INFO_DEBUG.spec.ts new file mode 100644 index 0000000000..d6c7f2c5f8 --- /dev/null +++ b/packages/time-series/lib/commands/INFO_DEBUG.spec.ts @@ -0,0 +1,57 @@ +import { strict as assert } from 'assert'; +import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from '.'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './INFO_DEBUG'; + +describe('INFO_DEBUG', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('key'), + ['TS.INFO', 'key', 'DEBUG'] + ); + }); + + testUtils.testWithClient('client.ts.get', async client => { + await Promise.all([ + client.ts.create('key', { + LABELS: { id: "2" }, + DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST + }), + client.ts.create('key2'), + client.ts.createRule('key', 'key2', TimeSeriesAggregationType.COUNT, 5), + client.ts.add('key', 1, 10) + ]); + + assert.deepEqual( + await client.ts.infoDebug('key'), + { + totalSamples: 1, + memoryUsage: 4261, + firstTimestamp: 1, + lastTimestamp: 1, + retentionTime: 0, + chunkCount: 1, + chunkSize: 4096, + chunkType: 'compressed', + duplicatePolicy: 'last', + labels: [{ + name: 'id', + value: '2' + }], + sourceKey: null, + rules: [{ + aggregationType: 'COUNT', + key: 'key2', + timeBucket: 5 + }], + chunks: [{ + startTimestamp: 1, + endTimestamp: 1, + samples: 1, + size: 4096, + bytesPerSample: '4096' + }] + } + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MADD.spec.ts b/packages/time-series/lib/commands/MADD.spec.ts new file mode 100644 index 0000000000..eed014f2b1 --- /dev/null +++ b/packages/time-series/lib/commands/MADD.spec.ts @@ -0,0 +1,39 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MADD'; + +describe('MADD', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments([{ + key: '1', + timestamp: 0, + value: 0 + }, { + key: '2', + timestamp: 1, + value: 1 + }]), + ['TS.MADD', '1', '0', '0', '2', '1', '1'] + ); + }); + + // Should we check empty array? + + testUtils.testWithClient('client.ts.mAdd', async client => { + await client.ts.create('key'); + + assert.deepEqual( + await client.ts.mAdd([{ + key: 'key', + timestamp: 0, + value: 0 + }, { + key: 'key', + timestamp: 1, + value: 1 + }]), + [0, 1] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MADD.ts b/packages/time-series/lib/commands/MADD.ts index 8970cac06a..426eae7e3f 100644 --- a/packages/time-series/lib/commands/MADD.ts +++ b/packages/time-series/lib/commands/MADD.ts @@ -1,5 +1,7 @@ import { Timestamp, transformTimestampArgument } from '.'; +export const FIRST_KEY_INDEX = 1; + interface MAddSample { key: string; timestamp: Timestamp; diff --git a/packages/time-series/lib/commands/MGET.spec.ts b/packages/time-series/lib/commands/MGET.spec.ts new file mode 100644 index 0000000000..7c6c32927d --- /dev/null +++ b/packages/time-series/lib/commands/MGET.spec.ts @@ -0,0 +1,29 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MGET'; + +describe('MGET', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('label=value'), + ['TS.MGET', 'FILTER', 'label=value'] + ); + }); + + testUtils.testWithClient('client.ts.mGet', async client => { + await client.ts.add('key', 0, 0, { + LABELS: { label: 'value' } + }); + + assert.deepEqual( + await client.ts.mGet('label=value'), + [{ + key: 'key', + sample: { + timestamp: 0, + value: 0 + } + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MGET.ts b/packages/time-series/lib/commands/MGET.ts index 94fc45e03c..1dbd077db3 100644 --- a/packages/time-series/lib/commands/MGET.ts +++ b/packages/time-series/lib/commands/MGET.ts @@ -1,31 +1,26 @@ -import { pushVerdictArgument, pushVerdictArguments } from '@node-redis/client/lib/commands/generic-transformers'; +import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; +import { Filter, pushFilterArgument, RawLabels, SampleRawReply, SampleReply, transformSampleReply } from '.'; export const IS_READ_ONLY = true; -interface WithLabels { - WITHLABELS: true; +export function transformArguments(filter: Filter): RedisCommandArguments { + return pushFilterArgument(['TS.MGET'], filter); } -interface SelectedLabels { - SELECTED_LABELS: string | Array; +export type MGetRawReply = Array<[ + key: string, + labels: RawLabels, + sample: SampleRawReply +]>; + +export interface MGetReply { + key: string, + sample: SampleReply } -type MGetOptions = WithLabels & SelectedLabels; - -export function transformArguments(filter: string, options?: MGetOptions): Array { - const args = ['TS.MGET']; - - if (options?.WITHLABELS) { - args.push('WITHLABELS'); - } else if (options?.SELECTED_LABELS) { - pushVerdictArguments(args, options.SELECTED_LABELS); - } - - args.push('FILTER', filter); - - return args; -} - -export function transformReply() { - +export function transformReply(reply: MGetRawReply): Array { + return reply.map(([key, _, sample]) => ({ + key, + sample: transformSampleReply(sample) + })); } diff --git a/packages/time-series/lib/commands/MGET_WITHLABELS.spec.ts b/packages/time-series/lib/commands/MGET_WITHLABELS.spec.ts new file mode 100644 index 0000000000..55fcfde409 --- /dev/null +++ b/packages/time-series/lib/commands/MGET_WITHLABELS.spec.ts @@ -0,0 +1,39 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MGET_WITHLABELS'; + +describe('MGET_WITHLABELS', () => { + describe('transformArguments', () => { + it('without options', () => { + assert.deepEqual( + transformArguments('label=value'), + ['TS.MGET', 'WITHLABELS', 'FILTER', 'label=value'] + ); + }); + + it('with SELECTED_LABELS', () => { + assert.deepEqual( + transformArguments('label=value', { SELECTED_LABELS: 'label' }), + ['TS.MGET', 'SELECTED_LABELS', 'label', 'FILTER', 'label=value'] + ); + }); + }); + + testUtils.testWithClient('client.ts.mGetWithLabels', async client => { + await client.ts.add('key', 0, 0, { + LABELS: { label: 'value' } + }); + + assert.deepEqual( + await client.ts.mGetWithLabels('label=value'), + [{ + key: 'key', + labels: { label: 'value'}, + sample: { + timestamp: 0, + value: 0 + } + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MGET_WITHLABELS.ts b/packages/time-series/lib/commands/MGET_WITHLABELS.ts new file mode 100644 index 0000000000..cf83f4bcd1 --- /dev/null +++ b/packages/time-series/lib/commands/MGET_WITHLABELS.ts @@ -0,0 +1,41 @@ +import { + SelectedLabels, + pushWithLabelsArgument, + Labels, + transformLablesReply, + transformSampleReply, + Filter, + pushFilterArgument +} from '.'; +import { MGetRawReply, MGetReply } from './MGET'; + +export const IS_READ_ONLY = true; + +interface MGetWithLabelsOptions { + SELECTED_LABELS?: SelectedLabels; +} + +export function transformArguments( + filter: Filter, + options?: MGetWithLabelsOptions +): Array { + const args = ['TS.MGET']; + + pushWithLabelsArgument(args, options?.SELECTED_LABELS); + + pushFilterArgument(args, filter); + + return args; +} + +export interface MGetWithLabelsReply extends MGetReply { + labels: Labels; +}; + +export function transformReply(reply: MGetRawReply): Array { + return reply.map(([key, labels, sample]) => ({ + key, + labels: transformLablesReply(labels), + sample: transformSampleReply(sample) + })); +} diff --git a/packages/time-series/lib/commands/MRANGE.spec.ts b/packages/time-series/lib/commands/MRANGE.spec.ts new file mode 100644 index 0000000000..da01ebb204 --- /dev/null +++ b/packages/time-series/lib/commands/MRANGE.spec.ts @@ -0,0 +1,50 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MRANGE'; +import { TimeSeriesAggregationType, TimeSeriesReducers } from '.'; + +describe('MRANGE', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('-', '+', 'label=value', { + FILTER_BY_TS: [0], + FILTER_BY_VALUE: { + min: 0, + max: 1 + }, + COUNT: 1, + ALIGN: '-', + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + }, + GROUPBY: { + label: 'label', + reducer: TimeSeriesReducers.SUM + }, + }), + ['TS.MRANGE', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE', '0', '1', + 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'avg', '1', 'FILTER', 'label=value', + 'GROUPBY', 'label', 'REDUCE', 'sum'] + ); + }); + + testUtils.testWithClient('client.ts.mRange', async client => { + await client.ts.add('key', 0, 0, { + LABELS: { label: 'value'} + }); + + assert.deepEqual( + await client.ts.mRange('-', '+', 'label=value', { + COUNT: 1 + }), + [{ + key: 'key', + samples: [{ + timestamp: 0, + value: 0 + }] + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MRANGE.ts b/packages/time-series/lib/commands/MRANGE.ts new file mode 100644 index 0000000000..340201419f --- /dev/null +++ b/packages/time-series/lib/commands/MRANGE.ts @@ -0,0 +1,21 @@ +import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; +import { MRangeOptions, Timestamp, pushMRangeArguments, Filter } from '.'; + +export const IS_READ_ONLY = true; + +export function transformArguments( + fromTimestamp: Timestamp, + toTimestamp: Timestamp, + filters: Filter, + options?: MRangeOptions +): RedisCommandArguments { + return pushMRangeArguments( + ['TS.MRANGE'], + fromTimestamp, + toTimestamp, + filters, + options + ); +} + +export { transformMRangeReply as transformReply } from '.'; diff --git a/packages/time-series/lib/commands/MRANGE_WITHLABELS.spec.ts b/packages/time-series/lib/commands/MRANGE_WITHLABELS.spec.ts new file mode 100644 index 0000000000..e8381a1793 --- /dev/null +++ b/packages/time-series/lib/commands/MRANGE_WITHLABELS.spec.ts @@ -0,0 +1,52 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MRANGE_WITHLABELS'; +import { TimeSeriesAggregationType, TimeSeriesReducers } from '.'; + +describe('MRANGE_WITHLABELS', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('-', '+', 'label=value', { + FILTER_BY_TS: [0], + FILTER_BY_VALUE: { + min: 0, + max: 1 + }, + SELECTED_LABELS: ['label'], + COUNT: 1, + ALIGN: '-', + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + }, + GROUPBY: { + label: 'label', + reducer: TimeSeriesReducers.SUM + }, + }), + ['TS.MRANGE', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE', '0', '1', + 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'avg', '1', 'SELECTED_LABELS', 'label', + 'FILTER', 'label=value', 'GROUPBY', 'label', 'REDUCE', 'sum'] + ); + }); + + testUtils.testWithClient('client.ts.mRangeWithLabels', async client => { + await client.ts.add('key', 0, 0, { + LABELS: { label: 'value'} + }); + + assert.deepEqual( + await client.ts.mRangeWithLabels('-', '+', 'label=value', { + COUNT: 1 + }), + [{ + key: 'key', + labels: { label: 'value' }, + samples: [{ + timestamp: 0, + value: 0 + }] + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MRANGE_WITHLABELS.ts b/packages/time-series/lib/commands/MRANGE_WITHLABELS.ts new file mode 100644 index 0000000000..f4ce254235 --- /dev/null +++ b/packages/time-series/lib/commands/MRANGE_WITHLABELS.ts @@ -0,0 +1,21 @@ +import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; +import { Timestamp, MRangeWithLabelsOptions, pushMRangeWithLabelsArguments } from '.'; + +export const IS_READ_ONLY = true; + +export function transformArguments( + fromTimestamp: Timestamp, + toTimestamp: Timestamp, + filters: string | Array, + options?: MRangeWithLabelsOptions +): RedisCommandArguments { + return pushMRangeWithLabelsArguments( + ['TS.MRANGE'], + fromTimestamp, + toTimestamp, + filters, + options + ); +} + +export { transformMRangeWithLabelsReply as transformReply } from '.'; diff --git a/packages/time-series/lib/commands/MREVRANGE.spec.ts b/packages/time-series/lib/commands/MREVRANGE.spec.ts new file mode 100644 index 0000000000..08c40d8c60 --- /dev/null +++ b/packages/time-series/lib/commands/MREVRANGE.spec.ts @@ -0,0 +1,50 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MREVRANGE'; +import { TimeSeriesAggregationType, TimeSeriesReducers } from '.'; + +describe('MREVRANGE', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('-', '+', 'label=value', { + FILTER_BY_TS: [0], + FILTER_BY_VALUE: { + min: 0, + max: 1 + }, + COUNT: 1, + ALIGN: '-', + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + }, + GROUPBY: { + label: 'label', + reducer: TimeSeriesReducers.SUM + }, + }), + ['TS.MREVRANGE', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE', '0', '1', + 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'avg', '1', 'FILTER', 'label=value', + 'GROUPBY', 'label', 'REDUCE', 'sum'] + ); + }); + + testUtils.testWithClient('client.ts.mRevRange', async client => { + await client.ts.add('key', 0, 0, { + LABELS: { label: 'value'} + }); + + assert.deepEqual( + await client.ts.mRevRange('-', '+', 'label=value', { + COUNT: 1 + }), + [{ + key: 'key', + samples: [{ + timestamp: 0, + value: 0 + }] + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MREVRANGE.ts b/packages/time-series/lib/commands/MREVRANGE.ts new file mode 100644 index 0000000000..f31677fdb3 --- /dev/null +++ b/packages/time-series/lib/commands/MREVRANGE.ts @@ -0,0 +1,21 @@ +import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; +import { MRangeOptions, Timestamp, pushMRangeArguments, Filter } from '.'; + +export const IS_READ_ONLY = true; + +export function transformArguments( + fromTimestamp: Timestamp, + toTimestamp: Timestamp, + filters: Filter, + options?: MRangeOptions +): RedisCommandArguments { + return pushMRangeArguments( + ['TS.MREVRANGE'], + fromTimestamp, + toTimestamp, + filters, + options + ); +} + +export { transformMRangeReply as transformReply } from '.'; diff --git a/packages/time-series/lib/commands/MREVRANGE_WITHLABELS.spec.ts b/packages/time-series/lib/commands/MREVRANGE_WITHLABELS.spec.ts new file mode 100644 index 0000000000..a636450bef --- /dev/null +++ b/packages/time-series/lib/commands/MREVRANGE_WITHLABELS.spec.ts @@ -0,0 +1,52 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './MREVRANGE_WITHLABELS'; +import { TimeSeriesAggregationType, TimeSeriesReducers } from '.'; + +describe('MREVRANGE_WITHLABELS', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('-', '+', 'label=value', { + FILTER_BY_TS: [0], + FILTER_BY_VALUE: { + min: 0, + max: 1 + }, + SELECTED_LABELS: ['label'], + COUNT: 1, + ALIGN: '-', + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + }, + GROUPBY: { + label: 'label', + reducer: TimeSeriesReducers.SUM + }, + }), + ['TS.MREVRANGE', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE', '0', '1', + 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'avg', '1', 'SELECTED_LABELS', 'label', + 'FILTER', 'label=value', 'GROUPBY', 'label', 'REDUCE', 'sum'] + ); + }); + + testUtils.testWithClient('client.ts.mRevRangeWithLabels', async client => { + await client.ts.add('key', 0, 0, { + LABELS: { label: 'value'} + }); + + assert.deepEqual( + await client.ts.mRevRangeWithLabels('-', '+', 'label=value', { + COUNT: 1 + }), + [{ + key: 'key', + labels: { label: 'value' }, + samples: [{ + timestamp: 0, + value: 0 + }] + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/MREVRANGE_WITHLABELS.ts b/packages/time-series/lib/commands/MREVRANGE_WITHLABELS.ts new file mode 100644 index 0000000000..34109e00a6 --- /dev/null +++ b/packages/time-series/lib/commands/MREVRANGE_WITHLABELS.ts @@ -0,0 +1,21 @@ +import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; +import { Timestamp, MRangeWithLabelsOptions, pushMRangeWithLabelsArguments, Filter } from '.'; + +export const IS_READ_ONLY = true; + +export function transformArguments( + fromTimestamp: Timestamp, + toTimestamp: Timestamp, + filters: Filter, + options?: MRangeWithLabelsOptions +): RedisCommandArguments { + return pushMRangeWithLabelsArguments( + ['TS.MREVRANGE'], + fromTimestamp, + toTimestamp, + filters, + options + ); +} + +export { transformMRangeWithLabelsReply as transformReply } from '.'; diff --git a/packages/time-series/lib/commands/QUERYINDEX.spec.ts b/packages/time-series/lib/commands/QUERYINDEX.spec.ts new file mode 100644 index 0000000000..ebd3e14dcd --- /dev/null +++ b/packages/time-series/lib/commands/QUERYINDEX.spec.ts @@ -0,0 +1,27 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './QUERYINDEX'; + +describe('QUERYINDEX', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('*'), + ['TS.QUERYINDEX', '*'] + ); + }); + + testUtils.testWithClient('client.ts.queryIndex', async client => { + await Promise.all([ + client.ts.create('key', { + LABELS: { + label: 'value' + } + }) + ]); + + assert.deepEqual( + await client.ts.queryIndex('label=value'), + ['key'] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/RANGE.spec.ts b/packages/time-series/lib/commands/RANGE.spec.ts new file mode 100644 index 0000000000..15d2706d95 --- /dev/null +++ b/packages/time-series/lib/commands/RANGE.spec.ts @@ -0,0 +1,38 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './RANGE'; +import { TimeSeriesAggregationType } from '.'; + +describe('RANGE', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + FILTER_BY_TS: [0], + FILTER_BY_VALUE: { + min: 1, + max: 2 + }, + COUNT: 1, + ALIGN: '-', + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + } + }), + ['TS.RANGE', 'key', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE', + '1', '2', 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'avg', '1'] + ); + }); + + testUtils.testWithClient('client.ts.range', async client => { + await client.ts.add('key', 1, 2); + + assert.deepEqual( + await client.ts.range('key', '-', '+'), + [{ + timestamp: 1, + value: 2 + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/RANGE.ts b/packages/time-series/lib/commands/RANGE.ts index b22d2e33b2..73dfa90d39 100644 --- a/packages/time-series/lib/commands/RANGE.ts +++ b/packages/time-series/lib/commands/RANGE.ts @@ -1,21 +1,24 @@ import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; -import { RangeOptions, Timestamp, pushRangeArguments } from '.'; +import { RangeOptions, Timestamp, pushRangeArguments, SampleRawReply, SampleReply, transformRangeReply } from '.'; export const FIRST_KEY_INDEX = 1; export const IS_READ_ONLY = true; export function transformArguments( + key: string, fromTimestamp: Timestamp, toTimestamp: Timestamp, options?: RangeOptions ): RedisCommandArguments { return pushRangeArguments( - ['TS.RANGE'], + ['TS.RANGE', key], fromTimestamp, toTimestamp, options ); } -export { transformRangeReply } from '.'; +export function transformReply(reply: Array): Array { + return transformRangeReply(reply); +} diff --git a/packages/time-series/lib/commands/REVRANGE.spec.ts b/packages/time-series/lib/commands/REVRANGE.spec.ts new file mode 100644 index 0000000000..10a7f4b423 --- /dev/null +++ b/packages/time-series/lib/commands/REVRANGE.spec.ts @@ -0,0 +1,106 @@ +import { strict as assert } from 'assert'; +import testUtils, { GLOBAL } from '../test-utils'; +import { transformArguments } from './REVRANGE'; +import { TimeSeriesAggregationType } from '.'; + +describe('REVRANGE', () => { + describe('transformArguments', () => { + it('without options', () => { + assert.deepEqual( + transformArguments('key', '-', '+'), + ['TS.REVRANGE', 'key', '-', '+'] + ); + }); + + it('with FILTER_BY_TS', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + FILTER_BY_TS: [0] + }), + ['TS.REVRANGE', 'key', '-', '+', 'FILTER_BY_TS', '0'] + ); + }); + + it('with FILTER_BY_VALUE', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + FILTER_BY_VALUE: { + min: 1, + max: 2 + } + }), + ['TS.REVRANGE', 'key', '-', '+', 'FILTER_BY_VALUE', '1', '2'] + ); + }); + + it('with COUNT', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + COUNT: 1 + }), + ['TS.REVRANGE', 'key', '-', '+', 'COUNT', '1'] + ); + }); + + it('with ALIGN', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + ALIGN: '-' + }), + ['TS.REVRANGE', 'key', '-', '+', 'ALIGN', '-'] + ); + }); + + it('with AGGREGATION', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + } + }), + ['TS.REVRANGE', 'key', '-', '+', 'AGGREGATION', 'avg', '1'] + ); + }); + + it('with FILTER_BY_TS, FILTER_BY_VALUE, COUNT, ALIGN, AGGREGATION', () => { + assert.deepEqual( + transformArguments('key', '-', '+', { + FILTER_BY_TS: [0], + FILTER_BY_VALUE: { + min: 1, + max: 2 + }, + COUNT: 1, + ALIGN: '-', + AGGREGATION: { + type: TimeSeriesAggregationType.AVARAGE, + timeBucket: 1 + } + }), + [ + 'TS.REVRANGE', 'key', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE', + '1', '2', 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'avg', '1' + ] + ); + }); + }); + + testUtils.testWithClient('client.ts.revRange', async client => { + await Promise.all([ + client.ts.add('key', 0, 1), + client.ts.add('key', 1, 2) + ]); + + assert.deepEqual( + await client.ts.revRange('key', '-', '+'), + [{ + timestamp: 1, + value: 2 + }, { + timestamp: 0, + value: 1 + }] + ); + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/time-series/lib/commands/REVRANGE.ts b/packages/time-series/lib/commands/REVRANGE.ts index ba961265ac..f2bfcb1cb5 100644 --- a/packages/time-series/lib/commands/REVRANGE.ts +++ b/packages/time-series/lib/commands/REVRANGE.ts @@ -1,21 +1,24 @@ import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; -import { RangeOptions, Timestamp, pushRangeArguments } from '.'; +import { RangeOptions, Timestamp, pushRangeArguments, SampleRawReply, SampleReply, transformRangeReply } from '.'; export const FIRST_KEY_INDEX = 1; export const IS_READ_ONLY = true; export function transformArguments( + key: string, fromTimestamp: Timestamp, toTimestamp: Timestamp, options?: RangeOptions ): RedisCommandArguments { return pushRangeArguments( - ['TS.REVRANGE'], + ['TS.REVRANGE', key], fromTimestamp, toTimestamp, options ); } -export { transformRangeReply } from '.'; +export function transformReply(reply: Array): Array { + return transformRangeReply(reply); +} diff --git a/packages/time-series/lib/commands/index.spec.ts b/packages/time-series/lib/commands/index.spec.ts index d02d259eb7..b9d1eaaef2 100644 --- a/packages/time-series/lib/commands/index.spec.ts +++ b/packages/time-series/lib/commands/index.spec.ts @@ -9,9 +9,17 @@ import { pushLabelsArgument, transformIncrDecrArguments, transformSampleReply, + TimeSeriesAggregationType, pushRangeArguments, + pushMRangeGroupByArguments, + TimeSeriesReducers, + pushFilterArgument, + pushMRangeArguments, + pushWithLabelsArgument, + pushMRangeWithLabelsArguments, transformRangeReply, - TimeSeriesAggregationType + transformMRangeReply, + transformMRangeWithLabelsReply } from '.'; describe('transformTimestampArgument', () => { @@ -115,6 +123,15 @@ describe('transformIncrDecrArguments', () => { ['TS.INCRBY', 'key', '1', 'UNCOMPRESSED'] ); }); + + it('with UNCOMPRESSED false', () => { + assert.deepEqual( + transformIncrDecrArguments('TS.INCRBY', 'key', 1, { + UNCOMPRESSED: false + }), + ['TS.INCRBY', 'key', '1'] + ); + }); }); it('transformSampleReply', () => { @@ -139,7 +156,7 @@ describe('pushRangeArguments', () => { it('string', () => { assert.deepEqual( pushRangeArguments([], '-', '+', { - FILTER_BY_TS: 'ts' + FILTER_BY_TS: ['ts'] }), ['-', '+', 'FILTER_BY_TS', 'ts'] ); @@ -200,7 +217,7 @@ describe('pushRangeArguments', () => { it('with FILTER_BY_TS, FILTER_BY_VALUE, COUNT, ALIGN, AGGREGATION', () => { assert.deepEqual( pushRangeArguments([], '-', '+', { - FILTER_BY_TS: 'ts', + FILTER_BY_TS: ['ts'], FILTER_BY_VALUE: { min: 1, max: 2 @@ -212,11 +229,91 @@ describe('pushRangeArguments', () => { timeBucket: 1 } }), - ['-', '+', 'FILTER_BY_TS', 'ts', 'FILTER_BY_VALUE', '1', '2', 'COUNT', '1', 'ALIGN', '1', 'AGGREGATION', 'first', '1'] + ['-', '+', 'FILTER_BY_TS', 'ts', 'FILTER_BY_VALUE', '1', '2', + 'COUNT', '1', 'ALIGN', '1', 'AGGREGATION', 'first', '1'] ); }); }); +describe('pushMRangeGroupByArguments', () => { + it('undefined', () => { + assert.deepEqual( + pushMRangeGroupByArguments([]), + [] + ); + }); + + it('with GROUPBY', () => { + assert.deepEqual( + pushMRangeGroupByArguments([], { + label: 'label', + reducer: TimeSeriesReducers.MAXIMUM + }), + ['GROUPBY', 'label', 'REDUCE', 'max'] + ); + }); +}); + +describe('pushFilterArgument', () => { + it('string', () => { + assert.deepEqual( + pushFilterArgument([], 'label=value'), + ['FILTER', 'label=value'] + ); + }); + + it('Array', () => { + assert.deepEqual( + pushFilterArgument([], ['1=1', '2=2']), + ['FILTER', '1=1', '2=2'] + ); + }); +}); + +describe('pushMRangeArguments', () => { + it('without options', () => { + assert.deepEqual( + pushMRangeArguments([], '-', '+', 'label=value'), + ['-', '+', 'FILTER', 'label=value'] + ); + }); + + it('with GROUPBY', () => { + assert.deepEqual( + pushMRangeArguments([], '-', '+', 'label=value', { + GROUPBY: { + label: 'label', + reducer: TimeSeriesReducers.MAXIMUM + } + }), + ['-', '+', 'FILTER', 'label=value', 'GROUPBY', 'label', 'REDUCE', 'max'] + ); + }); +}); + +describe('pushWithLabelsArgument', () => { + it('without selected labels', () => { + assert.deepEqual( + pushWithLabelsArgument([]), + ['WITHLABELS'] + ); + }); + + it('with selected labels', () => { + assert.deepEqual( + pushWithLabelsArgument([], ['label']), + ['SELECTED_LABELS', 'label'] + ); + }); +}); + +it('pushMRangeWithLabelsArguments', () => { + assert.deepEqual( + pushMRangeWithLabelsArguments([], '-', '+', 'label=value'), + ['-', '+', 'WITHLABELS', 'FILTER', 'label=value'] + ); +}); + it('transformRangeReply', () => { assert.deepEqual( transformRangeReply([[1, '1.1'], [2, '2.2']]), @@ -229,3 +326,46 @@ it('transformRangeReply', () => { }] ); }); + +describe('transformMRangeReply', () => { + assert.deepEqual( + transformMRangeReply([[ + 'key', + [], + [[1, '1.1'], [2, '2.2']] + ]]), + [{ + key: 'key', + samples: [{ + timestamp: 1, + value: 1.1 + }, { + timestamp: 2, + value: 2.2 + }] + }] + ); +}); + +describe('transformMRangeWithLabelsReply', () => { + assert.deepEqual( + transformMRangeWithLabelsReply([[ + 'key', + [['label', 'value']], + [[1, '1.1'], [2, '2.2']] + ]]), + [{ + key: 'key', + labels: { + label: 'value' + }, + samples: [{ + timestamp: 1, + value: 1.1 + }, { + timestamp: 2, + value: 2.2 + }] + }] + ); +}); diff --git a/packages/time-series/lib/commands/index.ts b/packages/time-series/lib/commands/index.ts index 07034c6137..b8b43eb943 100644 --- a/packages/time-series/lib/commands/index.ts +++ b/packages/time-series/lib/commands/index.ts @@ -11,9 +11,14 @@ import * as INFO_DEBUG from './INFO_DEBUG'; import * as INFO from './INFO'; import * as MADD from './MADD'; import * as MGET from './MGET'; +import * as MGET_WITHLABELS from './MGET_WITHLABELS'; import * as QUERYINDEX from './QUERYINDEX'; import * as RANGE from './RANGE'; import * as REVRANGE from './REVRANGE'; +import * as MRANGE from './MRANGE'; +import * as MRANGE_WITHLABELS from './MRANGE_WITHLABELS'; +import * as MREVRANGE from './MREVRANGE'; +import * as MREVRANGE_WITHLABELS from './MREVRANGE_WITHLABELS'; import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands'; import { pushVerdictArguments } from '@node-redis/client/lib/commands/generic-transformers'; @@ -44,12 +49,22 @@ export default { mAdd: MADD, MGET, mGet: MGET, + MGET_WITHLABELS, + mGetWithLabels: MGET_WITHLABELS, QUERYINDEX, queryIndex: QUERYINDEX, RANGE, range: RANGE, REVRANGE, - revRange: REVRANGE + revRange: REVRANGE, + MRANGE, + mRange: MRANGE, + MRANGE_WITHLABELS, + mRangeWithLabels: MRANGE_WITHLABELS, + MREVRANGE, + mRevRange: MREVRANGE, + MREVRANGE_WITHLABELS, + mRevRangeWithLabels: MREVRANGE_WITHLABELS }; export enum TimeSeriesAggregationType { @@ -67,6 +82,21 @@ export enum TimeSeriesAggregationType { VAR_S = 'var.s' } +export enum TimeSeriesDuplicatePolicies { + BLOCK = 'BLOCK', + FIRST = 'FIRST', + LAST = 'LAST', + MIN = 'MIN', + MAX = 'MAX', + SUM = 'SUM' +} + +export enum TimeSeriesReducers { + SUM = 'sum', + MINIMUM = 'min', + MAXIMUM = 'max', +} + export type Timestamp = number | Date | string; export function transformTimestampArgument(timestamp: Timestamp): string { @@ -117,19 +147,22 @@ export function pushChunkSizeArgument(args: RedisCommandArguments, chunkSize?: n return args; } -export enum TimeSeriesDuplicatePolicies { - BLOCK = 'BLOCK', - FIRST = 'FIRST', - LAST = 'LAST', - MIN = 'MIN', - MAX = 'MAX', - SUM = 'SUM' -} +export type RawLabels = Array<[label: string, value: string]>; export type Labels = { [label: string]: string; }; +export function transformLablesReply(reply: RawLabels): Labels { + const labels: Labels = {}; + + for (const [key, value] of reply) { + labels[key] = value; + } + + return labels +} + export function pushLabelsArgument(args: RedisCommandArguments, labels?: Labels): RedisCommandArguments { if (labels) { args.push('LABELS'); @@ -162,7 +195,7 @@ export function transformIncrDecrArguments( value.toString() ]; - if (options?.TIMESTAMP) { + if (options?.TIMESTAMP !== undefined && options?.TIMESTAMP !== null) { args.push('TIMESTAMP', transformTimestampArgument(options.TIMESTAMP)); } @@ -194,7 +227,7 @@ export function transformSampleReply(reply: SampleRawReply): SampleReply { } export interface RangeOptions { - FILTER_BY_TS?: string | Array; + FILTER_BY_TS?: Array; FILTER_BY_VALUE?: { min: number; max: number; @@ -220,7 +253,9 @@ export function pushRangeArguments( if (options?.FILTER_BY_TS) { args.push('FILTER_BY_TS'); - pushVerdictArguments(args, options.FILTER_BY_TS); + for(const ts of options.FILTER_BY_TS) { + args.push(transformTimestampArgument(ts)); + } } if (options?.FILTER_BY_VALUE) { @@ -256,6 +291,121 @@ export function pushRangeArguments( return args; } +interface MRangeGroupBy { + label: string; + reducer: TimeSeriesReducers; +} + +export function pushMRangeGroupByArguments(args: RedisCommandArguments, groupBy?: MRangeGroupBy): RedisCommandArguments { + if (groupBy) { + args.push( + 'GROUPBY', + groupBy.label, + 'REDUCE', + groupBy.reducer + ); + } + + return args; +} + +export type Filter = string | Array; + +export function pushFilterArgument(args: RedisCommandArguments, filter: string | Array): RedisCommandArguments { + args.push('FILTER'); + pushVerdictArguments(args, filter); + return args; +} + +export interface MRangeOptions extends RangeOptions { + GROUPBY?: MRangeGroupBy; +} + +export function pushMRangeArguments( + args: RedisCommandArguments, + fromTimestamp: Timestamp, + toTimestamp: Timestamp, + filter: Filter, + options?: MRangeOptions +): RedisCommandArguments { + pushRangeArguments(args, fromTimestamp, toTimestamp, options); + pushFilterArgument(args, filter); + pushMRangeGroupByArguments(args, options?.GROUPBY); + return args; +} + +export type SelectedLabels = string | Array; + +export function pushWithLabelsArgument(args: RedisCommandArguments, selectedLabels?: SelectedLabels): RedisCommandArguments { + if (!selectedLabels) { + args.push('WITHLABELS'); + } else { + args.push('SELECTED_LABELS'); + pushVerdictArguments(args, selectedLabels); + } + + return args; +} + +export interface MRangeWithLabelsOptions extends MRangeOptions { + SELECTED_LABELS?: SelectedLabels; +} + +export function pushMRangeWithLabelsArguments( + args: RedisCommandArguments, + fromTimestamp: Timestamp, + toTimestamp: Timestamp, + filter: Filter, + options?: MRangeWithLabelsOptions +): RedisCommandArguments { + pushRangeArguments(args, fromTimestamp, toTimestamp, options); + pushWithLabelsArgument(args, options?.SELECTED_LABELS); + pushFilterArgument(args, filter); + pushMRangeGroupByArguments(args, options?.GROUPBY); + return args; +} + export function transformRangeReply(reply: Array): Array { return reply.map(transformSampleReply); } + +type MRangeRawReply = Array<[ + key: string, + labels: RawLabels, + samples: Array +]>; + +interface MRangeReplyItem { + key: string; + samples: Array; +} + +export function transformMRangeReply(reply: MRangeRawReply): Array { + const args = []; + + for (const [key, _, sample] of reply) { + args.push({ + key, + samples: sample.map(transformSampleReply) + }); + } + + return args; +} +export interface MRangeWithLabelsReplyItem extends MRangeReplyItem { + labels: Labels; +} + +export function transformMRangeWithLabelsReply(reply: MRangeRawReply): Array { + const args = []; + + for (const [key, labels, samples] of reply) { + args.push({ + key, + labels: transformLablesReply(labels), + samples: samples.map(transformSampleReply) + }); + } + + return args; +}