@naturalcycles/redis-lib 3.0.0 → 3.1.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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/redisClient.d.ts +15 -6
- package/dist/redisClient.js +46 -2
- package/dist/redisHashKeyValueDB.d.ts +27 -0
- package/dist/redisHashKeyValueDB.js +114 -0
- package/dist/redisKeyValueDB.d.ts +1 -1
- package/dist/redisKeyValueDB.js +3 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/redisClient.ts +64 -5
- package/src/redisHashKeyValueDB.ts +144 -0
- package/src/redisKeyValueDB.ts +4 -0
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -3,3 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
tslib_1.__exportStar(require("./redisClient"), exports);
|
|
5
5
|
tslib_1.__exportStar(require("./redisKeyValueDB"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./redisHashKeyValueDB"), exports);
|
package/dist/redisClient.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { CommonLogger, NullableBuffer, NullableString, Promisable, UnixTimestampNumber } from '@naturalcycles/js-lib';
|
|
1
|
+
import { AnyObject, CommonLogger, NullableBuffer, NullableString, Promisable, UnixTimestampNumber } from '@naturalcycles/js-lib';
|
|
3
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
3
|
import type { Redis, RedisOptions } from 'ioredis';
|
|
5
4
|
import type { ScanStreamOptions } from 'ioredis/built/types';
|
|
@@ -40,11 +39,19 @@ export declare class RedisClient implements CommonClient {
|
|
|
40
39
|
getBuffer(key: string): Promise<NullableBuffer>;
|
|
41
40
|
mget(keys: string[]): Promise<NullableString[]>;
|
|
42
41
|
mgetBuffer(keys: string[]): Promise<NullableBuffer[]>;
|
|
43
|
-
set(key: string, value: string | Buffer): Promise<void>;
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
set(key: string, value: string | number | Buffer): Promise<void>;
|
|
43
|
+
hgetall<T extends Record<string, string> = Record<string, string>>(key: string): Promise<T | null>;
|
|
44
|
+
hget(key: string, field: string): Promise<NullableString>;
|
|
45
|
+
hset(key: string, value: AnyObject): Promise<void>;
|
|
46
|
+
hdel(key: string, fields: string[]): Promise<void>;
|
|
47
|
+
hmget(key: string, fields: string[]): Promise<NullableString[]>;
|
|
48
|
+
hmgetBuffer(key: string, fields: string[]): Promise<NullableBuffer[]>;
|
|
49
|
+
hincr(key: string, field: string, increment?: number): Promise<number>;
|
|
50
|
+
setWithTTL(key: string, value: string | number | Buffer, expireAt: UnixTimestampNumber): Promise<void>;
|
|
51
|
+
hsetWithTTL(key: string, value: AnyObject, expireAt: UnixTimestampNumber): Promise<void>;
|
|
52
|
+
mset(obj: Record<string, string | number>): Promise<void>;
|
|
46
53
|
msetBuffer(obj: Record<string, Buffer>): Promise<void>;
|
|
47
|
-
incr(key: string): Promise<number>;
|
|
54
|
+
incr(key: string, by?: number): Promise<number>;
|
|
48
55
|
ttl(key: string): Promise<number>;
|
|
49
56
|
dropTable(table: string): Promise<void>;
|
|
50
57
|
clearAll(): Promise<void>;
|
|
@@ -58,6 +65,8 @@ export declare class RedisClient implements CommonClient {
|
|
|
58
65
|
*/
|
|
59
66
|
scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string>;
|
|
60
67
|
scanCount(opt: ScanStreamOptions): Promise<number>;
|
|
68
|
+
hscanStream(key: string, opt: ScanStreamOptions): ReadableTyped<string[]>;
|
|
69
|
+
hscanCount(key: string, opt: ScanStreamOptions): Promise<number>;
|
|
61
70
|
withPipeline(fn: (pipeline: ChainableCommander) => Promisable<void>): Promise<void>;
|
|
62
71
|
private log;
|
|
63
72
|
}
|
package/dist/redisClient.js
CHANGED
|
@@ -74,17 +74,50 @@ class RedisClient {
|
|
|
74
74
|
async set(key, value) {
|
|
75
75
|
await this.redis().set(key, value);
|
|
76
76
|
}
|
|
77
|
+
async hgetall(key) {
|
|
78
|
+
const result = await this.redis().hgetall(key);
|
|
79
|
+
if (Object.keys(result).length === 0)
|
|
80
|
+
return null;
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
async hget(key, field) {
|
|
84
|
+
return await this.redis().hget(key, field);
|
|
85
|
+
}
|
|
86
|
+
async hset(key, value) {
|
|
87
|
+
await this.redis().hset(key, value);
|
|
88
|
+
}
|
|
89
|
+
async hdel(key, fields) {
|
|
90
|
+
await this.redis().hdel(key, ...fields);
|
|
91
|
+
}
|
|
92
|
+
async hmget(key, fields) {
|
|
93
|
+
return await this.redis().hmget(key, ...fields);
|
|
94
|
+
}
|
|
95
|
+
async hmgetBuffer(key, fields) {
|
|
96
|
+
return await this.redis().hmgetBuffer(key, ...fields);
|
|
97
|
+
}
|
|
98
|
+
async hincr(key, field, increment = 1) {
|
|
99
|
+
return await this.redis().hincrby(key, field, increment);
|
|
100
|
+
}
|
|
77
101
|
async setWithTTL(key, value, expireAt) {
|
|
78
102
|
await this.redis().set(key, value, 'EXAT', expireAt);
|
|
79
103
|
}
|
|
104
|
+
async hsetWithTTL(key, value, expireAt) {
|
|
105
|
+
const valueKeys = Object.keys(value);
|
|
106
|
+
const numberOfKeys = valueKeys.length;
|
|
107
|
+
const keyList = valueKeys.join(' ');
|
|
108
|
+
const commandString = `HEXPIREAT ${key} ${expireAt} FIELDS ${numberOfKeys} ${keyList}`;
|
|
109
|
+
const [command, ...args] = commandString.split(' ');
|
|
110
|
+
await this.redis().hset(key, value);
|
|
111
|
+
await this.redis().call(command, args);
|
|
112
|
+
}
|
|
80
113
|
async mset(obj) {
|
|
81
114
|
await this.redis().mset(obj);
|
|
82
115
|
}
|
|
83
116
|
async msetBuffer(obj) {
|
|
84
117
|
await this.redis().mset(obj);
|
|
85
118
|
}
|
|
86
|
-
async incr(key) {
|
|
87
|
-
return await this.redis().
|
|
119
|
+
async incr(key, by = 1) {
|
|
120
|
+
return await this.redis().incrby(key, by);
|
|
88
121
|
}
|
|
89
122
|
async ttl(key) {
|
|
90
123
|
return await this.redis().ttl(key);
|
|
@@ -135,6 +168,17 @@ class RedisClient {
|
|
|
135
168
|
});
|
|
136
169
|
return count;
|
|
137
170
|
}
|
|
171
|
+
hscanStream(key, opt) {
|
|
172
|
+
return this.redis().hscanStream(key, opt);
|
|
173
|
+
}
|
|
174
|
+
async hscanCount(key, opt) {
|
|
175
|
+
let count = 0;
|
|
176
|
+
const stream = this.redis().hscanStream(key, opt);
|
|
177
|
+
await stream.forEach((keyValueList) => {
|
|
178
|
+
count += keyValueList.length / 2;
|
|
179
|
+
});
|
|
180
|
+
return count;
|
|
181
|
+
}
|
|
138
182
|
async withPipeline(fn) {
|
|
139
183
|
const pipeline = this.redis().pipeline();
|
|
140
184
|
await fn(pipeline);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CommonKeyValueDBSaveBatchOptions, CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
2
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
3
|
+
import { RedisClient } from './redisClient';
|
|
4
|
+
import { RedisKeyValueDBCfg } from './redisKeyValueDB';
|
|
5
|
+
export interface RedisHashKeyValueDBCfg extends RedisKeyValueDBCfg {
|
|
6
|
+
hashKey: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
9
|
+
client: RedisClient;
|
|
10
|
+
keyOfHashField: string;
|
|
11
|
+
constructor(cfg: RedisHashKeyValueDBCfg);
|
|
12
|
+
ping(): Promise<void>;
|
|
13
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
14
|
+
getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
|
|
15
|
+
deleteByIds(table: string, ids: string[]): Promise<void>;
|
|
16
|
+
saveBatch(table: string, entries: KeyValueDBTuple[], opt?: CommonKeyValueDBSaveBatchOptions): Promise<void>;
|
|
17
|
+
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
18
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
19
|
+
streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple>;
|
|
20
|
+
count(table: string): Promise<number>;
|
|
21
|
+
increment(table: string, id: string, by?: number): Promise<number>;
|
|
22
|
+
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
23
|
+
private idsToKeys;
|
|
24
|
+
private idToKey;
|
|
25
|
+
private keysToIds;
|
|
26
|
+
private keyToId;
|
|
27
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisHashKeyValueDB = void 0;
|
|
4
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
+
class RedisHashKeyValueDB {
|
|
6
|
+
constructor(cfg) {
|
|
7
|
+
this.client = cfg.client;
|
|
8
|
+
this.keyOfHashField = cfg.hashKey;
|
|
9
|
+
}
|
|
10
|
+
async ping() {
|
|
11
|
+
await this.client.ping();
|
|
12
|
+
}
|
|
13
|
+
async [Symbol.asyncDispose]() {
|
|
14
|
+
await this.client.disconnect();
|
|
15
|
+
}
|
|
16
|
+
async getByIds(table, ids) {
|
|
17
|
+
if (!ids.length)
|
|
18
|
+
return [];
|
|
19
|
+
// we assume that the order of returned values is the same as order of input ids
|
|
20
|
+
const bufs = await this.client.hmgetBuffer(this.keyOfHashField, this.idsToKeys(table, ids));
|
|
21
|
+
return bufs.map((buf, i) => [ids[i], buf]).filter(([_k, v]) => v !== null);
|
|
22
|
+
}
|
|
23
|
+
async deleteByIds(table, ids) {
|
|
24
|
+
if (!ids.length)
|
|
25
|
+
return;
|
|
26
|
+
await this.client.hdel(this.keyOfHashField, this.idsToKeys(table, ids));
|
|
27
|
+
}
|
|
28
|
+
async saveBatch(table, entries, opt) {
|
|
29
|
+
if (!entries.length)
|
|
30
|
+
return;
|
|
31
|
+
const entriesWithKey = entries.map(([k, v]) => [this.idToKey(table, k), v]);
|
|
32
|
+
const map = Object.fromEntries(entriesWithKey);
|
|
33
|
+
if (opt?.expireAt) {
|
|
34
|
+
await this.client.hsetWithTTL(this.keyOfHashField, map, opt.expireAt);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
await this.client.hset(this.keyOfHashField, map);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
streamIds(table, limit) {
|
|
41
|
+
let stream = this.client
|
|
42
|
+
.hscanStream(this.keyOfHashField, {
|
|
43
|
+
match: `${table}:*`,
|
|
44
|
+
})
|
|
45
|
+
.flatMap(keyValueList => {
|
|
46
|
+
const keys = [];
|
|
47
|
+
keyValueList.forEach((keyOrValue, index) => {
|
|
48
|
+
if (index % 2 !== 0)
|
|
49
|
+
return;
|
|
50
|
+
keys.push(keyOrValue);
|
|
51
|
+
});
|
|
52
|
+
return this.keysToIds(table, keys);
|
|
53
|
+
});
|
|
54
|
+
if (limit) {
|
|
55
|
+
stream = stream.take(limit);
|
|
56
|
+
}
|
|
57
|
+
return stream;
|
|
58
|
+
}
|
|
59
|
+
streamValues(table, limit) {
|
|
60
|
+
return this.client
|
|
61
|
+
.hscanStream(this.keyOfHashField, {
|
|
62
|
+
match: `${table}:*`,
|
|
63
|
+
})
|
|
64
|
+
.flatMap(keyValueList => {
|
|
65
|
+
const values = [];
|
|
66
|
+
keyValueList.forEach((keyOrValue, index) => {
|
|
67
|
+
if (index % 2 !== 1)
|
|
68
|
+
return;
|
|
69
|
+
values.push(keyOrValue);
|
|
70
|
+
});
|
|
71
|
+
return values.map(v => Buffer.from(v));
|
|
72
|
+
})
|
|
73
|
+
.take(limit || Infinity);
|
|
74
|
+
}
|
|
75
|
+
streamEntries(table, limit) {
|
|
76
|
+
return this.client
|
|
77
|
+
.hscanStream(this.keyOfHashField, {
|
|
78
|
+
match: `${table}:*`,
|
|
79
|
+
})
|
|
80
|
+
.flatMap(keyValueList => {
|
|
81
|
+
const entries = (0, js_lib_1._chunk)(keyValueList, 2);
|
|
82
|
+
return entries.map(([k, v]) => {
|
|
83
|
+
return [this.keyToId(table, String(k)), Buffer.from(String(v))];
|
|
84
|
+
});
|
|
85
|
+
})
|
|
86
|
+
.take(limit || Infinity);
|
|
87
|
+
}
|
|
88
|
+
async count(table) {
|
|
89
|
+
return await this.client.hscanCount(this.keyOfHashField, {
|
|
90
|
+
match: `${table}:*`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async increment(table, id, by = 1) {
|
|
94
|
+
return await this.client.hincr(this.keyOfHashField, this.idToKey(table, id), by);
|
|
95
|
+
}
|
|
96
|
+
async createTable(table, opt) {
|
|
97
|
+
if (!opt?.dropIfExists)
|
|
98
|
+
return;
|
|
99
|
+
await this.client.dropTable(table);
|
|
100
|
+
}
|
|
101
|
+
idsToKeys(table, ids) {
|
|
102
|
+
return ids.map(id => this.idToKey(table, id));
|
|
103
|
+
}
|
|
104
|
+
idToKey(table, id) {
|
|
105
|
+
return `${table}:${id}`;
|
|
106
|
+
}
|
|
107
|
+
keysToIds(table, keys) {
|
|
108
|
+
return keys.map(key => this.keyToId(table, key));
|
|
109
|
+
}
|
|
110
|
+
keyToId(table, key) {
|
|
111
|
+
return key.slice(table.length + 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.RedisHashKeyValueDB = RedisHashKeyValueDB;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { CommonKeyValueDBSaveBatchOptions, CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
3
2
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
3
|
import { RedisClient } from './redisClient';
|
|
@@ -17,6 +16,7 @@ export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposabl
|
|
|
17
16
|
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
18
17
|
streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple>;
|
|
19
18
|
count(table: string): Promise<number>;
|
|
19
|
+
increment(table: string, id: string, by?: number): Promise<number>;
|
|
20
20
|
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
21
21
|
private idsToKeys;
|
|
22
22
|
private idToKey;
|
package/dist/redisKeyValueDB.js
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/redisClient.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AnyObject,
|
|
2
3
|
CommonLogger,
|
|
3
4
|
NullableBuffer,
|
|
4
5
|
NullableString,
|
|
@@ -120,19 +121,61 @@ export class RedisClient implements CommonClient {
|
|
|
120
121
|
return await this.redis().mgetBuffer(keys)
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
async set(key: string, value: string | Buffer): Promise<void> {
|
|
124
|
+
async set(key: string, value: string | number | Buffer): Promise<void> {
|
|
124
125
|
await this.redis().set(key, value)
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
async hgetall<T extends Record<string, string> = Record<string, string>>(
|
|
129
|
+
key: string,
|
|
130
|
+
): Promise<T | null> {
|
|
131
|
+
const result = await this.redis().hgetall(key)
|
|
132
|
+
if (Object.keys(result).length === 0) return null
|
|
133
|
+
return result as T
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async hget(key: string, field: string): Promise<NullableString> {
|
|
137
|
+
return await this.redis().hget(key, field)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async hset(key: string, value: AnyObject): Promise<void> {
|
|
141
|
+
await this.redis().hset(key, value)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async hdel(key: string, fields: string[]): Promise<void> {
|
|
145
|
+
await this.redis().hdel(key, ...fields)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async hmget(key: string, fields: string[]): Promise<NullableString[]> {
|
|
149
|
+
return await this.redis().hmget(key, ...fields)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async hmgetBuffer(key: string, fields: string[]): Promise<NullableBuffer[]> {
|
|
153
|
+
return await this.redis().hmgetBuffer(key, ...fields)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async hincr(key: string, field: string, increment: number = 1): Promise<number> {
|
|
157
|
+
return await this.redis().hincrby(key, field, increment)
|
|
158
|
+
}
|
|
159
|
+
|
|
127
160
|
async setWithTTL(
|
|
128
161
|
key: string,
|
|
129
|
-
value: string | Buffer,
|
|
162
|
+
value: string | number | Buffer,
|
|
130
163
|
expireAt: UnixTimestampNumber,
|
|
131
164
|
): Promise<void> {
|
|
132
165
|
await this.redis().set(key, value, 'EXAT', expireAt)
|
|
133
166
|
}
|
|
134
167
|
|
|
135
|
-
async
|
|
168
|
+
async hsetWithTTL(key: string, value: AnyObject, expireAt: UnixTimestampNumber): Promise<void> {
|
|
169
|
+
const valueKeys = Object.keys(value)
|
|
170
|
+
const numberOfKeys = valueKeys.length
|
|
171
|
+
const keyList = valueKeys.join(' ')
|
|
172
|
+
const commandString = `HEXPIREAT ${key} ${expireAt} FIELDS ${numberOfKeys} ${keyList}`
|
|
173
|
+
const [command, ...args] = commandString.split(' ')
|
|
174
|
+
await this.redis().hset(key, value)
|
|
175
|
+
await this.redis().call(command!, args)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async mset(obj: Record<string, string | number>): Promise<void> {
|
|
136
179
|
await this.redis().mset(obj)
|
|
137
180
|
}
|
|
138
181
|
|
|
@@ -140,8 +183,8 @@ export class RedisClient implements CommonClient {
|
|
|
140
183
|
await this.redis().mset(obj)
|
|
141
184
|
}
|
|
142
185
|
|
|
143
|
-
async incr(key: string): Promise<number> {
|
|
144
|
-
return await this.redis().
|
|
186
|
+
async incr(key: string, by: number = 1): Promise<number> {
|
|
187
|
+
return await this.redis().incrby(key, by)
|
|
145
188
|
}
|
|
146
189
|
|
|
147
190
|
async ttl(key: string): Promise<number> {
|
|
@@ -205,6 +248,22 @@ export class RedisClient implements CommonClient {
|
|
|
205
248
|
return count
|
|
206
249
|
}
|
|
207
250
|
|
|
251
|
+
hscanStream(key: string, opt: ScanStreamOptions): ReadableTyped<string[]> {
|
|
252
|
+
return this.redis().hscanStream(key, opt)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async hscanCount(key: string, opt: ScanStreamOptions): Promise<number> {
|
|
256
|
+
let count = 0
|
|
257
|
+
|
|
258
|
+
const stream = this.redis().hscanStream(key, opt)
|
|
259
|
+
|
|
260
|
+
await stream.forEach((keyValueList: string[]) => {
|
|
261
|
+
count += keyValueList.length / 2
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return count
|
|
265
|
+
}
|
|
266
|
+
|
|
208
267
|
async withPipeline(fn: (pipeline: ChainableCommander) => Promisable<void>): Promise<void> {
|
|
209
268
|
const pipeline = this.redis().pipeline()
|
|
210
269
|
await fn(pipeline)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommonKeyValueDBSaveBatchOptions,
|
|
3
|
+
CommonDBCreateOptions,
|
|
4
|
+
CommonKeyValueDB,
|
|
5
|
+
KeyValueDBTuple,
|
|
6
|
+
} from '@naturalcycles/db-lib'
|
|
7
|
+
import { _chunk, StringMap } from '@naturalcycles/js-lib'
|
|
8
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
9
|
+
import { RedisClient } from './redisClient'
|
|
10
|
+
import { RedisKeyValueDBCfg } from './redisKeyValueDB'
|
|
11
|
+
|
|
12
|
+
export interface RedisHashKeyValueDBCfg extends RedisKeyValueDBCfg {
|
|
13
|
+
hashKey: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
17
|
+
client: RedisClient
|
|
18
|
+
keyOfHashField: string
|
|
19
|
+
|
|
20
|
+
constructor(cfg: RedisHashKeyValueDBCfg) {
|
|
21
|
+
this.client = cfg.client
|
|
22
|
+
this.keyOfHashField = cfg.hashKey
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async ping(): Promise<void> {
|
|
26
|
+
await this.client.ping()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async [Symbol.asyncDispose](): Promise<void> {
|
|
30
|
+
await this.client.disconnect()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
34
|
+
if (!ids.length) return []
|
|
35
|
+
// we assume that the order of returned values is the same as order of input ids
|
|
36
|
+
const bufs = await this.client.hmgetBuffer(this.keyOfHashField, this.idsToKeys(table, ids))
|
|
37
|
+
return bufs.map((buf, i) => [ids[i], buf] as KeyValueDBTuple).filter(([_k, v]) => v !== null)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async deleteByIds(table: string, ids: string[]): Promise<void> {
|
|
41
|
+
if (!ids.length) return
|
|
42
|
+
await this.client.hdel(this.keyOfHashField, this.idsToKeys(table, ids))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async saveBatch(
|
|
46
|
+
table: string,
|
|
47
|
+
entries: KeyValueDBTuple[],
|
|
48
|
+
opt?: CommonKeyValueDBSaveBatchOptions,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
if (!entries.length) return
|
|
51
|
+
|
|
52
|
+
const entriesWithKey = entries.map(([k, v]) => [this.idToKey(table, k), v])
|
|
53
|
+
const map: StringMap<any> = Object.fromEntries(entriesWithKey)
|
|
54
|
+
|
|
55
|
+
if (opt?.expireAt) {
|
|
56
|
+
await this.client.hsetWithTTL(this.keyOfHashField, map, opt.expireAt)
|
|
57
|
+
} else {
|
|
58
|
+
await this.client.hset(this.keyOfHashField, map)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
63
|
+
let stream = this.client
|
|
64
|
+
.hscanStream(this.keyOfHashField, {
|
|
65
|
+
match: `${table}:*`,
|
|
66
|
+
})
|
|
67
|
+
.flatMap(keyValueList => {
|
|
68
|
+
const keys: string[] = []
|
|
69
|
+
keyValueList.forEach((keyOrValue, index) => {
|
|
70
|
+
if (index % 2 !== 0) return
|
|
71
|
+
keys.push(keyOrValue)
|
|
72
|
+
})
|
|
73
|
+
return this.keysToIds(table, keys)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (limit) {
|
|
77
|
+
stream = stream.take(limit)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return stream
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
84
|
+
return this.client
|
|
85
|
+
.hscanStream(this.keyOfHashField, {
|
|
86
|
+
match: `${table}:*`,
|
|
87
|
+
})
|
|
88
|
+
.flatMap(keyValueList => {
|
|
89
|
+
const values: string[] = []
|
|
90
|
+
keyValueList.forEach((keyOrValue, index) => {
|
|
91
|
+
if (index % 2 !== 1) return
|
|
92
|
+
values.push(keyOrValue)
|
|
93
|
+
})
|
|
94
|
+
return values.map(v => Buffer.from(v))
|
|
95
|
+
})
|
|
96
|
+
.take(limit || Infinity)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple> {
|
|
100
|
+
return this.client
|
|
101
|
+
.hscanStream(this.keyOfHashField, {
|
|
102
|
+
match: `${table}:*`,
|
|
103
|
+
})
|
|
104
|
+
.flatMap(keyValueList => {
|
|
105
|
+
const entries = _chunk(keyValueList, 2)
|
|
106
|
+
return entries.map(([k, v]) => {
|
|
107
|
+
return [this.keyToId(table, String(k)), Buffer.from(String(v))] satisfies KeyValueDBTuple
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
.take(limit || Infinity)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async count(table: string): Promise<number> {
|
|
114
|
+
return await this.client.hscanCount(this.keyOfHashField, {
|
|
115
|
+
match: `${table}:*`,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async increment(table: string, id: string, by: number = 1): Promise<number> {
|
|
120
|
+
return await this.client.hincr(this.keyOfHashField, this.idToKey(table, id), by)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {
|
|
124
|
+
if (!opt?.dropIfExists) return
|
|
125
|
+
|
|
126
|
+
await this.client.dropTable(table)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private idsToKeys(table: string, ids: string[]): string[] {
|
|
130
|
+
return ids.map(id => this.idToKey(table, id))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private idToKey(table: string, id: string): string {
|
|
134
|
+
return `${table}:${id}`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private keysToIds(table: string, keys: string[]): string[] {
|
|
138
|
+
return keys.map(key => this.keyToId(table, key))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private keyToId(table: string, key: string): string {
|
|
142
|
+
return key.slice(table.length + 1)
|
|
143
|
+
}
|
|
144
|
+
}
|
package/src/redisKeyValueDB.ts
CHANGED
|
@@ -118,6 +118,10 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
|
|
|
118
118
|
})
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
async increment(table: string, id: string, by: number = 1): Promise<number> {
|
|
122
|
+
return await this.client.incr(this.idToKey(table, id), by)
|
|
123
|
+
}
|
|
124
|
+
|
|
121
125
|
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {
|
|
122
126
|
if (!opt?.dropIfExists) return
|
|
123
127
|
|