@pi-r/redis 0.12.4 → 0.13.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
@@ -7,6 +7,7 @@ const { DB_TYPE, asFunction, coerceObject, errorMessage, isArray, isError, isObj
7
7
  const { parseConnectionString, parseServerAuth } = require('@e-mc/db/util');
8
8
  const DbPool = require('@pi-r/redis/client/pool');
9
9
  const POOL_STATE = {};
10
+ const CLIENT_RESP = process.env.NODE_REDIS_CLIENT_RESP === '2' ? 2 : process.env.NODE_REDIS_RESP === '3' ? 3 : 0;
10
11
  async function doScan(client, key, index, cursor = [], iterations = [], options) {
11
12
  const target = (Array.isArray(cursor) ? cursor : [cursor]).map(item => Buffer.isBuffer(item) ? item : item.toString());
12
13
  if (!Array.isArray(iterations)) {
@@ -21,6 +22,9 @@ async function doScan(client, key, index, cursor = [], iterations = [], options)
21
22
  return result;
22
23
  }
23
24
  const convertFloat = (value) => typeof value === 'number' || isString(value) && !isNaN(value = +value) ? value : undefined;
25
+ const isValue = (arg) => typeof arg === 'string' || Buffer.isBuffer(arg) || typeof arg === 'number';
26
+ const asString = (arg) => typeof arg === 'number' || Buffer.isBuffer(arg) ? arg.toString() : arg;
27
+ const isInt = (n) => typeof n === 'number' && n > 0;
24
28
  async function setCredential(item) {
25
29
  const credential = this.getCredential(item);
26
30
  const options = item.options ||= {};
@@ -29,6 +33,9 @@ async function setCredential(item) {
29
33
  if (database > 0) {
30
34
  client.database = database;
31
35
  }
36
+ if (CLIENT_RESP !== 0) {
37
+ client.RESP = CLIENT_RESP;
38
+ }
32
39
  if (credential) {
33
40
  const auth = parseServerAuth(credential, 6379);
34
41
  if (auth.database && (database = +auth.database) > 0) {
@@ -118,9 +125,9 @@ async function executeBatchQuery(batch, options = '', outResult) {
118
125
  if (length === 0) {
119
126
  return [];
120
127
  }
121
- let parallel, checkObject, connectOnce, errorQuery, sessionKey, outCacheMiss;
128
+ let parallel, checkObject, connectOnce, errorQuery, errorAsEmpty, sessionKey, outCacheMiss;
122
129
  if (isPlainObject(options)) {
123
- ({ parallel, checkObject, connectOnce, errorQuery, sessionKey, outCacheMiss } = options);
130
+ ({ parallel, checkObject, connectOnce, errorQuery, errorAsEmpty, sessionKey, outCacheMiss } = options);
124
131
  }
125
132
  else {
126
133
  if (typeof options === 'string') {
@@ -179,7 +186,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
179
186
  const item = batch[i];
180
187
  const { source, key, search, aggregate, streams, options: clientOptions = {}, ignoreCache } = item;
181
188
  let credential = redisCredential || onceCredential, error;
182
- if (!credential && !isPlainObject(credential = clientOptions.client) && (error = errorMessage(source, "Invalid credentials")) || !(isString(key) || isArray(key) || isPlainObject(search) && (isPlainObject(search.schema) || isString(search.query)) || isPlainObject(aggregate) && (isPlainObject(aggregate.schema) || isString(aggregate.query))) && (error = errorMessage(source, "Missing database query", item.uri))) {
189
+ if (!credential && !isPlainObject(credential = clientOptions.client) && (error = errorMessage(source, "Invalid credentials")) || !(isString(key) || isArray(key) || streams || isObject(search) && (isPlainObject(search.schema) || isString(search.query) || clientOptions.hybrid) || isObject(aggregate) && (isPlainObject(aggregate.schema) || isString(aggregate.query))) && (error = errorMessage(source, "Missing database query", item.uri))) {
183
190
  if (this.handleFail(error, item, { errorQuery })) {
184
191
  if (!parallel) {
185
192
  tasks.length = 0;
@@ -196,8 +203,10 @@ async function executeBatchQuery(batch, options = '', outResult) {
196
203
  continue;
197
204
  }
198
205
  item.transactionState = 1;
199
- const uuidKey = credential.uuidKey ||= (onceCredential ? batch[0] : item).credential?.uuidKey;
200
- const targetObject = typeof checkObject === 'string' ? this.hasCoerce("redis", 'options', uuidKey) ? asFunction(checkObject) : null : checkObject;
206
+ const ftQuery = isObject(search) ? search : isObject(aggregate) ? aggregate : null;
207
+ const hasCoerce = () => this.hasCoerce("redis", 'options', credential.uuidKey || (onceCredential ? batch[0] : item).credential?.uuidKey);
208
+ const ftType = () => ftQuery === search ? clientOptions.hybrid ? 'FT.HYBRID' : 'FT.SEARCH' : 'FT.AGGREGATE';
209
+ const targetObject = ftQuery || streams ? null : typeof checkObject === 'string' ? hasCoerce() ? asFunction(checkObject) : null : checkObject;
201
210
  const cacheValue = ignoreCache === undefined ? sessionKey : Array.isArray(ignoreCache) ? { sessionKey, exclusiveOf: ignoreCache } : { sessionKey, renewCache: ignoreCache === 0 };
202
211
  const format = (key ? item.format?.toUpperCase() || 'HASH' : undefined);
203
212
  let queryString = '', cacheCredential, rows;
@@ -212,15 +221,15 @@ async function executeBatchQuery(batch, options = '', outResult) {
212
221
  queryString = Db.asString(streams, true);
213
222
  }
214
223
  else if (key) {
215
- queryString = Db.asString(key, true) + '_' + format;
216
- if (targetObject && typeof item.cacheObjectKey === 'string') {
217
- queryString += '_' + targetObject.toString() + item.cacheObjectKey;
218
- }
224
+ queryString = Db.keyForResult(key, format);
219
225
  }
220
226
  cacheCredential = DbPool.sanitize(redisCredential || credential, sortKeys);
221
227
  if (ignoreCache !== 1 && !item.update) {
222
228
  rows = this.getQueryResult(source, cacheCredential, queryString, cacheValue);
223
229
  if (rows) {
230
+ if (targetObject) {
231
+ rows = targetObject(item, rows);
232
+ }
224
233
  if (parallel) {
225
234
  tasks[i] = Promise.resolve(rows);
226
235
  }
@@ -249,23 +258,20 @@ async function executeBatchQuery(batch, options = '', outResult) {
249
258
  let commandType;
250
259
  try {
251
260
  let client = redisClient || await getConnection(item, credential);
252
- const a = (arg) => typeof arg === 'string' || Buffer.isBuffer(arg) || typeof arg === 'number';
253
- const b = (arg) => typeof arg === 'number' || Buffer.isBuffer(arg) ? arg.toString() : arg;
254
261
  if (item.update) {
255
262
  commandType = this.commandType.UPDATE;
256
263
  const values = (Array.isArray(item.update) ? item.update : [item.update]).map(async (target) => {
257
264
  return new Promise(async (success, failed) => {
258
- let { key: k, value: v, format: f, NX, XX, options: setOptions = {} } = target;
265
+ let { key: k, value: v, format: f, NX, XX, options: hashOptions = {} } = target;
259
266
  f = (isString(f) ? f.toUpperCase() : 'HASH');
260
267
  if (v === undefined && f !== 'JSON') {
261
268
  success(target);
262
269
  return;
263
270
  }
264
- const has = (n) => typeof n === 'number' && n > 0;
265
271
  const failKey = () => failed(errorMessage(f, "Invalid key", Db.asString(k) || "Unknown"));
266
272
  const failValue = () => failed(errorMessage(f, "Invalid value", Db.asString(v) || "Unknown"));
267
- if (isPlainObject(setOptions.command)) {
268
- client = client.withCommandOptions(setOptions.command);
273
+ if (isPlainObject(hashOptions.command)) {
274
+ client = client.withCommandOptions(hashOptions.command);
269
275
  }
270
276
  let pending, codeMin = 0, EX, PX, EXAT, PXAT, KEEPTTL;
271
277
  if (f === 'JSON') {
@@ -273,7 +279,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
273
279
  failKey();
274
280
  return;
275
281
  }
276
- if (this.hasCoerce("redis", 'options', uuidKey) && isString(v) && v.startsWith('new')) {
282
+ if (isString(v) && v.startsWith('new') && hasCoerce()) {
277
283
  ({ outV: v } = coerceObject({ outV: v }));
278
284
  }
279
285
  const { path = '$', command: cmd, start, stop, index } = target;
@@ -301,7 +307,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
301
307
  case 'ARRINDEX': {
302
308
  const s1 = convertFloat(start);
303
309
  codeMin = -1;
304
- pending = client.json.arrIndex(k, path, v, s1 !== undefined ? { range: { start: s1, stop: convertFloat(stop) } } : undefined);
310
+ pending = client.json.arrIndex(k, path, v, typeof s1 === 'number' ? { range: { start: s1, stop: convertFloat(stop) } } : undefined);
305
311
  break;
306
312
  }
307
313
  case 'ARRLEN':
@@ -315,7 +321,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
315
321
  case 'ARRTRIM': {
316
322
  const s1 = convertFloat(start);
317
323
  const s2 = convertFloat(stop);
318
- if (s1 !== undefined && s2 !== undefined) {
324
+ if (typeof s1 === 'number' && typeof s2 === 'number') {
319
325
  pending = client.json.arrTrim(k, path, s1, s2);
320
326
  }
321
327
  break;
@@ -343,7 +349,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
343
349
  case 'NUMINCRBY':
344
350
  case 'NUMMULTBY': {
345
351
  const n = convertFloat(v);
346
- if (n !== undefined) {
352
+ if (typeof n === 'number') {
347
353
  pending = client.json[name === 'NUMINCRBY' ? 'numIncrBy' : 'numMultBy'](k, path, n);
348
354
  }
349
355
  break;
@@ -368,36 +374,44 @@ async function executeBatchQuery(batch, options = '', outResult) {
368
374
  ({ EX, PX, EXAT, PXAT, KEEPTTL } = target);
369
375
  const field = target.field;
370
376
  if (field == null) {
371
- if (a(v)) {
372
- const flags = setOptions.set || {};
373
- if (NX) {
374
- flags.condition = 'NX';
375
- }
376
- if (XX) {
377
- flags.condition = 'XX';
377
+ const applyFlags = (flags, condition) => {
378
+ if (condition) {
379
+ if (NX) {
380
+ flags.condition = 'NX';
381
+ }
382
+ if (XX) {
383
+ flags.condition = 'XX';
384
+ }
385
+ if (KEEPTTL) {
386
+ flags.expiration = { type: 'KEEPTTL' };
387
+ KEEPTTL = false;
388
+ }
378
389
  }
379
- if (has(EX)) {
390
+ if (isInt(EX)) {
380
391
  flags.expiration = { type: 'EX', value: EX };
381
392
  EX = 0;
382
393
  }
383
- if (has(PX)) {
394
+ if (isInt(PX)) {
384
395
  flags.expiration = { type: 'PX', value: PX };
385
396
  PX = 0;
386
397
  }
387
- if (has(EXAT)) {
398
+ if (isInt(EXAT)) {
388
399
  flags.expiration = { type: 'EXAT', value: EXAT };
389
400
  EXAT = 0;
390
401
  }
391
- if (has(PXAT)) {
402
+ if (isInt(PXAT)) {
392
403
  flags.expiration = { type: 'PXAT', value: PXAT };
393
404
  PXAT = 0;
394
405
  }
395
- if (KEEPTTL) {
396
- flags.expiration = { type: 'KEEPTTL' };
397
- KEEPTTL = false;
398
- }
406
+ return flags;
407
+ };
408
+ if (hashOptions.incrEx) {
399
409
  codeMin = NaN;
400
- pending = client.set(k, v, flags);
410
+ pending = typeof v === 'number' ? client.incrExByFloat(k, v, applyFlags(hashOptions.incrEx, false)) : client.increx(k, applyFlags(hashOptions.incrEx, false));
411
+ }
412
+ else if (isValue(v)) {
413
+ codeMin = NaN;
414
+ pending = client.set(k, v, applyFlags(hashOptions.set || {}, true));
401
415
  }
402
416
  else if (isObject(v)) {
403
417
  let valid = true;
@@ -414,18 +428,22 @@ async function executeBatchQuery(batch, options = '', outResult) {
414
428
  pending = client.hSet(k, v);
415
429
  }
416
430
  }
431
+ else if (isPlainObject(v)) {
432
+ codeMin = NaN;
433
+ pending = client.xAdd(k, asString(field), v, hashOptions.xAdd);
434
+ }
417
435
  else if (NX) {
418
- if (a(field) && a(v)) {
436
+ if (isValue(field) && isValue(v)) {
419
437
  codeMin = -1;
420
- pending = client.hSetNX(k, b(field), b(v));
438
+ pending = client.hSetNX(k, asString(field), asString(v));
421
439
  }
422
440
  }
423
441
  else {
424
442
  let valid = 1;
425
- if (XX && a(k) && a(field)) {
426
- valid = await client.hExists(b(k), b(field));
443
+ if (XX && isValue(k) && isValue(field)) {
444
+ valid = await client.hExists(k, asString(field));
427
445
  }
428
- if (a(field) && a(v)) {
446
+ if (isValue(field) && isValue(v)) {
429
447
  if (valid === 0) {
430
448
  success(target);
431
449
  return;
@@ -440,13 +458,13 @@ async function executeBatchQuery(batch, options = '', outResult) {
440
458
  }
441
459
  if (pending) {
442
460
  pending.then(async (code) => {
443
- if (code == null || isString(code) && code !== 'OK' && codeMin !== -Infinity || typeof code === 'number' && code <= codeMin || Array.isArray(code) && code.every(resp => resp === null || typeof resp === 'number' && resp <= codeMin)) {
461
+ if (code == null || isString(code) && !(code === 'OK' || codeMin === -Infinity || isPlainObject(v)) || typeof code === 'number' && code <= codeMin || Array.isArray(code) && code.every(resp => resp === null || typeof resp === 'number' && resp <= codeMin)) {
444
462
  failValue();
445
463
  return;
446
464
  }
447
465
  let ttl = false;
448
- if (!isNaN(codeMin) && (has(EX) || has(PX) || has(EXAT) || has(PXAT) || (ttl = !!KEEPTTL))) {
449
- const expire = setOptions.expire;
466
+ if (!isNaN(codeMin) && (isInt(EX) || isInt(PX) || isInt(EXAT) || isInt(PXAT) || (ttl = !!KEEPTTL))) {
467
+ const expire = hashOptions.expire;
450
468
  let mode;
451
469
  if (expire) {
452
470
  if (expire.NX) {
@@ -472,13 +490,13 @@ async function executeBatchQuery(batch, options = '', outResult) {
472
490
  return;
473
491
  }
474
492
  }
475
- (has(EX) ? client.expire(k, EX, mode) : has(PX) ? client.pExpire(k, PX, mode) : has(EXAT) ? client.expireAt(k, EXAT, mode) : client.pExpireAt(k, PXAT, mode))
493
+ (isInt(EX) ? client.expire(k, EX, mode) : isInt(PX) ? client.pExpire(k, PX, mode) : isInt(EXAT) ? client.expireAt(k, EXAT, mode) : client.pExpireAt(k, PXAT, mode))
476
494
  .then(valid => {
477
495
  if (valid) {
478
496
  success(target);
479
497
  }
480
498
  else {
481
- failed(errorMessage(f, "Invalid value", has(EX) ? 'EX: ' + EX : has(PX) ? 'PX: ' + PX : has(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
499
+ failed(errorMessage(f, "Invalid value", isInt(EX) ? 'EX: ' + EX : isInt(PX) ? 'PX: ' + PX : isInt(EXAT) ? 'EXAT: ' + EX : 'PXAT: ' + PXAT));
482
500
  }
483
501
  })
484
502
  .catch(failed);
@@ -515,17 +533,15 @@ async function executeBatchQuery(batch, options = '', outResult) {
515
533
  client = client.withCommandOptions(commandOptions);
516
534
  }
517
535
  commandType = this.commandType.SELECT;
518
- const target = isObject(search) ? search : isObject(aggregate) ? aggregate : null;
519
- if (target) {
520
- const { query = '', schema, index } = target;
521
- const ftType = () => target === search ? clientOptions.hybrid ? 'FT.HYBRID' : 'FT.SEARCH' : 'FT.AGGREGATE';
536
+ if (ftQuery) {
537
+ const { query = '', schema, index } = ftQuery;
522
538
  let idx = '';
523
539
  if (isObject(schema)) {
524
540
  idx = index || randomUUID();
525
- await client.ft.create(idx, schema, target.options);
541
+ await client.ft.create(idx, schema, ftQuery.options);
526
542
  }
527
543
  try {
528
- if (target === search) {
544
+ if (ftQuery === search) {
529
545
  if (clientOptions.hybrid) {
530
546
  const reply = await client.ft.hybrid(index || idx, clientOptions.hybrid);
531
547
  rows = reply.results;
@@ -557,10 +573,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
557
573
  }
558
574
  }
559
575
  else if (streams) {
560
- const data = await client.xRead(streams, clientOptions.xread);
561
- if (data) {
562
- rows = data;
563
- }
576
+ rows = await client.xRead(streams, clientOptions.xread);
564
577
  }
565
578
  else if (key) {
566
579
  let data;
@@ -579,7 +592,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
579
592
  data = await Promise.all(key.map(async (k) => client.sMembers(k)));
580
593
  break;
581
594
  case 'JSON':
582
- data = (await client.json.mGet(key.map(c => b(c)), item.path || '$')).flat();
595
+ data = (await client.json.mGet(key.map(c => asString(c)), item.path || '$')).flat();
583
596
  break;
584
597
  default:
585
598
  data = await client.mGet(key);
@@ -601,7 +614,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
601
614
  data = await client.sMembers(key);
602
615
  break;
603
616
  case 'JSON':
604
- data = await client.json.get(b(key), clientOptions.get);
617
+ data = await client.json.get(asString(key), clientOptions.get);
605
618
  break;
606
619
  default:
607
620
  if (Array.isArray(item.field)) {
@@ -616,13 +629,20 @@ async function executeBatchQuery(batch, options = '', outResult) {
616
629
  break;
617
630
  }
618
631
  }
619
- rows = (targetObject ? targetObject(item, data) : data);
632
+ rows = data;
620
633
  }
621
- if (rows == null) {
634
+ else {
622
635
  throw errorMessage(source, "Missing database query");
623
636
  }
637
+ if (rows == null) {
638
+ throw errorMessage(source, "Invalid key");
639
+ }
624
640
  this.add(item, 4);
625
- resolve(this.setQueryResult(source, cacheCredential, queryString, rows, cacheValue));
641
+ rows = this.setQueryResult(source, cacheCredential, queryString, rows, cacheValue);
642
+ if (targetObject) {
643
+ rows = targetObject(item, rows);
644
+ }
645
+ resolve(rows);
626
646
  }
627
647
  catch (err) {
628
648
  switch (isError(err) && err.message) {
@@ -655,6 +675,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
655
675
  }
656
676
  return this.processRows(batch, tasks, {
657
677
  parallel,
678
+ errorAsEmpty,
658
679
  disconnect() {
659
680
  for (const item of clients) {
660
681
  item.destroy();
package/client/pool.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const DbPool = require('@e-mc/db/pool');
4
4
  const POOL_ACTIVE = new WeakSet();
5
5
  class RedisPool extends DbPool {
6
+ static CACHE_UNUSED = ['pingInterval', 'clientSideCache', 'himportRegistry', 'disableClientInfo', 'clientInfoTag', 'emitInvalidate'];
6
7
  static CACHE_IGNORE = ['modules', 'functions', 'scripts', 'credentialsProvider'];
7
8
  static asString(credential) {
8
9
  if (credential.url) {
@@ -14,19 +15,25 @@ class RedisPool extends DbPool {
14
15
  if (!this.canCache(credential)) {
15
16
  return;
16
17
  }
17
- if (!POOL_ACTIVE.has(credential) || credential.uuidKey) {
18
+ if (credential.uuidKey) {
18
19
  return credential;
19
20
  }
20
- if (sort) {
21
- credential = super.sanitize(credential, true);
21
+ let result = super.sanitize(credential, sort);
22
+ const { socket, commandOptions } = result;
23
+ if (socket) {
24
+ if (result === credential) {
25
+ result = { ...credential };
26
+ }
27
+ result.socket = socket.tls ? { tls: true } : undefined;
22
28
  }
23
- if ('socket' in credential) {
24
- if (!sort) {
25
- credential = { ...credential };
29
+ if (commandOptions) {
30
+ if (result === credential) {
31
+ result = { ...credential };
26
32
  }
27
- credential.socket = credential.socket?.tls ? { tls: true } : undefined;
33
+ const { typeMapping, chainId, asap } = commandOptions;
34
+ result.commandOptions = typeMapping || chainId || typeof asap === 'boolean' ? { chainId, typeMapping, asap } : undefined;
28
35
  }
29
- return credential;
36
+ return result;
30
37
  }
31
38
  async getConnection(credential) {
32
39
  if (credential) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/redis",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
4
4
  "description": "Redis client driver for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "types": "client/index.d.ts",
@@ -17,8 +17,8 @@
17
17
  "license": "MIT",
18
18
  "homepage": "https://github.com/anpham6/pi-r#readme",
19
19
  "dependencies": {
20
- "@e-mc/db": "^0.14.5",
21
- "@e-mc/types": "^0.14.5",
22
- "redis": "^5.12.1"
20
+ "@e-mc/db": "^0.14.6",
21
+ "@e-mc/types": "^0.14.6",
22
+ "redis": "^6.2.1"
23
23
  }
24
24
  }
package/types/index.d.ts CHANGED
@@ -8,6 +8,9 @@ import type { RedisClientOptions } from '@redis/client';
8
8
  import type { RedisJSON } from '@redis/json/dist/lib/commands';
9
9
  import type { RedisArgument } from '@redis/client/dist/lib/RESP/types';
10
10
  import type { CommandOptions } from '@redis/client/dist/lib/client/commands-queue';
11
+ import type { XAddOptions } from '@redis/client/dist/lib/commands/XADD';
12
+ import type { IncrExOptions } from '@redis/client/dist/lib/commands/INCREX';
13
+ import type { IncrExByFloatOptions } from '@redis/client/dist/lib/commands/INCREXBYFLOAT';
11
14
  import type { ScanOptions } from '@redis/client/dist/lib/commands/SCAN';
12
15
  import type { CreateOptions } from '@redis/search/dist/lib/commands/CREATE';
13
16
  import type { FtAggregateOptions } from '@redis/search/dist/lib/commands/AGGREGATE';
@@ -51,11 +54,17 @@ export interface RedisCommand<T = "HASH" | "JSON" | undefined, U = unknown, V =
51
54
  XX?: boolean;
52
55
  options?: {
53
56
  set?: SetOptions;
57
+ incrEx?: IncrExOptions | IncrExByFloatOptions;
58
+ xAdd?: XAddOptions;
54
59
  expire?: { [K in RedisExpireCondition]?: boolean; };
55
60
  command?: CommandOptions;
56
61
  };
57
62
  }
58
63
 
64
+ export interface RedisAddValue extends RedisCommand<"HASH", RedisArgument, StandardMap> {
65
+ field?: RedisArgument;
66
+ }
67
+
59
68
  export interface RedisSetValue extends RedisCommand<"HASH", RedisArgument, RedisCommandValue | RedisHSETObject> {
60
69
  field?: RedisCommandValue | RedisHSETObject;
61
70
  EX?: number;