@pi-r/redis 0.7.2 → 0.8.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/client/index.js CHANGED
@@ -1,26 +1,31 @@
1
1
  "use strict";
2
- exports.DB_SOURCE_TYPE = exports.DB_SOURCE_CLIENT = exports.checkTimeout = exports.executeBatchQuery = exports.executeQuery = exports.setCredential = void 0;
2
+ exports.DB_SOURCE_TYPE = exports.DB_SOURCE_CLIENT = void 0;
3
+ exports.setCredential = setCredential;
4
+ exports.executeQuery = executeQuery;
5
+ exports.executeBatchQuery = executeBatchQuery;
6
+ exports.checkTimeout = checkTimeout;
3
7
  const redis = require("redis");
8
+ const crypto_1 = require("crypto");
9
+ const Db = require("@e-mc/db");
4
10
  const util_1 = require("@e-mc/db/util");
5
11
  const types_1 = require("@e-mc/types");
6
- const Db = require("@e-mc/db");
7
- const DbPool = require('@e-mc/db/pool');
8
- class RedisPool extends DbPool {
9
- async getConnection() {
10
- return this.client;
11
- }
12
- async close() {
13
- return this.client.disconnect();
14
- }
15
- isEmpty() {
16
- return this.closed;
12
+ const DbPool = require("@pi-r/redis/client/pool");
13
+ const POOL_STATE = {};
14
+ async function doScan(client, key, index, cursor = [0], iterations = [Infinity], options) {
15
+ if (!Array.isArray(cursor)) {
16
+ cursor = [cursor];
17
17
  }
18
- get closed() {
19
- return !this.client.isOpen;
18
+ if (!Array.isArray(iterations)) {
19
+ iterations = [iterations];
20
20
  }
21
+ let result = [], current = cursor[index] ?? 0, length = iterations[index] ?? Infinity;
22
+ do {
23
+ const item = await client.hScan(key, current, options);
24
+ result = result.concat(item.tuples);
25
+ current = item.cursor;
26
+ } while (current > 0 && --length > 0);
27
+ return result;
21
28
  }
22
- const POOL_STATE = {};
23
- const POOL_ACTIVE = new WeakSet();
24
29
  async function getClient(db, options, poolKey) {
25
30
  const socket = options.socket ||= {};
26
31
  if (!poolKey) {
@@ -36,16 +41,13 @@ async function getClient(db, options, poolKey) {
36
41
  delete POOL_STATE[poolKey];
37
42
  });
38
43
  }
39
- client.connect().then(() => resolve(client)).catch((err) => reject(err));
44
+ client.connect().then(() => {
45
+ resolve(client);
46
+ }).catch((err) => {
47
+ reject(err);
48
+ });
40
49
  });
41
50
  }
42
- function removePoolProperties(options) {
43
- if (!options.uuidKey && ('socket' in options || 'isolationPoolOptions' in options) && POOL_ACTIVE.has(options)) {
44
- return { ...options, socket: options.socket?.tls ? { tls: true } : undefined, isolationPoolOptions: undefined };
45
- }
46
- return options;
47
- }
48
- const getPoolKey = (options) => options.url && JSON.stringify(options);
49
51
  async function setCredential(item) {
50
52
  const credential = this.getCredential(item);
51
53
  const options = item.options ||= {};
@@ -122,26 +124,24 @@ async function setCredential(item) {
122
124
  poolOptions.acquireTimeoutMillis ??= timeout;
123
125
  }
124
126
  }
125
- const poolKey = getPoolKey(client);
127
+ const poolKey = DbPool.asString(client);
126
128
  if ((pool = POOL_STATE[poolKey]) && !pool.closed) {
127
129
  pool.add(item);
128
130
  return;
129
131
  }
130
132
  try {
131
- const target = new RedisPool(await getClient(redis, client, poolKey), poolKey, username && password ? { username, password } : undefined).add(item);
132
- target.parent = POOL_STATE;
133
- target.success = 1;
133
+ const instance = new DbPool(await getClient(redis, client, poolKey), poolKey, username && password ? { username, password } : undefined).add(item);
134
+ instance.parent = POOL_STATE;
135
+ instance.success = 1;
134
136
  }
135
137
  catch {
136
138
  item.usePool = false;
137
139
  delete client.isolationPoolOptions;
138
140
  }
139
141
  }
140
- exports.setCredential = setCredential;
141
142
  async function executeQuery(item, options) {
142
143
  return (await executeBatchQuery.call(this, [item], options))[0] || [];
143
144
  }
