@naturalcycles/redis-lib 3.1.0 → 3.2.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 CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './redisClient';
2
- export * from './redisKeyValueDB';
3
2
  export * from './redisHashKeyValueDB';
3
+ export * from './redisKeyValueDB';
package/dist/index.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./redisClient"), exports);
5
- tslib_1.__exportStar(require("./redisKeyValueDB"), exports);
6
5
  tslib_1.__exportStar(require("./redisHashKeyValueDB"), exports);
6
+ tslib_1.__exportStar(require("./redisKeyValueDB"), exports);
@@ -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(key: string, value: AnyObject, expireAt: UnixTimestampNumber): Promise<void>;
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>;
@@ -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(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);
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
  }
@@ -158,6 +184,7 @@ class RedisClient {
158
184
  * Like scanStream, but flattens the stream of keys.
159
185
  */
160
186
  scanStreamFlat(opt) {
187
+ // biome-ignore lint/correctness/noFlatMapIdentity: ok
161
188
  return this.redis().scanStream(opt).flatMap(keys => keys);
162
189
  }
163
190
  async scanCount(opt) {
@@ -1,4 +1,4 @@
1
- import { CommonKeyValueDBSaveBatchOptions, CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
1
+ import { CommonDBCreateOptions, CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
3
  import { RedisClient } from './redisClient';
4
4
  import { RedisKeyValueDBCfg } from './redisKeyValueDB';
@@ -9,6 +9,10 @@ export declare class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDispo
9
9
  client: RedisClient;
10
10
  keyOfHashField: string;
11
11
  constructor(cfg: RedisHashKeyValueDBCfg);
12
+ support: {
13
+ count?: boolean;
14
+ increment?: boolean;
15
+ };
12
16
  ping(): Promise<void>;
13
17
  [Symbol.asyncDispose](): Promise<void>;
14
18
  getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
@@ -16,9 +20,9 @@ export declare class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDispo
16
20
  saveBatch(table: string, entries: KeyValueDBTuple[], opt?: CommonKeyValueDBSaveBatchOptions): Promise<void>;
17
21
  streamIds(table: string, limit?: number): ReadableTyped<string>;
18
22
  streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
19
- streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple>;
23
+ streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
20
24
  count(table: string): Promise<number>;
21
- increment(table: string, id: string, by?: number): Promise<number>;
25
+ incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]>;
22
26
  createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
23
27
  private idsToKeys;
24
28
  private idToKey;
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RedisHashKeyValueDB = void 0;
4
+ const db_lib_1 = require("@naturalcycles/db-lib");
4
5
  const js_lib_1 = require("@naturalcycles/js-lib");
5
6
  class RedisHashKeyValueDB {
6
7
  constructor(cfg) {
8
+ this.support = {
9
+ ...db_lib_1.commonKeyValueDBFullSupport,
10
+ };
7
11
  this.client = cfg.client;
8
12
  this.keyOfHashField = cfg.hashKey;
9
13
  }
@@ -90,8 +94,11 @@ class RedisHashKeyValueDB {
90
94
  match: `${table}:*`,
91
95
  });
92
96
  }
93
- async increment(table, id, by = 1) {
94
- return await this.client.hincr(this.keyOfHashField, this.idToKey(table, id), by);
97
+ async incrementBatch(table, increments) {
98
+ const incrementTuplesWithInternalKeys = increments.map(([id, v]) => [this.idToKey(table, id), v]);
99
+ const resultsWithInternalKeys = await this.client.hincrBatch(this.keyOfHashField, incrementTuplesWithInternalKeys);
100
+ const results = resultsWithInternalKeys.map(([k, v]) => [this.keyToId(table, k), v]);
101
+ return results;
95
102
  }
96
103
  async createTable(table, opt) {
97
104
  if (!opt?.dropIfExists)
@@ -1,4 +1,4 @@
1
- import { CommonKeyValueDBSaveBatchOptions, CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
1
+ import { CommonDBCreateOptions, CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib';
2
2
  import { ReadableTyped } from '@naturalcycles/nodejs-lib';
3
3
  import { RedisClient } from './redisClient';
4
4
  export interface RedisKeyValueDBCfg {
@@ -7,6 +7,10 @@ export interface RedisKeyValueDBCfg {
7
7
  export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
8
8
  constructor(cfg: RedisKeyValueDBCfg);
9
9
  client: RedisClient;
10
+ support: {
11
+ count?: boolean;
12
+ increment?: boolean;
13
+ };
10
14
  ping(): Promise<void>;
11
15
  [Symbol.asyncDispose](): Promise<void>;
12
16
  getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
@@ -14,9 +18,9 @@ export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposabl
14
18
  saveBatch(table: string, entries: KeyValueDBTuple[], opt?: CommonKeyValueDBSaveBatchOptions): Promise<void>;
15
19
  streamIds(table: string, limit?: number): ReadableTyped<string>;
16
20
  streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
17
- streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple>;
21
+ streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
18
22
  count(table: string): Promise<number>;
19
- increment(table: string, id: string, by?: number): Promise<number>;
23
+ incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]>;
20
24
  createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
21
25
  private idsToKeys;
22
26
  private idToKey;
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RedisKeyValueDB = void 0;
4
+ const db_lib_1 = require("@naturalcycles/db-lib");
4
5
  const js_lib_1 = require("@naturalcycles/js-lib");
5
6
  class RedisKeyValueDB {
6
7
  constructor(cfg) {
8
+ this.support = {
9
+ ...db_lib_1.commonKeyValueDBFullSupport,
10
+ };
7
11
  this.client = cfg.client;
8
12
  }
9
13
  async ping() {
@@ -85,8 +89,11 @@ class RedisKeyValueDB {
85
89
  match: `${table}:*`,
86
90
  });
87
91
  }
88
- async increment(table, id, by = 1) {
89
- return await this.client.incr(this.idToKey(table, id), by);
92
+ async incrementBatch(table, increments) {
93
+ const incrementTuplesWithInternalKeys = increments.map(([id, v]) => [this.idToKey(table, id), v]);
94
+ const resultsWithInternalKeys = await this.client.incrBatch(incrementTuplesWithInternalKeys);
95
+ const results = resultsWithInternalKeys.map(([k, v]) => [this.keyToId(table, k), v]);
96
+ return results;
90
97
  }
91
98
  async createTable(table, opt) {
92
99
  if (!opt?.dropIfExists)
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@naturalcycles/redis-lib",
3
3
  "scripts": {
4
- "prepare": "husky"
4
+ "prepare": "husky",
5
+ "build": "dev-lib build",
6
+ "test": "dev-lib test",
7
+ "lint": "dev-lib lint",
8
+ "bt": "dev-lib bt",
9
+ "lbt": "dev-lib lbt"
5
10
  },
6
11
  "dependencies": {
7
12
  "@naturalcycles/db-lib": "^9.9.2",
@@ -10,8 +15,8 @@
10
15
  "ioredis": "^5.3.2"
11
16
  },
12
17
  "devDependencies": {
13
- "@naturalcycles/dev-lib": "^13.49.2",
14
- "@types/node": "^20.12.2",
18
+ "@naturalcycles/dev-lib": "^15.22.0",
19
+ "@types/node": "^22.7.5",
15
20
  "jest": "^29.7.0"
16
21
  },
17
22
  "files": [
@@ -33,9 +38,9 @@
33
38
  "url": "https://github.com/NaturalCycles/redis-lib"
34
39
  },
35
40
  "engines": {
36
- "node": ">=18.12"
41
+ "node": ">=20.13"
37
42
  },
38
- "version": "3.1.0",
43
+ "version": "3.2.0",
39
44
  "description": "Redis implementation of CommonKeyValueDB interface",
40
45
  "author": "Natural Cycles Team",
41
46
  "license": "MIT"
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './redisClient'
2
- export * from './redisKeyValueDB'
3
2
  export * from './redisHashKeyValueDB'
3
+ export * from './redisKeyValueDB'
@@ -1,15 +1,17 @@
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'
10
- // eslint-disable-next-line import/no-duplicates
12
+ // eslint-disable-next-line import-x/no-duplicates
11
13
  import type { Redis, RedisOptions } from 'ioredis'
12
- // eslint-disable-next-line import/no-duplicates
14
+ // eslint-disable-next-line import-x/no-duplicates
13
15
  import type * as RedisLib from 'ioredis'
14
16
  import type { ScanStreamOptions } from 'ioredis/built/types'
15
17
  import type { ChainableCommander } from 'ioredis/built/utils/RedisCommander'
@@ -153,10 +155,29 @@ export class RedisClient implements CommonClient {
153
155
  return await this.redis().hmgetBuffer(key, ...fields)
154
156
  }
155
157
 
156
- async hincr(key: string, field: string, increment: number = 1): Promise<number> {
158
+ async hincr(key: string, field: string, increment = 1): Promise<number> {
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(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)
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> {
@@ -183,10 +209,29 @@ export class RedisClient implements CommonClient {
183
209
  await this.redis().mset(obj)
184
210
  }
185
211
 
186
- async incr(key: string, by: number = 1): Promise<number> {
212
+ async incr(key: string, by = 1): Promise<number> {
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
  }
@@ -234,6 +279,7 @@ export class RedisClient implements CommonClient {
234
279
  * Like scanStream, but flattens the stream of keys.
235
280
  */
236
281
  scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string> {
282
+ // biome-ignore lint/correctness/noFlatMapIdentity: ok
237
283
  return (this.redis().scanStream(opt) as ReadableTyped<string[]>).flatMap(keys => keys)
238
284
  }
239
285
 
@@ -1,7 +1,9 @@
1
1
  import {
2
- CommonKeyValueDBSaveBatchOptions,
3
2
  CommonDBCreateOptions,
4
3
  CommonKeyValueDB,
4
+ commonKeyValueDBFullSupport,
5
+ CommonKeyValueDBSaveBatchOptions,
6
+ IncrementTuple,
5
7
  KeyValueDBTuple,
6
8
  } from '@naturalcycles/db-lib'
7
9
  import { _chunk, StringMap } from '@naturalcycles/js-lib'
@@ -22,6 +24,10 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
22
24
  this.keyOfHashField = cfg.hashKey
23
25
  }
24
26
 
27
+ support = {
28
+ ...commonKeyValueDBFullSupport,
29
+ }
30
+
25
31
  async ping(): Promise<void> {
26
32
  await this.client.ping()
27
33
  }
@@ -96,7 +102,7 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
96
102
  .take(limit || Infinity)
97
103
  }
98
104
 
99
- streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple> {
105
+ streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
100
106
  return this.client
101
107
  .hscanStream(this.keyOfHashField, {
102
108
  match: `${table}:*`,
@@ -116,8 +122,18 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
116
122
  })
117
123
  }
118
124
 
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)
125
+ async incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]> {
126
+ const incrementTuplesWithInternalKeys = increments.map(
127
+ ([id, v]) => [this.idToKey(table, id), v] as [string, number],
128
+ )
129
+ const resultsWithInternalKeys = await this.client.hincrBatch(
130
+ this.keyOfHashField,
131
+ incrementTuplesWithInternalKeys,
132
+ )
133
+ const results = resultsWithInternalKeys.map(
134
+ ([k, v]) => [this.keyToId(table, k), v] as IncrementTuple,
135
+ )
136
+ return results
121
137
  }
122
138
 
123
139
  async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {
@@ -1,7 +1,9 @@
1
1
  import {
2
- CommonKeyValueDBSaveBatchOptions,
3
2
  CommonDBCreateOptions,
4
3
  CommonKeyValueDB,
4
+ commonKeyValueDBFullSupport,
5
+ CommonKeyValueDBSaveBatchOptions,
6
+ IncrementTuple,
5
7
  KeyValueDBTuple,
6
8
  } from '@naturalcycles/db-lib'
7
9
  import { _isTruthy, _zip } from '@naturalcycles/js-lib'
@@ -19,6 +21,10 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
19
21
 
20
22
  client: RedisClient
21
23
 
24
+ support = {
25
+ ...commonKeyValueDBFullSupport,
26
+ }
27
+
22
28
  async ping(): Promise<void> {
23
29
  await this.client.ping()
24
30
  }
@@ -93,7 +99,7 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
93
99
  .take(limit || Infinity)
94
100
  }
95
101
 
96
- streamEntries(table: string, limit?: number | undefined): ReadableTyped<KeyValueDBTuple> {
102
+ streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
97
103
  return this.client
98
104
  .scanStream({
99
105
  match: `${table}:*`,
@@ -118,8 +124,15 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
118
124
  })
119
125
  }
120
126
 
121
- async increment(table: string, id: string, by: number = 1): Promise<number> {
122
- return await this.client.incr(this.idToKey(table, id), by)
127
+ async incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]> {
128
+ const incrementTuplesWithInternalKeys = increments.map(
129
+ ([id, v]) => [this.idToKey(table, id), v] as [string, number],
130
+ )
131
+ const resultsWithInternalKeys = await this.client.incrBatch(incrementTuplesWithInternalKeys)
132
+ const results = resultsWithInternalKeys.map(
133
+ ([k, v]) => [this.keyToId(table, k), v] as IncrementTuple,
134
+ )
135
+ return results
123
136
  }
124
137
 
125
138
  async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {