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

init time series

This commit is contained in:
leibale
2021-11-29 08:52:14 -05:00
parent bc1bf7e7b1
commit f648f37f5a
29 changed files with 1226 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './ADD';
import { TimeSeriesDuplicatePolicies, TimeSeriesEncoding } 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 RETENTION, ENCODING, CHUNK_SIZE, ON_DUPLICATE, LABELS', () => {
assert.deepEqual(
transformArguments('key', '*', 1, {
RETENTION: 1,
ENCODING: TimeSeriesEncoding.UNCOMPRESSED,
CHUNK_SIZE: 1,
ON_DUPLICATE: TimeSeriesDuplicatePolicies.BLOCK,
LABELS: { label: 'value' }
}),
['TS.ADD', 'key', '*', '1', 'RETENTION', '1', 'ENCODING', 'UNCOMPRESSED', 'CHUNK_SIZE', '1', 'ON_DUPLICATE', 'BLOCK', 'LABELS', 'label', 'value']
);
});
});
testUtils.testWithClient('client.ts.add', async client => {
assert.equal(
await client.ts.add('key', 0, 1),
0
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,46 @@
import {
transformTimestampArgument,
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
TimeSeriesDuplicatePolicies,
Labels,
pushLabelsArgument,
Timestamp,
} from '.';
interface AddOptions {
RETENTION?: number;
ENCODING?: TimeSeriesEncoding;
CHUNK_SIZE?: number;
ON_DUPLICATE?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
}
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, timestamp: Timestamp, value: number, options?: AddOptions): Array<string> {
const args = [
'TS.ADD',
key,
transformTimestampArgument(timestamp),
value.toString()
];
pushRetentionArgument(args, options?.RETENTION);
pushEncodingArgument(args, options?.ENCODING);
pushChunkSizeArgument(args, options?.CHUNK_SIZE);
if (options?.ON_DUPLICATE) {
args.push('ON_DUPLICATE', options.ON_DUPLICATE);
}
pushLabelsArgument(args, options?.LABELS);
return args;
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,51 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './ALTER';
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 LABELS', () => {
assert.deepEqual(
transformArguments('key', {
LABELS: { label: 'value' }
}),
['TS.ALTER', 'key', 'LABELS', 'label', 'value']
);
});
it('with RETENTION, LABELS', () => {
assert.deepEqual(
transformArguments('key', {
RETENTION: 1,
LABELS: { label: 'value' }
}),
['TS.ALTER', 'key', 'RETENTION', '1', 'LABELS', 'label', 'value']
);
});
});
testUtils.testWithClient('client.ts.alter', async client => {
await client.ts.create('key');
assert.equal(
await client.ts.alter('key', { RETENTION: 1 }),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,20 @@
import { pushRetentionArgument, Labels, pushLabelsArgument } from '.';
export const FIRST_KEY_INDEX = 1;
interface AlterOptions {
RETENTION?: number;
LABELS?: Labels;
}
export function transformArguments(key: string, options?: AlterOptions): Array<string> {
const args = ['TS.ALTER', key];
pushRetentionArgument(args, options?.RETENTION);
pushLabelsArgument(args, options?.LABELS);
return args;
}
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,80 @@
import { strict as assert } from 'assert';
import { TimeSeriesDuplicatePolicies, TimeSeriesEncoding } from '.';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CREATE';
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 RETENTION, ENCODING, CHUNK_SIZE, DUPLICATE_POLICY, LABELS', () => {
assert.deepEqual(
transformArguments('key', {
RETENTION: 1,
ENCODING: TimeSeriesEncoding.UNCOMPRESSED,
CHUNK_SIZE: 1,
DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK,
LABELS: { label: 'value' }
}),
['TS.CREATE', 'key', 'RETENTION', '1', 'ENCODING', 'UNCOMPRESSED', 'CHUNK_SIZE', '1', 'DUPLICATE_POLICY', 'BLOCK', 'LABELS', 'label', 'value']
);
});
});
testUtils.testWithClient('client.ts.create', async client => {
assert.equal(
await client.ts.create('key'),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,42 @@
import {
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
TimeSeriesDuplicatePolicies,
Labels,
pushLabelsArgument
} from '.';
export const FIRST_KEY_INDEX = 1;
interface CreateOptions {
RETENTION?: number;
ENCODING?: TimeSeriesEncoding;
CHUNK_SIZE?: number;
DUPLICATE_POLICY?: TimeSeriesDuplicatePolicies;
LABELS?: Labels;
}
export function transformArguments(key: string, options?: CreateOptions): Array<string> {
const args = ['TS.CREATE', key];
pushRetentionArgument(args, options?.RETENTION);
pushEncodingArgument(args, options?.ENCODING);
pushChunkSizeArgument(args, options?.CHUNK_SIZE);
if (options?.DUPLICATE_POLICY) {
args.push(
'DUPLICATE_POLICY',
options.DUPLICATE_POLICY
);
}
pushLabelsArgument(args, options?.LABELS);
return args;
}
export declare function transformReply(): 'OK';

View File

@@ -0,0 +1,25 @@
import { strict as assert } from 'assert';
import { TimeSeriesAggregationType } from '.';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CREATERULE';
describe('CREATERULE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('source', 'destination', TimeSeriesAggregationType.AVARAGE, 1),
['TS.CREATERULE', 'source', 'destination', 'avg', 1]
);
});
testUtils.testWithClient('client.ts.createRule', async client => {
await Promise.all([
client.ts.create('source'),
client.ts.create('destination')
]);
assert.equal(
await client.ts.createRule('source', 'destination', TimeSeriesAggregationType.AVARAGE, 1),
'OK'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,20 @@
import { TimeSeriesAggregationType } from '.';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(
sourceKey: string,
destinationKey: string,
aggregationType: TimeSeriesAggregationType,
timeBucket: number
): Array<string> {
return [
'TS.CREATERULE',
sourceKey,
destinationKey,
aggregationType,
timeBucket.toString()
];
}
export declare function transfromReply(): 'OK';

View File

@@ -0,0 +1,10 @@
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { IncrDecrOptions, transformIncrDecrArguments } from '.';
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;

View File

@@ -0,0 +1,15 @@
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { Timestamp, transformTimestampArgument } from '.';
export const FIRTS_KEY_INDEX = 1;
export function transformArguments(key: string, fromTimestamp: Timestamp, toTimestamp: Timestamp): RedisCommandArguments {
return [
'TS.DEL',
key,
transformTimestampArgument(fromTimestamp),
transformTimestampArgument(toTimestamp)
];
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,9 @@
export function transformArguments(sourceKey: string,destinationKey: string,): Array<string> {
return [
'TS.DELETERULE',
sourceKey,
destinationKey,
];
}
export declare function transfromReply(): 'OK';

View File

@@ -0,0 +1,15 @@
import { SampleRawReply, SampleReply, transformSampleReply } from '.';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(key: string): Array<string> {
return ['TS.GET', key];
}
export function transformReply(reply: [] | SampleRawReply): null | SampleReply {
if (reply.length === 0) return null;
return transformSampleReply(reply);
}

View File

@@ -0,0 +1,10 @@
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { IncrDecrOptions, transformIncrDecrArguments } from '.';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, value: number, options?: IncrDecrOptions): RedisCommandArguments {
return transformIncrDecrArguments('TS.INCRBY', key, value, options);
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,82 @@
import { TimeSeriesAggregationType, TimeSeriesDuplicatePolicies } from '.';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(key: string): Array<string> {
return ['TS.INFO', key];
}
export type InfoRawReply = [
_: string,
totalSamples: number,
_: string,
memoryUsage: number,
_: string,
firstTimestamp: number,
_: string,
lastTimestamp: number,
_: string,
retentionTime: number,
_: string,
chunkCount: number,
_: string,
chunkSize: number,
_: string,
chunkType: string,
_: string,
duplicatePolicy: TimeSeriesDuplicatePolicies | null,
_: string,
labels: Array<[name: string, value: string]>,
_: string,
sourceKey: string | null,
_: string,
rules: Array<[key: string, timeBucket: number, aggregationType: TimeSeriesAggregationType]>
];
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
}>;
}
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
}))
};
}

