You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
init time series
This commit is contained in:
4
packages/time-series/.nycrc.json
Normal file
4
packages/time-series/.nycrc.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "@istanbuljs/nyc-config-typescript",
|
||||||
|
"exclude": ["**/*.spec.ts", "lib/test-utils.ts"]
|
||||||
|
}
|
10
packages/time-series/.release-it.json
Normal file
10
packages/time-series/.release-it.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"git": {
|
||||||
|
"tagName": "search@${version}",
|
||||||
|
"commitMessage": "Release ${tagName}",
|
||||||
|
"tagAnnotation": "Release ${tagName}"
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"publishArgs": ["--access", "public"]
|
||||||
|
}
|
||||||
|
}
|
2
packages/time-series/README.md
Normal file
2
packages/time-series/README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# @node-redis/search
|
||||||
|
The sources and docs for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
|
80
packages/time-series/lib/commands/ADD.spec.ts
Normal file
80
packages/time-series/lib/commands/ADD.spec.ts
Normal 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);
|
||||||
|
});
|
46
packages/time-series/lib/commands/ADD.ts
Normal file
46
packages/time-series/lib/commands/ADD.ts
Normal 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;
|
51
packages/time-series/lib/commands/ALTER.spec.ts
Normal file
51
packages/time-series/lib/commands/ALTER.spec.ts
Normal 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);
|
||||||
|
});
|
20
packages/time-series/lib/commands/ALTER.ts
Normal file
20
packages/time-series/lib/commands/ALTER.ts
Normal 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';
|
80
packages/time-series/lib/commands/CREATE.spec.ts
Normal file
80
packages/time-series/lib/commands/CREATE.spec.ts
Normal 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);
|
||||||
|
});
|
42
packages/time-series/lib/commands/CREATE.ts
Normal file
42
packages/time-series/lib/commands/CREATE.ts
Normal 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';
|
25
packages/time-series/lib/commands/CREATERULE.spec.ts
Normal file
25
packages/time-series/lib/commands/CREATERULE.spec.ts
Normal 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);
|
||||||
|
});
|
20
packages/time-series/lib/commands/CREATERULE.ts
Normal file
20
packages/time-series/lib/commands/CREATERULE.ts
Normal 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';
|
10
packages/time-series/lib/commands/DECRBY.ts
Normal file
10
packages/time-series/lib/commands/DECRBY.ts
Normal 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;
|
15
packages/time-series/lib/commands/DEL.ts
Normal file
15
packages/time-series/lib/commands/DEL.ts
Normal 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;
|
9
packages/time-series/lib/commands/DELETERULE.ts
Normal file
9
packages/time-series/lib/commands/DELETERULE.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export function transformArguments(sourceKey: string,destinationKey: string,): Array<string> {
|
||||||
|
return [
|
||||||
|
'TS.DELETERULE',
|
||||||
|
sourceKey,
|
||||||
|
destinationKey,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function transfromReply(): 'OK';
|
15
packages/time-series/lib/commands/GET.ts
Normal file
15
packages/time-series/lib/commands/GET.ts
Normal 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);
|
||||||
|
}
|
10
packages/time-series/lib/commands/INCRBY.ts
Normal file
10
packages/time-series/lib/commands/INCRBY.ts
Normal 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;
|
82
packages/time-series/lib/commands/INFO.ts
Normal file
82
packages/time-series/lib/commands/INFO.ts
Normal 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
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
53
packages/time-series/lib/commands/INFO_DEBUG.ts
Normal file
53
packages/time-series/lib/commands/INFO_DEBUG.ts
Normal 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;
|
||||||
|
}
|
23
packages/time-series/lib/commands/MADD.ts
Normal file
23
packages/time-series/lib/commands/MADD.ts
Normal 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>;
|
31
packages/time-series/lib/commands/MGET.ts
Normal file
31
packages/time-series/lib/commands/MGET.ts
Normal 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() {
|
||||||
|
|
||||||
|
}
|
7
packages/time-series/lib/commands/QUERYINDEX.ts
Normal file
7
packages/time-series/lib/commands/QUERYINDEX.ts
Normal 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>;
|
21
packages/time-series/lib/commands/RANGE.ts
Normal file
21
packages/time-series/lib/commands/RANGE.ts
Normal 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 '.';
|
21
packages/time-series/lib/commands/REVRANGE.ts
Normal file
21
packages/time-series/lib/commands/REVRANGE.ts
Normal 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 '.';
|
231
packages/time-series/lib/commands/index.spec.ts
Normal file
231
packages/time-series/lib/commands/index.spec.ts
Normal 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
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
});
|
261
packages/time-series/lib/commands/index.ts
Normal file
261
packages/time-series/lib/commands/index.ts
Normal 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);
|
||||||
|
}
|
3
packages/time-series/lib/index.ts
Normal file
3
packages/time-series/lib/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default } from './commands';
|
||||||
|
|
||||||
|
// TODO
|
21
packages/time-series/lib/test-utils.ts
Normal file
21
packages/time-series/lib/test-utils.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import TestUtils from '@node-redis/test-utils';
|
||||||
|
import TimeSeries from '.';
|
||||||
|
|
||||||
|
export default new TestUtils({
|
||||||
|
dockerImageName: 'redislabs/redistimeseries',
|
||||||
|
dockerImageVersionArgument: 'timeseries-version',
|
||||||
|
defaultDockerVersion: '1.6.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GLOBAL = {
|
||||||
|
SERVERS: {
|
||||||
|
OPEN: {
|
||||||
|
serverArguments: ['--loadmodule /usr/lib/redis/modules/redistimeseries.so'],
|
||||||
|
clientOptions: {
|
||||||
|
modules: {
|
||||||
|
ts: TimeSeries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
24
packages/time-series/package.json
Normal file
24
packages/time-series/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "@node-redis/time-series",
|
||||||
|
"version": "1.0.0-rc.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "nyc -r text-summary -r lcov mocha -r source-map-support/register -r ts-node/register './lib/**/*.spec.ts'",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@node-redis/client": "^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||||
|
"@node-redis/test-utils": "*",
|
||||||
|
"@types/node": "^16.11.7",
|
||||||
|
"nyc": "^15.1.0",
|
||||||
|
"release-it": "^14.11.7",
|
||||||
|
"source-map-support": "^0.5.20",
|
||||||
|
"ts-node": "^10.4.0",
|
||||||
|
"typescript": "^4.4.4"
|
||||||
|
}
|
||||||
|
}
|
9
packages/time-series/tsconfig.json
Normal file
9
packages/time-series/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./lib/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user