@pi-r/redis 0.12.5 → 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) {
@@ -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) || streams || isPlainObject(search) && (isPlainObject(search.schema) || isString(search.query) || clientOptions.hybrid) || 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;
@@ -576,7 +592,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
576
592
  data = await Promise.all(key.map(async (k) => client.sMembers(k)));
577
593
  break;
578
594
  case 'JSON':
579
- 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();
580
596
  break;
581
597
  default:
582
598
  data = await client.mGet(key);
@@ -598,7 +614,7 @@ async function executeBatchQuery(batch, options = '', outResult) {
598
614
  data = await client.sMembers(key);
599
615
  break;
600
616
  case 'JSON':
601
- data = await client.json.get(b(key), clientOptions.get);
617
+ data = await client.json.get(asString(key), clientOptions.get);
602
618
  break;
603
619
  default:
604
620
  if (Array.isArray(item.field)) {
@@ -613,13 +629,20 @@ async function executeBatchQuery(batch, options = '', outResult) {
613
629
  break;
614
630
  }
615
631
  }
616
- rows = (targetObject ? targetObject(item, data) : data);
632
+ rows = data;
617
633
  }
618
- if (rows == null) {
634
+ else {
619
635
  throw errorMessage(source, "Missing database query");
620
636
  }
637
+ if (rows == null) {
638
+ throw errorMessage(source, "Invalid key");
639
+ }
621
640
  this.add(item, 4);
622
- 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);
623
646
  }
624
647
  catch (err) {
625
648
  switch (isError(err) && err.message) {
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.5",
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",
@@ -19,6 +19,6 @@
19
19
  "dependencies": {
20
20
  "@e-mc/db": "^0.14.6",
21
21
  "@e-mc/types": "^0.14.6",
22
- "redis": "^5.12.1"
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;