View File

@@ -0,0 +1,53 @@
import {
transformArguments as transformInfoArguments,
InfoRawReply,
InfoReply,
transformReply as transformInfoReply
} from './INFO';
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 InfoDebugRawReply = [
...infoArgs: InfoRawReply,
_: string,
chunks: Array<[
_: string,
startTimestamp: number,
_: string,
endTimestamp: number,
_: string,
samples: number,
_: string,
size: number,
_: string,
bytesPerSample: string
]>
]
interface InfoDebugReply extends InfoReply {
chunks: Array<{
startTimestamp: number;
endTimestamp: number;
samples: number;
size: number;
bytesPerSample: string;
}>;
}
export function transformReply(rawReply: InfoDebugRawReply): InfoDebugReply {
const reply = transformInfoReply(rawReply as unknown as InfoRawReply);
(reply as InfoDebugReply).chunks = rawReply[25].map(chunk => ({
startTimestamp: chunk[1],
endTimestamp: chunk[3],
samples: chunk[5],
size: chunk[7],
bytesPerSample: chunk[9]
}));
return reply as InfoDebugReply;
}

View File

@@ -0,0 +1,23 @@
import { Timestamp, transformTimestampArgument } from '.';
interface MAddSample {
key: string;
timestamp: Timestamp;
value: number;
}
export function transformArguments(toAdd: Array<MAddSample>): Array<string> {
const args = ['TS.MADD'];
for (const { key, timestamp, value } of toAdd) {
args.push(
key,
transformTimestampArgument(timestamp),
value.toString()
);
}
return args;
}
export declare function transformReply(): Array<number>;

