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

V5 bringing RESP3, Sentinel and TypeMapping to node-redis

RESP3 Support
   - Some commands responses in RESP3 aren't stable yet and therefore return an "untyped" ReplyUnion.
 
Sentinel

TypeMapping

Correctly types Multi commands

Note: some API changes to be further documented in v4-to-v5.md
This commit is contained in:
Shaya Potter
2024-10-15 17:46:52 +03:00
committed by GitHub
parent 2fc79bdfb3
commit b2d35c5286
1174 changed files with 45931 additions and 36274 deletions

View File

@@ -1,90 +1,93 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './ADD';
import { TimeSeriesDuplicatePolicies, TimeSeriesEncoding } from '.';
import ADD from './ADD';
import { TIME_SERIES_ENCODING, TIME_SERIES_DUPLICATE_POLICIES } from '.';
describe('ADD', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('key', '*', 1),
['TS.ADD', 'key', '*', '1']
);
});
it('with RETENTION', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
RETENTION: 1
}),
['TS.ADD', 'key', '*', '1', 'RETENTION', '1']
);
});
it('with ENCODING', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
ENCODING: TimeSeriesEncoding.UNCOMPRESSED
}),
['TS.ADD', 'key', '*', '1', 'ENCODING', 'UNCOMPRESSED']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
CHUNK_SIZE: 1
}),
['TS.ADD', 'key', '*', '1', 'CHUNK_SIZE', '1']
);
});
it('with ON_DUPLICATE', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
ON_DUPLICATE: TimeSeriesDuplicatePolicies.BLOCK
}),
['TS.ADD', 'key', '*', '1', 'ON_DUPLICATE', 'BLOCK']
);
});
it('with LABELS', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
LABELS: { label: 'value' }
}),
['TS.ADD', 'key', '*', '1', 'LABELS', 'label', 'value']
);
});
it('with IGNORE', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
IGNORE: { MAX_TIME_DIFF: 1, MAX_VAL_DIFF: 1}
}),
['TS.ADD', 'key', '*', '1', 'IGNORE', '1', '1']
)
});
it('with RETENTION, ENCODING, CHUNK_SIZE, ON_DUPLICATE, LABELS, IGNORE', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
RETENTION: 1,
ENCODING: TimeSeriesEncoding.UNCOMPRESSED,
CHUNK_SIZE: 1,
ON_DUPLICATE: TimeSeriesDuplicatePolicies.BLOCK,
LABELS: { label: 'value' },
IGNORE: { MAX_TIME_DIFF: 1, MAX_VAL_DIFF: 1}
}),
['TS.ADD', 'key', '*', '1', 'RETENTION', '1', 'ENCODING', 'UNCOMPRESSED', 'CHUNK_SIZE', '1', 'ON_DUPLICATE', 'BLOCK', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
describe('TS.ADD', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1),
['TS.ADD', 'key', '*', '1']
);
});
testUtils.testWithClient('client.ts.add', async client => {
assert.equal(
await client.ts.add('key', 0, 1),
0
);
}, GLOBAL.SERVERS.OPEN);
it('with RETENTION', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
RETENTION: 1
}),
['TS.ADD', 'key', '*', '1', 'RETENTION', '1']
);
});
it('with ENCODING', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
ENCODING: TIME_SERIES_ENCODING.UNCOMPRESSED
}),
['TS.ADD', 'key', '*', '1', 'ENCODING', 'UNCOMPRESSED']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
CHUNK_SIZE: 1
}),
['TS.ADD', 'key', '*', '1', 'CHUNK_SIZE', '1']
);
});
it('with ON_DUPLICATE', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
ON_DUPLICATE: TIME_SERIES_DUPLICATE_POLICIES.BLOCK
}),
['TS.ADD', 'key', '*', '1', 'ON_DUPLICATE', 'BLOCK']
);
});
it('with LABELS', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
LABELS: { label: 'value' }
}),
['TS.ADD', 'key', '*', '1', 'LABELS', 'label', 'value']
);
});
it ('with IGNORE', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
IGNORE: {
maxTimeDiff: 1,
maxValDiff: 1
}
}),
['TS.ADD', 'key', '*', '1', 'IGNORE', '1', '1']
)
});
it('with RETENTION, ENCODING, CHUNK_SIZE, ON_DUPLICATE, LABELS, IGNORE', () => {
assert.deepEqual(
ADD.transformArguments('key', '*', 1, {
RETENTION: 1,
ENCODING: TIME_SERIES_ENCODING.UNCOMPRESSED,
CHUNK_SIZE: 1,
ON_DUPLICATE: TIME_SERIES_DUPLICATE_POLICIES.BLOCK,
LABELS: { label: 'value' },
IGNORE: { maxTimeDiff: 1, maxValDiff: 1}
}),
['TS.ADD', 'key', '*', '1', 'RETENTION', '1', 'ENCODING', 'UNCOMPRESSED', 'CHUNK_SIZE', '1', 'ON_DUPLICATE', 'BLOCK', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
});
testUtils.testWithClient('client.ts.add', async client => {
assert.equal(
await client.ts.add('key', 0, 1),
0
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,38 +1,45 @@
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
import {
transformTimestampArgument,
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
TimeSeriesDuplicatePolicies,
Labels,
pushLabelsArgument,
Timestamp,
pushIgnoreArgument,
transformTimestampArgument,
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
TimeSeriesDuplicatePolicies,
Labels,
pushLabelsArgument,
Timestamp,
pushIgnoreArgument
} from '.';
export interface TsIgnoreOptions {
MAX_TIME_DIFF: number;
MAX_VAL_DIFF: number;
maxTimeDiff: number;
maxValDiff: number;
}
interface AddOptions {
RETENTION?: number;
ENCODING?: TimeSeriesEncoding;
CHUNK_SIZE?: number;
ON_DUPLICATE?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
IGNORE?: TsIgnoreOptions;
export interface TsAddOptions {
RETENTION?: number;
ENCODING?: TimeSeriesEncoding;
CHUNK_SIZE?: number;
ON_DUPLICATE?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
IGNORE?: TsIgnoreOptions;
}
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, timestamp: Timestamp, value: number, options?: AddOptions): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(
key: RedisArgument,
timestamp: Timestamp,
value: number,
options?: TsAddOptions
) {
const args = [
'TS.ADD',
key,
transformTimestampArgument(timestamp),
value.toString()
'TS.ADD',
key,
transformTimestampArgument(timestamp),
value.toString()
];
pushRetentionArgument(args, options?.RETENTION);
@@ -42,7 +49,7 @@ export function transformArguments(key: string, timestamp: Timestamp, value: num
pushChunkSizeArgument(args, options?.CHUNK_SIZE);
if (options?.ON_DUPLICATE) {
args.push('ON_DUPLICATE', options.ON_DUPLICATE);
args.push('ON_DUPLICATE', options.ON_DUPLICATE);
}
pushLabelsArgument(args, options?.LABELS);
@@ -50,6 +57,6 @@ export function transformArguments(key: string, timestamp: Timestamp, value: num
pushIgnoreArgument(args, options?.IGNORE);
return args;
}
export declare function transformReply(): number;
},
transformReply: undefined as unknown as () => NumberReply
} as const satisfies Command;

View File

@@ -1,82 +1,85 @@
import { strict as assert } from 'assert';
import { TimeSeriesDuplicatePolicies } from '.';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './ALTER';
import ALTER from './ALTER';
import { TIME_SERIES_DUPLICATE_POLICIES } from '.';
describe('ALTER', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('key'),
['TS.ALTER', 'key']
);
});
it('with RETENTION', () => {
assert.deepEqual(
transformArguments('key', {
RETENTION: 1
}),
['TS.ALTER', 'key', 'RETENTION', '1']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
transformArguments('key', {
CHUNK_SIZE: 1
}),
['TS.ALTER', 'key', 'CHUNK_SIZE', '1']
);
});
it('with DUPLICATE_POLICY', () => {
assert.deepEqual(
transformArguments('key', {
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK
}),
['TS.ALTER', 'key', 'DUPLICATE_POLICY', 'BLOCK']
);
});
it('with LABELS', () => {
assert.deepEqual(
transformArguments('key', {
LABELS: { label: 'value' }
}),
['TS.ALTER', 'key', 'LABELS', 'label', 'value']
);
});
it('with IGNORE with MAX_TIME_DIFF', () => {
assert.deepEqual(
transformArguments('key', {
IGNORE: { MAX_TIME_DIFF: 1, MAX_VAL_DIFF: 1}
}),
['TS.ALTER', 'key', 'IGNORE', '1', '1']
)
});
it('with RETENTION, CHUNK_SIZE, DUPLICATE_POLICY, LABELS, IGNORE', () => {
assert.deepEqual(
transformArguments('key', {
RETENTION: 1,
CHUNK_SIZE: 1,
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK,
LABELS: { label: 'value' },
IGNORE: { MAX_TIME_DIFF: 1, MAX_VAL_DIFF: 1}
}),
['TS.ALTER', 'key', 'RETENTION', '1', 'CHUNK_SIZE', '1', 'DUPLICATE_POLICY', 'BLOCK', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
describe('TS.ALTER', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
ALTER.transformArguments('key'),
['TS.ALTER', 'key']
);
});
testUtils.testWithClient('client.ts.alter', async client => {
await client.ts.create('key');
it('with RETENTION', () => {
assert.deepEqual(
ALTER.transformArguments('key', {
RETENTION: 1
}),
['TS.ALTER', 'key', 'RETENTION', '1']
);
});
assert.equal(
await client.ts.alter('key', { RETENTION: 1 }),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
it('with CHUNK_SIZE', () => {
assert.deepEqual(
ALTER.transformArguments('key', {
CHUNK_SIZE: 1
}),
['TS.ALTER', 'key', 'CHUNK_SIZE', '1']
);
});
it('with DUPLICATE_POLICY', () => {
assert.deepEqual(
ALTER.transformArguments('key', {
DUPLICATE_POLICY: TIME_SERIES_DUPLICATE_POLICIES.BLOCK
}),
['TS.ALTER', 'key', 'DUPLICATE_POLICY', 'BLOCK']
);
});
it('with LABELS', () => {
assert.deepEqual(
ALTER.transformArguments('key', {
LABELS: { label: 'value' }
}),
['TS.ALTER', 'key', 'LABELS', 'label', 'value']
);
});
it('with IGNORE with MAX_TIME_DIFF', () => {
assert.deepEqual(
ALTER.transformArguments('key', {
IGNORE: {
maxTimeDiff: 1,
maxValDiff: 1
}
}),
['TS.ALTER', 'key', 'IGNORE', '1', '1']
)
});
it('with RETENTION, CHUNK_SIZE, DUPLICATE_POLICY, LABELS, IGNORE', () => {
assert.deepEqual(
ALTER.transformArguments('key', {
RETENTION: 1,
CHUNK_SIZE: 1,
DUPLICATE_POLICY: TIME_SERIES_DUPLICATE_POLICIES.BLOCK,
LABELS: { label: 'value' },
IGNORE: { maxTimeDiff: 1, maxValDiff: 1}
}),
['TS.ALTER', 'key', 'RETENTION', '1', 'CHUNK_SIZE', '1', 'DUPLICATE_POLICY', 'BLOCK', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
});
testUtils.testWithClient('client.ts.alter', async client => {
const [, reply] = await Promise.all([
client.ts.create('key'),
client.ts.alter('key')
]);
assert.equal(reply, 'OK');
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,17 +1,13 @@
import { pushRetentionArgument, Labels, pushLabelsArgument, TimeSeriesDuplicatePolicies, pushChunkSizeArgument, pushDuplicatePolicy, pushIgnoreArgument } from '.';
import { TsIgnoreOptions } from './ADD';
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
import { TsCreateOptions } from './CREATE';
import { pushRetentionArgument, pushChunkSizeArgument, pushDuplicatePolicy, pushLabelsArgument, pushIgnoreArgument } from '.';
export const FIRST_KEY_INDEX = 1;
export type TsAlterOptions = Pick<TsCreateOptions, 'RETENTION' | 'CHUNK_SIZE' | 'DUPLICATE_POLICY' | 'LABELS' | 'IGNORE'>;
interface AlterOptions {
RETENTION?: number;
CHUNK_SIZE?: number;
DUPLICATE_POLICY?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
IGNORE?: TsIgnoreOptions;
}
export function transformArguments(key: string, options?: AlterOptions): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(key: RedisArgument, options?: TsAlterOptions) {
const args = ['TS.ALTER', key];
pushRetentionArgument(args, options?.RETENTION);
@@ -25,6 +21,6 @@ export function transformArguments(key: string, options?: AlterOptions): Array<s
pushIgnoreArgument(args, options?.IGNORE);
return args;
}
export declare function transformReply(): 'OK';
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,90 +1,93 @@
import { strict as assert } from 'assert';
import { TimeSeriesDuplicatePolicies, TimeSeriesEncoding } from '.';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CREATE';
import CREATE from './CREATE';
import { TIME_SERIES_ENCODING, TIME_SERIES_DUPLICATE_POLICIES } from '.';
describe('CREATE', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('key'),
['TS.CREATE', 'key']
);
});
it('with RETENTION', () => {
assert.deepEqual(
transformArguments('key', {
RETENTION: 1
}),
['TS.CREATE', 'key', 'RETENTION', '1']
);
});
it('with ENCODING', () => {
assert.deepEqual(
transformArguments('key', {
ENCODING: TimeSeriesEncoding.UNCOMPRESSED
}),
['TS.CREATE', 'key', 'ENCODING', 'UNCOMPRESSED']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
transformArguments('key', {
CHUNK_SIZE: 1
}),
['TS.CREATE', 'key', 'CHUNK_SIZE', '1']
);
});
it('with DUPLICATE_POLICY', () => {
assert.deepEqual(
transformArguments('key', {
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK
}),
['TS.CREATE', 'key', 'DUPLICATE_POLICY', 'BLOCK']
);
});
it('with LABELS', () => {
assert.deepEqual(
transformArguments('key', {
LABELS: { label: 'value' }
}),
['TS.CREATE', 'key', 'LABELS', 'label', 'value']
);
});
it('with IGNORE with MAX_TIME_DIFF', () => {
assert.deepEqual(
transformArguments('key', {
IGNORE: { MAX_TIME_DIFF: 1, MAX_VAL_DIFF: 1}
}),
['TS.CREATE', 'key', 'IGNORE', '1', '1']
)
});
it('with RETENTION, ENCODING, CHUNK_SIZE, DUPLICATE_POLICY, LABELS, IGNORE', () => {
assert.deepEqual(
transformArguments('key', {
RETENTION: 1,
ENCODING: TimeSeriesEncoding.UNCOMPRESSED,
CHUNK_SIZE: 1,
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK,
LABELS: { label: 'value' },
IGNORE: { MAX_TIME_DIFF: 1, MAX_VAL_DIFF: 1}
}),
['TS.CREATE', 'key', 'RETENTION', '1', 'ENCODING', 'UNCOMPRESSED', 'CHUNK_SIZE', '1', 'DUPLICATE_POLICY', 'BLOCK', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
describe('TS.CREATE', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
CREATE.transformArguments('key'),
['TS.CREATE', 'key']
);
});
testUtils.testWithClient('client.ts.create', async client => {
assert.equal(
await client.ts.create('key'),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
it('with RETENTION', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
RETENTION: 1
}),
['TS.CREATE', 'key', 'RETENTION', '1']
);
});
it('with ENCODING', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
ENCODING: TIME_SERIES_ENCODING.UNCOMPRESSED
}),
['TS.CREATE', 'key', 'ENCODING', 'UNCOMPRESSED']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
CHUNK_SIZE: 1
}),
['TS.CREATE', 'key', 'CHUNK_SIZE', '1']
);
});
it('with DUPLICATE_POLICY', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
DUPLICATE_POLICY: TIME_SERIES_DUPLICATE_POLICIES.BLOCK
}),
['TS.CREATE', 'key', 'DUPLICATE_POLICY', 'BLOCK']
);
});
it('with LABELS', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
LABELS: { label: 'value' }
}),
['TS.CREATE', 'key', 'LABELS', 'label', 'value']
);
});
it('with IGNORE with MAX_TIME_DIFF', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
IGNORE: {
maxTimeDiff: 1,
maxValDiff: 1
}
}),
['TS.CREATE', 'key', 'IGNORE', '1', '1']
)
});
it('with RETENTION, ENCODING, CHUNK_SIZE, DUPLICATE_POLICY, LABELS, IGNORE', () => {
assert.deepEqual(
CREATE.transformArguments('key', {
RETENTION: 1,
ENCODING: TIME_SERIES_ENCODING.UNCOMPRESSED,
CHUNK_SIZE: 1,
DUPLICATE_POLICY: TIME_SERIES_DUPLICATE_POLICIES.BLOCK,
LABELS: { label: 'value' },
IGNORE: { maxTimeDiff: 1, maxValDiff: 1}
}),
['TS.CREATE', 'key', 'RETENTION', '1', 'ENCODING', 'UNCOMPRESSED', 'CHUNK_SIZE', '1', 'DUPLICATE_POLICY', 'BLOCK', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
});
testUtils.testWithClient('client.ts.create', async client => {
assert.equal(
await client.ts.create('key'),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,28 +1,30 @@
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
import {
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
TimeSeriesDuplicatePolicies,
Labels,
pushLabelsArgument,
pushDuplicatePolicy,
pushIgnoreArgument
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
TimeSeriesDuplicatePolicies,
pushDuplicatePolicy,
Labels,
pushLabelsArgument,
pushIgnoreArgument
} from '.';
import { TsIgnoreOptions } from './ADD';
export const FIRST_KEY_INDEX = 1;
interface CreateOptions {
RETENTION?: number;
ENCODING?: TimeSeriesEncoding;
CHUNK_SIZE?: number;
DUPLICATE_POLICY?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
IGNORE?: TsIgnoreOptions;
export interface TsCreateOptions {
RETENTION?: number;
ENCODING?: TimeSeriesEncoding;
CHUNK_SIZE?: number;
DUPLICATE_POLICY?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
IGNORE?: TsIgnoreOptions;
}
export function transformArguments(key: string, options?: CreateOptions): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(key: RedisArgument, options?: TsCreateOptions) {
const args = ['TS.CREATE', key];
pushRetentionArgument(args, options?.RETENTION);
@@ -38,6 +40,6 @@ export function transformArguments(key: string, options?: CreateOptions): Array<
pushIgnoreArgument(args, options?.IGNORE);
return args;
}
export declare function transformReply(): 'OK';
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,34 +1,31 @@
import { strict as assert } from 'assert';
import { TimeSeriesAggregationType } from '.';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CREATERULE';
import CREATERULE, { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('CREATERULE', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('source', 'destination', TimeSeriesAggregationType.AVERAGE, 1),
['TS.CREATERULE', 'source', 'destination', 'AGGREGATION', 'AVG', '1']
);
});
it('with alignTimestamp', () => {
assert.deepEqual(
transformArguments('source', 'destination', TimeSeriesAggregationType.AVERAGE, 1, 1),
['TS.CREATERULE', 'source', 'destination', 'AGGREGATION', 'AVG', '1', '1']
);
});
describe('TS.CREATERULE', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
CREATERULE.transformArguments('source', 'destination', TIME_SERIES_AGGREGATION_TYPE.AVG, 1),
['TS.CREATERULE', 'source', 'destination', 'AGGREGATION', 'AVG', '1']
);
});
testUtils.testWithClient('client.ts.createRule', async client => {
await Promise.all([
client.ts.create('source'),
client.ts.create('destination')
]);
it('with alignTimestamp', () => {
assert.deepEqual(
CREATERULE.transformArguments('source', 'destination', TIME_SERIES_AGGREGATION_TYPE.AVG, 1, 1),
['TS.CREATERULE', 'source', 'destination', 'AGGREGATION', 'AVG', '1', '1']
);
});
});
assert.equal(
await client.ts.createRule('source', 'destination', TimeSeriesAggregationType.AVERAGE, 1),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('client.ts.createRule', async client => {
const [, , reply] = await Promise.all([
client.ts.create('source'),
client.ts.create('destination'),
client.ts.createRule('source', 'destination', TIME_SERIES_AGGREGATION_TYPE.AVG, 1)
]);
assert.equal(reply, 'OK');
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,28 +1,47 @@
import { TimeSeriesAggregationType } from '.';
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
export const FIRST_KEY_INDEX = 1;
export const TIME_SERIES_AGGREGATION_TYPE = {
AVG: 'AVG',
FIRST: 'FIRST',
LAST: 'LAST',
MIN: 'MIN',
MAX: 'MAX',
SUM: 'SUM',
RANGE: 'RANGE',
COUNT: 'COUNT',
STD_P: 'STD.P',
STD_S: 'STD.S',
VAR_P: 'VAR.P',
VAR_S: 'VAR.S',
TWA: 'TWA'
} as const;
export function transformArguments(
sourceKey: string,
destinationKey: string,
export type TimeSeriesAggregationType = typeof TIME_SERIES_AGGREGATION_TYPE[keyof typeof TIME_SERIES_AGGREGATION_TYPE];
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(
sourceKey: RedisArgument,
destinationKey: RedisArgument,
aggregationType: TimeSeriesAggregationType,
bucketDuration: number,
alignTimestamp?: number
): Array<string> {
) {
const args = [
'TS.CREATERULE',
sourceKey,
destinationKey,
'AGGREGATION',
aggregationType,
bucketDuration.toString()
'TS.CREATERULE',
sourceKey,
destinationKey,
'AGGREGATION',
aggregationType,
bucketDuration.toString()
];
if (alignTimestamp) {
args.push(alignTimestamp.toString());
if (alignTimestamp !== undefined) {
args.push(alignTimestamp.toString());
}
return args;
}
export declare function transformReply(): 'OK';
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,81 +1,92 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './DECRBY';
import DECRBY 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']
);
});
describe('TS.DECRBY', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1),
['TS.DECRBY', 'key', '1']
);
});
testUtils.testWithClient('client.ts.decrBy', async client => {
assert.equal(
await client.ts.decrBy('key', 1, {
TIMESTAMP: 0
}),
0
);
}, GLOBAL.SERVERS.OPEN);
it('with TIMESTAMP', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
TIMESTAMP: '*'
}),
['TS.DECRBY', 'key', '1', 'TIMESTAMP', '*']
);
});
it('with RETENTION', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
RETENTION: 1
}),
['TS.DECRBY', 'key', '1', 'RETENTION', '1']
);
});
it('with UNCOMPRESSED', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
UNCOMPRESSED: true
}),
['TS.DECRBY', 'key', '1', 'UNCOMPRESSED']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
CHUNK_SIZE: 100
}),
['TS.DECRBY', 'key', '1', 'CHUNK_SIZE', '100']
);
});
it('with LABELS', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
LABELS: { label: 'value' }
}),
['TS.DECRBY', 'key', '1', 'LABELS', 'label', 'value']
);
});
it ('with IGNORE', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
IGNORE: {
maxTimeDiff: 1,
maxValDiff: 1
}
}),
['TS.DECRBY', 'key', '1', 'IGNORE', '1', '1']
)
});
it('with TIMESTAMP, RETENTION, UNCOMPRESSED, CHUNK_SIZE and LABELS', () => {
assert.deepEqual(
DECRBY.transformArguments('key', 1, {
TIMESTAMP: '*',
RETENTION: 1,
UNCOMPRESSED: true,
CHUNK_SIZE: 2,
LABELS: { label: 'value' },
IGNORE: { maxTimeDiff: 1, maxValDiff: 1 }
}),
['TS.DECRBY', 'key', '1', 'TIMESTAMP', '*', 'RETENTION', '1', 'UNCOMPRESSED', 'CHUNK_SIZE', '2', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
});
testUtils.testWithClient('client.ts.decrBy', async client => {
assert.equal(
typeof await client.ts.decrBy('key', 1),
'number'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,10 +1,9 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { IncrDecrOptions, transformIncrDecrArguments } from '.';
import { Command } from '@redis/client/dist/lib/RESP/types';
import INCRBY, { transformIncrByArguments } from './INCRBY';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, value: number, options?: IncrDecrOptions): RedisCommandArguments {
return transformIncrDecrArguments('TS.DECRBY', key, value, options);
}
export declare function transformReply(): number;
export default {
FIRST_KEY_INDEX: INCRBY.FIRST_KEY_INDEX,
IS_READ_ONLY: INCRBY.IS_READ_ONLY,
transformArguments: transformIncrByArguments.bind(undefined, 'TS.DECRBY'),
transformReply: INCRBY.transformReply
} as const satisfies Command;

View File

@@ -1,21 +1,21 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './DEL';
import DEL from './DEL';
describe('DEL', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', '-', '+'),
['TS.DEL', 'key', '-', '+']
);
});
describe('TS.DEL', () => {
it('transformArguments', () => {
assert.deepEqual(
DEL.transformArguments('key', '-', '+'),
['TS.DEL', 'key', '-', '+']
);
});
testUtils.testWithClient('client.ts.del', async client => {
await client.ts.create('key');
testUtils.testWithClient('client.ts.del', async client => {
const [, reply] = await Promise.all([
client.ts.create('key'),
client.ts.del('key', '-', '+')
]);
assert.equal(
await client.ts.del('key', '-', '+'),
0
);
}, GLOBAL.SERVERS.OPEN);
assert.equal(reply, 0);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,15 +1,16 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { Timestamp, transformTimestampArgument } from '.';
import { RedisArgument, NumberReply, Command, } from '@redis/client/dist/lib/RESP/types';
export const FIRTS_KEY_INDEX = 1;
export function transformArguments(key: string, fromTimestamp: Timestamp, toTimestamp: Timestamp): RedisCommandArguments {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(key: RedisArgument, fromTimestamp: Timestamp, toTimestamp: Timestamp) {
return [
'TS.DEL',
key,
transformTimestampArgument(fromTimestamp),
transformTimestampArgument(toTimestamp)
'TS.DEL',
key,
transformTimestampArgument(fromTimestamp),
transformTimestampArgument(toTimestamp)
];
}
export declare function transformReply(): number;
},
transformReply: undefined as unknown as () => NumberReply
} as const satisfies Command;

View File

@@ -1,26 +1,24 @@
import { strict as assert } from 'assert';
import { TimeSeriesAggregationType } from '.';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './DELETERULE';
import DELETERULE from './DELETERULE';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('DELETERULE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('source', 'destination'),
['TS.DELETERULE', 'source', 'destination']
);
});
describe('TS.DELETERULE', () => {
it('transformArguments', () => {
assert.deepEqual(
DELETERULE.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.AVERAGE, 1)
]);
testUtils.testWithClient('client.ts.deleteRule', async client => {
const [, , , reply] = await Promise.all([
client.ts.create('source'),
client.ts.create('destination'),
client.ts.createRule('source', 'destination', TIME_SERIES_AGGREGATION_TYPE.AVG, 1),
client.ts.deleteRule('source', 'destination')
]);
assert.equal(
await client.ts.deleteRule('source', 'destination'),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
assert.equal(reply, 'OK');
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,11 +1,14 @@
export const FIRST_KEY_INDEX = 1;
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types';
export function transformArguments(sourceKey: string, destinationKey: string): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(sourceKey: RedisArgument, destinationKey: RedisArgument) {
return [
'TS.DELETERULE',
sourceKey,
destinationKey
'TS.DELETERULE',
sourceKey,
destinationKey
];
}
export declare function transformReply(): 'OK';
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,46 +1,46 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './GET';
import GET from './GET';
describe('GET', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('key'),
['TS.GET', 'key']
);
});
it('with LATEST', () => {
assert.deepEqual(
transformArguments('key', {
LATEST: true
}),
['TS.GET', 'key', 'LATEST']
);
});
describe('TS.GET', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
GET.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);
it('with LATEST', () => {
assert.deepEqual(
GET.transformArguments('key', {
LATEST: true
}),
['TS.GET', 'key', 'LATEST']
);
});
});
describe('client.ts.get', () => {
testUtils.testWithClient('null', async client => {
const [, reply] = await Promise.all([
client.ts.create('key'),
client.ts.get('key')
]);
assert.equal(reply, null);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('with sample', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 1),
client.ts.get('key')
]);
assert.deepEqual(reply, {
timestamp: 0,
value: 1
});
}, GLOBAL.SERVERS.OPEN);
});
});

