@naturalcycles/redis-lib 3.1.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/redisClient.d.ts +6 -4
- package/dist/redisClient.js +34 -8
- package/dist/redisHashKeyValueDB.d.ts +17 -16
- package/dist/redisHashKeyValueDB.js +51 -67
- package/dist/redisKeyValueDB.d.ts +3 -5
- package/dist/redisKeyValueDB.js +19 -19
- package/package.json +1 -1
- package/src/redisClient.ts +56 -11
- package/src/redisHashKeyValueDB.ts +50 -82
- package/src/redisKeyValueDB.ts +25 -28
package/dist/redisClient.d.ts
CHANGED
|
@@ -47,11 +47,13 @@ export declare class RedisClient implements CommonClient {
|
|
|
47
47
|
hmget(key: string, fields: string[]): Promise<NullableString[]>;
|
|
48
48
|
hmgetBuffer(key: string, fields: string[]): Promise<NullableBuffer[]>;
|
|
49
49
|
hincr(key: string, field: string, increment?: number): Promise<number>;
|
|
50
|
+
hincrBatch(key: string, incrementTuples: [string, number][]): Promise<[string, number][]>;
|
|
50
51
|
setWithTTL(key: string, value: string | number | Buffer, expireAt: UnixTimestampNumber): Promise<void>;
|
|
51
|
-
hsetWithTTL(
|
|
52
|
+
hsetWithTTL(_key: string, _value: AnyObject, _expireAt: UnixTimestampNumber): Promise<void>;
|
|
52
53
|
mset(obj: Record<string, string | number>): Promise<void>;
|
|
53
54
|
msetBuffer(obj: Record<string, Buffer>): Promise<void>;
|
|
54
55
|
incr(key: string, by?: number): Promise<number>;
|
|
56
|
+
incrBatch(incrementTuples: [string, number][]): Promise<[string, number][]>;
|
|
55
57
|
ttl(key: string): Promise<number>;
|
|
56
58
|
dropTable(table: string): Promise<void>;
|
|
57
59
|
clearAll(): Promise<void>;
|
|
@@ -59,14 +61,14 @@ export declare class RedisClient implements CommonClient {
|
|
|
59
61
|
Convenient type-safe wrapper.
|
|
60
62
|
Returns BATCHES of keys in each iteration (as-is).
|
|
61
63
|
*/
|
|
62
|
-
scanStream(opt
|
|
64
|
+
scanStream(opt?: ScanStreamOptions): ReadableTyped<string[]>;
|
|
63
65
|
/**
|
|
64
66
|
* Like scanStream, but flattens the stream of keys.
|
|
65
67
|
*/
|
|
66
68
|
scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string>;
|
|
67
69
|
scanCount(opt: ScanStreamOptions): Promise<number>;
|
|
68
|
-
hscanStream(key: string, opt
|
|
69
|
-
hscanCount(key: string, opt
|
|
70
|
+
hscanStream(key: string, opt?: ScanStreamOptions): ReadableTyped<string[]>;
|
|
71
|
+
hscanCount(key: string, opt?: ScanStreamOptions): Promise<number>;
|
|
70
72
|
withPipeline(fn: (pipeline: ChainableCommander) => Promisable<void>): Promise<void>;
|
|
71
73
|
private log;
|
|
72
74
|
}
|
package/dist/redisClient.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RedisClient = void 0;
|
|
4
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
4
5
|
/**
|
|
5
6
|
Wraps the redis sdk with unified interface.
|
|
6
7
|
Features:
|
|
@@ -98,17 +99,30 @@ class RedisClient {
|
|
|
98
99
|
async hincr(key, field, increment = 1) {
|
|
99
100
|
return await this.redis().hincrby(key, field, increment);
|
|
100
101
|
}
|
|
102
|
+
async hincrBatch(key, incrementTuples) {
|
|
103
|
+
const results = {};
|
|
104
|
+
await this.withPipeline(async (pipeline) => {
|
|
105
|
+
for (const [field, increment] of incrementTuples) {
|
|
106
|
+
pipeline.hincrby(key, field, increment, (_err, newValue) => {
|
|
107
|
+
results[field] = newValue;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const validResults = (0, js_lib_1._stringMapEntries)(results).filter(([_, v]) => v !== undefined);
|
|
112
|
+
return validResults;
|
|
113
|
+
}
|
|
101
114
|
async setWithTTL(key, value, expireAt) {
|
|
102
115
|
await this.redis().set(key, value, 'EXAT', expireAt);
|
|
103
116
|
}
|
|
104
|
-
async hsetWithTTL(
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
await this.redis().
|
|
117
|
+
async hsetWithTTL(_key, _value, _expireAt) {
|
|
118
|
+
throw new Error('Not supported until Redis 7.4.0');
|
|
119
|
+
// const valueKeys = Object.keys(value)
|
|
120
|
+
// const numberOfKeys = valueKeys.length
|
|
121
|
+
// const keyList = valueKeys.join(' ')
|
|
122
|
+
// const commandString = `HEXPIREAT ${key} ${expireAt} FIELDS ${numberOfKeys} ${keyList}`
|
|
123
|
+
// const [command, ...args] = commandString.split(' ')
|
|
124
|
+
// await this.redis().hset(key, value)
|
|
125
|
+
// await this.redis().call(command!, args)
|
|
112
126
|
}
|
|
113
127
|
async mset(obj) {
|
|
114
128
|
await this.redis().mset(obj);
|
|
@@ -119,6 +133,18 @@ class RedisClient {
|
|
|
119
133
|
async incr(key, by = 1) {
|
|
120
134
|
return await this.redis().incrby(key, by);
|
|
121
135
|
}
|
|
136
|
+
async incrBatch(incrementTuples) {
|
|
137
|
+
const results = {};
|
|
138
|
+
await this.withPipeline(async (pipeline) => {
|
|
139
|
+
for (const [key, increment] of incrementTuples) {
|
|
140
|
+
pipeline.incrby(key, increment, (_err, newValue) => {
|
|
141
|
+
results[key] = newValue;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const validResults = (0, js_lib_1._stringMapEntries)(results).filter(([_, v]) => v !== undefined);
|
|
146
|
+
return validResults;
|
|
147
|
+
}
|
|
122
148
|
async ttl(key) {
|
|
123
149
|
return await this.redis().ttl(key);
|
|
124
150
|
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import { CommonDBCreateOptions, CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
2
|
-
import { StringMap } from '@naturalcycles/js-lib';
|
|
1
|
+
import { CommonDBCreateOptions, CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
3
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
|
-
import { RedisClient } from './redisClient';
|
|
5
3
|
import { RedisKeyValueDBCfg } from './redisKeyValueDB';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
/**
|
|
5
|
+
* RedisHashKeyValueDB is a KeyValueDB implementation that uses hash fields to simulate tables.
|
|
6
|
+
* The value in the `table` arguments points to a hash field in Redis.
|
|
7
|
+
*
|
|
8
|
+
* The reason for having this approach and also the traditional RedisKeyValueDB is that
|
|
9
|
+
* the currently available Redis versions (in Memorystore, or on MacOs) do not support
|
|
10
|
+
* expiring hash properties.
|
|
11
|
+
* The expiring fields feature is important, and only available via RedisKeyValueDB.
|
|
12
|
+
*
|
|
13
|
+
* Once the available Redis version reaches 7.4.0+,
|
|
14
|
+
* this implementation can take over for RedisKeyValueDB.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RedishHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
17
|
+
cfg: RedisKeyValueDBCfg;
|
|
18
|
+
constructor(cfg: RedisKeyValueDBCfg);
|
|
13
19
|
support: {
|
|
14
20
|
count?: boolean;
|
|
15
21
|
increment?: boolean;
|
|
@@ -23,11 +29,6 @@ export declare class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDispo
|
|
|
23
29
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
24
30
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
25
31
|
count(table: string): Promise<number>;
|
|
26
|
-
|
|
27
|
-
incrementBatch(_table: string, _incrementMap: StringMap<number>): Promise<StringMap<number>>;
|
|
32
|
+
incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]>;
|
|
28
33
|
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
29
|
-
private idsToKeys;
|
|
30
|
-
private idToKey;
|
|
31
|
-
private keysToIds;
|
|
32
|
-
private keyToId;
|
|
33
34
|
}
|
|
@@ -1,121 +1,105 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.RedishHashKeyValueDB = void 0;
|
|
4
4
|
const db_lib_1 = require("@naturalcycles/db-lib");
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* RedisHashKeyValueDB is a KeyValueDB implementation that uses hash fields to simulate tables.
|
|
7
|
+
* The value in the `table` arguments points to a hash field in Redis.
|
|
8
|
+
*
|
|
9
|
+
* The reason for having this approach and also the traditional RedisKeyValueDB is that
|
|
10
|
+
* the currently available Redis versions (in Memorystore, or on MacOs) do not support
|
|
11
|
+
* expiring hash properties.
|
|
12
|
+
* The expiring fields feature is important, and only available via RedisKeyValueDB.
|
|
13
|
+
*
|
|
14
|
+
* Once the available Redis version reaches 7.4.0+,
|
|
15
|
+
* this implementation can take over for RedisKeyValueDB.
|
|
16
|
+
*/
|
|
17
|
+
class RedishHashKeyValueDB {
|
|
7
18
|
constructor(cfg) {
|
|
19
|
+
this.cfg = cfg;
|
|
8
20
|
this.support = {
|
|
9
21
|
...db_lib_1.commonKeyValueDBFullSupport,
|
|
10
22
|
};
|
|
11
|
-
this.client = cfg.client;
|
|
12
|
-
this.keyOfHashField = cfg.hashKey;
|
|
13
23
|
}
|
|
14
24
|
async ping() {
|
|
15
|
-
await this.client.ping();
|
|
25
|
+
await this.cfg.client.ping();
|
|
16
26
|
}
|
|
17
27
|
async [Symbol.asyncDispose]() {
|
|
18
|
-
await this.client.disconnect();
|
|
28
|
+
await this.cfg.client.disconnect();
|
|
19
29
|
}
|
|
20
30
|
async getByIds(table, ids) {
|
|
21
31
|
if (!ids.length)
|
|
22
32
|
return [];
|
|
23
33
|
// we assume that the order of returned values is the same as order of input ids
|
|
24
|
-
const bufs = await this.client.hmgetBuffer(
|
|
34
|
+
const bufs = await this.cfg.client.hmgetBuffer(table, ids);
|
|
25
35
|
return bufs.map((buf, i) => [ids[i], buf]).filter(([_k, v]) => v !== null);
|
|
26
36
|
}
|
|
27
37
|
async deleteByIds(table, ids) {
|
|
28
38
|
if (!ids.length)
|
|
29
39
|
return;
|
|
30
|
-
await this.client.hdel(
|
|
40
|
+
await this.cfg.client.hdel(table, ids);
|
|
31
41
|
}
|
|
32
42
|
async saveBatch(table, entries, opt) {
|
|
33
43
|
if (!entries.length)
|
|
34
44
|
return;
|
|
35
|
-
const
|
|
36
|
-
const map = Object.fromEntries(entriesWithKey);
|
|
45
|
+
const record = Object.fromEntries(entries);
|
|
37
46
|
if (opt?.expireAt) {
|
|
38
|
-
await this.client.hsetWithTTL(
|
|
47
|
+
await this.cfg.client.hsetWithTTL(table, record, opt.expireAt);
|
|
39
48
|
}
|
|
40
49
|
else {
|
|
41
|
-
await this.client.hset(
|
|
50
|
+
await this.cfg.client.hset(table, record);
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
streamIds(table, limit) {
|
|
45
|
-
|
|
46
|
-
.hscanStream(
|
|
47
|
-
match: `${table}:*`,
|
|
48
|
-
})
|
|
54
|
+
const stream = this.cfg.client
|
|
55
|
+
.hscanStream(table)
|
|
49
56
|
.flatMap(keyValueList => {
|
|
50
57
|
const keys = [];
|
|
51
|
-
keyValueList.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
58
|
-
if (limit) {
|
|
59
|
-
stream = stream.take(limit);
|
|
60
|
-
}
|
|
58
|
+
for (let i = 0; i < keyValueList.length; i += 2) {
|
|
59
|
+
keys.push(keyValueList[i]);
|
|
60
|
+
}
|
|
61
|
+
return keys;
|
|
62
|
+
})
|
|
63
|
+
.take(limit || Infinity);
|
|
61
64
|
return stream;
|
|
62
65
|
}
|
|
63
66
|
streamValues(table, limit) {
|
|
64
|
-
return this.client
|
|
65
|
-
.hscanStream(
|
|
66
|
-
match: `${table}:*`,
|
|
67
|
-
})
|
|
67
|
+
return this.cfg.client
|
|
68
|
+
.hscanStream(table)
|
|
68
69
|
.flatMap(keyValueList => {
|
|
69
70
|
const values = [];
|
|
70
|
-
keyValueList.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return values.map(v => Buffer.from(v));
|
|
71
|
+
for (let i = 0; i < keyValueList.length; i += 2) {
|
|
72
|
+
const value = Buffer.from(keyValueList[i + 1]);
|
|
73
|
+
values.push(value);
|
|
74
|
+
}
|
|
75
|
+
return values;
|
|
76
76
|
})
|
|
77
77
|
.take(limit || Infinity);
|
|
78
78
|
}
|
|
79
79
|
streamEntries(table, limit) {
|
|
80
|
-
return this.client
|
|
81
|
-
.hscanStream(
|
|
82
|
-
match: `${table}:*`,
|
|
83
|
-
})
|
|
80
|
+
return this.cfg.client
|
|
81
|
+
.hscanStream(table)
|
|
84
82
|
.flatMap(keyValueList => {
|
|
85
|
-
const entries =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const entries = [];
|
|
84
|
+
for (let i = 0; i < keyValueList.length; i += 2) {
|
|
85
|
+
const key = keyValueList[i];
|
|
86
|
+
const value = Buffer.from(keyValueList[i + 1]);
|
|
87
|
+
entries.push([key, value]);
|
|
88
|
+
}
|
|
89
|
+
return entries;
|
|
89
90
|
})
|
|
90
91
|
.take(limit || Infinity);
|
|
91
92
|
}
|
|
92
93
|
async count(table) {
|
|
93
|
-
return await this.client.hscanCount(
|
|
94
|
-
match: `${table}:*`,
|
|
95
|
-
});
|
|
94
|
+
return await this.cfg.client.hscanCount(table);
|
|
96
95
|
}
|
|
97
|
-
async
|
|
98
|
-
return await this.client.
|
|
99
|
-
}
|
|
100
|
-
async incrementBatch(_table, _incrementMap) {
|
|
101
|
-
throw new Error('Not implemented');
|
|
96
|
+
async incrementBatch(table, increments) {
|
|
97
|
+
return await this.cfg.client.hincrBatch(table, increments);
|
|
102
98
|
}
|
|
103
99
|
async createTable(table, opt) {
|
|
104
100
|
if (!opt?.dropIfExists)
|
|
105
101
|
return;
|
|
106
|
-
await this.client.
|
|
107
|
-
}
|
|
108
|
-
idsToKeys(table, ids) {
|
|
109
|
-
return ids.map(id => this.idToKey(table, id));
|
|
110
|
-
}
|
|
111
|
-
idToKey(table, id) {
|
|
112
|
-
return `${table}:${id}`;
|
|
113
|
-
}
|
|
114
|
-
keysToIds(table, keys) {
|
|
115
|
-
return keys.map(key => this.keyToId(table, key));
|
|
116
|
-
}
|
|
117
|
-
keyToId(table, key) {
|
|
118
|
-
return key.slice(table.length + 1);
|
|
102
|
+
await this.cfg.client.del([table]);
|
|
119
103
|
}
|
|
120
104
|
}
|
|
121
|
-
exports.
|
|
105
|
+
exports.RedishHashKeyValueDB = RedishHashKeyValueDB;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { CommonDBCreateOptions, CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
2
|
-
import { StringMap } from '@naturalcycles/js-lib';
|
|
1
|
+
import { CommonDBCreateOptions, CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
3
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
3
|
import { RedisClient } from './redisClient';
|
|
5
4
|
export interface RedisKeyValueDBCfg {
|
|
6
5
|
client: RedisClient;
|
|
7
6
|
}
|
|
8
7
|
export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
8
|
+
cfg: RedisKeyValueDBCfg;
|
|
9
9
|
constructor(cfg: RedisKeyValueDBCfg);
|
|
10
|
-
client: RedisClient;
|
|
11
10
|
support: {
|
|
12
11
|
count?: boolean;
|
|
13
12
|
increment?: boolean;
|
|
@@ -21,8 +20,7 @@ export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposabl
|
|
|
21
20
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
22
21
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
23
22
|
count(table: string): Promise<number>;
|
|
24
|
-
|
|
25
|
-
incrementBatch(_table: string, _incrementMap: StringMap<number>): Promise<StringMap<number>>;
|
|
23
|
+
incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]>;
|
|
26
24
|
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
27
25
|
private idsToKeys;
|
|
28
26
|
private idToKey;
|
package/dist/redisKeyValueDB.js
CHANGED
|
@@ -5,28 +5,28 @@ const db_lib_1 = require("@naturalcycles/db-lib");
|
|
|
5
5
|
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
6
|
class RedisKeyValueDB {
|
|
7
7
|
constructor(cfg) {
|
|
8
|
+
this.cfg = cfg;
|
|
8
9
|
this.support = {
|
|
9
10
|
...db_lib_1.commonKeyValueDBFullSupport,
|
|
10
11
|
};
|
|
11
|
-
this.client = cfg.client;
|
|
12
12
|
}
|
|
13
13
|
async ping() {
|
|
14
|
-
await this.client.ping();
|
|
14
|
+
await this.cfg.client.ping();
|
|
15
15
|
}
|
|
16
16
|
async [Symbol.asyncDispose]() {
|
|
17
|
-
await this.client.disconnect();
|
|
17
|
+
await this.cfg.client.disconnect();
|
|
18
18
|
}
|
|
19
19
|
async getByIds(table, ids) {
|
|
20
20
|
if (!ids.length)
|
|
21
21
|
return [];
|
|
22
22
|
// we assume that the order of returned values is the same as order of input ids
|
|
23
|
-
const bufs = await this.client.mgetBuffer(this.idsToKeys(table, ids));
|
|
23
|
+
const bufs = await this.cfg.client.mgetBuffer(this.idsToKeys(table, ids));
|
|
24
24
|
return bufs.map((buf, i) => [ids[i], buf]).filter(([_k, v]) => v !== null);
|
|
25
25
|
}
|
|
26
26
|
async deleteByIds(table, ids) {
|
|
27
27
|
if (!ids.length)
|
|
28
28
|
return;
|
|
29
|
-
await this.client.del(this.idsToKeys(table, ids));
|
|
29
|
+
await this.cfg.client.del(this.idsToKeys(table, ids));
|
|
30
30
|
}
|
|
31
31
|
async saveBatch(table, entries, opt) {
|
|
32
32
|
if (!entries.length)
|
|
@@ -34,7 +34,7 @@ class RedisKeyValueDB {
|
|
|
34
34
|
if (opt?.expireAt) {
|
|
35
35
|
// There's no supported mset with TTL: https://stackoverflow.com/questions/16423342/redis-multi-set-with-a-ttl
|
|
36
36
|
// so we gonna use a pipeline instead
|
|
37
|
-
await this.client.withPipeline(pipeline => {
|
|
37
|
+
await this.cfg.client.withPipeline(pipeline => {
|
|
38
38
|
for (const [k, v] of entries) {
|
|
39
39
|
pipeline.set(this.idToKey(table, k), v, 'EXAT', opt.expireAt);
|
|
40
40
|
}
|
|
@@ -42,11 +42,11 @@ class RedisKeyValueDB {
|
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
44
|
const obj = Object.fromEntries(entries.map(([k, v]) => [this.idToKey(table, k), v]));
|
|
45
|
-
await this.client.msetBuffer(obj);
|
|
45
|
+
await this.cfg.client.msetBuffer(obj);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
streamIds(table, limit) {
|
|
49
|
-
let stream = this.client
|
|
49
|
+
let stream = this.cfg.client
|
|
50
50
|
.scanStream({
|
|
51
51
|
match: `${table}:*`,
|
|
52
52
|
// count: limit, // count is actually a "batchSize", not a limit
|
|
@@ -58,25 +58,25 @@ class RedisKeyValueDB {
|
|
|
58
58
|
return stream;
|
|
59
59
|
}
|
|
60
60
|
streamValues(table, limit) {
|
|
61
|
-
return this.client
|
|
61
|
+
return this.cfg.client
|
|
62
62
|
.scanStream({
|
|
63
63
|
match: `${table}:*`,
|
|
64
64
|
})
|
|
65
65
|
.flatMap(async (keys) => {
|
|
66
|
-
return (await this.client.mgetBuffer(keys)).filter(js_lib_1._isTruthy);
|
|
66
|
+
return (await this.cfg.client.mgetBuffer(keys)).filter(js_lib_1._isTruthy);
|
|
67
67
|
}, {
|
|
68
68
|
concurrency: 16,
|
|
69
69
|
})
|
|
70
70
|
.take(limit || Infinity);
|
|
71
71
|
}
|
|
72
72
|
streamEntries(table, limit) {
|
|
73
|
-
return this.client
|
|
73
|
+
return this.cfg.client
|
|
74
74
|
.scanStream({
|
|
75
75
|
match: `${table}:*`,
|
|
76
76
|
})
|
|
77
77
|
.flatMap(async (keys) => {
|
|
78
78
|
// casting as Buffer[], because values are expected to exist for given keys
|
|
79
|
-
const bufs = (await this.client.mgetBuffer(keys));
|
|
79
|
+
const bufs = (await this.cfg.client.mgetBuffer(keys));
|
|
80
80
|
return (0, js_lib_1._zip)(this.keysToIds(table, keys), bufs);
|
|
81
81
|
}, {
|
|
82
82
|
concurrency: 16,
|
|
@@ -85,20 +85,20 @@ class RedisKeyValueDB {
|
|
|
85
85
|
}
|
|
86
86
|
async count(table) {
|
|
87
87
|
// todo: implement more efficiently, e.g via LUA?
|
|
88
|
-
return await this.client.scanCount({
|
|
88
|
+
return await this.cfg.client.scanCount({
|
|
89
89
|
match: `${table}:*`,
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
async
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
async incrementBatch(table, increments) {
|
|
93
|
+
const incrementTuplesWithInternalKeys = increments.map(([id, v]) => [this.idToKey(table, id), v]);
|
|
94
|
+
const resultsWithInternalKeys = await this.cfg.client.incrBatch(incrementTuplesWithInternalKeys);
|
|
95
|
+
const results = resultsWithInternalKeys.map(([k, v]) => [this.keyToId(table, k), v]);
|
|
96
|
+
return results;
|
|
97
97
|
}
|
|
98
98
|
async createTable(table, opt) {
|
|
99
99
|
if (!opt?.dropIfExists)
|
|
100
100
|
return;
|
|
101
|
-
await this.client.dropTable(table);
|
|
101
|
+
await this.cfg.client.dropTable(table);
|
|
102
102
|
}
|
|
103
103
|
idsToKeys(table, ids) {
|
|
104
104
|
return ids.map(id => this.idToKey(table, id));
|
package/package.json
CHANGED
package/src/redisClient.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
_stringMapEntries,
|
|
2
3
|
AnyObject,
|
|
3
4
|
CommonLogger,
|
|
4
5
|
NullableBuffer,
|
|
5
6
|
NullableString,
|
|
6
7
|
Promisable,
|
|
8
|
+
StringMap,
|
|
7
9
|
UnixTimestampNumber,
|
|
8
10
|
} from '@naturalcycles/js-lib'
|
|
9
11
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
@@ -157,6 +159,25 @@ export class RedisClient implements CommonClient {
|
|
|
157
159
|
return await this.redis().hincrby(key, field, increment)
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
async hincrBatch(key: string, incrementTuples: [string, number][]): Promise<[string, number][]> {
|
|
163
|
+
const results: StringMap<number | undefined> = {}
|
|
164
|
+
|
|
165
|
+
await this.withPipeline(async pipeline => {
|
|
166
|
+
for (const [field, increment] of incrementTuples) {
|
|
167
|
+
pipeline.hincrby(key, field, increment, (_err, newValue) => {
|
|
168
|
+
results[field] = newValue
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const validResults = _stringMapEntries(results).filter(([_, v]) => v !== undefined) as [
|
|
174
|
+
string,
|
|
175
|
+
number,
|
|
176
|
+
][]
|
|
177
|
+
|
|
178
|
+
return validResults
|
|
179
|
+
}
|
|
180
|
+
|
|
160
181
|
async setWithTTL(
|
|
161
182
|
key: string,
|
|
162
183
|
value: string | number | Buffer,
|
|
@@ -165,14 +186,19 @@ export class RedisClient implements CommonClient {
|
|
|
165
186
|
await this.redis().set(key, value, 'EXAT', expireAt)
|
|
166
187
|
}
|
|
167
188
|
|
|
168
|
-
async hsetWithTTL(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
async hsetWithTTL(
|
|
190
|
+
_key: string,
|
|
191
|
+
_value: AnyObject,
|
|
192
|
+
_expireAt: UnixTimestampNumber,
|
|
193
|
+
): Promise<void> {
|
|
194
|
+
throw new Error('Not supported until Redis 7.4.0')
|
|
195
|
+
// const valueKeys = Object.keys(value)
|
|
196
|
+
// const numberOfKeys = valueKeys.length
|
|
197
|
+
// const keyList = valueKeys.join(' ')
|
|
198
|
+
// const commandString = `HEXPIREAT ${key} ${expireAt} FIELDS ${numberOfKeys} ${keyList}`
|
|
199
|
+
// const [command, ...args] = commandString.split(' ')
|
|
200
|
+
// await this.redis().hset(key, value)
|
|
201
|
+
// await this.redis().call(command!, args)
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
async mset(obj: Record<string, string | number>): Promise<void> {
|
|
@@ -187,6 +213,25 @@ export class RedisClient implements CommonClient {
|
|
|
187
213
|
return await this.redis().incrby(key, by)
|
|
188
214
|
}
|
|
189
215
|
|
|
216
|
+
async incrBatch(incrementTuples: [string, number][]): Promise<[string, number][]> {
|
|
217
|
+
const results: StringMap<number | undefined> = {}
|
|
218
|
+
|
|
219
|
+
await this.withPipeline(async pipeline => {
|
|
220
|
+
for (const [key, increment] of incrementTuples) {
|
|
221
|
+
pipeline.incrby(key, increment, (_err, newValue) => {
|
|
222
|
+
results[key] = newValue
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const validResults = _stringMapEntries(results).filter(([_, v]) => v !== undefined) as [
|
|
228
|
+
string,
|
|
229
|
+
number,
|
|
230
|
+
][]
|
|
231
|
+
|
|
232
|
+
return validResults
|
|
233
|
+
}
|
|
234
|
+
|
|
190
235
|
async ttl(key: string): Promise<number> {
|
|
191
236
|
return await this.redis().ttl(key)
|
|
192
237
|
}
|
|
@@ -226,7 +271,7 @@ export class RedisClient implements CommonClient {
|
|
|
226
271
|
Convenient type-safe wrapper.
|
|
227
272
|
Returns BATCHES of keys in each iteration (as-is).
|
|
228
273
|
*/
|
|
229
|
-
scanStream(opt
|
|
274
|
+
scanStream(opt?: ScanStreamOptions): ReadableTyped<string[]> {
|
|
230
275
|
return this.redis().scanStream(opt)
|
|
231
276
|
}
|
|
232
277
|
|
|
@@ -249,11 +294,11 @@ export class RedisClient implements CommonClient {
|
|
|
249
294
|
return count
|
|
250
295
|
}
|
|
251
296
|
|
|
252
|
-
hscanStream(key: string, opt
|
|
297
|
+
hscanStream(key: string, opt?: ScanStreamOptions): ReadableTyped<string[]> {
|
|
253
298
|
return this.redis().hscanStream(key, opt)
|
|
254
299
|
}
|
|
255
300
|
|
|
256
|
-
async hscanCount(key: string, opt
|
|
301
|
+
async hscanCount(key: string, opt?: ScanStreamOptions): Promise<number> {
|
|
257
302
|
let count = 0
|
|
258
303
|
|
|
259
304
|
const stream = this.redis().hscanStream(key, opt)
|
|
@@ -3,48 +3,49 @@ import {
|
|
|
3
3
|
CommonKeyValueDB,
|
|
4
4
|
commonKeyValueDBFullSupport,
|
|
5
5
|
CommonKeyValueDBSaveBatchOptions,
|
|
6
|
+
IncrementTuple,
|
|
6
7
|
KeyValueDBTuple,
|
|
7
8
|
} from '@naturalcycles/db-lib'
|
|
8
|
-
import { _chunk, StringMap } from '@naturalcycles/js-lib'
|
|
9
9
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
10
|
-
import { RedisClient } from './redisClient'
|
|
11
10
|
import { RedisKeyValueDBCfg } from './redisKeyValueDB'
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
/**
|
|
13
|
+
* RedisHashKeyValueDB is a KeyValueDB implementation that uses hash fields to simulate tables.
|
|
14
|
+
* The value in the `table` arguments points to a hash field in Redis.
|
|
15
|
+
*
|
|
16
|
+
* The reason for having this approach and also the traditional RedisKeyValueDB is that
|
|
17
|
+
* the currently available Redis versions (in Memorystore, or on MacOs) do not support
|
|
18
|
+
* expiring hash properties.
|
|
19
|
+
* The expiring fields feature is important, and only available via RedisKeyValueDB.
|
|
20
|
+
*
|
|
21
|
+
* Once the available Redis version reaches 7.4.0+,
|
|
22
|
+
* this implementation can take over for RedisKeyValueDB.
|
|
23
|
+
*/
|
|
24
|
+
export class RedishHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
25
|
+
constructor(public cfg: RedisKeyValueDBCfg) {}
|
|
25
26
|
|
|
26
27
|
support = {
|
|
27
28
|
...commonKeyValueDBFullSupport,
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async ping(): Promise<void> {
|
|
31
|
-
await this.client.ping()
|
|
32
|
+
await this.cfg.client.ping()
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
async [Symbol.asyncDispose](): Promise<void> {
|
|
35
|
-
await this.client.disconnect()
|
|
36
|
+
await this.cfg.client.disconnect()
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
39
40
|
if (!ids.length) return []
|
|
40
41
|
// we assume that the order of returned values is the same as order of input ids
|
|
41
|
-
const bufs = await this.client.hmgetBuffer(
|
|
42
|
+
const bufs = await this.cfg.client.hmgetBuffer(table, ids)
|
|
42
43
|
return bufs.map((buf, i) => [ids[i], buf] as KeyValueDBTuple).filter(([_k, v]) => v !== null)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
async deleteByIds(table: string, ids: string[]): Promise<void> {
|
|
46
47
|
if (!ids.length) return
|
|
47
|
-
await this.client.hdel(
|
|
48
|
+
await this.cfg.client.hdel(table, ids)
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async saveBatch(
|
|
@@ -54,103 +55,70 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
54
55
|
): Promise<void> {
|
|
55
56
|
if (!entries.length) return
|
|
56
57
|
|
|
57
|
-
const
|
|
58
|
-
const map: StringMap<any> = Object.fromEntries(entriesWithKey)
|
|
58
|
+
const record = Object.fromEntries(entries)
|
|
59
59
|
|
|
60
60
|
if (opt?.expireAt) {
|
|
61
|
-
await this.client.hsetWithTTL(
|
|
61
|
+
await this.cfg.client.hsetWithTTL(table, record, opt.expireAt)
|
|
62
62
|
} else {
|
|
63
|
-
await this.client.hset(
|
|
63
|
+
await this.cfg.client.hset(table, record)
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
68
|
-
|
|
69
|
-
.hscanStream(
|
|
70
|
-
match: `${table}:*`,
|
|
71
|
-
})
|
|
68
|
+
const stream = this.cfg.client
|
|
69
|
+
.hscanStream(table)
|
|
72
70
|
.flatMap(keyValueList => {
|
|
73
71
|
const keys: string[] = []
|
|
74
|
-
keyValueList.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return this.keysToIds(table, keys)
|
|
72
|
+
for (let i = 0; i < keyValueList.length; i += 2) {
|
|
73
|
+
keys.push(keyValueList[i]!)
|
|
74
|
+
}
|
|
75
|
+
return keys
|
|
79
76
|
})
|
|
80
|
-
|
|
81
|
-
if (limit) {
|
|
82
|
-
stream = stream.take(limit)
|
|
83
|
-
}
|
|
77
|
+
.take(limit || Infinity)
|
|
84
78
|
|
|
85
79
|
return stream
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
89
|
-
return this.client
|
|
90
|
-
.hscanStream(
|
|
91
|
-
match: `${table}:*`,
|
|
92
|
-
})
|
|
83
|
+
return this.cfg.client
|
|
84
|
+
.hscanStream(table)
|
|
93
85
|
.flatMap(keyValueList => {
|
|
94
|
-
const values:
|
|
95
|
-
keyValueList.
|
|
96
|
-
|
|
97
|
-
values.push(
|
|
98
|
-
}
|
|
99
|
-
return values
|
|
86
|
+
const values: Buffer[] = []
|
|
87
|
+
for (let i = 0; i < keyValueList.length; i += 2) {
|
|
88
|
+
const value = Buffer.from(keyValueList[i + 1]!)
|
|
89
|
+
values.push(value)
|
|
90
|
+
}
|
|
91
|
+
return values
|
|
100
92
|
})
|
|
101
93
|
.take(limit || Infinity)
|
|
102
94
|
}
|
|
103
95
|
|
|
104
96
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
|
|
105
|
-
return this.client
|
|
106
|
-
.hscanStream(
|
|
107
|
-
match: `${table}:*`,
|
|
108
|
-
})
|
|
97
|
+
return this.cfg.client
|
|
98
|
+
.hscanStream(table)
|
|
109
99
|
.flatMap(keyValueList => {
|
|
110
|
-
const entries =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
const entries: [string, Buffer][] = []
|
|
101
|
+
for (let i = 0; i < keyValueList.length; i += 2) {
|
|
102
|
+
const key = keyValueList[i]!
|
|
103
|
+
const value = Buffer.from(keyValueList[i + 1]!)
|
|
104
|
+
entries.push([key, value])
|
|
105
|
+
}
|
|
106
|
+
return entries
|
|
114
107
|
})
|
|
115
108
|
.take(limit || Infinity)
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
async count(table: string): Promise<number> {
|
|
119
|
-
return await this.client.hscanCount(
|
|
120
|
-
match: `${table}:*`,
|
|
121
|
-
})
|
|
112
|
+
return await this.cfg.client.hscanCount(table)
|
|
122
113
|
}
|
|
123
114
|
|
|
124
|
-
async
|
|
125
|
-
return await this.client.
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async incrementBatch(
|
|
129
|
-
_table: string,
|
|
130
|
-
_incrementMap: StringMap<number>,
|
|
131
|
-
): Promise<StringMap<number>> {
|
|
132
|
-
throw new Error('Not implemented')
|
|
115
|
+
async incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]> {
|
|
116
|
+
return await this.cfg.client.hincrBatch(table, increments)
|
|
133
117
|
}
|
|
134
118
|
|
|
135
119
|
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {
|
|
136
120
|
if (!opt?.dropIfExists) return
|
|
137
121
|
|
|
138
|
-
await this.client.
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private idsToKeys(table: string, ids: string[]): string[] {
|
|
142
|
-
return ids.map(id => this.idToKey(table, id))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private idToKey(table: string, id: string): string {
|
|
146
|
-
return `${table}:${id}`
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private keysToIds(table: string, keys: string[]): string[] {
|
|
150
|
-
return keys.map(key => this.keyToId(table, key))
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
private keyToId(table: string, key: string): string {
|
|
154
|
-
return key.slice(table.length + 1)
|
|
122
|
+
await this.cfg.client.del([table])
|
|
155
123
|
}
|
|
156
124
|
}
|
package/src/redisKeyValueDB.ts
CHANGED
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
CommonKeyValueDB,
|
|
4
4
|
commonKeyValueDBFullSupport,
|
|
5
5
|
CommonKeyValueDBSaveBatchOptions,
|
|
6
|
+
IncrementTuple,
|
|
6
7
|
KeyValueDBTuple,
|
|
7
8
|
} from '@naturalcycles/db-lib'
|
|
8
|
-
import { _isTruthy, _zip
|
|
9
|
+
import { _isTruthy, _zip } from '@naturalcycles/js-lib'
|
|
9
10
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
10
11
|
import { RedisClient } from './redisClient'
|
|
11
12
|
|
|
@@ -14,34 +15,30 @@ export interface RedisKeyValueDBCfg {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
17
|
-
constructor(cfg: RedisKeyValueDBCfg) {
|
|
18
|
-
this.client = cfg.client
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
client: RedisClient
|
|
18
|
+
constructor(public cfg: RedisKeyValueDBCfg) {}
|
|
22
19
|
|
|
23
20
|
support = {
|
|
24
21
|
...commonKeyValueDBFullSupport,
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
async ping(): Promise<void> {
|
|
28
|
-
await this.client.ping()
|
|
25
|
+
await this.cfg.client.ping()
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
async [Symbol.asyncDispose](): Promise<void> {
|
|
32
|
-
await this.client.disconnect()
|
|
29
|
+
await this.cfg.client.disconnect()
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
36
33
|
if (!ids.length) return []
|
|
37
34
|
// we assume that the order of returned values is the same as order of input ids
|
|
38
|
-
const bufs = await this.client.mgetBuffer(this.idsToKeys(table, ids))
|
|
35
|
+
const bufs = await this.cfg.client.mgetBuffer(this.idsToKeys(table, ids))
|
|
39
36
|
return bufs.map((buf, i) => [ids[i], buf] as KeyValueDBTuple).filter(([_k, v]) => v !== null)
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
async deleteByIds(table: string, ids: string[]): Promise<void> {
|
|
43
40
|
if (!ids.length) return
|
|
44
|
-
await this.client.del(this.idsToKeys(table, ids))
|
|
41
|
+
await this.cfg.client.del(this.idsToKeys(table, ids))
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
async saveBatch(
|
|
@@ -54,7 +51,7 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
54
51
|
if (opt?.expireAt) {
|
|
55
52
|
// There's no supported mset with TTL: https://stackoverflow.com/questions/16423342/redis-multi-set-with-a-ttl
|
|
56
53
|
// so we gonna use a pipeline instead
|
|
57
|
-
await this.client.withPipeline(pipeline => {
|
|
54
|
+
await this.cfg.client.withPipeline(pipeline => {
|
|
58
55
|
for (const [k, v] of entries) {
|
|
59
56
|
pipeline.set(this.idToKey(table, k), v, 'EXAT', opt.expireAt!)
|
|
60
57
|
}
|
|
@@ -63,12 +60,12 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
63
60
|
const obj: Record<string, Buffer> = Object.fromEntries(
|
|
64
61
|
entries.map(([k, v]) => [this.idToKey(table, k), v]) as KeyValueDBTuple[],
|
|
65
62
|
)
|
|
66
|
-
await this.client.msetBuffer(obj)
|
|
63
|
+
await this.cfg.client.msetBuffer(obj)
|
|
67
64
|
}
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
71
|
-
let stream = this.client
|
|
68
|
+
let stream = this.cfg.client
|
|
72
69
|
.scanStream({
|
|
73
70
|
match: `${table}:*`,
|
|
74
71
|
// count: limit, // count is actually a "batchSize", not a limit
|
|
@@ -83,13 +80,13 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
83
80
|
}
|
|
84
81
|
|
|
85
82
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
86
|
-
return this.client
|
|
83
|
+
return this.cfg.client
|
|
87
84
|
.scanStream({
|
|
88
85
|
match: `${table}:*`,
|
|
89
86
|
})
|
|
90
87
|
.flatMap(
|
|
91
88
|
async keys => {
|
|
92
|
-
return (await this.client.mgetBuffer(keys)).filter(_isTruthy)
|
|
89
|
+
return (await this.cfg.client.mgetBuffer(keys)).filter(_isTruthy)
|
|
93
90
|
},
|
|
94
91
|
{
|
|
95
92
|
concurrency: 16,
|
|
@@ -99,14 +96,14 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
|
|
102
|
-
return this.client
|
|
99
|
+
return this.cfg.client
|
|
103
100
|
.scanStream({
|
|
104
101
|
match: `${table}:*`,
|
|
105
102
|
})
|
|
106
103
|
.flatMap(
|
|
107
104
|
async keys => {
|
|
108
105
|
// casting as Buffer[], because values are expected to exist for given keys
|
|
109
|
-
const bufs = (await this.client.mgetBuffer(keys)) as Buffer[]
|
|
106
|
+
const bufs = (await this.cfg.client.mgetBuffer(keys)) as Buffer[]
|
|
110
107
|
return _zip(this.keysToIds(table, keys), bufs)
|
|
111
108
|
},
|
|
112
109
|
{
|
|
@@ -118,26 +115,26 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
118
115
|
|
|
119
116
|
async count(table: string): Promise<number> {
|
|
120
117
|
// todo: implement more efficiently, e.g via LUA?
|
|
121
|
-
return await this.client.scanCount({
|
|
118
|
+
return await this.cfg.client.scanCount({
|
|
122
119
|
match: `${table}:*`,
|
|
123
120
|
})
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
async
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
123
|
+
async incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]> {
|
|
124
|
+
const incrementTuplesWithInternalKeys = increments.map(
|
|
125
|
+
([id, v]) => [this.idToKey(table, id), v] as [string, number],
|
|
126
|
+
)
|
|
127
|
+
const resultsWithInternalKeys = await this.cfg.client.incrBatch(incrementTuplesWithInternalKeys)
|
|
128
|
+
const results = resultsWithInternalKeys.map(
|
|
129
|
+
([k, v]) => [this.keyToId(table, k), v] as IncrementTuple,
|
|
130
|
+
)
|
|
131
|
+
return results
|
|
135
132
|
}
|
|
136
133
|
|
|
137
134
|
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {
|
|
138
135
|
if (!opt?.dropIfExists) return
|
|
139
136
|
|
|
140
|
-
await this.client.dropTable(table)
|
|
137
|
+
await this.cfg.client.dropTable(table)
|
|
141
138
|
}
|
|
142
139
|
|
|
143
140
|
private idsToKeys(table: string, ids: string[]): string[] {
|