View File

@@ -0,0 +1,31 @@
import { pushVerdictArgument, pushVerdictArguments } from '@node-redis/client/lib/commands/generic-transformers';
export const IS_READ_ONLY = true;
interface WithLabels {
WITHLABELS: true;
}
interface SelectedLabels {
SELECTED_LABELS: string | Array<string>;
}
type MGetOptions = WithLabels & SelectedLabels;
export function transformArguments(filter: string, options?: MGetOptions): Array<string> {
const args = ['TS.MGET'];
if (options?.WITHLABELS) {
args.push('WITHLABELS');
} else if (options?.SELECTED_LABELS) {
pushVerdictArguments(args, options.SELECTED_LABELS);
}
args.push('FILTER', filter);
return args;
}
export function transformReply() {
}

View File

@@ -0,0 +1,7 @@
export const IS_READ_ONLY = true;
export function transformArguments(query: string): Array<string> {
return ['TS.QUERYINDEX', query];
}
export declare function transformReply(): Array<string>;

View File

@@ -0,0 +1,21 @@
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { RangeOptions, Timestamp, pushRangeArguments } from '.';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: RangeOptions
): RedisCommandArguments {
return pushRangeArguments(
['TS.RANGE'],
fromTimestamp,
toTimestamp,
options
);
}
export { transformRangeReply } from '.';

View File

@@ -0,0 +1,21 @@
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { RangeOptions, Timestamp, pushRangeArguments } from '.';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: RangeOptions
): RedisCommandArguments {
return pushRangeArguments(
['TS.REVRANGE'],
fromTimestamp,
toTimestamp,
options
);
}
export { transformRangeReply } from '.';

View File

@@ -0,0 +1,231 @@
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { strict as assert } from 'assert';
import {
transformTimestampArgument,
pushRetentionArgument,
TimeSeriesEncoding,
pushEncodingArgument,
pushChunkSizeArgument,
pushLabelsArgument,
transformIncrDecrArguments,
transformSampleReply,
pushRangeArguments,
transformRangeReply,
TimeSeriesAggregationType
} from '.';
describe('transformTimestampArgument', () => {
it('number', () => {
assert.equal(
transformTimestampArgument(0),
'0'
);
});
it('Date', () => {
assert.equal(
transformTimestampArgument(new Date(0)),
'0'
);
});
it('string', () => {
assert.equal(
transformTimestampArgument('*'),
'*'
);
});
});
function testOptionalArgument(fn: (args: RedisCommandArguments) => unknown): void {
it('undefined', () => {
assert.deepEqual(
fn([]),
[]
);
});
}
describe('pushRetentionArgument', () => {
testOptionalArgument(pushRetentionArgument);
it('number', () => {
assert.deepEqual(
pushRetentionArgument([], 1),
['RETENTION', '1']
);
});
});
describe('pushEncodingArgument', () => {
testOptionalArgument(pushEncodingArgument);
it('UNCOMPRESSED', () => {
assert.deepEqual(
pushEncodingArgument([], TimeSeriesEncoding.UNCOMPRESSED),
['ENCODING', 'UNCOMPRESSED']
);
});
});
describe('pushChunkSizeArgument', () => {
testOptionalArgument(pushChunkSizeArgument);
it('number', () => {
assert.deepEqual(
pushChunkSizeArgument([], 1),
['CHUNK_SIZE', '1']
);
});
});
describe('pushLabelsArgument', () => {
testOptionalArgument(pushLabelsArgument);
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']
);
});
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('transformSampleReply', () => {
assert.deepEqual(
transformSampleReply([1, '1.1']),
{
timestamp: 1,
value: 1.1
}
);
});
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']
);
});
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 COUNT', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
COUNT: 1
}),
['-', '+', 'COUNT', '1']
);
});
it('with ALIGN', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
ALIGN: 1
}),
['-', '+', 'ALIGN', '1']
);
});
it('with AGGREGATION', () => {
assert.deepEqual(
pushRangeArguments([], '-', '+', {
AGGREGATION: {
type: TimeSeriesAggregationType.FIRST,
timeBucket: 1
}
}),
['-', '+', 'AGGREGATION', 'first', '1']
);
});
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
}
}),
['-', '+', 'FILTER_BY_TS', 'ts', 'FILTER_BY_VALUE', '1', '2', 'COUNT', '1', 'ALIGN', '1', 'AGGREGATION', 'first', '1']
);
});
});
it('transformRangeReply', () => {
assert.deepEqual(
transformRangeReply([[1, '1.1'], [2, '2.2']]),
[{
timestamp: 1,
value: 1.1
}, {
timestamp: 2,
value: 2.2
}]
);
});