View File

@@ -1,20 +1,35 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { pushLatestArgument, SampleRawReply, SampleReply, transformSampleReply } from '.';
import { RedisArgument, TuplesReply, NumberReply, DoubleReply, UnwrapReply, Resp2Reply, Command } from '@redis/client/dist/lib/RESP/types';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
interface GetOptions {
LATEST?: boolean;
export interface TsGetOptions {
LATEST?: boolean;
}
export function transformArguments(key: string, options?: GetOptions): RedisCommandArguments {
return pushLatestArgument(['TS.GET', key], options?.LATEST);
}
export type TsGetReply = TuplesReply<[]> | TuplesReply<[NumberReply, DoubleReply]>;
export function transformReply(reply: [] | SampleRawReply): null | SampleReply {
if (reply.length === 0) return null;
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments(key: RedisArgument, options?: TsGetOptions) {
const args = ['TS.GET', key];
if (options?.LATEST) {
args.push('LATEST');
}
return transformSampleReply(reply);
}
return args;
},
transformReply: {
2(reply: UnwrapReply<Resp2Reply<TsGetReply>>) {
return reply.length === 0 ? null : {
timestamp: reply[0],
value: Number(reply[1])
};
},
3(reply: UnwrapReply<TsGetReply>) {
return reply.length === 0 ? null : {
timestamp: reply[0],
value: reply[1]
};
}
}
} as const satisfies Command;

View File