144
- exports.executeQuery = executeQuery;
145
145
  async function executeBatchQuery(batch, options = '', outResult) {
146
146
  const length = batch.length;
147
147
  if (length === 0) {
@@ -175,11 +175,10 @@ async function executeBatchQuery(batch, options = '', outResult) {
175
175
  item.transactionState = 64;
176
176
  let client;
177
177
  if (item.usePool) {
178
- const pool = DbPool.findKey(POOL_STATE, item.usePool, getPoolKey(credential), ...connectOnce ? [item, batch[0]] : [item]);
178
+ const pool = DbPool.findKey(POOL_STATE, item.usePool, DbPool.asString(credential), ...connectOnce ? [item, batch[0]] : [item]);
179
179
  if (pool) {
180
- client = pool.client;
180
+ client = await pool.getConnection(credential);
181
181
  pool.connected = true;
182
- POOL_ACTIVE.add(credential);
183
182
  }
184
183
  }
185
184
  if (!client) {
@@ -245,7 +244,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
245
244
  queryString += '_' + format;
246
245
  }
247
246
  if (ignoreCache !== 1) {
248
- if (rows = this.getQueryResult(source, removePoolProperties(credential), queryString, cacheValue)) {
247
+ if (rows = this.getQueryResult(source, DbPool.sanitize(credential), queryString, cacheValue)) {
249
248
  ++redisCache;
250
249
  if (parallel) {
251
250
  tasks[i] = Promise.resolve(rows);
@@ -288,8 +287,12 @@ async function executeBatchQuery(batch, options = '', outResult) {
288
287
  return;
289
288
  }
290
289
  const has = (n) => typeof n === 'number' && n > 0;
291
- const failKey = () => failed((0, types_1.errorMessage)(f, "Invalid key", Db.asString(k) || "Unknown"));
292
- const failValue = () => failed((0, types_1.errorMessage)(f, "Invalid value", Db.asString(v) || "Unknown"));
290
+ const failKey = () => {
291
+ failed((0, types_1.errorMessage)(f, "Invalid key", Db.asString(k) || "Unknown"));
292
+ };
293
+ const failValue = () => {
294
+ failed((0, types_1.errorMessage)(f, "Invalid value", Db.asString(v) || "Unknown"));
295
+ };
293
296
  const commandSet = (0, types_1.isPlainObject)(setOptions.command) && redis.commandOptions(setOptions.command);
294
297
  let pending, codeMin = 0, EX, PX, EXAT, PXAT, KEEPTTL;
295
298
  if (f === 'JSON') {
@@ -342,7 +345,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
342
345
  m.path ||= path;
343
346
  return m;
344
347
  });
345
- if (data.length) {
348
+ if (data.length > 0) {
346
349
  pending = commandSet ? client.json.mSet(commandSet, data) : client.json.mSet(data);
347
350
  }
348
351
  break;
@@ -482,13 +485,17 @@ async function executeBatchQuery(batch, options = '', outResult) {
482
485
  failed((0, types_1.errorMessage)(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
483
486
  }
484
487
  })
485
- .catch((err) => failed(err));
488
+ .catch((err) => {
489
+ failed(err);
490
+ });
486
491
  }
487
492
  else {
488
493
  success(target);
489
494
  }
490
495
  })
491
- .catch((err) => failed(err));
496
+ .catch((err) => {
497
+ failed(err);
498
+ });
492
499
  }
493
500
  else {
494
501
  failValue();
@@ -516,7 +523,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
516
523
  const { query = '', schema, index } = target;
517
524
  let idx = '';
518
525
  if ((0, types_1.isObject)(schema)) {
519
- idx = index || (0, types_1.generateUUID)();
526
+ idx = index || (0, crypto_1.randomUUID)();
520
527
  await (command ? client.ft.create(command, idx, schema, target.options) : client.ft.create(idx, schema, target.options));
521
528
  }
522
529
  try {
@@ -535,7 +542,9 @@ async function executeBatchQuery(batch, options = '', outResult) {
535
542
  }
536
543
  finally {
537
544
  if (idx && idx !== index) {
538
- client.ft.dropIndex(idx).catch((err) => this.writeFail([`Unable to drop ${target === search ? 'search' : 'aggregate'} index`, idx], err, { type: 65536, fatal: false }));
545
+ client.ft.dropIndex(idx).catch((err) => {
546
+ this.writeFail([`Unable to drop ${target === search ? 'search' : 'aggregate'} index`, idx], err, { type: 65536, fatal: false });
547
+ });
539
548
  }
540
549
  }
541
550
  }
@@ -549,6 +558,9 @@ async function executeBatchQuery(batch, options = '', outResult) {
549
558
  case 'HVALS':
550
559
  data = await Promise.all(key.map(async (k) => client.hVals(...command ? [command, k] : [k])));
551
560
  break;
561
+ case 'HSCAN':
562
+ data = await Promise.all(key.map(async (k, index) => doScan(client, k, index, item.cursor, item.iterations, clientOptions.scan)));
563
+ break;
552
564
  case 'JSON':
553
565
  data = (await (command ? client.json.mGet(command, key.map(c => b(c)), item.path || '$') : client.json.mGet(key.map(c => b(c)), item.path || '$'))).flat();
554
566
  break;
@@ -565,6 +577,9 @@ async function executeBatchQuery(batch, options = '', outResult) {
565
577
  case 'HVALS':
566
578
  data = await client.hVals(...command ? [command, key] : [key]);
567
579
  break;
580
+ case 'HSCAN':
581
+ data = await doScan(client, key, 0, item.cursor, item.iterations, clientOptions.scan);
582
+ break;
568
583
  case 'JSON':
569
584
  data = await (clientOptions.get ? client.json.get(...command ? [command, b(key), clientOptions.get] : [b(key), clientOptions.get]) : client.json.get(...command ? [command, b(key)] : [b(key)]));
570
585
  break;
@@ -584,14 +599,14 @@ async function executeBatchQuery(batch, options = '', outResult) {
584
599
  throw (0, types_1.errorMessage)(source, "Missing database query");
585
600
  }
586
601
  this.add(item, 4);
587
- resolve(this.setQueryResult(source, removePoolProperties(redisCredential || credential), queryString, rows, cacheValue));
602
+ resolve(this.setQueryResult(source, DbPool.sanitize(redisCredential || credential), queryString, rows, cacheValue));
588
603
  }
589
604
  catch (err) {
590
605
  if (err instanceof Error) {
591
606
  switch (err.message) {
592
607
  case "The client is closed":
593
608
  case "Disconnects client": {
594
- const pool = item.usePool && DbPool.findKey(POOL_STATE, item.usePool, getPoolKey(credential));
609
+ const pool = item.usePool && DbPool.findKey(POOL_STATE, item.usePool, DbPool.asString(credential));
595
610
  if (pool) {
596
611
  await pool.detach(true);
597
612
  }
@@ -618,16 +633,16 @@ async function executeBatchQuery(batch, options = '', outResult) {
618
633
  }
619
634
  }
620
635
  return this.processRows(batch, tasks, {
621
- disconnect: () => clients.forEach(item => {
622
- item.disconnect();
623
- }),
624
- parallel
636
+ parallel,
637
+ disconnect() {
638
+ for (const item of clients) {
639
+ void item.disconnect();
640
+ }
641
+ }
625
642
  }, outResult);
626
643
  }
627
- exports.executeBatchQuery = executeBatchQuery;
628
644
  async function checkTimeout(value, limit = 0) {
629
645
  return DbPool.checkTimeout(POOL_STATE, value, limit);
630
646
  }
631
- exports.checkTimeout = checkTimeout;
632
647
  exports.DB_SOURCE_CLIENT = true;
633
648
  exports.DB_SOURCE_TYPE = types_1.DB_TYPE.NOSQL | types_1.DB_TYPE.KEYVALUE;
@@ -0,0 +1,9 @@
1
+ import type { DbPoolConstructor } from '@e-mc/types/lib/db';
2
+
3
+ import type { DbPoolCredential, RedisDataSource } from '../types';
4
+
5
+ import type { RedisClientType } from 'redis';
6
+
7
+ declare const RedisPool: DbPoolConstructor<RedisDataSource, RedisClientType, RedisClientType, DbPoolCredential>;
8
+
9
+ export = RedisPool;
package/client/pool.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ const DbPool = require('@e-mc/db/pool');
3
+ const POOL_ACTIVE = new WeakSet();
4
+ class RedisPool extends DbPool {
5
+ static asString(credential) {
6
+ if (credential.url) {
7
+ return JSON.stringify(credential);
8
+ }
9
+ return super.asString(credential);
10
+ }
11
+ static sanitize(credential) {
12
+ if (!POOL_ACTIVE.has(credential) || credential.uuidKey) {
13
+ return credential;
14
+ }
15
+ if ('socket' in credential || 'isolationPoolOptions' in credential) {
16
+ return { ...credential, socket: credential.socket?.tls ? { tls: true } : undefined, isolationPoolOptions: undefined };
17
+ }
18
+ return credential;
19
+ }
20
+ async getConnection(credential) {
21
+ if (credential) {
22
+ POOL_ACTIVE.add(credential);
23
+ }
24
+ return this.client;
25
+ }
26
+ async close() {
27
+ return this.client.disconnect();
28
+ }
29
+ isEmpty() {
30
+ return this.closed;
31
+ }
32
+ get closed() {
33
+ return !this.client.isOpen;
34
+ }
35
+ }
36
+ module.exports = RedisPool;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/redis",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Redis client driver for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "types": "client/index.d.ts",
@@ -21,8 +21,8 @@
21
21
  "license": "MIT",
22
22
  "homepage": "https://github.com/anpham6/pi-r#readme",
23
23
  "dependencies": {
24
- "@e-mc/db": "^0.9.4",
25
- "@e-mc/types": "^0.9.4",
26
- "redis": "^4.6.14"
24
+ "@e-mc/db": "^0.10.0",
25
+ "@e-mc/types": "^0.10.0",
26
+ "redis": "^4.7.0"
27
27
  }
28
28
  }
package/types/index.d.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  import type { DbDataSource } from '@e-mc/types/lib/squared';
2
2
 
3
+ import type { IdentifierAction } from '@e-mc/types/lib/core';
3
4
  import type { CascadeAction, ServerAuth } from '@e-mc/types/lib/db';
4
5
 
5
- import type { CommandOptions } from '@redis/client/dist/lib/command-options';
6
6
  import type { ClientCommandOptions } from '@redis/client/dist/lib/client';
7
- import type { RedisClientOptions } from '@redis/client';
8
7
  import type { RedisCommandArgument } from '@redis/client/dist/lib/commands';
8
+ import type { CommandOptions } from '@redis/client/dist/lib/command-options';
9
+ import type { RedisClientOptions } from '@redis/client';
9
10
  import type { RedisJSON } from '@redis/json/dist/commands';
11
+ import type { AggregateOptions } from '@redis/search/dist/commands/AGGREGATE';
12
+ import type { HScanTuple } from '@redis//client/dist/lib/commands/HSCAN';
13
+ import type { ScanOptions } from '@redis/client/dist/lib/commands/generic-transformers';
10
14
  import type { RediSearchSchema, SearchOptions, SetOptions } from 'redis';
11
15
 
12
16
  export interface RedisDataSource extends DbDataSource<string, PlainObject, ArrayOf<RedisSetValue> | ArrayOf<RedisJSONValue>, RedisCredential, string>, CascadeAction, AuthValue {
@@ -14,15 +18,18 @@ export interface RedisDataSource extends DbDataSource<string, PlainObject, Array
14
18
  key?: ArrayOf<RedisCommandArgument>;
15
19
  field?: RedisCommandArgument;
16
20
  path?: string;
17
- format?: RedisFormat | "HKEYS" | "HVALS";
21
+ format?: RedisFormat | "HKEYS" | "HVALS" | "HSCAN";
18
22
  search?: RedisQuery;
19
23
  aggregate?: RedisQuery;
24
+ cursor?: ArrayOf<number>;
25
+ iterations?: ArrayOf<number>;
20
26
  options?: {
21
27
  client?: RedisClientOptions;
22
28
  command?: RedisCommandOptions;
23
29
  get?: PlainObject;
24
30
  search?: SearchOptions;
25
- aggregate?: PlainObject;
31
+ aggregate?: AggregateOptions;
32
+ scan?: ScanOptions;
26
33
  };
27
34
  database?: number;
28
35
  }
@@ -79,5 +86,6 @@ export type RedisExpireCondition = "NX" | "XX" | "GT" | "LT";
79
86
  export type RedisCommandValue = RedisCommandArgument | number;
80
87
  export type RedisCommandOptions = CommandOptions<ClientCommandOptions>;
81
88
  export type RedisHSETObject = Record<number | string, RedisCommandValue>;
89
+ export type DbPoolCredential = RedisClientOptions & IdentifierAction;
82
90
 
83
- export type { RedisCommandArgument, RedisJSON };
91
+ export type { HScanTuple, RedisCommandArgument, RedisJSON, ScanOptions };