View File

@@ -0,0 +1,261 @@
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 QUERYINDEX from './QUERYINDEX';
import * as RANGE from './RANGE';
import * as REVRANGE from './REVRANGE';
import { RedisCommandArguments } from '@node-redis/client/dist/lib/commands';
import { pushVerdictArguments } from '@node-redis/client/lib/commands/generic-transformers';
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,
QUERYINDEX,
queryIndex: QUERYINDEX,
RANGE,
range: RANGE,
REVRANGE,
revRange: REVRANGE
};
export enum TimeSeriesAggregationType {
AVARAGE = 'avg',
SUM = 'sum',
MINIMUM = 'min',
MAXIMUM = 'max',
RANGE = 'range',
COUNT = 'count',
FIRST = 'first',
LAST = 'last',
STD_P = 'std.p',
STD_S = 'std.s',
VAR_P = 'var.p',
VAR_S = 'var.s'
}
export type Timestamp = number | Date | string;
export function transformTimestampArgument(timestamp: Timestamp): string {
if (typeof timestamp === 'string') return timestamp;
return (
typeof timestamp === 'number' ?
timestamp :
timestamp.getTime()
).toString();
}
export function pushRetentionArgument(args: RedisCommandArguments, retention?: number): RedisCommandArguments {
if (retention) {
args.push(
'RETENTION',
retention.toString()
);
}
return args;
}
export enum TimeSeriesEncoding {
COMPRESSED = 'COMPRESSED',
UNCOMPRESSED = 'UNCOMPRESSED'
}
export function pushEncodingArgument(args: RedisCommandArguments, encoding?: TimeSeriesEncoding): RedisCommandArguments {
if (encoding) {
args.push(
'ENCODING',
encoding
);
}
return args;
}
export function pushChunkSizeArgument(args: RedisCommandArguments, chunkSize?: number): RedisCommandArguments {
if (chunkSize) {
args.push(
'CHUNK_SIZE',
chunkSize.toString()
);
}
return args;
}
export enum TimeSeriesDuplicatePolicies {
BLOCK = 'BLOCK',
FIRST = 'FIRST',
LAST = 'LAST',
MIN = 'MIN',
MAX = 'MAX',
SUM = 'SUM'
}
export type Labels = {
[label: string]: string;
};
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) {
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 interface RangeOptions {
FILTER_BY_TS?: string | Array<string>;
FILTER_BY_VALUE?: {
min: number;
max: number;
};
COUNT?: number;
ALIGN?: Timestamp;
AGGREGATION?: {
type: TimeSeriesAggregationType;
timeBucket: Timestamp;
};
}
export function pushRangeArguments(
args: RedisCommandArguments,
fromTimestamp: Timestamp,
toTimestamp: Timestamp,
options?: RangeOptions
): RedisCommandArguments {
args.push(
transformTimestampArgument(fromTimestamp),
transformTimestampArgument(toTimestamp)
);
if (options?.FILTER_BY_TS) {
args.push('FILTER_BY_TS');
pushVerdictArguments(args, options.FILTER_BY_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)
);
}
return args;
}
export function transformRangeReply(reply: Array<SampleRawReply>): Array<SampleReply> {
return reply.map(transformSampleReply);
}