@@ -1,91 +1,102 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './INCRBY';
import INCRBY 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']
);
});
describe('TS.INCRBY', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1),
['TS.INCRBY', 'key', '1']
);
});
testUtils.testWithClient('client.ts.incrBy', async client => {
assert.equal(
await client.ts.incrBy('key', 1, {
TIMESTAMP: 0
}),
0
);
}, GLOBAL.SERVERS.OPEN);
it('with TIMESTAMP', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
TIMESTAMP: '*'
}),
['TS.INCRBY', 'key', '1', 'TIMESTAMP', '*']
);
});
it('with RETENTION', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
RETENTION: 1
}),
['TS.INCRBY', 'key', '1', 'RETENTION', '1']
);
});
it('with UNCOMPRESSED', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
UNCOMPRESSED: true
}),
['TS.INCRBY', 'key', '1', 'UNCOMPRESSED']
);
});
it('without UNCOMPRESSED', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
UNCOMPRESSED: false
}),
['TS.INCRBY', 'key', '1']
);
});
it('with CHUNK_SIZE', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
CHUNK_SIZE: 1
}),
['TS.INCRBY', 'key', '1', 'CHUNK_SIZE', '1']
);
});
it('with LABELS', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
LABELS: { label: 'value' }
}),
['TS.INCRBY', 'key', '1', 'LABELS', 'label', 'value']
);
});
it ('with IGNORE', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
IGNORE: {
maxTimeDiff: 1,
maxValDiff: 1
}
}),
['TS.INCRBY', 'key', '1', 'IGNORE', '1', '1']
)
});
it('with TIMESTAMP, RETENTION, UNCOMPRESSED, CHUNK_SIZE and LABELS', () => {
assert.deepEqual(
INCRBY.transformArguments('key', 1, {
TIMESTAMP: '*',
RETENTION: 1,
UNCOMPRESSED: true,
CHUNK_SIZE: 1,
LABELS: { label: 'value' },
IGNORE: { maxTimeDiff: 1, maxValDiff: 1 }
}),
['TS.INCRBY', 'key', '1', 'TIMESTAMP', '*', 'RETENTION', '1', 'UNCOMPRESSED',
'CHUNK_SIZE', '1', 'LABELS', 'label', 'value', 'IGNORE', '1', '1']
);
});
});
testUtils.testWithClient('client.ts.incrBy', async client => {
assert.equal(
typeof await client.ts.incrBy('key', 1),
'number'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,10 +1,50 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { IncrDecrOptions, transformIncrDecrArguments } from '.';
import { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';
import { Timestamp, transformTimestampArgument, pushRetentionArgument, pushChunkSizeArgument, Labels, pushLabelsArgument, pushIgnoreArgument } from '.';
import { TsIgnoreOptions } from './ADD';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, value: number, options?: IncrDecrOptions): RedisCommandArguments {
return transformIncrDecrArguments('TS.INCRBY', key, value, options);
export interface TsIncrByOptions {
TIMESTAMP?: Timestamp;
RETENTION?: number;
UNCOMPRESSED?: boolean;
CHUNK_SIZE?: number;
LABELS?: Labels;
IGNORE?: TsIgnoreOptions;
}
export declare function transformReply(): number;
export function transformIncrByArguments(
command: RedisArgument,
key: RedisArgument,
value: number,
options?: TsIncrByOptions
) {
const args = [
command,
key,
value.toString()
];
if (options?.TIMESTAMP !== undefined && options?.TIMESTAMP !== null) {
args.push('TIMESTAMP', transformTimestampArgument(options.TIMESTAMP));
}
pushRetentionArgument(args, options?.RETENTION);
if (options?.UNCOMPRESSED) {
args.push('UNCOMPRESSED');
}
pushChunkSizeArgument(args, options?.CHUNK_SIZE);
pushLabelsArgument(args, options?.LABELS);
pushIgnoreArgument(args, options?.IGNORE);
return args;
}
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments: transformIncrByArguments.bind(undefined, 'TS.INCRBY'),
transformReply: undefined as unknown as () => NumberReply
} as const satisfies Command;

View File

@@ -1,12 +1,13 @@
import { strict as assert } from 'assert';
import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from '.';
import { strict as assert } from 'node:assert';
import { TIME_SERIES_DUPLICATE_POLICIES } from '.';
import testUtils, { GLOBAL } from '../test-utils';
import { InfoReply, transformArguments } from './INFO';
import INFO, { InfoReply } from './INFO';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('INFO', () => {
describe('TS.INFO', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
INFO.transformArguments('key'),
['TS.INFO', 'key']
);
});
@@ -15,14 +16,14 @@ describe('INFO', () => {
await Promise.all([
client.ts.create('key', {
LABELS: { id: '1' },
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST
DUPLICATE_POLICY: TIME_SERIES_DUPLICATE_POLICIES.LAST
}),
client.ts.create('key2'),
client.ts.createRule('key', 'key2', TimeSeriesAggregationType.COUNT, 5),
client.ts.createRule('key', 'key2', TIME_SERIES_AGGREGATION_TYPE.COUNT, 5),
client.ts.add('key', 1, 10)
]);
assertInfo(await client.ts.info('key'));
assertInfo(await client.ts.info('key') as any);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,82 +1,128 @@
import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from '.';
import { ArrayReply, BlobStringReply, Command, DoubleReply, NumberReply, ReplyUnion, SimpleStringReply, TypeMapping } from "@redis/client/lib/RESP/types";
import { TimeSeriesDuplicatePolicies } from ".";
import { TimeSeriesAggregationType } from "./CREATERULE";
import { transformDoubleReply } from '@redis/client/lib/commands/generic-transformers';
export const FIRST_KEY_INDEX = 1;
export type InfoRawReplyTypes = SimpleStringReply |
NumberReply |
TimeSeriesDuplicatePolicies | null |
Array<[name: BlobStringReply, value: BlobStringReply]> |
BlobStringReply |
Array<[key: BlobStringReply, timeBucket: NumberReply, aggregationType: TimeSeriesAggregationType]> |
DoubleReply
export const IS_READ_ONLY = true;
export type InfoRawReply = Array<InfoRawReplyTypes>;
export function transformArguments(key: string): Array<string> {
return ['TS.INFO', key];
}
export type InfoRawReply = [
'totalSamples',
number,
'memoryUsage',
number,
'firstTimestamp',
number,
'lastTimestamp',
number,
'retentionTime',
number,
'chunkCount',
number,
'chunkSize',
number,
'chunkType',
string,
'duplicatePolicy',
TimeSeriesDuplicatePolicies | null,
'labels',
Array<[name: string, value: string]>,
'sourceKey',
string | null,
'rules',
Array<[key: string, timeBucket: number, aggregationType: TimeSeriesAggregationType]>
export type InfoRawReplyOld = [
'totalSamples',
NumberReply,
'memoryUsage',
NumberReply,
'firstTimestamp',
NumberReply,
'lastTimestamp',
NumberReply,
'retentionTime',
NumberReply,
'chunkCount',
NumberReply,
'chunkSize',
NumberReply,
'chunkType',
SimpleStringReply,
'duplicatePolicy',
TimeSeriesDuplicatePolicies | null,
'labels',
ArrayReply<[name: BlobStringReply, value: BlobStringReply]>,
'sourceKey',
BlobStringReply | null,
'rules',
ArrayReply<[key: BlobStringReply, timeBucket: NumberReply, aggregationType: TimeSeriesAggregationType]>,
'ignoreMaxTimeDiff',
NumberReply,
'ignoreMaxValDiff',
DoubleReply,
];
export interface InfoReply {
totalSamples: number;
memoryUsage: number;
firstTimestamp: number;
lastTimestamp: number;
retentionTime: number;
chunkCount: number;
chunkSize: number;
chunkType: string;
duplicatePolicy: TimeSeriesDuplicatePolicies | null;
labels: Array<{
name: string;
value: string;
}>;
sourceKey: string | null;
rules: Array<{
key: string;
timeBucket: number;
aggregationType: TimeSeriesAggregationType
}>;
totalSamples: NumberReply;
memoryUsage: NumberReply;
firstTimestamp: NumberReply;
lastTimestamp: NumberReply;
retentionTime: NumberReply;
chunkCount: NumberReply;
chunkSize: NumberReply;
chunkType: SimpleStringReply;
duplicatePolicy: TimeSeriesDuplicatePolicies | null;
labels: Array<{
name: BlobStringReply;
value: BlobStringReply;
}>;
sourceKey: BlobStringReply | null;
rules: Array<{
key: BlobStringReply;
timeBucket: NumberReply;
aggregationType: TimeSeriesAggregationType
}>;
/** Added in 7.4 */
ignoreMaxTimeDiff: NumberReply;
/** Added in 7.4 */
ignoreMaxValDiff: DoubleReply;
}
export function transformReply(reply: InfoRawReply): InfoReply {
return {
totalSamples: reply[1],
memoryUsage: reply[3],
firstTimestamp: reply[5],
lastTimestamp: reply[7],
retentionTime: reply[9],
chunkCount: reply[11],
chunkSize: reply[13],
chunkType: reply[15],
duplicatePolicy: reply[17],
labels: reply[19].map(([name, value]) => ({
name,
value
})),
sourceKey: reply[21],
rules: reply[23].map(([key, timeBucket, aggregationType]) => ({
key,
timeBucket,
aggregationType
}))
};
}
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments(key: string) {
return ['TS.INFO', key];
},
transformReply: {
2: (reply: InfoRawReply, _, typeMapping?: TypeMapping): InfoReply => {
const ret = {} as any;
for (let i=0; i < reply.length; i += 2) {
const key = (reply[i] as any).toString();
switch (key) {
case 'totalSamples':
case 'memoryUsage':
case 'firstTimestamp':
case 'lastTimestamp':
case 'retentionTime':
case 'chunkCount':
case 'chunkSize':
case 'chunkType':
case 'duplicatePolicy':
case 'sourceKey':
case 'ignoreMaxTimeDiff':
ret[key] = reply[i+1];
break;
case 'labels':
ret[key] = (reply[i+1] as Array<[name: BlobStringReply, value: BlobStringReply]>).map(
([name, value]) => ({
name,
value
})
);
break;
case 'rules':
ret[key] = (reply[i+1] as Array<[key: BlobStringReply, timeBucket: NumberReply, aggregationType: TimeSeriesAggregationType]>).map(
([key, timeBucket, aggregationType]) => ({
key,
timeBucket,
aggregationType
})
);
break;
case 'ignoreMaxValDiff':
ret[key] = transformDoubleReply[2](reply[27] as unknown as BlobStringReply, undefined, typeMapping);
break;
}
}
return ret;
},
3: undefined as unknown as () => ReplyUnion
},
unstableResp3: true
} as const satisfies Command;

View File

@@ -1,30 +1,31 @@
import { strict as assert } from 'assert';
import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from '.';
import { strict as assert } from 'node:assert';
import { TIME_SERIES_DUPLICATE_POLICIES } from '.';
import testUtils, { GLOBAL } from '../test-utils';
import { assertInfo } from './INFO.spec';
import { transformArguments } from './INFO_DEBUG';
import INFO_DEBUG from './INFO_DEBUG';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('INFO_DEBUG', () => {
describe('TS.INFO_DEBUG', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
INFO_DEBUG.transformArguments('key'),
['TS.INFO', 'key', 'DEBUG']
);
});
testUtils.testWithClient('client.ts.get', async client => {
testUtils.testWithClient('client.ts.infoDebug', async client => {
await Promise.all([
client.ts.create('key', {
LABELS: { id: '1' },
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.LAST
DUPLICATE_POLICY: TIME_SERIES_DUPLICATE_POLICIES.LAST
}),
client.ts.create('key2'),
client.ts.createRule('key', 'key2', TimeSeriesAggregationType.COUNT, 5),
client.ts.createRule('key', 'key2', TIME_SERIES_AGGREGATION_TYPE.COUNT, 5),
client.ts.add('key', 1, 10)
]);
const infoDebug = await client.ts.infoDebug('key');
assertInfo(infoDebug);
assertInfo(infoDebug as any);
assert.equal(typeof infoDebug.keySelfName, 'string');
assert.ok(Array.isArray(infoDebug.chunks));
for (const chunk of infoDebug.chunks) {

View File

@@ -1,57 +1,79 @@
import {
transformArguments as transformInfoArguments,
InfoRawReply,
InfoReply,
transformReply as transformInfoReply
} from './INFO';
import { BlobStringReply, Command, NumberReply, SimpleStringReply, TypeMapping } from "@redis/client/lib/RESP/types";
import INFO, { InfoRawReply, InfoRawReplyTypes, InfoReply } from "./INFO";
import { ReplyUnion } from '@redis/client/lib/RESP/types';
export { IS_READ_ONLY, FIRST_KEY_INDEX } from './INFO';
export function transformArguments(key: string): Array<string> {
const args = transformInfoArguments(key);
args.push('DEBUG');
return args;
}
type chunkType = Array<[
'startTimestamp',
NumberReply,
'endTimestamp',
NumberReply,
'samples',
NumberReply,
'size',
NumberReply,
'bytesPerSample',
SimpleStringReply
]>;
type InfoDebugRawReply = [
...InfoRawReply,
'keySelfName',
string,
'chunks',
Array<[
'startTimestamp',
number,
'endTimestamp',
number,
'samples',
number,
'size',
number,
'bytesPerSample',
string
]>
...InfoRawReply,
'keySelfName',
BlobStringReply,
'Chunks',
chunkType
];
interface InfoDebugReply extends InfoReply {
keySelfName: string;
chunks: Array<{
startTimestamp: number;
endTimestamp: number;
samples: number;
size: number;
bytesPerSample: string;
}>;
export type InfoDebugRawReplyType = InfoRawReplyTypes | chunkType
export interface InfoDebugReply extends InfoReply {
keySelfName: BlobStringReply,
chunks: Array<{
startTimestamp: NumberReply;
endTimestamp: NumberReply;
samples: NumberReply;
size: NumberReply;
bytesPerSample: SimpleStringReply;
}>;
}
export function transformReply(rawReply: InfoDebugRawReply): InfoDebugReply {
const reply = transformInfoReply(rawReply as unknown as InfoRawReply);
(reply as InfoDebugReply).keySelfName = rawReply[25];
(reply as InfoDebugReply).chunks = rawReply[27].map(chunk => ({
startTimestamp: chunk[1],
endTimestamp: chunk[3],
samples: chunk[5],
size: chunk[7],
bytesPerSample: chunk[9]
}));
return reply as InfoDebugReply;
}
export default {
FIRST_KEY_INDEX: INFO.FIRST_KEY_INDEX,
IS_READ_ONLY: INFO.IS_READ_ONLY,
transformArguments(key: string) {
const args = INFO.transformArguments(key);
args.push('DEBUG');
return args;
},
transformReply: {
2: (reply: InfoDebugRawReply, _, typeMapping?: TypeMapping): InfoDebugReply => {
const ret = INFO.transformReply[2](reply as unknown as InfoRawReply, _, typeMapping) as any;
for (let i=0; i < reply.length; i += 2) {
const key = (reply[i] as any).toString();
switch (key) {
case 'keySelfName': {
ret[key] = reply[i+1];
break;
}
case 'Chunks': {
ret['chunks'] = (reply[i+1] as chunkType).map(
chunk => ({
startTimestamp: chunk[1],
endTimestamp: chunk[3],
samples: chunk[5],
size: chunk[7],
bytesPerSample: chunk[9]
})
);
break;
}
}
}
return ret;
},
3: undefined as unknown as () => ReplyUnion
},
unstableResp3: true
} as const satisfies Command;

View File

@@ -1,39 +1,41 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MADD';
import MADD from './MADD';
import { SimpleError } from '@redis/client/lib/errors';
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']
);
});
describe('TS.MADD', () => {
it('transformArguments', () => {
assert.deepEqual(
MADD.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 => {
const [, reply] = await Promise.all([
client.ts.create('key'),
client.ts.mAdd([{
key: 'key',
timestamp: 0,
value: 1
}, {
key: 'key',
timestamp: 0,
value: 1
}])
]);
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);
assert.ok(Array.isArray(reply));
assert.equal(reply.length, 2);
assert.equal(reply[0], 0);
assert.ok(reply[1] instanceof SimpleError);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,25 +1,27 @@
import { Timestamp, transformTimestampArgument } from '.';
import { ArrayReply, NumberReply, SimpleErrorReply, Command } from '@redis/client/dist/lib/RESP/types';
export const FIRST_KEY_INDEX = 1;
interface MAddSample {
key: string;
timestamp: Timestamp;
value: number;
export interface TsMAddSample {
key: string;
timestamp: Timestamp;
value: number;
}
export function transformArguments(toAdd: Array<MAddSample>): Array<string> {
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: false,
transformArguments(toAdd: Array<TsMAddSample>) {
const args = ['TS.MADD'];
for (const { key, timestamp, value } of toAdd) {
args.push(
key,
transformTimestampArgument(timestamp),
value.toString()
);
args.push(
key,
transformTimestampArgument(timestamp),
value.toString()
);
}
return args;
}
export declare function transformReply(): Array<number>;
},
transformReply: undefined as unknown as () => ArrayReply<NumberReply | SimpleErrorReply>
} as const satisfies Command;

View File

@@ -1,40 +1,45 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MGET';
import MGET from './MGET';
describe('MGET', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('label=value'),
['TS.MGET', 'FILTER', 'label=value']
);
});
it('with LATEST', () => {
assert.deepEqual(
transformArguments('label=value', {
LATEST: true
}),
['TS.MGET', 'LATEST', 'FILTER', 'label=value']
);
});
describe('TS.MGET', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
MGET.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' }
});
it('with LATEST', () => {
assert.deepEqual(
MGET.transformArguments('label=value', {
LATEST: true
}),
['TS.MGET', 'LATEST', 'FILTER', 'label=value']
);
});
});
assert.deepEqual(
await client.ts.mGet('label=value'),
[{
key: 'key',
sample: {
timestamp: 0,
value: 0
}
}]
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('client.ts.mGet', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mGet('label=value')
]);
assert.deepStrictEqual(reply, Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
sample: {
timestamp: 0,
value: 0
}
}
}
}));
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,31 +1,61 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { Filter, pushFilterArgument, pushLatestArgument, RawLabels, SampleRawReply, SampleReply, transformSampleReply } from '.';
import { CommandArguments, Command, BlobStringReply, ArrayReply, Resp2Reply, MapReply, TuplesReply, TypeMapping } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
import { resp2MapToValue, resp3MapToValue, SampleRawReply, transformSampleReply } from '.';
export const IS_READ_ONLY = true;
export interface MGetOptions {
LATEST?: boolean;
export interface TsMGetOptions {
LATEST?: boolean;
}
export function transformArguments(filter: Filter, options?: MGetOptions): RedisCommandArguments {
export function pushLatestArgument(args: CommandArguments, latest?: boolean) {
if (latest) {
args.push('LATEST');
}
return args;
}
export function pushFilterArgument(args: CommandArguments, filter: RedisVariadicArgument) {
args.push('FILTER');
return pushVariadicArguments(args, filter);
}
export type MGetRawReply2 = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: never,
sample: Resp2Reply<SampleRawReply>
]>
>;
export type MGetRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: never,
sample: SampleRawReply
]>
>;
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(filter: RedisVariadicArgument, options?: TsMGetOptions) {
const args = pushLatestArgument(['TS.MGET'], options?.LATEST);
return pushFilterArgument(args, filter);
}
export type MGetRawReply = Array<[
key: string,
labels: RawLabels,
sample: SampleRawReply
]>;
export interface MGetReply {
key: string,
sample: SampleReply
}
export function transformReply(reply: MGetRawReply): Array<MGetReply> {
return reply.map(([key, _, sample]) => ({
key,
sample: transformSampleReply(sample)
}));
}
},
transformReply: {
2(reply: MGetRawReply2, _, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([,, sample]) => {
return {
sample: transformSampleReply[2](sample)
};
}, typeMapping);
},
3(reply: MGetRawReply3) {
return resp3MapToValue(reply, ([, sample]) => {
return {
sample: transformSampleReply[3](sample)
};
});
}
}
} as const satisfies Command;

View File

@@ -0,0 +1,46 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MGET_SELECTED_LABELS from './MGET_SELECTED_LABELS';
describe('TS.MGET_SELECTED_LABELS', () => {
it('transformArguments', () => {
assert.deepEqual(
MGET_SELECTED_LABELS.transformArguments('label=value', 'label'),
['TS.MGET', 'SELECTED_LABELS', 'label', 'FILTER', 'label=value']
);
});
testUtils.testWithClient('client.ts.mGetSelectedLabels', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mGetSelectedLabels('label=value', ['label', 'NX'])
]);
assert.deepStrictEqual(reply, Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
},
NX: {
configurable: true,
enumerable: true,
value: null
}
}),
sample: {
timestamp: 0,
value: 0
}
}
}
}));
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,16 @@
import { Command, BlobStringReply, NullReply } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
import { TsMGetOptions, pushLatestArgument, pushFilterArgument } from './MGET';
import { pushSelectedLabelsArguments } from '.';
import { createTransformMGetLabelsReply } from './MGET_WITHLABELS';
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(filter: RedisVariadicArgument, selectedLabels: RedisVariadicArgument, options?: TsMGetOptions) {
let args = pushLatestArgument(['TS.MGET'], options?.LATEST);
args = pushSelectedLabelsArguments(args, selectedLabels);
return pushFilterArgument(args, filter);
},
transformReply: createTransformMGetLabelsReply<BlobStringReply | NullReply>(),
} as const satisfies Command;

