@naturalcycles/redis-lib 4.3.1 → 4.4.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.
@@ -1,6 +1,6 @@
1
1
  import type { CommonLogger } from '@naturalcycles/js-lib/log';
2
2
  import type { AnyObject, NullableBuffer, NullableString, Promisable, UnixTimestamp } from '@naturalcycles/js-lib/types';
3
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
3
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
4
4
  import type { Redis, RedisOptions } from 'ioredis';
5
5
  import type { ScanStreamOptions } from 'ioredis/built/types.js';
6
6
  import type { ChainableCommander } from 'ioredis/built/utils/RedisCommander.js';
@@ -62,13 +62,13 @@ export declare class RedisClient implements CommonClient {
62
62
  Convenient type-safe wrapper.
63
63
  Returns BATCHES of keys in each iteration (as-is).
64
64
  */
65
- scanStream(opt?: ScanStreamOptions): ReadableTyped<string[]>;
65
+ scanStream(opt?: ScanStreamOptions): Pipeline<string[]>;
66
66
  /**
67
67
  * Like scanStream, but flattens the stream of keys.
68
68
  */
69
- scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string>;
69
+ scanStreamFlat(opt: ScanStreamOptions): Pipeline<string>;
70
70
  scanCount(opt: ScanStreamOptions): Promise<number>;
71
- hscanStream(key: string, opt?: ScanStreamOptions): ReadableTyped<string[]>;
71
+ hscanStream(key: string, opt?: ScanStreamOptions): Pipeline<string[]>;
72
72
  hscanCount(key: string, opt?: ScanStreamOptions): Promise<number>;
73
73
  withPipeline(fn: (pipeline: ChainableCommander) => Promisable<void>): Promise<void>;
74
74
  private log;
@@ -1,5 +1,5 @@
1
- import { Transform } from 'node:stream';
2
1
  import { _stringMapEntries } from '@naturalcycles/js-lib/types';
2
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream';
3
3
  /**
4
4
  Wraps the redis sdk with unified interface.
5
5
  Features:
@@ -174,7 +174,7 @@ export class RedisClient {
174
174
  await this.withPipeline(async (pipeline) => {
175
175
  await this.scanStream({
176
176
  match: `${table}:*`,
177
- }).forEach(keys => {
177
+ }).forEachSync(keys => {
178
178
  pipeline.del(keys);
179
179
  count += keys.length;
180
180
  });
@@ -187,7 +187,7 @@ export class RedisClient {
187
187
  await this.withPipeline(async (pipeline) => {
188
188
  await this.scanStream({
189
189
  match: `*`,
190
- }).forEach(keys => {
190
+ }).forEachSync(keys => {
191
191
  pipeline.del(keys);
192
192
  count += keys.length;
193
193
  });
@@ -199,7 +199,7 @@ export class RedisClient {
199
199
  Returns BATCHES of keys in each iteration (as-is).
200
200
  */
201
201
  scanStream(opt) {
202
- return createReadableFromAsync(async () => {
202
+ return Pipeline.fromAsyncReadable(async () => {
203
203
  const redis = await this.redis();
204
204
  return redis.scanStream(opt);
205
205
  });
@@ -208,20 +208,19 @@ export class RedisClient {
208
208
  * Like scanStream, but flattens the stream of keys.
209
209
  */
210
210
  scanStreamFlat(opt) {
211
- // biome-ignore lint/complexity/noFlatMapIdentity: ok
212
- return this.scanStream(opt).flatMap(keys => keys);
211
+ return this.scanStream(opt).flatten();
213
212
  }
214
213
  async scanCount(opt) {
215
214
  const redis = await this.redis();
216
215
  // todo: implement more efficiently, e.g via LUA?
217
216
  let count = 0;
218
- await redis.scanStream(opt).forEach(keys => {
217
+ await redis.scanStream(opt).forEach((keys) => {
219
218
  count += keys.length;
220
219
  });
221
220
  return count;
222
221
  }
223
222
  hscanStream(key, opt) {
224
- return createReadableFromAsync(async () => {
223
+ return Pipeline.fromAsyncReadable(async () => {
225
224
  const redis = await this.redis();
226
225
  return redis.hscanStream(key, opt);
227
226
  });
@@ -245,20 +244,3 @@ export class RedisClient {
245
244
  this.cfg.logger.log(...args);
246
245
  }
247
246
  }
248
- /**
249
- * Turn async function into Readable.
250
- */
251
- function createReadableFromAsync(fn) {
252
- const transform = new Transform({
253
- objectMode: true,
254
- transform: (chunk, _encoding, cb) => {
255
- cb(null, chunk);
256
- },
257
- });
258
- void fn()
259
- .then(readable => {
260
- readable.on('error', err => transform.destroy(err)).pipe(transform);
261
- })
262
- .catch(err => transform.destroy(err));
263
- return transform;
264
- }
@@ -1,6 +1,6 @@
1
1
  import type { CommonDBCreateOptions } from '@naturalcycles/db-lib';
2
2
  import type { CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib/kv';
3
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
3
+ import type { Pipeline } from '@naturalcycles/nodejs-lib/stream';
4
4
  import type { RedisKeyValueDBCfg } from './redisKeyValueDB.js';
5
5
  /**
6
6
  * RedisHashKeyValueDB is a KeyValueDB implementation that uses hash fields to simulate tables.
@@ -23,9 +23,9 @@ export declare class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDispo
23
23
  getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
24
24
  deleteByIds(table: string, ids: string[]): Promise<void>;
25
25
  saveBatch(table: string, entries: KeyValueDBTuple[], opt?: CommonKeyValueDBSaveBatchOptions): Promise<void>;
26
- streamIds(table: string, limit?: number): ReadableTyped<string>;
27
- streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
28
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
26
+ streamIds(table: string, limit?: number): Pipeline<string>;
27
+ streamValues(table: string, limit?: number): Pipeline<Buffer>;
28
+ streamEntries(table: string, limit?: number): Pipeline<KeyValueDBTuple>;
29
29
  count(table: string): Promise<number>;
30
30
  incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]>;
31
31
  createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
@@ -49,22 +49,22 @@ export class RedisHashKeyValueDB {
49
49
  }
50
50
  }
51
51
  streamIds(table, limit) {
52
- const stream = this.cfg.client
52
+ return this.cfg.client
53
53
  .hscanStream(table)
54
- .flatMap(keyValueList => {
54
+ .mapSync(keyValueList => {
55
55
  const keys = [];
56
56
  for (let i = 0; i < keyValueList.length; i += 2) {
57
57
  keys.push(keyValueList[i]);
58
58
  }
59
59
  return keys;
60
60
  })
61
- .take(limit || Infinity);
62
- return stream;
61
+ .flatten()
62
+ .limit(limit);
63
63
  }
64
64
  streamValues(table, limit) {
65
65
  return this.cfg.client
66
66
  .hscanStream(table)
67
- .flatMap(keyValueList => {
67
+ .mapSync(keyValueList => {
68
68
  const values = [];
69
69
  for (let i = 0; i < keyValueList.length; i += 2) {
70
70
  const value = Buffer.from(keyValueList[i + 1]);
@@ -72,12 +72,13 @@ export class RedisHashKeyValueDB {
72
72
  }
73
73
  return values;
74
74
  })
75
- .take(limit || Infinity);
75
+ .flatten()
76
+ .limit(limit);
76
77
  }
77
78
  streamEntries(table, limit) {
78
79
  return this.cfg.client
79
80
  .hscanStream(table)
80
- .flatMap(keyValueList => {
81
+ .mapSync(keyValueList => {
81
82
  const entries = [];
82
83
  for (let i = 0; i < keyValueList.length; i += 2) {
83
84
  const key = keyValueList[i];
@@ -86,7 +87,8 @@ export class RedisHashKeyValueDB {
86
87
  }
87
88
  return entries;
88
89
  })
89
- .take(limit || Infinity);
90
+ .flatten()
91
+ .limit(limit);
90
92
  }
91
93
  async count(table) {
92
94
  return await this.cfg.client.hscanCount(table);
@@ -1,6 +1,6 @@
1
1
  import type { CommonDBCreateOptions } from '@naturalcycles/db-lib';
2
2
  import type { CommonKeyValueDB, CommonKeyValueDBSaveBatchOptions, IncrementTuple, KeyValueDBTuple } from '@naturalcycles/db-lib/kv';
3
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
3
+ import type { Pipeline } from '@naturalcycles/nodejs-lib/stream';
4
4
  import type { RedisClient } from './redisClient.js';
5
5
  export interface RedisKeyValueDBCfg {
6
6
  client: RedisClient;
@@ -14,9 +14,9 @@ export declare class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposabl
14
14
  getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
15
15
  deleteByIds(table: string, ids: string[]): Promise<void>;
16
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): ReadableTyped<KeyValueDBTuple>;
17
+ streamIds(table: string, limit?: number): Pipeline<string>;
18
+ streamValues(table: string, limit?: number): Pipeline<Buffer>;
19
+ streamEntries(table: string, limit?: number): Pipeline<KeyValueDBTuple>;
20
20
  count(table: string): Promise<number>;
21
21
  incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]>;
22
22
  createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
@@ -45,42 +45,38 @@ export class RedisKeyValueDB {
45
45
  }
46
46
  }
47
47
  streamIds(table, limit) {
48
- let stream = this.cfg.client
48
+ return this.cfg.client
49
49
  .scanStream({
50
50
  match: `${table}:*`,
51
51
  // count: limit, // count is actually a "batchSize", not a limit
52
52
  })
53
- .flatMap(keys => this.keysToIds(table, keys));
54
- if (limit) {
55
- stream = stream.take(limit);
56
- }
57
- return stream;
53
+ .mapSync(keys => this.keysToIds(table, keys))
54
+ .flatten()
55
+ .limit(limit);
58
56
  }
59
57
  streamValues(table, limit) {
60
58
  return this.cfg.client
61
59
  .scanStream({
62
60
  match: `${table}:*`,
63
61
  })
64
- .flatMap(async (keys) => {
62
+ .map(async (keys) => {
65
63
  return (await this.cfg.client.mgetBuffer(keys)).filter(_isTruthy);
66
- }, {
67
- concurrency: 16,
68
- })
69
- .take(limit || Infinity);
64
+ }, { concurrency: 16 })
65
+ .flatten()
66
+ .limit(limit);
70
67
  }
71
68
  streamEntries(table, limit) {
72
69
  return this.cfg.client
73
70
  .scanStream({
74
71
  match: `${table}:*`,
75
72
  })
76
- .flatMap(async (keys) => {
73
+ .map(async (keys) => {
77
74
  // casting as Buffer[], because values are expected to exist for given keys
78
75
  const bufs = (await this.cfg.client.mgetBuffer(keys));
79
76
  return _zip(this.keysToIds(table, keys), bufs);
80
- }, {
81
- concurrency: 16,
82
- })
83
- .take(limit || Infinity);
77
+ }, { concurrency: 16 })
78
+ .flatten()
79
+ .limit(limit);
84
80
  }
85
81
  async count(table) {
86
82
  // todo: implement more efficiently, e.g via LUA?
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "devDependencies": {
12
12
  "@types/node": "^24",
13
- "@naturalcycles/dev-lib": "18.4.2"
13
+ "@naturalcycles/dev-lib": "19.37.0"
14
14
  },
15
15
  "exports": {
16
16
  ".": "./dist/index.js"
@@ -37,7 +37,7 @@
37
37
  "engines": {
38
38
  "node": ">=22.12.0"
39
39
  },
40
- "version": "4.3.1",
40
+ "version": "4.4.0",
41
41
  "description": "Redis implementation of CommonKeyValueDB interface",
42
42
  "author": "Natural Cycles Team",
43
43
  "license": "MIT",
@@ -1,8 +1,6 @@
1
- import { Transform } from 'node:stream'
2
1
  import type { CommonLogger } from '@naturalcycles/js-lib/log'
3
2
  import type {
4
3
  AnyObject,
5
- AsyncFunction,
6
4
  NullableBuffer,
7
5
  NullableString,
8
6
  Promisable,
@@ -10,7 +8,7 @@ import type {
10
8
  UnixTimestamp,
11
9
  } from '@naturalcycles/js-lib/types'
12
10
  import { _stringMapEntries } from '@naturalcycles/js-lib/types'
13
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
11
+ import { Pipeline } from '@naturalcycles/nodejs-lib/stream'
14
12
  import type { Redis, RedisOptions } from 'ioredis'
15
13
  import type { ScanStreamOptions } from 'ioredis/built/types.js'
16
14
  import type { ChainableCommander } from 'ioredis/built/utils/RedisCommander.js'
@@ -258,7 +256,7 @@ export class RedisClient implements CommonClient {
258
256
  await this.withPipeline(async pipeline => {
259
257
  await this.scanStream({
260
258
  match: `${table}:*`,
261
- }).forEach(keys => {
259
+ }).forEachSync(keys => {
262
260
  pipeline.del(keys)
263
261
  count += keys.length
264
262
  })
@@ -274,7 +272,7 @@ export class RedisClient implements CommonClient {
274
272
  await this.withPipeline(async pipeline => {
275
273
  await this.scanStream({
276
274
  match: `*`,
277
- }).forEach(keys => {
275
+ }).forEachSync(keys => {
278
276
  pipeline.del(keys)
279
277
  count += keys.length
280
278
  })
@@ -287,8 +285,8 @@ export class RedisClient implements CommonClient {
287
285
  Convenient type-safe wrapper.
288
286
  Returns BATCHES of keys in each iteration (as-is).
289
287
  */
290
- scanStream(opt?: ScanStreamOptions): ReadableTyped<string[]> {
291
- return createReadableFromAsync(async () => {
288
+ scanStream(opt?: ScanStreamOptions): Pipeline<string[]> {
289
+ return Pipeline.fromAsyncReadable(async () => {
292
290
  const redis = await this.redis()
293
291
  return redis.scanStream(opt)
294
292
  })
@@ -297,9 +295,8 @@ export class RedisClient implements CommonClient {
297
295
  /**
298
296
  * Like scanStream, but flattens the stream of keys.
299
297
  */
300
- scanStreamFlat(opt: ScanStreamOptions): ReadableTyped<string> {
301
- // biome-ignore lint/complexity/noFlatMapIdentity: ok
302
- return this.scanStream(opt).flatMap(keys => keys)
298
+ scanStreamFlat(opt: ScanStreamOptions): Pipeline<string> {
299
+ return this.scanStream(opt).flatten()
303
300
  }
304
301
 
305
302
  async scanCount(opt: ScanStreamOptions): Promise<number> {
@@ -307,15 +304,15 @@ export class RedisClient implements CommonClient {
307
304
  // todo: implement more efficiently, e.g via LUA?
308
305
  let count = 0
309
306
 
310
- await (redis.scanStream(opt) as ReadableTyped<string[]>).forEach(keys => {
307
+ await redis.scanStream(opt).forEach((keys: string[]) => {
311
308
  count += keys.length
312
309
  })
313
310
 
314
311
  return count
315
312
  }
316
313
 
317
- hscanStream(key: string, opt?: ScanStreamOptions): ReadableTyped<string[]> {
318
- return createReadableFromAsync(async () => {
314
+ hscanStream(key: string, opt?: ScanStreamOptions): Pipeline<string[]> {
315
+ return Pipeline.fromAsyncReadable(async () => {
319
316
  const redis = await this.redis()
320
317
  return redis.hscanStream(key, opt)
321
318
  })
@@ -345,23 +342,3 @@ export class RedisClient implements CommonClient {
345
342
  this.cfg.logger.log(...args)
346
343
  }
347
344
  }
348
-
349
- /**
350
- * Turn async function into Readable.
351
- */
352
- function createReadableFromAsync<T>(fn: AsyncFunction<ReadableTyped<T>>): ReadableTyped<T> {
353
- const transform = new Transform({
354
- objectMode: true,
355
- transform: (chunk, _encoding, cb) => {
356
- cb(null, chunk)
357
- },
358
- })
359
-
360
- void fn()
361
- .then(readable => {
362
- readable.on('error', err => transform.destroy(err)).pipe(transform)
363
- })
364
- .catch(err => transform.destroy(err))
365
-
366
- return transform
367
- }
@@ -6,7 +6,7 @@ import type {
6
6
  KeyValueDBTuple,
7
7
  } from '@naturalcycles/db-lib/kv'
8
8
  import { commonKeyValueDBFullSupport } from '@naturalcycles/db-lib/kv'
9
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
9
+ import type { Pipeline } from '@naturalcycles/nodejs-lib/stream'
10
10
  import type { RedisKeyValueDBCfg } from './redisKeyValueDB.js'
11
11
 
12
12
  /**
@@ -64,25 +64,24 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
64
64
  }
65
65
  }
66
66
 
67
- streamIds(table: string, limit?: number): ReadableTyped<string> {
68
- const stream = this.cfg.client
67
+ streamIds(table: string, limit?: number): Pipeline<string> {
68
+ return this.cfg.client
69
69
  .hscanStream(table)
70
- .flatMap(keyValueList => {
70
+ .mapSync(keyValueList => {
71
71
  const keys: string[] = []
72
72
  for (let i = 0; i < keyValueList.length; i += 2) {
73
73
  keys.push(keyValueList[i]!)
74
74
  }
75
75
  return keys
76
76
  })
77
- .take(limit || Infinity)
78
-
79
- return stream
77
+ .flatten()
78
+ .limit(limit)
80
79
  }
81
80
 
82
- streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
81
+ streamValues(table: string, limit?: number): Pipeline<Buffer> {
83
82
  return this.cfg.client
84
83
  .hscanStream(table)
85
- .flatMap(keyValueList => {
84
+ .mapSync(keyValueList => {
86
85
  const values: Buffer[] = []
87
86
  for (let i = 0; i < keyValueList.length; i += 2) {
88
87
  const value = Buffer.from(keyValueList[i + 1]!)
@@ -90,13 +89,14 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
90
89
  }
91
90
  return values
92
91
  })
93
- .take(limit || Infinity)
92
+ .flatten()
93
+ .limit(limit)
94
94
  }
95
95
 
96
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
96
+ streamEntries(table: string, limit?: number): Pipeline<KeyValueDBTuple> {
97
97
  return this.cfg.client
98
98
  .hscanStream(table)
99
- .flatMap(keyValueList => {
99
+ .mapSync(keyValueList => {
100
100
  const entries: [string, Buffer][] = []
101
101
  for (let i = 0; i < keyValueList.length; i += 2) {
102
102
  const key = keyValueList[i]!
@@ -105,7 +105,8 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
105
105
  }
106
106
  return entries
107
107
  })
108
- .take(limit || Infinity)
108
+ .flatten()
109
+ .limit(limit)
109
110
  }
110
111
 
111
112
  async count(table: string): Promise<number> {
@@ -8,7 +8,7 @@ import type {
8
8
  import { commonKeyValueDBFullSupport } from '@naturalcycles/db-lib/kv'
9
9
  import { _isTruthy } from '@naturalcycles/js-lib'
10
10
  import { _zip } from '@naturalcycles/js-lib/array/array.util.js'
11
- import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
11
+ import type { Pipeline } from '@naturalcycles/nodejs-lib/stream'
12
12
  import type { RedisClient } from './redisClient.js'
13
13
 
14
14
  export interface RedisKeyValueDBCfg {
@@ -65,53 +65,47 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
65
65
  }
66
66
  }
67
67
 
68
- streamIds(table: string, limit?: number): ReadableTyped<string> {
69
- let stream = this.cfg.client
68
+ streamIds(table: string, limit?: number): Pipeline<string> {
69
+ return this.cfg.client
70
70
  .scanStream({
71
71
  match: `${table}:*`,
72
72
  // count: limit, // count is actually a "batchSize", not a limit
73
73
  })
74
- .flatMap(keys => this.keysToIds(table, keys))
75
-
76
- if (limit) {
77
- stream = stream.take(limit)
78
- }
79
-
80
- return stream
74
+ .mapSync(keys => this.keysToIds(table, keys))
75
+ .flatten()
76
+ .limit(limit)
81
77
  }
82
78
 
83
- streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
79
+ streamValues(table: string, limit?: number): Pipeline<Buffer> {
84
80
  return this.cfg.client
85
81
  .scanStream({
86
82
  match: `${table}:*`,
87
83
  })
88
- .flatMap(
84
+ .map(
89
85
  async keys => {
90
86
  return (await this.cfg.client.mgetBuffer(keys)).filter(_isTruthy)
91
87
  },
92
- {
93
- concurrency: 16,
94
- },
88
+ { concurrency: 16 },
95
89
  )
96
- .take(limit || Infinity)
90
+ .flatten()
91
+ .limit(limit)
97
92
  }
98
93
 
99
- streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
94
+ streamEntries(table: string, limit?: number): Pipeline<KeyValueDBTuple> {
100
95
  return this.cfg.client
101
96
  .scanStream({
102
97
  match: `${table}:*`,
103
98
  })
104
- .flatMap(
99
+ .map(
105
100
  async keys => {
106
101
  // casting as Buffer[], because values are expected to exist for given keys
107
102
  const bufs = (await this.cfg.client.mgetBuffer(keys)) as Buffer[]
108
103
  return _zip(this.keysToIds(table, keys), bufs)
109
104
  },
110
- {
111
- concurrency: 16,
112
- },
105
+ { concurrency: 16 },
113
106
  )
114
- .take(limit || Infinity)
107
+ .flatten()
108
+ .limit(limit)
115
109
  }
116
110
 
117
111
  async count(table: string): Promise<number> {