View File

@@ -1,39 +1,41 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MGET_WITHLABELS';
import MGET_WITHLABELS from './MGET_WITHLABELS';
describe('MGET_WITHLABELS', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('label=value'),
['TS.MGET', 'WITHLABELS', 'FILTER', 'label=value']
);
});
describe('TS.MGET_WITHLABELS', () => {
it('transformArguments', () => {
assert.deepEqual(
MGET_WITHLABELS.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);
testUtils.testWithClient('client.ts.mGetWithLabels', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mGetWithLabels('label=value')
]);
assert.deepStrictEqual(reply, Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
}
}),
sample: {
timestamp: 0,
value: 0
}
}
}
}));
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,37 +1,61 @@
import {
SelectedLabels,
pushWithLabelsArgument,
Labels,
transformLablesReply,
transformSampleReply,
Filter,
pushFilterArgument
} from '.';
import { MGetOptions, MGetRawReply, MGetReply } from './MGET';
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
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 { TsMGetOptions, pushLatestArgument, pushFilterArgument } from './MGET';
import { RawLabelValue, resp2MapToValue, resp3MapToValue, SampleRawReply, transformRESP2Labels, transformSampleReply } from '.';
export const IS_READ_ONLY = true;
interface MGetWithLabelsOptions extends MGetOptions {
SELECTED_LABELS?: SelectedLabels;
export interface TsMGetWithLabelsOptions extends TsMGetOptions {
SELECTED_LABELS?: RedisVariadicArgument;
}
export function transformArguments(
filter: Filter,
options?: MGetWithLabelsOptions
): RedisCommandArguments {
const args = pushWithLabelsArgument(['TS.MGET'], options?.SELECTED_LABELS);
export type MGetLabelsRawReply2<T extends RawLabelValue> = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: ArrayReply<
TuplesReply<[
label: BlobStringReply,
value: T
]>
>,
sample: Resp2Reply<SampleRawReply>
]>
>;
export type MGetLabelsRawReply3<T extends RawLabelValue> = MapReply<
BlobStringReply,
TuplesReply<[
labels: MapReply<BlobStringReply, T>,
sample: SampleRawReply
]>
>;
export function createTransformMGetLabelsReply<T extends RawLabelValue>() {
return {
2(reply: MGetLabelsRawReply2<T>, _, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([, labels, sample]) => {
return {
labels: transformRESP2Labels(labels),
sample: transformSampleReply[2](sample)
};
}, typeMapping);
},
3(reply: MGetLabelsRawReply3<T>) {
return resp3MapToValue(reply, ([labels, sample]) => {
return {
labels,
sample: transformSampleReply[3](sample)
};
});
}
} satisfies Command['transformReply'];
}
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(filter: RedisVariadicArgument, options?: TsMGetOptions) {
const args = pushLatestArgument(['TS.MGET'], options?.LATEST);
args.push('WITHLABELS');
return pushFilterArgument(args, filter);
}
export interface MGetWithLabelsReply extends MGetReply {
labels: Labels;
};
export function transformReply(reply: MGetRawReply): Array<MGetWithLabelsReply> {
return reply.map(([key, labels, sample]) => ({
key,
labels: transformLablesReply(labels),
sample: transformSampleReply(sample)
}));
}
},
transformReply: createTransformMGetLabelsReply<BlobStringReply>(),
} as const satisfies Command;

View File

@@ -1,50 +1,62 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MRANGE';
import { TimeSeriesAggregationType, TimeSeriesReducers } from '.';
import MRANGE from './MRANGE';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
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.AVERAGE,
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']
);
});
describe('TS.MRANGE', () => {
it('transformArguments', () => {
assert.deepEqual(
MRANGE.transformArguments('-', '+', 'label=value', {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-',
'AGGREGATION', 'AVG', '1',
'FILTER', 'label=value'
]
);
});
testUtils.testWithClient('client.ts.mRange', async client => {
await client.ts.add('key', 0, 0, {
LABELS: { label: 'value'}
});
testUtils.testWithClient('client.ts.mRange', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: {
label: 'value'
}
}),
client.ts.mRange('-', '+', 'label=value', {
COUNT: 1
})
]);
assert.deepEqual(
await client.ts.mRange('-', '+', 'label=value', {
COUNT: 1
}),
[{
key: 'key',
samples: [{
timestamp: 0,
value: 0
}]
}]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepStrictEqual(
reply,
Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: [{
timestamp: 0,
value: 0
}]
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,21 +1,58 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { MRangeOptions, Timestamp, pushMRangeArguments, Filter } from '.';
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 { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.';
import { TsRangeOptions, pushRangeArguments } from './RANGE';
import { pushFilterArgument } from './MGET';
export const IS_READ_ONLY = true;
export type TsMRangeRawReply2 = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: never, // empty array without WITHLABELS or SELECTED_LABELS
samples: ArrayReply<Resp2Reply<SampleRawReply>>
]>
>;
export function transformArguments(
export type TsMRangeRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: never, // empty hash without WITHLABELS or SELECTED_LABELS
metadata: never, // ?!
samples: ArrayReply<SampleRawReply>
]>
>;
export function createTransformMRangeArguments(command: RedisArgument) {
return (
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
filters: Filter,
options?: MRangeOptions
): RedisCommandArguments {
return pushMRangeArguments(
['TS.MRANGE'],
fromTimestamp,
toTimestamp,
filters,
options
filter: RedisVariadicArgument,
options?: TsRangeOptions
) => {
const args = pushRangeArguments(
[command],
fromTimestamp,
toTimestamp,
options
);
return pushFilterArgument(args, filter);
};
}
export { transformMRangeReply as transformReply } from '.';
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments: createTransformMRangeArguments('TS.MRANGE'),
transformReply: {
2(reply: TsMRangeRawReply2, _?: any, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([_key, _labels, samples]) => {
return transformSamplesReply[2](samples);
}, typeMapping);
},
3(reply: TsMRangeRawReply3) {
return resp3MapToValue(reply, ([_labels, _metadata, samples]) => {
return transformSamplesReply[3](samples);
});
}
},
} as const satisfies Command;

View File

@@ -0,0 +1,66 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MRANGE_GROUPBY, { TIME_SERIES_REDUCERS } from './MRANGE_GROUPBY';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MRANGE_GROUPBY', () => {
it('transformArguments', () => {
assert.deepEqual(
MRANGE_GROUPBY.transformArguments('-', '+', 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
}, {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-', 'AGGREGATION', 'AVG', '1',
'FILTER', 'label=value',
'GROUPBY', 'label', 'REDUCE', 'AVG'
]
);
});
testUtils.testWithClient('client.ts.mRangeGroupBy', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRangeGroupBy('-', '+', 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
'label=value': {
configurable: true,
enumerable: true,
value: {
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,108 @@
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 { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.';
import { TsRangeOptions, pushRangeArguments } from './RANGE';
import { pushFilterArgument } from './MGET';
export const TIME_SERIES_REDUCERS = {
AVG: 'AVG',
SUM: 'SUM',
MIN: 'MIN',
MAX: 'MAX',
RANGE: 'RANGE',
COUNT: 'COUNT',
STD_P: 'STD.P',
STD_S: 'STD.S',
VAR_P: 'VAR.P',
VAR_S: 'VAR.S'
} as const;
export type TimeSeriesReducer = typeof TIME_SERIES_REDUCERS[keyof typeof TIME_SERIES_REDUCERS];
export interface TsMRangeGroupBy {
label: RedisArgument;
REDUCE: TimeSeriesReducer;
}
export function pushGroupByArguments(args: Array<RedisArgument>, groupBy: TsMRangeGroupBy) {
args.push('GROUPBY', groupBy.label, 'REDUCE', groupBy.REDUCE);
}
export type TsMRangeGroupByRawReply2 = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: never, // empty array without WITHLABELS or SELECTED_LABELS
samples: ArrayReply<Resp2Reply<SampleRawReply>>
]>
>;
export type TsMRangeGroupByRawMetadataReply3 = TuplesToMapReply<[
[BlobStringReply<'sources'>, ArrayReply<BlobStringReply>]
]>;
export type TsMRangeGroupByRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: never, // empty hash without WITHLABELS or SELECTED_LABELS
metadata1: never, // ?!
metadata2: TsMRangeGroupByRawMetadataReply3,
samples: ArrayReply<SampleRawReply>
]>
>;
export function createTransformMRangeGroupByArguments(command: RedisArgument) {
return (
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
filter: RedisVariadicArgument,
groupBy: TsMRangeGroupBy,
options?: TsRangeOptions
) => {
let args = pushRangeArguments(
[command],
fromTimestamp,
toTimestamp,
options
);
args = pushFilterArgument(args, filter);
pushGroupByArguments(args, groupBy);
return args;
};
}
export function extractResp3MRangeSources(raw: TsMRangeGroupByRawMetadataReply3) {
const unwrappedMetadata2 = raw as unknown as UnwrapReply<typeof raw>;
if (unwrappedMetadata2 instanceof Map) {
return unwrappedMetadata2.get('sources')!;
} else if (unwrappedMetadata2 instanceof Array) {
return unwrappedMetadata2[1];
} else {
return unwrappedMetadata2.sources;
}
}
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments: createTransformMRangeGroupByArguments('TS.MRANGE'),
transformReply: {
2(reply: TsMRangeGroupByRawReply2, _?: any, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([_key, _labels, samples]) => {
return {
samples: transformSamplesReply[2](samples)
};
}, typeMapping);
},
3(reply: TsMRangeGroupByRawReply3) {
return resp3MapToValue(reply, ([_labels, _metadata1, metadata2, samples]) => {
return {
sources: extractResp3MRangeSources(metadata2),
samples: transformSamplesReply[3](samples)
};
});
}
},
} as const satisfies Command;

View File

@@ -0,0 +1,72 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MRANGE_SELECTED_LABELS from './MRANGE_SELECTED_LABELS';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MRANGE_SELECTED_LABELS', () => {
it('transformArguments', () => {
assert.deepEqual(
MRANGE_SELECTED_LABELS.transformArguments('-', '+', 'label', 'label=value', {
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MRANGE', '-', '+',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-', 'AGGREGATION', 'AVG', '1',
'SELECTED_LABELS', 'label',
'FILTER', 'label=value'
]
);
});
testUtils.testWithClient('client.ts.mRangeSelectedLabels', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRangeSelectedLabels('-', '+', ['label', 'NX'], 'label=value', {
COUNT: 1
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
},
NX: {
configurable: true,
enumerable: true,
value: null
}
}),
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,70 @@
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 { pushSelectedLabelsArguments, resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformRESP2Labels, transformSamplesReply } from '.';
import { TsRangeOptions, pushRangeArguments } from './RANGE';
import { pushFilterArgument } from './MGET';
export type TsMRangeSelectedLabelsRawReply2 = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: ArrayReply<TuplesReply<[
label: BlobStringReply,
value: BlobStringReply | NullReply
]>>,
samples: ArrayReply<Resp2Reply<SampleRawReply>>
]>
>;
export type TsMRangeSelectedLabelsRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: MapReply<BlobStringReply, BlobStringReply | NullReply>,
metadata: never, // ?!
samples: ArrayReply<SampleRawReply>
]>
>;
export function createTransformMRangeSelectedLabelsArguments(command: RedisArgument) {
return (
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
selectedLabels: RedisVariadicArgument,
filter: RedisVariadicArgument,
options?: TsRangeOptions
) => {
let args = pushRangeArguments(
[command],
fromTimestamp,
toTimestamp,
options
);
args = pushSelectedLabelsArguments(args, selectedLabels);
return pushFilterArgument(args, filter);
};
}
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments: createTransformMRangeSelectedLabelsArguments('TS.MRANGE'),
transformReply: {
2(reply: TsMRangeSelectedLabelsRawReply2, _?: any, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([_key, labels, samples]) => {
return {
labels: transformRESP2Labels(labels, typeMapping),
samples: transformSamplesReply[2](samples)
};
}, typeMapping);
},
3(reply: TsMRangeSelectedLabelsRawReply3) {
return resp3MapToValue(reply, ([_key, labels, samples]) => {
return {
labels,
samples: transformSamplesReply[3](samples)
};
});
}
},
} as const satisfies Command;

View File

@@ -0,0 +1,80 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MRANGE_SELECTED_LABELS_GROUPBY from './MRANGE_SELECTED_LABELS_GROUPBY';
import { TIME_SERIES_REDUCERS } from './MRANGE_GROUPBY';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MRANGE_SELECTED_LABELS_GROUPBY', () => {
it('transformArguments', () => {
assert.deepEqual(
MRANGE_SELECTED_LABELS_GROUPBY.transformArguments('-', '+', 'label', 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
}, {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-', 'AGGREGATION', 'AVG', '1',
'SELECTED_LABELS', 'label',
'FILTER', 'label=value',
'GROUPBY', 'label', 'REDUCE', 'AVG'
]
);
});
testUtils.testWithClient('client.ts.mRangeSelectedLabelsGroupBy', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRangeSelectedLabelsGroupBy('-', '+', ['label', 'NX'], 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
'label=value': {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
},
NX: {
configurable: true,
enumerable: true,
value: null
}
}),
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,63 @@
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 { pushSelectedLabelsArguments, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.';
import { TsRangeOptions, pushRangeArguments } from './RANGE';
import { extractResp3MRangeSources, pushGroupByArguments, TsMRangeGroupBy, TsMRangeGroupByRawMetadataReply3 } from './MRANGE_GROUPBY';
import { pushFilterArgument } from './MGET';
import MRANGE_SELECTED_LABELS from './MRANGE_SELECTED_LABELS';
export type TsMRangeWithLabelsGroupByRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: MapReply<BlobStringReply, BlobStringReply | NullReply>,
metadata: never, // ?!
metadata2: TsMRangeGroupByRawMetadataReply3,
samples: ArrayReply<SampleRawReply>
]>
>;
export function createMRangeSelectedLabelsGroupByTransformArguments(
command: RedisArgument
) {
return (
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
selectedLabels: RedisVariadicArgument,
filter: RedisVariadicArgument,
groupBy: TsMRangeGroupBy,
options?: TsRangeOptions
) => {
let args = pushRangeArguments(
[command],
fromTimestamp,
toTimestamp,
options
);
args = pushSelectedLabelsArguments(args, selectedLabels);
args = pushFilterArgument(args, filter);
pushGroupByArguments(args, groupBy);
return args;
};
}
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments: createMRangeSelectedLabelsGroupByTransformArguments('TS.MRANGE'),
transformReply: {
2: MRANGE_SELECTED_LABELS.transformReply[2],
3(reply: TsMRangeWithLabelsGroupByRawReply3) {
return resp3MapToValue(reply, ([labels, _metadata, metadata2, samples]) => {
return {
labels,
sources: extractResp3MRangeSources(metadata2),
samples: transformSamplesReply[3](samples)
};
});
}
},
} as const satisfies Command;

View File

@@ -1,52 +1,68 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MRANGE_WITHLABELS';
import { TimeSeriesAggregationType, TimeSeriesReducers } from '.';
import MRANGE_WITHLABELS from './MRANGE_WITHLABELS';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
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.AVERAGE,
timeBucket: 1
},
GROUPBY: {
label: 'label',
reducer: TimeSeriesReducers.SUM
},
describe('TS.MRANGE_WITHLABELS', () => {
it('transformArguments', () => {
assert.deepEqual(
MRANGE_WITHLABELS.transformArguments('-', '+', 'label=value', {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-',
'AGGREGATION', 'AVG', '1',
'WITHLABELS',
'FILTER', 'label=value'
]
);
});
testUtils.testWithClient('client.ts.mRangeWithLabels', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRangeWithLabels('-', '+', 'label=value')
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
}
}),
['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
}]
samples: [{
timestamp: 0,
value: 0
}]
);
}, GLOBAL.SERVERS.OPEN);
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,21 +1,78 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { Timestamp, MRangeWithLabelsOptions, pushMRangeWithLabelsArguments } from '.';
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 { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformSamplesReply } from '.';
import { TsRangeOptions, pushRangeArguments } from './RANGE';
import { pushFilterArgument } from './MGET';
export const IS_READ_ONLY = true;
export type TsMRangeWithLabelsRawReply2 = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: ArrayReply<TuplesReply<[
label: BlobStringReply,
value: BlobStringReply
]>>,
samples: ArrayReply<Resp2Reply<SampleRawReply>>
]>
>;
export function transformArguments(
export type TsMRangeWithLabelsRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: MapReply<BlobStringReply, BlobStringReply>,
metadata: never, // ?!
samples: ArrayReply<SampleRawReply>
]>
>;
export function createTransformMRangeWithLabelsArguments(command: RedisArgument) {
return (
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
filters: string | Array<string>,
options?: MRangeWithLabelsOptions
): RedisCommandArguments {
return pushMRangeWithLabelsArguments(
['TS.MRANGE'],
fromTimestamp,
toTimestamp,
filters,
options
filter: RedisVariadicArgument,
options?: TsRangeOptions
) => {
const args = pushRangeArguments(
[command],
fromTimestamp,
toTimestamp,
options
);
args.push('WITHLABELS');
return pushFilterArgument(args, filter);
};
}
export { transformMRangeWithLabelsReply as transformReply } from '.';
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments: createTransformMRangeWithLabelsArguments('TS.MRANGE'),
transformReply: {
2(reply: TsMRangeWithLabelsRawReply2, _?: any, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([_key, labels, samples]) => {
const unwrappedLabels = labels as unknown as UnwrapReply<typeof labels>;
// TODO: use Map type mapping for labels
const labelsObject: Record<string, BlobStringReply> = 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 {
labels: labelsObject,
samples: transformSamplesReply[2](samples)
};
}, typeMapping);
},
3(reply: TsMRangeWithLabelsRawReply3) {
return resp3MapToValue(reply, ([labels, _metadata, samples]) => {
return {
labels,
samples: transformSamplesReply[3](samples)
};
});
}
},
} as const satisfies Command;

View File

@@ -0,0 +1,77 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MRANGE_WITHLABELS_GROUPBY from './MRANGE_WITHLABELS_GROUPBY';
import { TIME_SERIES_REDUCERS } from './MRANGE_GROUPBY';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MRANGE_WITHLABELS_GROUPBY', () => {
it('transformArguments', () => {
assert.deepEqual(
MRANGE_WITHLABELS_GROUPBY.transformArguments('-', '+', 'label=value', {
label: 'label',
REDUCE: TIME_SERIES_REDUCERS.AVG
}, {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-',
'AGGREGATION', 'AVG', '1',
'WITHLABELS',
'FILTER', 'label=value',
'GROUPBY', 'label', 'REDUCE', 'AVG'
]
);
});
testUtils.testWithClient('client.ts.mRangeWithLabelsGroupBy', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRangeWithLabelsGroupBy('-', '+', 'label=value', {
label: 'label',
REDUCE: TIME_SERIES_REDUCERS.AVG
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
'label=value': {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
}
}),
sources: ['key'],
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,79 @@
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 { resp2MapToValue, resp3MapToValue, SampleRawReply, Timestamp, transformRESP2LabelsWithSources, transformSamplesReply } from '.';
import { TsRangeOptions, pushRangeArguments } from './RANGE';
import { extractResp3MRangeSources, pushGroupByArguments, TsMRangeGroupBy, TsMRangeGroupByRawMetadataReply3 } from './MRANGE_GROUPBY';
import { pushFilterArgument } from './MGET';
export type TsMRangeWithLabelsGroupByRawReply2 = ArrayReply<
TuplesReply<[
key: BlobStringReply,
labels: ArrayReply<TuplesReply<[
label: BlobStringReply,
value: BlobStringReply
]>>,
samples: ArrayReply<Resp2Reply<SampleRawReply>>
]>
>;
export type TsMRangeWithLabelsGroupByRawReply3 = MapReply<
BlobStringReply,
TuplesReply<[
labels: MapReply<BlobStringReply, BlobStringReply>,
metadata: never, // ?!
metadata2: TsMRangeGroupByRawMetadataReply3,
samples: ArrayReply<SampleRawReply>
]>
>;
export function createMRangeWithLabelsGroupByTransformArguments(command: RedisArgument) {
return (
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
filter: RedisVariadicArgument,
groupBy: TsMRangeGroupBy,
options?: TsRangeOptions
) => {
let args = pushRangeArguments(
[command],
fromTimestamp,
toTimestamp,
options
);
args.push('WITHLABELS');
args = pushFilterArgument(args, filter);
pushGroupByArguments(args, groupBy);
return args;
};
}
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments: createMRangeWithLabelsGroupByTransformArguments('TS.MRANGE'),
transformReply: {
2(reply: TsMRangeWithLabelsGroupByRawReply2, _?: any, typeMapping?: TypeMapping) {
return resp2MapToValue(reply, ([_key, labels, samples]) => {
const transformed = transformRESP2LabelsWithSources(labels);
return {
labels: transformed.labels,
sources: transformed.sources,
samples: transformSamplesReply[2](samples)
};
}, typeMapping);
},
3(reply: TsMRangeWithLabelsGroupByRawReply3) {
return resp3MapToValue(reply, ([labels, _metadata, metadata2, samples]) => {
return {
labels,
sources: extractResp3MRangeSources(metadata2),
samples: transformSamplesReply[3](samples)
};
});
}
},
} as const satisfies Command;

View File

@@ -1,50 +1,62 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MREVRANGE';
import { TimeSeriesAggregationType, TimeSeriesReducers } from '.';
import MREVRANGE from './MREVRANGE';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
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.AVERAGE,
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']
);
});
describe('TS.MREVRANGE', () => {
it('transformArguments', () => {
assert.deepEqual(
MREVRANGE.transformArguments('-', '+', 'label=value', {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MREVRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-',
'AGGREGATION', 'AVG', '1',
'FILTER', 'label=value'
]
);
});
testUtils.testWithClient('client.ts.mRevRange', async client => {
await client.ts.add('key', 0, 0, {
LABELS: { label: 'value'}
});
testUtils.testWithClient('client.ts.mRevRange', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: {
label: 'value'
}
}),
client.ts.mRevRange('-', '+', 'label=value', {
COUNT: 1
})
]);
assert.deepEqual(
await client.ts.mRevRange('-', '+', 'label=value', {
COUNT: 1
}),
[{
key: 'key',
samples: [{
timestamp: 0,
value: 0
}]
}]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepStrictEqual(
reply,
Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: [{
timestamp: 0,
value: 0
}]
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,21 +1,9 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { MRangeOptions, Timestamp, pushMRangeArguments, Filter } from '.';
import { Command } from '@redis/client/dist/lib/RESP/types';
import MRANGE, { createTransformMRangeArguments } from './MRANGE';
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 '.';
export default {
FIRST_KEY_INDEX: MRANGE.FIRST_KEY_INDEX,
IS_READ_ONLY: MRANGE.IS_READ_ONLY,
transformArguments: createTransformMRangeArguments('TS.MREVRANGE'),
transformReply: MRANGE.transformReply,
} as const satisfies Command;

View File

@@ -0,0 +1,67 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MREVRANGE_GROUPBY from './MREVRANGE_GROUPBY';
import { TIME_SERIES_REDUCERS } from './MRANGE_GROUPBY';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MREVRANGE_GROUPBY', () => {
it('transformArguments', () => {
assert.deepEqual(
MREVRANGE_GROUPBY.transformArguments('-', '+', 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
}, {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MREVRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-', 'AGGREGATION', 'AVG', '1',
'FILTER', 'label=value',
'GROUPBY', 'label', 'REDUCE', 'AVG'
]
);
});
testUtils.testWithClient('client.ts.mRevRangeGroupBy', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRevRangeGroupBy('-', '+', 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
'label=value': {
configurable: true,
enumerable: true,
value: {
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,9 @@
import { Command } from '@redis/client/dist/lib/RESP/types';
import MRANGE_GROUPBY, { createTransformMRangeGroupByArguments } from './MRANGE_GROUPBY';
export default {
FIRST_KEY_INDEX: MRANGE_GROUPBY.FIRST_KEY_INDEX,
IS_READ_ONLY: MRANGE_GROUPBY.IS_READ_ONLY,
transformArguments: createTransformMRangeGroupByArguments('TS.MREVRANGE'),
transformReply: MRANGE_GROUPBY.transformReply,
} as const satisfies Command;

View File

@@ -0,0 +1,73 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MREVRANGE_SELECTED_LABELS from './MREVRANGE_SELECTED_LABELS';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MREVRANGE_SELECTED_LABELS', () => {
it('transformArguments', () => {
assert.deepEqual(
MREVRANGE_SELECTED_LABELS.transformArguments('-', '+', 'label', 'label=value', {
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MREVRANGE', '-', '+',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-', 'AGGREGATION', 'AVG', '1',
'SELECTED_LABELS', 'label',
'FILTER', 'label=value'
]
);
});
testUtils.testWithClient('client.ts.mRevRangeSelectedLabels', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRevRangeSelectedLabels('-', '+', ['label', 'NX'], 'label=value', {
COUNT: 1
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
},
NX: {
configurable: true,
enumerable: true,
value: null
}
}),
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,9 @@
import { Command } from '@redis/client/dist/lib/RESP/types';
import MRANGE_SELECTED_LABELS, { createTransformMRangeSelectedLabelsArguments } from './MRANGE_SELECTED_LABELS';
export default {
FIRST_KEY_INDEX: MRANGE_SELECTED_LABELS.FIRST_KEY_INDEX,
IS_READ_ONLY: MRANGE_SELECTED_LABELS.IS_READ_ONLY,
transformArguments: createTransformMRangeSelectedLabelsArguments('TS.MREVRANGE'),
transformReply: MRANGE_SELECTED_LABELS.transformReply,
} as const satisfies Command;

View File

@@ -0,0 +1,80 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MREVRANGE_SELECTED_LABELS_GROUPBY from './MREVRANGE_SELECTED_LABELS_GROUPBY';
import { TIME_SERIES_REDUCERS } from './MRANGE_GROUPBY';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MREVRANGE_SELECTED_LABELS_GROUPBY', () => {
it('transformArguments', () => {
assert.deepEqual(
MREVRANGE_SELECTED_LABELS_GROUPBY.transformArguments('-', '+', 'label', 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
}, {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MREVRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-', 'AGGREGATION', 'AVG', '1',
'SELECTED_LABELS', 'label',
'FILTER', 'label=value',
'GROUPBY', 'label', 'REDUCE', 'AVG'
]
);
});
testUtils.testWithClient('client.ts.mRevRangeSelectedLabelsGroupBy', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRevRangeSelectedLabelsGroupBy('-', '+', ['label', 'NX'], 'label=value', {
REDUCE: TIME_SERIES_REDUCERS.AVG,
label: 'label'
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
'label=value': {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
},
NX: {
configurable: true,
enumerable: true,
value: null
}
}),
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,9 @@
import { Command } from '@redis/client/dist/lib/RESP/types';
import MRANGE_SELECTED_LABELS_GROUPBY, { createMRangeSelectedLabelsGroupByTransformArguments } from './MRANGE_SELECTED_LABELS_GROUPBY';
export default {
FIRST_KEY_INDEX: MRANGE_SELECTED_LABELS_GROUPBY.FIRST_KEY_INDEX,
IS_READ_ONLY: MRANGE_SELECTED_LABELS_GROUPBY.IS_READ_ONLY,
transformArguments: createMRangeSelectedLabelsGroupByTransformArguments('TS.MREVRANGE'),
transformReply: MRANGE_SELECTED_LABELS_GROUPBY.transformReply,
} as const satisfies Command;

View File

@@ -1,52 +1,68 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './MREVRANGE_WITHLABELS';
import { TimeSeriesAggregationType, TimeSeriesReducers } from '.';
import MREVRANGE_WITHLABELS from './MREVRANGE_WITHLABELS';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
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.AVERAGE,
timeBucket: 1
},
GROUPBY: {
label: 'label',
reducer: TimeSeriesReducers.SUM
},
describe('TS.MREVRANGE_WITHLABELS', () => {
it('transformArguments', () => {
assert.deepEqual(
MREVRANGE_WITHLABELS.transformArguments('-', '+', 'label=value', {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MREVRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-',
'AGGREGATION', 'AVG', '1',
'WITHLABELS',
'FILTER', 'label=value'
]
);
});
testUtils.testWithClient('client.ts.mRevRangeWithLabels', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRevRangeWithLabels('-', '+', 'label=value')
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
key: {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
}
}),
['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
}]
samples: [{
timestamp: 0,
value: 0
}]
);
}, GLOBAL.SERVERS.OPEN);
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,21 +1,9 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { Timestamp, MRangeWithLabelsOptions, pushMRangeWithLabelsArguments, Filter } from '.';
import { Command } from '@redis/client/dist/lib/RESP/types';
import MRANGE_WITHLABELS, { createTransformMRangeWithLabelsArguments } from './MRANGE_WITHLABELS';
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 '.';
export default {
FIRST_KEY_INDEX: MRANGE_WITHLABELS.FIRST_KEY_INDEX,
IS_READ_ONLY: MRANGE_WITHLABELS.IS_READ_ONLY,
transformArguments: createTransformMRangeWithLabelsArguments('TS.MREVRANGE'),
transformReply: MRANGE_WITHLABELS.transformReply,
} as const satisfies Command;

View File

@@ -0,0 +1,77 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import MREVRANGE_WITHLABELS_GROUPBY from './MREVRANGE_WITHLABELS_GROUPBY';
import { TIME_SERIES_REDUCERS } from './MRANGE_GROUPBY';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
describe('TS.MREVRANGE_WITHLABELS_GROUPBY', () => {
it('transformArguments', () => {
assert.deepEqual(
MREVRANGE_WITHLABELS_GROUPBY.transformArguments('-', '+', 'label=value', {
label: 'label',
REDUCE: TIME_SERIES_REDUCERS.AVG
}, {
LATEST: true,
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 0,
max: 1
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.MREVRANGE', '-', '+',
'LATEST',
'FILTER_BY_TS', '0',
'FILTER_BY_VALUE', '0', '1',
'COUNT', '1',
'ALIGN', '-',
'AGGREGATION', 'AVG', '1',
'WITHLABELS',
'FILTER', 'label=value',
'GROUPBY', 'label', 'REDUCE', 'AVG'
]
);
});
testUtils.testWithClient('client.ts.mRevRangeWithLabelsGroupBy', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 0, 0, {
LABELS: { label: 'value' }
}),
client.ts.mRevRangeWithLabelsGroupBy('-', '+', 'label=value', {
label: 'label',
REDUCE: TIME_SERIES_REDUCERS.AVG
})
]);
assert.deepStrictEqual(
reply,
Object.create(null, {
'label=value': {
configurable: true,
enumerable: true,
value: {
labels: Object.create(null, {
label: {
configurable: true,
enumerable: true,
value: 'value'
}
}),
sources: ['key'],
samples: [{
timestamp: 0,
value: 0
}]
}
}
})
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,9 @@
import { Command } from '@redis/client/dist/lib/RESP/types';
import MRANGE_WITHLABELS_GROUPBY, { createMRangeWithLabelsGroupByTransformArguments } from './MRANGE_WITHLABELS_GROUPBY';
export default {
FIRST_KEY_INDEX: MRANGE_WITHLABELS_GROUPBY.FIRST_KEY_INDEX,
IS_READ_ONLY: MRANGE_WITHLABELS_GROUPBY.IS_READ_ONLY,
transformArguments: createMRangeWithLabelsGroupByTransformArguments('TS.MREVRANGE'),
transformReply: MRANGE_WITHLABELS_GROUPBY.transformReply,
} as const satisfies Command;

View File

@@ -1,34 +1,34 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './QUERYINDEX';
import QUERYINDEX from './QUERYINDEX';
describe('QUERYINDEX', () => {
describe('transformArguments', () => {
it('single filter', () => {
assert.deepEqual(
transformArguments('*'),
['TS.QUERYINDEX', '*']
);
});
it('multiple filters', () => {
assert.deepEqual(
transformArguments(['a=1', 'b=2']),
['TS.QUERYINDEX', 'a=1', 'b=2']
);
});
describe('TS.QUERYINDEX', () => {
describe('transformArguments', () => {
it('single filter', () => {
assert.deepEqual(
QUERYINDEX.transformArguments('*'),
['TS.QUERYINDEX', '*']
);
});
testUtils.testWithClient('client.ts.queryIndex', async client => {
await client.ts.create('key', {
LABELS: {
label: 'value'
}
});
it('multiple filters', () => {
assert.deepEqual(
QUERYINDEX.transformArguments(['a=1', 'b=2']),
['TS.QUERYINDEX', 'a=1', 'b=2']
);
});
});
assert.deepEqual(
await client.ts.queryIndex('label=value'),
['key']
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('client.ts.queryIndex', async client => {
const [, reply] = await Promise.all([
client.ts.create('key', {
LABELS: {
label: 'value'
}
}),
client.ts.queryIndex('label=value')
]);
assert.deepEqual(reply, ['key']);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,11 +1,14 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
import { Filter } from '.';
import { ArrayReply, BlobStringReply, SetReply, Command } from '@redis/client/dist/lib/RESP/types';
import { RedisVariadicArgument, pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
export const IS_READ_ONLY = true;
export function transformArguments(filter: Filter): RedisCommandArguments {
return pushVerdictArguments(['TS.QUERYINDEX'], filter);
}
export declare function transformReply(): Array<string>;
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(filter: RedisVariadicArgument) {
return pushVariadicArguments(['TS.QUERYINDEX'], filter);
},
transformReply: {
2: undefined as unknown as () => ArrayReply<BlobStringReply>,
3: undefined as unknown as () => SetReply<BlobStringReply>
}
} as const satisfies Command;

View File

@@ -1,38 +1,40 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './RANGE';
import { TimeSeriesAggregationType } from '.';
import RANGE from './RANGE';
import { TIME_SERIES_AGGREGATION_TYPE } from './CREATERULE';
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.AVERAGE,
timeBucket: 1
}
}),
['TS.RANGE', 'key', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE',
'1', '2', 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'AVG', '1']
);
});
describe('TS.RANGE', () => {
it('transformArguments', () => {
assert.deepEqual(
RANGE.transformArguments('key', '-', '+', {
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 1,
max: 2
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
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);
testUtils.testWithClient('client.ts.range', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 1, 2),
client.ts.range('key', '-', '+')
]);
assert.deepEqual(
await client.ts.range('key', '-', '+'),
[{
timestamp: 1,
value: 2
}]
);
}, GLOBAL.SERVERS.OPEN);
assert.deepEqual(reply, [{
timestamp: 1,
value: 2
}]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,24 +1,119 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { RangeOptions, Timestamp, pushRangeArguments, SampleRawReply, SampleReply, transformRangeReply } from '.';
import { CommandArguments, RedisArgument, Command } from '@redis/client/dist/lib/RESP/types';
import { Timestamp, transformTimestampArgument, SamplesRawReply, transformSamplesReply } from '.';
import { TimeSeriesAggregationType } from './CREATERULE';
import { Resp2Reply } from '@redis/client/dist/lib/RESP/types';
export const FIRST_KEY_INDEX = 1;
export const TIME_SERIES_BUCKET_TIMESTAMP = {
LOW: '-',
MIDDLE: '~',
END: '+'
};
export const IS_READ_ONLY = true;
export type TimeSeriesBucketTimestamp = typeof TIME_SERIES_BUCKET_TIMESTAMP[keyof typeof TIME_SERIES_BUCKET_TIMESTAMP];
export function transformArguments(
key: string,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: RangeOptions
): RedisCommandArguments {
return pushRangeArguments(
['TS.RANGE', key],
fromTimestamp,
toTimestamp,
options
export interface TsRangeOptions {
LATEST?: boolean;
FILTER_BY_TS?: Array<Timestamp>;
FILTER_BY_VALUE?: {
min: number;
max: number;
};
COUNT?: number;
ALIGN?: Timestamp;
AGGREGATION?: {
ALIGN?: Timestamp;
type: TimeSeriesAggregationType;
timeBucket: Timestamp;
BUCKETTIMESTAMP?: TimeSeriesBucketTimestamp;
EMPTY?: boolean;
};
}
export function pushRangeArguments(
args: CommandArguments,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: TsRangeOptions
) {
args.push(
transformTimestampArgument(fromTimestamp),
transformTimestampArgument(toTimestamp)
);
if (options?.LATEST) {
args.push('LATEST');
}
if (options?.FILTER_BY_TS) {
args.push('FILTER_BY_TS');
for (const timestamp of options.FILTER_BY_TS) {
args.push(transformTimestampArgument(timestamp));
}
}
if (options?.FILTER_BY_VALUE) {
args.push(
'FILTER_BY_VALUE',
options.FILTER_BY_VALUE.min.toString(),
options.FILTER_BY_VALUE.max.toString()
);
}
if (options?.COUNT !== undefined) {
args.push('COUNT', options.COUNT.toString());
}
if (options?.AGGREGATION) {
if (options?.ALIGN !== undefined) {
args.push('ALIGN', transformTimestampArgument(options.ALIGN));
}
args.push(
'AGGREGATION',
options.AGGREGATION.type,
transformTimestampArgument(options.AGGREGATION.timeBucket)
);
if (options.AGGREGATION.BUCKETTIMESTAMP) {
args.push(
'BUCKETTIMESTAMP',
options.AGGREGATION.BUCKETTIMESTAMP
);
}
if (options.AGGREGATION.EMPTY) {
args.push('EMPTY');
}
}
return args;
}
export function transformReply(reply: Array<SampleRawReply>): Array<SampleReply> {
return transformRangeReply(reply);
export function transformRangeArguments(
command: RedisArgument,
key: RedisArgument,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: TsRangeOptions
) {
return pushRangeArguments(
[command, key],
fromTimestamp,
toTimestamp,
options
);
}
export default {
FIRST_KEY_INDEX: 1,
IS_READ_ONLY: true,
transformArguments: transformRangeArguments.bind(undefined, 'TS.RANGE'),
transformReply: {
2(reply: Resp2Reply<SamplesRawReply>) {
return transformSamplesReply[2](reply);
},
3(reply: SamplesRawReply) {
return transformSamplesReply[3](reply);
}
}
} as const satisfies Command;

View File

@@ -1,106 +1,40 @@
import { strict as assert } from 'assert';
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './REVRANGE';
import { TimeSeriesAggregationType } from '.';
import REVRANGE from './REVRANGE';
import { TIME_SERIES_AGGREGATION_TYPE } from '../index';
describe('REVRANGE', () => {
describe('transformArguments', () => {
it('without options', () => {
assert.deepEqual(
transformArguments('key', '-', '+'),
['TS.REVRANGE', 'key', '-', '+']
);
});
describe('TS.REVRANGE', () => {
it('transformArguments', () => {
assert.deepEqual(
REVRANGE.transformArguments('key', '-', '+', {
FILTER_BY_TS: [0],
FILTER_BY_VALUE: {
min: 1,
max: 2
},
COUNT: 1,
ALIGN: '-',
AGGREGATION: {
type: TIME_SERIES_AGGREGATION_TYPE.AVG,
timeBucket: 1
}
}),
[
'TS.REVRANGE', 'key', '-', '+', 'FILTER_BY_TS', '0', 'FILTER_BY_VALUE',
'1', '2', 'COUNT', '1', 'ALIGN', '-', 'AGGREGATION', 'AVG', '1'
]
);
});
it('with FILTER_BY_TS', () => {
assert.deepEqual(
transformArguments('key', '-', '+', {
FILTER_BY_TS: [0]
}),
['TS.REVRANGE', 'key', '-', '+', 'FILTER_BY_TS', '0']
);
});
testUtils.testWithClient('client.ts.revRange', async client => {
const [, reply] = await Promise.all([
client.ts.add('key', 1, 2),
client.ts.revRange('key', '-', '+')
]);
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.AVERAGE,
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.AVERAGE,
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);
assert.deepEqual(reply, [{
timestamp: 1,
value: 2
}]);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,24 +1,9 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { RangeOptions, Timestamp, pushRangeArguments, SampleRawReply, SampleReply, transformRangeReply } from '.';
import { Command } from '@redis/client/dist/lib/RESP/types';
import RANGE, { transformRangeArguments } from './RANGE';
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', key],
fromTimestamp,
toTimestamp,
options
);
}
export function transformReply(reply: Array<SampleRawReply>): Array<SampleReply> {
return transformRangeReply(reply);
}
export default {
FIRST_KEY_INDEX: RANGE.FIRST_KEY_INDEX,
IS_READ_ONLY: RANGE.IS_READ_ONLY,
transformArguments: transformRangeArguments.bind(undefined, 'TS.REVRANGE'),
transformReply: RANGE.transformReply
} as const satisfies Command;

View File

@@ -1,439 +1,423 @@
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
import { strict as assert } from 'assert';
import {
transformTimestampArgument,
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
pushDuplicatePolicy,
pushLabelsArgument,
transformIncrDecrArguments,
transformSampleReply,
TimeSeriesAggregationType,
pushRangeArguments,
pushMRangeGroupByArguments,
TimeSeriesReducers,
pushFilterArgument,
pushMRangeArguments,
pushWithLabelsArgument,
pushMRangeWithLabelsArguments,
transformRangeReply,
transformMRangeReply,
transformMRangeWithLabelsReply,
TimeSeriesDuplicatePolicies,
pushLatestArgument,
TimeSeriesBucketTimestamp
} from '.';
// import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
// import { strict as assert } from 'node:assert';
// import {
// transformTimestampArgument,
// pushRetentionArgument,
// TimeSeriesEncoding,
// pushEncodingArgument,
// pushChunkSizeArgument,
// pushDuplicatePolicy,
// pushLabelsArgument,
// transformIncrDecrArguments,
// transformSampleReply,
// TimeSeriesAggregationType,
// pushRangeArguments,
// pushMRangeGroupByArguments,
// TimeSeriesReducers,
// pushFilterArgument,
// pushMRangeArguments,
// pushWithLabelsArgument,
// pushMRangeWithLabelsArguments,
// transformRangeReply,
// transformMRangeReply,
// transformMRangeWithLabelsReply,
// TimeSeriesDuplicatePolicies,
// pushLatestArgument,
// TimeSeriesBucketTimestamp
// } from '.';
describe('transformTimestampArgument', () => {
it('number', () => {
assert.equal(
transformTimestampArgument(0),
'0'
);
});
// describe('transformTimestampArgument', () => {
// it('number', () => {
// assert.equal(
// transformTimestampArgument(0),
// '0'
// );
// });
it('Date', () => {
assert.equal(
transformTimestampArgument(new Date(0)),
'0'
);
});
// it('Date', () => {
// assert.equal(
// transformTimestampArgument(new Date(0)),
// '0'
// );
// });
it('string', () => {
assert.equal(
transformTimestampArgument('*'),
'*'
);
});
});
// it('string', () => {
// assert.equal(
// transformTimestampArgument('*'),
// '*'
// );
// });
// });
function testOptionalArgument(fn: (args: RedisCommandArguments) => unknown): void {
it('undefined', () => {
assert.deepEqual(
fn([]),
[]
);
});
}
// function testOptionalArgument(fn: (args: RedisCommandArguments) => unknown): void {
// it('undefined', () => {
// assert.deepEqual(
// fn([]),
// []
// );
// });
// }
describe('pushRetentionArgument', () => {
testOptionalArgument(pushRetentionArgument);
// describe('pushRetentionArgument', () => {
// testOptionalArgument(pushRetentionArgument);
it('number', () => {
assert.deepEqual(
pushRetentionArgument([], 1),
['RETENTION', '1']
);
});
});
// it('number', () => {
// assert.deepEqual(
// pushRetentionArgument([], 1),
// ['RETENTION', '1']
// );
// });
// });
describe('pushEncodingArgument', () => {
testOptionalArgument(pushEncodingArgument);
// describe('pushEncodingArgument', () => {
// testOptionalArgument(pushEncodingArgument);
it('UNCOMPRESSED', () => {
assert.deepEqual(
pushEncodingArgument([], TimeSeriesEncoding.UNCOMPRESSED),
['ENCODING', 'UNCOMPRESSED']
);
});
});
// it('UNCOMPRESSED', () => {
// assert.deepEqual(
// pushEncodingArgument([], TimeSeriesEncoding.UNCOMPRESSED),
// ['ENCODING', 'UNCOMPRESSED']
// );
// });
// });
describe('pushChunkSizeArgument', () => {
testOptionalArgument(pushChunkSizeArgument);
// describe('pushChunkSizeArgument', () => {
// testOptionalArgument(pushChunkSizeArgument);
it('number', () => {
assert.deepEqual(
pushChunkSizeArgument([], 1),
['CHUNK_SIZE', '1']
);
});
});
// it('number', () => {
// assert.deepEqual(
// pushChunkSizeArgument([], 1),
// ['CHUNK_SIZE', '1']
// );
// });
// });
describe('pushDuplicatePolicy', () => {
testOptionalArgument(pushDuplicatePolicy);
// describe('pushDuplicatePolicy', () => {
// testOptionalArgument(pushDuplicatePolicy);
it('BLOCK', () => {
assert.deepEqual(
pushDuplicatePolicy([], TimeSeriesDuplicatePolicies.BLOCK),
['DUPLICATE_POLICY', 'BLOCK']
);
});
});
// it('BLOCK', () => {
// assert.deepEqual(
// pushDuplicatePolicy([], TimeSeriesDuplicatePolicies.BLOCK),
// ['DUPLICATE_POLICY', 'BLOCK']
// );
// });
// });
describe('pushLabelsArgument', () => {
testOptionalArgument(pushLabelsArgument);
// describe('pushLabelsArgument', () => {
// testOptionalArgument(pushLabelsArgument);
it("{ label: 'value' }", () => {
assert.deepEqual(
pushLabelsArgument([], { label: 'value' }),
['LABELS', 'label', 'value']
);
});
});
// it("{ label: 'value' }", () => {
// assert.deepEqual(
// pushLabelsArgument([], { label: 'value' }),
// ['LABELS', 'label', 'value']
// );
// });
// });
describe('transformIncrDecrArguments', () => {
it('without options', () => {
assert.deepEqual(
transformIncrDecrArguments('TS.INCRBY', 'key', 1),
['TS.INCRBY', 'key', '1']
);
});
// describe('transformIncrDecrArguments', () => {
// it('without options', () => {
// assert.deepEqual(
// transformIncrDecrArguments('TS.INCRBY', 'key', 1),
// ['TS.INCRBY', 'key', '1']
// );
// });
it('with TIMESTAMP', () => {
assert.deepEqual(
transformIncrDecrArguments('TS.INCRBY', 'key', 1, {
TIMESTAMP: '*'
}),
['TS.INCRBY', 'key', '1', 'TIMESTAMP', '*']
);
});
// it('with TIMESTAMP', () => {
// assert.deepEqual(
// transformIncrDecrArguments('TS.INCRBY', 'key', 1, {
// TIMESTAMP: '*'
// }),
// ['TS.INCRBY', 'key', '1', 'TIMESTAMP', '*']
// );
// });
it('with UNCOMPRESSED', () => {
assert.deepEqual(
transformIncrDecrArguments('TS.INCRBY', 'key', 1, {
UNCOMPRESSED: true
}),
['TS.INCRBY', 'key', '1', 'UNCOMPRESSED']
);
});
// it('with UNCOMPRESSED', () => {
// assert.deepEqual(
// transformIncrDecrArguments('TS.INCRBY', 'key', 1, {
// UNCOMPRESSED: true
// }),
// ['TS.INCRBY', 'key', '1', 'UNCOMPRESSED']
// );
// });
it('with UNCOMPRESSED false', () => {
assert.deepEqual(
transformIncrDecrArguments('TS.INCRBY', 'key', 1, {
UNCOMPRESSED: false
}),
['TS.INCRBY', 'key', '1']
);
});
});
// it('with UNCOMPRESSED false', () => {
// assert.deepEqual(
// transformIncrDecrArguments('TS.INCRBY', 'key', 1, {
// UNCOMPRESSED: false
// }),
// ['TS.INCRBY', 'key', '1']
// );
// });
// });
it('transformSampleReply', () => {
assert.deepEqual(
transformSampleReply([1, '1.1']),
{
timestamp: 1,
value: 1.1
}
);
});
// it('transformSampleReply', () => {
// assert.deepEqual(
// transformSampleReply([1, '1.1']),
// {
// timestamp: 1,
// value: 1.1
// }
// );
// });
describe('pushRangeArguments', () => {
it('without options', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+'),
['-', '+']
);
});
// describe('pushRangeArguments', () => {
// it('without options', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+'),
// ['-', '+']
// );
// });
describe('with FILTER_BY_TS', () => {
it('string', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
FILTER_BY_TS: ['ts']
}),
['-', '+', 'FILTER_BY_TS', 'ts']
);
});
// describe('with FILTER_BY_TS', () => {
// it('string', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// FILTER_BY_TS: ['ts']
// }),
// ['-', '+', 'FILTER_BY_TS', 'ts']
// );
// });
it('Array', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
FILTER_BY_TS: ['1', '2']
}),
['-', '+', 'FILTER_BY_TS', '1', '2']
);
});
});
// it('Array', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// FILTER_BY_TS: ['1', '2']
// }),
// ['-', '+', 'FILTER_BY_TS', '1', '2']
// );
// });
// });
it('with FILTER_BY_VALUE', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
FILTER_BY_VALUE: {
min: 1,
max: 2
}
}),
['-', '+', 'FILTER_BY_VALUE', '1', '2']
);
});
// it('with FILTER_BY_VALUE', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// FILTER_BY_VALUE: {
// min: 1,
// max: 2
// }
// }),
// ['-', '+', 'FILTER_BY_VALUE', '1', '2']
// );
// });
it('with COUNT', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
COUNT: 1
}),
['-', '+', 'COUNT', '1']
);
});
// it('with COUNT', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// COUNT: 1
// }),
// ['-', '+', 'COUNT', '1']
// );
// });
it('with ALIGN', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
ALIGN: 1
}),
['-', '+', 'ALIGN', '1']
);
});
// it('with ALIGN', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// ALIGN: 1
// }),
// ['-', '+', 'ALIGN', '1']
// );
// });
describe('with AGGREGATION', () => {
it('without options', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
AGGREGATION: {
type: TimeSeriesAggregationType.FIRST,
timeBucket: 1
}
}),
['-', '+', 'AGGREGATION', 'FIRST', '1']
);
});
// describe('with AGGREGATION', () => {
// it('without options', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// AGGREGATION: {
// type: TimeSeriesAggregationType.FIRST,
// timeBucket: 1
// }
// }),
// ['-', '+', 'AGGREGATION', 'FIRST', '1']
// );
// });
it('with BUCKETTIMESTAMP', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
AGGREGATION: {
type: TimeSeriesAggregationType.FIRST,
timeBucket: 1,
BUCKETTIMESTAMP: TimeSeriesBucketTimestamp.LOW
}
}),
['-', '+', 'AGGREGATION', 'FIRST', '1', 'BUCKETTIMESTAMP', '-']
);
});
// it('with BUCKETTIMESTAMP', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// AGGREGATION: {
// type: TimeSeriesAggregationType.FIRST,
// timeBucket: 1,
// BUCKETTIMESTAMP: TimeSeriesBucketTimestamp.LOW
// }
// }),
// ['-', '+', 'AGGREGATION', 'FIRST', '1', 'BUCKETTIMESTAMP', '-']
// );
// });
it('with BUCKETTIMESTAMP', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
AGGREGATION: {
type: TimeSeriesAggregationType.FIRST,
timeBucket: 1,
EMPTY: true
}
}),
['-', '+', 'AGGREGATION', 'FIRST', '1', 'EMPTY']
);
});
});
// it('with BUCKETTIMESTAMP', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// AGGREGATION: {
// type: TimeSeriesAggregationType.FIRST,
// timeBucket: 1,
// EMPTY: true
// }
// }),
// ['-', '+', 'AGGREGATION', 'FIRST', '1', 'EMPTY']
// );
// });
// });
it('with FILTER_BY_TS, FILTER_BY_VALUE, COUNT, ALIGN, AGGREGATION', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
FILTER_BY_TS: ['ts'],
FILTER_BY_VALUE: {
min: 1,
max: 2
},
COUNT: 1,
ALIGN: 1,
AGGREGATION: {
type: TimeSeriesAggregationType.FIRST,
timeBucket: 1,
BUCKETTIMESTAMP: TimeSeriesBucketTimestamp.LOW,
EMPTY: true
}
}),
['-', '+', 'FILTER_BY_TS', 'ts', 'FILTER_BY_VALUE', '1', '2',
'COUNT', '1', 'ALIGN', '1', 'AGGREGATION', 'FIRST', '1', 'BUCKETTIMESTAMP', '-', 'EMPTY']
);
});
});
// it('with FILTER_BY_TS, FILTER_BY_VALUE, COUNT, ALIGN, AGGREGATION', () => {
// assert.deepEqual(
// pushRangeArguments([], '-', '+', {
// FILTER_BY_TS: ['ts'],
// FILTER_BY_VALUE: {
// min: 1,
// max: 2
// },
// COUNT: 1,
// ALIGN: 1,
// AGGREGATION: {
// type: TimeSeriesAggregationType.FIRST,
// timeBucket: 1,
// BUCKETTIMESTAMP: TimeSeriesBucketTimestamp.LOW,
// EMPTY: true
// }
// }),
// ['-', '+', 'FILTER_BY_TS', 'ts', 'FILTER_BY_VALUE', '1', '2',
// 'COUNT', '1', 'ALIGN', '1', 'AGGREGATION', 'FIRST', '1', 'BUCKETTIMESTAMP', '-', 'EMPTY']
// );
// });
// });
describe('pushMRangeGroupByArguments', () => {
it('undefined', () => {
assert.deepEqual(
pushMRangeGroupByArguments([]),
[]
);
});
// describe('pushMRangeGroupByArguments', () => {
// it('undefined', () => {
// assert.deepEqual(
// pushMRangeGroupByArguments([]),
// []
// );
// });
it('with GROUPBY', () => {
assert.deepEqual(
pushMRangeGroupByArguments([], {
label: 'label',
reducer: TimeSeriesReducers.MAXIMUM
}),
['GROUPBY', 'label', 'REDUCE', 'MAX']
);
});
});
// 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']
);
});
// 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']
);
});
});
// 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']
);
});
// 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']
);
});
});
// 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('pushMRangeWithLabelsArguments', () => {
// assert.deepEqual(
// pushMRangeWithLabelsArguments([], '-', '+', 'label=value'),
// ['-', '+', 'WITHLABELS', 'FILTER', 'label=value']
// );
// });
it('with selected labels', () => {
assert.deepEqual(
pushWithLabelsArgument([], ['label']),
['SELECTED_LABELS', 'label']
);
});
});
// it('transformRangeReply', () => {
// assert.deepEqual(
// transformRangeReply([[1, '1.1'], [2, '2.2']]),
// [{
// timestamp: 1,
// value: 1.1
// }, {
// timestamp: 2,
// value: 2.2
// }]
// );
// });
it('pushMRangeWithLabelsArguments', () => {
assert.deepEqual(
pushMRangeWithLabelsArguments([], '-', '+', 'label=value'),
['-', '+', 'WITHLABELS', 'FILTER', 'label=value']
);
});
// 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
// }]
// }]
// );
// });
it('transformRangeReply', () => {
assert.deepEqual(
transformRangeReply([[1, '1.1'], [2, '2.2']]),
[{
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
// }]
// }]
// );
// });
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('pushLatestArgument', () => {
// it('undefined', () => {
// assert.deepEqual(
// pushLatestArgument([]),
// []
// );
// });
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
}]
}]
);
});
// it('false', () => {
// assert.deepEqual(
// pushLatestArgument([], false),
// []
// );
// });
describe('pushLatestArgument', () => {
it('undefined', () => {
assert.deepEqual(
pushLatestArgument([]),
[]
);
});
it('false', () => {
assert.deepEqual(
pushLatestArgument([], false),
[]
);
});
it('true', () => {
assert.deepEqual(
pushLatestArgument([], true),
['LATEST']
);
});
})
// it('true', () => {
// assert.deepEqual(
// pushLatestArgument([], true),
// ['LATEST']
// );
// });
// })

View File

@@ -1,473 +1,399 @@
import * as ADD from './ADD';
import * as ALTER from './ALTER';
import * as CREATE from './CREATE';
import * as CREATERULE from './CREATERULE';
import * as DECRBY from './DECRBY';
import * as DEL from './DEL';
import * as DELETERULE from './DELETERULE';
import * as GET from './GET';
import * as INCRBY from './INCRBY';
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 '@redis/client/dist/lib/commands';
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
import type { DoubleReply, NumberReply, RedisArgument, RedisCommands, TuplesReply, UnwrapReply, Resp2Reply, ArrayReply, BlobStringReply, MapReply, NullReply, TypeMapping, ReplyUnion, RespType } from '@redis/client/lib/RESP/types';
import ADD, { TsIgnoreOptions } from './ADD';
import ALTER from './ALTER';
import CREATE from './CREATE';
import CREATERULE from './CREATERULE';
import DECRBY from './DECRBY';
import DEL from './DEL';
import DELETERULE from './DELETERULE';
import GET from './GET';
import INCRBY from './INCRBY';
import INFO_DEBUG from './INFO_DEBUG';
import INFO from './INFO';
import MADD from './MADD';
import MGET_SELECTED_LABELS from './MGET_SELECTED_LABELS';
import MGET_WITHLABELS from './MGET_WITHLABELS';
import MGET from './MGET';
import MRANGE_GROUPBY from './MRANGE_GROUPBY';
import MRANGE_SELECTED_LABELS_GROUPBY from './MRANGE_SELECTED_LABELS_GROUPBY';
import MRANGE_SELECTED_LABELS from './MRANGE_SELECTED_LABELS';
import MRANGE_WITHLABELS_GROUPBY from './MRANGE_WITHLABELS_GROUPBY';
import MRANGE_WITHLABELS from './MRANGE_WITHLABELS';
import MRANGE from './MRANGE';
import MREVRANGE_GROUPBY from './MREVRANGE_GROUPBY';
import MREVRANGE_SELECTED_LABELS_GROUPBY from './MREVRANGE_SELECTED_LABELS_GROUPBY';
import MREVRANGE_SELECTED_LABELS from './MREVRANGE_SELECTED_LABELS';
import MREVRANGE_WITHLABELS_GROUPBY from './MREVRANGE_WITHLABELS_GROUPBY';
import MREVRANGE_WITHLABELS from './MREVRANGE_WITHLABELS';
import MREVRANGE from './MREVRANGE';
import QUERYINDEX from './QUERYINDEX';
import RANGE from './RANGE';
import REVRANGE from './REVRANGE';
import { RedisVariadicArgument, pushVariadicArguments } from '@redis/client/lib/commands/generic-transformers';
import { RESP_TYPES } from '@redis/client/lib/RESP/decoder';
export default {
ADD,
add: ADD,
ALTER,
alter: ALTER,
CREATE,
create: CREATE,
CREATERULE,
createRule: CREATERULE,
DECRBY,
decrBy: DECRBY,
DEL,
del: DEL,
DELETERULE,
deleteRule: DELETERULE,
GET,
get: GET,
INCRBY,
incrBy: INCRBY,
INFO_DEBUG,
infoDebug: INFO_DEBUG,
INFO,
info: INFO,
MADD,
mAdd: MADD,
MGET,
mGet: MGET,
MGET_WITHLABELS,
mGetWithLabels: MGET_WITHLABELS,
QUERYINDEX,
queryIndex: QUERYINDEX,
RANGE,
range: RANGE,
REVRANGE,
revRange: REVRANGE,
MRANGE,
mRange: MRANGE,
MRANGE_WITHLABELS,
mRangeWithLabels: MRANGE_WITHLABELS,
MREVRANGE,
mRevRange: MREVRANGE,
MREVRANGE_WITHLABELS,
mRevRangeWithLabels: MREVRANGE_WITHLABELS
};
ADD,
add: ADD,
ALTER,
alter: ALTER,
CREATE,
create: CREATE,
CREATERULE,
createRule: CREATERULE,
DECRBY,
decrBy: DECRBY,
DEL,
del: DEL,
DELETERULE,
deleteRule: DELETERULE,
GET,
get: GET,
INCRBY,
incrBy: INCRBY,
INFO_DEBUG,
infoDebug: INFO_DEBUG,
INFO,
info: INFO,
MADD,
mAdd: MADD,
MGET_SELECTED_LABELS,
mGetSelectedLabels: MGET_SELECTED_LABELS,
MGET_WITHLABELS,
mGetWithLabels: MGET_WITHLABELS,
MGET,
mGet: MGET,
MRANGE_GROUPBY,
mRangeGroupBy: MRANGE_GROUPBY,
MRANGE_SELECTED_LABELS_GROUPBY,
mRangeSelectedLabelsGroupBy: MRANGE_SELECTED_LABELS_GROUPBY,
MRANGE_SELECTED_LABELS,
mRangeSelectedLabels: MRANGE_SELECTED_LABELS,
MRANGE_WITHLABELS_GROUPBY,
mRangeWithLabelsGroupBy: MRANGE_WITHLABELS_GROUPBY,
MRANGE_WITHLABELS,
mRangeWithLabels: MRANGE_WITHLABELS,
MRANGE,
mRange: MRANGE,
MREVRANGE_GROUPBY,
mRevRangeGroupBy: MREVRANGE_GROUPBY,
MREVRANGE_SELECTED_LABELS_GROUPBY,
mRevRangeSelectedLabelsGroupBy: MREVRANGE_SELECTED_LABELS_GROUPBY,
MREVRANGE_SELECTED_LABELS,
mRevRangeSelectedLabels: MREVRANGE_SELECTED_LABELS,
MREVRANGE_WITHLABELS_GROUPBY,
mRevRangeWithLabelsGroupBy: MREVRANGE_WITHLABELS_GROUPBY,
MREVRANGE_WITHLABELS,
mRevRangeWithLabels: MREVRANGE_WITHLABELS,
MREVRANGE,
mRevRange: MREVRANGE,
QUERYINDEX,
queryIndex: QUERYINDEX,
RANGE,
range: RANGE,
REVRANGE,
revRange: REVRANGE
} as const satisfies RedisCommands;
export enum TimeSeriesAggregationType {
AVG = 'AVG',
// @deprecated
AVERAGE = 'AVG',
FIRST = 'FIRST',
LAST = 'LAST',
MIN = 'MIN',
// @deprecated
MINIMUM = 'MIN',
MAX = 'MAX',
// @deprecated
MAXIMUM = 'MAX',
SUM = 'SUM',
RANGE = 'RANGE',
COUNT = 'COUNT',
STD_P = 'STD.P',
STD_S = 'STD.S',
VAR_P = 'VAR.P',
VAR_S = 'VAR.S',
TWA = 'TWA'
export function pushIgnoreArgument(args: Array<RedisArgument>, ignore?: TsIgnoreOptions) {
if (ignore !== undefined) {
args.push('IGNORE', ignore.maxTimeDiff.toString(), ignore.maxValDiff.toString());
}
}
export enum TimeSeriesDuplicatePolicies {
BLOCK = 'BLOCK',
FIRST = 'FIRST',
LAST = 'LAST',
MIN = 'MIN',
MAX = 'MAX',
SUM = 'SUM'
export function pushRetentionArgument(args: Array<RedisArgument>, retention?: number) {
if (retention !== undefined) {
args.push('RETENTION', retention.toString());
}
}
export enum TimeSeriesReducers {
AVG = 'AVG',
SUM = 'SUM',
MIN = 'MIN',
// @deprecated
MINIMUM = 'MIN',
MAX = 'MAX',
// @deprecated
MAXIMUM = 'MAX',
RANGE = 'range',
COUNT = 'COUNT',
STD_P = 'STD.P',
STD_S = 'STD.S',
VAR_P = 'VAR.P',
VAR_S = 'VAR.S',
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 pushEncodingArgument(args: Array<RedisArgument>, encoding?: TimeSeriesEncoding) {
if (encoding !== undefined) {
args.push('ENCODING', encoding);
}
}
export function pushChunkSizeArgument(args: Array<RedisArgument>, chunkSize?: number) {
if (chunkSize !== undefined) {
args.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 pushDuplicatePolicy(args: Array<RedisArgument>, duplicatePolicy?: TimeSeriesDuplicatePolicies) {
if (duplicatePolicy !== undefined) {
args.push('DUPLICATE_POLICY', duplicatePolicy);
}
}
export type Timestamp = number | Date | string;
export function transformTimestampArgument(timestamp: Timestamp): string {
if (typeof timestamp === 'string') return timestamp;
if (typeof timestamp === 'string') return timestamp;
return (
typeof timestamp === 'number' ?
timestamp :
timestamp.getTime()
).toString();
return (
typeof timestamp === 'number' ?
timestamp :
timestamp.getTime()
).toString();
}
export function pushIgnoreArgument(args: RedisCommandArguments, ignore?: ADD.TsIgnoreOptions) {
if (ignore !== undefined) {
args.push('IGNORE', ignore.MAX_TIME_DIFF.toString(), ignore.MAX_VAL_DIFF.toString());
export type Labels = {
[label: string]: string;
};
export function pushLabelsArgument(args: Array<RedisArgument>, labels?: Labels) {
if (labels) {
args.push('LABELS');
for (const [label, value] of Object.entries(labels)) {
args.push(label, value);
}
}
return args;
}
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 pushSelectedLabelsArguments(
args: Array<RedisArgument>,
selectedLabels: RedisVariadicArgument
) {
args.push('SELECTED_LABELS');
return pushVariadicArguments(args, 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 pushRetentionArgument(args: RedisCommandArguments, retention?: number): RedisCommandArguments {
if (retention !== undefined) {
args.push(
'RETENTION',
retention.toString()
);
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;
}
return args;
}
export enum TimeSeriesEncoding {
COMPRESSED = 'COMPRESSED',
UNCOMPRESSED = 'UNCOMPRESSED'
}
export function pushEncodingArgument(args: RedisCommandArguments, encoding?: TimeSeriesEncoding): RedisCommandArguments {
if (encoding !== undefined) {
args.push(
'ENCODING',
encoding
);
}
return args;
}
export function pushChunkSizeArgument(args: RedisCommandArguments, chunkSize?: number): RedisCommandArguments {
if (chunkSize !== undefined) {
args.push(
'CHUNK_SIZE',
chunkSize.toString()
);
}
return args;
}
export function pushDuplicatePolicy(args: RedisCommandArguments, duplicatePolicy?: TimeSeriesDuplicatePolicies): RedisCommandArguments {
if (duplicatePolicy !== undefined) {
args.push(
'DUPLICATE_POLICY',
duplicatePolicy
);
}
return args;
}
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');
for (const [label, value] of Object.entries(labels)) {
args.push(label, value);
}
}
return args;
}
export interface IncrDecrOptions {
TIMESTAMP?: Timestamp;
RETENTION?: number;
UNCOMPRESSED?: boolean;
CHUNK_SIZE?: number;
LABELS?: Labels;
}
export function transformIncrDecrArguments(
command: 'TS.INCRBY' | 'TS.DECRBY',
key: string,
value: number,
options?: IncrDecrOptions
): RedisCommandArguments {
const args = [
command,
key,
value.toString()
];
if (options?.TIMESTAMP !== undefined && options?.TIMESTAMP !== null) {
args.push('TIMESTAMP', transformTimestampArgument(options.TIMESTAMP));
}
pushRetentionArgument(args, options?.RETENTION);
if (options?.UNCOMPRESSED) {
args.push('UNCOMPRESSED');
}
pushChunkSizeArgument(args, options?.CHUNK_SIZE);
pushLabelsArgument(args, options?.LABELS);
return args;
}
export type SampleRawReply = [timestamp: number, value: string];
export interface SampleReply {
timestamp: number;
value: number;
}
export function transformSampleReply(reply: SampleRawReply): SampleReply {
return {
timestamp: reply[0],
value: Number(reply[1])
};
}
export enum TimeSeriesBucketTimestamp {
LOW = '-',
HIGH = '+',
MID = '~'
}
export interface RangeOptions {
LATEST?: boolean;
FILTER_BY_TS?: Array<Timestamp>;
FILTER_BY_VALUE?: {
min: number;
max: number;
};
COUNT?: number;
ALIGN?: Timestamp;
AGGREGATION?: {
type: TimeSeriesAggregationType;
timeBucket: Timestamp;
BUCKETTIMESTAMP?: TimeSeriesBucketTimestamp;
EMPTY?: boolean;
};
}
export function pushRangeArguments(
args: RedisCommandArguments,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: RangeOptions
): RedisCommandArguments {
args.push(
transformTimestampArgument(fromTimestamp),
transformTimestampArgument(toTimestamp)
const source = unwrappedSources.subarray(
previousComma,
indexOf
);
sourcesArray.push(source);
previousComma = indexOf + 1;
}
pushLatestArgument(args, options?.LATEST);
if (options?.FILTER_BY_TS) {
args.push('FILTER_BY_TS');
for (const ts of options.FILTER_BY_TS) {
args.push(transformTimestampArgument(ts));
}
}
if (options?.FILTER_BY_VALUE) {
args.push(
'FILTER_BY_VALUE',
options.FILTER_BY_VALUE.min.toString(),
options.FILTER_BY_VALUE.max.toString()
);
}
if (options?.COUNT) {
args.push(
'COUNT',
options.COUNT.toString()
);
}
if (options?.ALIGN) {
args.push(
'ALIGN',
transformTimestampArgument(options.ALIGN)
);
}
if (options?.AGGREGATION) {
args.push(
'AGGREGATION',
options.AGGREGATION.type,
transformTimestampArgument(options.AGGREGATION.timeBucket)
);
if (options.AGGREGATION.BUCKETTIMESTAMP) {
args.push(
'BUCKETTIMESTAMP',
options.AGGREGATION.BUCKETTIMESTAMP
);
}
if (options.AGGREGATION.EMPTY) {
args.push('EMPTY');
}
}
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<string>;
export function pushFilterArgument(args: RedisCommandArguments, filter: string | Array<string>): RedisCommandArguments {
args.push('FILTER');
return pushVerdictArguments(args, filter);
}
export interface MRangeOptions extends RangeOptions {
GROUPBY?: MRangeGroupBy;
}
export function pushMRangeArguments(
args: RedisCommandArguments,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
filter: Filter,
options?: MRangeOptions
): RedisCommandArguments {
args = pushRangeArguments(args, fromTimestamp, toTimestamp, options);
args = pushFilterArgument(args, filter);
return pushMRangeGroupByArguments(args, options?.GROUPBY);
}
export type SelectedLabels = string | Array<string>;
export function pushWithLabelsArgument(args: RedisCommandArguments, selectedLabels?: SelectedLabels): RedisCommandArguments {
if (!selectedLabels) {
args.push('WITHLABELS');
} else {
args.push('SELECTED_LABELS');
args = 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 {
args = pushRangeArguments(args, fromTimestamp, toTimestamp, options);
args = pushWithLabelsArgument(args, options?.SELECTED_LABELS);
args = pushFilterArgument(args, filter);
return pushMRangeGroupByArguments(args, options?.GROUPBY);
}
export function transformRangeReply(reply: Array<SampleRawReply>): Array<SampleReply> {
return reply.map(transformSampleReply);
}
type MRangeRawReply = Array<[
key: string,
labels: RawLabels,
samples: Array<SampleRawReply>
]>;
interface MRangeReplyItem {
key: string;
samples: Array<SampleReply>;
}
export function transformMRangeReply(reply: MRangeRawReply): Array<MRangeReplyItem> {
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<MRangeWithLabelsReplyItem> {
const args = [];
for (const [key, labels, samples] of reply) {
args.push({
key,
labels: transformLablesReply(labels),
samples: samples.map(transformSampleReply)
});
}
return args;
}
export function pushLatestArgument(args: RedisCommandArguments, latest?: boolean): RedisCommandArguments {
if (latest) {
args.push('LATEST');
}
return args;
return sourcesArray;
}